Package enrichpy :: Module lifetime

Source Code for Module enrichpy.lifetime

  1  """Stellar lifetimes as a function of stellar mass, and related functions. 
  2  """ 
  3  import math 
  4   
  5  import numpy 
  6  import scipy.interpolate 
  7   
8 -def main_sequence_life(mass):
9 """Rough approxiamtion of main sequence lifetime in years. 10 11 tauMS ~ 10^10 years * (M/Msun)^-2.5 12 13 mass in solar masses. 14 """ 15 return 1.e10 * mass**-2.5
16
17 -def mass_from_main_sequence_life(life):
18 """Approximate mass given main sequence lifetime in years. 19 20 tauMS ~ 10^10 years * (M/Msun)^-2.5 21 22 mass in solar masses. 23 """ 24 return (life/1.e10)**(-1./(2.5))
25
26 -def invert_function(function, xmin, xmax, xstep, bounds_error=False):
27 """Invert function y = F(x) using interpolation. 28 29 Returns an interpolation function giving x as a function of y. 30 """ 31 xi = numpy.arange(xmin, xmax + xstep, xstep) 32 yi = function(xi) 33 ifunc = scipy.interpolate.interp1d(yi, xi, bounds_error=bounds_error) 34 return ifunc
35
36 -def invert_function_log(function, logxmin, logxmax, logxstep, 37 bounds_error=False):
38 """Invert function y = F(x) using interpolation. 39 40 Internally uses logarithmic scale in the x values and in 41 performing the interpolation. 42 43 Returns 44 ------- 45 46 an interpolation function giving x as a function of y. NOTE: 47 provide y, don't provide log(y). 48 49 """ 50 logxi = numpy.arange(logxmin, logxmax + logxstep, 51 logxstep) 52 logyi = numpy.log10(function(10.**logxi)) 53 logifunc = scipy.interpolate.interp1d(logyi, logxi, 54 bounds_error=bounds_error) 55 ifunc = lambda y: 10.**logifunc(numpy.log10(y)) 56 return ifunc
57
58 -def invert_function_semilogy(function, xmin, xmax, xstep, 59 bounds_error=False):
60 """Invert function y = F(x) using interpolation. 61 62 Internally uses logarithmic scale in the y values. 63 64 Returns 65 ------- 66 67 an interpolation function giving x as a function of y. NOTE: 68 provide y, don't provide log(y). 69 70 """ 71 xi = numpy.arange(xmin, xmax + xstep, 72 xstep) 73 logyi = numpy.log10(function(xi)) 74 ifunclog = scipy.interpolate.interp1d(logyi, xi, 75 bounds_error=bounds_error) 76 ifunc = lambda y: ifunclog(numpy.log10(y)) 77 return ifunc
78
79 -def mass_from_main_sequence_life_function(function, mmin=0., mmax=1000., 80 mstep=0.01):
81 """Returns a function giving stellar mass from lifetime. 82 83 Uses invert_function_semilogy to define an interpolation function 84 that will return stellar mass when given stellar lifetime. 85 86 """ 87 return invert_function_semilogy(function, mmax, mmin, -1. * mstep)
88
89 -def main_sequence_life_MM89(mass):
90 """Lifetime in years from Maeder & Meynet (1989) via Romano et al. (2005). 91 92 The minlife attribute gives the minimum lifetime for a star as M->inf. 93 94 Parameters 95 ---------- 96 mass (1D array): 97 Stellar mass in solar masses. 98 99 Notes 100 ----- 101 102 From Maeder & Meynet (1989), via Romano, Chiappini, Matteucci, 103 Tosi, 2005, A\&A, 430, 491 (2005A&A...430..491R), who say: 104 105 :: 106 107 t = 108 10^(-0.6545 logm + 1) for m <= 1.3 MSun 109 10^(-3.7 logm + 1.35) for 1.3 < m/MSun <= 3 110 10^(-2.51 logm + 0.77) for 3 < m/MSun <= 7 111 10^(-1.78 logm + 0.17) for 7 < m/MSun <= 15 112 10^(-0.86 logm - 0.94) for 15 < m/MSun <= 60 113 1.2 m^-1.85 + 0.003 for m > 60 MSun 114 with t in units of Gyr. 115 116 """ 117 118 import numpy 119 120 mass = numpy.atleast_1d(mass) 121 122 if mass.ndim > 1: 123 raise ValueError("mass must be a 1D array or scalar.") 124 125 logm = numpy.log10(mass) 126 127 logmfactor = numpy.array([[-0.6545, -3.7, -2.51, -1.78, -0.86]]).transpose() 128 constant = numpy.array([[1., 1.35, 0.77, 0.17, -0.94]]).transpose() 129 mlimit = numpy.array([[0., 1.3, 3., 7., 15., 60.]]).transpose() 130 131 t_star = numpy.sum(10**(logmfactor * logm + constant) * 132 ((mass > mlimit[0:-1]) * (mass <= mlimit[1:])), 133 axis=0) 134 t_star[mass > 60.] = 1.2 * mass[mass > 60.]**-1.85 + 0.003 135 return 1.e9 * t_star
136 main_sequence_life_MM89.minlife = 0.003 * 1.e9 137
138 -def main_sequence_life_K97(mass):
139 """Lifetime in years from Kodama (1997) via Romano et al. (2005). 140 141 The minlife attribute gives the minimum lifetime for a star as M->inf. 142 143 Parameters 144 ---------- 145 mass: array 146 Stellar mass in solar masses. 147 148 Notes 149 ----- 150 151 50 for m <= 0.56 MSun 152 10^((0.334 - sqrt(1.790 - 0.2232*(7.764 - logm)))/0.1116) for m <= 6.6 MSun 153 1.2 m^-1.85 + 0.003 for m > 6.6 MSun 154 """ 155 import numpy 156 mass = numpy.atleast_1d(mass) 157 t = numpy.empty(mass.shape) 158 159 t[mass <= 0.56] = 50. 160 imask = numpy.logical_and(mass > 0.56, mass <= 6.6) 161 t[imask] = \ 162 10.**((0.334 - numpy.sqrt(1.790 - 163 0.2232*(7.764 - numpy.log10(mass[imask]))) 164 ) / 0.1116) 165 t[mass > 6.6] = 1.2 * mass[mass > 6.6]**-1.85 + 0.003 166 167 # Make sure the max lifetime is preserved. 168 t[t>50.] = 50. 169 170 return 1.e9 * t
171 main_sequence_life_K97.minlife = 0.003 * 1.e9 172 main_sequence_life_K97.maxlife = 50. * 1e9 173
174 -def test_conversion(lifetime_function):
175 """Tests conversion of mass into lifetime and back. Note that this 176 test is not passed right now because of the numerical 177 discontinuties in the lifetime functions where their different 178 segments meet up.""" 179 print lifetime_function.__name__ 180 import numpy.testing.utils as ntest 181 mass = numpy.arange(0., 200., 0.1) 182 time_from_mass = lifetime_function(mass) 183 mass_from_time_func = \ 184 mass_from_main_sequence_life_function(lifetime_function) 185 mass_from_time_from_mass = mass_from_time_func(time_from_mass) 186 187 frac_diff = (mass_from_time_from_mass-mass)/mass 188 189 pylab.subplot(221) 190 pylab.plot(mass, mass_from_time_from_mass) 191 192 pylab.subplot(222) 193 pylab.plot(mass, time_from_mass, label=lifetime_function.__name__) 194 pylab.yscale('log') 195 pylab.legend(loc='best') 196 197 pylab.subplot(223) 198 pylab.plot(mass, frac_diff) 199 200 goodmask = numpy.ones(len(time_from_mass), dtype=numpy.bool) 201 if hasattr(lifetime_function, 'minlife'): 202 goodmask[time_from_mass <= lifetime_function.minlife] = False 203 if hasattr(lifetime_function, 'maxlife'): 204 goodmask[time_from_mass >= lifetime_function.maxlife] = False 205 206 goodmask[numpy.logical_not(numpy.isfinite(time_from_mass))] = False 207 208 print "Max frac. diff: ", 209 print numpy.nanmax(numpy.abs(frac_diff[goodmask])) 210 print "SKIPPING TEST THAT FAILS!!!!" 211 if (False): 212 threshold = 1e-9 213 ntest.assert_array_less(numpy.abs(frac_diff[goodmask]), 214 threshold * numpy.ones(len(frac_diff[goodmask])), 215 err_msg='Error in mass recovery exceeds %s' % 216 threshold) 217 print 'Mass recovery discrepency is below %s' % threshold
218 219 if __name__ == '__main__': 220 221 import os 222 import sys 223 224 ### Argument parsing. ### 225 if len(sys.argv)==1: 226 print "Run with a filename argument to produce image files, e.g.:" 227 print " python lifetime.py lifetime.png" 228 if len(sys.argv) > 1: 229 filename = sys.argv[1] 230 prefix, extension = os.path.splitext(filename) 231 else: 232 filename = None 233 234 # Get a list of all the main_sequence_life_* functions. 235 module = sys.modules[__name__] 236 functionlist = [getattr(module, name) for name in dir(module) if name.startswith('main_sequence_life_')] 237 238 import pylab 239 #import scipy 240 #import scipy.integrate 241 # Plot the functions by mass. 242 for function in functionlist: 243 pylab.figure(figsize=(11,6)) 244 test_conversion(function) 245 246 ### Plot output code. ### 247 if filename is None: 248 pylab.show() 249 else: 250 from matplotlib import _pylab_helpers 251 for manager in _pylab_helpers.Gcf.get_all_fig_managers(): 252 fig = manager.canvas.figure 253 if len(fig.get_label()) > 0: 254 label = fig.get_label() 255 else: 256 label = '_Fig' + str(manager.num) 257 newfilename = prefix + '_' + label + extension 258 fig.savefig(newfilename, bbox_inches="tight") 259