def _CheckBadData(Tracks): """Takes current set of tracks, checks last entries for bad data.""" # Originally assume no error and sould continue error = False # Make sure rotation rates are positive if (Tracks['OmegaEnv'][-1] <= 0.0): misc._PrintError( "negative value of OmegaEnv in rotational evolution calculation") error = True if (Tracks['OmegaCore'][-1] <= 0.0): misc._PrintError( "negative value of OmegaCore in rotational evolution calculation") error = True # Loop over elements in Tracks and check each one for NaN or Inf for track in Tracks: if math.isnan(Tracks[track][-1]): misc._PrintError("NaN found for " + track + " in rotational evolution calculation") error = True if math.isinf(Tracks[track][-1]): misc._PrintError("inf found for " + track + " in rotational evolution calculation") error = True # If there was an error, kill the code if error: misc._PrintErrorKill("ending simulation due to bad data found") return
def Values(self, Age=None, Quantity=None): """ Takes age in Myr and a string with name of quantity to output, returns value of that quantity at specified age for all stars. Parameters ---------- Age : float Age of cluster. Quantity : str String holding name of parameter to get. Returns ---------- values : numpy.ndarray Set of extended quantities. """ # Make sure input parameters are set if Age is None: misc._PrintErrorKill( "keyword parameter Age not set in call to function") if Quantity is None: misc._PrintErrorKill( "keyword parameter Quantity not set in call to function") # Make array to hold values values = np.zeros(self.nStars) # Loop over stars and get values for each for iStar in range(0, self.nStars): values[iStar] = self.stars[iStar].Value(Age=Age, Quantity=Quantity) return values
def _Interpolate1D(Xarray, Yarray, X): """Takes 1D arrays for corresponding X and Y values, returns interpolated Y value corresponding to input X.""" # Note that it is assumed here that Xarray is in ascending order and this won't work if it is not # Round the input values to decimal places determined by nDecValue at top of file X = round(X, nDecValue) # Make sure X is in limits if not (Xarray[0] <= X <= Xarray[-1]): misc._PrintErrorKill("input value " + str(X) + " is not within limits of " + str(Xarray[0]) + " to " + str(Xarray[-1])) # Get index of X closest to but smaller than value iMin = misc._getIndexLTordered(Xarray, X) iMax = iMin + 1 # Check if X is an element in Xarray if (Xarray[iMin] == X): return Yarray[iMin] elif (Xarray[iMax] == X): return Yarray[iMax] else: # Do linear interpolation mInterp = (Yarray[iMax] - Yarray[iMin]) / (Xarray[iMax] - Xarray[iMin]) cInterp = Yarray[iMin] - mInterp * Xarray[iMin] Y = mInterp * X + cInterp return Y
def _GaussianElimination(Ain, B): """Solves the equation A*X = B using Gaussian elimination.""" # This is meant to be used when getting the k coefficients in the Rosenbrock # method. This subroutine is able to be relatively simple because the NxN # matrix A is given by I-dt*gamma*J, where I is the identity matrix. It is # highly unlikely that any of the diagonal terms in A are zero, since that # would only really be the case if a diagonal term in J is exactly 1.0/(dt*gamma), # which would be quite a coincidence. If any diagonal terms in A are zero, # this subroutine will give a nonsense result. I have added an if statement # below to catch these errors, so the user will at least know what is going on. #--------------------------------------- # GET N AND ALLOCATE X AND A N = len(B) X = np.zeros(N) A = np.zeros((N, N)) # --------------------------------------- # COPY A and B A[:, :] = Ain[:, :] X[:] = B[:] #--------------------------------------- # GET IN ROW ECHELON FORMAT # Loop over rows for i in range(0, N): # Make sure diagonal term not zero if (A[i, i] == 0.0): misc._PrintErrorKill("diagonal term in zero") # Divide row by value in diagonal element X[i] = X[i] / A[i, i] A[i, i:N] = A[i, i:N] / A[i, i] # Get rid of zeros in columns below this diagonal # unless doing final row if (i < N - 1): for k in range(i + 1, N): if (A[k, i] != 0.0): X[k] += -X[i] * A[k, i] A[k, :] += -A[i, :] * A[k, i] #--------------------------------------- # GET IN REDUCED ROW ECHELON FORMAT for i in range(N - 2, -1, -1): for j in range(N - 1, i, -1): if (A[i, j] != 0.0): X[i] += -A[i, j] * X[j] A[i, :] += -A[i, j] * A[j, :] #--------------------------------------- return X
def _PerPercentile(Mstar, Omega, MstarDist, OmegaDist, params): """Gets percentile of the rotation distribution for a given mass and rotation rate.""" # Get part of distribution to include includeStars = np.where(np.abs(MstarDist - Mstar) <= params['dMstarPer']) # Need at least two stars if (len(includeStars[0]) < 2): misc._PrintErrorKill( "need at least two stars in mass bin to get percentile") # If Omega is less than minimum then make this as the 0th percentile if (Omega <= np.min(OmegaDist[includeStars])): return 0.0 # If Omega is above maximum then make this as the 100th percentile if (Omega >= np.max(OmegaDist[includeStars])): return 100.0 # Use bisector to invert numpy.percentile function # Initial limits perMin = 0.0 perMax = 100.0 # Omegas for limits OmegaMin = np.percentile(OmegaDist[includeStars], perMin) OmegaMax = np.percentile(OmegaDist[includeStars], perMax) # Tolerance and maximum number of iterations tol = 1.0e-5 nIterMax = 10000 # Start iterating found = False for iIter in range(nIterMax): # Get mid value and percentle perMid = 0.5 * (perMin + perMax) OmegaMid = np.percentile(OmegaDist[includeStars], perMid) # See if OmegaMid is equal to or close enough to answer if (abs(OmegaMid / Omega - 1.0) < tol): return perMid # See which limit to change if (OmegaMid > Omega): perMax = perMid OmegaMax = OmegaMid else: perMin = perMid OmegaMin = OmegaMid # Make sure it was found if not found: misc._PrintErrorKill("did not find percentile " + str(per) + " for Omega of " + str(Omega)) return per
def _ValueSingle(Mstar, Age, ParamString, ModelData=ModelDataDefault): """Takes stellar mass and age and parameter string, returns value of that parameter at that mass and age.""" ## Make sure Mstar and age are floats #if not isinstance(Mstar,float): #misc._PrintErrorKill("argument Mstar must be float in call to Value") #if not isinstance(Age,float): #misc._PrintErrorKill("argument Age must be float in call to Value") # Make sure ParamString is indeed a string if not isinstance(ParamString, str): misc._PrintErrorKill( "argument ParamString must be string in call to Value") # Make sure ParamString corresponds to a valid parameter if not (ParamString in ModelData['ParamsAll']): misc._PrintErrorKill("parameter '" + ParamString + "' is not valid") # Check if an evolutionary track for this exact stellar mass is present if (Mstar in ModelData): # In this case, an evo track for this exact stellar mass is already loaded and all is needed is an interpolation # but first check the age is within the correct age limit _CheckAgeLimit(ModelData[Mstar]['Age'], Age) value = _Interpolate1D(ModelData[Mstar]['Age'], ModelData[Mstar][ParamString], Age) else: # In this case, no evo track for this specific mass is loaded and so a 2D interpolation between values from two # separate mass bins is needed # Note, it is not assumed that the mass bins are loaded into the dictionary in ascending order # Make sure within mass limit _CheckMassLimit(ModelData['MstarAll'], Mstar) # Get nearest mass bin below iMin = misc._getIndexLT(ModelData['MstarAll'], Mstar) MstarMin = ModelData['MstarAll'][iMin] # Get nearest mass bin above iMax = misc._getIndexGT(ModelData['MstarAll'], Mstar) MstarMax = ModelData['MstarAll'][iMax] # Check age ranges for both (max first since the upper limit is likely lower) _CheckAgeLimit(ModelData[MstarMax]['Age'], Age) _CheckAgeLimit(ModelData[MstarMin]['Age'], Age) # Do interpolation value = _Interpolate2D(MstarMin, MstarMax, Mstar, ModelData[MstarMin]['Age'], ModelData[MstarMax]['Age'], Age, ModelData[MstarMin][ParamString], ModelData[MstarMax][ParamString]) return value
def _CheckMassLimit(MstarArray, Mstar): """Takes array of masses and an age, outputs error and stops code if mass is not within limits.""" if not (np.min(MstarArray) <= Mstar <= np.max(MstarArray)): misc._PrintErrorKill("input stellar mass " + str(Mstar) + " is not within limits of " + str(np.min(MstarArray)) + " to " + str(np.max(MstarArray))) return
def _CheckAgeLimit(AgeArray, Age): """Takes age track and an age, outputs error and stops code if age is not within limits.""" # Round the age to decimal places determined by nDecValue at top of file Age = round(Age, nDecValue) # Do check if not (AgeArray[0] <= Age <= AgeArray[-1]): misc._PrintErrorKill("input age " + str(Age) + " is not within limits of " + str(AgeArray[0]) + " to " + str(AgeArray[-1])) return
def _OmegaPercentile(Mstar, percentile, MstarDist, OmegaDist, params): """Gets rotation rate that corresponds to a given percentile of the rotation distribution.""" ## If percentile was a string, get actual value # Get part of distribution to include includeStars = np.where(np.abs(MstarDist - Mstar) <= params['dMstarPer']) # Need at least two stars if (len(includeStars[0]) < 2): misc._PrintErrorKill( "need at least two stars in mass bin to get percentile") # Get percentile Omega Omega = np.percentile(OmegaDist[includeStars], percentile) return Omega
def ActivityLifetime(self, Quantity=None, Threshold=None, AgeMax=None): """ Takes threshold value, returns ages at which each star last drops below this threshold. This function can be used to determine when each star's emission crosses a given threshold value for a few activity quantities. These are Lx, Fx, Rx, and FxHZ for X-rays, and similar values for EUV1, EUV2, EUV, XUV, and Ly-alpha. If the star crosses the threshold (from above it to below it) multiple times, this will find the final time it will cross the threshold. If the user wants to set a maximum age so that the code only looks for crossings of the threshold below this age then this can be done using the AgeMax keyword argument. Parameters ---------- Quantity : str Gives which quantity to consider (e.g. 'Lx'). Threshold : float or str Value for threshold in units of quantity or string of 'sat'. AgeMax : float , optional End age of track to consider in Myr. Returns ---------- AgeActive : float Activity lifetime in Myr. """ # Make sure Quantity is set if Quantity is None: misc._PrintErrorKill("Quantity not set in call to function") # Make sure Quantity is string if not isinstance(Quantity, str): misc._PrintErrorKill("Quantity must be string") # Make array AgeActive = np.zeros(self.nStars) # Loop over stars and get each for iStar in range(self.nStars): AgeActive[iStar] = self.stars[iStar].ActivityLifetime( Quantity=Quantity, Threshold=Threshold, AgeMax=AgeMax) return AgeActive
def _CheckInputRotation(Age, Omega, OmegaEnv, OmegaCore): """Takes input rotation, checks if values are setup correctly.""" # Make sure if Age is set that OmegaCore is not set if (not Age is None) and (not OmegaCore is None): misc._PrintErrorKill( "cannot set both Age and OmegaCore as arguments of Cluster") # Make sure at least one rotation rate is set if (Omega is None): # Since Omega is not set, make sure both OmegaEnv and OmegaCore are set if (OmegaEnv is None) or (OmegaCore is None): misc._PrintErrorKill( "must set either Omega or both OmegaEnv and OmegaCore as arugment of Star" ) else: # Since Omega is set, make sure both OmegaEnv and OmegaCore are not set if not ((OmegaEnv is None) and (OmegaCore is None)): misc._PrintErrorKill( "cannot set OmegaEnv and OmegaCore when Omega is set as arugment of Star" ) # Set both OmegaEnv and OmegaCore to Omega OmegaEnv = Omega OmegaCore = Omega return Omega, OmegaEnv, OmegaCore
def _shouldAppend(Age, AgesOut): """Takes information about age in evolutionary calculation, returns if age should be output.""" # If AgesOut is not set, then should always output if AgesOut is None: return AgesOut, True # If AgesOut is set, work out if this is one of the output ages if not AgesOut is None: # If AgesOut is just a number (float or int) then it is easy if isinstance(AgesOut, (float, int)): # Check if close enough to AgesOut to output if (abs(Age / float(AgesOut)) - 1.0 < 1.0e-6): return AgesOut, True else: return AgesOut, False else: # Get smallest distance between age in AgesOut and current Age indexMin = np.argmin(np.abs(AgesOut - Age)) deltaAgeMin = np.min(np.abs(AgesOut - Age)) # Since we are removing this output age each time, indexMin should always be zero if not indexMin == 0: misc._PrintErrorKill( "indexMin is not zero, probably because the input AgesOut is not in ascending order" ) # If age is close enough to element in AgesOut then remove this element and return True if deltaAgeMin < 1.0e-3: AgesOut = np.delete(AgesOut, indexMin) return AgesOut, True else: return AgesOut, False return AgesOut, False
def Track(self, Quantity=None): """Takes a quantity string, return evolutionary track for that quantity.""" # Make sure Quantity is set if Quantity is None: misc._PrintErrorKill("Quantity not set in call to function") # Make sure Quantity is string if not isinstance(Quantity, str): misc._PrintErrorKill("Quantity must be string") # Make sure Quantity is a valid quantity with a track if not Quantity in self.Tracks: misc._PrintErrorKill("no available track for " + Quantity) return self.Tracks[Quantity]
def _CheckInputMstar(Mstar): """Takes stellar mass, checks if value is a valid input mass.""" # Make sure Mstar was set if Mstar is None: misc._PrintErrorKill("stellar mass not given") # Make sure it is a float #if not isinstance(Mstar,float): #misc._PrintErrorKill("stellar mass must be given as float") # Make sure it above minimum if (Mstar < MstarMin): misc._PrintErrorKill( "stellar mass cannot be less than lower limit of " + str(MstarMin) + " Msun") # Make sure it above minimum if (Mstar > MstarMax): misc._PrintErrorKill( "stellar mass cannot be greater than upper limit of " + str(MstarMax) + " Msun") return
def _ReadEvolutionTrack(starEvoDir, evoModels, Mstar, MstarFilenameMiddle): # This function loads the original stellar evolution models from Spada et al. (2013) for an individual # mass bin and puts them into a dictionary. # Set strings for starting and ending of filenames filename_prefix = starEvoDir + evoModels + "/M" filename_postfix1 = "_" + evoModels + ".track1" filename_postfix2 = "_" + evoModels + ".track2" # Get names of data files holding evo models filename1 = filename_prefix + MstarFilenameMiddle + filename_postfix1 filename2 = filename_prefix + MstarFilenameMiddle + filename_postfix2 # Read contents of files with open(filename1, 'r') as f: content1 = f.readlines() with open(filename2, 'r') as f: content2 = f.readlines() # Number of header lines nHeader = 1 # Remove any duplicate ages content1 = _RemoveDuplicateAges(nHeader, content1) content2 = _RemoveDuplicateAges(nHeader, content2) # Make sure they are the same lengths if not (len(content1) == len(content2)): misc._PrintErrorKill("two evo files not same length") # Get number of age bins nAge = len(content1) - nHeader # Make arrays to hold desired quantities Age = np.zeros(nAge) Rstar = np.zeros(nAge) Lbol = np.zeros(nAge) Teff = np.zeros(nAge) Itotal = np.zeros(nAge) Icore = np.zeros(nAge) Ienv = np.zeros(nAge) Mcore = np.zeros(nAge) Menv = np.zeros(nAge) Rcore = np.zeros(nAge) tauConv = np.zeros(nAge) dItotaldt = np.zeros(nAge) dIcoredt = np.zeros(nAge) dIenvdt = np.zeros(nAge) dMcoredt = np.zeros(nAge) dRcoredt = np.zeros(nAge) # Read quantities from first file into the arrays (has age, Lbol, and Rstar) iAge = 0 for line in content1[nHeader:len(content1)]: # Split data into list data = line.split() # Read data Age[iAge] = data[0] Lbol[iAge] = data[4] Teff[iAge] = data[7] Rstar[iAge] = data[5] # Update index iAge += 1 # Read quantities from second file into the arrays (has Itotal, Ienv, Menv, Rcore, and tauConv) iAge = 0 for line in content2[nHeader:len(content2)]: # Split data into list data = line.split() # Read data Itotal[iAge] = data[7] Ienv[iAge] = data[6] Menv[iAge] = data[2] Rcore[iAge] = data[3] tauConv[iAge] = data[4] # Update i iAge += 1 # Change some units and convert from log to lin Age[:] = Age[:] * 1000.0 # convert from Gyr into Myr Rstar[:] = 10.0**Rstar[:] # convert into linear since Spada has in log Lbol[:] = 10.0**Lbol[:] # also this one Teff[:] = 10.0**Teff[:] # also this one # Get some core quantities Icore[:] = Itotal[:] - Ienv[:] Mcore[:] = (1.0 - Menv[:]) * Mstar Rcore[:] = Rcore[:] * Rstar[:] # Get gradients dItotaldt[:] = _CalculateGradient(Age, Itotal) dIcoredt[:] = _CalculateGradient(Age, Icore) dIenvdt[:] = _CalculateGradient(Age, Ienv) dMcoredt[:] = _CalculateGradient(Age, Mcore) dRcoredt[:] = _CalculateGradient(Age, Rcore) # Round mass and ages to nDecValue decimal places as specified at top of file Mstar = round(Mstar, nDecValue) Age = np.round(Age, decimals=nDecValue) # Add all quantities to the dictionary Data = {} Data['Mstar'] = Mstar Data['Age'] = Age Data['Rstar'] = Rstar Data['Lbol'] = Lbol Data['Teff'] = Teff Data['Itotal'] = Itotal Data['Icore'] = Icore Data['Ienv'] = Ienv Data['Mcore'] = Mcore Data['Menv'] = Menv Data['Rcore'] = Rcore Data['tauConv'] = tauConv Data['dItotaldt'] = dItotaldt Data['dIcoredt'] = dIcoredt Data['dIenvdt'] = dIenvdt Data['dMcoredt'] = dMcoredt Data['dRcoredt'] = dRcoredt #Data[''] = return Data
def __init__(self, Mstar=None, Age=None, Omega=None, OmegaEnv=None, OmegaCore=None, AgesOut=None, starEvoDir=None, evoModels=None, params=params.paramsDefault, verbose=False): """ Initialises instance of Cluster class. This is the main function that is run when creating an instance of the Cluster class and it sets up all the things needed including calculating evolutionary tracks for each of the stars star. The function requires that an array of stellar masses (in Msun) and initial rotation rates (in OmegaSun=2.67e-6 rad s^-1) are input. using the Mstar and Omega keyword arguments. Alternatively, OmegaEnv and OmegaCore can be set, in which case Omega does not need to be specified. If the argument Age (in Myr) is also specified, then the code will find the evolutionary tracks for each star that passes through this rotation rate at this age, otherwise if Age is not set then it will calculate evolutionary tracks assuming this Omega as the initial (1 Myr) rotation rate. The user should not specify OmegaCore and Age simultaneously, and if Age is set then either Omega or OmegaEnv can be used to specify the surface rotation rate. Age can also be set as an array giving different ages for each star. """ # Make sure Mstar was set if Mstar is None: misc._PrintErrorKill("Mstar keyword argument not set") # Make sure Omega, OmegaEnv, and OmegaCore are well set # (if Omega is set, OmegaEnv and OmegaCore will both be set to that value) Omega, OmegaEnv, OmegaCore = _CheckInputRotation( Age, Omega, OmegaEnv, OmegaCore) # Make sure arrays all same length if not (len(Mstar) == len(Omega)): misc._PrintErrorKill("Mstar and Omega have different lengths") if not (len(Mstar) == len(OmegaEnv)): misc._PrintErrorKill("Mstar and OmegaEnv have different lengths") if not (len(Mstar) == len(OmegaCore)): misc._PrintErrorKill("Mstar and OmegaCore have different lengths") # Set number of stars self.nStars = len(Mstar) # Set parameters self.params = params # Set the ExtendedTracks parameter to True so we get all parameters self.params['ExtendedTracks'] = True # Set Mstar arrays self.Mstar = Mstar # Set AgesOut self.AgesOut = AgesOut # Set stellar evo model self.starEvoDir = starEvoDir self.evoModels = evoModels # Get evolutionary tracks self._LoadEvoTracks(Age, OmegaEnv, OmegaCore, verbose) # Get HZ boundaries self.aOrbHZ = phys.aOrbHZ(Mstar=self.Mstar, params=self.params) return
def _LoadEvoTracks(self, Age, OmegaEnv0, OmegaCore0, AgesOut=None): """Loads rotation and activity tracks.""" # Set starting age to 1 Myr self.AgeMin = 1.0 # Get ending age as end of stellar evolution track self.AgeMax = self.StarEvo.ModelData[self.Mstar]['Age'][-1] # If needing to get initial rotation rate then get it if not Age is None: # Get initial rotation Omega0 = RE.FitRotation(Mstar=self.Mstar, Age=Age, Omega=OmegaEnv0, AgeMin=self.AgeMin, params=self.params, StarEvo=self.StarEvo) # Make sure a correct initial rotation rate was found if (Omega0 == -1): misc._PrintErrorKill( "input rotation rate too low for given mass and age") if (Omega0 == -2): misc._PrintErrorKill( "input rotation rate too high for given mass and age") if (Omega0 == -3): misc._PrintErrorKill( "input rotation rate in valid range for mass and age but solver was unable to fit track" ) # Set initial rotation rates for input into RE.EvolveRotation() OmegaEnv0 = Omega0 OmegaCore0 = Omega0 # Run evolution model using OmegaEnv0 and OmegaCore0 as initial rotation rate self.Tracks = RE.EvolveRotation(Mstar=self.Mstar, OmegaEnv0=OmegaEnv0, OmegaCore0=OmegaCore0, AgeMin=self.AgeMin, AgeMax=self.AgeMax, AgesOut=AgesOut, params=self.params, StarEvo=self.StarEvo) # Set the percentile of this track in the starting distribution self.percentile = Percentile(Mstar=self.Mstar, Omega=OmegaEnv0) # Make evolutionary tracks attributes of this class # (this is so the user can get a track using e.g. star.Age instead of star.Tracks['Age']) for track in self.Tracks: # Note: the following two options are basically equivalent #self.__dict__[track+'Track'] = self.Tracks[track] setattr(self, track + 'Track', self.Tracks[track]) # Load dictionary holding units for each quantity self.Units = phys.QuantitiesUnits() # Make functions for each quantity that return this quantity at a given age as attributes of class self._setupQuantityFunctions() return
def Percentile(self, Mstar=None, Age=None, Omega=None, Prot=None, percentile=None): """ Gets rotation rate of percentile or percentile of rotation rate in the rotation distribution at given age. This function can be used for two purposes 1. to determine the percentile in a rotation distribution of a star given its mass, rotation rate, and age 2. to determine the rotation rate of a star in a rotation distribution given its mass, percentile, and age In the first case, the user should specify the rotation rate using either the Omega or Prot keyword arguments (given in OmegaSun and days respectively) and the mass. In the second case, the user should specify the percentile using the percentile keyword argument (in the range 0 to 100) and the mass. The mass should be specified in Msun using the Mstar keyword argument and the age in Myr using the Age keyword argument. These should only be given as floats. Parameters ---------- Mstar : float Stellar mass in Msun. Age : float Age of star in Myr. Omega : float , optional Rotation rate of star in OmegaSun. Prot : float , optional Rotation period of star in days. percentile : float , optional percentile in distribution (between 0 and 100). Returns ---------- result : float Either rotation rate for percentile or percentile for rotation rate. """ # Make sure the age is specified if Age is None: misc._PrintErrorKill( "keyword parameter Age not set in call to function") # If percentile was set to a string, get float version if isinstance(percentile, str): if (percentile == 'slow'): percentile = 5.0 elif (percentile == 'medium'): percentile = 50.0 elif (percentile == 'fast'): percentile = 95.0 else: misc._PrintErrorKill( "invalid percentile string (options are 'slow', 'medium', or 'fast')" ) # Get the rotation distribution at this age OmegaDist = self.Values(Age=Age, Quantity='OmegaEnv') # Get the result result = star.Percentile(Mstar=Mstar, Omega=Omega, Prot=Prot, percentile=percentile, MstarDist=self.Mstar, OmegaDist=OmegaDist, params=self.params) return result
def ActivityLifetime(self, Quantity=None, Threshold=None, AgeMax=None): """ Takes threshold value, returns age at which a star last drops below this threshold. This function can be used to determine when a star's emission crosses a given threshold value for a few activity quantities. These are Lx, Fx, Rx, and FxHZ for X-rays, and similar values for EUV1, EUV2, EUV, XUV, and Ly-alpha. If the star crosses the threshold (from above it to below it) multiple times, this will find the final time it will cross the threshold. If the user wants to set a maximum age so that the code only looks for crossings of the threshold below this age then this can be done using the AgeMax keyword argument. Parameters ---------- Quantity : str Gives which quantity to consider (e.g. 'Lx'). Threshold : float or str Value for threshold in units of quantity or string of 'sat'. AgeMax : float , optional End age of track to consider in Myr. Returns ---------- AgeActive : float Activity lifetime in Myr. """ # Allowed quantities QuantitiesAllowed = [ 'Lx', 'Fx', 'Rx', 'FxHZ', 'Leuv1', 'Feuv1', 'Reuv1', 'Feuv1HZ', 'Leuv2', 'Feuv2', 'Reuv2', 'Feuv2HZ', 'Leuv', 'Feuv', 'Reuv', 'FeuvHZ', 'Lly', 'Fly', 'Rly', 'FlyHZ' ] # Make sure Quantity is set if Quantity is None: misc._PrintErrorKill("Quantity not set in call to function") # Make sure Quantity is string if not isinstance(Quantity, str): misc._PrintErrorKill("Quantity must be string") # Check input Quantity is valid if not Quantity in QuantitiesAllowed: QuantitiesString = '' for quantity in QuantitiesAllowed: QuantitiesString += quantity + " , " misc._PrintErrorKill("invalid quantity " + Quantity + "\n valid options are " + QuantitiesString[0:-3]) # Set tracks AgeTrack = self.AgeTrack Track = self.Tracks[Quantity] # If Threshold is 'sat' then normalise track to saturation value and set threshold to unity if (Threshold == 'sat'): # Get saturation rotation rate as function of age OmegaSatTrack = phys.OmegaSat(Mstar=self.Mstar, Age=self.AgeTrack, param='XUV', params=self.params, StarEvo=self.StarEvo) # Make track just OmegaEnv/OmegaSat Track = self.OmegaEnvTrack / OmegaSatTrack # Set threshold to unity Threshold = 1.0 # Get result AgeActive = misc.ActivityLifetime(Age=AgeTrack, Track=Track, Threshold=Threshold, AgeMax=AgeMax) return AgeActive
def EvolveRotationStep(Mstar=None, Age=None, OmegaEnv=None, OmegaCore=None, dAgeMax=None, dAge=None, params=params.paramsDefault, StarEvo=None): """ Evolves rotation by a single step. This is the main function to be used to do timestep updates of the core and envelope rotation rates for a star given its basic parameters. The five arguments Mstar, Age, OmegaEnv, OmegaCore, and either dAgeMax or dAge must be set in the call to this function. For certain solvers, the timestep is determined automatically and this value is returned along with the updated envelope and core rotation rates Parameters ---------- Mstar : float Mass of star in Msun. Age : float , optional Age of star. OmegaEnv : float Envelope rotation rate in OmegaSun (=2.67e-6 rad/s). OmegaCore : float Core rotation rate in OmegaSun (=2.67e-6 rad/s). dAgeMax : float , optional Maximum timestep to take in Myr. dAgeMax : float , optional Timestep to take in Myr. Returns ---------- dAge : numpy.ndarray Age step in Myr. OmegaEnv : numpy.ndarray Envelope rotation rates in OmegaSun (=2.67e-6 rad/s). OmegaCore : numpy.ndarray Core rotation rates in OmegaSun (=2.67e-6 rad/s). """ # Make sure parameters are set if Mstar is None: misc._PrintErrorKill("argument Mstar must be set in call to function") if Age is None: misc._PrintErrorKill("argument Age must be set in call to function") if OmegaEnv is None: misc._PrintErrorKill( "argument OmegaEnv must be set in call to function") if OmegaCore is None: misc._PrintErrorKill( "argument OmegaCore must be set in call to function") if ((dAgeMax is None) and (dAge is None)): misc._PrintErrorKill( "either dAge or dAgeMax argument must be set in call to function") # If an instance of the StarEvo class is not input, load it with defaults if StarEvo is None: StarEvo = SE.StarEvo() # Do step based on method used if (params['TimeIntegrationMethod'] == 'ForwardEuler'): dAge, dAgeNew, OmegaEnv, OmegaCore = _EvolveRotationStepFE( Mstar=Mstar, Age=Age, OmegaEnv=OmegaEnv, OmegaCore=OmegaCore, dAge=dAge, params=params, StarEvo=StarEvo) elif (params['TimeIntegrationMethod'] == 'RungeKutta4'): dAge, dAgeNew, OmegaEnv, OmegaCore = _EvolveRotationStepRK4( Mstar=Mstar, Age=Age, OmegaEnv=OmegaEnv, OmegaCore=OmegaCore, dAge=dAge, params=params, StarEvo=StarEvo) elif (params['TimeIntegrationMethod'] == 'RungeKuttaFehlberg'): dAge, dAgeNew, OmegaEnv, OmegaCore = _EvolveRotationStepRKF( Mstar=Mstar, Age=Age, OmegaEnv=OmegaEnv, OmegaCore=OmegaCore, dAge=dAge, dAgeMax=dAgeMax, params=params, StarEvo=StarEvo) elif (params['TimeIntegrationMethod'] == 'Rosenbrock'): dAge, dAgeNew, OmegaEnv, OmegaCore = _EvolveRotationStepRB( Mstar=Mstar, Age=Age, OmegaEnv=OmegaEnv, OmegaCore=OmegaCore, dAge=dAge, dAgeMax=dAgeMax, params=params, StarEvo=StarEvo) else: misc._PrintErrorKill( "invalid value of TimeIntegrationMethod in parameters") return (dAge, dAgeNew, OmegaEnv, OmegaCore)
def FitRotation(Mstar=None, Age=None, Omega=None, AgeMin=None, params=params.paramsDefault, StarEvo=None): """ Takes stellar mass, age, and surface rotation rate, returns initial rotation rate of star. This function can be used if the age and surface rotation rate of the star is known and the user wants to get the corresponding initial (usually 1 Myr) rotation rate for the star. The star's mass, age, and rotation rates must be input. The starting age to get the initial rotation rate for can be set, and by default this is set to AgeMinFit in the parameters and it is not recommended to change this. In addition, model parameters and an instance of the StarEvo class can be input. The return value of this function should be the initial rotation rate but if the input Omega is below the minimum that can be fit, it will be -1, if it is above the maximum that can be fit, it will be -2, and if it is in the range but no solution could be found it will be -3. Parameters ---------- Mstar : float Mass of star in Msun. Age : float Age of the star in Myr. Omega : float Rotation rate in OmegaSun (=2.67e-6 rad/s). AgeMin : float , optional Starting age (default = 1 Myr). params : dict , optional Parameters to determine behavior of code (default given in parameters.py). StarEvo : Mors.stellarevo.StarEvo Instance of StarEvo class holding stellar evolution models to use. Returns ---------- Omega0 : dict Starting rotation rate in OmegaSun (=2.67e-6 rad/s) or negative if cannot get initial rotation. """ #--------------------------------------------------------------------- # Make sure stellar mass is set if Mstar is None: misc._PrintErrorKill("argument Mstar must be set in call to function") # Make sure initial rotation is set correctly if Omega is None: misc._PrintErrorKill("argument Omega must be set in call to function") # Make sure AgeMin is set if AgeMin is None: AgeMin = params['AgeMinFit'] # If an instance of the StarEvo class is not input, load it with defaults if StarEvo is None: StarEvo = SE.StarEvo() # Make sure this exact stellar mass is loaded (will do nothing if it already is) StarEvo.LoadTrack(Mstar) #--------------------------------------------------------------------- # First thing to be checked is if the model can actually fit this rotation rate at this age since # we have defined minimum and maximum initial rotation rates in the parameters, given by # Set starting Omega0Min and Omega0Max for fitting Omega0Min = params['Omega0FitMin'] Omega0Max = params['Omega0FitMax'] # Get minimum rotation rate at Age corresponding to Omega0Min Tracks = EvolveRotation(Mstar=Mstar, Omega0=Omega0Min, AgeMin=params['AgeMinFit'], AgeMax=Age, params=params, StarEvo=StarEvo) OmegaMin = Tracks['OmegaEnv'][-1] # Get maximum rotation rate at Age corresponding to Omega0Max Tracks = EvolveRotation(Mstar=Mstar, Omega0=Omega0Max, AgeMin=params['AgeMinFit'], AgeMax=Age, params=params, StarEvo=StarEvo) OmegaMax = Tracks['OmegaEnv'][-1] # Check that Omega is between OmegaMin and OmegaMax if (Omega < OmegaMin): return -1 if (Omega > OmegaMax): return -2 #--------------------------------------------------------------------- # Initially assume not found solution found = False # Start looping until solution has been found for iStep in range(0, params['nStepMaxFit']): # Get middle Omega0 Omega0Mid = 0.5 * (Omega0Min + Omega0Max) # Get rotation at mid spot Tracks = EvolveRotation(Mstar=Mstar, Omega0=Omega0Mid, AgeMin=params['AgeMinFit'], AgeMax=Age, params=params, StarEvo=StarEvo) OmegaMid = Tracks['OmegaEnv'][-1] #print(Omega0Min,Omega0Mid,Omega0Max,OmegaMin,Omega,OmegaMax,OmegaMid/Omega,Omega0Min/Omega0Max) # Check if OmegaMid is close enough to the desired answer if (abs(OmegaMid / Omega - 1.0) < params['toleranceFit']): found = True Omega0 = Omega0Mid break # Check if Omega0Min and Omega0Max are is close enough together (closest they will get) if (abs(Omega0Min / Omega0Max - 1.0) < params['toleranceFit']): found = True Omega0 = Omega0Mid break # Change either min or max Omega0 # Note that we already know that the solution is between the min and max if (Omega < OmegaMid): Omega0Max = Omega0Mid OmegaMax = OmegaMid else: Omega0Min = Omega0Mid OmegaMin = OmegaMid # Return -3 if not found result if not found: return -3 #--------------------------------------------------------------------- return Omega0
def EvolveRotation(Mstar=None, Omega0=None, OmegaEnv0=None, OmegaCore0=None, AgeMin=None, AgeMax=None, AgesOut=None, params=params.paramsDefault, StarEvo=None): """ Takes stellar mass and rotation rate, evolves rotation. This is the main function for rotational evolution calculations. It will take some basic stellar parameters and evolve rotation between two ages. Parameters ---------- Mstar : float Mass of star in Msun. Omega0 : float , optional Starting rotation rate in OmegaSun (=2.67e-6 rad/s). OmegaEnv0 : float , optional Starting envelope rotation rate in OmegaSun (=2.67e-6 rad/s). OmegaCore0 : float , optional Starting envelope rotation rate in OmegaSun (=2.67e-6 rad/s). AgeMin : float , optional Starting age (default = 1 Myr). AgeMax : float , optional Final age (default = 5 Gyr). AgesOut : float or numpy.ndarray , optional Ages to output data for in Myr. params : dict , optional Parameters to determine behavior of code (default given in parameters.py). StarEvo : Mors.stellarevo.StarEvo Instance of StarEvo class holding stellar evolution models to use. Returns ---------- Tracks : dict Dictionary with evolutionary tracks and related information. """ # Make sure stellar mass is set if Mstar is None: misc._PrintErrorKill("argument Mstar must be set in call to function") # Make sure initial rotation is set correctly if Omega0 is None: if ((OmegaEnv0 is None) or (OmegaCore0 is None)): misc._PrintErrorKill( "if Omega0 is not set then both OmegaEnv0 and OmegaCore0 must be set in call to function" ) else: if not ((OmegaEnv0 is None) and (OmegaCore0 is None)): misc._PrintErrorKill( "if Omega0 is set then neither OmegaEnv0 nor OmegaCore0 can be set in call to function" ) # Make sure AgeMin is set if AgeMin is None: AgeMin = params['AgeMinDefault'] # Make sure AgeMax is set if AgeMax is None: AgeMax = params['AgeMaxDefault'] # Make editable and np array form of AgesOut and use this from now on if not AgesOut is None: if isinstance(AgesOut, (float, int)): AgesOut2 = np.array([AgesOut]) else: AgesOut2 = copy.deepcopy(AgesOut) else: AgesOut2 = None # If AgesOut has been set, use the final age from that as ending age if not AgesOut2 is None: AgeMax = AgesOut2[-1] # If an instance of the StarEvo class is not input, load it with defaults if StarEvo is None: StarEvo = SE.StarEvo() # Make sure this exact stellar mass is loaded (will do nothing if it already is) StarEvo.LoadTrack(Mstar) # Starting values for time iAge = 0 Age = AgeMin # Starting timestep in Myr dAge = 0.5 # If AgesOut was set, make sure no ages are below AgeMin if not AgesOut2 is None: AgesOut2 = np.delete(AgesOut2, np.where(AgesOut2 < AgeMin)) # Starting rotation rates if Omega0 is None: OmegaEnv = OmegaEnv0 OmegaCore = OmegaCore0 else: OmegaEnv = Omega0 OmegaCore = Omega0 # Make dictionary for holding tracks and add initial values Tracks = _CreateTracks(Mstar, Age, dAge, OmegaEnv, OmegaCore, params, StarEvo) # Start looping, end only when end time is reached (additional 0.999 factor to stop code getting stuck) while (Age < 0.999 * AgeMax): # Make sure timestep is not too long dAge, dAgeMax = _dAgeCalc(dAge, Age, AgeMax, AgesOut2, params) # Do timestep dAge, dAgeNew, OmegaEnv, OmegaCore = EvolveRotationStep( Mstar=Mstar, Age=Age, OmegaEnv=OmegaEnv, OmegaCore=OmegaCore, dAgeMax=dAgeMax, dAge=dAge, params=params, StarEvo=StarEvo) # Update time iAge += 1 Age += dAge # Work out if should add this age to output AgesOut2, shouldAppend = _shouldAppend(Age, AgesOut2) # Add current state to tracks if should output if shouldAppend: Tracks = _AppendTracks(Tracks, Mstar, Age, dAge, OmegaEnv, OmegaCore, params, StarEvo) # Check for bad data _CheckBadData(Tracks) # Get new age step dAge = dAgeNew # Determine if too many steps taken if (iAge >= params['nStepMax']): misc._PrintErrorKill("too many timesteps taken") # In Tracks, replace Mstar array with simple Mstar float Tracks['Mstar'] = Mstar Tracks['nAge'] = len(Tracks['Age']) return Tracks
def IntegrateEmission(self, AgeMin=None, AgeMax=None, Band=None, aOrb=None): """ Takes age range, calculates integrated emission in band within that range. This code can be used to integrate a star's luminosity between two ages. This can be applied to any wavelength band and the result is a total energy emitted in this time in erg. If the user also specifies an orbital distance using the aOrb keyword argument, the code integrates the flux at this obital distance returns a fluence in erg cm^-2. The user can specify aOrb as a string to get the fluences at various habitable zone boundaries (using the HZ calculated at the age defined in params when creating this cluster). Options are 'RecentVenus', 'RunawayGreenhouse', 'MoistGreenhouse', 'MaximumGreenhouse', 'EarlyMars', and 'HZ'. Parameters ---------- AgeMin : float Start of time period to integrate in Myr. AgeMax : float End of time period to integrate in Myr. Band : str Gives which wavelength band to consider (options are 'XUV', 'Xray', 'EUV1', 'EUV2', 'EUV', 'Lyman', 'bol'). aOrb : float or str , optional Orbital distance to get fluence at in AU or string identifying HZ boundary. Returns ---------- Energy : float Integrated luminosity or flux in erg or erg cm^-2. """ # Allowed quantities BandsAllowed = ['XUV', 'Xray', 'EUV1', 'EUV2', 'EUV', 'Lyman', 'bol'] # Make sure Band is set if Band is None: misc._PrintErrorKill("Band not set in call to function") # Make sure Band is string if not isinstance(Band, str): misc._PrintErrorKill("Band must be string") # Check input Band is valid if not Band in BandsAllowed: BandsString = '' for band in BandsAllowed: BandsString += band + " , " misc._PrintErrorKill("invalid Band " + Band + "\n valid options are " + BandsString[0:-3]) # Get luminosity track to use if Band == 'XUV': Luminosity = self.LxTrack + self.LeuvTrack elif Band == 'Xray': Luminosity = self.LxTrack elif Band == 'EUV1': Luminosity = self.Leuv1Track elif Band == 'EUV2': Luminosity = self.Leuv2Track elif Band == 'EUV': Luminosity = self.LeuvTrack elif Band == 'Lyman': Luminosity = self.LlyTrack elif Band == 'bol': Luminosity = self.LbolTrack else: misc._PrintErrorKill("did not find right luminosity track") # If aOrb was set to a string, do necessary work to get useable aOrb (given by aOrbUse) if isinstance(aOrb, str): # Valid options aOrbAllowed = [ 'RecentVenus', 'RunawayGreenhouse', 'MoistGreenhouse', 'MaximumGreenhouse', 'EarlyMars', 'HZ' ] # Check input is valid if not aOrb in aOrbAllowed: aOrbString = '' for a in aOrbAllowed: aOrbString += a + " , " misc._PrintErrorKill("invalid aOrb " + aOrb + "\n valid options are a value in AU or " + aOrbString[0:-3]) # Get orbital distance aOrbUse = self.aOrbHZ[aOrb] else: # Just set to input value aOrbUse = aOrb # Do calculation Energy = misc.IntegrateEmission(AgeMin=AgeMin, AgeMax=AgeMax, Age=self.AgeTrack, Luminosity=Luminosity, aOrb=aOrbUse) return Energy
def Value(MstarIn, AgeIn, ParamString, ModelData=ModelDataDefault): """ Takes stellar mass, age, and a parameter string, returns values corresponding to named parameter. The set of models should have already been loaded. With this function, the user can ask for a value of one of the parameters for a specific stellar mass and age. All three of these can be input as multiple values and an array of values will be returned if this is the case. For example, if MstarIn is input as a 1D array, the function will return a 1D array giving the value for each of these masses. ParamString can be input as a list of strings and values for each parameter in that list will be returned in a 1D array. If two are given as arrays or lists, then a 2D array will be returned. If all three then a 3D array with dimensions len(MstarIn)xlen(AgeIn)xlen(ParamString) will be returned. Parameters ---------- MstarIn : float or int or numpy.ndarray Mass of star in Msun. AgeIn : float or int or numpy.ndarray Age in Myr. ParamString : str String holding name of parameter to get value for. ModelData : dict , optional Dictionary of dictionaries holding set of stellar evolution models. Returns ---------- value : float or numpy.ndarray Value of parameter at this mass and age. """ # Check if ModelData is not None, otherwise need to load defaults if ModelData is None: ModelData = _LoadDefaultModelData() # There are now eight scenarios here # 1. Mstar and Age are both floats, so return float. # 2. Mstar is float and Age is numpy.ndarray, so return numpy.ndarray of length len(Age). # 3. Mstar is numpy.ndarray and Age is float, so return numpy.ndarray of length len(Age) # 4. Mstar and Age are both numpy.ndarray, so return numpy.ndarray of shape ( len(Mstar) , len(Age) ). # Note: could use type(X).__module__ == 'numpy' to test for numpy but it doesn't work if # Get Mstar and Age in correct types Mstar = misc._convertFloatArray(MstarIn) Age = misc._convertFloatArray(AgeIn) # Make sure Mstar and Age are correct types if Mstar is None: misc._PrintErrorKill("argument Mstar has invalid type") if Age is None: misc._PrintErrorKill("argument Age has invalid type") # Find if scenario 1 (most likely) if (isinstance(Mstar, float) and isinstance(Age, float) and isinstance(ParamString, str)): # Scenario 1 so just get value value = _ValueSingle(Mstar, Age, ParamString, ModelData=ModelData) else: # In this case, the output will be an array with up to three dimensions, so first make 3D array # and then remove dimensions with only one element # Get number of elements in all three directions try: nMstar = len(Mstar) except: nMstar = 1 try: nAge = len(Age) except: nAge = 1 if isinstance(ParamString, list): nParam = len(ParamString) else: nParam = 1 # Make array value = np.zeros((nMstar, nAge, nParam)) # Loop over values for iMstar in range(0, nMstar): for iAge in range(0, nAge): for iParam in range(0, nParam): # Get values of Mstar, Age, and ParamString if isinstance(Mstar, float): MstarValue = Mstar else: MstarValue = Mstar[iMstar] if isinstance(Age, float): AgeValue = Age else: AgeValue = Age[iAge] if isinstance(ParamString, str): ParamStringValue = ParamString else: ParamStringValue = ParamString[iParam] # Now get the value value[iMstar, iAge, iParam] = _ValueSingle(MstarValue, AgeValue, ParamStringValue, ModelData=ModelData) # Now get rid of unwanted dimensions value = np.squeeze(value) return value
def Percentile(Mstar=None, Omega=None, Prot=None, percentile=None, MstarDist=None, OmegaDist=None, ProtDist=None, params=params.paramsDefault): """ Gets rotation rate of percentile or percentile of rotation rate in the model rotation distribution. This function can be used for two purposes 1. to determine the percentile in a rotation distribution of a star given its mass and rotation rate 2. to determine the rotation rate of a star in a rotation distribution given its mass and percentile In the first case, the user should specify the rotation rate using either the Omega or Prot keyword arguments (given in OmegaSun and days respectively) and the mass. In the second case, the user should specify the percentile using the percentile keyword argument (in the range 0 to 100) and the mass. The mass should be specified in Msun using the Mstar keyword argument. These should only be given as floats. The user can also specify the rotation distribution using the MstarDist and the OmegaDist or ProtDist keyword arguments. If this is not done, then the code will assume the 1 Myr rotation distribution from the empirical model distribution used in Johnstone et al. (2020). Parameters ---------- Mstar : float Stellar mass in Msun. Omega : float , optional Rotation rate of star in OmegaSun. Prot : float , optional Rotation period of star in days. percentile : float , optional percentile in distribution (between 0 and 100). MstarDist : numpy.ndarray , optional Array of masses of stars in distribution. OmegaDist : numpy.ndarray , optional Array of rotation rates of stars in distribution in OmegaSun. ProtDist : numpy.ndarray , optional Array of rotation periods of stars in distribution in days. params : dict , optional Parameter dictionary used for getting width of bin to use for distribution. Returns ---------- result : float Either rotation rate for percentile or percentile for rotation rate. """ # Make sure Mstar was set if Mstar is None: misc._PrintErrorKill("keyword argument Mstar must be set") # If percentile is not set, make sure rotation rate is set if percentile is None: # Not both if not Omega is None: if not Prot is None: misc._PrintErrorKill( "keyword arguments Omega and Prot cannot both be set") # At least one if (Omega is None) and (Prot is None): misc._PrintErrorKill( "must set either Omega, Prot, or percentile keyword arguments") # Set Omega is not set then set it if Omega is None: Omega = phys._Omega(Prot) # Setup the distribution if MstarDist is None: MstarDist, OmegaDist = misc.ModelCluster() else: # Check that one of OmegaDist and ProtDist are set if (OmegaDist is None) and (ProtDist is None): misc._PrintErrorKill( "one of keyword arguments OmegaDist and ProtDist must be set") # Check that OmegaDist and ProtDist are not both set if (not OmegaDist is None) and (not ProtDist is None): misc._PrintErrorKill( "keyword arguments OmegaDist and ProtDist cannot both be set") # Setup OmegaDist if ProtDist was set if OmegaDist is None: OmegaDist = misc._Omega(ProtDist) # Make sure arrays are same length if not (len(MstarDist) == len(OmegaDist)): misc._PrintErrorKill( "MstarDist and OmegaDist (or ProtDist) must be same length") # Work out which one to do if not percentile is None: # percentile was set, so get corresponding rotation rates result = _OmegaPercentile(Mstar, percentile, MstarDist, OmegaDist, params) else: # Rotation rate was set, so get corresponding percentile result = _PerPercentile(Mstar, Omega, MstarDist, OmegaDist, params) return result
def _InputRotation(Mstar, Age, Omega, OmegaEnv, OmegaCore, Prot, percentile, params): """Takes input rotation values, checks sets up values correctly.""" # Make sure percentile and rotation are not both set if not percentile is None: if not Omega is None: misc._PrintErrorKill( "cannot set both percentile and Omega as arguments of Star") if not OmegaEnv is None: misc._PrintErrorKill( "cannot set both percentile and OmegaEnv as arguments of Star") if not OmegaCore is None: misc._PrintErrorKill( "cannot set both percentile and OmegaCore as arguments of Star" ) if not Prot is None: misc._PrintErrorKill( "cannot set both percentile and Prot as arguments of Star") # Make sure percentile and OmegaEnv are not both set if (not percentile is None) and (not OmegaEnv is None): misc._PrintErrorKill( "cannot set both percentile and OmegaEnv as arguments of Star") # If percentile was set to a string, get float version if isinstance(percentile, str): if (percentile == 'slow'): percentile = 5.0 elif (percentile == 'medium'): percentile = 50.0 elif (percentile == 'fast'): percentile = 95.0 else: misc._PrintErrorKill( "invalid percentile string (options are 'slow', 'medium', or 'fast')" ) # If percentile was set, get Omega if not percentile is None: Omega = Percentile(Mstar=Mstar, percentile=percentile, params=params) # Make sure if Age is set that OmegaCore is not set if (not Age is None) and (not OmegaCore is None): misc._PrintErrorKill( "cannot set both Age and OmegaCore as arguments of Star") # Make sure not both Omega and Prot were set if (not Omega is None) and (not Prot is None): misc._PrintErrorKill( "cannot set both Omega and Prot as arguments of Star") # If Prot is set, get Omega if not Prot is None: Omega = phys._Omega(Prot) # Make sure at least one rotation rate is set if (Omega is None): # If Age is set then make sure OmegaEnv is set, otherwise make sure both OmegaEnv and OmegaCore are set if (not Age is None): if (OmegaEnv is None): misc._PrintErrorKill( "if Age is set then must set either Omega or OmegaEnv as argument of Star" ) elif (OmegaEnv is None) or (OmegaCore is None): misc._PrintErrorKill( "must set either Omega or both OmegaEnv and OmegaCore as argument of Star" ) else: # Since Omega is set, make sure both OmegaEnv and OmegaCore are not set if not ((OmegaEnv is None) and (OmegaCore is None)): misc._PrintErrorKill( "cannot set OmegaEnv and OmegaCore when Omega is set as arugment of Star" ) # Set both OmegaEnv and OmegaCore to Omega OmegaEnv = Omega OmegaCore = Omega return Omega, OmegaEnv, OmegaCore