Source code for sevnpy.sevn.star

"""
==============================================================
Star , (:mod:`sevnpy.sevn.star`)
==============================================================

This module contains the class Star. It is used to initialise stars and evolve
them using the SEVN backend.

"""

from __future__ import annotations
import warnings
import numpy as np
import pandas as pd
import copy

from scipy.interpolate import interp1d

try:
    from . import sevnwrap as sw
except:
    raise ImportError("The sevnwrap is not installed")
from ..sevnpy_types import Number, Optional, Union, Dict, Any, Tuple, ListLikeType
from .sevnmanager import SEVNmanager
from .. import utility as ut
from ..io.logreader import readlogstring


def _check_masses(Mass: Optional[float] = None, MHE: Optional[float] = None, MCO: Optional[float] = None):
    """
    Auxiliary function for the class Star to check the input modifications to the masses

    Raises
    ------
    ValueError
        If the Mass hierarchy is not satisfied (Mass, MHE, MCO), a mass type cannot be not None if the precedent
        mass type is None. The error is raised also if the mass is larger than the preceded mass type

    """
    # Check hierarchy: MHE and MCO not None, only if Mass is not None. MCO not None, only if MHE not None
    if Mass is None and (MHE is not None or MCO is not None):
        raise ValueError("In Star initialisation the input Mass is None, but MHE or MCO is not None,"
                         " this is not allowed")
    if MHE is None and MCO is not None:
        raise ValueError("In Star initialisation the input MHE is None, but MCO is not None,"
                         " this is not allowed")
    # Check problems
    if Mass is not None and MHE is not None:
        if MHE > Mass:
            raise ValueError(f"In Star initialisation the input Mass ({Mass}) is larger "
                             f"than helium core mass ({MHE}), this is not allowed")
    if MCO is not None:
        if MCO > MHE:
            raise ValueError(f"In Star initialisation the input MHE ({MHE}) is larger "
                             f"than CO core mass ({MCO}), this is not allowed")

    return True


[docs] class Star: """ The class Star is used to initialise a star and evolve it using the SEVN backend. Initilisation -------------- The basic parameter used to initialise the star are the zero-age-main-seqeunce mass Mzams and Z. These two values identify a unique interpolating track (see the SEVN userguide) for the evolution. The user need also to define the initial age of the star (set by defaul to "zams"). In addition, the used can set the initial stellar rotation, the snmodel used to create the stellar remnant. The user can also decide to modify the stadandard property of the interpolating track setting the current value of the total mass and mass of the cores. In addition to a normal H-star, the parameter star_flag can be used to intialise a pureHe star (in this case the Mzams value is the mass of the star at the beginning of the core helium burning), or a remnant such as a black hole, a neutron star or a whith dwarf (in this case Mzams represents the mass of the remnant). See the :func:`~Star.__init__` method for additional details. The Star can also be initialised from the current properties of another star using the class method :func:`~Star.from_star`. Similarly a star can create a new Star instance from the current properties using the method :func:`~Star.to_star` Any instance of the class Star needs to be created within an initialised SEVNmanager, otherwise an error is raised, example: >>> from sevnpy.sevn import Star, SEVNmanager >>> s=Star(10,0.02) # !ERROR, SEVNmanager is not initialised yet >>> SEVNmanager.init() >>> s=Star(10,0.02) # !OK, SEVNmanager is initiliased When a Star instance is created it get the SEVNparameters from the current SEVNmanager initialisation. The Star evolution methods can be call only within the same SEVNmanager session, is the SEVNmanager is re-intialised any further evolution will raise an error, e.g. >>> from sevnpy.sevn import Star, SEVNmanager >>> # OK! >>> SEVNmanager.init() >>> s=Star(10,0.02) >>> s.evolve() >>> SEVNmanager.close() >>> # Error! >>> SEVNmanager.init({"ce_alpha:2"}) >>> s.evolve() #Error, the SEVNmanager has been reinitialised with respect to the Star initialisation Get the stellar properties -------------------------- The stellar properties are stored in an internal evolution table reporting the star properties at each time step. After the initilisation the evolution table contains one single row storing the initial stellar properties. The methods :func:`~Star.getp` and :func:`~Star.getp_array` can be used to retrieve the stellar properties as a pandas DataFrame and a numpy array, respectively. As a shortchut the :func:`~Star.getp` method can be called directly using the [] operator on the class instance, e.g. >>> s=Star(10,0.02) >>> s["Mass"] #return the Mass column from the current evolved Dataframe >>> s["Worldtime","Mass","Radius"] #return the Worldtime,Mass and Radius columns from the current evolved Dataframe Single stellar evolution ------------------------ The main purpose of the class Star is to faciliate the call of the SEVN evolution backend. this is done through the methods :func:`~Star.evolve` and :func:`~Star.evolve_for`: - :func:`~Star.evolve`: Evolve the Star from the initial age (the one set at the initilisation), up to a given time in input. During the evolution, the internal evolution table is filled with properties at each timestep. Each time the method is called, the evolve_table will be overwritten. - :func:`~Star.evolve_for`: Evolve the Star for an amount of time in input starting from the current properties of the star, e.g. the last row in the evolution table. This method do not overwrite the evolution table rather it appends the new value to it. When the star is evolved, the log messages (see SEVN documentation) are also internally store. To retrieve it use the property :func:`~Star.log`. Same as for the evolution table, each call to :func:`~Star.evolve` ovewrites the log, while calls to :func:`~Star.evolve_for` append their log messages. Remnant -------- It is possibile to retreive directly the property of the remnant generated by the Star using the method :func:`~Star.get_remnant`. Internally a complete evolution will be performed, but whitout updating or overwriting the evolution table, therefore the Star status will not be changed. The method returns a DataFrame with all the properties of the remnant. """ _static_ID = 0 # static counter to generate IDs
[docs] def __init__(self, Mzams: Number, Z: Number, spin: Number = 0, tini: Union[Number, str] = "zams", snmodel: str = "rapid_gauNS", star_flag: str = "H", rseed: Optional[int] = None, ID: Optional[int] = None, Mass: Optional[Number] = None, MHE: Optional[Number] = None, MCO: Optional[Number] = None, Radius: Optional[Number] = None ): """ Initialise a Star instance. During the initialisation, the evolve table will be filled with one single row containing the initial Stellar properties. Parameters ---------- Mzams: Zero-age-main-sequence mass of the star (or better of the interpolating track) [Msun] Z: Metallicity of the star (or better of the interpolating track) spin: Initial stellar spin, i.e. the ratio between the rotational angular velocity and the critical angular velocity tini: Initial age of the star to initialise, the options are: - age number, a float in Myr - phase initialisation, initialise the star at a given phase, using the string: - *zams*: Zero age main sequence - *tams*: Terminal main sequence - *shb*: shell H burning - *cheb*: core helium burning - *tcheb*: terminal core helium burning - *sheb*: shel He burning - percentage phase initialisation, using the string *%<P>:<IDphase>* where *<P>* is the percentage of the phase, and *<IDphase>* is the integer ID depending on the phase: - 1: *zams* - 2: *tams* - 3: *shb* - 4: *cheb* - 5: *tcheb* - 6: *sheb* so, for example to initialise the Star at 48% of the cheb phase: *%48:4* snmodel: SEVN snmodel to use to transform a Star to a remnant (see the SEVN userguide) star_flag: String that defines the type of Star: - *H*: initialise an Hydrogen star - *HE*: initialise a pureHe star (use the pureHe trakcs, see the SEVN userguide) - *HEWD*: initialise a Helium White Dwarf remnant with mass equal to Mzams - *COWD*: initialise a Carbon-Oxygen White Dwarf remnant with mass equal to Mzams - *ONEWD*: initialise a Oxygen-Neon White Dwarf remnant with mass equal to Mzams - *NS*: initialise a Neutron Star with mass equal to Mzams - *BH*: initialise a Black Hole with mass equal to Mzams rseed: Random seed to be used in the stellar evolution, if None a random seen will be automatically generated ID: ID of the Star, if None a ID will be automatically assigned Mass: Use this value to modify the initial Mass of the star [Msun]. If None the Mass will be the one from the interpolating track. If star_flag="HE", to modify the total mass use Mass instead of MHE MHE: Use this value to modify the initial helium core mass (MHE) of the star [Msun]. If None MHE will be the one from the interpolating track. MHE can be not None only if Mass is not None. It cannot be larger than Mass, if equal to Mass a pureHe star will be initialised (forcing the star_model to be "HE"). MCO: Use this value to modify the initial carbon-oxygen core mass (MCO) of the star [Msun]. If None MCO will be the one from the interpolating track. MCO can be not None only if MHE is not None. It cannot be larger than MHE. Radius: Use this value to modify the initial value of the stellar radius [Rsun]. .. note:: If you want to initialise a pureHe star with a custom initial mass use the star_flag="HE" and set the initial mass with the parameter Mass (not MHE) E.g. to initialise a pureHE star following the interpolating track with Zams mass 30 Msun, Z =0.01 but with initial Mass 25 Msun (instead of 30 Msun). >>> s=Star(Mzams=30, Z=0.01, tini="cheb", star_flag="HE", Mass=30) Raises ------ ValueError If the hierarchy of the mass in input is not satisfied (Mass, MHE, MCO), a mass type cannot be not None if the precedent mass type is None. The error is raised also if the mass is larger than the preceded mass type """ # Check_masses _check_masses(Mass, MHE, MCO) # Check star_flag if Mass is not None and MHE is not None: if ut.check_equality(Mass, MHE) and star_flag != "HE": star_flag = "HE" warnings.warn(f"In star initialisation the input Mass ({Mass}) is equal to " f"the helium core mass ({MHE}), the Star will be initialised as pureHE star") elif star_flag == "HE" and Mass > MHE: star_flag = "H" warnings.warn(f"In star initialisation the star_flag is HE, but input Mass ({Mass}) is larger than " f"the helium core mass ({MHE}), the Star will be initialised as a H star") elif Mass is not None and star_flag == "HE": MHE = Mass self._Mzams = Mzams self._Z = Z self._spin = spin self._tini = tini self._massini = Mass self._mheini = MHE self._mcoini = MCO self._radiusini = Radius self._snmodel = snmodel self._star_flag = star_flag self._evolve_counter = 0 self._properties_interpolators = {"last_counter": 0} if rseed is None: self._rseed = np.random.randint(1, int(1E15)) else: self._rseed = rseed self._SEVNmanager_ID = None self._used_sevnParams = None self._evolve_dataframe = pd.DataFrame() self._tlife = None self._name = None self._log = None # Set ID if ID is None: self._ID = Star._static_ID Star._static_ID += 1 else: self._ID = ID # Initialise the star to the starting value self._goto(t=tini, Mass=self._massini, MHE=self._mheini, MCO=self._mcoini, Radius=self._radiusini) self._tlife_at_initialisation = self._tlife # Same as tini but never change (unless a reinitialisation is called)
[docs] @classmethod def from_star(cls, star: Star, ID: Optional[int] = None, rseed: Optional[Union[int, str]] = "same") -> Star: """ Construct a new Star instance from the current state (i.e. the last points in the current evolution dataframe) of a Star in input Parameters ---------- star: An instance of class :class:`Star`. The new instance will be created using the current properties of star, i.e., the properties in the last row of the evolution dataframe. ID: The ID of the new star, if None automatically generate one from a static ID counter rseed: The random seed of the new star, if equal to the string *same* get the random seed from star Returns ------- new_star : Star a new instance of the class :class:`Star` """ # Get star properties zams, spin, tini, tnow, mass, mhe, mco, rad, remtype = star.getp_array( properties=["Zams", "Spin", "Localtime", "Worldtime", "Mass", "MHE", "MCO", "Radius", "RemnantType"], mode="last") if rseed is None or isinstance(rseed, int): pass elif rseed == "same": rseed = star.rseed else: raise ValueError(f"Rseed {rseed} not allowed") return Star(Mzams=zams, Z=star.Zmet, spin=spin, tini=tini, snmodel=star.snmodel, star_flag=star.star_flag, rseed=rseed, ID=ID, Mass=mass, MHE=mhe, MCO=mco, Radius=rad )
[docs] def to_star(self, ID: Optional[int] = None, rseed: Optional[Union[int, str]] = "same") -> Star: """ Create a new Star instance from the current state (i.e. the last points in the current evolution dataframe) Parameters ---------- star: An instance of class :class:`Star`. The new instance will be created using the current properties of star, i.e., the properties in the last row of the evolution dataframe. ID: The ID of the new star, if None automatically generate one from a static ID counter rseed: The random seed of the new star, if equal to the string *same* get the random seed from star Returns ------- new_star : Star a new instance of the class :class:`Star` """ return self.from_star(self, ID, rseed)
def _evolve_basic(self, tstart: Union[str, Number] = "zams", tend: Union[str, Number] = "end", just_init: bool = False, Mass: Optional[Number] = None, MHE: Optional[Number] = None, MCO: Optional[Number] = None, Radius: Optional[Number] = None, Mzams: Optional[Number] = None, spin: Optional[Number] = None) -> Tuple[Dict, Dict]: """ This function represent the interface to call the function evolve_star from the SEVN C++ wrapper. Every evolve-like call, even the one used just to initialise the star need to pass through this function since in addition to the evolve call it sets some other class quantities. The initialised variable Z cannot be change Parameters ---------- tstart: Initial age of the star to initialise, the options are: - age number, a float in Myr - phase initialisation, initialise the star at a given phase, using the string: - *zams*: Zero age main sequence - *tams*: Terminal main sequence - *shb*: shell H burning - *cheb*: core helium burning - *tcheb*: terminal core helium burning - *sheb*: shel He burning - percentage phase initialisation, using the string *%<P>:<IDphase>* where *<P>* is the percentage of the phase, and *<IDphase>* is the integer ID depending on the phase: - 1: *zams* - 2: *tams* - 3: *shb* - 4: *cheb* - 5: *tcheb* - 6: *sheb* so, for example to initialise the Star at 48% of the cheb phase: *%48:4* tend: Stopping time of the simulation, can be a number in Myr or the word end. If end, the evolution will stop when a remnant is generated just_init: If True the star will be just initialised and the evolution is not called, i.e. the results will just store the initialisation values. In this case, the parameter tend will be not considered. Mass: Use this value to modify the initial Mass of the star [Msun]. If None the Mass will be the one from the interpolating track. MHE: Use this value to modify the initial helium core mass (MHE) of the star [Msun]. If None MHE will be the one from the interpolating track. MHE can be not None only if Mass is not None. It cannot be larger than Mass, if equal to Mass a pureHe star will be initialised (forcing the star_model to be "HE"). MCO: Use this value to modify the initial carbon-oxygen core mass (MCO) of the star [Msun]. If None MCO will be the one from the interpolating track. MCO can be not None only if MHE is not None. It cannot be larger than MHE. Radius: Use this value to modify the initial value of the stellar radius [Rsun]. Mzams: Zero-age-main-sequence mass of the interpolating track [Msun]. I Mzams is None use the one defined at the star initialisation spin: Initial stellar spin, i.e. the ratio between the rotational angular velocity and the critical angular velocity. If None use the one defined at the star initialisation Returns ------- evolution_results: Dictionary Dictionary containing the evolution results. Each key is a star property and each item is a numpy array storing the given properties during the stellar evolution additiona_evolution_info: Dictionary Dictionary storing additional evolution info such as the name assigned to the star, the logfile, etc.. """ # Check_masses _check_masses(Mass, MHE, MCO) star_flag = self._star_flag if Mass is not None and MHE is not None: if ut.check_equality(Mass, MHE) and star_flag != "HE": star_flag = "HE" warnings.warn(f"In Star::_evolve_basic the input Mass ({Mass}) is equal to " f"the helium core mass ({MHE}), the Star will be initialised as pureHE star") if Mzams is None: Mzams = self._Mzams if spin is None: spin = self._spin self._check_SEVNmanager_synchronisation() results, extra_info = sw.evolve_star(Mzams=Mzams, Z=self._Z, spin=spin, tstart=tstart, tend=tend, star_flag=star_flag, snmodel=self._snmodel, rseed=self._rseed, Mass=Mass, MHE=MHE, MCO=MCO, Radius=Radius, just_init=just_init, ) self._after_evolve_duties() # Increment counters and update other private variables return results, extra_info def _goto(self, t: Union[str, Number], Mass: Optional[Number] = None, MHE: Optional[Number] = None, MCO: Optional[Number] = None, Radius: Optional[Number] = None): """ Auxiliary function to initialise the property of the Star """ results, extra_info = self._evolve_basic(tstart=t, Mass=Mass, MHE=MHE, MCO=MCO, Radius=Radius, just_init=True) # Update and overwrite quantities self._evolve_dataframe = pd.DataFrame(results) self._name = extra_info["name"] self._log = extra_info["Log"] self._tlife = extra_info["tlife"]
[docs] def evolve(self, tend: Union[str, Number] = "end"): """ Main evolve function. The star is evolved from their initial age (set at the initialisation) to the value in input. .. warning:: Each time the evolve function is called, the past evolution of the Star is overwritten. If you want to continue the evolution from the current Star state use the method :func:`~Star.evolve_for` Parameters ---------- tend: Stopping time of the simulation, can be a number in Myr or the word *end*. If *end*, the evolution will stop when a remnant is generated """ results, extra_info = self._evolve_basic(tstart=self._tini, tend=tend, just_init=False, Mass=self._massini, MHE=self._mheini, MCO=self._mcoini, Radius=self._radiusini, ) # Update and overwrite quantities self._evolve_dataframe = pd.DataFrame(results) self._name = extra_info["name"] self._log = extra_info["Log"] self._tlife = extra_info["tlife"]
[docs] def evolve_for(self, dt: Number): """ Evolve the star from the current status for a given time interval. Parameters ---------- dt: Evolution time interval [Myr] Examples -------- It is possible to use evolve_for to perform a step-by-step stellar evolution directly in Python. For example assume that we want to evolve a star for 10 Myr and get the properties each 1 Myr >>> from sevnpy.sevn import SEVNmanager, Star >>> import pandas as pd >>> SEVNmanager.init() >>> s1=Star(10,0.02) >>> res_df = s1.getp(mode="last") #Store initial properties >>> t=0 >>> dt=1 >>> while t<10: >>> s1.evolve_for(dt) >>> res_df = pd.concat(res_df, s1.getp(mode="last")) >>> t+=dt >>> SEVNmanager.close() """ # Get current properties zams, spin, tini, tnow, mass, mhe, mco, rad, phase = self.getp_array( properties=["Zams", "Spin", "Localtime", "Worldtime", "Mass", "MHE", "MCO", "Radius", "Phase"], mode="last") # Set star flag to deal with remnants old_star_flag = self._star_flag self._star_flag = self.star_flag # Deal with remnant if int(phase) == 7: zams = mass mass = None mco = None mhe = None rad = None results, extra_info = self._evolve_basic(tstart=tini, tend=dt, just_init=False, Mass=mass, MHE=mhe, MCO=mco, Radius=rad, Mzams=zams, spin=spin ) # Reset starflag self._star_flag = old_star_flag # Update quantities (not overwrite, except for tlife) current_results = pd.DataFrame(results).iloc[1:] current_results["Worldtime"] = current_results["Worldtime"] + tnow if int(phase) == 7: current_results["Zams"] = zams current_results["Localtime"] = current_results["Worldtime"] self._evolve_dataframe = pd.concat([self._evolve_dataframe, current_results]) self._log += extra_info["Log"] self._tlife = extra_info["tlife"]
[docs] def get_remnant(self) -> pd.DataFrame: """ Get the property of the remnant. This method retrieve the properties of the remnant produced by evolving the star from the properties set at the star initialization, therefore it neglect the current status of the star. Returns ------- remnant: pandas DataFrame A Pandas DataFrame containing the remnant properties .. note:: Even if the method will internally evolve the star, the results of this evolution will not be stored in the class attribute, but only returned by the method """ results, extra_info = self._evolve_basic(tstart=self._tini, tend="end", just_init=False, Mass=self._massini, MHE=self._mheini, MCO=self._mcoini, Radius=self._radiusini, ) return pd.DataFrame(results).tail(1)
[docs] def look_at_track(self, t: Union[str, Number]) -> pd.DataFrame: """ The method returns the properties of the *interpolating tracks* followed by the star at a given time t. Since this will just consider the interpolating track, the initialisation properties Mass, MHE and MCO will not be considered. Parameters ---------- t: Time in Myr at which look for the properties of the interpolating track Returns ------- star_properties: pandas DataFrame A Pandas DataFrame containing the properties of the interpolating tracks at time t. If the time t is larger than the stellar lifetime, the properties of the generated remnant is returned (see :func:`~Star.get_remnant`) .. warning:: The time inserted in this method refers to the Localtime, while the time used in :func:`~Star.evolver` and :func:`~Star.evolve_for` refers to the Worldtime """ if t >= self._tlife_at_initialisation: warnings.warn("The input t in look_at_tracks is larger than the life time, a remnant is returned") # Save the initial mass values _Mass, _MHE, _MCO = self._massini, self._mheini, self._mcoini # Set the None to call remnant self._massini = None self._mheini = None self._mcoini = None ret = self.get_remnant() # Restore values self._massini = _Mass self._mheini = _MHE self._mcoini = _MCO ret["Worldtime"] = t return ret else: results, _ = sw.evolve_star(Mzams=self._Mzams, Z=self._Z, spin=self._spin, tstart=t, tend="end", star_flag=self._star_flag, snmodel=self._snmodel, rseed=self._rseed, Mass=None, MHE=None, MCO=None, Radius=None, just_init=True, ) ret = pd.DataFrame(results) ret["Worldtime"] = t return ret
[docs] def reinit(self, spin: Optional[float] = None, tini: Optional[Union[str, Number]] = None, snmodel: Optional[str] = None, Mass: Optional[Number] = None, MHE: Optional[Number] = None, MCO: Optional[Number] = None, Radius: Optional[Number] = None): """ Use this method to re-initialise the star changing the initial properties, except for Mzams and Z. If you need to change Mzams and Z create a new star. Parameters ----------- spin: Initial stellar spin, i.e. the ratio between the rotational angular velocity and the critical angular velocity tini: Initial age of the star to initialise, the options are: - age number, a float in Myr - phase initialisation, initialise the star at a given phase, using the string: - *zams*: Zero age main sequence - *tams*: Terminal main sequence - *shb*: shell H burning - *cheb*: core helium burning - *tcheb*: terminal core helium burning - *sheb*: shel He burning - percentage phase initialisation, using the string *%<P>:<IDphase>* where *<P>* is the percentage of the phase, and *<IDphase>* is the integer ID depending on the phase: - 1: *zams* - 2: *tams* - 3: *shb* - 4: *cheb* - 5: *tcheb* - 6: *sheb* so, for example to initialise the Star at 48% of the cheb phase: *%48:4* snmodel: SEVN snmodel to use to transform a Star to a remnant (see the SEVN userguide) star_flag: String that defines the type of Star: - *H*: initialise an Hydrogen star - *HE*: initialise a pureHe star (use the pureHe trakcs, see the SEVN userguide) - *HEWD*: initialise a Helium White Dwarf remnant with mass equal to Mzams - *COWD*: initialise a Carbon-Oxygen White Dwarf remnant with mass equal to Mzams - *ONEWD*: initialise a Oxygen-Neon White Dwarf remnant with mass equal to Mzams - *NS*: initialise a Neutron Star with mass equal to Mzams - *BH*: initialise a Black Hole with mass equal to Mzams rseed: Random seed to be used in the stellar evolution, if None a random seen will be automatically generated ID: ID of the Star, if None a ID will be automatically assigned Mass: Use this value to modify the initial Mass of the star [Msun]. If None the Mass will be the one from the interpolating track. If star_flag="HE", to modify the total mass use Mass instead of MHE MHE: Use this value to modify the initial helium core mass (MHE) of the star [Msun]. If None MHE will be the one from the interpolating track. MHE can be not None only if Mass is not None. It cannot be larger than Mass, if equal to Mass a pureHe star will be initialised (forcing the star_model to be "HE"). MCO: Use this value to modify the initial carbon-oxygen core mass (MCO) of the star [Msun]. If None MCO will be the one from the interpolating track. MCO can be not None only if MHE is not None. It cannot be larger than MHE. Radius: Use this value to modify the initial value of the stellar radius [Rsun]. """ self.__init__(Mzams=self._Mzams, Z=self._Z, spin=self._spin if spin is None else spin, tini=self._tini if tini is None else tini, snmodel=self._snmodel if snmodel is None else snmodel, star_flag=self._star_flag, rseed=self._rseed, ID=self._ID, Mass=self._massini if Mass is None else Mass, MHE=self._mheini if MHE is None else MHE, MCO=self._mcoini if MCO is None else MCO, Radius=self._radiusini if Radius is None else Radius )
[docs] def getp(self, properties: Optional[Union[str, ListLikeType]] = None, mode: str = "all", t: Optional[Number, ListLikeType] = None) -> pd.DataFrame: """ Return the stellar properties as a Pandas DataFrame. Parameters ---------- properties: single property name or list of property names (see SEVN documentation for the property names). If None return all the available properties. mode: Set the return type: - *all*: return all the timesteps for the stellar evolution - *last*: return just the current Stellar properties (last row form the evolution tables) - *first*: return the initial stellar properties, i.e. the properties set at the initialisation t: If not None, overwrite the mode property and return the properties at time specified by the input value(s) In order to return the values at time not stored in the evolution table, the proeprties are interpolated linearly Returns ------- star_properties: pandas DataFrame A pandas dataframe containing the values of the properties chosen in input Raises ------ RuntimeError If the evolution table is empty Examples --------- >>> s = Star(10,0.02) >>> s.evolve() >>> dfevolve = s.getp(mode="all") #Get the complete evolution table >>> dfevolve = s.getp(mode="last") #Get all the current properties >>> t = np.linspace(0.1,10) >>> dfevolve = s.getp(properties=["Worldtime","Mass","Radius"], t=t) #Get the evolution of time mass and radius >>> #at given t .. warning:: Use the t option wisely. The interpolation consists on a simple 1D interpolation, so it is not comparable with the detailed interpolation implemente in SEVN. THerefore, although the SEVN adaptive timestep catches all the important changes during the evolution do not bindly rely on the results obtain from this method when t is used (if t=None, the results will be robust since they are the one derived by SEVN directly), """ if self._evolve_dataframe.empty: raise RuntimeError("Cannot retrieve stellar property with get without " "at least one evolution call") if properties is None: properties = self._evolve_dataframe.columns properties = np.atleast_1d(properties) if t is not None: t = np.atleast_1d(t) self._set_property_interpolator() df = pd.DataFrame({prop: self._properties_interpolators[prop](t) for prop in properties}) return df elif mode == "all": return self._evolve_dataframe[properties] elif mode == "last": return self._evolve_dataframe[properties].tail(1) elif mode == "first": return self._evolve_dataframe[properties].head(1)
[docs] def getp_array(self, properties: Optional[Union[str, ListLikeType]] = None, mode: str = "all", t: Optional[Number, ListLikeType] = None) -> np.ndarray[np.float]: """ Return the stellar properties as a numpy array. Parameters ---------- properties: single property name or list of property names (see SEVN documentation for the property names). If None return all the available properties. mode: Set the return type: - *all*: return all the timesteps for the stellar evolution - *last*: return just the current Stellar properties (last row form the evolution tables) - *first*: return the initial stellar properties, i.e. the properties set at the initialisation t: If not None, overwrite the mode property and return the properties at time specified by the input value(s) In order to return the values at time not stored in the evolution table, the proeprties are interpolated linearly Returns ------- star_properties: numpy array A numpy array containing the values of the properties chosen in input. The shape depends on the input data and mode option: - If mode="all" or t is not None, 2D array with the is equal to NxC, where N is the number of row in the evolved dataframe and C are the number of properties in input. - if mode="last" or mode="first", 1D array with length=C, where C are the number of properties in input. Raises ------ RuntimeError If the evolution table is empty Examples --------- >>> s = Star(10,0.02) >>> s.evolve() >>> dfevolve = s.getp_array(mode="all") #Get the complete evolution table >>> dfevolve = s.getp_array(mode="last") #Get all the current properties >>> t = np.linspace(0.1,10) >>> dfevolve = s.getp_array(properties=["Worldtime","Mass","Radius"], t=t) #Get the evolution of time mass and radius >>> #at given t .. warning:: Use the t option wisely. The interpolation consists on a simple 1D interpolation, so it is not comparable with the detailed interpolation implemente in SEVN. THerefore, although the SEVN adaptive timestep catches all the important changes during the evolution do not bindly rely on the results obtain from this method when t is used (if t=None, the results will be robust since they are the one derived by SEVN directly), """ retarray = self.getp(properties=properties, mode=mode, t=t).values if t is not None: return retarray elif mode=="all": return retarray #if retarray.shape[1] == 1: retarray = retarray[:,0] if len(retarray) == 1 \ or (len(retarray.shape) == 2 and retarray.shape[1] == 1): retarray = retarray[0] #if len(retarray) == 1: retarray = retarray[0] return retarray
@property def pnames(self) -> List: """ List of names of available properties """ return self.getp().keys() @property def Zmet(self) -> float: """ Stellar metallicity in input """ return self._Z @property def snmodel(self) -> str: """ SEVN model used to pass from a Star to a remnant (see the SEVN documentation) """ return self._snmodel @property def tlife(self) -> float: """ Star lifetime based on the Mzams and Z in input [Myr] """ return self._tlife @property def rseed(self) -> int: """ Random seed used in the evolution """ return self._rseed @property def name(self) -> float: """ Unique identifier of this object. """ if self._name is None: warnings.warn("Star name is set just after an evolution call") return self._name @property def ID(self) -> int: """ ID identifier of this object. """ return self._ID @property def Mzams(self) -> float: """ Zero-age-main-sequence mass used to initialise the star [Msun] """ return self._Mzams @property def star_flag(self) -> str: """ Stellar star flag based on the current properties (e.g. the last entry of the evolved table). """ # Get current status mass, mhe, remtype = self.getp_array(properties=["Mass", "MHE", "RemnantType"], mode="last") # Check if it is a remnant or a pureHE and set star_flag star_flag = "H" if remtype == 1: star_flag = "HEWD" elif remtype == 2: star_flag = "COWD" elif remtype == 3: star_flag = "ONEWD" elif remtype == 4: star_flag = "NSEC" elif remtype == 5: star_flag = "NS" elif remtype == 6: star_flag = "BH" elif ut.check_equality(mass, mhe): star_flag = "HE" return star_flag # but never change the initial self._star_flag @property def log(self) -> str: """ Log created during the stellar evolution """ return copy.deepcopy(self._log) @property def used_sevnParams(self) -> Dict[str:Union[float, str, bool]]: """ Dictionary containing the SEVN parameters used during the stellar evolution """ return copy.deepcopy(self._used_sevnParams) @property def SEVNmanager_ID(self) -> int: """ Return the ID of the SEVNmanager associated with this Star at the Star initialisation """ return self._SEVNmanager_ID @property def tables_info(self) -> Dict[str:Union[str, float]]: """ Get a dictionary with the info of the loaded stellar tables (paths and minimum/maximum Zams - Z values) """ if SEVNmanager.check_initiliased(): return SEVNmanager.tables_info() else: warnings.warn("The tables info are filled just after the SEVNmanager initialisation") return {} @property def evolve_table(self) -> pd.DataFrame: """ Return the pandas dataframe containing the table with the stellar properties and their evolution """ if self._evolve_dataframe.empty: warnings.warn("Star evolved dataframe is empty before the evolution calls") return copy.deepcopy(self._evolve_dataframe)
[docs] def get_SN_kick(self) -> Dict[str:Any]: """ Get the info about the SN kick velocities. Returns ------- SNkick_properties: Dictionary A Dictionary containing the following keys: - SNtime: the time of the SN explosion (Worldtime) - Vkick: a numpy array containing the three Cartesian component of the kick (the axes orientation are arbitrary) .. warning:: If a SN never exploded in the current evolution table, SNtime will be equal to nan and the three components of the Vkick will be nan too """ df_log = readlogstring(self.log, events=["SN"]) SNtime = np.nan Vkick = np.array([np.nan, np.nan, np.nan]) if not df_log.empty: SNtime = df_log["time"][0] Vkick = np.array([df_log.Vfkx_SN[0], df_log.Vfky_SN[0], df_log.Vfkz_SN[0]]) return {"SNtime": SNtime, "Vkick": Vkick}
def _set_property_interpolator(self): """ Set the linear interpolator for all the properties. Raises ------- RuntimeError: If the current evolve table contains just one row """ if self._properties_interpolators["last_counter"] == self._evolve_counter: return else: self._properties_interpolators["last_counter"] = self._evolve_counter if len(self._evolve_dataframe) < 2: raise RuntimeError("Star is trying to set the property interpolators, but the evolve table has only " "one value") for prop in self._evolve_dataframe.columns: prop_int_list = ["Phase", "RemnantType", "PhaseBSE", "Event"] if prop == "Worldtime": self._properties_interpolators[prop] = lambda x: x else: if prop in prop_int_list: kind = "previous" else: kind = "linear" self._properties_interpolators[prop] = interp1d(self._evolve_dataframe["Worldtime"], self._evolve_dataframe[prop], bounds_error=False, kind=kind) def _check_SEVNmanager_synchronisation(self): """ Check if the SEVNmanager is initialised and if the star is running within the context of the same SEVNmanager (this assure that there are not unexpected change of SEVN parameters) Raises ------- RuntimeError If the SEVNmanager is not initialised or if the SEVmanagerID is not the one saved at the star initilisation """ if not SEVNmanager.check_initiliased(): raise RuntimeError("Cannot evolve Star, the SEVNmanager has not been initialised") elif self._SEVNmanager_ID is not None and self._SEVNmanager_ID != SEVNmanager.get_ID(): raise RuntimeError("Cannot evolve Star, the SEVNmanager has been reinitialised") def _after_evolve_duties(self): """ Some assignment to perform after an evolution call, i.e. increases the evolve_counter and save the SEVNmanager ID and the SEVNmanager parameter related to the ID. """ self._evolve_counter += 1 self._SEVNmanager_ID = SEVNmanager.get_ID() self._used_sevnParams = SEVNmanager.get_sevnParams()
[docs] def __getitem__(self, item): """ Same as :func:`~Star.getp` but with mode set to "all" and t is None """ return self.getp(properties=item,mode="all",t=None)
[docs] def __repr__(self): ret = "*** Init value ***" ret += f"\nMzams={self._Mzams} Msun Z={self._Z}" ret += f"\nSpin={self._spin} Age={self._tini}" ret += f"\nSN model={self._snmodel}" ret += "\n*** Current properties ***" properties = self.getp(mode="last") for p in properties.columns: ret += f"\n{p}={properties[p].values[0]:.3g}" ret += "\n*** Tables ***" Htable = self.tables_info["tables"] HEtable = self.tables_info["tables_HE"] ret += f"\nH-tables: {Htable}" ret += f"\nHe-tables: {HEtable}" return ret