1
2 """
3 Interface to the limb-darkening library.
4
5 Section 1. Basic interface
6 ==========================
7
8 Retrieve limb darkening coeffcients and goodness of fit parameters:
9
10 >>> coefs,fac,ssr,idiff = get_coeffs(teff=10000,logg=4.0,'JOHNSON.V',law='claret')
11
12 When you need this for a large grid of parameters, consider using L{get_itable}.
13 The available laws are C{claret}, C{linear}, C{logarithmic}, C{quadratic} and
14 C{power}.
15
16 Retrieve a normalised passband-integrated limb darkening profile via:
17
18 >>> mu,intensities = get_limbdarkening(teff=10000,logg=4.0,photbands=['JOHNSON.V'],normalised=True)
19
20 and fit a particular law via:
21
22 >>> coeffs,ssr,idiff = fit_law(mu,intensities[:,0],law='claret',fitmethod='equidist_mu_leastsq')
23 >>> print(coeffs)
24 [ 2.00943266 -3.71261279 4.25469623 -1.70466934]
25
26 You can evaluate this law on a particular angle-sampling grid:
27
28 >>> mus = np.linspace(0,1,1000)
29 >>> mu_law = ld_claret(mus,coeffs)
30
31 Or on a disk-sampling grid:
32
33 >>> rs = np.linspace(0,1,1000)
34 >>> r_law = ld_claret(_mu(rs),coeffs)
35
36 Make a simple plot:
37
38 >>> p = pl.figure()
39 >>> p,q = pl.subplot(121),pl.title('Angle-sampled')
40 >>> p = pl.plot(mu,intensities,'ko-')
41 >>> p = pl.plot(mus,mu_law,'r-',lw=2)
42 >>> p,q = pl.subplot(122),pl.title('Disk-sampled')
43 >>> p = pl.plot(_r(mu),intensities,'ko-')
44 >>> p = pl.plot(rs,r_law,'r-',lw=2)
45
46 ]include figure]]ivs_limbdark_basic.png]
47
48 Section 2. Fit comparison
49 =========================
50
51 You can fit a limb darkening law in various ways: using different laws, different
52 optimizers and different x-coordinates (limb angle mu or radius). In the figure
53 below, you can see the influence of some of these choices:
54
55 >>> p = pl.figure()
56
57 >>> laws = ['claret','linear','logarithmic','quadratic','power']
58 >>> fitmethods = ['equidist_mu_leastsq','equidist_r_leastsq','leastsq']
59
60 >>> r,rs = _r(mu),_r(mus)
61 >>> integ_mu = np.trapz(intensities[:,0],x=mu)
62 >>> integ_r = np.trapz(intensities[:,0],x=r)
63 >>> for i,fitmethod in enumerate(fitmethods):
64 ... p = pl.subplot(3,2,2*i+1)
65 ... p = pl.title(fitmethod)
66 ... p = pl.plot(mu,intensities[:,0],'ko-')
67 ... p = pl.xlabel('$\mu$ = cos(limb angle)')
68 ... p = pl.ylabel('Normalised intensity')
69 ... p = pl.subplot(3,2,2*i+2)
70 ... p = pl.title(fitmethod)
71 ... p = pl.plot(r,intensities[:,0],'ko-')
72 ... p = pl.xlabel('r')
73 ... p = pl.ylabel('Normalised intensity')
74 ... for law in laws:
75 ... cl,ssr,idiff = fit_law(mu,intensities[:,0],law=law,fitmethod=fitmethod)
76 ... print("{:12s} ({:19s}): {}".format(law,fitmethod,cl))
77 ... Imus = globals()['ld_{}'.format(law)](mus,cl)
78 ... p = pl.subplot(3,2,2*i+1)
79 ... idiff = (np.trapz(Imus,x=mus)-integ_mu)/integ_mu
80 ... p = pl.plot(mus,Imus,'-',lw=2,label="{} ({:.3f},{:.3f}%)".format(law,ssr,idiff*100))
81 ... p = pl.subplot(3,2,2*i+2)
82 ... idiff = (np.trapz(Imus,x=rs)-integ_r)/integ_r
83 ... p = pl.plot(rs,Imus,'-',lw=2,label="{} ({:.3f},{:.3}%)".format(law,ssr,idiff*100))
84 ... p = pl.subplot(3,2,2*i+1)
85 ... leg = pl.legend(loc='best',fancybox=True,prop=dict(size='small'))
86 ... leg.get_frame().set_alpha(0.5)
87 ... p = pl.subplot(3,2,2*i+2)
88 ... leg = pl.legend(loc='best',fancybox=True,prop=dict(size='small'))
89 ... leg.get_frame().set_alpha(0.5)
90 claret (equidist_mu_leastsq): [ 2.00943266 -3.71261279 4.25469623 -1.70466934]
91 linear (equidist_mu_leastsq): [ 0.48655636]
92 divide by zero encountered in log
93 invalid value encountered in multiply
94 logarithmic (equidist_mu_leastsq): [ 0.6 0. ]
95 quadratic (equidist_mu_leastsq): [ 0.21209044 0.36591797]
96 power (equidist_mu_leastsq): [ 0.29025864]
97 claret (equidist_r_leastsq ): [ 1.99805089 -3.04504952 3.02780048 -1.09105603]
98 linear (equidist_r_leastsq ): [ 0.42641628]
99 logarithmic (equidist_r_leastsq ): [ 0.6 0. ]
100 quadratic (equidist_r_leastsq ): [ 0.28650391 0.24476527]
101 power (equidist_r_leastsq ): [ 0.31195553]
102 claret (leastsq ): [ 3.49169665 -8.96767096 11.16873314 -4.72869922]
103 linear (leastsq ): [ 0.57180245]
104 logarithmic (leastsq ): [ 0.6 0. ]
105 quadratic (leastsq ): [ 0.00717934 0.65416204]
106 power (leastsq ): [ 0.27032565]
107
108 ]include figure]]ivs_limbdark_fitcomp.png]
109
110 Author: Pieter Degroote, with thanks to Steven Bloemen.
111 """
112 import logging
113 import os
114 import itertools
115 import astropy.io.fits as pf
116 import numpy as np
117 from scipy.optimize import leastsq,fmin
118 from scipy.interpolate import splrep, splev
119 from scipy.interpolate import LinearNDInterpolator
120 from Scientific.Functions.Interpolation import InterpolatingFunction
121 from ivs.aux import loggers
122 from ivs.sed import reddening
123 from ivs.sed import model
124 from ivs.spectra import tools
125 from ivs.units import constants
126 from ivs.aux.decorators import memoized,clear_memoization
127 from ivs import config
128
129 logger = logging.getLogger("SED.LIMBDARK")
130 logger.addHandler(loggers.NullHandler())
131
132
133 defaults = dict(grid='kurucz',odfnew=False,z=+0.0,vturb=2,
134 alpha=False,nover=False)
135
136 basedir = 'ldtables'
141 """
142 Set defaults of this module
143
144 If you give no keyword arguments, the default values will be reset.
145 """
146 clear_memoization(keys=['ivs.sed.ld'])
147 if not kwargs:
148 kwargs = dict(grid='kurucz',odfnew=True,z=+0.0,vturb=2,
149 alpha=False,nover=False,
150 He=97,
151 t=1.0,a=0.0,c=0.5,m=1.0,co=1.05)
152
153 for key in kwargs:
154 if key in defaults:
155 defaults[key] = kwargs[key]
156 logger.info('Set %s to %s'%(key,kwargs[key]))
157
161 """
162 Return a list of available grid names.
163
164 @return: list of grid names
165 @rtype: list of str
166 """
167 return ['kurucz']
168
171 """
172 Retrieve the possible effective temperatures and gravities from a grid.
173
174 @rtype: (ndarray,ndarray)
175 @return: effective temperatures, gravities
176 """
177 gridfile = get_file(**kwargs)
178 ff = pf.open(gridfile)
179 teffs = []
180 loggs = []
181 for mod in ff[1:]:
182 teffs.append(float(mod.header['TEFF']))
183 loggs.append(float(mod.header['LOGG']))
184 ff.close()
185 return np.array(teffs),np.array(loggs)
186
187
188 @memoized
189 -def get_grid_mesh(wave=None,teffrange=None,loggrange=None,**kwargs):
190 """
191 Return InterpolatingFunction spanning the available grid of atmosphere models.
192
193 WARNING: the grid must be entirely defined on a mesh grid, but it does not
194 need to be equidistant.
195
196 It is thus the user's responsibility to know whether the grid is evenly
197 spaced in logg and teff (e.g. this is not so for the CMFGEN models).
198
199 You can supply your own wavelength range, since the grid models'
200 resolution are not necessarily homogeneous. If not, the first wavelength
201 array found in the grid will be used as a template.
202
203 It might take a long a time and cost a lot of memory if you load the entire
204 grid. Therefor, you can also set range of temperature and gravity.
205
206 @param wave: wavelength to define the grid on
207 @type wave: ndarray
208 @param teffrange: starting and ending of the grid in teff
209 @type teffrange: tuple of floats
210 @param loggrange: starting and ending of the grid in logg
211 @type loggrange: tuple of floats
212 @return: wavelengths, teffs, loggs and fluxes of grid, and the interpolating
213 function
214 @rtype: (3x1Darray,3Darray,interp_func)
215 """
216
217 teffs,loggs = get_grid_dimensions(**kwargs)
218
219
220 if teffrange is not None:
221 sa = (teffrange[0]<=teffs) & (teffs<=teffrange[1])
222 teffs = teffs[sa]
223 if loggrange is not None:
224 sa = (loggrange[0]<=loggs) & (loggs<=loggrange[1])
225 loggs = loggs[sa]
226
227
228
229 gridfile = get_file(**kwargs)
230
231 if wave is not None:
232 table = np.zeros((len(teffs),len(wave),18))
233 ff = pf.open(gridfile)
234 for i,(teff,logg) in enumerate(zip(teffs,loggs)):
235 mod_name = "T%05d_logg%01.02f" %(teff,logg)
236 mod = ff[mod_name]
237 imu = np.array(mod.columns.names[1:], float)
238 itable = np.array(mod.data.tolist())[:,1:]
239 iwave = mod.data.field('wavelength')
240
241
242
243 if wave is None:
244 wave = iwave
245 table = np.zeros((len(teffs),len(wave),len(imu)))
246 try:
247 table[i] = itable
248 except:
249 for j,jimu in enumerate(imu):
250 table[i,:,j] = np.interp(wave,iwave,itable[:,j])
251 ff.close()
252 table_grid = LinearNDInterpolator(np.array([np.log10(teffs),loggs]).T,table)
253 return imu,wave,teffs,loggs,table,table_grid
254
255
256
257 -def get_file(integrated=False,**kwargs):
258 """
259 Retrieve the filename containing the specified SED grid.
260
261 The keyword arguments are specific to the kind of grid you're using.
262
263 Basic keywords are 'grid' for the name of the grid, and 'z' for metallicity.
264 For other keywords, see the source code.
265
266 Available grids and example keywords:
267 - grid='kurucz93':
268 * metallicity (z): m01 is -0.1 log metal abundance relative to solar (solar abundances from Anders and Grevesse 1989)
269 * metallicity (z): p01 is +0.1 log metal abundance relative to solar (solar abundances from Anders and Grevesse 1989)
270 * alpha enhancement (alpha): True means alpha enhanced (+0.4)
271 * turbulent velocity (vturb): vturb in km/s
272 * nover= True means no overshoot
273 * odfnew=True means no overshoot but with better opacities and abundances
274
275 @param integrated: choose integrated version of the grid
276 @type integrated: boolean
277 @keyword grid: gridname (default Kurucz)
278 @type grid: str
279 @return: gridfile
280 @rtype: str
281 """
282
283 grid = kwargs.get('grid',defaults['grid'])
284 if os.path.isfile(grid):
285 return grid
286
287
288 z = kwargs.get('z',defaults['z'])
289
290 vturb = int(kwargs.get('vturb',defaults['vturb']))
291 odfnew = kwargs.get('odfnew',defaults['odfnew'])
292 alpha = kwargs.get('alpha',defaults['alpha'])
293 nover = kwargs.get('nover',defaults['nover'])
294
295
296 if grid=='kurucz':
297 if not isinstance(z,str): z = '%.1f'%(z)
298 if not isinstance(vturb,str): vturb = '%d'%(vturb)
299 if not alpha and not nover and not odfnew:
300 basename = 'kurucz93_z%s_k%s_ld.fits'%(z,vturb)
301 elif alpha and odfnew:
302 basename = 'kurucz93_z%s_ak%sodfnew_ld.fits'%(z,vturb)
303 elif odfnew:
304 basename = 'kurucz93_z%s_k%sodfnew_ld.fits'%(z,vturb)
305 elif nover:
306 basename = 'kurucz93_z%s_k%snover_ld.fits'%(z,vturb)
307 else:
308 basename = grid
309
310
311 if not '*' in basename:
312 if integrated:
313 grid = config.get_datafile(basedir,'i'+basename)
314 else:
315 grid = config.get_datafile(basedir,basename)
316
317 else:
318 grid = config.glob(basedir,'i'+basename)
319 if integrated:
320 grid = config.glob(basedir,'i'+basename)
321 else:
322 grid = config.glob(basedir,basename)
323 logger.debug('Selected %s'%(grid))
324
325 return grid
326
327
328
329 -def get_table(teff=None,logg=None,ebv=None,vrad=None,star=None,
330 wave_units='AA',flux_units='erg/cm2/s/AA/sr',**kwargs):
331 """
332 Retrieve the specific intensity of a model atmosphere.
333
334 ebv is reddening
335 vrad is radial velocity: positive is redshift, negative is blueshift (km/s!)
336
337 extra kwargs are for reddening
338
339 You get limb angles, wavelength and a table. The shape of the table is
340 (N_wave,N_mu).
341
342 WARNING: wave and flux units cannot be specificed for the moment.
343
344 >>> mu,wave,table = get_table(10000,4.0)
345
346 >>> p = pl.figure()
347 >>> ax1 = pl.subplot(221)
348 >>> p = pl.title('E(B-V)=0, vrad=0')
349 >>> ax = pl.gca()
350 >>> ax.set_color_cycle([pl.cm.spectral(i) for i in np.linspace(0,1,len(mu))])
351 >>> p = pl.loglog(wave,table)
352 >>> p = pl.xlim(700,15000)
353 >>> p = pl.ylim(1e3,1e8)
354
355 >>> mu,wave,table = get_table(10000,4.0,ebv=0.5)
356
357 >>> p = pl.subplot(222,sharex=ax1,sharey=ax1)
358 >>> p = pl.title('E(B-V)=0.5, vrad=0')
359 >>> ax = pl.gca()
360 >>> ax.set_color_cycle([pl.cm.spectral(i) for i in np.linspace(0,1,len(mu))])
361 >>> p = pl.loglog(wave,table)
362 >>> p = pl.xlim(700,15000)
363 >>> p = pl.ylim(1e3,1e8)
364
365 >>> mu,wave,table = get_table(10000,4.0,vrad=-1000.)
366
367 >>> p = pl.subplot(223,sharex=ax1,sharey=ax1)
368 >>> p = pl.title('E(B-V)=0., vrad=-10000.')
369 >>> ax = pl.gca()
370 >>> ax.set_color_cycle([pl.cm.spectral(i) for i in np.linspace(0,1,len(mu))])
371 >>> p = pl.loglog(wave,table)
372 >>> p = pl.xlim(700,15000)
373 >>> p = pl.ylim(1e3,1e8)
374
375 >>> mu,wave,table = get_table(10000,4.0,vrad=-1000.,ebv=0.5)
376
377 >>> p = pl.subplot(224,sharex=ax1,sharey=ax1)
378 >>> p = pl.title('E(B-V)=0.5, vrad=-10000.')
379 >>> ax = pl.gca()
380 >>> ax.set_color_cycle([pl.cm.spectral(i) for i in np.linspace(0,1,len(mu))])
381 >>> p = pl.loglog(wave,table)
382 >>> p = pl.xlim(700,15000)
383 >>> p = pl.ylim(1e3,1e8)
384
385 >>> mu,wave,table = get_table(10050,4.12)
386
387 >>> p = pl.figure()
388 >>> pl.gca().set_color_cycle([pl.cm.spectral(i) for i in np.linspace(0,1,len(mu))])
389 >>> p = pl.loglog(wave,table)
390
391
392 @param teff: effective temperature (K)
393 @type teff: float
394 @param logg: log surface gravity (cgs, dex)
395 @type logg: float
396 @param ebv: reddening (mag)
397 @type ebv: float
398 @param vrad: radial velocity (for doppler shifting) (km/s)
399 @type vrad: float
400 @return: mu angles, wavelengths, table (Nwave x Nmu)
401 @rtype: array, array, array
402 """
403
404 gridfile = get_file(**kwargs)
405
406
407 ff = pf.open(gridfile)
408
409 teff = float(teff)
410 logg = float(logg)
411
412
413 try:
414
415 mod_name = "T%05d_logg%01.02f" %(teff,logg)
416 mod = ff[mod_name]
417 mu = np.array(mod.columns.names[1:], float)
418 table = np.array(mod.data.tolist())[:,1:]
419 wave = mod.data.field('wavelength')
420 logger.debug('Model LD taken directly from file (%s)'%(os.path.basename(gridfile)))
421 except KeyError:
422 mu,wave,teffs,loggs,flux,flux_grid = get_grid_mesh(**kwargs)
423 logger.debug('Model LD interpolated from grid %s (%s)'%(os.path.basename(gridfile),kwargs))
424 wave = wave + 0.
425 table = flux_grid(np.log10(teff),logg) + 0.
426
427 ff.close()
428
429
430 if vrad is not None and vrad!=0:
431 cc = constants.cc/1000.
432 for i in range(len(mu)):
433 flux_shift = tools.doppler_shift(wave,vrad,flux=table[:,i])
434 table[:,i] = flux_shift - 5.*vrad/cc*table[:,i]
435
436
437 if ebv is not None and ebv>0:
438 for i in range(len(mu)):
439 table[:,i] = reddening.redden(table[:,i],wave=wave,ebv=ebv,rtype='flux',**kwargs)
440
441
442 return mu,wave,table
443
444
445
446
447
448
449
450
451 -def get_itable2(teff=None,logg=None,theta=None,mu=1,photbands=None,absolute=False,**kwargs):
452 """
453 mu=1 is center of disk
454 """
455 if theta is not None:
456 mu = np.cos(theta)
457
458 try:
459 out = get_ld_grid(photbands,integrated=True,**kwargs)(teff,logg)
460 except ValueError:
461 print 'Used teff and logg',teff,logg
462 raise
463 a1x_,a2x_,a3x_,a4x_, I_x1 = out.reshape((len(photbands),5)).T
464 Imu = ld_eval(mu,[a1x_,a2x_,a3x_,a4x_])
465 if absolute:
466 return Imu*I_x1
467 else:
468 return Imu
469
470 @memoized
471 -def _get_itable_markers(photband,gridfile,
472 teffrange=(-np.inf,np.inf),loggrange=(-np.inf,np.inf)):
473 """
474 Get a list of markers to more easily retrieve integrated fluxes.
475 """
476 clear_memoization()
477
478 ff = pf.open(gridfile)
479 ext = ff[photband]
480 columns = ext.columns.names
481 names = ['teff','logg']
482 grid = [ext.data.field('teff'),
483 ext.data.field('logg')]
484 if 'ebv' in columns:
485 names.append('ebv')
486 grid.append(ext.data.field('ebv'))
487 if 'vrad' in columns:
488 names.append('vrad')
489 grid.append(ext.data.field('vrad'))
490
491 grid_axes = [np.sort(list(set(i))) for i in grid]
492
493
494
495
496 N = len(grid[0])
497 markers = np.zeros(N,float)
498 gridpnts = np.zeros((N,len(grid)))
499 pars = np.zeros((N,5))
500
501 for i,entries in enumerate(zip(*grid)):
502 markers[i] = encode(**{key:entry for key,entry in zip(names,entries)})
503 gridpnts[i]= entries
504 pars[i] = list(ext.data[i][-5:])
505 ff.close()
506 sa = np.argsort(markers)
507 print 'read in gridfile',gridfile
508 pars[:,-1] = np.log10(pars[:,-1])
509 return markers[sa],grid_axes,gridpnts[sa],pars[sa]
510
511
512
513
514 -def get_limbdarkening(teff=None,logg=None,ebv=None,vrad=None,z=None,photbands=None,normalised=False,**kwargs):
515 """
516 Retrieve a limb darkening law for a specific star and specific bandpass.
517
518 Possibility to include reddening via EB-V parameter. If not given,
519 reddening will not be performed...
520
521 You choose your own reddening function.
522
523 See e.g. Heyrovsky et al., 2007
524
525 If you specify one angle (mu in radians), it will take the closest match
526 from the grid.
527
528 Mu = cos(theta) where theta is the angle between the surface and the line
529 of sight. mu=1 means theta=0 means center of the star.
530
531 Example usage:
532
533 >>> teff,logg = 5000,4.5
534 >>> mu,intensities = get_limbdarkening(teff=teff,logg=logg,photbands=['JOHNSON.V'])
535
536 @keyword teff: effective temperature
537 @type teff: float
538 @keyword logg: logarithmic gravity (cgs)
539 @type logg: float
540 @keyword system: bandpass system
541 @type system: string
542 @keyword photbands: bandpass filters
543 @type photbands: list of strings
544 @keyword ebv: reddening coefficient
545 @type ebv: float
546 @keyword vrad: radial velocity (+ is redshift, - is blueshift)
547 @type vrad: float
548 @keyword mu: specificy specific angle
549 @type mu: float
550 """
551 if z is None:
552 z = defaults['z']
553
554 mus,wave,table = get_table(teff=teff,logg=logg,ebv=ebv,vrad=vrad,z=z,**kwargs)
555
556 intensities = np.zeros((len(mus),len(photbands)))
557 for i in range(len(mus)):
558 intensities[i] = model.synthetic_flux(wave,table[:,i],photbands)
559 if normalised:
560 intensities/= intensities.max(axis=0)
561
562 logger.info('Calculated LD')
563 return mus,intensities
564
565 -def get_coeffs(teff,logg,photband,ebv=None,vrad=None,law='claret',fitmethod='equidist_r_leastsq'):
566 """
567 Retrieve limb darkening coefficients on the fly.
568
569 This is particularly useful if you only need a few and speed is not really
570 a problem (although this function is nearly instantaneous, looping over it
571 will expose that it's actually pretty slow).
572
573 @keyword teff: effective temperature
574 @type teff: float
575 @keyword logg: logarithmic gravity (cgs)
576 @type logg: float
577 @keyword photbands: bandpass filters
578 @type photbands: list of strings
579 @keyword ebv: reddening coefficient
580 @type ebv: float
581 @keyword vrad: radial velocity (+ is redshift, - is blueshift)
582 @type vrad: float
583 """
584 mu,intensities = get_limbdarkening(teff=teff,logg=logg,ebv=ebv,vrad=vrad,photbands=[photband])
585 norm_factor = intensities.max(axis=0)
586 coeffs,ssr,idiff = fit_law(mu,intensities[:,0]/norm_factor,law=law,fitmethod=fitmethod)
587 return coeffs,norm_factor,ssr,idiff
588
593 """
594 Returns the gridpoints of the limbdarkening grid (not unique values).
595 """
596
597 gridfile = get_file(**kwargs)
598
599
600 ff = pf.open(gridfile)
601 teff_,logg_ = ff[1].data.field('Teff'),ff[1].data.field('logg')
602 ff.close()
603 return teff_,logg_
604
606 """
607 Calculate the intensity moment (see Townsend 2003, eq. 39).
608
609 Test analytical versus numerical implementation:
610
611 #>>> photband = 'JOHNSON.V'
612 #>>> l,logg = 2.,4.0
613 #>>> gridfile = 'tables/ikurucz93_z0.0_k2_ld.fits'
614 #>>> mu = np.linspace(0,1,100000)
615 #>>> P_l = legendre(l)(mu)
616 #>>> check = []
617 #>>> for teff in np.linspace(9000,12000,19):
618 #... a1x,a2x,a3x,a4x,I_x1 = get_itable(gridfile,teff=teff,logg=logg,mu=1,photband=photband,evaluate=False)
619 #... coeffs = [a1x,a2x,a3x,a4x,I_x1]
620 #... Ix_mu = ld_claret(mu,coeffs)
621 #... Ilx1 = I_x1*np.trapz(P_l*mu*Ix_mu,x=mu)
622 #... a0x = 1 - a1x - a2x - a3x - a4x
623 #... limb_coeffs = [a0x,a1x,a2x,a3x,a4x]
624 #... Ilx2 = 0
625 #... for r,ar in enumerate(limb_coeffs):
626 #... s = 1 + r/2.
627 #... myIls = _I_ls(l,s)
628 #... Ilx2 += ar*myIls
629 #... Ilx2 = I_x1*Ilx2
630 #... Ilx3 = intensity_moment(teff,logg,photband,coeffs)
631 #... check.append(abs(Ilx1-Ilx2)<0.1)
632 #... check.append(abs(Ilx1-Ilx3)<0.1)
633 #>>> np.all(np.array(check))
634 #True
635
636
637 @parameter teff: effecitve temperature (K)
638 @type teff: float
639 @parameter logg: log of surface gravity (cgs)
640 @type logg: float
641 @parameter ll: degree of the mode
642 @type ll: float
643 @parameter photband: photometric passbands
644 @type photband: list of strings ( or iterable container)
645 @keyword full_output: if True, returns intensity, coefficients and integrals
646 separately
647 @type full_output: boolean
648 @return: intensity moment
649 """
650 full_output = kwargs.get('full_output',False)
651
652
653
654 a1x_,a2x_,a3x_,a4x_, I_x1 = coeffs
655 a0x_ = 1 - a1x_ - a2x_ - a3x_ - a4x_
656 limb_coeffs = np.array([a0x_,a1x_,a2x_,a3x_,a4x_])
657
658
659 int_moms = np.array([_I_ls(ll,1 + r/2.) for r in range(0,5,1)])
660
661 I_lx = I_x1 * (limb_coeffs * int_moms).sum(axis=0)
662
663 if full_output:
664 return I_x1,limb_coeffs,int_moms
665 else:
666 return I_lx
667
668
669
670
671
672
673 -def ld_eval(mu,coeffs):
674 """
675 Evaluate Claret's limb darkening law.
676 """
677 return ld_claret(mu,coeffs)
678
680 """
681 Claret's limb darkening law.
682 """
683 a1,a2,a3,a4 = coeffs
684 Imu = 1-a1*(1-mu**0.5)-a2*(1-mu)-a3*(1-mu**1.5)-a4*(1-mu**2.)
685 return Imu
686
688 """
689 Linear or linear cosine law
690 """
691 return 1-coeffs[0]*(1-mu)
692
694 """
695 Nonlinear or logarithmic law
696 """
697 return 1-coeffs[0]*(1-mu)-coeffs[1]*mu*np.log(mu)
698
700 """
701 Nonlinear or logarithmic law
702 """
703 return ld_nonlinear(mu,coeffs)
704
706 """
707 Quadratic law
708 """
709 return 1-coeffs[0]*(1-mu)-coeffs[1]*(1-mu)**2.0
710
716
718 """
719 Power law.
720 """
721 return mu**coeffs[0]
722
723
724
725
726
727 -def fit_law(mu,Imu,law='claret',fitmethod='equidist_r_leastsq'):
728 """
729 Fit an LD law to a sampled set of limb angles/intensities.
730
731 In my (Pieter) experience, C{fitmethod='equidist_r_leastsq' seems
732 appropriate for the Kurucz models.
733
734 Make sure the intensities are normalised!
735
736 >>> mu,intensities = get_limbdarkening(teff=10000,logg=4.0,photbands=['JOHNSON.V'],normalised=True)
737 >>> p = pl.figure()
738 >>> p = pl.plot(mu,intensities[:,0],'ko-')
739 >>> cl,ssr,rdiff = fit_law(mu,intensities[:,0],law='claret')
740
741 >>> mus = np.linspace(0,1,1000)
742 >>> Imus = ld_claret(mus,cl)
743 >>> p = pl.plot(mus,Imus,'r-',lw=2)
744
745
746
747 @return: coefficients, sum of squared residuals,relative flux difference between prediction and model integrated intensity
748 @rtype: array, float, float
749 """
750
751 Ncoeffs = dict(claret=4,linear=1,nonlinear=2,logarithmic=2,quadratic=2,
752 power=1)
753 c0 = np.zeros(Ncoeffs[law])
754 c0[0] = 0.6
755
756 if fitmethod=='leastsq':
757 (csol, ierr) = leastsq(ldres_leastsq, c0, args=(mu,Imu,law))
758 elif fitmethod=='fmin':
759 csol = fmin(ldres_fmin, c0, maxiter=1000, maxfun=2000,args=(mu,Imu,law),disp=0)
760 elif fitmethod=='equidist_mu_leastsq':
761 mu_order = np.argsort(mu)
762 tck = splrep(mu[mu_order],Imu[mu_order],s=0.0, k=2)
763 mu_spl = np.linspace(mu[mu_order][0],1,5000)
764 Imu_spl = splev(mu_spl,tck,der=0)
765 (csol, ierr) = leastsq(ldres_leastsq, c0, args=(mu_spl,Imu_spl,law))
766 elif fitmethod=='equidist_r_leastsq':
767 mu_order = np.argsort(mu)
768 tck = splrep(mu[mu_order],Imu[mu_order],s=0., k=2)
769 r_spl = np.linspace(mu[mu_order][0],1,5000)
770 mu_spl = np.sqrt(1-r_spl**2)
771 Imu_spl = splev(mu_spl,tck,der=0)
772 (csol,ierr) = leastsq(ldres_leastsq, c0, args=(mu_spl,Imu_spl,law))
773 elif fitmethod=='equidist_mu_fmin':
774 mu_order = np.argsort(mu)
775 tck = splrep(mu[mu_order],Imu[mu_order],k=2, s=0.0)
776 mu_spl = np.linspace(mu[mu_order][0],1,5000)
777 Imu_spl = splev(mu_spl,tck,der=0)
778 csol = fmin(ldres_fmin, c0, maxiter=1000, maxfun=2000,args=(mu_spl,Imu_spl,law),disp=0)
779 elif fitmethod=='equidist_r_fmin':
780 mu_order = np.argsort(mu)
781 tck = splrep(mu[mu_order],Imu[mu_order],k=2, s=0.0)
782 r_spl = np.linspace(mu[mu_order][0],1,5000)
783 mu_spl = np.sqrt(1-r_spl**2)
784 Imu_spl = splev(mu_spl,tck,der=0)
785 csol = fmin(ldres_fmin, c0, maxiter=1000, maxfun=2000,args=(mu_spl,Imu_spl,law),disp=0)
786 else:
787 raise ValueError("Fitmethod {} not recognised".format(fitmethod))
788 myfit = globals()['ld_%s'%(law)](mu,csol)
789 res = np.sum(Imu - myfit)**2
790 int1,int2 = np.trapz(Imu,x=mu),np.trapz(myfit,x=mu)
791 dflux = (int1-int2)/int1
792 return csol,res,dflux
793
794
795 -def fit_law_to_grid(photband,vrads=[0],ebvs=[0],zs=[0],
796 law='claret',fitmethod='equidist_r_leastsq',**kwargs):
797 """
798 Gets the grid and fits LD law to all the models.
799
800 Does not use mu=0 point in the fitting process.
801 """
802 teffs, loggs = get_grid_dimensions(**kwargs)
803 teffs=teffs
804 loggs=loggs
805 grid_pars = []
806 grid_coeffs = []
807 Imu1s = []
808 logger.info('Fitting photband {}'.format(photband))
809 for teff_, logg_ in zip(teffs, loggs):
810 for ebv_,vrad_,z_ in itertools.product(ebvs,vrads,zs):
811 logger.info('teff={}, logg={}, ebv={}, vrad={}, z={}'.format(teff_, logg_,ebv_,vrad_,z_))
812 mu, Imu = get_limbdarkening(teff=teff_, logg=logg_, ebv=ebv_,vrad=vrad_,z=z_,photbands=[photband],**kwargs)
813 Imu1 = Imu.max()
814 Imu = Imu[:,0]/Imu1
815 coeffs,res,dflux = fit_law(mu[mu>0], Imu[mu>0],law=law,fitmethod=fitmethod)
816 grid_coeffs.append(coeffs)
817 grid_pars.append([teff_,logg_,ebv_,vrad_,z_])
818 Imu1s.append([Imu1,res,dflux])
819
820 grid_pars = np.array(grid_pars)
821 grid_coeffs = np.array(grid_coeffs)
822 Imu1s = np.array(Imu1s)
823
824 return grid_pars, grid_coeffs, Imu1s
825
826 -def generate_grid(photbands,vrads=[0],ebvs=[0],zs=[0],
827 law='claret',fitmethod='equidist_r_leastsq',outfile='mygrid.fits',**kwargs):
828
829 if os.path.isfile(outfile):
830 hdulist = pf.open(outfile,mode='update')
831 existing_bands = [ext.header['extname'] for ext in hdulist[1:]]
832 else:
833 hdulist = pf.HDUList([])
834 hdulist.append(pf.PrimaryHDU(np.array([[0,0]])))
835 existing_bands = []
836
837 hd = hdulist[0].header
838 hd.update('FIT', fitmethod, 'FIT ROUTINE')
839 hd.update('LAW', law, 'FITTED LD LAW')
840 hd.update('GRID', kwargs.get('grid',defaults['grid']), 'GRID')
841
842 for photband in photbands:
843 if photband in existing_bands:
844 logger.info('BAND {} already exists: skipping'.format(photband))
845 continue
846 pars,coeffs,Imu1s = fit_law_to_grid(photband,vrads=vrads,ebvs=ebvs,zs=zs,**kwargs)
847 cols = []
848
849 cols.append(pf.Column(name='Teff', format='E', array=pars[:,0]))
850 cols.append(pf.Column(name="logg", format='E', array=pars[:,1]))
851 cols.append(pf.Column(name="ebv" , format='E', array=pars[:,2]))
852 cols.append(pf.Column(name="vrad", format='E', array=pars[:,3]))
853 cols.append(pf.Column(name="z" , format='E', array=pars[:,4]))
854 for col in range(coeffs.shape[1]):
855 cols.append(pf.Column(name='a{:d}'.format(col+1), format='E', array=coeffs[:,col]))
856 cols.append(pf.Column(name='Imu1', format='E', array=Imu1s[:,0]))
857 cols.append(pf.Column(name='SRS', format='E', array=Imu1s[:,1]))
858 cols.append(pf.Column(name='dint', format='E', array=Imu1s[:,2]))
859
860 newtable = pf.new_table(pf.ColDefs(cols))
861 newtable.header.update('EXTNAME', photband, "SYSTEM.FILTER")
862 newtable.header.update('SYSTEM', photband.split('.')[0], 'PASSBAND SYSTEM')
863 newtable.header.update('FILTER', photband.split('.')[1], 'PASSBAND FILTER')
864 hdulist.append(newtable)
865
866 if os.path.isfile(outfile):
867 hdulist.close()
868 else:
869 hdulist.writeto(outfile)
870
871
872
873
874
875 -def _r(mu):
876 """
877 Convert mu to r coordinates
878 """
879 return np.sqrt(1.-mu**2.)
880
882 """
883 Convert r to mu coordinates
884 """
885 return np.sqrt(1.-r_**2.)
886
888 """
889 Residual function for Nelder-Mead optimizer.
890 """
891 return sum((Imu - globals()['ld_%s'%(law)](mu,coeffs))**2)
892
894 """
895 Residual function for Levenberg-Marquardt optimizer.
896 """
897 return Imu - globals()['ld_%s'%(law)](mu,coeffs)
898
900 """
901 Limb darkening moments (Dziembowski 1977, Townsend 2002)
902
903 Recursive implementation.
904
905 >>> _I_ls(0,0.5)
906 0.6666666666666666
907 >>> _I_ls(1,0.5)
908 0.4
909 >>> _I_ls(2,0.5)
910 0.09523809523809523
911 """
912 if ll==0:
913 return 1./(1.+ss)
914 elif ll==1:
915 return 1./(2.+ss)
916 else:
917 return (ss-ll+2)/(ss+ll-2+3.)*_I_ls(ll-2,ss)
918
921 """
922 Retrieve an interpolating grid for the LD coefficients
923
924 Check outcome:
925
926 #>>> bands = ['GENEVA.U', 'GENEVA.B', 'GENEVA.G', 'GENEVA.V']
927 #>>> f_ld_grid = get_ld_grid(bands)
928 #>>> ff = pf.open(_atmos['file'])
929 #>>> all(ff['GENEVA.U'].data[257][2:]==f_ld_grid(ff['GENEVA.U'].data[257][0],ff['GENEVA.U'].data[257][1])[0:5])
930 #True
931 #>>> all(ff['GENEVA.G'].data[257][2:]==f_ld_grid(ff['GENEVA.G'].data[257][0],ff['GENEVA.G'].data[257][1])[10:15])
932 #True
933 #>>> ff.close()
934
935 #Make some plots:
936
937 #>>> photband = ['GENEVA.V']
938 #>>> f_ld = get_ld_grid(photband)
939 #>>> logg = 4.0
940 #>>> mu = linspace(0,1,100)
941 #>>> p = figure()
942 #>>> p = gcf().canvas.set_window_title('test of function <get_ld_grid>')
943 #>>> for teff in linspace(9000,12000,19):
944 #... out = f_ld(teff,logg)
945 #... a1x,a2x,a3x,a4x, I_x1 = out.reshape((len(photband),5)).T
946 #... p = subplot(221);p = title('Interpolation of absolute intensities')
947 #... p = plot(teff,I_x1,'ko')
948 #... p = subplot(222);p = title('Interpolation of LD coefficients')
949 #... p = scatter(4*[teff],[a1x,a2x,a3x,a4x],c=range(4),vmin=0,vmax=3,cmap=cm.spectral,edgecolors='none')
950 #... p = subplot(223);p = title('Without absolute intensity')
951 #... p = plot(mu,ld_eval(mu,[a1x,a2x,a3x,a4x]),'-')
952 #... p = subplot(224);p = title('With absolute intensity')
953 #... p = plot(mu,I_x1*ld_eval(mu,[a1x,a2x,a3x,a4x]),'-')
954
955 """
956
957 teffs,loggs = get_ld_grid_dimensions(**kwargs)
958 teffs_grid = np.sort(np.unique1d(teffs))
959 loggs_grid = np.sort(np.unique1d(loggs))
960 coeff_grid = np.zeros((len(teffs_grid),len(loggs_grid),5*len(photband)))
961
962
963 gridfile = get_file(**kwargs)
964
965 ff = pf.open(gridfile)
966 for pp,iband in enumerate(photband):
967 teffs = ff[iband].data.field('Teff')
968 loggs = ff[iband].data.field('logg')
969 for ii,(iteff,ilogg) in enumerate(zip(teffs,loggs)):
970 indext = np.searchsorted(teffs_grid,iteff)
971 indexg = np.searchsorted(loggs_grid,ilogg)
972
973
974 coeff_grid[indext,indexg,5*pp:5*(pp+1)] = np.array(list(ff[iband].data[ii]))[2:]
975 ff.close()
976
977 f_ld_grid = InterpolatingFunction([teffs_grid,loggs_grid],coeff_grid)
978 return f_ld_grid
979
980
981 @memoized
982 -def _get_itable_markers(photband,gridfile,
983 teffrange=(-np.inf,np.inf),loggrange=(-np.inf,np.inf)):
984 """
985 Get a list of markers to more easily retrieve integrated fluxes.
986 """
987 clear_memoization()
988
989 ff = pf.open(gridfile)
990 ext = ff[photband]
991 columns = ext.columns.names
992 names = ['teff','logg']
993 grid = [ext.data.field('teff'),
994 ext.data.field('logg')]
995 if 'ebv' in columns:
996 names.append('ebv')
997 grid.append(ext.data.field('ebv'))
998 if 'vrad' in columns:
999 names.append('vrad')
1000 grid.append(ext.data.field('vrad'))
1001
1002 grid_axes = [np.sort(list(set(i))) for i in grid]
1003
1004
1005
1006
1007 N = len(grid[0])
1008 markers = np.zeros(N,float)
1009 gridpnts = np.zeros((N,len(grid)))
1010 pars = np.zeros((N,5))
1011
1012 for i,entries in enumerate(zip(*grid)):
1013 markers[i] = encode(**{key:entry for key,entry in zip(names,entries)})
1014 gridpnts[i]= entries
1015 pars[i] = list(ext.data[i][-5:])
1016 ff.close()
1017 sa = np.argsort(markers)
1018 print 'read in gridfile',gridfile
1019 pars[:,-1] = np.log10(pars[:,-1])
1020 return markers[sa],grid_axes,gridpnts[sa],pars[sa]
1021
1022
1023
1024
1025 if __name__=="__main__":
1026 import sys
1027 if not sys.argv[1:]:
1028 import pylab as pl
1029 import doctest
1030 doctest.testmod()
1031 pl.show()
1032 else:
1033
1034
1035
1036 photbands = ['JOHNSON.U','JOHNSON.B','JOHNSON.V','KEPLER.V','COROT.SIS','COROT.EXO']
1037 photbands+= ['2MASS.J','2MASS.H','2MASS.KS']
1038
1039
1040 generate_grid(['MOST.V','IRAC.36','COROT.EXO'],vrads=[0],ebvs=[0.06],zs=[0,0],
1041 law='claret',fitmethod='equidist_r_leastsq',outfile='HD261903.fits')
1042