Package ivs :: Package roche :: Module binary
[hide private]
[frames] | no frames]

Source Code for Module ivs.roche.binary

   1  """ 
   2  Roche models of binary or multiple stars. 
   3   
   4  You'd better not use this module, but talk to Pieter Degroote. 
   5   
   6  The premisse of the Roche potential is that the stellar mass can be represented 
   7  by a point source. 
   8   
   9  Currently, three types of stellar shapes are implemented: 
  10      1. asynchronously rotating eccentric binary (L{get_binary_roche_radius}) 
  11      2. fast rotating star (L{get_fastrot_roche_radius}) 
  12      3. differentially (fast) rotating star (L{get_diffrot_roche_surface_gravity}) 
  13   
  14  This module can be used to calculate the following information: 
  15      1.  the distorted shape of the star due to a Roche potential 
  16      2.  the local surface gravity assuming Von Zeipel gravity darkening 
  17      3.  the L{local effective temperature<local_temperature>} 
  18      4.  the local velocity vector due to rotation 
  19      5.  the radial velocity 
  20      6.  the total distorted surface area 
  21      7.  the total distorted luminosity 
  22      8.  the L{projected intensity<projected_intensity>} in the line of sight 
  23      9.  the mean radial velocity (e.g. Rossiter effect in binaries) 
  24      10. a L{synthetic spectrum<spectral_synthesis>} starting from a library of spectra 
  25      11. a L{synthetic light curve<binary_light_curve_synthesis>} from the projected intensities 
  26   
  27  Section 1. Binary star 
  28  ====================== 
  29   
  30  As an example we take the binary star SX Aurigae. This is the minimum set of 
  31  parameters. 
  32   
  33  >>> P = 1.2100802        # period in days 
  34  >>> e = 0.               # eccentricity 
  35  >>> q = 0.54369          # mass ratio M2/M1 
  36  >>> asini = 11.9*constants.Rsol/constants.au # semi-major axis 
  37  >>> i = 81.27            # inclination angle 
  38  >>> F = 1.               # synchronicity parameter 
  39  >>> d = 1.               # separation in semi-major axis units 
  40  >>> Phi1 = 3.            # gravitational potential primary 
  41  >>> Phi2 = 5.05          # gravitational potential secondary 
  42  >>> T_pole1 = 25000.     # polar temperature primary 
  43  >>> T_pole2 = 18850.     # polar temperature secondary 
  44  >>> M1 = 10.3            # primary mass 
  45  >>> M2 = 5.6             # secondary mass 
  46   
  47  Note that we actually do not need the masses, since we can derive them from 
  48  the third kepler law with the other parameters. We do so for convenience. 
  49   
  50  Everything is calculated in units of semi-major axis. Thus, to convert to SI 
  51  units we need the following conversion factor: 
  52   
  53  >>> a = asini * sin(i/180.*pi) 
  54  >>> to_SI = a*constants.au 
  55   
  56  We need some constants for the calculations: the polar radii and angular velocity 
  57   
  58  >>> r_pole1 = newton(binary_roche_potential,1e-5,args=(0,0,Phi1,  q,d,F)) 
  59  >>> r_pole2 = newton(binary_roche_potential,1e-5,args=(0,0,Phi2,1/q,d,F)) 
  60  >>> omega_rot = F * 2*pi/(P*24*3600) * 1/d**2 * sqrt( (1+e)*(1-e)) 
  61  >>> omega_rot_vec = np.array([0.,0.,-omega_rot]) 
  62   
  63  Derive the shape of the two stars 
  64   
  65  >>> radius1 = np.array([get_binary_roche_radius(itheta,iphi,Phi=Phi1,q=  q,d=d,F=F,r_pole=r_pole1) for itheta,iphi in zip(thetas,phis)]).reshape(theta.shape) 
  66  >>> radius2 = np.array([get_binary_roche_radius(itheta,iphi,Phi=Phi2,q=1/q,d=d,F=F,r_pole=r_pole2) for itheta,iphi in zip(thetas,phis)]).reshape(theta.shape) 
  67   
  68  We focus on the primary, then repeat everything for the secondary: The local 
  69  surface gravity can only be calculated if we have Cartesian coordinates. 
  70   
  71  >>> x1,y1,z1 = vectors.spher2cart_coord(radius1,phi,theta) 
  72  >>> g_pole1 = binary_roche_surface_gravity(0,0,r_pole1*to_SI,d*to_SI,omega_rot,M1*constants.Msol,M2*constants.Msol,norm=True) 
  73  >>> Gamma_pole1 = binary_roche_potential_gradient(0,0,r_pole1,q,d,F,norm=True) 
  74  >>> zeta1 = g_pole1 / Gamma_pole1 
  75  >>> dOmega1 = binary_roche_potential_gradient(x1,y1,z1,q,d,F,norm=False) 
  76  >>> grav_local1 = dOmega1*zeta1 
  77  >>> grav_local1 = np.array([i.reshape(theta.shape) for i in grav_local1]) 
  78  >>> grav1 = vectors.norm(grav_local1) 
  79   
  80  Now we can, as before, calculate the other local quantities: 
  81   
  82  >>> areas_local1,cos_gamma1 = surface_elements((radius1,theta,phi),-grav_local1) 
  83  >>> teff_local1 = local_temperature(grav1,g_pole1,T_pole1,beta=1.) 
  84  >>> ints_local1 = local_intensity(teff_local1,grav1,np.ones_like(cos_gamma1),photband='OPEN.BOL') 
  85  >>> velo_local1 = np.cross(np.array([x1,y1,z1]).T*to_SI,omega_rot_vec).T 
  86   
  87   
  88  Now make some plots showing the local quantities: 
  89   
  90  >>> quantities = areas_local1,np.log10(grav1*100),teff_local1,ints_local1 
  91  >>> names = 'Area','log g', 'Teff', 'Flux' 
  92  >>> p = pl.figure() 
  93  >>> rows,cols = 2,2     
  94  >>> for i,(quantity,name) in enumerate(zip(quantities,names)): 
  95  ...    p = pl.subplot(rows,cols,i+1) 
  96  ...    p = pl.title(name) 
  97  ...    q = quantity.ravel() 
  98  ...    vmin,vmax = q[-np.isnan(q)].min(),q[-np.isnan(q)].max() 
  99  ...    if vmin==vmax: vmin,vmax = q[-np.isnan(q)].mean()-0.01*q[-np.isnan(q)].mean(),q[-np.isnan(q)].mean()+0.01*q[-np.isnan(q)].mean() 
 100  ...    p = pl.scatter(phis/pi*180,thetas/pi*180,c=q,edgecolors='none',vmin=vmin,vmax=vmax) 
 101  ...    p = pl.colorbar() 
 102  ...    p = pl.xlim(0,360) 
 103  ...    p = pl.ylim(180,0) 
 104   
 105  ]include figure]]ivs_binary_rochepotential_binarystar.png] 
 106   
 107  >>> p = pl.figure() 
 108  >>> p = pl.subplot(121,aspect='equal') 
 109  >>> p = pl.title('top view') 
 110  >>> p = pl.scatter(x1,y1,c=teff_local1.ravel(),edgecolors='none') 
 111  >>> p = pl.subplot(122,aspect='equal') 
 112  >>> p = pl.title('side view') 
 113  >>> p = pl.scatter(x1,z1,c=teff_local1.ravel(),edgecolors='none') 
 114   
 115  ]include figure]]ivs_binary_rochepotential_binarystar_shape.png] 
 116   
 117  Section 5. Synthesizing spectra 
 118  =============================== 
 119   
 120  In this case study, we calculate the projected radial velocity of a uniformly 
 121  rotating star, but in the limit of no deformation due to the rotation. We do this, 
 122  so that we can easily compare rotational broadening calculated with the ROTIN 
 123  package, and numerically. So let us build a star with these parameters: 
 124   
 125  >>> omega = 0.0        # ratio to critical velocity 
 126  >>> T_pole = 13000.    # K 
 127  >>> r_pole = 2.0       # solar radii 
 128  >>> M = 1.5            # solar mass 
 129  >>> view_angle = pi/2  # radians 
 130  >>> theta,phi = get_grid(20,100,full=True,gtype='spher') 
 131  >>> thetas,phis = np.ravel(theta),np.ravel(phi) 
 132   
 133  Then calculate the shape of this star 
 134   
 135  >>> radius = np.array([get_fastrot_roche_radius(itheta,r_pole,omega) for itheta in thetas]).reshape(theta.shape) 
 136  >>> grav_local = np.array([fastrot_roche_surface_gravity(iradius,itheta,iphi,r_pole,omega,M) for iradius,itheta,iphi in zip(radius.ravel(),thetas,phis)]).T 
 137  >>> grav_local = np.array([i.reshape(theta.shape) for i in grav_local]) 
 138  >>> g_pole = fastrot_roche_surface_gravity(r_pole,0,0,r_pole,omega,M)[-1] 
 139  >>> grav = vectors.norm(grav_local) 
 140   
 141  and the local quantities 
 142   
 143  >>> areas_local,cos_gamma = surface_elements((radius,theta,phi),-grav_local) 
 144  >>> teff_local = local_temperature(vectors.norm(grav_local),g_pole,T_pole,beta=1.) 
 145  >>> ints_local = local_intensity(teff_local,grav,photband='OPEN.BOL') 
 146  >>> x,y,z = vectors.spher2cart_coord(radius.ravel(),phis,thetas) 
 147   
 148  Assume, with a shape of a non-rotating star, that we have a velocity component 
 149  on the surface of the star: 
 150   
 151  >>> myomega = 0.5 
 152  >>> velo_local = diffrot_velocity((phi,theta,radius*constants.Rsol),myomega,myomega,r_pole,M) 
 153   
 154  Collect all the necessary information in one record array. 
 155   
 156  >>> starlist = [x,y,z] + [i.ravel() for i in velo_local] + [i.ravel() for i in grav_local] + [teff_local.ravel(),areas_local.ravel()] 
 157  >>> starnames = ['x','y','z','vx','vy','vz','gravx','gravy','gravz','teff','areas'] 
 158  >>> star = np.rec.array(starlist,names=starnames) 
 159   
 160  Project the star in some line-of-sight. The velocity component in the X-direction 
 161  is the radial velocity. 
 162   
 163  >>> view_angle = pi/2 # edge on 
 164  >>> mystar = project(star,view_long=(0,0,0),view_lat=(view_angle,0,0),photband='OPEN.BOL',only_visible=True,plot_sort=True) 
 165   
 166  We can calculate the synthetic spectra for all surface elements between 7055 and 
 167  7075 angstrom: 
 168   
 169  >>> loggs = np.log10(np.sqrt(mystar['gravx']**2 + mystar['gravy']**2 + mystar['gravz']**2)*100) 
 170  >>> iterator = zip(mystar['teff'],loggs,mystar['vx']/1000.) 
 171  >>> wave_spec = np.linspace(7055,7075,750) 
 172  >>> spectra = np.array([spectra_model.get_table(teff=iteff,logg=ilogg,vrad=ivrad,wave=wave_spec)[1] for iteff,ilogg,ivrad in iterator]) 
 173   
 174  The total observed spectrum is then simply the weighted sum with the local projected 
 175  intensities: 
 176   
 177  >>> average_spectrum = np.average(spectra,weights=mystar['projflux'],axis=0) 
 178   
 179  We compare with the ROTIN package, which assumes a linear limbdarkening law 
 180   
 181  >>> original = spectra_model.get_table(teff=mystar['teff'][0],logg=loggs[0],wave=wave_spec)[1] 
 182  >>> rotin1 = spectra_model.get_table(teff=mystar['teff'][0],logg=loggs[0],vrot=vmax/1000.*sin(view_angle),wave=wave_spec,fwhm=0,epsilon=0.6,stepr=-1)[1] 
 183       
 184  And a plot can be made via 
 185   
 186  >>> colors = mystar['eyeflux']/mystar['eyeflux'].max() 
 187  >>> p = pl.figure(figsize=(7,7)) 
 188  >>> ax = pl.axes([0,0.5,1,0.5]) 
 189  >>> p = ax.set_aspect('equal') 
 190  >>> p = ax.set_axis_bgcolor('k') 
 191  >>> p = pl.box(on=False) 
 192  >>> p = pl.xticks([]);p = pl.yticks([]) 
 193  >>> p = pl.scatter(mystar['y'],mystar['z'],c=colors,edgecolors='none',cmap=pl.cm.gray,vmin=0,vmax=1) 
 194  >>> p = pl.scatter(mystar['y']+1.02*mystar['y'].ptp(),mystar['z'],c=mystar['vx'],edgecolors='none',vmin=vmin,vmax=vmax,cmap=pl.cm.RdBu) 
 195  >>> ax = pl.axes([0.13,0.1,0.78,0.4]) 
 196  >>> p = pl.plot(wave_spec,average_spectrum,'k-',lw=2,label='Numerical') 
 197  >>> p = pl.plot(wave_spec,original,'r-',label='No rotation') 
 198  >>> p = pl.plot(wave_spec,rotin1,'b-',label='Rotin v=%.1f km/s'%(vmax/1000.),lw=2) 
 199  >>> p = pl.ylim(0.95,1.0) 
 200  >>> p = pl.legend(loc='lower right',prop=dict(size='small')) 
 201  >>> p = pl.xlabel('Wavelength [angstrom]',color='1') 
 202  >>> p = pl.ylabel('Normalised flux',color='1') 
 203   
 204  ]include figure]]ivs_binary_rochepotential_norot_spectrum_b.png] 
 205   
 206  ]include figure]]ivs_binary_rochepotential_norot_spectrum_a.png] 
 207   
 208   
 209  """ 
 210  import logging 
 211  import os 
 212  import pylab as pl 
 213  import numpy as np 
 214  from numpy import pi,cos,sin,sqrt,nan 
 215  from scipy.optimize import newton 
 216  from scipy.spatial import KDTree 
 217  try: 
 218      from scipy.spatial import Delaunay 
 219  except ImportError: 
 220      print "import Error Delaunay" 
 221  import time 
 222  from ivs.timeseries import keplerorbit 
 223  from ivs.units import constants 
 224  from ivs.units import conversions 
 225  from ivs.coordinates import vectors 
 226  from ivs.sed import model as sed_model 
 227  from ivs.sed import limbdark 
 228  from ivs.spectra import model as spectra_model 
 229  from ivs.roche import local 
 230  from ivs.aux import loggers 
 231  from ivs.inout import ascii 
 232  from ivs.inout import fits 
 233   
 234  logger = logging.getLogger("BIN.ROCHE") 
 235   
 236  #{ Eccentric asynchronous binary Roche potential in spherical coordinates 
 237   
238 -def binary_roche_potential(r,theta,phi,Phi,q,d,F):
239 """ 240 Unitless eccentric asynchronous Roche potential in spherical coordinates. 241 242 See Wilson, 1979. 243 244 The synchronicity parameter F is 1 for synchronised circular orbits. For 245 pseudo-synchronous eccentrical orbits, it is equal to (Hut, 1981) 246 247 F = sqrt( (1+e)/ (1-e)^3) 248 249 Periastron is reached when d = 1-e. 250 251 @param r: radius of Roche volume at potential Phi (in units of semi-major axis) 252 @type r: float 253 @param theta: colatitude (0 at the pole, pi/2 at the equator) 254 @type theta: float 255 @param phi: longitude (0 in direction of COM) 256 @type phi: float 257 @param Phi: Roche potential value (unitless) 258 @type Phi: float 259 @param q: mass ratio 260 @type q: float 261 @param d: separation (in units of semi-major axis) 262 @type d: float 263 @param F: synchronicity parameter 264 @type F: float 265 @return: residu between Phi and roche potential 266 @rtype: float 267 """ 268 lam,nu = cos(phi)*sin(theta),cos(theta) 269 term1 = 1. / r 270 term2 = q * ( 1./sqrt(d**2 - 2*lam*d*r + r**2) - lam*r/d**2) 271 term3 = 0.5 * F**2 * (q+1) * r**2 * (1-nu**2) 272 return (Phi - (term1 + term2 + term3))
273
274 -def binary_roche_potential_gradient(x,y,z,q,d,F,norm=False):
275 """ 276 Gradient of eccenctric asynchronous Roche potential in cartesian coordinates. 277 278 See Phoebe scientific reference, http://phoebe.fiz.uni-lj.si/docs/phoebe_science.ps.gz 279 280 x,y,z,d in real units! (otherwise you have to scale it yourself) 281 282 @param x: x-axis 283 @type x: float' 284 @param y: y-axis 285 @type y: float' 286 @param z: z-axis 287 @type z: float' 288 @param q: mass ratio 289 @type q: float 290 @param d: separation (in units of semi-major axis) 291 @type d: float 292 @param F: synchronicity parameter 293 @type F: float 294 @param norm: flag to return magnitude (True) or vector form (False) 295 @type norm: boolean 296 @return: Roche potential gradient 297 @rtype: ndarray or float 298 """ 299 r = sqrt(x**2 + y**2 + z**2) 300 r_= sqrt((d-x)**2 + y**2 + z**2) 301 dOmega_dx = - x / r**3 + q * (d-x) / r_**3 + F**2 * (1+q)*x - q/d**2 302 dOmega_dy = - y / r**3 - q * y / r_**3 + F**2 * (1+q)*y 303 dOmega_dz = - z / r**3 - q * z / r_**3 304 305 dOmega = np.array([dOmega_dx,dOmega_dy,dOmega_dz]) 306 if norm: 307 return vectors.norm(dOmega) 308 else: 309 return dOmega
310 311
312 -def binary_roche_surface_gravity(x,y,z,d,omega,M1,M2,a=1.,norm=False):
313 """ 314 Calculate surface gravity in an eccentric asynchronous binary roche potential. 315 """ 316 q = M2/M1 317 x_com = q*d/(1+q) 318 319 r = np.array([x,y,z]) 320 d_cf = np.array([d-x_com,0,0]) 321 d = np.array([d,0,0]) 322 h = d - r 323 324 term1 = - constants.GG*M1/vectors.norm(r)**3*r 325 term2 = - constants.GG*M2/vectors.norm(h)**3*h 326 term3 = - omega**2 * d_cf 327 328 g_pole = term1 + term2 + term3 329 if norm: 330 return vectors.norm(g_pole) 331 else: 332 return g_pole
333 334
335 -def get_binary_roche_radius(theta,phi,Phi,q,d,F,r_pole=None):
336 """ 337 Calculate the eccentric asynchronous binary Roche radius in spherical coordinates. 338 339 This is done via the Newton-Raphson secant method. If r_pole is not given 340 as a starting value, it will be calculated here (slowing down the function). 341 342 If no radius can be calculated for the given coordinates, 'nan' is returned. 343 344 @param theta: colatitude (0 at the pole, pi/2 at the equator) 345 @type theta: float 346 @param phi: longitude (0 in direction of COM) 347 @type phi: float 348 @param Phi: Roche potential value (unitless) 349 @type Phi: float 350 @param q: mass ratio 351 @type q: float 352 @param d: separation (in units of semi-major axis) 353 @type d: float 354 @param F: synchronicity parameter 355 @type F: float 356 @param r_pole: polar radius (serves as starting value for NR method) 357 @type r_pole: float 358 @return r: radius of Roche volume at potential Phi (in units of semi-major axis) 359 @rtype r: float 360 """ 361 if r_pole is None: 362 r_pole = newton(binary_roche_potential,1e-5,args=(0,0,Phi,q,ds.min(),F)) 363 try: 364 r = newton(binary_roche_potential,r_pole,args=(theta,phi,Phi,q,d,F)) 365 if r<0 or r>d: 366 r = nan 367 except RuntimeError: 368 r = nan 369 return r
370 371 #} 372 373 374
375 -def reflection_effect(primary,secondary,theta,phi,A1=1.,A2=1.,max_iter=1):
376 #-- reflection effect 377 #-------------------- 378 reflection_iter = 0 379 while (reflection_iter<max_iter): 380 R1,R2 = np.ones(len(primary['teff'])/4),np.ones(len(primary['teff'])/4) 381 for i in xrange(len(R1)): 382 if i%100==0: print i,len(R1) 383 #-- radiation from secondary onto primary 384 s12 = np.array([ primary['x'][i]-secondary['x'], 385 primary['y'][i]-secondary['y'], 386 primary['z'][i]-secondary['z']]) 387 psi2 = vectors.angle(+s12,-np.array([secondary['gravx'],secondary['gravy'],secondary['gravz']])) 388 psi1 = vectors.angle(-s12,-np.array([primary['gravx'][i:i+1],primary['gravy'][i:i+1],primary['gravz'][i:i+1]])) 389 s12 = s12.ravel() 390 391 keep = (psi2<pi/2.) & (psi1<pi/2.) 392 Lambda_2_bol = np.array([limbdark.get_itable(teff=iteff,logg=np.log10(igrav*100),theta=ipsi2,photbands=['OPEN.BOL'],absolute=False)[0]\ 393 for iteff,igrav,ipsi2 in zip(secondary['teff'][keep],secondary['grav'][keep],psi2[keep])]) 394 395 s = vectors.norm(s12[keep]) 396 J_21_entrant = A1 * R2[i] * np.sum(secondary['flux'][keep] * cos(psi1[keep])*cos(psi2[keep])*Lambda_2_bol*secondary['areas'][keep]/s**2) 397 if np.isnan(J_21_entrant).any(): raise ValueError 398 J_1_tot = primary['flux'][i] 399 R1_new = 1+ J_21_entrant/J_1_tot 400 401 #-- radiation from primary onto secondary 402 s12 = np.array([primary['x']-secondary['x'][i], 403 primary['y']-secondary['y'][i], 404 primary['z']-secondary['z'][i]]) 405 psi2 = vectors.angle(+s12,-np.array([primary['gravx'],primary['gravy'],primary['gravz']])) 406 psi1 = vectors.angle(-s12,-np.array([secondary['gravx'][i:i+1],secondary['gravy'][i:i+1],secondary['gravz'][i:i+1]])) 407 s12 = s12.ravel() 408 409 keep = (psi2<pi/2.) & (psi1<pi/2.) 410 Lambda_2_bol = np.array([limbdark.get_itable(teff=iteff,logg=np.log10(igrav*100),theta=ipsi2,photbands=['OPEN.BOL'],absolute=False)[0]\ 411 for iteff,igrav,ipsi2 in zip(primary['teff'][keep],primary['grav'][keep],psi2[keep])]) 412 413 s = vectors.norm(s12) 414 J_12_entrant = A2 * R1[i] * np.nansum(primary['flux'][keep] * cos(psi1[keep])*cos(psi2[keep])*Lambda_2_bol*primary['areas'][keep]/s**2) 415 J_2_tot = secondary['flux'][i] 416 R2_new = 1+ J_12_entrant/J_2_tot 417 418 R1[i] = R1_new 419 R2[i] = R2_new 420 421 #================ START DEBUGGING PLOTS =================== 422 #pl.figure() 423 #pl.subplot(423);pl.title('old primary') 424 #pl.scatter(phis_,thetas_,c=teff_,edgecolors='none',cmap=pl.cm.spectral) 425 #pl.colorbar() 426 #pl.subplot(424);pl.title('old secondary') 427 #pl.scatter(phis_,thetas_,c=teff2_,edgecolors='none',cmap=pl.cm.spectral) 428 #pl.colorbar() 429 #================ END DEBUGGING PLOTS =================== 430 431 #-- adapt the teff only (and when) the increase is more than 1% (=>1.005**0.25=1.01) 432 break_out = True 433 trash,trash2,R1,R2 = stitch_grid(theta,phi,R1.reshape(theta.shape),R2.reshape(theta.shape)) 434 del trash,trash2 435 R1 = R1.ravel() 436 R2 = R2.ravel() 437 438 if (R1[-np.isnan(R1)]>1.05).any(): 439 print "Significant reflection effect on primary (max %.3f%%)"%((R1.max()**0.25-1)*100) 440 primary['teff']*= R1**0.25 441 primary['flux'] = local_intensity(primary['teff'],primary['grav'],np.ones_like(primary['teff']),photbands=['OPEN.BOL']) 442 break_out = False 443 else: 444 print 'Maximum reflection effect on primary: %.3f%%'%((R1.max()**0.25-1)*100) 445 446 if (R2[-np.isnan(R2)]>1.05).any(): 447 print "Significant reflection effect on secondary (max %.3g%%)"%((R2.max()**0.25-1)*100) 448 secondary['teff']*= R2**0.25 449 secondary['flux'] = local_intensity(secondary['teff'],secondary['grav'],np.ones_like(secondary['teff']),photbands=['OPEN.BOL']) 450 break_out = False 451 else: 452 print 'Maximum reflection effect on secondary: %.3g%%'%((R1.max()**0.25-1)*100) 453 454 if break_out: 455 break 456 457 458 reflection_iter += 1 459 460 #================ START DEBUGGING PLOTS =================== 461 #pl.subplot(411) 462 #pl.scatter(x,y,c=teff_,edgecolors='none',cmap=pl.cm.spectral,vmin=T_pole,vmax=T_pole2) 463 #pl.scatter(x2,y2,c=teff2_,edgecolors='none',cmap=pl.cm.spectral,vmin=T_pole,vmax=T_pole2) 464 #pl.colorbar() 465 466 #pl.subplot(425);pl.title('new primary') 467 #pl.scatter(phis_,thetas_,c=teff_,edgecolors='none',cmap=pl.cm.spectral) 468 #pl.colorbar() 469 #pl.subplot(426);pl.title('new secondary') 470 #pl.scatter(phis_,thetas_,c=teff2_,edgecolors='none',cmap=pl.cm.spectral) 471 #pl.colorbar() 472 473 #pl.subplot(427) 474 #pl.scatter(phis_,thetas_,c=R1,edgecolors='none',cmap=pl.cm.spectral) 475 #pl.colorbar() 476 #pl.subplot(428) 477 #pl.scatter(phis_,thetas_,c=R2,edgecolors='none',cmap=pl.cm.spectral) 478 #pl.colorbar() 479 #================ END DEBUGGING PLOTS =================== 480 return primary,secondary
481 482 #} 483
484 -def spectral_synthesis(*stars,**kwargs):
485 """ 486 Generate a synthetic spectrum of one or more stars. 487 488 If you give more than one star, you get more than one synthesized spectrum 489 back. To compute the total spectrum, just add them up. 490 491 WARNING: the spectra are scaled with the value of the projected intensity. 492 This is usually calculated within a specific photometric passband. The total 493 spectrum will thus be dependent on the photometric passband, unless you 494 specified 'OPEN.BOL' (which is the bolometric open filter). If you want to 495 know the RV for a specific line, you might want to look into calculating the 496 projected intensities at specific wavelength intervals. 497 498 @param stars: any number of (projected) star record arrays 499 @type stars: tuple of star record arrays 500 @keyword wave: wavelength template to compute the synthetic spectrum on 501 @type wave: numpy array 502 @return: tuple of synthetic spectra, scaled to total intensity 503 @rtype: tuple of numpy arrays 504 """ 505 wave = kwargs.get('wave',np.linspace(7050,7080,1000)) 506 #-- these are the log surface gravity values in cgs [dex] 507 spectra = [] 508 get_spectrum = spectra_model.get_table 509 for mystar in stars: 510 loggs = np.log10(np.sqrt(mystar['gravx']**2 + mystar['gravy']**2 + mystar['gravz']**2)*100) 511 iterator = zip(mystar['teff'],loggs,mystar['vx']/1000.) 512 print "Temperature range:",mystar['teff'].min(),mystar['teff'].max() 513 print "logg range:",loggs.min(),loggs.max() 514 #-- retrieve the synthetic spectra for all surface elements, interpolated 515 # on the supplied wavelength grid, and taking care of radial velocity shifts 516 spectra_elements = np.array([get_spectrum(teff=iteff,logg=ilogg,vrad=ivrad,wave=wave)[1] for iteff,ilogg,ivrad in iterator]) 517 #-- and add them up according to the flux projected in the line of sight 518 average_spectrum = np.sum(spectra_elements*mystar['projflux'],axis=0) 519 spectra.append(average_spectrum) 520 return spectra
521 522
523 -def binary_light_curve_synthesis(**parameters):
524 """ 525 Generate a synthetic light curve of a binary system. 526 527 @keyword name: name of the binary system, used for output 528 @type name: string 529 @keyword direc: directory to put output files 530 @type direc: string (if empty string, then current directory will be taken, 531 if None, then not output will be written) 532 @parameter gres: grid resolution for primary and secondary. If integer, the 533 resolution of both components and both longitude and latitude will be the 534 same. A 2-tuple will be interpreted as (latitude resolution, longitude resolution) 535 @type gres: integer, 2-tuple or 4-tuple 536 @parameter tres: number of phase steps to comptue the light curve on 537 @type tres: integer 538 """ 539 #-- some parameters are optional 540 # file output parameters 541 name = parameters.setdefault('name','mybinary') 542 direc = parameters.setdefault('direc','') 543 # calculation details 544 res = parameters.setdefault('gres',20) # resolution of the grid 545 gtype = parameters.setdefault('gtype','spher') 546 tres= parameters.pop('tres',125) # resolution of the phase diagram 547 photband = parameters.setdefault('photband','JOHNSON.V') # photometric passband 548 max_iter_reflection = parameters.setdefault('ref_iter',1) # maximum number of iterations of reflection effect 549 # orbital parameters 550 gamma = parameters.setdefault('gamma',0.) # systemic velocity [km/s] 551 incl = parameters.setdefault('incl',90.) # system inclination angle [deg] 552 q = parameters.setdefault('q',1.) # mass ratio (M2/M1) 553 e = parameters.setdefault('e',0.) # eccentricity 554 omega = parameters.setdefault('omega',0.) # argument of periastron 555 # component parameters 556 F = parameters.setdefault('F1',sqrt((1+e)/(1-e)**3)) # synchronicity parameter primary 557 F2= parameters.setdefault('F2',sqrt((1+e)/(1-e)**3)) # synchronicity parameter secondary 558 A1= parameters.setdefault('A1',1.) # albedo primary 559 A2= parameters.setdefault('A2',1.) # albedo secondary 560 beta1 = parameters.setdefault('beta1',1.0) # gravity darkening primary 561 beta2 = parameters.setdefault('beta2',1.0) # gravity darkening secondary 562 563 #-- others are mandatory: 564 T_pole = parameters['Tpole1'] # Primary Polar temperature [K] 565 T_pole2 = parameters['Tpole2'] # Secondary Polar temperature [K] 566 P = parameters['P'] # Period [days] 567 asini = parameters['asini'] # total semi-major axis*sini [AU] 568 Phi = parameters['Phi1'] # Gravitational potential of primary [-] 569 Phi2 = parameters['Phi2'] # Gravitational potential of secondary [-] 570 571 #-- derive parameters needed in the calculation 572 a = asini/sin(incl/180.*pi) 573 a1 = a / (1.+1./q) 574 a2 = a / (1.+ q) 575 q2 = 1./q 576 view_angle = incl/180.*pi 577 # calculate the total mass (in solar mass units) from Kepler's third law: 578 a_ = conversions.convert('au','m',a) 579 P_ = conversions.convert('d','s',P) 580 M = (4*pi**2 * a_**3 / P_**2 / constants.GG ) / constants.Msol 581 M1 = M / (1.+ q) 582 M2 = M / (1.+1./q) 583 # calculate location of first Lagrangian point L1 and L2 584 mu = M2 / (M1+M2) 585 z = (mu/3.)**(1./3.) 586 L1 = z- 1/3.*z**2 - 1/9.*z**3 + 58./81*z**4 587 L2 = z+ 1/3.*z**2 - 1/9.*z**3 + 50./81*z**4 588 parameters['M1'] = M1 589 parameters['M2'] = M2 590 parameters['L1'] = L1*a*constants.au/constants.Rsol 591 parameters['L2'] = L2*a*constants.au/constants.Rsol 592 593 #-- calculate the Keplerian orbits of the primary and secondary 594 if not hasattr(tres,'__iter__'): 595 times = np.linspace(0.5*P,1.5*P,tres) 596 else: 597 times = tres 598 r1,theta1 = keplerorbit.orbit_in_plane(times,[P,e,a1,gamma],component='primary') 599 r2,theta2 = keplerorbit.orbit_in_plane(times,[P,e,a2,gamma],component='secondary') 600 RV1 = keplerorbit.radial_velocity([P,e,a1,pi/2,view_angle,gamma],theta=theta1,itermax=8) 601 RV2 = keplerorbit.radial_velocity([P,e,a2,pi/2,view_angle,gamma],theta=theta2,itermax=8) 602 603 r1 = r1/constants.au 604 r2 = r2/constants.au 605 # put them in cartesian coordinates 606 x1o,y1o = r1*cos(theta1)/a,r1*sin(theta1)/a 607 x2o,y2o = r2*cos(theta2)/a,r2*sin(theta2)/a 608 # and rotate towards the line of sight in the XZ plane 609 rot_i = -(pi/2 - view_angle) 610 x1o_,z1o = vectors.rotate(x1o,np.zeros_like(y1o),rot_i) 611 x2o_,z2o = vectors.rotate(x2o,np.zeros_like(y2o),rot_i) 612 613 #-- calculate separation at all phases 614 ds = np.sqrt( (x1o-x2o)**2 + (y1o-y2o)**2) 615 616 #-- calculate the polar radius and the radius towards L1 at minimum separation 617 r_pole = newton(binary_roche_potential,1e-5,args=(0,0,Phi,q,ds.min(),F)) 618 r_pole2 = newton(binary_roche_potential,1e-5,args=(0,0,Phi2,q2,ds.min(),F2)) 619 parameters['Rp1'] = r_pole*a*constants.au/constants.Rsol 620 parameters['Rp2'] = r_pole2*a*constants.au/constants.Rsol 621 622 #-- calculate the critical Phis and Roche radius 623 phi1_crit = binary_roche_potential(L1,0,0,Phi,q,ds.min(),F) 624 phi2_crit = binary_roche_potential(L1,0,0,Phi2,q2,ds.min(),F2) 625 R_roche1 = a * 0.49 * q2**(2./3.) / (0.6*q2**(2./3.) + np.log(1+q2**(1./3.))) 626 R_roche2 = a * 0.49 * q**(2./3.) / (0.6*q**(2./3.) + np.log(1+q**(1./3.))) 627 628 #-- timescales 629 tdyn1 = np.sqrt(2*(r_pole*constants.au)**3/(constants.GG*M1*constants.Msol)) 630 tdyn2 = np.sqrt(2*(r_pole2*constants.au)**3/(constants.GG*M2*constants.Msol)) 631 lumt1 = (M1<2.) and M1**4 or (1.5*M1**3.5) # from mass luminosity relation 632 lumt2 = (M2<2.) and M2**4 or (1.5*M2**3.5) # from mass luminosity relation 633 tthr1 = constants.GG * (M1*constants.Msol)**2 / (r_pole*constants.au*lumt1*constants.Lsol) 634 tthr2 = constants.GG * (M2*constants.Msol)**2 / (r_pole2*constants.au*lumt2*constants.Lsol) 635 tnuc1 = 7e9 * M1 / lumt1 636 tnuc2 = 7e9 * M2 / lumt2 637 638 logger.info('=================================================') 639 logger.info('GENERAL SYSTEM AND COMPONENT PROPERTIES') 640 logger.info('=================================================') 641 logger.info("Period P = %.3g d"%(P)) 642 logger.info("Inclination angle i = %.3g deg"%(incl)) 643 logger.info("Systemic velocity gamma = %.3g km/s"%(gamma)) 644 logger.info("Eccentricity e = %.3g"%(e)) 645 logger.info("Mass ratio q = %.3g"%(q)) 646 logger.info("Mass primary M1 = %.3g Msun"%(M1)) 647 logger.info("Mass secondary M2 = %.3g Msun"%(M2)) 648 logger.info("Polar Radius primary R1 = %.3g Rsun (Roche = %.3g)"%(r_pole*a*constants.au/constants.Rsol,R_roche1*constants.au/constants.Rsol)) 649 logger.info("Polar Radius secondary R2 = %.3g Rsun (Roche = %.3g)"%(r_pole2*a*constants.au/constants.Rsol,R_roche2*constants.au/constants.Rsol)) 650 logger.info("Center-of-mass = %.3g Rsun"%(q/(1+q)*a*constants.au/constants.Rsol)) 651 logger.info('Lagrange L1 = %.3g Rsun'%(L1*a*constants.au/constants.Rsol)) 652 logger.info('Lagrange L2 = %.3g Rsun'%(L2*a*constants.au/constants.Rsol)) 653 logger.info('Phi primary = %.3g (critical = %.3g)'%(Phi,phi1_crit)) 654 logger.info('Phi secondary = %.3g (critical = %.3g)'%(Phi2,phi2_crit)) 655 logger.info('Luminosity primary L1 (MS) = %.3g Lsun'%(lumt1)) 656 logger.info('Luminosity secondary L2 (MS) = %.3g Lsun'%(lumt2)) 657 logger.info('System semi-major axis a = %.3g Rsun'%(a*constants.au/constants.Rsol)) 658 logger.info('------') 659 logger.info('TDYN primary = %.3g hr'%(tdyn1/3600.)) 660 logger.info('TDYN secondary = %.3g hr'%(tdyn2/3600.)) 661 logger.info('TNUC primary = %.3g yr'%(tnuc1)) 662 logger.info('TNUC secondary = %.3g yr'%(tnuc2)) 663 logger.info('TTHERM primary = %.3g yr'%(tthr1/(24*3600*365))) 664 logger.info('TTHERM secondary = %.3g yr'%(tthr2/(24*3600*365))) 665 logger.info('=================================================') 666 667 #-- construct the grid to calculate stellar shapes 668 if hasattr(res,'__iter__'): 669 mygrid = local.get_grid(res[0],res[1],gtype=gtype) 670 theta,phi = mygrid[:2] 671 else: 672 mygrid = local.get_grid(res,gtype=gtype) 673 theta,phi = mygrid[:2] 674 thetas,phis = np.ravel(theta),np.ravel(phi) 675 676 light_curve = np.zeros_like(times) 677 RV1_corr = np.zeros_like(times) 678 RV2_corr = np.zeros_like(times) 679 to_SI = a*constants.au 680 to_CGS = a*constants.au*100. 681 scale_factor = a*constants.au/constants.Rsol 682 683 fitsfile = os.path.join(direc,'%s.fits'%(name)) 684 if direc is not None and os.path.isfile(fitsfile): 685 os.remove(fitsfile) 686 logger.warning("Removed existing file %s"%(fitsfile)) 687 if direc is not None: 688 parameters['scalefac'] = a*constants.au/constants.Rsol 689 parameters.pop('gres') 690 outputfile_prim = os.path.join(direc,'%s_primary.fits'%(name)) 691 outputfile_secn = os.path.join(direc,'%s_secondary.fits'%(name)) 692 outputfile_prim = fits.write_primary(outputfile_prim,header_dict=parameters) 693 outputfile_secn = fits.write_primary(outputfile_secn,header_dict=parameters) 694 695 ext_dict = {} 696 for di,d in enumerate(ds): 697 report = "STEP %04d"%(di) 698 699 #-- this is the angular velocity due to rotation and orbit 700 # you get the rotation period of the star via 2pi/omega_rot (in sec) 701 omega_rot = F * 2*pi/P_ * 1/d**2 * sqrt( (1+e)*(1-e)) 702 omega_rot_vec = np.array([0.,0.,-omega_rot]) 703 704 if e>0 or di==0: 705 #-- compute the star's radius and surface gravity 706 out = [[get_binary_roche_radius(itheta,iphi,Phi=Phi,q=q,d=d,F=F,r_pole=r_pole), 707 get_binary_roche_radius(itheta,iphi,Phi=Phi2,q=q2,d=d,F=F2,r_pole=r_pole2)] for itheta,iphi in zip(thetas,phis)] 708 rprim,rsec = np.array(out).T 709 710 #-- for the primary 711 #------------------ 712 radius = rprim.reshape(theta.shape) 713 this_r_pole = get_binary_roche_radius(0,0,Phi=Phi,q=q,d=d,F=F,r_pole=r_pole) 714 x,y,z = vectors.spher2cart_coord(radius,phi,theta) 715 g_pole = binary_roche_surface_gravity(0,0,this_r_pole*to_SI,d*to_SI,omega_rot,M1*constants.Msol,M2*constants.Msol,norm=True) 716 Gamma_pole = binary_roche_potential_gradient(0,0,this_r_pole,q,d,F,norm=True) 717 zeta = g_pole / Gamma_pole 718 dOmega = binary_roche_potential_gradient(x,y,z,q,d,F,norm=False) 719 grav_local = dOmega*zeta 720 721 #-- here we can compute local quantities: surface gravity, area, 722 # effective temperature, flux and velocity 723 grav_local = np.array([i.reshape(theta.shape) for i in grav_local]) 724 grav = vectors.norm(grav_local) 725 areas_local,cos_gamma = local.surface_elements((radius,mygrid),-grav_local,gtype=gtype) 726 teff_local = local.temperature(grav,g_pole,T_pole,beta=beta1) 727 ints_local = local.intensity(teff_local,grav,np.ones_like(cos_gamma),photband='OPEN.BOL') 728 velo_local = np.cross(np.array([x,y,z]).T*to_SI,omega_rot_vec).T 729 730 #-- here we can compute the global quantities: total surface area 731 # and luminosity 732 lumi_prim = 4*pi*(ints_local*areas_local*to_CGS**2).sum()/constants.Lsol_cgs 733 area_prim = 4*areas_local.sum()*to_CGS**2/(4*pi*constants.Rsol_cgs**2) 734 logger.info('----PRIMARY DERIVED PROPERTIES') 735 logger.info('Polar Radius primary = %.3g Rsun'%(this_r_pole*a*constants.au/constants.Rsol)) 736 logger.info("Polar logg primary = %.3g dex"%(np.log10(g_pole*100))) 737 logger.info("Luminosity primary = %.3g Lsun"%(lumi_prim)) 738 logger.info("Surface area primary = %.3g Asun"%(area_prim)) 739 logger.info("Mean Temp primary = %.3g K"%(np.average(teff_local,weights=areas_local))) 740 ext_dict['Rp1'] = this_r_pole*a*constants.au/constants.Rsol 741 ext_dict['loggp1'] = np.log10(g_pole*100) 742 ext_dict['LUMI1'] = lumi_prim 743 ext_dict['SURF1'] = area_prim 744 745 #-- for the secondary 746 #-------------------- 747 radius2 = rsec.reshape(theta.shape) 748 this_r_pole2 = get_binary_roche_radius(itheta,iphi,Phi=Phi2,q=q2,d=d,F=F2,r_pole=r_pole2) 749 x2,y2,z2 = vectors.spher2cart_coord(radius2,phi,theta) 750 g_pole2 = binary_roche_surface_gravity(0,0,this_r_pole2*to_SI,d*to_SI,omega_rot,M2*constants.Msol,M1*constants.Msol,norm=True) 751 Gamma_pole2 = binary_roche_potential_gradient(0,0,this_r_pole2,q2,d,F2,norm=True) 752 zeta2 = g_pole2 / Gamma_pole2 753 dOmega2 = binary_roche_potential_gradient(x2,y2,z2,q2,d,F2,norm=False) 754 grav_local2 = dOmega2*zeta2 755 756 #-- here we can compute local quantities: : surface gravity, area, 757 # effective temperature, flux and velocity 758 grav_local2 = np.array([i.reshape(theta.shape) for i in grav_local2]) 759 grav2 = vectors.norm(grav_local2) 760 areas_local2,cos_gamma2 = local.surface_elements((radius2,mygrid),-grav_local2,gtype=gtype) 761 teff_local2 = local.temperature(grav2,g_pole2,T_pole2,beta=beta2) 762 ints_local2 = local.intensity(teff_local2,grav2,np.ones_like(cos_gamma2),photband='OPEN.BOL') 763 velo_local2 = np.cross(np.array([x2,y2,z2]).T*to_SI,omega_rot_vec).T 764 765 #-- here we can compute the global quantities: total surface area 766 # and luminosity 767 lumi_sec = 4*pi*(ints_local2*areas_local2*to_CGS**2).sum()/constants.Lsol_cgs 768 area_sec = 4*areas_local2.sum()*to_CGS**2/(4*pi*constants.Rsol_cgs**2) 769 logger.info('----SECONDARY DERIVED PROPERTIES') 770 logger.info('Polar Radius secondary = %.3g Rsun'%(this_r_pole2*a*constants.au/constants.Rsol)) 771 logger.info("Polar logg secondary = %.3g dex"%(np.log10(g_pole2*100))) 772 logger.info("Luminosity secondary = %.3g Lsun"%(lumi_sec)) 773 logger.info("Surface area secondary = %.3g Asun"%(area_sec)) 774 logger.info("Mean Temp secondary = %.3g K"%(np.average(teff_local2,weights=areas_local2))) 775 ext_dict['Rp2'] = this_r_pole2*a*constants.au/constants.Rsol 776 ext_dict['loggp2'] = np.log10(g_pole2*100) 777 ext_dict['LUMI2'] = lumi_sec 778 ext_dict['SURF2'] = area_sec 779 780 #================ START DEBUGGING PLOTS =================== 781 #plot_quantities(phi,theta,np.log10(grav2*100.),areas_local2,np.arccos(cos_gamma2)/pi*180,teff_local2,ints_local2, 782 # names=['grav','area','angle','teff','ints'],rows=2,cols=3) 783 #pl.show() 784 #================ END DEBUGGING PLOTS =================== 785 786 #-- stitch the grid! 787 theta_,phi_,radius,gravx,gravy,gravz,grav,areas,teff,ints,vx,vy,vz = \ 788 local.stitch_grid(theta,phi,radius,grav_local[0],grav_local[1],grav_local[2], 789 grav,areas_local,teff_local,ints_local,velo_local[0],velo_local[1],velo_local[2], 790 seamless=False,gtype=gtype, 791 vtype=['scalar','x','y','z','scalar','scalar','scalar','scalar','vx','vy','vz']) 792 #-- stitch the grid! 793 theta2_,phi2_,radius2,gravx2,gravy2,gravz2,grav2,areas2,teff2,ints2,vx2,vy2,vz2 = \ 794 local.stitch_grid(theta,phi,radius2,grav_local2[0],grav_local2[1],grav_local2[2], 795 grav2,areas_local2,teff_local2,ints_local2,velo_local2[0],velo_local2[1],velo_local2[2], 796 seamless=False,gtype=gtype, 797 vtype=['scalar','x','y','z','scalar','scalar','scalar','scalar','vx','vy','vz']) 798 799 #-- vectors and coordinates in original frame 800 x_of,y_of,z_of = vectors.spher2cart_coord(radius.ravel(),phi_.ravel(),theta_.ravel()) 801 x2_of,y2_of,z2_of = vectors.spher2cart_coord(radius2.ravel(),phi2_.ravel(),theta2_.ravel()) 802 x2_of = -x2_of 803 #-- store information on primary and secondary in a record array 804 primary = np.rec.fromarrays([theta_.ravel(),phi_.ravel(),radius.ravel(), 805 x_of,y_of,z_of, 806 vx.ravel(),vy.ravel(),vz.ravel(), 807 gravx.ravel(),gravy.ravel(),gravz.ravel(),grav.ravel(), 808 areas.ravel(),teff.ravel(),ints.ravel()], 809 names=['theta','phi','r', 810 'x','y','z', 811 'vx','vy','vz', 812 'gravx','gravy','gravz','grav', 813 'areas','teff','flux']) 814 815 secondary = np.rec.fromarrays([theta2_.ravel(),phi2_.ravel(),radius2.ravel(), 816 x2_of,y2_of,z2_of, 817 vx2.ravel(),-vy2.ravel(),vz2.ravel(), 818 -gravx2.ravel(),gravy2.ravel(),gravz2.ravel(),grav2.ravel(), 819 areas2.ravel(),teff2.ravel(),ints2.ravel()], 820 names=['theta','phi','r', 821 'x','y','z', 822 'vx','vy','vz', 823 'gravx','gravy','gravz','grav', 824 'areas','teff','flux']) 825 826 #-- take care of the reflection effect 827 primary,secondary = reflection_effect(primary,secondary,theta,phi, 828 A1=A1,A2=A2,max_iter=max_iter_reflection) 829 830 #-- now compute the integrated intensity in the line of sight: 831 #------------------------------------------------------------- 832 rot_theta = np.arctan2(y1o[di],x1o[di]) 833 #-- if we want to save the binary to a file, we'd better want it in some 834 # real units, and the entire star, instead of just the projected star: 835 if direc is not None: 836 prim = local.project(primary,view_long=(rot_theta,x1o[di],y1o[di]), 837 view_lat=(view_angle,0,0),photband=photband, 838 only_visible=False,plot_sort=False,scale_factor=scale_factor) 839 secn = local.project(secondary,view_long=(rot_theta,x2o[di],y2o[di]), 840 view_lat=(view_angle,0,0),photband=photband, 841 only_visible=False,plot_sort=False,scale_factor=scale_factor) 842 #-- calculate center-of-mass (is this correct?) 843 com_x = (x1o[di] + q*x2o[di]) / (1.0+q) 844 com_y = (y1o[di] + q*y2o[di]) / (1.0+q) 845 com = np.array([com_x,com_y,0.]) 846 rot_i = -(pi/2 - view_angle) 847 com[0],com[1] = vectors.rotate(com[0],com[1],rot_theta,x0=x1o[di],y0=y1o[di]) 848 com[0],com[2] = vectors.rotate(com[0],com[2],rot_i) 849 prim_header = dict(x0=x1o[di],y0=y1o[di],i=view_angle,comx=com[0],comy=com[1],com_z=com[2],nr=di,time=times[di]) 850 secn_header = dict(x0=x2o[di],y0=y2o[di],i=view_angle,comx=com[0],comy=com[1],com_z=com[2],nr=di,time=times[di]) 851 #-- only close the file every 20 cycles (for speed) 852 if direc is not None and (di%20==0): close = True 853 else: close = False 854 # and append to primary HDUList 855 outputfile_prim = fits.write_recarray(prim,outputfile_prim,close=close,header_dict=prim_header) 856 outputfile_secn = fits.write_recarray(secn,outputfile_secn,close=close,header_dict=secn_header) 857 858 prim = local.project(primary,view_long=(rot_theta,x1o[di],y1o[di]), 859 view_lat=(view_angle,0,0),photband=photband, 860 only_visible=True,plot_sort=True) 861 secn = local.project(secondary,view_long=(rot_theta,x2o[di],y2o[di]), 862 view_lat=(view_angle,0,0),photband=photband, 863 only_visible=True,plot_sort=True) 864 prim['vx'] = -prim['vx'] + RV1[di]*1000. 865 secn['vx'] = -secn['vx'] + RV2[di]*1000. 866 867 #-- the total intensity is simply the sum of the projected intensities 868 # over all visible meshpoints. To calculate the visibility, we 869 # we collect the Y-Z coordinates in one array for easy matching in 870 # the KDTree 871 # We need to know which star is in front. It is the one with the 872 # largest x coordinate 873 if secn['x'].min()<prim['x'].min(): 874 front,back = prim,secn 875 front_component = 1 876 report += ' Primary in front' 877 else: 878 front,back = secn,prim 879 front_component = 2 880 report += ' Secondary in front' 881 coords_front = np.column_stack([front['y'],front['z']]) 882 coords_back = np.column_stack([back['y'],back['z']]) 883 884 if gtype!='delaunay': 885 # now find the coordinates of the front component closest to the 886 # the coordinates of the back component 887 tree = KDTree(coords_front) 888 distance,order = tree.query(coords_back) 889 # meshpoints of the back component inside an eclipse have a 890 # nearest neighbouring point in the (projected) front component 891 # which is closer than sqrt(area) of the surface element connected 892 # to that neighbouring point on the front component 893 in_eclipse = distance < np.sqrt(front['areas'][order]) 894 else: 895 # find which coordinates of the back lie inside the convex hull 896 # of the front star 897 eclipse_detection = Delaunay(coords_front) 898 in_eclipse = eclipse_detection.find_simplex(coords_back)>=0 899 if np.sum(in_eclipse)>0: 900 report += ' during eclipse' 901 else: 902 report += ' outside eclipse' 903 904 #-- so now we can easily compute the total intensity as the sum of 905 # all visible meshpoints: 906 total_intensity = front['projflux'].sum() + back['projflux'][-in_eclipse].sum() 907 light_curve[di] = total_intensity 908 report += "---> Total intensity: %g "%(total_intensity) 909 910 if di==0: 911 ylim_lc = (0.95*min(prim['projflux'].sum(),secn['projflux'].sum()),1.2*(prim['projflux'].sum()+secn['projflux'].sum())) 912 back['projflux'][in_eclipse] = 0 913 back['eyeflux'][in_eclipse] = 0 914 back['vx'][in_eclipse] = 0 915 back['vy'][in_eclipse] = 0 916 back['vz'][in_eclipse] = 0 917 918 #-- now calculate the *real* observed radial velocity and projected intensity 919 if front_component==1: 920 RV1_corr[di] = np.average(front['vx']/1000.,weights=front['projflux']) 921 RV2_corr[di] = np.average(back['vx'][-in_eclipse]/1000.,weights=back['projflux'][-in_eclipse]) 922 front_cmap = pl.cm.hot 923 back_cmap = pl.cm.cool_r 924 else: 925 RV2_corr[di] = np.average(front['vx']/1000.,weights=front['projflux']) 926 RV1_corr[di] = np.average(back['vx'][-in_eclipse]/1000.,weights=back['projflux'][-in_eclipse]) 927 front_cmap = pl.cm.cool_r 928 back_cmap = pl.cm.hot 929 report += 'RV1=%.3f, RV2=%.3f'%(RV1_corr[di],RV2_corr[di]) 930 logger.info(report) 931 932 #================ START DEBUGGING PLOTS =================== 933 if direc is not None: 934 #-- first calculate the size of the picture, and the color scales 935 if di==0: 936 size_x = 1.2*(max(prim['y'].ptp(),secn['y'].ptp())/2. + max(ds)) 937 size_y = 1.2*(max(prim['z'].ptp(),secn['z'].ptp())/2. + max(ds) * cos(view_angle)) 938 vmin_image,vmax_image = 0,max([front['eyeflux'].max(),back['eyeflux'].max()]) 939 size_top = 1.2*(max(prim['x'].ptp(),secn['x'].ptp())/2. + max(ds)) 940 941 pl.figure(figsize=(16,11)) 942 pl.subplot(221,aspect='equal');pl.title('line of sight intensity') 943 pl.scatter(back['y'],back['z'],c=back['eyeflux'],edgecolors='none',cmap=back_cmap) 944 pl.scatter(front['y'],front['z'],c=front['eyeflux'],edgecolors='none',cmap=front_cmap) 945 pl.xlim(-size_x,size_x) 946 pl.ylim(-size_y,size_y) 947 pl.xlabel('X [semi-major axis]') 948 pl.ylabel('Z [semi-major axis]') 949 950 #-- line-of-sight velocity of the system 951 pl.subplot(222,aspect='equal');pl.title('line of sight velocity') 952 if di==0: 953 vmin_rv = min((prim['vx'].min()/1000.+RV1.min()),(prim['vx'].min()/1000.+RV2.min())) 954 vmax_rv = max((secn['vx'].max()/1000.+RV2.max()),(secn['vx'].max()/1000.+RV2.max())) 955 pl.scatter(front['y'],front['z'],c=front['vx']/1000.,edgecolors='none',cmap=pl.cm.RdBu_r,vmin=vmin_rv,vmax=vmax_rv) 956 pl.scatter(back['y'],back['z'],c=back['vx']/1000.,edgecolors='none',cmap=pl.cm.RdBu_r,vmin=vmin_rv,vmax=vmax_rv) 957 cbar = pl.colorbar() 958 cbar.set_label('Radial velocity [km/s]') 959 pl.xlim(-size_x,size_x) 960 pl.ylim(-size_y,size_y) 961 pl.xlabel('X [semi-major axis]') 962 pl.ylabel('Z [semi-major axis]') 963 964 #-- top view of the system 965 pl.subplot(223,aspect='equal');pl.title('Top view') 966 pl.scatter(prim['x'],prim['y'],c=prim['eyeflux'],edgecolors='none',cmap=pl.cm.hot) 967 pl.scatter(secn['x'],secn['y'],c=secn['eyeflux'],edgecolors='none',cmap=pl.cm.cool_r) 968 pl.xlim(-size_top,size_top) 969 pl.ylim(-size_top,size_top) 970 pl.xlabel('X [semi-major axis]') 971 pl.ylabel('Y [semi-major axis]') 972 973 #-- light curve and radial velocity curve 974 pl.subplot(224);pl.title('light curve and RV curve') 975 pl.plot(times[:di+1],2.5*np.log10(light_curve[:di+1]),'k-',label=photband) 976 pl.plot(times[di],2.5*np.log10(light_curve[di]),'ko',ms=10) 977 pl.xlim(times.min(),times.max()) 978 pl.ylim(2.5*np.log10(ylim_lc[0]),2.5*np.log10(ylim_lc[1])) 979 pl.ylabel('Flux [erg/s/cm2/A/sr]') 980 pl.legend(loc='lower left',prop=dict(size='small')) 981 982 pl.twinx(pl.gca()) 983 # primary radial velocity (numerical, kepler and current) 984 pl.plot(times[:di+1],RV1_corr[:di+1],'b-',lw=2,label='Numeric 1') 985 pl.plot(times[:di+1],RV1[:di+1]-gamma,'g--',lw=2,label='Kepler 1') 986 pl.plot(times[di],RV1[di]-gamma,'go',ms=10) 987 pl.plot(times[di],RV1_corr[di],'bo',ms=10) 988 # secondary radial velocity (numerical, kepler and current) 989 pl.plot(times[:di+1],RV2_corr[:di+1],'r-',lw=2,label='Numeric 2') 990 pl.plot(times[:di+1],RV2[:di+1]-gamma,'c--',lw=2,label='Kepler 2') 991 pl.plot(times[di],RV2[di]-gamma,'cs',ms=10) 992 pl.plot(times[di],RV2_corr[di],'rs',ms=10) 993 pl.ylim(1.2*min(min(RV1-gamma),min(RV2-gamma)),+1.2*max(max(RV1-gamma),max(RV2-gamma))) 994 pl.xlim(times.min(),times.max()) 995 pl.ylabel('Radial velocity [km/s]') 996 pl.xlabel('Time [d]') 997 pl.legend(loc='upper left',prop=dict(size='small')) 998 pl.savefig(os.path.join(direc,'%s_los_%04d'%(name,di)),facecolor='0.75') 999 pl.close() 1000 1001 #-- REAL IMAGE picture 1002 pl.figure(figsize=(7,size_y/size_x*7)) 1003 ax = pl.axes([0,0,1,1]) 1004 ax.set_aspect('equal') 1005 ax.set_axis_bgcolor('k') 1006 pl.xticks([]);pl.yticks([]) 1007 if front_component==1: sfront,sback = 16,9 1008 else: sfront,sback = 9,16 1009 pl.scatter(back['y'][-in_eclipse],back['z'][-in_eclipse],s=sback,c=back['eyeflux'][-in_eclipse],edgecolors='none',cmap=pl.cm.gray,vmin=vmin_image,vmax=vmax_image) 1010 pl.scatter(front['y'],front['z'],s=sfront,c=front['eyeflux'],edgecolors='none',cmap=pl.cm.gray,vmin=vmin_image,vmax=vmax_image) 1011 pl.xlim(-size_x,size_x);pl.ylim(-size_y,size_y) 1012 pl.savefig(os.path.join(direc,'%s_image_%04d'%(name,di)),facecolor='k') 1013 pl.close() 1014 #================ END DEBUGGING PLOTS =================== 1015 1016 #-- make sure to have everything 1017 if direc is not None: 1018 outputfile_prim.close() 1019 outputfile_secn.close() 1020 return times, light_curve, RV1_corr, RV2_corr
1021 1022 if __name__=="__main__": 1023 import doctest 1024 doctest.testmod() 1025 pl.show() 1026 1027 logger = loggers.get_basic_logger("") 1028 import sys 1029