1 """
2 Derive local quantities such as effective temperature and surface gravity for Roche potentials.
3 """
4 import numpy as np
5 import pylab as pl
6 from numpy import pi,cos,sin,sqrt
7 from scipy.optimize import newton
8 try:
9 from scipy.spatial import Delaunay
10 except ImportError:
11 print 'No Delaunay'
12
13 from ivs.sed import limbdark
14 from ivs.coordinates import vectors
15
16
17
18
20 """
21 Construct a coordinate grid
22
23 If you give two resolutions, the first is for theta, the second for phi
24
25 @param args: one or two integers indicating number of grid points in theta
26 and phi direction
27 @type args: integer
28 @keyword gtype: grid type ('spher' or 'delaunay')
29 @type gtype: str
30 @return: theta,phi(,grid)
31 """
32 gtype = kwargs.get('gtype','spherical')
33 full = kwargs.get('full',False)
34
35 if 'spher' in gtype.lower():
36 if len(args)==1: res1 = res2 = args[0]
37 else: res1,res2 = args
38
39 dtheta = pi/res1
40 dphi = pi/res2
41
42
43 if full:
44 theta0,thetan = dtheta, pi-dtheta
45 phi0,phin = 0,2*pi-2*dphi
46 else:
47 theta0,thetan = dtheta/4,pi/2-dtheta/4
48 phi0,phin = 0,pi-dphi
49
50 theta,phi = np.mgrid[theta0:thetan:res1*1j,phi0:phin:res2*1j]
51 return theta,phi
52
53 elif 'cil' in gtype.lower():
54 if len(args)==1: res1 = res2 = args[0]
55 else: res1,res2 = args
56
57 dsintheta = 2./res1
58 dphi = pi/res2
59
60
61 if full:
62 sintheta0,sinthetan = -1+dsintheta, 1.-dsintheta
63 phi0,phin = 0,2*pi-dphi
64 else:
65 sintheta0,sinthetan = dsintheta,1-dsintheta
66 phi0,phin = 0,pi-dphi
67
68 sintheta,phi = np.mgrid[sintheta0:sinthetan:res1*1j,phi0:phin:res2*1j]
69 return np.arccos(sintheta),phi
70
71 elif 'delaunay' in gtype.lower():
72
73 if len(args)==1: res1 = res2 = args[0]
74 else: res1,res2 = args
75
76 u,v = np.random.uniform(size=res1*res2),np.random.uniform(size=res1*res2)
77 phi = 2*pi*u
78 theta = np.arccos(2*v-1)
79
80 x = sin(theta)*sin(phi)
81 y = sin(theta)*cos(phi)
82 z = cos(theta)
83
84 points = np.array([x,y,z]).T
85 grid = Delaunay(points)
86 centers = np.zeros((len(grid.convex_hull),3))
87 for i,indices in enumerate(grid.convex_hull):
88 centers[i] = [x[indices].sum()/3,y[indices].sum()/3,z[indices].sum()/3]
89 theta,phi = np.arccos(centers[:,2]),np.arctan2(centers[:,1],centers[:,0])+pi
90
91 return theta,phi,grid
92
93 elif 'tri' in gtype.lower():
94
95 if len(args)==1: res1 = res2 = args[0]
96 else: res1,res2 = args
97
98 u,v = np.random.uniform(size=res1*res2),np.random.uniform(size=res1*res2)
99 phi = 2*pi*u
100 theta = np.arccos(2*v-1)
101 return theta,phi
102
103
104
105
106
107
108
110 """
111 Stitch a grid together that was originally defined on 1 quadrant.
112
113 We add the three other quandrants.
114 """
115 seamless = kwargs.get('seamless',False)
116 vtype = kwargs.get('vtype',['scalar' for i in quant])
117 gtype = kwargs.get('gtype','spher')
118 ravel = kwargs.get('ravel',False)
119
120 if gtype == 'spher':
121
122 alltheta = np.vstack([np.hstack([theta,theta]),np.hstack([theta+pi/2,theta+pi/2])])
123 allphi = np.vstack([np.hstack([phi,phi+pi]),np.hstack([phi,phi+pi])])
124
125
126 allquan = []
127 for i,iquant in enumerate(quant):
128
129 top1,top2 = iquant,iquant[:,::-1]
130 bot1,bot2 = iquant[::-1],iquant[::-1][:,::-1]
131 if vtype[i]=='scalar':
132 allquan.append(np.vstack([np.hstack([top1,top2]),np.hstack([bot1,bot2])]))
133
134 elif vtype[i]=='x':
135 allquan.append(np.vstack([np.hstack([top1,top2]),np.hstack([bot1,bot2])]))
136 elif vtype[i]=='y':
137 allquan.append(np.vstack([np.hstack([top1,-top2]),np.hstack([bot1,-bot2])]))
138 elif vtype[i]=='z':
139 allquan.append(np.vstack([np.hstack([top1,top2]),np.hstack([-bot1,-bot2])]))
140 elif vtype[i]=='vx':
141 allquan.append(np.vstack([np.hstack([top1,-top2]),np.hstack([bot1,-bot2])]))
142 elif vtype[i]=='vy':
143 allquan.append(np.vstack([np.hstack([top1,top2]),np.hstack([bot1,bot2])]))
144 elif vtype[i]=='vz':
145 allquan.append(np.vstack([np.hstack([top1,top2]),np.hstack([-bot1,-bot2])]))
146
147 out = [alltheta,allphi]+allquan
148
149
150 if seamless:
151
152 out = [np.column_stack([i,i[:,0]]) for i in out]
153 out[1][:,-1] += 2*pi
154
155 out = [np.vstack([i,i[0]]) for i in out]
156 out[0][-1] += pi
157
158
159 if ravel:
160 out = [i.ravel() for i in out]
161 else:
162 out = [theta,phi] + list(quant)
163
164 return out
165
166
168 """
169 Numerically compute surface normals of a grid (in absence of analytical alternative).
170
171 Also computes the surface elements, making L{surface_elements} obsolete.
172 """
173 if gtype=='spher':
174 raise NotImplementedError
175 elif gtype=='delaunay':
176 raise NotImplementedError
177 elif gtype=='triangular':
178
179 x,y,z = vectors.spher2cart_coord(r,phi,theta)
180
181 centers = np.zeros((len(grid.convex_hull),3))
182 normals = np.zeros((len(grid.convex_hull),3))
183 sizes = np.zeros(len(grid.convex_hull))
184
185
186
187
188 for i,indices in enumerate(grid.convex_hull):
189
190 centers[i] = [x[indices].sum()/3,y[indices].sum()/3,z[indices].sum()/3]
191
192 a = sqrt((x[indices[0]]-x[indices[1]])**2 + (y[indices[0]]-y[indices[1]])**2 + (z[indices[0]]-z[indices[1]])**2)
193 b = sqrt((x[indices[0]]-x[indices[2]])**2 + (y[indices[0]]-y[indices[2]])**2 + (z[indices[0]]-z[indices[2]])**2)
194 c = sqrt((x[indices[1]]-x[indices[2]])**2 + (y[indices[1]]-y[indices[2]])**2 + (z[indices[1]]-z[indices[2]])**2)
195 s = 0.5*(a+b+c)
196 sizes[i] = sqrt( s*(s-a)*(s-b)*(s-c))
197
198 side1 = [x[indices[1]]-x[indices[0]],y[indices[1]]-y[indices[0]],z[indices[1]]-z[indices[0]]]
199 side2 = [x[indices[2]]-x[indices[0]],y[indices[2]]-y[indices[0]],z[indices[2]]-z[indices[0]]]
200 normals[i] = np.cross(side1,side2)
201
202
203 normal_r,normal_phi,normal_theta = vectors.cart2spher(centers.T,normals.T)
204 normal_r = np.abs(normal_r)
205 centers_sph = vectors.cart2spher_coord(*centers.T)
206 normals = np.array(vectors.spher2cart(centers_sph,(normal_r,normal_phi,normal_theta)))
207
208
209 normals_T = normals.T
210 normals = normals_T / vectors.norm(normals_T)
211
212 print centers.shape,sizes.shape,normals.shape
213 return centers, sizes, normals
214
215
216
217 -def surface_elements((r,mygrid),(surfnormal_x,surfnormal_y,surfnormal_z),gtype='spher'):
218 """
219 Compute surface area of elements in a grid.
220
221 theta,phi must be generated like mgrid(theta_range,phi_range)
222
223 usually, the surfnormals are acquired via differentiation of a gravity potential,
224 and is then equal to the *negative* of the local surface gravity.
225 """
226 theta,phi = mygrid[:2]
227 if gtype=='spher':
228
229 dtheta = theta[1:]-theta[:-1]
230 dtheta = np.vstack([dtheta,dtheta[-1]])
231
232 dphi = phi[:,1:]-phi[:,:-1]
233 dphi = np.column_stack([dphi,dphi[:,-1]])
234
235 x,y,z = vectors.spher2cart_coord(r,phi,theta)
236
237 a = np.array([x,y,z])
238 b = np.array([surfnormal_x,surfnormal_y,surfnormal_z])
239
240 cos_gamma = vectors.cos_angle(a,b)
241
242 return r**2 * sin(theta) * dtheta * dphi / cos_gamma, cos_gamma
243
244 elif gtype=='delaunay':
245
246 x,y,z = vectors.spher2cart_coord(r,phi,theta)
247 a = np.array([x,y,z])
248 b = np.array([surfnormal_x,surfnormal_y,surfnormal_z])
249 cos_gamma = vectors.cos_angle(a,b)
250
251 delaunay_grid = mygrid[2]
252
253 sizes = np.zeros(len(delaunay_grid.convex_hull))
254 points = delaunay_grid.points
255 vertx,verty,vertz = points.T
256
257
258
259
260
261
262 centers = np.zeros((len(delaunay_grid.convex_hull),3))
263 for i,indices in enumerate(delaunay_grid.convex_hull):
264
265 a = sqrt((vertx[indices[0]]-vertx[indices[1]])**2 + (verty[indices[0]]-verty[indices[1]])**2 + (vertz[indices[0]]-vertz[indices[1]])**2)
266 b = sqrt((vertx[indices[0]]-vertx[indices[2]])**2 + (verty[indices[0]]-verty[indices[2]])**2 + (vertz[indices[0]]-vertz[indices[2]])**2)
267 c = sqrt((vertx[indices[1]]-vertx[indices[2]])**2 + (verty[indices[1]]-verty[indices[2]])**2 + (vertz[indices[1]]-vertz[indices[2]])**2)
268 s = 0.5*(a+b+c)
269 sizes[i] = sqrt( s*(s-a)*(s-b)*(s-c))
270
271
272
273
274
275
276
277 return sizes*r**2, cos_gamma
278
279 -def temperature(surface_gravity,g_pole,T_pole,beta=1.):
280 """
281 Calculate local temperature.
282
283 beta is gravity darkening parameter.
284 """
285 Grav = abs(surface_gravity/g_pole)**beta
286 Teff = Grav**0.25 * T_pole
287 return Teff
288
289 -def intensity(teff,grav,mu=None,photband='OPEN.BOL'):
290 """
291 Calculate local intensity.
292
293 beta is gravity darkening parameter.
294 """
295 if mu is None:
296 mu = np.ones_like(teff)
297 if (teff<3500).any() or np.isnan(teff).any():
298 print 'WARNING: point outside of grid, minimum temperature is 3500K'
299 teff = np.where((teff<3500) | np.isnan(teff),3500,teff)
300 if (grav<0.01).any() or np.isnan(grav).any():
301 print 'WARNING: point outside of grid, minimum gravity is 0 dex'
302 grav = np.where((np.log10(grav*100)<0.) | np.isnan(grav),0.01,grav)
303 intens = np.array([limbdark.get_itable(teff=iteff,logg=np.log10(igrav*100),absolute=True,mu=imu,photbands=[photband])[0] for iteff,igrav,imu in zip(teff.ravel(),grav.ravel(),mu.ravel())])
304 return intens.reshape(teff.shape)
305
306
308 """
309 Compute projected intensity in the line of sight.
310
311 gravity is vector directed inwards in the star
312 line of sight is vector.
313 """
314 ones = np.ones_like(gravity[0])
315 losx = line_of_sight[0]*ones
316 losy = line_of_sight[1]*ones
317 losz = line_of_sight[2]*ones
318 angles = vectors.angle(-gravity,np.array([losx,losy,losz]))
319 mus = cos(angles)
320 grav_ = vectors.norm(gravity)
321
322 intens = intensity(teff,grav_,mu=mus,photband=photband)
323
324
325 return intens*areas*mus,mus
326
327
328 -def project(star,view_long=(0,0,0),view_lat=(pi/2,0,0),photband='OPEN.BOL',
329 only_visible=False,plot_sort=False,scale_factor=1.):
330 """
331 Project and transform coordinates and vectors to align with the line-of-sight.
332
333 Parameter C{star} should be a record array containing fields 'teff','gravx',
334 'gravy','gravz','areas','vx','vy','vz'
335
336 and either you suply ('r','theta','phi') or ('x','y','z')
337
338 The XY direction is then the line-of-sight, and the YZ plane is the plane
339 of the sky.
340
341 An extra column 'projflux' and 'eyeflux' will be added. Projected flux
342 takes care of limb darkening, and projected surface area. Eye flux only
343 takes care of limbdarkening, and should only be used for plotting reasons.
344
345 view_long[0] of 0 means looking in the XY line, pi means looking in the YX line.
346 view_lat[0] of pi/2 means edge on, 0 or pi is pole-on.
347
348 This function updates all Cartesian coordinates present in the star, but not
349 the polar coordinates! The projected fluxes are added as a field 'projflux'
350 to the returned record array.
351
352 If you set 'only_visible' to True, only the information on the visible parts
353 of the star will be contained.
354
355 If you set 'plot_sort' to True, the arrays will be returned in a sorted order,
356 where the areas at the back come first. This is especially handy for plotting.
357
358 @parameters star: record array containing all necessary information on the
359 star
360 @type star: numpy record array
361 @parameter view_long: longitude viewing angle (radians) and coordinate zeropoint
362 @type view_long: tuple floats (radians,x,y)
363 @parameter view_lat: inclination viewing angle (radians) and coordinate zeropoint
364 @type view_lat: tuple floats (radians,x,z)
365 @parameter photband: photometric passband
366 @type photband: string
367 @parameter only_visible: flag to return only information on visible surface elements
368 @type only_visible: boolean
369 @parameter plot_sort: flag to sort the surface elements from back to front
370 @type plot_sort: boolean
371 """
372 myshape = star['gravx'].shape
373 gravx,gravy,gravz = np.array([star['gravx'].ravel(),star['gravy'].ravel(),star['gravz'].ravel()])
374 areas = star['areas'].ravel()
375 teff = star['teff'].ravel()
376 vx,vy,vz = star['vx'].ravel(),star['vy'].ravel(),star['vz'].ravel()
377
378
379 if not 'x' in star.dtype.names:
380 x,y,z = vectors.spher2cart_coord(star['r'].ravel(),star['phi'].ravel(),star['theta'].ravel())
381 else:
382 x,y,z = star['x'].ravel(),star['y'].ravel(),star['z'].ravel(),
383
384
385
386 x,y = vectors.rotate(x,y,view_long[0],x0=view_long[1],y0=view_long[2])
387 gravx,gravy = vectors.rotate(gravx,gravy,view_long[0])
388 vx,vy = vectors.rotate(vx,vy,view_long[0])
389
390 if view_lat[0]!=pi/2:
391 rot_i = -(pi/2 - view_lat[0])
392 x,z = vectors.rotate(x,z,rot_i)
393 gravx,gravz = vectors.rotate(gravx,gravz,rot_i)
394 vx,vz = vectors.rotate(vx,vz,rot_i)
395
396
397 view_vector = np.array([1.,0,0])
398 grav_local = np.array([gravx,gravy,gravz])
399 proj_flux,mus = projected_intensity(teff,grav_local,areas,view_vector,photband=photband)
400
401
402
403 new_star = star.copy()
404 new_star['gravx'],new_star['gravy'],new_star['gravz'] = gravx,gravy,gravz
405 new_star['vx'],new_star['vy'],new_star['vz'] = vx,vy,vz
406 if 'x' in star.dtype.names:
407 new_star['x'],new_star['y'],new_star['z'] = x*scale_factor,y*scale_factor,z*scale_factor
408 else:
409 new_star = pl.mlab.rec_append_fields(new_star,'x',x*scale_factor)
410 new_star = pl.mlab.rec_append_fields(new_star,'y',y*scale_factor)
411 new_star = pl.mlab.rec_append_fields(new_star,'z',z*scale_factor)
412 new_star = pl.mlab.rec_append_fields(new_star,'projflux',proj_flux)
413 new_star = pl.mlab.rec_append_fields(new_star,'eyeflux',proj_flux/areas)
414 new_star = pl.mlab.rec_append_fields(new_star,'mu',mus)
415
416
417 if only_visible:
418 new_star = new_star[-np.isnan(new_star['projflux'])]
419 if plot_sort:
420 new_star = new_star[np.argsort(new_star['x'])]
421 return new_star
422