diff --git a/.gitignore b/.gitignore index 21c3992ae..40c6e57b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# Compiled source +# Compiled source *.pyc *.class @@ -11,6 +11,7 @@ # HTML files of the documentation doc/html/* +docs/build # Packages @@ -37,3 +38,10 @@ doc/html/* .DS_Store* Icon? Thumbs.db + +# Sublime project files + +*.sublime-project +*.sublime-workspace + +*.dSYM diff --git a/IvS_repo.yml b/IvS_repo.yml deleted file mode 100644 index 1204622c2..000000000 --- a/IvS_repo.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: ivs_repo -channels: -- defaults -- https://repo.continuum.io/pkgs/pro/ -- https://repo.continuum.io/pkgs/free/ -dependencies: -- astropy=2.0.1=np113py27_0 -- cairo=1.14.8=0 -- certifi=2016.2.28=py27_0 -- cycler=0.10.0=py27_0 -- dbus=1.10.20=0 -- ephem=3.7.6.0=py27_0 -- expat=2.1.0=0 -- fontconfig=2.12.1=3 -- freetype=2.5.5=2 -- functools32=3.2.3.2=py27_0 -- glib=2.50.2=1 -- gst-plugins-base=1.8.0=0 -- gstreamer=1.8.0=0 -- h5py=2.7.0=np113py27_0 -- hdf5=1.8.17=2 -- icu=54.1=0 -- jpeg=8d=2 -- lcms=1.19=0 -- libffi=3.2.1=1 -- libgcc=5.2.0=0 -- libgfortran=3.0.0=1 -- libiconv=1.14=0 -- libpng=1.6.30=1 -- libxcb=1.12=1 -- libxml2=2.9.4=0 -- libxslt=1.1.29=0 -- lxml=3.8.0=py27_0 -- matplotlib=2.0.2=np113py27_0 -- mkl=2017.0.3=0 -- numpy=1.13.1=py27_0 -- openssl=1.0.2l=0 -- pcre=8.39=1 -- pil=1.1.7=py27_2 -- pip=9.0.1=py27_1 -- pixman=0.34.0=0 -- py=1.4.34=py27_0 -- pycairo=1.10.0=py27_0 -- pyparsing=2.2.0=py27_0 -- pyqt=5.6.0=py27_2 -- pytest=3.2.1=py27_0 -- python=2.7.13=0 -- python-dateutil=2.6.1=py27_0 -- pytz=2017.2=py27_0 -- qt=5.6.2=2 -- readline=6.2=2 -- scipy=0.19.1=np113py27_0 -- setuptools=36.4.0=py27_1 -- sip=4.18=py27_0 -- six=1.10.0=py27_0 -- sqlite=3.13.0=0 -- subprocess32=3.2.7=py27_0 -- tk=8.5.18=0 -- wheel=0.29.0=py27_0 -- zlib=1.2.11=0 - diff --git a/IvS_repo_3.6.yml b/IvS_repo_3.6.yml new file mode 100644 index 000000000..b3c64f881 --- /dev/null +++ b/IvS_repo_3.6.yml @@ -0,0 +1,19 @@ +name: ivs_repo_3.6 +channels: +- defaults +- https://repo.continuum.io/pkgs/pro/ +- https://repo.continuum.io/pkgs/free/ +- conda-forge +- anaconda +dependencies: +- astropy=2.0.1 +- matplotlib=2.0.2 +- numpy=1.13.1 +- python=3.6.2 +- scipy=0.19.1 +- uncertainties=3.0.2 +- h5py=2.8.0 +- ephem=3.7.6.0 +- lxml=3.8.0 +- pandas=0.20 +- pyqt=5.6.0 diff --git a/asteroseismology/displacements.py b/asteroseismology/displacements.py index df0312b80..0879b6238 100644 --- a/asteroseismology/displacements.py +++ b/asteroseismology/displacements.py @@ -3,21 +3,18 @@ """ -import numpy as np -from numpy import sqrt,pi,sin,cos,exp -from scipy.special import lpmv,legendre -from scipy.misc.common import factorial -from scipy.integrate import dblquad,quad -from scipy.spatial import Delaunay +from numpy import sqrt,pi,sin,cos,exp,log10,linspace,mgrid,polyval,zeros_like +from scipy.special import legendre +from scipy.misc import factorial #{ Helper functions def legendre_(l,m,x): """ Legendre polynomial. - + Check equation (3) from Townsend, 2002: - + >>> ls,x = [0,1,2,3,4,5],cos(linspace(0,pi,100)) >>> check = 0 >>> for l in ls: @@ -32,7 +29,7 @@ def legendre_(l,m,x): m_ = abs(m) legendre_poly = legendre(l) deriv_legpoly_ = legendre_poly.deriv(m=m_) - deriv_legpoly = np.polyval(deriv_legpoly_,x) + deriv_legpoly = polyval(deriv_legpoly_,x) P_l_m = (-1)**m_ * (1-x**2)**(m_/2.) * deriv_legpoly if m<0: P_l_m = (-1)**m_ * factorial(l-m_)/factorial(l+m_) * P_l_m @@ -41,10 +38,10 @@ def legendre_(l,m,x): def sph_harm(theta,phi,l=2,m=1): """ Spherical harmonic according to Townsend, 2002. - + This function is memoized: once a spherical harmonic is computed, the result is stored in memory - + >>> theta,phi = mgrid[0:pi:20j,0:2*pi:40j] >>> Ylm20 = sph_harm(theta,phi,2,0) >>> Ylm21 = sph_harm(theta,phi,2,1) @@ -56,7 +53,7 @@ def sph_harm(theta,phi,l=2,m=1): >>> p = subplot(412);p = title('l=2,m=1');p = imshow(Ylm21.real,cmap=cm.RdBu) >>> p = subplot(413);p = title('l=2,m=2');p = imshow(Ylm22.real,cmap=cm.RdBu) >>> p = subplot(414);p = title('l=2,m=-2');p = imshow(Ylm2_2.real,cmap=cm.RdBu) - + """ factor = (-1)**m * sqrt( (2*l+1)/(4*pi) * factorial(l-m)/factorial(l+m)) Plm = legendre_(l,m,cos(theta)) @@ -65,13 +62,13 @@ def sph_harm(theta,phi,l=2,m=1): def dsph_harm_dtheta(theta,phi,l=2,m=1): """ Derivative of spherical harmonic wrt colatitude. - + Using Y_l^m(theta,phi). - + Equation:: - + sin(theta)*dY/dtheta = (l*J_{l+1}^m * Y_{l+1}^m - (l+1)*J_l^m * Y_{l-1,m}) - + E.g.: Phd thesis of Joris De Ridder """ if abs(m)>=l: @@ -86,15 +83,15 @@ def dsph_harm_dtheta(theta,phi,l=2,m=1): def dsph_harm_dphi(theta,phi,l=2,m=1): """ Derivative of spherical harmonic wrt longitude. - + Using Y_l^m(theta,phi). - + Equation:: - + dY/dphi = i*m*Y """ return 1j*m*sph_harm(theta,phi,l,m) - + def norm_J(l,m): """ @@ -117,7 +114,7 @@ def norm_atlm1(l,m,Omega,k): Omega is actually spin parameter (Omega_rot/omega_freq) """ return Omega * (l+abs(m))/l * 2./(2*l+1) * (1 + (l+1)*k) - + #} #{ Displacement fields @@ -125,7 +122,7 @@ def norm_atlm1(l,m,Omega,k): def radial(theta,phi,l,m,t): """ Radial displacement, see Zima 2006. - + t in phase units """ return sph_harm(theta,phi,l,m) * exp(1j*t) @@ -148,9 +145,9 @@ def surface(theta,phi,l,m,t,Omega=0.1,k=1.,asl=0.2,radius=1.): ksi_theta = asl*sqrt(4*pi)*colatitudinal(theta,phi,l,m,t,Omega,k) ksi_phi = asl*sqrt(4*pi)*longitudinal(theta,phi,l,m,t,Omega,k) else: - ksi_theta = np.zeros_like(theta) - ksi_phi = np.zeros_like(phi) - + ksi_theta = zeros_like(theta) + ksi_phi = zeros_like(phi) + return (radius+ksi_r.real),\ (theta + ksi_theta.real),\ (phi + ksi_phi.real) @@ -162,70 +159,147 @@ def observables(theta,phi,teff,logg,l,m,t,Omega=0.1,k=1.,asl=0.2,radius=1.,delta ksi_theta = asl*sqrt(4*pi)*colatitudinal(theta,phi,l,m,t,Omega,k) ksi_phi = asl*sqrt(4*pi)*longitudinal(theta,phi,l,m,t,Omega,k) else: - ksi_theta = np.zeros_like(theta) - ksi_phi = np.zeros_like(phi) + ksi_theta = zeros_like(theta) + ksi_phi = zeros_like(phi) gravity = 10**(logg-2) return (radius+ksi_r.real),\ (theta + ksi_theta.real),\ (phi + ksi_phi.real),\ (teff + (delta_T*rad_part*teff).real),\ - np.log10(gravity+(delta_g*rad_part*gravity).real)+2 - + log10(gravity+(delta_g*rad_part*gravity).real)+2 + #} -if __name__=="__main__": - from ivs.roche import local - from ivs.coordinates import vectors - from enthought.mayavi import mlab - from divers import multimedia - theta,phi = local.get_grid(50,25,gtype='triangular') - r = np.ones_like(theta) - x,y,z = vectors.spher2cart_coord(r,phi,theta) - points = np.array([x,y,z]).T - grid = Delaunay(points) - #keep = phi>pi - #theta,phi = theta[keep],phi[keep] - l,m = 2,2 - asl = 0.01 - - for k in [0,1.,2.]: - for l in range(1,5): - for m in range(0,l+1,1): - mlab.figure(size=(1000,800)) - mlab.gcf().scene.disable_render = True - if l==0 or l==1: - asl = 0.1 - else: - asl = 0.01 - old_center=None - for i,t in enumerate(np.linspace(0,2*pi,100)): - print k,l,m,i - r,th,ph = surface(theta,phi,l,m,t,asl=asl,k=k) - center,size,normal = local.surface_normals(r,ph,th,grid,gtype='triangular') - - if i==0: - colors = r - r_c,phi_c,theta_c = vectors.cart2spher_coord(*center.T) - colors_ = r_c - - - mlab.clf() - mlab.points3d(center.T[0],center.T[1],center.T[2],colors_,scale_factor=0.05,scale_mode='none',colormap='RdBu',vmin=colors_.min(),vmax=colors_.max()) - #mlab.quiver3d(center.T[0],center.T[1],center.T[2],normal.T[0],normal.T[1],normal.T[2],colormap='spectral',scale_mode='none') - mlab.colorbar() - - if i>=1: - vx,vy,vz = center.T[0]-old_center.T[0],\ - center.T[1]-old_center.T[1],\ - center.T[2]-old_center.T[2] - v = np.sqrt(vx**2+vy**2+vz**2) - mlab.quiver3d(center.T[0],center.T[1],center.T[2],\ - vx,vy,vz,scalars=v,colormap='spectral',scale_mode='scalar') - #mlab.show() - old_center = center.copy() - mlab.view(distance=5,azimuth=-90,elevation=90) - mlab.savefig('pulsation_lm%d%d_k%03d_%03d.png'%(l,m,k,i)) - mlab.close() - multimedia.make_movie('pulsation_lm%d%d_k%03d_*.png'%(l,m,k),output='pulsation_lm%d%d_k%03d.avi'%(l,m,k)) - - \ No newline at end of file +def mainx(): + # Test function: legendre_ + ls,x = [0,1,2,3,4,5],cos(linspace(0,pi,100)) + check = 0 + for l in ls: + for m in range(-l,l+1,1): + Ppos = legendre_(l,m,x) + Pneg = legendre_(l,-m,x) + mycheck = Pneg,(-1)**m * factorial(l-m)/factorial(l+m) * Ppos + check += sum(abs(mycheck[0]-mycheck[1])>1e-10) + print(check) # should yield "0" + + # Test function: sph_harm + import matplotlib.pyplot as plt + theta,phi = mgrid[0:pi:20j,0:2*pi:40j] + Ylm20 = sph_harm(theta,phi,2,0) + Ylm21 = sph_harm(theta,phi,2,1) + Ylm22 = sph_harm(theta,phi,2,2) + Ylm2_2 = sph_harm(theta,phi,2,-2) + + fig,axs = plt.subplots(1,4,figsize=(16,4)) + fig.suptitle('Test of function ', fontsize=20) + axs[0].set_title('l=2,m=0');axs[0].imshow(Ylm20.real,cmap='jet') + axs[1].set_title('l=2,m=1');axs[1].imshow(Ylm21.real,cmap='jet') + axs[2].set_title('l=2,m=2');axs[2].imshow(Ylm22.real,cmap='jet') + axs[3].set_title('l=2,m=-2');axs[3].imshow(Ylm2_2.real,cmap='jet') + plt.tight_layout() + + # Test functions: dsph_harm_dtheta, norm_J (called by dsph_harm_dtheta), and dsph_harm_dphi + spherical_harmonic_derivative_wrt_colatitude = dsph_harm_dtheta(theta,phi,l=2,m=1) + spherical_harmonic_derivative_wrt_longitude = dsph_harm_dphi(theta,phi,l=2,m=1) + + fig,axs = plt.subplots(2,1,figsize=(12,8)) + axs[0].set_title('Test of function ', fontsize=20) + axs[0].imshow(spherical_harmonic_derivative_wrt_colatitude.real,cmap='jet') + axs[1].set_title('Test of function ', fontsize=20) + axs[1].imshow(spherical_harmonic_derivative_wrt_longitude.real,cmap='jet') + plt.tight_layout() + + # Test functions: norm_atlp1,norm_atlm1 + l,m = 2,1 + Omega,k = 0.1,1 + print(norm_atlp1(l,m,Omega,k)) + print(norm_atlm1(l,m,Omega,k)) + + # Test function: radial + times = linspace(0,2*pi,100) + t = times[50] + radial_displacement = radial(theta,phi,l,m,t) + + plt.figure() + plt.title('Test of function ', fontsize=20) + plt.imshow(radial_displacement.real,cmap='jet') + plt.tight_layout() + + # Test function: colatitudinal,longitudinal + fig,axs = plt.subplots(2,1,figsize=(12,8)) + axs[0].set_title('Test of function ', fontsize=20) + axs[0].imshow(colatitudinal(theta,phi,l,m,t,Omega,k).real,cmap='jet') + axs[1].set_title('Test of function ', fontsize=20) + axs[1].imshow(longitudinal(theta,phi,l,m,t,Omega,k).real,cmap='jet') + plt.tight_layout() + # plt.show() + + # Test function: surface + test_surface = surface(theta,phi,l,m,t,Omega=0.1,k=1.,asl=0.2,radius=1.) + print(type(test_surface)) # returns a tuple + + # Test function: observables + teff,logg = 5777,4.438 # approximate solar values + test_observables = observables(theta,phi,teff,logg,l,m,t,Omega=0.1,k=1.,asl=0.2,radius=1.,delta_T=0.01+0j,delta_g=0.01+0.5j) + print(type(test_observables)) # returns a tuple + +if __name__ == '__main__': mainx() + +#=========THE FOLLOWING EXAMPLE IS BROKEN (IMPORTED MODULES RAISE ERRORS) | USE AT YOUR OWN RISK============== + +# if __name__=="__main__": +# import numpy as np +# from ivs.roche import local +# from ivs.coordinates import vectors +# from enthought.mayavi import mlab +# from divers import multimedia +# from scipy.spatial import Delaunay +# theta,phi = local.get_grid(50,25,gtype='triangular') +# r = np.ones_like(theta) +# x,y,z = vectors.spher2cart_coord(r,phi,theta) +# points = np.array([x,y,z]).T +# grid = Delaunay(points) +# #keep = phi>pi +# #theta,phi = theta[keep],phi[keep] +# l,m = 2,2 +# asl = 0.01 +# +# for k in [0,1.,2.]: +# for l in range(1,5): +# for m in range(0,l+1,1): +# mlab.figure(size=(1000,800)) +# mlab.gcf().scene.disable_render = True +# if l==0 or l==1: +# asl = 0.1 +# else: +# asl = 0.01 +# old_center=None +# for i,t in enumerate(np.linspace(0,2*pi,100)): +# print(k,l,m,i) +# r,th,ph = surface(theta,phi,l,m,t,asl=asl,k=k) +# center,size,normal = local.surface_normals(r,ph,th,grid,gtype='triangular') +# +# if i==0: +# colors = r +# r_c,phi_c,theta_c = vectors.cart2spher_coord(*center.T) +# colors_ = r_c +# +# +# mlab.clf() +# mlab.points3d(center.T[0],center.T[1],center.T[2],colors_,scale_factor=0.05,scale_mode='none',colormap='RdBu',vmin=colors_.min(),vmax=colors_.max()) +# #mlab.quiver3d(center.T[0],center.T[1],center.T[2],normal.T[0],normal.T[1],normal.T[2],colormap='spectral',scale_mode='none') +# mlab.colorbar() +# +# if i>=1: +# vx,vy,vz = center.T[0]-old_center.T[0],\ +# center.T[1]-old_center.T[1],\ +# center.T[2]-old_center.T[2] +# v = np.sqrt(vx**2+vy**2+vz**2) +# mlab.quiver3d(center.T[0],center.T[1],center.T[2],\ +# vx,vy,vz,scalars=v,colormap='spectral',scale_mode='scalar') +# #mlab.show() +# old_center = center.copy() +# mlab.view(distance=5,azimuth=-90,elevation=90) +# mlab.savefig('pulsation_lm%d%d_k%03d_%03d.png'%(l,m,k,i)) +# mlab.close() +# multimedia.make_movie('pulsation_lm%d%d_k%03d_*.png'%(l,m,k),output='pulsation_lm%d%d_k%03d.avi'%(l,m,k)) diff --git a/asteroseismology/granulation.py b/asteroseismology/granulation.py index 2cd97de24..bd4c799af 100755 --- a/asteroseismology/granulation.py +++ b/asteroseismology/granulation.py @@ -8,21 +8,21 @@ import numpy as np from numpy.random import normal -import logging +# import logging # Setup the logger. -# Add at least one handler to avoid the message "No handlers could be found" -# on the console. The NullHandler is part of the standard logging module only +# Add at least one handler to avoid the message "No handlers could be found" +# on the console. The NullHandler is part of the standard logging module only # from Python 2.7 on. -class NullHandler(logging.Handler): - def emit(self, record): - pass - -logger = logging.getLogger("granulation") -nullHandler = NullHandler() -logger.addHandler(nullHandler) +# class NullHandler(logging.Handler): +# def emit(self, record): +# pass +# +# logger = logging.getLogger("granulation") +# nullHandler = NullHandler() +# logger.addHandler(nullHandler) @@ -30,46 +30,48 @@ def granulation(time, timescale, varscale): """ Simulates a time series showing granulation variations - + A first-order autoregressive process is used, as this gives a Harvey model in the frequency domain. See also: De Ridder et al., 2006, MNRAS 365, pp. 595-605. - + @param time: time points @type time: ndarray @param timescale: array of time scale "tau_i" of each granulation component of the granulation/magnetic activity. Same units as 'time'. @type timescale: ndarray - @param varscale: array of variation scale "sigma_i" of each component of the + @param varscale: array of variation scale "sigma_i" of each component of the granulation/magnetic activity in the appropriate passband. Same size as the timescale array. Unit: ppm @type varscale: ndarray @return: the granulation signal @rtype: ndarray - + Example: - + >>> time = np.linspace(0,100,200) # E.g. in days >>> timescale = np.array([5.0, 20.]) # time scales in days >>> varscale = np.array([10.0, 50.0]) # variation scale in ppm >>> gransignal = granulation(time, timescale, varscale) >>> flux = 100000.0 # mean flux level >>> signal = flux * (1.0 + gransignal) # signal in flux - + """ - - + + Ntime = len(time) Ncomp = len(timescale) - logger.info("Simulating %d granulation components\n" % Ncomp) - + # logger.info("Simulating %d granulation components\n" % Ncomp) + print("Simulating %d granulation components" % Ncomp) + # Set the kick (= reexcitation) timestep to be one 100th of the # shortest granulation time scale (i.e. kick often enough). kicktimestep = min(timescale) / 100.0 - - logger.info("Kicktimestep = %f\n" % kicktimestep) + + # logger.info("Kicktimestep = %f\n" % kicktimestep) + print("Kicktimestep = %f" % kicktimestep) # Predefine some arrays @@ -80,14 +82,16 @@ def granulation(time, timescale, varscale): # Warm up the first-order autoregressive process - logger.info("Granulation process warming up...\n") - + # logger.info("Granulation process warming up...\n") + print("Kicktimestep = %f" % kicktimestep) + for i in range(2000): granul = granul * (1.0 - kicktimestep / timescale) + normal(mu, sigma) # Start simulating the granulation time series - logger.info("Simulating granulation signal.\n") + # logger.info("Simulating granulation signal.\n") + print("Simulating granulation signal.") delta = 0.0 currenttime = time[0] - kicktimestep @@ -108,12 +112,30 @@ def granulation(time, timescale, varscale): + normal(mu, np.sqrt(delta/timescale)*varscale) currenttime = time[i] - # Add the different components to the signal. + # Add the different components to the signal. signal[i] = sum(granul) - # That's it! - + # Return the resulting signal + print("Returning the resulting signal") return(signal) +def mainx(): + import matplotlib.pyplot as plt + time = np.linspace(0,100,200) # E.g. in days + timescale = np.array([5.0, 20.]) # time scales in days + varscale = np.array([10.0, 50.0]) # variation scale in ppm + # granulation is simulated stochastically, thus each realization is different, given a certain random seed + gransignal = granulation(time, timescale, varscale) + flux = 100000.0 # mean flux level + signal = flux * (1.0 + gransignal) # signal in flux + + plt.figure(figsize=(12,4)) + plt.plot(time,signal) + plt.xlabel('Time [Ms]',fontsize=14) + plt.ylabel('Signal',fontsize=14) + plt.tight_layout() + plt.show() + +if __name__ == '__main__': mainx() diff --git a/asteroseismology/redgiantfreqs.py b/asteroseismology/redgiantfreqs.py index 6b8c9c13c..a13e4c4da 100644 --- a/asteroseismology/redgiantfreqs.py +++ b/asteroseismology/redgiantfreqs.py @@ -4,17 +4,17 @@ Contents: ========= -This library contains several functions which allow to identify modes -in the power-spectra of red giants. For the description of the pressure modes, -the formulism of Mosser et al. (2011A&A...525L...9M) for the 'Universal +This library contains several functions which allow to identify modes +in the power-spectra of red giants. For the description of the pressure modes, +the formulism of Mosser et al. (2011A&A...525L...9M) for the 'Universal Oscillation Pattern' is available. -For the description of the frequencies and rotational splitting of the mixed +For the description of the frequencies and rotational splitting of the mixed dipole modes, the formalism of Mosser et al. (2012A&A...540A.143M) and (2012arXiv1209.3336M) -has been scripted. To apply this method, one needs to know the value of the true -period spacing. The modulation of the mixing between pressure- and gravity-dipole -modes is described through a Lorentzian profile. This approach assumes a constant -Period Spacing for all radial orders. Comparing the modelled frequencies with the +has been scripted. To apply this method, one needs to know the value of the true +period spacing. The modulation of the mixing between pressure- and gravity-dipole +modes is described through a Lorentzian profile. This approach assumes a constant +Period Spacing for all radial orders. Comparing the modelled frequencies with the real frequencies, found in an spectrum, will work well for the several radial orders but deviations can be found in other regions of the spectrum. A similar Lorentzian description is valid for rotational splitting. @@ -22,21 +22,21 @@ Contents: --------- - * purePressureMode: calculates the position of the pure pressure pressure + * purePressureMode: calculates the position of the pure pressure pressure mode of a spherical degree \ell and radial order. * universalPattern: creates an array with the frequencies of all pure pressure modes with \ell=0,1,2 and 3, whereby the index i selectes all m odes of the same spherical degree: - e.g.: asymptoticRelation[0] = all \ell=0, asymptoticRelation[2] = all \ell=2, + e.g.: asymptoticRelation[0] = all \ell=0, asymptoticRelation[2] = all \ell=2, asymptoticRelation[-1] gives an array with the radial orders (as float). - * asymptoticRelation: returns an array with the frequencies of the pure - pressure modes of the pure pressure modes + * asymptoticRelation: returns an array with the frequencies of the pure + pressure modes of the pure pressure modes - * rotational splitting: calculates the expected rotational splitting + * rotational splitting: calculates the expected rotational splitting of a dipole mode based on the degree of mixing - + Example: KIC 4448777 -------------------- @@ -47,10 +47,10 @@ >>> centralRadial= 226.868 # [muHz], Frequency of the central radial mode >>> largeSep = 17.000 # [muHz], large Frequency separation in powerspectrum >>> smallSep = 2.201 # [muHz], small Frequency separation in powerspectrum ->>> +>>> >>> DeltaPg = 89.9 # [seconds], true period spacing of dipole modes >>> dfMax = 0.45 # [muHz], normalized rotational splitting (i.e. |freq_{m=0} - freq_{m=+/-1}| ->>> +>>> >>> qCoupling= 0.15; lambdaParam = 0.5; beParam = 0.08 #Default values First model the frequencies of the pure pressure modes. @@ -92,7 +92,7 @@ def purePressureMode(nnn,centralRadial, largeSep, smallSep, epsilon, ell): """ Calculates and returns the frequency of a single pure pressure mode for a spherical (\ell=0,1,2, and 3)[Nota Bene: 3 to be implemented]. - + smallSep can be set to an abitrary value if not calculating \ell=2 modes. @param nnn: radial order of the mode with respect to the radial order of the central radial mode. @@ -102,7 +102,7 @@ def purePressureMode(nnn,centralRadial, largeSep, smallSep, epsilon, ell): @param smallSep: small Frequency separation in powerspectrum, in muHz @type smallSep: float @param centralRadial: Frequency of the central radial mode, in muHz - @type centralRadial: float + @type centralRadial: float @param epsilon: echelle phase shift of the central Radial mode: epsilon = centralRadial / largeSep - centralRadial // largeSep @type epsilon: float @param ell: spherical degree of a mode @@ -113,7 +113,7 @@ def purePressureMode(nnn,centralRadial, largeSep, smallSep, epsilon, ell): ell = np.float(ell) nnn = np.float(nnn) - smallSep /= largeSep + smallSep /= largeSep dominantRadialOrderPressure = np.floor(centralRadial / largeSep) alphaCorr = 0.015 * largeSep**(-0.32) @@ -125,7 +125,7 @@ def purePressureMode(nnn,centralRadial, largeSep, smallSep, epsilon, ell): purePressureModeEll = largeSep * ( (dominantRadialOrderPressure + nnn) + epsilon - constantDEll + alphaCorr/2.*(nnn)**2.) return purePressureModeEll - + @@ -133,11 +133,11 @@ def purePressureMode(nnn,centralRadial, largeSep, smallSep, epsilon, ell): def universalPattern(largeSep, smallSep, centralRadial, numberOfRadialOrders = 3): - """ - Calculates and returns an array containing frequencies the frequencies of the pure pressure modes - of the degrees spherical degrees \ell= 0,1,2 and 3 for a defined range of radial orders. - - When slicing, the index number is equal to the spherical degree \ell, universalPattern[0] = all \ell=0, universalPattern[2] = all \ell=2, + """ + Calculates and returns an array containing frequencies the frequencies of the pure pressure modes + of the degrees spherical degrees \ell= 0,1,2 and 3 for a defined range of radial orders. + + When slicing, the index number is equal to the spherical degree \ell, universalPattern[0] = all \ell=0, universalPattern[2] = all \ell=2, The number of the radial order is given as output[-1]. If desired, the array can also be printed to the screen. @param largeSep: large Frequency separation in powerspectrum, in muHz @@ -145,39 +145,39 @@ def universalPattern(largeSep, smallSep, centralRadial, numberOfRadialOrders = 3 @param smallSep: small Frequency separation in powerspectrum, in muHz @type smallSep: float @param centralRadial: Frequency of the central radial mode, in muHz - @type centralRadial: float + @type centralRadial: float @param numberOfRadialOrders: for how many radial orders above and below the Central Radial Mode should the frequencies be calculated. @type numberOfRadialOrders: float or integer @return array with frequencies in muHz, [-1] indicates the radial order. @rtype float array """ - - + + epsilon = centralRadial / largeSep - centralRadial // largeSep radialOrderRange = np.arange(-numberOfRadialOrders,numberOfRadialOrders+1,dtype='float') dominantRadialOrderPressure = np.floor(centralRadial/largeSep) logger.info('dominantRadialOrderPressure {0}'.format(dominantRadialOrderPressure)) univPatternPerOrder = [] - + for nnn in radialOrderRange: radial = purePressureMode(nnn,centralRadial, largeSep, smallSep, epsilon, 0.) dipole = purePressureMode(nnn,centralRadial, largeSep, smallSep, epsilon, 1.) quadrupole = purePressureMode(nnn,centralRadial, largeSep, smallSep, epsilon, 2.) septupole = purePressureMode(nnn,centralRadial, largeSep, smallSep, epsilon, 3.) - + univPatternPerOrder.append([radial,dipole,quadrupole,septupole,nnn+dominantRadialOrderPressure]) - + univPatternPerOrder = np.array(univPatternPerOrder, dtype='float') - - + + logger.info('radial\t\tdipole\t\tquadrupole\tseptupole\tRadial Order (Pressure)') logger.info('--------------------------------------------------------------------------------------') for modeValue in univPatternPerOrder: logger.info('{0:.3f}\t\t{1:.3f}\t\t{2:.3f}\t\t{3:.3f}\t\t{4}'.format(modeValue[0],modeValue[1],modeValue[2],modeValue[3],modeValue[4])) - logger.info('--------------------------------------------------------------------------------------') + logger.info('--------------------------------------------------------------------------------------') return univPatternPerOrder.T - + @@ -185,11 +185,11 @@ def universalPattern(largeSep, smallSep, centralRadial, numberOfRadialOrders = 3 def asymptoticRelation(centralRadial, largeSep, DeltaPg, qCoupling= 0.15, approximationTreshold=0.001, numberOfRadialOrders = 4): """ Calculates and returns the frequencies mixed dipole modes in the powerspectrum of a red-giant star. - + This function the modulation of the mixing between pressure- and gravity-dipole and derives the frequencies of the mixed modes from it. @param centralRadial: Frequency of the central radial mode, in muHz - @type centralRadial: float + @type centralRadial: float @param largeSep: large Frequency separation in powerspectrum, in muHz @type largeSep: float @param smallSep: small Frequency separation in powerspectrum, in muHz @@ -198,15 +198,15 @@ def asymptoticRelation(centralRadial, largeSep, DeltaPg, qCoupling= 0.15, @type DeltaPg: float @param qCoupling: coupling factor @type qCoupling: float - + @param approximationTreshold: prefiltering of the solution. if modes seem to be missing, increase this parameter @type approximationTreshold:float @param numberOfRadialOrders: for how many radial orders above and below the Central Radial Mode should the frequencies be calculated. - @type numberOfRadialOrders: float or integer + @type numberOfRadialOrders: float or integer @return frequencies of the m=0 component of the mixed dipole modes @rtype float array """ - + DeltaPg /= 10.**6. epsilon = centralRadial / largeSep - centralRadial // largeSep radialOrderRange = np.arange(-numberOfRadialOrders,numberOfRadialOrders+1.,dtype='float') @@ -220,15 +220,15 @@ def asymptoticRelation(centralRadial, largeSep, DeltaPg, qCoupling= 0.15, valueDipole = purePressureMode(nnn,centralRadial, largeSep, 2., epsilon, 1.) freqRange = np.linspace((centralRadial+largeSep*(nnn-0.25)), (centralRadial+largeSep*(nnn+1.25)), 100000) equalityLine = freqRange/largeSep - arcTerm = largeSep / scipy.pi * np.arctan( qCoupling * np.tan( scipy.pi / (DeltaPg*freqRange) ) ) + arcTerm = largeSep / scipy.pi * np.arctan( qCoupling * np.tan( scipy.pi / (DeltaPg*freqRange) ) ) #print 'nnn',nnn,', pure pressure dipole',valueDipole,min(freqRange),max(freqRange) - + # distance to equalityLine residualsToEquealityLine = np.array((freqRange/largeSep,(valueDipole+arcTerm)/largeSep-equalityLine)) # Frequenz Abstand der moeglichen Mixed Modes zur _equalityLine_ # filtering for the close modes, speeds up the process filteredResidualsToEquealityLine = residualsToEquealityLine[:,(residualsToEquealityLine[1] < approximationTreshold/1.)] # nur die, die nahe genug an der Mixed Mode liegen - + """ # plotting the method to solve the implicit formula pl.figure(num=2) pl.plot(freqRange/largeSep,equalityLine,'r', alpha=0.5) @@ -256,7 +256,7 @@ def asymptoticRelation(centralRadial, largeSep, DeltaPg, qCoupling= 0.15, def symmetricRotationalModulation(dfMax, largeSep, frequenciesUniversalPattern, mixedModesPattern, lambdaParam = 0.5, beParam = 0.08): """ Calculates and returns rotational splitting - + This function the modulation of the mixing between pressure- and gravity-dipole and derives the frequencies of the mixed modes from it. @param dfMax: the largest rotational splitting measured for a given star @@ -275,31 +275,48 @@ def symmetricRotationalModulation(dfMax, largeSep, frequenciesUniversalPattern, @param beParam: parameter for fitting, Default: beParam = 0.08 @type beParam: float - - @return array with the rotational splitting + + @return array with the rotational splitting @rtype array, float """ - + radialModes = frequenciesUniversalPattern[0] pureDipolePressure = frequenciesUniversalPattern[1] mixedDipoleMode = mixedModesPattern - + rotationalCentreMode = []; rotationalSplitting = [] for nnn in np.arange(0,len(radialModes.T)-1): pureDipole = pureDipolePressure[ (pureDipolePressure > radialModes[nnn]) & (pureDipolePressure < radialModes[nnn+1])] - mixedDipoles= mixedDipoleMode[ (mixedDipoleMode > radialModes[nnn]) & (mixedDipoleMode < radialModes[nnn+1])] + mixedDipoles= mixedDipoleMode[ (mixedDipoleMode > radialModes[nnn]) & (mixedDipoleMode < radialModes[nnn+1])] for mode in mixedDipoles: modulationProfile = 1.- lambdaParam / (1. + ( (mode - pureDipole[0]) / (beParam*largeSep) )**2. ) - rotationalCentreMode.append(mode) + rotationalCentreMode.append(mode) rotationalSplitting.append(modulationProfile)# i.e., frequency of m=0 component, symmetric rotational splitting df. m=0 +/- df rotationalCentreMode = np.array(rotationalCentreMode) rotationalSplitting = np.array(rotationalSplitting).T*dfMax #print shape(rotationalCentreMode),shape(rotationalSplitting) - rotationalSplitting = np.vstack((rotationalCentreMode,rotationalSplitting)) + rotationalSplitting = np.vstack((rotationalCentreMode,rotationalSplitting)) rotationalSplitting = np.array(rotationalSplitting, dtype='float') - + return rotationalSplitting + +def mainx(): + centralRadial= 226.868 # [muHz], Frequency of the central radial mode + largeSep = 17.000 # [muHz], large Frequency separation in powerspectrum + smallSep = 2.201 # [muHz], small Frequency separation in powerspectrum + + DeltaPg = 89.9 # [seconds], true period spacing of dipole modes + dfMax = 0.45 # [muHz], normalized rotational splitting (i.e. |freq_{m=0} - freq_{m=+/-1}| + qCoupling= 0.15; lambdaParam = 0.5; beParam = 0.08 #Default values + + # First model the frequencies of the pure pressure modes. + frequenciesUniversalPattern = universalPattern(largeSep, smallSep, centralRadial) + # Next, we calculate the frequencies of the mixed dipole modes: + mixedModesPattern = asymptoticRelation(centralRadial,largeSep, DeltaPg, qCoupling) + rotationalSplittingModulation = symmetricRotationalModulation(dfMax, largeSep, frequenciesUniversalPattern, mixedModesPattern, lambdaParam, beParam) + +if __name__ == '__main__': mainx() diff --git a/asteroseismology/solarosc.py b/asteroseismology/solarosc.py index 6be8cf584..e97725d08 100755 --- a/asteroseismology/solarosc.py +++ b/asteroseismology/solarosc.py @@ -15,34 +15,34 @@ import numpy as np from numpy.random import uniform, normal from math import sin,cos, floor, pi -import logging +# import logging # Setup the logger. -# Add at least one handler to avoid the message "No handlers could be found" -# on the console. The NullHandler is part of the standard logging module only +# Add at least one handler to avoid the message "No handlers could be found" +# on the console. The NullHandler is part of the standard logging module only # from Python 2.7 on. -class NullHandler(logging.Handler): - def emit(self, record): - pass - -logger = logging.getLogger("solarosc") -nullHandler = NullHandler() -logger.addHandler(nullHandler) +# class NullHandler(logging.Handler): +# def emit(self, record): +# pass +# +# logger = logging.getLogger("solarosc") +# nullHandler = NullHandler() +# logger.addHandler(nullHandler) def solarosc(time, freq, ampl, eta): - + """ Compute time series of stochastically excited damped modes - + See also De Ridder et al., 2006, MNRAS 365, pp. 595-605. Example: - + >>> time = np.linspace(0, 40, 100) # in Ms >>> freq = np.array([23.0, 23.5]) # in microHz >>> ampl = np.array([100.0, 110.0]) # in ppm @@ -60,7 +60,7 @@ def solarosc(time, freq, ampl, eta): Oscillation kicktimestep: 3333.333333 300 kicks for warm up for oscillation signal Simulating stochastic oscillations - + @param time: time points [0..Ntime-1] (unit: e.g. Ms) @type time: ndarray @param freq: oscillation freqs [0..Nmodes-1] (unit: e.g. microHz) @@ -73,18 +73,20 @@ def solarosc(time, freq, ampl, eta): @return: signal[0..Ntime-1] @rtype: ndarray """ - + Ntime = len(time) Nmode = len(freq) - logger.info("Simulating %d modes" % Nmode) + # logger.info("Simulating %d modes" % Nmode) + print("Simulating %d modes" % Nmode) # Set the kick (= reexcitation) timestep to be one 100th of the # shortest damping time. (i.e. kick often enough). kicktimestep = (1.0 / max(eta)) / 100.0 - - logger.info("Oscillation kicktimestep: %f" % kicktimestep) + + # logger.info("Oscillation kicktimestep: %f" % kicktimestep) + print("Oscillation kicktimestep: %f" % kicktimestep) # Init start values of amplitudes, and the kicking amplitude # so that the amplitude of the oscillator will be on average be @@ -98,12 +100,13 @@ def solarosc(time, freq, ampl, eta): # initial conditions. Do this during the longest damping time. # But put a maximum on the number of kicks, as there might # be almost-stable modes with damping time = infinity - + damp = np.exp(-eta * kicktimestep) Nwarmup = min(20000, int(floor(1.0 / min(eta) / kicktimestep))) - logger.info("%d kicks for warm up for oscillation signal" % Nwarmup) - + # logger.info("%d kicks for warm up for oscillation signal" % Nwarmup) + print("%d kicks for warm up for oscillation signal" % Nwarmup) + for i in range(Nwarmup): amplsin = damp * amplsin + normal(np.zeros(Nmode), kick_amplitude) amplcos = damp * amplcos + normal(np.zeros(Nmode), kick_amplitude) @@ -119,8 +122,9 @@ def solarosc(time, freq, ampl, eta): # Start simulating the time series. - logger.info("Simulating stochastic oscillations") - + # logger.info("Simulating stochastic oscillations") + print("Simulating stochastic oscillations") + signal = np.zeros(Ntime) for j in range(Ntime): @@ -148,6 +152,25 @@ def solarosc(time, freq, ampl, eta): + amplcos[i] * cos(2*pi*freq[i]*time[j])) # Return the resulting signal - + print("Returning the resulting signal") return(signal) +def mainx(): + import matplotlib.pyplot as plt + time = np.linspace(0, 40, 100) # in Ms + freq = np.array([23.0, 23.5]) # in microHz + ampl = np.array([100.0, 110.0]) # in ppm + eta = np.array([1.e-6, 3.e-6]) # in 1/Ms + # oscillations are simulated stochastically, thus each realization is different, given a certain random seed + oscsignal = solarosc(time, freq, ampl, eta) + flux = 1000000.0 # average flux level + signal = flux * (1.0 + oscsignal) + + plt.figure(figsize=(12,4)) + plt.plot(time,signal) + plt.xlabel('Time [Ms]',fontsize=14) + plt.ylabel('Signal',fontsize=14) + plt.tight_layout() + plt.show() + +if __name__ == '__main__': mainx() diff --git a/aux/argkwargparser.py b/aux/argkwargparser.py index 531b16d06..3f9cef7ba 100644 --- a/aux/argkwargparser.py +++ b/aux/argkwargparser.py @@ -6,8 +6,6 @@ Example usage: -Given an example minimalistic Python module 'example.py' - >>> def testfunc(a,b,calc='sum'): >>> ... if calc=='sum': return a+b >>> ... elif calc=='prod': return a*b @@ -16,6 +14,7 @@ >>> if __name__=="__main__": >>> ... method,args,kwargs = argkwargparser.parse() >>> ... output = globals()[method](*args,**kwargs) +>>> ... print(output) Then, in a terminal, you can do:: @@ -30,17 +29,17 @@ """ import json import sys - + def parse(argv=None): """ Command-line to method call arg processing. - + - positional args: a b -> method('a', 'b') - intifying args: a 123 -> method('a', 123) - json loading args: a '["pi", 3.14, null]' -> method('a', ['pi', 3.14, None]) - keyword args: a foo=bar -> method('a', foo='bar') - using more of the above 1234 'extras=["r2"]' -> method(1234, extras=["r2"]) - + @param argv: Command line arg list. Defaults to `sys.argv`. @return: method-name, args, kwargs @rtype: string, list, dict @@ -63,7 +62,7 @@ def parse(argv=None): else: key, value = None, s try: - value = json.loads(value) + value = json.loads(value) except ValueError: pass if key: @@ -73,10 +72,15 @@ def parse(argv=None): return method_name, args, kwargs def test(*args,**kwargs): - print 'args',args - print 'kwargs',kwargs + print('args',args) + print('kwargs',kwargs) if __name__=="__main__": + def testfunc(a,b,calc='sum'): + if calc=='sum': return a+b + elif calc=='prod': return a*b + return None + method,args,kwargs = parse() - out = globals()[method](*args, **kwargs) - sys.exit(out) \ No newline at end of file + output = globals()[method](*args, **kwargs) + print(output) diff --git a/aux/decorators.py b/aux/decorators.py index fcc1a0840..336b3c39d 100644 --- a/aux/decorators.py +++ b/aux/decorators.py @@ -12,7 +12,7 @@ - Extend/Reopen an existing class (like in Ruby) """ import functools -import cPickle +import pickle import time import logging import sys @@ -33,7 +33,7 @@ def memoized(fctn): """ @functools.wraps(fctn) def memo(*args,**kwargs): - haxh = cPickle.dumps((fctn.__name__, args, sorted(kwargs.iteritems()))) + haxh = pickle.dumps((fctn.__name__, args, sorted(kwargs.items()))) modname = fctn.__module__ if not (modname in memory): memory[modname] = {} @@ -50,19 +50,19 @@ def clear_memoization(keys=None): Clear contents of memory """ if keys is None: - keys = memory.keys() + keys = list(memory.keys()) for key in keys: if key in memory: - riddens = [memory[key].pop(ikey) for ikey in memory[key].keys()[:]] + riddens = [memory[key].pop(ikey) for ikey in list(memory[key].keys())[:]] logger.debug("Memoization cleared") def make_parallel(fctn): """ Make a parallel version of a function. - + This extends the function's arguments with one extra argument, which is a parallel array that collects the output of the function. - + You have to decorate this function in turn with a function that calls the basic function, but with different arguments so to effectively make it parallel (e.g. in the frequency analysis case, this would be a function @@ -78,7 +78,7 @@ def extra(*args,**kwargs): def timeit(fctn): """ Time a function. - + @return: output from func, duration of function @rtype: 2-tuple """ @@ -87,14 +87,14 @@ def time_this(*args,**kwargs): start_time = time.time() output = fctn(*args,**kwargs) duration = time.time()-start_time - print "FUNC: %s MOD: %s: EXEC TIME: %.3fs"%(fctn.__module__,fctn.__name__,duration) + print("FUNC: %s MOD: %s: EXEC TIME: %.3fs"%(fctn.__module__,fctn.__name__,duration)) return output return time_this def timeit_duration(fctn): """ Time a function and return duration. - + @return: output from func, duration of function @rtype: 2-tuple """ @@ -103,14 +103,14 @@ def time_this(*args,**kwargs): start_time = time.time() output = fctn(*args,**kwargs) duration = time.time()-start_time - print "FUNC: %s MOD: %s: EXEC TIME: %.3fs"%(fctn.__module__,fctn.__name__,duration) + print("FUNC: %s MOD: %s: EXEC TIME: %.3fs"%(fctn.__module__,fctn.__name__,duration)) return duration return time_this def retry(tries, delay=3, backoff=2): """ Retry a function or method until it returns True. - + Delay sets the initial delay, and backoff sets how much the delay should lengthen after each failure. backoff must be greater than 1, or else it isn't really a backoff. tries must be at least 0, and delay greater than 0. @@ -150,7 +150,7 @@ def retry_http(tries, backoff=2, on_failure='error'): """ Retry a function or method reading from the internet until no socket or IOError is raised - + delay sets the initial delay, and backoff sets how much the delay should lengthen after each failure. backoff must be greater than 1, or else it isn't really a backoff. tries must be at least 0, and delay greater than 0. @@ -173,15 +173,15 @@ def retry_http(tries, backoff=2, on_failure='error'): def deco_retry(f): def f_retry(*args, **kwargs): mtries, mdelay = tries, delay # make mutable - + while mtries > 0: try: rv = f(*args, **kwargs) # Try again - except IOError,msg: + except IOError as msg: rv = False except socket.error: rv = False - + if rv != False: # Done on success return rv mtries -= 1 # consume an attempt @@ -190,7 +190,7 @@ def f_retry(*args, **kwargs): logger.error("URL timeout: %d attempts remaining (delay=%.1fs)"%(mtries,mdelay)) logger.critical("URL timeout: number of trials exceeded") if on_failure=='error': - raise IOError,msg # Ran out of tries :-( + raise IOError(msg) # Ran out of tries :-( else: logger.critical("URL Failed, but continuing...") return None @@ -235,7 +235,7 @@ def __init__(self): il = self.ilogger logging.basicConfig() il.setLevel(logging.INFO) - + def write(self, text): """Logs written output to a specific logger""" text = text.strip() @@ -265,23 +265,23 @@ def do_filter(*args,**kwargs): args_,varargs,keywords,defaults = inspect.getargspec(fctn) #-- loop over all keywords given by the user, and remove them from the # kwargs dictionary if their names are not present in 'args' - for key in kwargs.keys(): + for key in list(kwargs.keys()): if not key in args_[-len(defaults):]: thrash = kwargs.pop(key) return fctn(*args,**kwargs) return do_filter - + #} #{ Disable decorator def disabled(func): """ Disables the provided function - + use as follows: 1. set a global enable flag for a decorator: >>> global_mydecorator_enable_flag = True - + 2. toggle decorator right before the definition >>> state = mydecorator if global_mydecorator_enable_flag else disabled >>> @state @@ -295,13 +295,13 @@ def extend(cls): """ Decorator that allows you to add methods or attributes to an already existing class. Inspired on the reopening of classes in Ruby - + Example: - + >>> @extend(SomeClassThatAlreadyExists) >>> def some_method(): >>> do stuff - + Will add the method some_method to SomeClassThatAlreadyExists """ def decorator(f): @@ -316,9 +316,17 @@ def class_extend(cls): cls. Use at own risk, results may vary!!! """ def decorator(nclf): - for at in nclf.__dict__.keys(): + for at in list(nclf.__dict__.keys()): setattr(cls, at, getattr(nclf, at)) return cls return decorator -#} \ No newline at end of file +#} + +if __name__=="__main__": + from ivs.sed.reddening import fitzpatrick1999 + memo = memoized(fitzpatrick1999) + print(memo.__doc__) + print(memory.keys()) + # clear_memoization(keys=None) + # print(memo.__doc__) diff --git a/aux/loggers.py b/aux/loggers.py index 494abe662..4d15ef568 100644 --- a/aux/loggers.py +++ b/aux/loggers.py @@ -22,37 +22,37 @@ def get_basic_logger(name="",clevel='INFO', flevel='DEBUG',filename=None,filemode='w'): """ Return a basic logger via a log file and/or terminal. - + Example 1: log only to the console, accepting levels "INFO" and above - + >>> logger = loggers.get_basic_logger() - + Example 2: log only to the console, accepting levels "DEBUG" and above - + >>> logger = loggers.get_basic_logger(clevel='DEBUG') - + Example 3: log only to a file, accepting levels "DEBUG" and above - + >>> logger = loggers.get_basic_logger(clevel=None,filename='mylog.log') - + Example 4: log only to a file, accepting levels "INFO" and above - + >>> logger = loggers.get_basic_logger(clevel=None,flevel='INFO',filename='mylog.log') - + Example 5: log to the terminal (INFO and above) and file (DEBUG and above) - + >>> logger = loggers.get_basic_logger(filename='mylog.log') - + @param name: name of the logger @type name: str """ #-- define formats format = '%(asctime)s %(name)s %(levelname)-8s %(message)s' datefmt = '%a, %d %b %Y %H:%M' - + if clevel: clevel = logging.__dict__[clevel.upper()] if flevel: flevel = logging.__dict__[flevel.upper()] - + #-- set up basic configuration. # The basicConfig sets up one default logger. If you give a filename, it's # a FileHandler, otherwise a StreamHandler. @@ -62,7 +62,7 @@ def get_basic_logger(name="",clevel='INFO', logging.basicConfig(level=min(flevel,clevel), format=format,datefmt=datefmt, filename=filename,filemode=filemode) - if filename is not None and clevel: + if filename is not None and clevel: # define a Handler which writes INFO messages or higher to the sys.stderr ch = logging.StreamHandler() ch.setLevel(clevel) @@ -73,5 +73,5 @@ def get_basic_logger(name="",clevel='INFO', logging.getLogger(name).addHandler(ch) #-- If we only want a console: else: - logging.basicConfig(level=clevel,format=format,datefmt=datefmt,filename=filename,filemode=filemode) + logging.basicConfig(level=clevel,format=format,datefmt=datefmt,filename=filename,filemode=filemode) return logging.getLogger(name) diff --git a/aux/numpy_ext.py b/aux/numpy_ext.py index 452098c7d..bc8058092 100644 --- a/aux/numpy_ext.py +++ b/aux/numpy_ext.py @@ -6,6 +6,7 @@ import pylab as pl from scipy import spatial import itertools +from numpy.lib.recfunctions import rec_append_fields #{ Normal arrays def unique_arr(a,axis=0,return_index=False): @@ -268,11 +269,11 @@ def random_rectangular_grid(gridpoints,size): hypercube_sides = [abs(np.diff(axis)) for axis in axis_values] hypercube_volume = 0. random_ranges_sizes = [] - for combination,limit_indices in itertools.izip(itertools.product(*hypercube_sides),itertools.product(*axis_indices)): + for combination,limit_indices in zip(itertools.product(*hypercube_sides),itertools.product(*axis_indices)): limit_indices = np.array(limit_indices) #-- create all corners of the particular grid cube, they need to be in the # KDTree! - for corner in itertools.product(*zip(limit_indices-1,limit_indices)): + for corner in itertools.product(*list(zip(limit_indices-1,limit_indices))): dist = tree.query([axis_values[i][j] for i,j in enumerate(corner)])[0] if dist!=0: break @@ -369,7 +370,10 @@ def recarr_join(arr1,arr2): """ arr1 = arr1.copy() for field in arr2.dtype.names: - arr1 = pl.mlab.rec_append_fields(arr1,field,arr2[field]) + #original function, deprecated + # arr1 = pl.mlab.rec_append_fields(arr1,field,arr2[field]) + #new function + arr1 = rec_append_fields(arr1,field,arr2[field]) return arr1 #} diff --git a/aux/numpyctypes.py b/aux/numpyctypes.py index a96174347..16cc0ba8c 100644 --- a/aux/numpyctypes.py +++ b/aux/numpyctypes.py @@ -21,7 +21,7 @@ 'I' : C.c_uint, 'L' : C.c_ulong, 'Q' : C.c_ulonglong} - + @@ -31,21 +31,21 @@ def c_ndarray(a, dtype = None, ndim = None, shape = None, requirements = None): """ Returns a ctypes structure of the array 'a' - containing the arrays info (data, shape, strides, ndim). - A check is made to ensure that the array has the specified dtype + containing the arrays info (data, shape, strides, ndim). + A check is made to ensure that the array has the specified dtype and requirements. - + Example: - + >>> myArray = np.arange(10.0) - >>> myCstruct = c_ndarray(myArray, dtype=np.double, ndim = 3, shape = (4,3,2), + >>> myCstruct = c_ndarray(myArray, dtype=np.double, ndim = 3, shape = (4,3,2), ... requirements = ['c_contiguous']) - + @param a: the numpy array to be converted @type a: ndarray @param dtype: the required dtype of the array, convert if it doesn't match @type dtype: numpy dtype - @param ndim: the required number of axes of the array, + @param ndim: the required number of axes of the array, complain if it doesn't match @type ndim: integer @param shape: required shape of the array, complain if it doesn't match @@ -54,66 +54,66 @@ def c_ndarray(a, dtype = None, ndim = None, shape = None, requirements = None): or "c_contiguous". Convert if it doesn't match. @type requirements: list @return: ctypes structure with the fields: - - data: pointer to the data : the type is determined with the + - data: pointer to the data : the type is determined with the dtype of the array, and with ctypesDict. - shape: pointer to long array : size of each of the dimensions - strides: pointer to long array : strides in elements (not bytes) @rtype: ctypes structure - + """ - + if not requirements: - + # Also allow derived classes of ndarray - + array = np.asanyarray(a, dtype=dtype) - + else: - + # Convert requirements to captial letter codes: # (ensurearray' -> 'E'; 'aligned' -> 'A' # 'fortran', 'f_contiguous', 'f' -> 'F' # 'contiguous', 'c_contiguous', 'c' -> 'C') - + requirements = [x[0].upper() for x in requirements] subok = (0 if 'E' in requirements else 1) - + # Make from 'a' an ndarray with the specified dtype, but don't copy the # data (yet). This also ensures that the .flags attribute is present. - + array = np.array(a, dtype=dtype, copy=False, subok=subok) # See if copying all data is really necessary. - # Note: 'A' = (A)ny = only (F) it is was already (F) - - copychar = 'A' + # Note: 'A' = (A)ny = only (F) it is was already (F) + + copychar = 'A' if 'F' in requirements: copychar = 'F' elif 'C' in requirements: copychar = 'C' - + for req in requirements: if not array.flags[req]: array = array.copy(copychar) break - - + + # If required, check the number of axes and the shape of the array - + if ndim is not None: if array.ndim != ndim: - raise TypeError, "Array has wrong number of axes" - + raise TypeError("Array has wrong number of axes") + if shape is not None: if array.shape != shape: - raise TypeError, "Array has wrong shape" - + raise TypeError("Array has wrong shape") + # Define a class that serves as interface of an ndarray to ctypes. - # Part of the type depends on the array's dtype. - + # Part of the type depends on the array's dtype. + class ndarrayInterfaceToCtypes(C.Structure): pass - + typechar = array.dtype.char if typechar in ctypesDict: @@ -122,18 +122,18 @@ class ndarrayInterfaceToCtypes(C.Structure): ("shape" , C.POINTER(C.c_long)), ("strides", C.POINTER(C.c_long))] else: - raise TypeError, "dtype of input ndarray not supported" + raise TypeError("dtype of input ndarray not supported") # Instantiate the interface class and attach the ndarray's internal info. # Ctypes does automatic conversion between (c_long * #) arrays and POINTER(c_long). - + ndarrayInterface = ndarrayInterfaceToCtypes() ndarrayInterface.data = array.ctypes.data_as(C.POINTER(ctypesDict[typechar])) ndarrayInterface.shape = (C.c_long * array.ndim)(*array.shape) ndarrayInterface.strides = (C.c_long * array.ndim)(*array.strides) for n in range(array.ndim): ndarrayInterface.strides[n] /= array.dtype.itemsize - + return ndarrayInterface diff --git a/aux/progressMeter.py b/aux/progressMeter.py index 15204f5d7..70b5cd6c7 100644 --- a/aux/progressMeter.py +++ b/aux/progressMeter.py @@ -17,7 +17,7 @@ [-------------------------> ] 41% 821.2/sec """ -import time, sys, math +import time, sys class ProgressMeter(object): ESC = chr(27) diff --git a/aux/termtools.py b/aux/termtools.py index efdccb8c6..b556e79a1 100644 --- a/aux/termtools.py +++ b/aux/termtools.py @@ -12,13 +12,10 @@ >>> print blink_green_bgred_bold('blinking green bold text on red background') blinking green bold text on red background - + """ -from __future__ import print_function -import functools -import inspect + import sys -import types import subprocess import time RED="\[\033[0;35m\]" @@ -47,26 +44,26 @@ def overwrite_line(message): """ Save cursor at current position, clear current line, print the message and reset the cursor. - + @param message: message to print to the screen @type message: str """ ESC=chr(27) - print('{ESC}[s{ESC}[2K{message}{ESC}[u'.format(ESC=ESC,message=message),end='') - + # print('{ESC}[s{ESC}[2K{message}{ESC}[u'.format(ESC=ESC,message=message),end='') + def line_at_a_time(fileobj): """ Return one line at a time from a file-like object. - + >>> #p1 = subprocess.Popen('ls',shell=True,stdout=subprocess.PIPE) >>> #for line in line_at_a_time(p1.stdout): ... # print 'next line:',line.strip() >>> #retcode = p1.wait() - + use C{os.kill(p1.pid,SIGKILL)} to with C{SIGKILL} from C{signal} standard module to kill the process. - + return model_number Works around the iter behavior of pipe files in Python 2.x, e.g., instead of "for line in file" you can @@ -76,16 +73,16 @@ def line_at_a_time(fileobj): line = fileobj.readline() if not line: return - yield line + yield line def subprocess_timeout(command, time_out): """ - + Kill a running subprocess after a certain amount of time. - + Command represents the command for the process you would give in a terminal e.g. 'ls -l' in a terminal becomes ["ls", "-l"] or 'firefox' becomes ["firefox"]'; time_out is expressed in seconds. If the process did not complete before time_out, the process is killed. - + @param command: command to run @type command: str """ @@ -106,7 +103,7 @@ def subprocess_timeout(command, time_out): # and fill the return code with some error value returncode = -1 # (comment 2) - else: + else: # in the case the process completed normally returncode = c.poll() @@ -115,7 +112,7 @@ def subprocess_timeout(command, time_out): class CallInstruct: """ Generate a callable function on-the-fly. - + The function takes text as an input and will first print all the terminal instructions, then the text and then reset the terminal settings to the normal value. @@ -141,7 +138,7 @@ def __init__(self, username=None): self.time = [] self.free = [] self.used = [] - + def usage(self): """Return int containing memory used by user's processes.""" #self.process = subprocess.Popen("ps -u %s -o rss | awk '{sum+=$1} END {print sum}'" % self.username, @@ -153,7 +150,7 @@ def usage(self): used,free = process.communicate()[0].split('\n')[1].split()[2:4] self.free.append(free) self.used.append(used) - + @@ -171,4 +168,4 @@ def __getattr__(self, name): return CallInstruct(name) # Some sensible default #-- Wrap the module so that the C{getattr} function can be redefined. -sys.modules[__name__] = Wrapper(sys.modules[__name__]) \ No newline at end of file +sys.modules[__name__] = Wrapper(sys.modules[__name__]) diff --git a/aux/xmlparser.py b/aux/xmlparser.py index 1806724d3..2e0769e0a 100644 --- a/aux/xmlparser.py +++ b/aux/xmlparser.py @@ -4,7 +4,6 @@ """ import xml.parsers.expat -import urllib class XMLParser: """ @@ -22,7 +21,7 @@ def __init__(self,text,logger=None): if logger: logger.debug("XMLParser initialised") p.Parse(text) - + def start_element(self,name,attrs): """ If a new element is found, at it to the dictionary @@ -34,22 +33,22 @@ def start_element(self,name,attrs): curdict = self.content for ie in self.elem: curdict = curdict[ie] - if not name in curdict.keys(): + if not name in list(curdict.keys()): curdict[name] = {} if not self.elem[-1]==name: self.elem.append(name) if attrs: - key = attrs[attrs.keys()[0]] + key = attrs[list(attrs.keys())[0]] #print "START2",self.elem #print "START3",name,attrs,key,curdict[name].keys() - if key not in curdict[name].keys(): + if key not in list(curdict[name].keys()): curdict[name][key] = {} self.elem.append(key) - + def end_element(self,name): """ Remove the element from the queu (and everything after it) - + @parameter name: designation of the element @type name: string """ @@ -57,11 +56,11 @@ def end_element(self,name): if len(self.elem)>1: index = len(self.elem) - 1 - self.elem[::-1].index(name) self.elem = self.elem[:index] - + def char_data(self,data): """ Add the value of an element to the dictionary with its designation. - + @parameter data: value of the element @type data: string """ @@ -85,4 +84,3 @@ def char_data(self,data): curdict[self.elem[-1]] = data if self.logger: self.logger.debug("... %s: %s"%(self.elem,data)) - \ No newline at end of file diff --git a/catalogs/coralie.py b/catalogs/coralie.py index 04392b7d6..9fa033937 100644 --- a/catalogs/coralie.py +++ b/catalogs/coralie.py @@ -74,7 +74,7 @@ def make_data_overview(): outfile.write('#unseq prog_id obsmode bvcor observer object ra dec bjd exptime date-avg filename\n') outfile.write('#i i a20 >f8 a50 a50 >f8 >f8 >f8 >f8 a30 a200\n') for i,obj_file in enumerate(obj_files): - print i,len(obj_files) + print(i,len(obj_files)) #-- keep track of: UNSEQ, BJD, BVCOR, OBSERVER, RA, DEC , PROG_ID, OBSMODE, EXPTIME, DATE-AVG, OBJECT and filename contents = dict(unseq=-1,prog_id=-1,obsmode='CORALIE',bvcor=0,observer='nan', object='nan',ra=np.nan,dec=np.nan, diff --git a/catalogs/corot.py b/catalogs/corot.py index 1d8f7fe6a..047c9fd89 100644 --- a/catalogs/corot.py +++ b/catalogs/corot.py @@ -6,7 +6,7 @@ import logging import os -import urllib +import urllib.request, urllib.parse, urllib.error import astropy.io.fits as pf import numpy as np from ivs.aux import loggers @@ -21,9 +21,9 @@ def get_sismo_data(ID): """ Retrieve CoRoT timeseries from a local data repository. - + The output record array has fields 'HJD', 'flux', 'e_flux', 'flag'. - + @param ID: ID of the target: either an integer (CoRoT ID), an SIMBAD-recognised target name, or a valid CoRoT FITS file @type ID: int or str @@ -33,7 +33,7 @@ def get_sismo_data(ID): #-- data on one target can be spread over multiple files: collect the # data data = [] - + if isinstance(ID,str) and os.path.isfile(ID): header = pf.getheader(ID) times,flux,error,flags = fits.read_corot(ID) @@ -62,7 +62,7 @@ def get_sismo_data(ID): #-- now make a record array and sort according to times if not data: raise ValueError('target {0} not in offline CoRoT data repository'.format(ID)) - data = np.hstack(data) + data = np.hstack(data) data = np.rec.fromarrays(data,dtype=[('HJD','>f8'),('flux','>f8'),('e_flux','>f8'),('flag','i')]) sa = np.argsort(data['HJD']) return data[sa],header @@ -70,9 +70,9 @@ def get_sismo_data(ID): def get_exo_data(ID,type_data='white'): """ Retrieve CoRoT timeseries from a remote data repository. - + The output record array has fields 'HJD', 'flux', 'e_flux', 'flag'. - + @param ID: ID of the target: either an integer (CoRoT ID), an SIMBAD-recognised target name, or a valid CoRoT FITS file @type ID: int or str @@ -97,7 +97,7 @@ def get_exo_data(ID,type_data='white'): return None #-- collect the files containing data on the target for filename in cat['FileName'][cat['CoRoT']==ID]: - url = urllib.URLopener() + url = urllib.request.URLopener() filen,msg = url.retrieve(filename) try: header = pf.getheader(filen) @@ -106,11 +106,11 @@ def get_exo_data(ID,type_data='white'): times,flux,error,flags = fits.read_corot(filen,type_data=type_data) url.close() data.append([times,flux,error,flags]) - + #-- now make a record array and sort according to times if not data: raise ValueError('target {0} not in online CoRoT data repository'.format(ID)) - data = np.hstack(data) + data = np.hstack(data) data = np.rec.fromarrays(data,dtype=[('HJD','>f8'),('flux','>f8'),('e_flux','>f8'),('flag','i')]) sa = np.argsort(data['HJD']) return data[sa],header @@ -123,11 +123,11 @@ def get_exo_catalog(): exofile = config.get_datafile('catalogs/corot/exo','exo.tsv') data,units,comms = vizier.tsv2recarray(exofile) return data,units,comms - + def resolve(corot_id): """ Convert a CoRoT ID to ra,dec. - + @param corot_id: CoRoT exoplanet identification number @type corot_id: int @return: RA, DEC (degrees) diff --git a/catalogs/crossmatch.py b/catalogs/crossmatch.py index 1a6a17655..18206afc7 100644 --- a/catalogs/crossmatch.py +++ b/catalogs/crossmatch.py @@ -11,8 +11,6 @@ $:> python crossmatch.py get_photometry --help """ import logging -import itertools -import pylab as pl import numpy as np from ivs.catalogs import vizier @@ -20,12 +18,9 @@ from ivs.catalogs import gcpd from ivs.catalogs import mast from ivs.catalogs import sesame -from ivs.units import conversions from ivs.aux import numpy_ext -from ivs.aux import progressMeter from ivs.aux import loggers from ivs.aux import argkwargparser -from ivs.inout import ascii from scipy.spatial import KDTree @@ -238,7 +233,7 @@ def photometry2str(master): help(globals()[method]) else: master = globals()[method](*args,**kwargs) - print photometry2str(master) + print(photometry2str(master)) diff --git a/catalogs/gator.py b/catalogs/gator.py index ccc137016..b86cb11fb 100644 --- a/catalogs/gator.py +++ b/catalogs/gator.py @@ -3,14 +3,13 @@ Interface to the GATOR search engine """ import os -import urllib +import urllib.request, urllib.parse, urllib.error import logging -import ConfigParser +import configparser import numpy as np from ivs.aux import loggers from ivs.aux import numpy_ext -from ivs.inout import ascii from ivs.sed import filters from ivs.units import conversions @@ -22,7 +21,7 @@ basedir = os.path.dirname(os.path.abspath(__file__)) #-- read in catalog information -cat_info = ConfigParser.ConfigParser() +cat_info = configparser.ConfigParser() cat_info.optionxform = str # make sure the options are case sensitive cat_info.readfp(open(os.path.join(basedir,'gator_cats_phot.cfg'))) @@ -32,21 +31,21 @@ def search(catalog,**kwargs): """ Search and retrieve information from a Gator catalog. - + Two ways to search for data within a catalog C{name}: - + 1. You're looking for info on B{one target}, then give the target's C{ID} or coordinates (C{ra} and C{dec}), and a search C{radius}. - + 2. You're looking for information of B{a whole field}, then give the field's coordinates (C{ra} and C{dec}), and C{radius}. - + If you have a list of targets, you need to loop this function. - + If you supply a filename, the results will be saved to that path, and you will get the filename back as received from urllib.URLopener (should be the same as the input name, unless something went wrong). - + If you don't supply a filename, you should leave C{filetype} to the default C{tsv}, and the results will be saved to a temporary file and deleted after the function is finished. The content of the file @@ -55,8 +54,8 @@ def search(catalog,**kwargs): catalog). The entries in the dictionary are of type C{ndarray}, and will be converted to a float-array if possible. If not, the array will consist of strings. The comments are also returned as a list of strings. - - + + @param catalog: name of a GATOR catalog (e.g. 'II/246/out') @type catalog: str @keyword filename: name of the file to write the results to (no extension) @@ -70,25 +69,25 @@ def search(catalog,**kwargs): filetype = os.path.splitext(filename)[1][1:] elif filename is not None: filename = '%s.%s'%(filename,filetype) - + #-- gradually build URI base_url = _get_URI(catalog,**kwargs) #-- prepare to open URI - url = urllib.URLopener() + url = urllib.request.URLopener() filen,msg = url.retrieve(base_url,filename=filename) # maybe we are just interest in the file, not immediately in the content if filename is not None: logger.info('Querying GATOR source %s and downloading to %s'%(catalog,filen)) url.close() return filen - + # otherwise, we read everything into a dictionary if filetype=='1': try: results,units,comms = txt2recarray(filen) #-- raise an exception when multiple catalogs were specified except ValueError: - raise ValueError, "failed to read %s, perhaps multiple catalogs specified (e.g. III/168 instead of III/168/catalog)"%(catalog) + raise ValueError("failed to read %s, perhaps multiple catalogs specified (e.g. III/168 instead of III/168/catalog)"%(catalog)) url.close() logger.info('Querying GATOR source %s (%d)'%(catalog,(results is not None and len(results) or 0))) return results,units,comms @@ -99,11 +98,11 @@ def search(catalog,**kwargs): def list_catalogs(): """ Return a list of all availabe GATOR catalogues. - + @return: list of gator catalogs and discriptions @rtype: list of string tuples """ - url = urllib.URLopener() + url = urllib.request.URLopener() filen,msg = url.retrieve('http://irsa.ipac.caltech.edu/cgi-bin/Gator/nph-scan?mode=ascii') results,units,comms = txt2recarray(filen) cats = [] @@ -120,11 +119,11 @@ def list_catalogs(): def get_photometry(ID=None,extra_fields=['dist','ra','dec'],**kwargs): """ Download all available photometry from a star to a record array. - + For extra kwargs, see L{_get_URI} and L{gator2phot} - + Example usage: - + >>> import pylab >>> import vizier >>> name = 'kr cam' @@ -137,7 +136,7 @@ def get_photometry(ID=None,extra_fields=['dist','ra','dec'],**kwargs): >>> p = pylab.gca().set_xscale('log') >>> p = pylab.gca().set_yscale('log') >>> p = pylab.show() - + Other examples: >>> master = get_photometry(ra=71.239527,dec=-70.589427,to_units='erg/s/cm2/AA',extra_fields=[],radius=1.) >>> master = get_photometry(ID='J044458.39-703522.6',to_units='W/m2',extra_fields=[],radius=1.) @@ -151,11 +150,11 @@ def get_photometry(ID=None,extra_fields=['dist','ra','dec'],**kwargs): results,units,comms = search(source,**kwargs) if results is not None: master = gator2phot(source,results,units,master,extra_fields=extra_fields) - + #-- convert the measurement to a common unit. if to_units and master is not None: #-- prepare columns to extend to basic master - dtypes = [('cwave','f8'),('cmeas','f8'),('e_cmeas','f8'),('cunit','a50')] + dtypes = [('cwave','f8'),('cmeas','f8'),('e_cmeas','f8'),('cunit','U50')] cols = [[],[],[],[]] #-- forget about 'nan' errors for the moment no_errors = np.isnan(master['e_meas']) @@ -181,12 +180,12 @@ def get_photometry(ID=None,extra_fields=['dist','ra','dec'],**kwargs): #-- reset errors master['e_meas'][no_errors] = np.nan master['e_cmeas'][no_errors] = np.nan - + if master_ is not None and master is not None: master = numpy_ext.recarr_addrows(master_,master.tolist()) elif master_ is not None: master = master_ - + #-- and return the results return master @@ -199,7 +198,7 @@ def get_photometry(ID=None,extra_fields=['dist','ra','dec'],**kwargs): def txt2recarray(filename): """ Read a GATOR outfmt=1__ (ASCII) file into a record array. - + @param filename: name of the TSV file @type filename: str @return: catalog data columns, units, comments @@ -212,11 +211,11 @@ def txt2recarray(filename): while 1: # might call read several times for a file line = ff.readline() if not line: break # end of file - + #-- strip return character from line if line.isspace(): continue # empty line - + #-- remove return characters line = line.replace('\n','') #-- when reading a comment line @@ -245,7 +244,7 @@ def txt2recarray(filename): names = [head.strip() for head in comms[0].split('|')[1:-1]] formats = comms[1] formats = formats.replace('double','f8') - formats = formats.replace('char','a100') + formats = formats.replace('char','U100') formats = formats.replace('int','f8') formats = formats.replace('long','f8') formats = [head.strip() for head in formats.split('|')[1:-1]] @@ -261,7 +260,7 @@ def txt2recarray(filename): #-- fill empty or null values with nan col = [(row.isspace() or not row or row=='null') and 'nan' or row for row in col] cols.append(col) - + #-- define columns for record array and construct record array if len(data)==0: results = None @@ -276,29 +275,29 @@ def txt2recarray(filename): def gator2phot(source,results,units,master=None,extra_fields=['_r','_RAJ2000','_DEJ2000']): """ Convert/combine Gator record arrays to measurement record arrays. - + Every line in the combined array represents a measurement in a certain band. - + This is probably only useful if C{results} contains only information on one target (or you have to give 'ID' as an extra field, maybe). - + The standard columns are: - + 1. C{meas}: containing the photometric measurement 2. C{e_meas}: the error on the photometric measurement 3. C{flag}: an optional quality flag 4. C{unit}: the unit of the measurement 5. C{photband}: the photometric passband (FILTER.BAND) 6. C{source}: name of the source catalog - + You can add extra information from the Gator catalog via the list of keys C{extra_fields}. - + If you give a C{master}, the information will be added to a previous record array. If not, a new master will be created. - + The result is a record array with each row a measurement. - + @param source: name of the Gator source @type source: str @param results: results from Gator C{search} @@ -316,22 +315,22 @@ def gator2phot(source,results,units,master=None,extra_fields=['_r','_RAJ2000','_ e_flag = 'e_' q_flag = 'q_' #-- basic dtypes - dtypes = [('meas','f8'),('e_meas','f8'),('flag','a20'), - ('unit','a30'),('photband','a30'),('source','a50')] - + dtypes = [('meas','f8'),('e_meas','f8'),('flag','U20'), + ('unit','U30'),('photband','U30'),('source','U50')] + #-- extra can be added: names = list(results.dtype.names) translation = {'_r':'dist','_RAJ2000':'ra','_DEJ2000':'dec'} if extra_fields is not None: for e_dtype in extra_fields: dtypes.append((e_dtype,results.dtype[names.index(translation[e_dtype])].str)) - + #-- create empty master if not given newmaster = False if master is None or len(master)==0: master = np.rec.array([tuple([('f' in dt[1]) and np.nan or 'none' for dt in dtypes])],dtype=dtypes) newmaster = True - + #-- add fluxes and magnitudes to the record array cols_added = 0 for key in cat_info.options(source): @@ -356,8 +355,8 @@ def gator2phot(source,results,units,master=None,extra_fields=['_r','_RAJ2000','_ rows.append(tuple([col[i] for col in cols])) master = np.core.records.fromrecords(master.tolist()+rows,dtype=dtypes) cols_added += 1 - - #-- skip first line from building + + #-- skip first line from building if newmaster: master = master[1:] return master @@ -368,22 +367,22 @@ def gator2phot(source,results,units,master=None,extra_fields=['_r','_RAJ2000','_ def _get_URI(name,ID=None,ra=None,dec=None,radius=1.,filetype='1',spatial='cone',**kwargs): """ Build GATOR URI from available options. - + Filetype should be one of: - - 6___ returns a program interface in XML - 3___ returns a VO Table (XML) - 2___ returns SVC (Software handshaking structure) message - 1___ returns an ASCII table + + 6___ returns a program interface in XML + 3___ returns a VO Table (XML) + 2___ returns SVC (Software handshaking structure) message + 1___ returns an ASCII table 0___ returns Gator Status Page in HTML (default) - + kwargs are to catch unused arguments. - + @param name: name of a GATOR catalog (e.g. 'wise_prelim_p3as_psd') @type name: str @keyword ID: target name @type ID: str - @param filetype: type of the retrieved file + @param filetype: type of the retrieved file @type filetype: str (one of '0','1','2','3','6'... see GATOR site) @keyword radius: search radius (arcseconds) @type radius: float @@ -396,14 +395,14 @@ def _get_URI(name,ID=None,ra=None,dec=None,radius=1.,filetype='1',spatial='cone' @return: url @rtype: str """ - base_url = 'http://irsa.ipac.caltech.edu/cgi-bin/Gator/nph-query?' + base_url = 'https://irsa.ipac.caltech.edu/cgi-bin/Gator/nph-scan?' base_url += 'catalog=%s'%(name) #base_url += '&spatial=cone' - + #-- in GATOR, many things should be given via the keyword 'objstr': right # ascension, declination, target name... objstr = None - + #-- transform input to the 'objstr' paradigm if ID is not None: #-- if the ID is given in the form 'J??????+??????', derive the @@ -416,7 +415,7 @@ def _get_URI(name,ID=None,ra=None,dec=None,radius=1.,filetype='1',spatial='cone' objstr = ra+dec else: objstr = ID - + #-- this is when ra and dec are given if ra is not None and dec is not None: ra = str(ra) @@ -424,45 +423,45 @@ def _get_URI(name,ID=None,ra=None,dec=None,radius=1.,filetype='1',spatial='cone' ra = ra.replace(' ','+').replace(':','+') dec = dec.replace(' ','+').replace(':','+') objstr = '+'.join([ra,dec]) - + #-- build up the URI if 'objstr' is not None: base_url += '&objstr=%s'%(objstr) if 'filetype' is not None: base_url += '&outfmt=%s'%(filetype) if 'radius' is not None: base_url += '&radius=%s'%(radius) - + logger.debug(base_url) - + return base_url #} if __name__=="__main__": - import vizier + from . import vizier logger = loggers.get_basic_logger("") #-- example 1 #master = get_photometry(ra=71.239527,dec=-70.589427,to_units='erg/s/cm2/AA',extra_fields=[],radius=1.) #master = vizier.get_photometry(ra=71.239527,dec=-70.589427,to_units='erg/s/cm2/AA',extra_fields=[],radius=5.,master=master) - + #-- example 2 #master = get_photometry(ID='J044458.39-703522.6',to_units='W/m2',extra_fields=[],radius=1.) #master = vizier.get_photometry(ID='J044458.39-703522.6',to_units='W/m2',extra_fields=[],radius=5.,master=master) - - + + #-- example 3 #master = get_photometry(ID='HD43317',to_units='W/m2',extra_fields=[],radius=1.) #master = vizier.get_photometry(ID='HD43317',to_units='W/m2',extra_fields=[],radius=5.,master=master) - + #-- example 4 #master = get_photometry(ID='HD143454',to_units='erg/s/cm2/AA',extra_fields=[],radius=1.) #master = vizier.get_photometry(ID='HD143454',to_units='erg/s/cm2/AA',extra_fields=[],radius=30.,master=master) #-- example 5 master = get_photometry(ID='RR Aql',to_units='erg/s/cm2/AA',extra_fields=[],radius=1.) master = vizier.get_photometry(ID='RR Aql',to_units='erg/s/cm2/AA',extra_fields=[],radius=30.,master=master) - - - print master - + + + print(master) + from pylab import * figure() #loglog(master['cwave'],master['cmeas'],'ko') @@ -475,10 +474,10 @@ def _get_URI(name,ID=None,ra=None,dec=None,radius=1.,filetype='1',spatial='cone' #plot(np.log10(2315.66),np.log10(conversions.convert('muJy','W/m2',7106.731,photband='GALEX.NUV'))+13,'go') #ylim(-1.5,2.5) #xlim(3.1,4.7) - - + + show() - + sys.exit() import doctest doctest.testmod() @@ -491,7 +490,7 @@ def _get_URI(name,ID=None,ra=None,dec=None,radius=1.,filetype='1',spatial='cone' #name = 'kr cam' #master = vizier.get_photometry(name,to_units='erg/s/cm2/AA',extra_fields=[]) #master = get_photometry(name,to_units='erg/s/cm2/AA',extra_fields=[],master=master) - + #pylab.figure() #wise = np.array(['WISE' in photband and True or False for photband in master['photband']]) #pylab.errorbar(master['cwave'],master['cmeas'],yerr=master['e_cmeas'],fmt='ko') diff --git a/catalogs/gcpd.py b/catalogs/gcpd.py index e9f683547..d940a50f3 100644 --- a/catalogs/gcpd.py +++ b/catalogs/gcpd.py @@ -2,10 +2,11 @@ """ Interface to Geneva's Genaral Catalogue of Photometric Data """ -import urllib +import urllib.request, urllib.parse, urllib.error import logging import os -import ConfigParser +import configparser +from itertools import compress import numpy as np @@ -34,7 +35,7 @@ basedir = os.path.dirname(os.path.abspath(__file__)) #-- read in catalog information -cat_info = ConfigParser.ConfigParser() +cat_info = configparser.ConfigParser() cat_info.optionxform = str # make sure the options are case sensitive cat_info.readfp(open(os.path.join(basedir,'gcpd_cats_phot.cfg'))) @@ -44,19 +45,19 @@ def search(name,**kwargs): """ Search and retrieve information from the GCPD catalog. - + @param name: name of photometric system @type name: string """ base_url = _get_URI(name,**kwargs) - #-- the data is listed in two lines: one with the header, one with # the values - webpage = urllib.urlopen(base_url) + webpage = urllib.request.urlopen(base_url) entries,values = None,None log_message = '0' start = -1 for line in webpage: + line = line.decode('utf-8') line = line.replace('\n','') #-- change this marker to be much more general: now it only works for # geneva photometry @@ -73,7 +74,7 @@ def search(name,**kwargs): start += 1 logger.info('Querying GCPD for %s (%s)'%(name,log_message)) webpage.close() - + #-- assume that all entries are floats, and are values are magnitudes if entries is not None: if len(values) < len(entries): @@ -92,36 +93,36 @@ def search(name,**kwargs): results = np.rec.array(cols, dtype=dtypes) else: results,units,comms = None,None,None - + return results, units, None - - + + def gcpd2phot(source,results,units,master=None,e_flag='e_',q_flag='q_',extra_fields=None): """ Convert/combine GCPD record arrays to measurement record arrays. - + Every line in the combined array represents a measurement in a certain band. - + The standard columns are: - + 1. C{meas}: containing the photometric measurement 2. C{e_meas}: the error on the photometric measurement 3. C{flag}: an optional quality flag 4. C{unit}: the unit of the measurement 5. C{photband}: the photometric passband (FILTER.BAND) 6. C{source}: name of the source catalog - + If you give a C{master}, the information will be added to a previous record array. If not, a new master will be created. - + Colors will be expanded, derived from the other columns and added to the master. - + The result is a record array with each row a measurement. - + Extra fields are not available for the GCPD, they will be filled in with nans. - + @param source: name of the VizieR source @type source: str @param results: results from VizieR C{search} @@ -142,27 +143,27 @@ def gcpd2phot(source,results,units,master=None,e_flag='e_',q_flag='q_',extra_fie """ if cat_info.has_option(source,'e_flag'): e_flag = cat_info.get(source,'e_flag') - + #-- basic dtypes - dtypes = [('meas','f8'),('e_meas','f8'),('flag','a20'), - ('unit','a30'),('photband','a30'),('source','a50')] - + dtypes = [('meas','f8'),('e_meas','f8'),('flag','U20'), + ('unit','U30'),('photband','U30'),('source','U50')] + #-- extra can be added, but only if a master is already given!! The reason # is that thre GCPD actually does not contain any extra information, so # we will never be able to add it and will not know what dtype the extra - # columns should be + # columns should be #-- extra can be added: names = list(results.dtype.names) if extra_fields is not None: for e_dtype in extra_fields: dtypes.append((e_dtype,'f8')) - + #-- create empty master if not given newmaster = False if master is None or len(master)==0: master = np.rec.array([tuple([('f' in dt[1]) and np.nan or 'none' for dt in dtypes])],dtype=dtypes) newmaster = True - + #-- add fluxes and magnitudes to the record array cols_added = 0 for key in cat_info.options(source): @@ -187,7 +188,7 @@ def gcpd2phot(source,results,units,master=None,e_flag='e_',q_flag='q_',extra_fie rows.append(tuple([col[i] for col in cols])) master = np.core.records.fromrecords(master.tolist()+rows,dtype=dtypes) cols_added += 1 - + #-- fix colours: we have to run through it two times to be sure to have # all the colours N = len(master)-cols_added @@ -196,17 +197,17 @@ def gcpd2phot(source,results,units,master=None,e_flag='e_',q_flag='q_',extra_fie master_ = vizier._breakup_colours(master_) #-- combine and return master = np.core.records.fromrecords(master.tolist()[:N]+master_.tolist(),dtype=dtypes) - - #-- skip first line from building + + #-- skip first line from building if newmaster: master = master[1:] return master def get_photometry(ID=None,extra_fields=[],**kwargs): """ Download all available photometry from a star to a record array. - + Extra fields will not be useful probably. - + For extra kwargs, see L{_get_URI} and L{gcpd2phot} """ to_units = kwargs.pop('to_units','erg/s/cm2/AA') @@ -217,11 +218,11 @@ def get_photometry(ID=None,extra_fields=[],**kwargs): results,units,comms = search(source,ID=ID,**kwargs) if results is not None: master = gcpd2phot(source,results,units,master,extra_fields=extra_fields) - + #-- convert the measurement to a common unit. if to_units and master is not None: #-- prepare columns to extend to basic master - dtypes = [('cwave','f8'),('cmeas','f8'),('e_cmeas','f8'),('cunit','a50')] + dtypes = [('cwave','f8'),('cmeas','f8'),('e_cmeas','f8'),('cunit','U50')] cols = [[],[],[],[]] #-- forget about 'nan' errors for the moment no_errors = np.isnan(master['e_meas']) @@ -230,7 +231,7 @@ def get_photometry(ID=None,extra_fields=[],**kwargs): try: zp = filters.get_info(master['photband']) except: - print master['photband'] + print(master['photband']) raise for i in range(len(master)): to_units_ = to_units+'' @@ -259,7 +260,7 @@ def get_photometry(ID=None,extra_fields=[],**kwargs): #-- reset errors master['e_meas'][no_errors] = np.nan master['e_cmeas'][no_errors] = np.nan - + #-- if a master is given as a keyword, and data is found in this module, # append the two if master_ is not None and master is not None: @@ -275,42 +276,55 @@ def get_photometry(ID=None,extra_fields=[],**kwargs): def _get_URI(name='GENEVA',ID=None,**kwargs): """ Build GCPD URI from available options. - + kwargs are to catch unused arguments. - + @param name: photometric system name (E.g. JOHNSON, STROMGREN, GENEVA...) @type name: string (automatically uppercased) @keyword ID: star name (should be resolvable by SIMBAD) @type ID: string - @return: + @return: """ #-- GCPD is poor at recognising aliases: therefore we try different - # identifiers retrieved from Sesame that GCPD understands - recognized_alias = ['HD','BD',"CD"] - + # identifiers retrieved from Sesame that GCPD understands SA:SAO, HI: HIP/HIC + recognized_alias = {'HD':100,'SA':150,"HI":160,'PP':170} + ubv=False + # recognized_alias = ['HD','BD',"CD"] try: aliases = sesame.search(ID)['alias'] + aliases.sort(reverse=True) + #First check if there is uvby, that directly gives us the ID for GCPD for alias in aliases: - if alias[:2] in recognized_alias: - ID = alias[:2]+' '+alias[2:] + if alias.startswith('uvby98'): + ID = alias.split('uvby98')[1].strip() + ubv=True break - else: - logger.error('Star %s has no aliases recognised by GCPD: query will not return results'%(ID)) + #If not, reorder to look for IDs from HD -> SAO + if not ubv: + aliases.sort() + for alias in aliases: + if alias[:2] in recognized_alias.keys(): + # if alias[:2] in recognized_alias: + # ID = alias[:2]+' '+alias[2:] + ID = f'{recognized_alias[alias[2:]]}{alias[2:].strip().zfill(6)}' + break + else: + logger.error(f'Star {ID} has no aliases recognised by GCPD: query will not return results') except KeyError: - logger.error('Unknown star %s: GCPD query will not return results'%(ID)) - - - base_url = 'http://obswww.unige.ch/gcpd/cgi-bin/photoSys.cgi?phot=%02d&type=original&refer=with&mode=starno&ident=%s'%(systems[name],urllib.quote(ID)) + logger.error(f'Unknown star {ID}: GCPD query will not return results') + + + # base_url = 'http://obswww.unige.ch/gcpd/cgi-bin/photoSys.cgi?phot=%02d&type=original&refer=with&mode=starno&ident=%s'%(systems[name],urllib.parse.quote(ID)) + base_url = 'https://gcpd.physics.muni.cz/cgi-bin/photoSys.cgi?phot=%02d&type=original&refer=with&mode=starno&ident=%s'%(systems[name],urllib.parse.quote(ID)) logger.debug(base_url) return base_url - + #} if __name__=="__main__": results,units,comms = search('GENEVA',ID='HD180642') master = gcpd2phot('GENEVA',results,units) - print master - print "" + print(master) + print("") master = get_photometry(ID='vega') - print master - \ No newline at end of file + print(master) diff --git a/catalogs/gcpd_cats_phot.cfg b/catalogs/gcpd_cats_phot.cfg index 2450b5dac..ed4cb9ff6 100644 --- a/catalogs/gcpd_cats_phot.cfg +++ b/catalogs/gcpd_cats_phot.cfg @@ -93,4 +93,4 @@ bibcode = 1997A&AS..124..349M # C4245 = DDO.42-45 # C4142 = DDO.41-42 # C3841 = DDO.38-41 -# C3538 = DDO.35-38 \ No newline at end of file +# C3538 = DDO.35-38 diff --git a/catalogs/hermes.py b/catalogs/hermes.py index 48ea6548b..684656966 100644 --- a/catalogs/hermes.py +++ b/catalogs/hermes.py @@ -60,7 +60,7 @@ >>> for rv,fname in zip(myselection['bvcor'],myselection['filename']): ... wave,flux = fits.read_spectrum(fname) -... wave_shifted = model.doppler_shift(wave,rv) +... wave_shifted = doppler_shift(wave,rv) ... velo_shifted = conversions.convert('angstrom','km/s',wave_shifted,wave=(4512.3,'angstrom')) ... p = pl.plot(velo_shifted,flux) >>> p = pl.ylim(0,45000) @@ -75,7 +75,7 @@ they took, you can do the following: >>> data = search('HD50230') ->>> print [(i,list(data['observer']).count(i)) for i in set(data['observer'])] +>>> print ([(i,list(data['observer']).count(i)) for i in set(data['observer'])]) [('Robin Lombaert', 4), ('Steven Bloemen', 6), ('Pieter Degroote', 25), ('Michel Hillen', 3)] Section 3. Radial velocity computation with the HERMES DRS @@ -91,11 +91,11 @@ >>> logg = 4.0 >>> ll = linelists.get_lines(teff,logg) >>> mask_file = '%.0f_%.2f.fits'%(teff,logg) ->>> make_mask_file(ll['wavelength'],ll['depth'],filename=mask_file) +>>> # make_mask_file(ll['wavelength'],ll['depth'],filename=mask_file) The Hermes pipeline is Py2.7 And run the DRS: ->>> CCFList('HD170200',mask_file=mask_file) +>>> #CCFList('HD170200',mask_file=mask_file) Section 4. Hermes overview file @@ -139,7 +139,6 @@ can be copied to. You need write permission in this directory. The default is set to '/scratch/user/' """ -import re import os import sys import glob @@ -153,8 +152,8 @@ import astropy.io.fits as pf import copy -from lxml import etree -from xml.etree import ElementTree as ET +# from lxml import etree +# from xml.etree import ElementTree as ET from collections import defaultdict from ivs.catalogs import sesame @@ -176,7 +175,7 @@ #{ User functions def search(ID=None,time_range=None,prog_ID=None,data_type='cosmicsremoved_log', - radius=1.,filename=None): + radius=1.,filename=None,extension='cf'): """ Retrieve datafiles from the Hermes catalogue. @@ -194,10 +193,14 @@ def search(ID=None,time_range=None,prog_ID=None,data_type='cosmicsremoved_log', the program. Individual stars are not queried in SIMBAD, so any information that is missing in the header will not be corrected. + B{For the keyword C{extension}}: The retrieved filename will contain "_cf" (cosmic corrected and flatfield removed) by default, unless specified to return the "_c" (only cosmic corrected). + If you don't give either ID or time_range, the info on all data will be returned. This is a huge amount of data, so it can take a while before it is returned. Remember that the header of each spectrum is read in and checked. + NOTE: If the C{extension} is 'cf', then it is possible that all the observations are not retrieved, as the pipeline did not (or could not) produce the cf extensions for all '_c.fits' files. + Data type can be any of: 1. cosmicsremoved_log: return log merged without cosmics 2. cosmicsremoved_wavelength: return wavelength merged without cosmics @@ -258,15 +261,15 @@ def search(ID=None,time_range=None,prog_ID=None,data_type='cosmicsremoved_log', Search for all data of HD50230 taken in the night of 22 September 2009: - >>> data = hermes.search('HD50230',time_range='2009-9-22') + >>> data = search('HD50230',time_range='2009-9-22') Or within an interval of a few days: - >>> data = hermes.search('HD50230',time_range=('2009-9-23','2009-9-30')) + >>> data = search('HD50230',time_range=('2009-9-22','2009-9-30')) Search for all data observed in a given night: - >>> data = hermes.search(time_range='2009-9-22') + >>> data = search(time_range='2009-9-22') B{Warning:} the heliocentric correction is not calculated when no ID is given, so make sure it is present in the header if you need it, or calculate it yourself. @@ -331,7 +334,7 @@ def search(ID=None,time_range=None,prog_ID=None,data_type='cosmicsremoved_log', #-- now derive the location of the 'data_type' types from the raw # files if not data_type=='raw': - data['filename'] = [_derive_filelocation_from_raw(ff,data_type) for ff in data['filename']] + data['filename'] = [_derive_filelocation_from_raw(ff,data_type,extension) for ff in data['filename']] existing_files = np.array([ff!='naf' for ff in data['filename']],bool) data = data[existing_files] seqs = sorted(set(data['unseq'])) @@ -568,11 +571,11 @@ def read_hermesVR_velocities(unique=True, return_latest=True, unseq=None, object line[20] = " ".join(line[20:]) rawdata[i] = tuple(line[0:21]) - dtype = [('unseq','int'),('object','a20'),('hjd','f8'),('exptime','f8'),('bvcor','f8'), + dtype = [('unseq','int'),('object','U20'),('hjd','f8'),('exptime','f8'),('bvcor','f8'), ('rvdrift','f8'),('telloff','f8'),('tellofferr','f8'),('telloffwidth','f8'), ('vrad','f8'),('vraderr','f8'),('nlines','int'),('ccfdepth','f8'), ('ccfdeptherr','f8'), ('ccfsigma','f8'),('ccfsigmaerr','f8'),('sn','f8'), - ('gaussoc','f8'),('wvlfile','a80'),('maskfile','a80'),('fffile','a80')] + ('gaussoc','f8'),('wvlfile','U80'),('maskfile','U80'),('fffile','U80')] data = np.rec.array(rawdata, dtype=dtype) #-- select which lines to keep @@ -606,8 +609,8 @@ def read_hermesVR_AllCCF(unseq): an integer. In this case the file will be searched in the hermesDir/hermesAnalyses/ folder. You can also provide the complete filename instead as a string fx. - >>> read_hermesVR_AllCCF(457640) - >>> read_hermesVR_AllCCF('/home/jorisv/hermesAnalysis/00457640_AllCCF.fits') + >>> #read_hermesVR_AllCCF(457640) + >>> #read_hermesVR_AllCCF('/home/jorisv/hermesAnalysis/00457640_AllCCF.fits') @param unseq: unique sequence number or filename to read @type unseq: int or str @@ -642,9 +645,9 @@ def write_hermesConfig(**kwargs): example use: - >>> old = write_hermesConfig(CurrentNight="/STER/mercator/hermes/20101214") - >>> " Do some stuff " - >>> write_hermesConfig(**old) + >>> #old = write_hermesConfig(CurrentNight="/STER/mercator/hermes/20101214") + >>> #" Do some stuff " + >>> #write_hermesConfig(**old) @attention: This function does not check the validity of the keywords that you provide, that is your own responsibility. @@ -661,7 +664,7 @@ def write_hermesConfig(**kwargs): #-- read current config file to dictionary old_config = _etree_to_dict( ET.parse(config_file).getroot() )['hermes'] - if len(kwargs.keys()) == 0.0: + if len(list(kwargs.keys())) == 0.0: logger.debug('Nothing written to hermesConfig.xml, returning current config.') return old_config @@ -670,7 +673,7 @@ def write_hermesConfig(**kwargs): new_config.update(kwargs) hermes = etree.Element("hermes") - for key in new_config.keys(): + for key in list(new_config.keys()): child = etree.SubElement(hermes, key) child.text = new_config[key] @@ -703,7 +706,7 @@ def run_hermesVR(filename, mask_file=None, wvl_file=None, cosmic_clipping=True, '_HRF_TH_ext_wavelengthScale.fits' part of the filename. Thus if you want to use: '/folder/2451577_HRF_TH_ext_wavelengthScale.fits', use: - >>> run_hermesVR(filename, wvl_file = '/folder/2451577') + >>> #run_hermesVR(filename, wvl_file = '/folder/2451577') The results of hermesVR are saved to the standard paths, as provided in the hermesConfig file. This method only returns the unseq number of the filename @@ -820,9 +823,9 @@ def run_hermesVR(filename, mask_file=None, wvl_file=None, cosmic_clipping=True, returncode = -35 logger.debug(out) - if verbose: print out + if verbose: print(out) - except Exception, e: + except Exception as e: runError = e #-- restore hermesConfig and delete coppied files @@ -961,8 +964,8 @@ def combine_ccf(self, orders): range of order which will include the starting and ending order. Fx the following two commands will return the same summed ccf: - >>> combine_ccf([54,55,56,57,75,76]) - >>> combine_ccf('54:57,75,76') + >>> #combine_ccf([54,55,56,57,75,76]) + >>> #combine_ccf('54:57,75,76') @param orders: The hermes orders of which you want the summed ccf @type orders: list or string @@ -979,7 +982,7 @@ def combine_ccf(self, orders): for o in orders: if ':' in o: o = o.split(':') - o_list.extend( range( int(o[0]), int(o[1])+1 ) ) + o_list.extend( list(range( int(o[0]), int(o[1])+1)) ) else: o_list.append( int(o) ) logger.debug("converted order string: ''{:}'' to list: {:}".format(orders, o_list)) @@ -1030,14 +1033,17 @@ def __str__(self): #{ Administrator functions -def make_data_overview(): +def make_data_overview(directory='/STER/mercator/hermes/'): """ Summarize all Hermes data in a file for easy data retrieval. - The file is located in one of date data directories (see C{config.py}), in - subdirectories C{catalogs/hermes/HermesFullDataOverview.tsv}. If it doesn't - exist, it will be created. It contains the following columns, which are - extracted from the Hermes FITS headers (except C{filename}: + By default the function attempts to create/update the master tsv file, + which is only permitted for a handful of users (such as Mercator). If you + wish to create or update your own copy, please provide a directory for the + file to be written to i.e.{/path/to/dir/}. By default this function is + looped indefinitely every 24h, feel free to stop python while the code is + sleeping. The {HermesFullDataOverview.tsv} contains the following columns, + which are extracted from the Hermes FITS headers (except C{filename}: 1. UNSEQ 2. PROG_ID @@ -1055,97 +1061,107 @@ def make_data_overview(): 14. airmass 15. filename - This file can most easily be read with the L{ivs.inout.ascii} module and the - command: + This file can most easily be read with the L{ivs.inout.ascii} module and + the command: - >>> hermes_file = config.get_datafile(os.path.join('catalogs','hermes'),'HermesFullDataOverview.tsv') + >>> hermes_file = config.get_datafile(os.path.join('catalogs','hermes'), + 'HermesFullDataOverview.tsv') >>> data = ascii.read2recarray(hermes_file,splitchar='\\t') """ - logger.info('Collecting files...') - #-- all hermes data directories - dirs = sorted(glob.glob(os.path.join(config.ivs_dirs['hermes'],'20??????'))) - dirs = [idir for idir in dirs if os.path.isdir(idir)] - obj_files = [] - #-- collect in those directories the raw and relevant reduced files - for idir in dirs: - obj_files += sorted(glob.glob(os.path.join(idir,'raw','*.fits'))) - #obj_files += sorted(glob.glob(os.path.join(idir,'reduced','*OBJ*wavelength_merged.fits'))) - #obj_files += sorted(glob.glob(os.path.join(idir,'reduced','*OBJ*wavelength_merged_c.fits'))) - #obj_files += sorted(glob.glob(os.path.join(idir,'reduced','*OBJ*log_merged.fits'))) - #obj_files += sorted(glob.glob(os.path.join(idir,'reduced','*OBJ*log_merged_c.fits'))) - - #-- keep track of what is already in the file, if it exists: - try: - overview_file = config.get_datafile('catalogs/hermes','HermesFullDataOverview.tsv') - #overview_file = config.get_datafile(os.path.join('catalogs','hermes'),'HermesFullDataOverview.tsv') - overview_data = ascii.read2recarray(overview_file,splitchar='\t') - outfile = open(overview_file,'a') - logger.info('Found %d FITS files: appending to overview file %s'%(len(obj_files),overview_file)) - # if not, begin a new file - except IOError: - overview_file = 'HermesFullDataOverview.tsv' - outfile = open(overview_file,'w') - outfile.write('#unseq prog_id obsmode bvcor observer object ra dec bjd exptime pmtotal date-avg airmass filename\n') - outfile.write('#i i a20 >f8 a50 a50 >f8 >f8 >f8 >f8 >f8 a30 >f8 a200\n') - overview_data = {'filename':[]} - logger.info('Found %d FITS files: starting new overview file %s'%(len(obj_files),overview_file)) - - #-- and summarize the contents in a tab separated file (some columns contain spaces) - existing_files = np.sort(overview_data['filename']) - for i,obj_file in enumerate(obj_files): - sys.stdout.write(chr(27)+'[s') # save cursor - sys.stdout.write(chr(27)+'[2K') # remove line - sys.stdout.write('Scanning %5d / %5d FITS files'%(i+1,len(obj_files))) - sys.stdout.flush() # flush to screen - - #-- maybe this file is already processed: forget about it then - index = existing_files.searchsorted(obj_file) - if indexf8 a50 a50 >f8 >f8 >f8 >f8 >f8 a30 >f8 a200\n') + overview_data = {'filename':[]} + logger.info('Found %d FITS files: starting new overview file %s'%(len(obj_files),overview_file)) + + #-- and summarize the contents in a tab separated file (some columns contain spaces) + existing_files = np.sort(overview_data['filename']) + for i,obj_file in enumerate(obj_files): + sys.stdout.write(chr(27)+'[s') # save cursor + sys.stdout.write(chr(27)+'[2K') # remove line + sys.stdout.write('Scanning %5d / %5d FITS files'%(i+1,len(obj_files))) + sys.stdout.flush() # flush to screen + + #-- maybe this file is already processed: forget about it then + if len(existing_files): + index = existing_files.searchsorted(obj_file) + if index>> data,header = getHipData(1234) >>> data = data[data['q_mag'] <= 2] # keep only the good points - + To write the retrieved data to a file: - + >>> data, header = getHipData(1234 , "myfile.txt") - + To store the different columns in separate arrays: - + >>> data, header = getHipData(1234) >>> time = data['time'] >>> magnitude = data['mag'] >>> errorbar = data['e_mag'] >>> qualityflag = data['q_mag'] - + In the case of intermediate data products: - orbit: orbit number - source: source of abscissa (F=FAST, f=rejected FAST, N=NDAC,n=NDAC rejected) @@ -62,7 +58,7 @@ def getHipData(ID,dtype='ep',outputFileName=None): - d_pi: partial derivative wrt parallax - d_mua: partial derivative wrt proper motion alpha cos(delta) - d_mud: partial derivative wrt proper motion delta - + @param ID: identification of the star: if you give an integer or string that can be converted to an integer, it is assumed to be the hipparcos number of the star. E.g. 1234 or "1234". If it is not an integer, the star will @@ -73,8 +69,8 @@ def getHipData(ID,dtype='ep',outputFileName=None): @param outputFileName: the name of the file that will be created to save the Hipparcos time series @type outputFileName: string - @return: record array with fields time, mag, e_mag (errorbar), - q_mag (quality flag), and a dictionary containing the + @return: record array with fields time, mag, e_mag (errorbar), + q_mag (quality flag), and a dictionary containing the header information. The header dictionary is of style {'HH14': ('A', 'Annex flag (light curves)'), ...} @rtype: rec array, dict @@ -82,8 +78,8 @@ def getHipData(ID,dtype='ep',outputFileName=None): server = "www.rssd.esa.int" webpage = "/hipparcos_scripts/HIPcatalogueSearch.pl?hip%sId="%(dtype) - - # Resolve the name if necessary (i.e., if it's not a HIP number). If the + + # Resolve the name if necessary (i.e., if it's not a HIP number). If the # star has no HIP number, log an error and return None try: hipnr = int(ID) @@ -94,10 +90,10 @@ def getHipData(ID,dtype='ep',outputFileName=None): logger.error("Data retrieval for %s not possible. Reason: no HIP number resolved" % (ID)) return hipnr = IDs[0].split(' ')[1] - + # Connect to the website, en retrieve the wanted webpage - - conn = httplib.HTTPConnection(server) + + conn = http.client.HTTPConnection(server) conn.request("GET", webpage + str(hipnr)) response = conn.getresponse() if response.reason != "OK": @@ -105,42 +101,42 @@ def getHipData(ID,dtype='ep',outputFileName=None): return else: logger.info("Data retrieval for HIP%s: OK" % str(hipnr)) - + contents = response.read() conn.close() - + # Parse the webpage, to remove the html codes (line starts with <"). # Put a "#" in front of the header information, and format nicely. # Write to the output file if asked for. - + data = [] header = {} - + if outputFileName: outputFile = open(outputFileName,'w') - + for line in contents.split('\n'): if line == "": continue if not line.startswith("<"): line = line.replace("\r", "") - + # This is the header - + if not line[0].isdigit(): sline = line.split(':') - - # Only keep header entries of the style + + # Only keep header entries of the style # "key: value information" in the dictionary - + if len(sline)==2: key,info = sline info = info.split() header[key] = (info[0]," ".join(info[1:])) if outputFileName: line = "# " + line - + # This is the real contents - + else: data.append(line.split('|')) #-- correct for empty fields @@ -149,27 +145,27 @@ def getHipData(ID,dtype='ep',outputFileName=None): outputFile.write(line + "\n") if outputFileName: outputFile.close() - + # Make a record array. # Choose the header names to be in the VizieR style. - + if dtype=='ep': dtypes = [('time','f8'),('mag','f8'),('e_mag','f8'),('q_mag','i')] elif dtype=='i': - dtypes = [('orbit','i'),('source','a1'), + dtypes = [('orbit','i'),('source','U1'), ('d_acosd','f8'),('d_d','f8'),('d_pi','f8'), ('d_mua','f8'),('d_mud','f8'), ('abs_res','f8'),('abs_std','f8'),('cor','f8')] data = np.rec.array(data,dtype=dtypes) - + # Fix the time offset if dtype=='ep': data['time'] += 2440000.0 - - + + return data,header - - + + @@ -188,5 +184,5 @@ def getIntermediateData(ID,outputFileName=None): Convenience function to retrieve intermediate data. """ return getHipData(ID,dtype='i',outputFileName=outputFileName) - - + + diff --git a/catalogs/kepler.py b/catalogs/kepler.py index fa3e5482b..183635e60 100644 --- a/catalogs/kepler.py +++ b/catalogs/kepler.py @@ -1,7 +1,7 @@ """ Retrieve light curves from the Kepler satellite mission. """ -import urllib +import urllib.request, urllib.parse, urllib.error import os import astropy.io.fits as pf import logging @@ -17,7 +17,7 @@ def download_light_curve(KIC,directory=''): """ Download a light curve file from the data archive. - + @param KIC: kic number @type KIC: integer @param directory: directory to save data to, defaults to cwd @@ -37,7 +37,7 @@ def download_light_curve(KIC,directory=''): #-- and download the files logger.info("Found %d public/proprietary light curves for KIC%s"%(len(links),KIC)) for base_url in links: - url = urllib.URLopener() + url = urllib.request.URLopener() filename = os.path.basename(base_url) if directory: filename = os.path.join(directory,filename) @@ -49,15 +49,15 @@ def download_light_curve(KIC,directory=''): logger.info('... downloaded %s'%(filename)) filenames.append(filen) url.close() - + return filenames def get_data(KIC): """ Retrieve Kepler timeseries from a remote data repository. - + Fields are 'HJD','flux','e_flux','bkg','quarter'. - + @param KIC: kic number or list of filenames @type KIC: integer or list @return: data, header @@ -84,7 +84,7 @@ def get_data(KIC): np.hstack(background),np.hstack(quarter)], names=['HJD','flux','e_flux','bkg','quarter']) return data,header - + def systematics(units='muHz'): """ @@ -96,4 +96,4 @@ def systematics(units='muHz'): systems['e_frequency'] = conversions.nconvert(systems['unit'],units,systems['e_frequency']) systems['w_frequency'] = conversions.nconvert(systems['unit'],units,systems['w_frequency']) return systems - + diff --git a/catalogs/mast.py b/catalogs/mast.py index 45779d1bc..06f8560e4 100644 --- a/catalogs/mast.py +++ b/catalogs/mast.py @@ -6,21 +6,18 @@ use, and sometimes confusing. It is probably best to check the data or retrieve the data manually from the archive. """ -import urllib +import urllib.request, urllib.parse, urllib.error import socket import logging import os -import ConfigParser -import shutil +import configparser import numpy as np import astropy.io.fits as pf from ivs.sed import filters -from ivs.aux import xmlparser from ivs.aux import loggers from ivs.aux import numpy_ext -from ivs.aux.decorators import retry_http from ivs.inout import ascii from ivs.inout import http from ivs.units import conversions @@ -34,7 +31,7 @@ basedir = os.path.dirname(os.path.abspath(__file__)) #-- read in catalog information -cat_info = ConfigParser.ConfigParser() +cat_info = configparser.ConfigParser() cat_info.optionxform = str # make sure the options are case sensitive cat_info.readfp(open(os.path.join(basedir,'mast_cats_phot.cfg'))) @@ -44,19 +41,19 @@ def _get_URI(name,ID=None,ra=None,dec=None,radius=5.,filetype='CSV', out_max=100000,resolver='SIMBAD',coord='dec',**kwargs): """ Build MAST URI from available options. - + Filetype should be one of: - + CSV,SSV,PSV... - + kwargs are to catch unused arguments. - + @param name: name of a MAST mission catalog (e.g. 'hst') or search type ('images','spectra') @type name: str @keyword ID: target name @type ID: str - @param filetype: type of the retrieved file + @param filetype: type of the retrieved file @type filetype: str (one of 'CSV','PSV'... see MAST site) @keyword radius: search radius (arcseconds) @type radius: float @@ -72,7 +69,7 @@ def _get_URI(name,ID=None,ra=None,dec=None,radius=5.,filetype='CSV', @rtype: str """ base_url = 'http://archive.stsci.edu/' - + if name == 'images': base_url += 'siap/search2.php?' elif name == 'spectra': @@ -81,21 +78,21 @@ def _get_URI(name,ID=None,ra=None,dec=None,radius=5.,filetype='CSV', # base_url = 'http://galex.stsci.edu/search.php?action=Search' else: base_url += '%s/search.php?action=Search'%(name) - + #-- only use the ID if ra and dec are not given if ID is not None and (ra is None and dec is None): base_url += '&target=%s'%(ID) - + if ra is not None and dec is not None: base_url += '&radius=%s'%(radius/60.) if ra is not None: base_url += '&RA=%s'%(ra) if dec is not None: base_url += '&DEC=%s'%(dec) elif radius is not None: base_url += '&SIZE=%s'%(radius/60.) - + for kwarg in kwargs: base_url += '&%s=%s'%(kwarg,kwargs[kwarg]) - + if name not in ['spectra','images']: base_url += '&outputformat=%s'%(filetype) base_url += '&resolver=%s'%(resolver) @@ -121,15 +118,15 @@ def galex(**kwargs): #radius = radius/60. base_url = 'http://galex.stsci.edu/gxws/conesearch/conesearch.asmx/ConeSearchToXml?ra={0:f}&dec={1:f}&sr={2:f}&verb=1'.format(ra,dec,radius) #base_url = 'http://galex.stsci.edu/GR4/?page=searchresults&RA={ra:f}&DEC={dec:f}&query=no'.format(ra=ra,dec=dec) - url = urllib.URLopener() + url = urllib.request.URLopener() filen,msg = url.retrieve(base_url,filename=None) fuv_flux,e_fuv_flux = None,None columns = ['_r','ra','dec','fuv_flux','fuv_fluxerr','nuv_flux','nuv_fluxerr'] values = [np.nan,np.nan,np.nan,np.nan,np.nan,np.nan,np.nan] - + #flux in microJy units = dict(fuv_flux='muJy',nuv_flux='muJy') - + got_target = False with open(filen,'r') as ff: for line in ff.readlines(): @@ -137,40 +134,40 @@ def galex(**kwargs): if col+'>' in line: values[i] = np.float(line.split('>')[1].split('<')[0]) got_target = (col=='fuv_fluxerr') - if got_target: + if got_target: break - + values[0] = np.sqrt( (values[1]-ra)**2 + (values[2]-dec)**2)*3600 columns[1] = '_RAJ2000' columns[2] = '_DEJ2000' - + results = np.rec.fromarrays(np.array([values]).T,names=columns) if np.all(np.isnan(np.array(values))): results = None - + return results,units,None - + def search(catalog,**kwargs): """ Search and retrieve information from a MAST catalog. - + Two ways to search for data within a catalog C{name}: - + 1. You're looking for info on B{one target}, then give the target's C{ID} or coordinates (C{ra} and C{dec}), and a search C{radius}. - + 2. You're looking for information of B{a whole field}, then give the field's coordinates (C{ra} and C{dec}), and C{radius}. - + If you have a list of targets, you need to loop this function. - + If you supply a filename, the results will be saved to that path, and you will get the filename back as received from urllib.URLopener (should be the same as the input name, unless something went wrong). - + If you don't supply a filename, you should leave C{filetype} to the default C{tsv}, and the results will be saved to a temporary file and deleted after the function is finished. The content of the file @@ -179,8 +176,8 @@ def search(catalog,**kwargs): catalog). The entries in the dictionary are of type C{ndarray}, and will be converted to a float-array if possible. If not, the array will consist of strings. The comments are also returned as a list of strings. - - + + @param catalog: name of a MAST mission catalog @type catalog: str @keyword filename: name of the file to write the results to (no extension) @@ -194,21 +191,21 @@ def search(catalog,**kwargs): filetype = os.path.splitext(filename)[1][1:] elif filename is not None: filename = '%s.%s'%(filename,filetype) - + #-- gradually build URI base_url = _get_URI(catalog,**kwargs) #-- prepare to open URI - - url = urllib.URLopener() + + url = urllib.request.URLopener() filen,msg = url.retrieve(base_url,filename=filename) # maybe we are just interest in the file, not immediately in the content if filename is not None: logger.info('Querying MAST source %s and downloading to %s'%(catalog,filename)) url.close() return filen - + # otherwise, we read everything into a dictionary - + if filetype=='CSV' and not filename: try: results,units,comms = csv2recarray(filen) @@ -227,29 +224,29 @@ def search(catalog,**kwargs): def mast2phot(source,results,units,master=None,extra_fields=None): """ Convert/combine MAST record arrays to measurement record arrays. - + Every line in the combined array represents a measurement in a certain band. - + This is probably only useful if C{results} contains only information on one target (or you have to give 'ID' as an extra field, maybe). - + The standard columns are: - + 1. C{meas}: containing the photometric measurement 2. C{e_meas}: the error on the photometric measurement 3. C{flag}: an optional quality flag 4. C{unit}: the unit of the measurement 5. C{photband}: the photometric passband (FILTER.BAND) 6. C{source}: name of the source catalog - + You can add extra information from the Mast catalog via the list of keys C{extra_fields}. - + If you give a C{master}, the information will be added to a previous record array. If not, a new master will be created. - + The result is a record array with each row a measurement. - + @param source: name of the Mast source @type source: str @param results: results from Mast C{search} @@ -267,14 +264,14 @@ def mast2phot(source,results,units,master=None,extra_fields=None): e_flag = 'e_' q_flag = 'q_' #-- basic dtypes - dtypes = [('meas','f8'),('e_meas','f8'),('flag','a20'), - ('unit','a30'),('photband','a30'),('source','a50')] - + dtypes = [('meas','f8'),('e_meas','f8'),('flag','U20'), + ('unit','U30'),('photband','U30'),('source','U50')] + #-- MAST has no unified terminology: translations = {'kepler/kgmatch':{'_r':"Ang Sep (')", '_RAJ2000':'KIC RA (J2000)', '_DEJ2000':'KIC Dec (J2000)'}} - + #-- extra can be added: names = list(results.dtype.names) if extra_fields is not None: @@ -288,14 +285,14 @@ def mast2phot(source,results,units,master=None,extra_fields=None): except ValueError: if e_dtype != '_r': raise dtypes.append((e_dtype,results.dtype[names.index('AngSep')].str)) - - + + #-- create empty master if not given newmaster = False if master is None or len(master)==0: master = np.rec.array([tuple([('f' in dt[1]) and np.nan or 'none' for dt in dtypes])],dtype=dtypes) newmaster = True - + #-- add fluxes and magnitudes to the record array cols_added = 0 for key in cat_info.options(source): @@ -320,7 +317,7 @@ def mast2phot(source,results,units,master=None,extra_fields=None): rows.append(tuple([col[i] for col in cols])) master = np.core.records.fromrecords(master.tolist()+rows,dtype=dtypes) cols_added += 1 - + #-- fix colours: we have to run through it two times to be sure to have # all the colours N = len(master)-cols_added @@ -328,8 +325,8 @@ def mast2phot(source,results,units,master=None,extra_fields=None): master_ = vizier._breakup_colours(master_) #-- combine and return master = np.core.records.fromrecords(master.tolist()[:N]+master_.tolist(),dtype=dtypes) - - #-- skip first line from building + + #-- skip first line from building if newmaster: master = master[1:] return master @@ -338,7 +335,7 @@ def mast2phot(source,results,units,master=None,extra_fields=None): def csv2recarray(filename): """ Read a MAST csv (comma-sep) file into a record array. - + @param filename: name of the TCSV file @type filename: str @return: catalog data columns, units, comments @@ -357,7 +354,7 @@ def csv2recarray(filename): # the contents as a long string formats = np.zeros_like(data[0]) for i,fmt in enumerate(data[1]): - if 'string' in fmt or fmt=='datetime': formats[i] = 'a100' + if 'string' in fmt or fmt=='datetime': formats[i] = 'U100' if fmt=='integer': formats[i] = 'f8' if fmt=='ra': formats[i] = 'f8' if fmt=='dec': formats[i] = 'f8' @@ -375,7 +372,7 @@ def csv2recarray(filename): if cat_info.has_option(source,data[0,i]+'_unit'): units[key] = cat_info.get(source,data[0,i]+'_unit') break - else: + else: units[key] = 'nan' #-- define columns for record array and construct record array cols = [np.cast[dtypes[i]](cols[i]) for i in range(len(cols))] @@ -390,7 +387,7 @@ def csv2recarray(filename): def get_photometry(ID=None,extra_fields=['_r','_RAJ2000','_DEJ2000'],**kwargs): """ Download all available photometry from a star to a record array. - + For extra kwargs, see L{_get_URI} and L{mast2phot} """ to_units = kwargs.pop('to_units','erg/s/cm2/AA') @@ -402,14 +399,14 @@ def get_photometry(ID=None,extra_fields=['_r','_RAJ2000','_DEJ2000'],**kwargs): results,units,comms = galex(ID=ID,**kwargs) else: results,units,comms = search(source,ID=ID,**kwargs) - + if results is not None: master = mast2phot(source,results,units,master,extra_fields=extra_fields) - + #-- convert the measurement to a common unit. if to_units and master is not None: #-- prepare columns to extend to basic master - dtypes = [('cwave','f8'),('cmeas','f8'),('e_cmeas','f8'),('cunit','a50')] + dtypes = [('cwave','f8'),('cmeas','f8'),('e_cmeas','f8'),('cunit','U50')] cols = [[],[],[],[]] #-- forget about 'nan' errors for the moment no_errors = np.isnan(master['e_meas']) @@ -435,23 +432,23 @@ def get_photometry(ID=None,extra_fields=['_r','_RAJ2000','_DEJ2000'],**kwargs): #-- reset errors master['e_meas'][no_errors] = np.nan master['e_cmeas'][no_errors] = np.nan - + if master_ is not None and master is not None: master = numpy_ext.recarr_addrows(master_,master.tolist()) elif master_ is not None: master = master_ - + #-- and return the results - return master + return master #@retry_http(3) def get_dss_image(ID,ra=None,dec=None,width=5,height=5): """ Retrieve an image from DSS - + plot with - + >>> data,coords,size = mast.get_image('HD21389') >>> pl.imshow(data[::-1],extent=[coords[0]-size[0]/2,coords[0]+size[0]/2, coords[1]-size[1]/2,coords[1]+size[1]/2]) @@ -462,7 +459,7 @@ def get_dss_image(ID,ra=None,dec=None,width=5,height=5): if ra is None or dec is None: info = sesame.search(ID) ra,dec = info['jradeg'],info['jdedeg'] - url = urllib.URLopener() + url = urllib.request.URLopener() myurl = "http://archive.stsci.edu/cgi-bin/dss_search?ra=%s&dec=%s&equinox=J2000&height=%s&generation=%s&width=%s&format=FITS"%(ra,dec,height,'2i',width) out = url.retrieve(myurl) data1 = pf.getdata(out[0]) @@ -475,9 +472,9 @@ def get_dss_image(ID,ra=None,dec=None,width=5,height=5): def get_FUSE_spectra(ID=None,directory=None,cat_info=False,select=['ano']): """ Get Fuse spectrum. - + select can be ['ano','all'] - + # what about MDRS aperture? """ direc = (directory is None) and os.getcwd() or directory @@ -519,60 +516,60 @@ def get_FUSE_spectra(ID=None,directory=None,cat_info=False,select=['ano']): if __name__=="__main__": #get_FUSE_spectrum(ID='hd163296') - print get_FUSE_spectrum(ID='hd163296').dtype.names - + print(get_FUSE_spectrum(ID='hd163296').dtype.names) + raise SystemExit mission = 'fuse' base_url = _get_URI(mission,ID='hd163296') - print base_url - url = urllib.URLopener() + print(base_url) + url = urllib.request.URLopener() filen,msg = url.retrieve(base_url,filename='%s.test'%(mission)) url.close() raise SystemExit - - missions = ['hst',# - Hubble Space Telescope - 'kepler',# - Kepler Data search form - 'kepler/kepler_fov',# - Kepler Target search form - 'kepler/kic10',# - Kepler Input Catalog search form - 'kepler/kgmatch',# - Kepler/Galex Cross match - 'iue',# - International Ultraviolet Explorer - 'hut',# - Hopkins Ultraviolet Telescope - 'euve',# - Extreme Ultraviolet Explorer - 'fuse',# - Far-UV Spectroscopic Explorer - 'uit',# - Ultraviolet Imageing Telescope - 'wuppe',# - Wisconsin UV Photo-Polarimeter Explorer - 'befs',# - Berkeley Extreme and Far-UV Spectrometer - 'tues',# - Tübingen Echelle Spectrograph - 'imaps',# - Interstellar Medium Absorption Profile Spectrograph - 'hlsp',# - High Level Science Products - 'pointings',# - HST Image Data grouped by position - 'copernicus',# - Copernicus Satellite - 'hpol',# - ground based spetropolarimeter - 'vlafirst',# - VLA Faint Images of the Radio Sky (21-cm) + + missions = ['hst',# - Hubble Space Telescope + 'kepler',# - Kepler Data search form + 'kepler/kepler_fov',# - Kepler Target search form + 'kepler/kic10',# - Kepler Input Catalog search form + 'kepler/kgmatch',# - Kepler/Galex Cross match + 'iue',# - International Ultraviolet Explorer + 'hut',# - Hopkins Ultraviolet Telescope + 'euve',# - Extreme Ultraviolet Explorer + 'fuse',# - Far-UV Spectroscopic Explorer + 'uit',# - Ultraviolet Imageing Telescope + 'wuppe',# - Wisconsin UV Photo-Polarimeter Explorer + 'befs',# - Berkeley Extreme and Far-UV Spectrometer + 'tues',# - Tübingen Echelle Spectrograph + 'imaps',# - Interstellar Medium Absorption Profile Spectrograph + 'hlsp',# - High Level Science Products + 'pointings',# - HST Image Data grouped by position + 'copernicus',# - Copernicus Satellite + 'hpol',# - ground based spetropolarimeter + 'vlafirst',# - VLA Faint Images of the Radio Sky (21-cm) 'xmm-om']# - X-ray Multi-Mirror Telescope Optical Monitor - + #search('euve',ID='hd46149') #filename = search('kepler/kgmatch',ID='KIC3955868',filename='kepler.test') - + #results,units,comms = search('kepler/kgmatch',ID='T CrB') #sys.exit() out = search('kepler/kepler_fov',ID='TYC3134-165-1',filename='kepler_fov.test',radius=1.) out = search('kepler/kgmatch',ID='3749404',filename='kgmatch.test',radius=1.) #results,units,comms = search('kepler/kgmatch',ID='3749404') master = mast2phot('kepler/kgmatch',results,units,master=None,extra_fields=None) - print master + print(master) #data,units,comms = search('spectra',ID='hd46149',filename='ssap.test') sys.exit() - + for mission in missions: base_url = _get_URI(mission,ID='hd46149') #ff = urllib.urlopen(base_url) try: - url = urllib.URLopener() + url = urllib.request.URLopener() filen,msg = url.retrieve(base_url,filename='%s.test'%(mission)) url.close() except IOError: - print 'failed' + print('failed') continue - - + + diff --git a/catalogs/p7.py b/catalogs/p7.py index e50d63048..c98b82089 100644 --- a/catalogs/p7.py +++ b/catalogs/p7.py @@ -2,14 +2,12 @@ Retrieve data from the P7 photometer. """ import logging -import os import astropy.io.fits as pf import numpy as np from ivs.aux import loggers from ivs.catalogs import sesame -from ivs.inout import fits from ivs import config - + logger = logging.getLogger("") logger.addHandler(loggers.NullHandler) @@ -18,13 +16,13 @@ def getP7Data(ID=None,code=None,include_nans=True): """ Extract P7 timeseries from the catalog. - + WARNING: only B{very} few target ID's can be resolved (HD,HIP,SAO and that's about it) - + WARNING: there could be nan's in the data somewhere. If you don't want nan's anywhere, set 'include_nans' to False. - + @param ID: target ID (limited!) @type ID: str @param code: target's GENEVA code (e.g. 100180642 for HD180642) @@ -36,7 +34,7 @@ def getP7Data(ID=None,code=None,include_nans=True): if ID is not None: if not 'HD' in ID or not 'SAO' in ID or not 'HIC' in ID: info = sesame.search(ID) - print info + print(info) if 'alias' in info: for alias in info['alias']: if 'HD' in alias: @@ -50,7 +48,7 @@ def getP7Data(ID=None,code=None,include_nans=True): break # this should resolve the GENEVA name code = _geneva_name_resolver(ID=ID) - + catfile = config.get_datafile('catalogs/p7','p7photometry.fits') ff = pf.open(catfile) @@ -64,25 +62,25 @@ def getP7Data(ID=None,code=None,include_nans=True): V1 = ff[1].data.field('V1')[valid] G = ff[1].data.field('G')[valid] ff.close() - + data = np.rec.fromarrays([hjd,U,B,B1,B2,V,V1,G],names='HJD,U,B,B1,B2,V,V1,G') - + logger.info('Retrieved %d photometric points from P7'%(len(data))) - + if not include_nans: nans = np.isnan(data['HJD']) for name in data.dtype.names: nans = nans | np.isnan(data[name]) data = data[-nans] logger.info('Keeping %d photometric points without "NaN" from P7'%(len(data))) - + return data,{'source':'P7'} def _geneva_name_resolver(code=None,ID=None): """ Resolve the GENEVA object codes. - + @param ID: target ID (not implemented yet) @type ID: str @param code: target's GENEVA code (e.g. 100180642 for HD180642) @@ -100,7 +98,7 @@ def _geneva_name_resolver(code=None,ID=None): elif code[:3]=='-00': ID = 'BD%s%s %s'%(code[0],code[3:5],code[5:]) elif code[:3]=='+00': ID = 'BD%s%s %s'%(code[0],code[3:5],code[5:]) return ID - + elif ID is not None: if 'HD' in ID: code = '10%06d'%(int(ID.split('HD')[1])) elif 'SAO' in ID: code = '15%06d'%(int(ID.split('SAO')[1])) @@ -108,7 +106,7 @@ def _geneva_name_resolver(code=None,ID=None): elif 'PPM' in ID: code = '17%06d'%(int(ID.split('PPM')[1])) else: code = ID return int(code) - + diff --git a/catalogs/sesame.py b/catalogs/sesame.py index 7486308b8..da5a5a125 100644 --- a/catalogs/sesame.py +++ b/catalogs/sesame.py @@ -2,7 +2,9 @@ """ Interface to Sesame for general information on a star (SIMBAD) """ -import urllib +import urllib.request +import urllib.parse +import urllib.error import logging import numpy as np @@ -12,10 +14,11 @@ logger = logging.getLogger("CAT.SESAME") -def get_URI(ID,db='S'): + +def get_URI(ID, db='S'): """ Build Sesame URI from available options. - + @param ID: name of the star @type ID: str @keyword db: database (one of 'S','N', or 'A') @@ -23,32 +26,29 @@ def get_URI(ID,db='S'): @return: uri name @rtype: str """ - #mirrors: + # mirrors: # http://vizier.cfa.harvard.edu/viz-bin/nph-sesame/-oxpsIF/~%s?%s' - ID = urllib.quote(ID) - return 'http://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame/-oxpsIF/%s?%s'%(db,ID) - - - + ID = urllib.parse.quote(ID) + return ('http://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame/-oxpsIF/%s?%s' + % (db, ID)) - -def search(ID,db='S',fix=False): +def search(ID, db='S', fix=False): """ Query Simbad, NED and/or Vizier for information on an identifier. - + This retrieves basic information on a star, e.g. as shown in a typical Simbad page: coordinates, spectral type, fluxes, aliases, references... - + Database C{db} is one of 'S' (SIMBAD), 'N' (NED), 'V' (Vizier) or 'A' (all). - + This function returns a (sometimes nested) dictionary. Example output is given below, where nested dictionaries are shown with the separator '.' between the keys. - + If you set C{fix} to C{False}, following values will be updated: - + 1. the spectral type will be replaced by the one from the Skiff (2010) catalog if possible. 2. The parallax will be replaced with the value from the new Van Leeuwen @@ -56,21 +56,21 @@ def search(ID,db='S',fix=False): 3. The galactic coordinates will be added (converted from RA and DEC) 4. The proper motions will be taken from the PPMXL catalog from Roeser 2010. - + Example usage: - + >>> info = search('vega',db='S') - >>> print info['jpos'] + >>> print (info['jpos']) 18:36:56.33 +38:47:01.2 - >>> print info['jdedeg'] + >>> print (info['jdedeg']) 38.78369194 - >>> print info['alias'][1] + >>> print (info['alias'][1]) * alf Lyr - >>> print info['plx']['v'] + >>> print (info['plx']['v']) 128.93 - >>> print info['mag']['B']['v'] + >>> print (info['mag']['B']['v']) 0.03 - + This is an exhaustive list of example contents:: Vel.e = 0.9 Vel.q = A @@ -121,11 +121,11 @@ def search(ID,db='S',fix=False): refPos = [u'1997A', u'&', u'A...323L..49P'] spNum = 0.0000C800.0030.0000000000000000 spType = A0V - - + + >>> info = search('vega',db='N') >>> for key1 in sorted(info.keys()): - ... print '%s = %s'%(key1.ljust(8),info[key1]) + ... print ('%s = %s'%(key1.ljust(8),info[key1])) INFO = from cache alias = [u'alpha Lyr', u'HR 7001', u'HD 172167', u'IRAS 18352+3844', u'IRAS F18352+3844'] errDEmas = 4824.0 @@ -133,10 +133,10 @@ def search(ID,db='S',fix=False): jdedeg = 38.782316 jpos = 18:36:55.70 +38:46:56.3 jradeg = 279.2321017 - oname = VEGA + oname = VEGA otype = !* refPos = 1990IRASF.C...0000M - + @param ID: name of the source @type ID: str @param db: database to use @@ -144,49 +144,50 @@ def search(ID,db='S',fix=False): @return: (nested) dictionary containing information on star @rtype: dictionary """ - base_url = get_URI(ID,db=db) - ff = urllib.urlopen(base_url) - xmlpage = "" - for line in ff.readlines(): - line_ = line[::-1].strip(' ')[::-1] - if line_[0]=='<': - line = line_ - xmlpage+=line.strip('\n') - database = xmlparser.XMLParser(xmlpage).content - try: - database = database['Sesame']['Target']['%s'%(db)]['Resolver'] - database = database[database.keys()[0]] - except KeyError,IndexError: - #-- we found nothing! - database = {} - ff.close() - + base_url = get_URI(ID, db=db) + with urllib.request.urlopen(base_url) as ff: + xmlpage = "" + for line in ff.readlines(): + line = line.decode('utf-8') + line_ = line[::-1].strip(' ')[::-1] + if line_[0] == '<': + line = line_ + xmlpage += line.strip('\n') + database = xmlparser.XMLParser(xmlpage).content + try: + database = database['Sesame']['Target']['%s' % (db)]['Resolver'] + database = database[list(database.keys())[0]] + except KeyError as IndexError: + # -- we found nothing! + database = {} + if fix: - #-- fix the parallax: make sure we have the Van Leeuwen 2007 value. - # simbad seems to have changed to old values to the new ones somewhere - # in 2011. We check if this is the case for all stars: + # -- fix the parallax: make sure we have the Van Leeuwen 2007 value. + # simbad seems to have changed to old values to the new ones + # somewhere in 2011. We check if this is the case for all stars: if 'plx' in database and not ('2007' in database['plx']['r']): - data,units,comms = vizier.search('I/311/hip2',ID=ID) + data, units, comms = vizier.search('I/311/hip2', ID=ID) if data is not None and len(data): - if not 'plx' in database: + if 'plx' not in database: database['plx'] = {} database['plx']['v'] = data['Plx'][0] database['plx']['e'] = data['e_Plx'][0] database['plx']['r'] = 'I/311/hip2' - #-- fix the spectral type - data,units,comms = vizier.search('B/mk/mktypes',ID=ID) + # -- fix the spectral type + data, units, comms = vizier.search('B/mk/mktypes', ID=ID) if data is not None and len(data): database['spType'] = data['SpType'][0] if 'jpos' in database: - #-- add galactic coordinates (in degrees) - ra,dec = database['jpos'].split() - gal = conversions.convert('equatorial','galactic',(str(ra),str(dec)),epoch='2000') - gal = float(gal[0])/np.pi*180,float(gal[1])/np.pi*180 + # -- add galactic coordinates (in degrees) + ra, dec = database['jpos'].split() + gal = conversions.convert('equatorial', 'galactic', + (str(ra), str(dec)), epoch='2000') + gal = float(gal[0])/np.pi*180, float(gal[1])/np.pi*180 database['galpos'] = gal - #-- fix the proper motions - data,units,comms = vizier.search('I/317/sample',ID=ID) + # -- fix the proper motions + data, units, comms = vizier.search('I/317/sample', ID=ID) if data is not None and len(data): - if not 'pm' in database: + if'pm' not in database: database['pm'] = {} database['pm']['pmRA'] = data['pmRA'][0] database['pm']['pmDE'] = data['pmDE'][0] @@ -194,7 +195,8 @@ def search(ID,db='S',fix=False): database['pm']['epmDE'] = data['e_pmDE'][0] database['pm']['r'] = 'I/317/sample' return database - -if __name__=="__main__": + + +if __name__ == "__main__": import doctest - doctest.testmod() \ No newline at end of file + doctest.testmod() diff --git a/catalogs/testHermes.py b/catalogs/testHermes.py index 24fe3ea38..160305c7e 100644 --- a/catalogs/testHermes.py +++ b/catalogs/testHermes.py @@ -3,14 +3,12 @@ import shutil import getpass import numpy as np -import pylab as pl from ivs.catalogs import hermes -from ivs.inout import fits import unittest +import imp try: import mock - from mock import patch noMock = False except Exception: noMock = True @@ -18,61 +16,61 @@ class HermesTestCase(unittest.TestCase): """Add some extra usefull assertion methods to the testcase class""" - + test_data_dir = '/home/jorisv/IVS_python/ivs_test_data/' testdirectories = [] - + def assertNoCosmic(self, wave, flux, wrange, sigma=3, msg=None): flux = flux[(wave>=wrange[0]-0.5) & (wave<=wrange[1]+0.5)] wave = wave[(wave>=wrange[0]-0.5) & (wave<=wrange[1]+0.5)] avg = np.median(flux) std = np.std(flux) - + msg_ = "Cosmic detected in %s <-> %s"%(wrange[0], wrange[1]) if msg != None: msg_ = msg_ + ", " + str(msg) self.assertTrue(np.all(flux < avg + sigma * std), msg=msg_) - + def assertAbsorptionLine(self, wave, flux, line, cont, depth=0.75, msg=None): lf = np.min(flux[(wave>=line[0]) & (wave<=line[1])]) cf = np.average(flux[(wave>=cont[0]) & (wave<=cont[1])]) - + msg_ = "Absorption line missing in %s <-> %s"%(line[0], line[1]) if msg != None: msg_ = msg_ + ", " + str(msg) self.assertTrue( lf <= cf * depth, msg=msg_ ) - + def assertArrayEqual(self, l1, l2, msg=None): for i, (f1, f2) in enumerate(zip(l1,l2)): msg_ = "Array not equal on: %i, %s != %s"%(i, str(f1), str(f2)) - if msg != None: msg_ = msg_ + ", " + msg + if msg != None: msg_ = msg_ + ", " + msg self.assertEqual(f1,f2, msg=msg_) - + def assertArrayAlmostEqual(self, l1,l2,places=None,delta=None, msg=None): for i, (f1, f2) in enumerate(zip(l1, l2)): msg_ = "Array not equal on: %i, %s != %s"%(i, str(f1), str(f2)) - if msg != None: msg_ = msg_ + ", " + msg + if msg != None: msg_ = msg_ + ", " + msg self.assertAlmostEqual(f1,f2,places=places, delta=delta, msg=msg_) - + @nose.tools.nottest def make_testDir(self): - + testdir = '/home/{:}/{:.0f}/'.format(getpass.getuser(), np.random.uniform()*10**6) while os.path.exists(testdir): testdir = '/home/{:}/{:.0f}/'.format(getpass.getuser(), np.random.uniform()*10**6) - + os.makedirs(testdir) - + self.testdirectories.append(testdir) - + return testdir - + @nose.tools.nottest def delete_testDir(self): - + for dirname in self.testdirectories: os.rmdir(dirname) - + class TestCase1MergeHermesSpectra(HermesTestCase): - + def testOrderMerged(self): """Merge order-merged hermes spectra NO vrad (Feige 66)""" temp = '/STER/mercator/hermes/%s/reduced/%s_HRF_OBJ_ext_CosmicsRemoved_log_merged_c.fits' @@ -93,19 +91,19 @@ def testOrderMerged(self): ('20130106','00445346'), ('20130215','00452556'), ('20130406','00457718'), ('20130530','00474128')] mergeList = [temp%o for o in objlist] - + #wtotal, f = fits.read_spectrum(mergeList[0]) #ftotal = np.zeros_like(f) #for ifile in mergeList: #w, f = fits.read_spectrum(ifile) - #ftotal += np.interp(wtotal,w,f) + #ftotal += np.interp(wtotal,w,f) #pl.plot(wtotal, ftotal) - + wave, flux, header = hermes.merge_hermes_spectra(mergeList, sigma=3, offset=None, base='average') #pl.plot(wave, flux) #pl.show() - + #-- Test that cosmics are removed self.assertNoCosmic(wave, flux, (4362.7, 4362.9)) self.assertNoCosmic(wave, flux, (4974.2, 4974.4)) @@ -116,22 +114,22 @@ def testOrderMerged(self): self.assertNoCosmic(wave, flux, (6546.9, 6547.1)) self.assertNoCosmic(wave, flux, (7996.8, 7997.0)) self.assertNoCosmic(wave, flux, (8001.7, 8001.9)) - - + + #-- Test that absorption lines are still there self.assertAbsorptionLine(wave, flux, (3933.4, 3933.7), (3934.1, 3934.9), depth=0.6) self.assertAbsorptionLine(wave, flux, (5159.8, 5160.3), (5157.8, 5159.4), depth=0.8) self.assertAbsorptionLine(wave, flux, (5874.9, 5876.2), (5870.7, 5872.9), depth=0.6) self.assertAbsorptionLine(wave, flux, (6001.7, 6002.0), (6000., 6001.45), depth=0.95) - - + + def testOrderMergedVrad(self): """Merge order-merged hermes spectra with vrad (KIC 9540226)""" - + pre = '/STER/mercator/hermes/' post_merged = '_HRF_OBJ_ext_CosmicsRemoved_log_merged_c.fits' # - + objlist = ['20120730/reduced/00415609', '20120808/reduced/00416266', '20120808/reduced/00416267', @@ -143,32 +141,32 @@ def testOrderMergedVrad(self): '20120913/reduced/00419914', '20120924/reduced/00421148'] mergeList = [pre+o+post_merged for o in objlist] - + vrad = [-21.190, -17.473, -17.528, -11.621, -11.613, 5.974, 13.297, 15.793, 15.810, 18.739] - + #wtotal, f = fits.read_spectrum(mergeList[0]) #ftotal = np.zeros_like(f) #for ifile in mergeList: #w, f = fits.read_spectrum(ifile) - #ftotal += np.interp(wtotal,w,f) + #ftotal += np.interp(wtotal,w,f) #pl.plot(wtotal, ftotal) - + wave, flux, header = hermes.merge_hermes_spectra(mergeList, vrads=vrad, sigma=1.5, offset='none', base='average', runs=3) #pl.plot(wave, flux) #pl.show() - + #-- Test that cosmics are removed self.assertNoCosmic(wave, flux, (4060.1, 4060.3)) self.assertNoCosmic(wave, flux, (4212.3, 4212.4)) self.assertNoCosmic(wave, flux, (4320.0, 4320.2), sigma=2) self.assertNoCosmic(wave, flux, (7056.5, 7056.7)) self.assertNoCosmic(wave, flux, (8344.1, 8344.8)) - + #-- Test that absorption lines are still there self.assertAbsorptionLine(wave, flux, (5892.5, 5893.0), (5893.4, 5894.8), depth=0.6) self.assertAbsorptionLine(wave, flux, (7326.0, 7326.3), (7324.8, 7325.8), depth=0.5) - + #-- Test that the shift is correct f1 = flux[(wave>5883.71) & (wave<5883.94)] f2 = flux[(wave>5883.32) & (wave<5883.68)] @@ -176,14 +174,14 @@ def testOrderMergedVrad(self): f1 = flux[(wave>5914.00) & (wave<5914.30)] f2 = flux[(wave>5913.69) & (wave<5914.00)] self.assertTrue(np.min(f1) < np.min(f2), msg='Spectra NOT correctly shifted') - - + + #def testExt(self): #"""Merge original 51-orders hermes spectra""" #post_ext = '_HRF_OBJ_ext_CosmicsRemoved.fits' - + class TestCase2SetHermesConfig(HermesTestCase): - + @classmethod def setUpClass(cls): #setup a directory for the test file if nessessary @@ -193,10 +191,10 @@ def setUpClass(cls): os.makedirs(testdir) else: cls.dirExisted = True - + cls.testdir = testdir cls.testfile = testdir+'hermesConfig.xml' - + def setUp(self): # create test hermesConfig.xml file config_file = "\n"\ @@ -211,7 +209,7 @@ def setUp(self): testfile = open(self.testfile, 'w') testfile.write(config_file) testfile.close() - + config = dict(Nights = '/STER/mercator/hermes', CurrentNight = '/STER/mercator/hermes/20110501', Reduced = '/STER/mercator/hermes/20110501/reduced', @@ -219,62 +217,62 @@ def setUp(self): DebugPath = '/home/jorisv/hermesDebug', LogFileName = '/home/jorisv/hermesDebug/hermes.log', LogFileFormater = r"%%(asctime)s :: %%(levelname)s :: %%(message)s") - + self.config = config - + def test1no_arguments(self): """catalogs.hermes write_hermesConfig no arguments""" - + old = hermes.write_hermesConfig(filename=self.testfile) - + msg = 'write_hermesConfig did not return the correct content of hermesConfig.xml' - for key in old.keys(): + for key in list(old.keys()): self.assertEqual(old[key], self.config[key], msg=msg) - - + + def test2correct_update(self): """catalogs.hermes write_hermesConfig update hermesConfig""" new_config = {'Nights' : '/STER/jorisv/hermesAnalysis', 'CurrentNight' : '/STER/jorisv/hermesAnalysis/BD+34.1543'} - + old = hermes.write_hermesConfig(filename=self.testfile, **new_config) new = hermes.write_hermesConfig(filename=self.testfile) - + msg = 'The new hermesConfig is not correctly written!' self.config.update(new_config) - for key in new.keys(): - self.assertEqual(new[key], self.config[key], msg=msg) - - + for key in list(new.keys()): + self.assertEqual(new[key], self.config[key], msg=msg) + + def test3restore_old_config(self): """catalogs.hermes write_hermesConfig restore hermesConfig""" new_config = {'Nights' : '/STER/jorisv/hermesAnalysis', 'CurrentNight' : '/STER/jorisv/hermesAnalysis/BD+34.1543'} - + old = hermes.write_hermesConfig(filename=self.testfile, **new_config) new = hermes.write_hermesConfig(filename=self.testfile, **old) restored = hermes.write_hermesConfig(filename=self.testfile) - + msg = 'The new hermesConfig is not correctly restored!' - for key in new.keys(): - self.assertEqual(restored[key], self.config[key], msg=msg) - - + for key in list(new.keys()): + self.assertEqual(restored[key], self.config[key], msg=msg) + + @classmethod def tearDownClass(cls): #remove the test hermesConfig file os.remove(cls.testfile) - + #remove the test directory if nessessary if not cls.dirExisted: try: os.rmdir(cls.testdir) except Exception: - print 'Error in removing directory' + print('Error in removing directory') class TestCase3ReadHermesVRVelocities(HermesTestCase): - + @classmethod def setUpClass(cls): #setup a directory for the test file if nessessary @@ -284,67 +282,67 @@ def setUpClass(cls): os.makedirs(testdir) else: cls.dirExisted = True - + cls.testdir = testdir cls.testfile = testdir + 'velocities.data' - + shutil.copy(cls.test_data_dir + 'catalogs_hermes_velocities.data', cls.testfile) - + def setUp(self): # create test velocities.data file - + self.unseq = [2455351,2455425,2455620,2455639,2455649,2455665,2455937,2455351, 2455425,2455620,2455617,2455649,2455659] self.object = ['BD+29.3070', 'EC11031-1348', 'J1736+2806'] - + def test1read_everything(self): """catalogs.hermes read_hermesVR_velocities basic read""" - + data = hermes.read_hermesVR_velocities(filename=self.testfile, unique=False) - + msg = 'Not all data read from file, expected 13 lines, got {:}'.format(len(data)) self.assertEqual(len(data), 13, msg=msg) msg = 'Not all expected unseq are present in data, expected:' \ +'\n{:}\ngot:\n{:}'.format(set(self.unseq), set(data['unseq'])) - for sq in self.unseq: + for sq in self.unseq: self.assertTrue(sq in data['unseq'], msg=msg) - + msg = 'Not all expected objects are present, expected:' \ +'\n{:}\ngot:\n{:}'.format(self.object, set(data['object'])) for obj in self.object: self.assertTrue(obj in data['object'], msg=msg) - + def test2unique(self): """catalogs.hermes read_hermesVR_velocities unique output""" - + # return the earliest added lines data = hermes.read_hermesVR_velocities(filename=self.testfile, unique=True, return_latest=False) - + msg = 'Amount of data not correct, expected 9 lines, got {:}'.format(len(data)) self.assertEqual(len(data), 9, msg=msg) - + msg = 'All returned sequence numbers should be unique!, got: {:}'.format(data['unseq']) for sq in set(self.unseq): self.assertTrue(len(np.where(data['unseq'] == sq)) == 1, msg=msg) - + msg = 'When return_latest == False, Should return the first added lines, got the lastest!' self.assertEqual(data['vrad'][data['unseq'] == 2455351][0], -64.6618, msg=msg) - + # return the lastest added lines msg = 'When return_latest == True, Should return the latest added lines, got the first!' data = hermes.read_hermesVR_velocities(filename=self.testfile, unique=True, return_latest=True) self.assertEqual(data['vrad'][data['unseq'] == 2455351][0], -66.5509, msg=msg) - - + + def test3list_unseq(self): """catalogs.hermes read_hermesVR_velocities list unseq""" - + unseq = [2455351, 2455425, 2455620] - + # return all data = hermes.read_hermesVR_velocities(filename=self.testfile, unique=False, unseq=unseq) @@ -354,7 +352,7 @@ def test3list_unseq(self): for sq in unseq: self.assertTrue(sq in data['unseq'], msg=msg) self.assertEqual(len(np.where(data['unseq']==sq)[0]), 2, msg=msg) - + # return unique data = hermes.read_hermesVR_velocities(filename=self.testfile, unique=True, unseq=unseq) @@ -364,7 +362,7 @@ def test3list_unseq(self): for sq in unseq: self.assertTrue(sq in data['unseq'], msg=msg) self.assertEqual(len(np.where(data['unseq']==sq)[0]), 1, msg=msg) - + # check returned order unseq = [2455937, 2455620, 2455425] data = hermes.read_hermesVR_velocities(filename=self.testfile, unique=True, @@ -373,13 +371,13 @@ def test3list_unseq(self): +'\nGot:\t\t{:}\nExpected:\t{:}'.format(data['unseq'], unseq) for i, sq in enumerate(unseq): self.assertEqual(data['unseq'][i], sq, msg=msg) - + def test4list_object(self): """catalogs.hermes read_hermesVR_velocities list object""" - + object = ['J1736+2806', 'EC11031-1348'] unseq = [2455617, 2455649, 2455659, 2455665, 2455937] - + # return all objects data = hermes.read_hermesVR_velocities(filename=self.testfile, unique=False, object=object) @@ -389,39 +387,39 @@ def test4list_object(self): for ob in object: self.assertTrue(ob in data['object'], msg=msg) self.assertFalse('BD+29.3070' in data['object'], msg=msg) - + # return all lines belonging to those objects msg = 'Should return 5 lines in total\nGot:\t\t{:}\nExpected:\t{:}'.format( \ set(data['unseq']), unseq) self.assertEqual(len(data['unseq']), 5, msg=msg) for sq in unseq: self.assertTrue(sq in data['unseq'], msg=msg) - + # return in the correct order object = ['J1736+2806','J1736+2806','J1736+2806','EC11031-1348','EC11031-1348'] msg = 'Should return the objects in the correct order' \ +'\nGot:\t\t{:}\nExpected:\t{:}'.format(data['object'], object) for i, ob in enumerate(object): self.assertEqual(data['object'][i], ob, msg=msg) - + @classmethod def tearDownClass(cls): #remove the test hermesConfig file os.remove(cls.testfile) - + #remove the test directory if nessessary if not cls.dirExisted: try: os.rmdir(cls.testdir) except Exception: - print 'Error in removing directory' - - + print('Error in removing directory') + + class TestCase4ReadHermesVRAllCCF(HermesTestCase): """ Tests regarding the function read_hermesVR_AllCCF of catalogs.hermes """ - + @classmethod def setUpClass(cls): #setup a directory for the test file if nessessary @@ -431,142 +429,142 @@ def setUpClass(cls): os.makedirs(testdir) else: cls.dirExisted = True - + #create fake hermesAnalyses directory if not os.path.exists(testdir + 'hermesAnalyses/'): os.makedirs(testdir + 'hermesAnalyses/') - + #copy the allccf file shutil.copy(cls.test_data_dir + 'catalogs_hermes_00457640_AllCCF.fits', testdir + 'hermesAnalyses/00457640_AllCCF.fits') - + cls.testdir = testdir cls.hermestestdir = testdir + 'hermesAnalyses/' cls.testfile = testdir + 'hermesAnalyses/00457640_AllCCF.fits' cls.unseq = 457640 - - + + @unittest.skipIf(noMock, "Mock not installed") def test1read_filename(self): """catalogs.hermes read_hermesVR_AllCCF read using filename""" - - hermes.HermesCCF.__init__ =mock.Mock(return_value=None) - + + hermes.HermesCCF.__init__ =mock.Mock(return_value=None) + ccf = hermes.read_hermesVR_AllCCF(unseq = self.testfile) ccf.__init__.assert_called_with(filename=self.testfile) - - + + @unittest.skipIf(noMock, "Mock not installed") def test2read_unseq(self): """catalogs.hermes read_hermesVR_AllCCF read using unseq""" - + hermes.HermesCCF.__init__ =mock.Mock(return_value=None) hermes.hermesDir = self.testdir - + ccf = hermes.read_hermesVR_AllCCF(unseq = self.unseq) ccf.__init__.assert_called_with(filename=self.testfile) - + @classmethod def tearDownClass(cls): #remove the test hermesConfig file os.remove(cls.testfile) - + #remove the hermes testdir os.rmdir(cls.hermestestdir) - + #remove the test directory if nessessary if not cls.dirExisted: try: os.rmdir(cls.testdir) except Exception: - print 'Error in removing directory' - - reload(hermes) - + print('Error in removing directory') + + imp.reload(hermes) + class TestCase5HermesCCF(HermesTestCase): """ Tests regarding the class HermesCCF in catalogs.hermes """ - + @classmethod def setUpClass(cls): #setup a directory for the test file if nessessary cls.testfile = cls.test_data_dir + 'catalogs_hermes_00457640_AllCCF.fits' cls.unseq = 457640 - + cls.ccfobj = hermes.HermesCCF(filename=cls.testfile) - - print cls.ccfobj - + + print(cls.ccfobj) + def test1init(self): """catalogs.hermes HermesCCF init""" - + ccf = self.ccfobj - + msg = 'Filename not correctly stored' \ +"\nGot:\t\t{:}\nExpected:\t{:}".format( ccf.filename, self.testfile) self.assertEqual(ccf.filename, self.testfile) - + msg = "ccf object doesn't have all attributes" self.assertTrue(hasattr(ccf, 'vr'), msg=msg) self.assertTrue(hasattr(ccf, 'ccfs'), msg=msg) self.assertTrue(hasattr(ccf, 'groups'), msg=msg) - + msg = 'Header is not read correctly' self.assertEqual(ccf.header['object'], 'BD+42.3250', msg=msg) self.assertEqual(ccf.header['unseq'], 457640, msg=msg) - + msg = "VR list is not read correctly, got: len={:}, min={:}, max={:}".format(len(ccf.vr), ccf.vr[0], ccf.vr[-1]) self.assertEqual(len(ccf.vr), 500, msg=msg) self.assertAlmostEqual(ccf.vr[0], -88.440071, places=3, msg=msg) self.assertAlmostEqual(ccf.vr[-1], 52.12719, places=3, msg=msg) - + msg = "Groups not correctly read, got:\n{:}".format(ccf.groups) names = ccf.groups['NAMES'] self.assertEqual(names[0], '84-94 (viol)', msg=msg) self.assertEqual(names[3], '54-63 ', msg=msg) - - + + def test2ccf(self): """catalogs.hermes HermesCCF ccf()""" - + ccf = self.ccfobj - + # valid order msg = "ccf(80) should return valid ccf function" exp = [548.75676331, 567.31354311, 592.54494387, 589.58018404] got = ccf.ccf(80)[([10, 182, 362, 475],)] self.assertArrayAlmostEqual(exp, got, msg=msg, places=5) - + # invalid order msg = "invalid orders (<40 or >94) should raise IndexError" self.assertRaises(IndexError, ccf.ccf, 39) self.assertRaises(IndexError, ccf.ccf, 95) - - + + def test3combine_ccf(self): """catalogs.hermes HermesCCF combine_ccf()""" - + ccf = self.ccfobj - + # valid orders list msg = "combine_ccf([75,76,77,80,81,94]) should return valid ccf function" exp = [1811.14160156, 1871.66992188, 1902.21154785, 1985.3449707] got = ccf.combine_ccf([75,76,77,80,81,94])[([10, 182, 362, 475],)] self.assertArrayAlmostEqual(exp, got, msg=msg, places=5) - + # valid orders string msg = "combine_ccf('75:77,80,81,94') should return valid ccf function" exp = [1811.14160156, 1871.66992188, 1902.21154785, 1985.3449707] got = ccf.combine_ccf('75:77,80,81,94')[([10, 182, 362, 475],)] self.assertArrayAlmostEqual(exp, got, msg=msg, places=5) - + # invalid order list msg = "invalid orders (<40 or >94) should raise IndexError" self.assertRaises(IndexError, ccf.combine_ccf, [39,45,75,76]) self.assertRaises(IndexError, ccf.combine_ccf, [45,75,76,95]) - + # invalid order string msg = "invalid orders (<40 or >94) should raise IndexError" self.assertRaises(IndexError, ccf.combine_ccf, '39:45,75,76') @@ -576,120 +574,120 @@ class TestCase6RunHermesVR(HermesTestCase): """ Integration Tests regarding run_hermesVR in catalogs.hermes """ - + @classmethod def setUpClass(self): """ Mock all methods used by run_hermesVR """ self.testfile = '/home/jorisv/IVS_python/ivs_test_data/catalogs_hermes_spectrum.fits' self.wvl_file = '/STER/mercator/hermes/20091130/reduced/00262831' - + hermes._subprocess_execute = mock.Mock(return_value=('', None, 0)) hermes.write_hermesConfig = mock.Mock(return_value=dict(AnalysesResults='/home/')) os.remove = mock.Mock(return_value=None) os.mkdir = mock.Mock(return_value=None) os.rmdir = mock.Mock(return_value=None) shutil.copyfile = mock.Mock(return_value=None) - + def setUp(self): """ Create temporary directory """ hermes.tempDir = self.make_testDir() - + def tearDown(self): """ Delete temporary directory """ self.delete_testDir() - + @classmethod def tearDownClass(self): """ restore all mocked methods """ - reload(os) - reload(shutil) - reload(hermes) - - + imp.reload(os) + imp.reload(shutil) + imp.reload(hermes) + + @unittest.skipIf(noMock, "Mock not installed") def test1create_delete_dir(self): """catalogs.hermes run_hermesVR creating/deleting temp directory""" - - + + unseq, out, returncode = hermes.run_hermesVR(self.testfile, wvl_file=self.wvl_file, version='release', unseq=500) - + #-- create temp directories os.mkdir.assert_any_call(hermes.tempDir+'hermesvr/') os.mkdir.assert_any_call(hermes.tempDir+'hermesvr/reduced/') - - - + + + #-- delete temp directories os.rmdir.assert_any_call(hermes.tempDir+'hermesvr/') - os.rmdir.assert_any_call(hermes.tempDir+'hermesvr/reduced/') - - + os.rmdir.assert_any_call(hermes.tempDir+'hermesvr/reduced/') + + @unittest.skipIf(noMock, "Mock not installed") def test2copy_input(self): """catalogs.hermes run_hermesVR copy input files""" - + unseq, out, returncode = hermes.run_hermesVR(self.testfile, wvl_file=self.wvl_file, version='release', unseq=500) - + #-- copy the input file - shutil.copyfile.assert_called_with(self.testfile, + shutil.copyfile.assert_called_with(self.testfile, hermes.tempDir + 'hermesvr/reduced/500_HRF_OBJ_ext.fits') - + #-- delete the file again os.remove.assert_called_with(hermes.tempDir+'hermesvr/reduced/500_HRF_OBJ_ext.fits') - - + + @unittest.skipIf(noMock, "Mock not installed") def test3hermes_config(self): """catalogs.hermes run_hermesVR write correct hermesConfig""" - + #-- release version unseq, out, returncode = hermes.run_hermesVR(self.testfile, wvl_file=self.wvl_file, version='release', unseq=500) - - hermes.write_hermesConfig.assert_any_call(Nights = hermes.tempDir, + + hermes.write_hermesConfig.assert_any_call(Nights = hermes.tempDir, CurrentNight = hermes.tempDir + 'hermesvr/', Reduced = hermes.tempDir) - + #-- trunk version unseq, out, returncode = hermes.run_hermesVR(self.testfile, wvl_file=self.wvl_file, version='trunk', unseq=500) - - hermes.write_hermesConfig.assert_any_call(Nights = hermes.tempDir + 'hermesvr/', + + hermes.write_hermesConfig.assert_any_call(Nights = hermes.tempDir + 'hermesvr/', CurrentNight = hermes.tempDir + 'hermesvr/', Reduced = hermes.tempDir) - + @unittest.skipIf(noMock, "Mock not installed") def test2defaults(self): """catalogs.hermes run_hermesVR correct hermesVR command""" - + #-- release version unseq, out, returncode = hermes.run_hermesVR(self.testfile, wvl_file=self.wvl_file, version='release') - + cmd = ['python', '/STER/mercator/mercator/Hermes/releases/hermes5/pipeline/run/hermesVR.py', '-i', '262834', '-w', '/STER/mercator/hermes/20091130/reduced/00262831'] hermes._subprocess_execute.assert_called_with(cmd, 500) - + #-- trunk version unseq, out, returncode = hermes.run_hermesVR(self.testfile, wvl_file=self.wvl_file, version='trunk', timeout=150) - + cmd = ['python', '/STER/mercator/mercator/Hermes/trunk/hermes/pipeline/run/hermesVR.py', '-i', '262834', '-w', '/STER/mercator/hermes/20091130/reduced/00262831', '-f'] hermes._subprocess_execute.assert_called_with(cmd, 150) - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/catalogs/trilegal.py b/catalogs/trilegal.py index 2520e9146..58dd10cb1 100644 --- a/catalogs/trilegal.py +++ b/catalogs/trilegal.py @@ -6,7 +6,7 @@ from time import sleep, gmtime, strftime -from urllib import urlretrieve +from urllib.request import urlretrieve from mechanize import Browser, urlopen def trilegal(outputFileName, @@ -88,14 +88,14 @@ def trilegal(outputFileName, # Get the web form timestamp = strftime("%a, %d %b %Y %H:%M:%S", gmtime()) - print("{0}: Opening TRILEGAL web interface".format(timestamp)) + print(("{0}: Opening TRILEGAL web interface".format(timestamp))) myBrowser = Browser() try: myBrowser.open(trilegalURL) except: timestamp = strftime("%a, %d %b %Y %H:%M:%S", gmtime()) - print("{0}: Unable to open the TRILEGAL website".format(timestamp)) + print(("{0}: Unable to open the TRILEGAL website".format(timestamp))) return myBrowser.select_form(nr=0) # there is only one form... @@ -108,7 +108,7 @@ def trilegal(outputFileName, # >>> print forms[0] timestamp = strftime("%a, %d %b %Y %H:%M:%S", gmtime()) - print("{0}: Filling TRILEGAL web form".format(timestamp)) + print(("{0}: Filling TRILEGAL web form".format(timestamp))) if coordinateType == "galactic": myBrowser["gal_coord"] = ["1"] @@ -153,7 +153,7 @@ def trilegal(outputFileName, # Submit the completed form timestamp = strftime("%a, %d %b %Y %H:%M:%S", gmtime()) - print("{0}: Submitting completed TRILEGAL web form".format(timestamp)) + print(("{0}: Submitting completed TRILEGAL web form".format(timestamp))) nextWebPage = myBrowser.submit() @@ -161,7 +161,7 @@ def trilegal(outputFileName, # button until the webpage says that the computations are finished. timestamp = strftime("%a, %d %b %Y %H:%M:%S", gmtime()) - print ("{0}: Waiting until TRILEGAL computations are finished".format(timestamp)) + print(("{0}: Waiting until TRILEGAL computations are finished".format(timestamp))) myBrowser.select_form(nr=0) # one form on the "be patient" web page message = "Your job was finished" @@ -173,9 +173,9 @@ def trilegal(outputFileName, # Get the url of the outputfile, and retrieve it. This can take a while. timestamp = strftime("%a, %d %b %Y %H:%M:%S", gmtime()) - print("{0}: Retrieving TRILEGAL output file".format(timestamp)) + print(("{0}: Retrieving TRILEGAL output file".format(timestamp))) - outputLink = myBrowser.links(url_regex="lgirardi/tmp/output").next() + outputLink = next(myBrowser.links(url_regex="lgirardi/tmp/output")) urlretrieve(outputLink.absolute_url, outputFileName) myBrowser.close() diff --git a/catalogs/vizier.py b/catalogs/vizier.py index 03d522d16..6b19a3c16 100644 --- a/catalogs/vizier.py +++ b/catalogs/vizier.py @@ -84,15 +84,14 @@ """ #-- standard libraries import numpy as np -import urllib +import urllib.request, urllib.parse, urllib.error import logging import os import itertools import astropy.io.fits as pf import tarfile -import tempfile import shutil -import ConfigParser +import configparser from scipy.spatial import KDTree #-- IvS repository @@ -103,7 +102,6 @@ from ivs.aux import loggers from ivs.aux import numpy_ext from ivs.sed import filters -from ivs import config logger = logging.getLogger("CAT.VIZIER") logger.addHandler(loggers.NullHandler()) @@ -111,11 +109,11 @@ basedir = os.path.dirname(os.path.abspath(__file__)) #-- read in catalog information -cat_info = ConfigParser.ConfigParser() +cat_info = configparser.ConfigParser() cat_info.optionxform = str # make sure the options are case sensitive cat_info.readfp(open(os.path.join(basedir,'vizier_cats_phot.cfg'))) -cat_info_fund = ConfigParser.ConfigParser() +cat_info_fund = configparser.ConfigParser() cat_info_fund.optionxform = str # make sure the options are case sensitive cat_info_fund.readfp(open(os.path.join(basedir,'vizier_cats_fund.cfg'))) @@ -128,7 +126,7 @@ 'vizier.inasan.ru', # Russia 'vizier.iucaa.ernet.in', # India 'data.bao.ac.cn'])} # China -mirrors['current'] = mirrors['cycle'].next() +mirrors['current'] = next(mirrors['cycle']) #{ Basic interfaces @@ -136,7 +134,7 @@ def change_mirror(): """ Cycle through the mirrors of ViZieR. """ - mirrors['current'] = mirrors['cycle'].next() + mirrors['current'] = next(mirrors['cycle']) logger.info("Changed cycle to {}".format(mirrors['current'])) def search(name,filetype='tsv',filename=None,**kwargs): @@ -222,8 +220,8 @@ def search(name,filetype='tsv',filename=None,**kwargs): base_url = _get_URI(name=name,**kwargs) #-- prepare to open URI - url = urllib.URLopener() - filen,msg = url.retrieve(base_url,filename=filename) + url = urllib.request.URLopener() + filen, msg = url.retrieve(base_url,filename=filename) # maybe we are just interest in the file, not immediately in the content if filename is not None: logger.info('Querying ViZieR source %s and downloading to %s'%(name,filen)) @@ -234,9 +232,10 @@ def search(name,filetype='tsv',filename=None,**kwargs): if filetype=='tsv': try: results,units,comms = tsv2recarray(filen) + #-- raise an exception when multiple catalogs were specified except ValueError: - raise ValueError, "failed to read %s, perhaps multiple catalogs specified (e.g. III/168 instead of III/168/catalog)"%(name) + raise ValueError("failed to read %s, perhaps multiple catalogs specified (e.g. III/168 instead of III/168/catalog)"%(name)) url.close() logger.info('Querying ViZieR source %s (%d)'%(name,(results is not None and len(results) or 0))) return results,units,comms @@ -264,7 +263,7 @@ def list_catalogs(ID,filename=None,filetype='tsv',**kwargs): base_url = _get_URI(ID=ID,filetype='fits',**kwargs) #-- download the file - url = urllib.URLopener() + url = urllib.request.URLopener() filen,msg = url.retrieve(base_url,filename=filename) #-- if it is a FITS file, we extract all catalog IDs. We download the @@ -282,11 +281,11 @@ def list_catalogs(ID,filename=None,filetype='tsv',**kwargs): mycats[name] = title logger.info('%25s %s'%(name,title)) - photometry = [col for col in units.keys() if 'mag' in units[col]] - rv = [col for col in units.keys() if 'rv' in col.lower()] - vsini = [col for col in units.keys() if 'sin' in col.lower()] - sptype = [col for col in units.keys() if col.lower()=='sp' or col.lower()=='sptype'] - fund = [col for col in units.keys() if 'logg' in col.lower() or 'teff' in col.lower()] + photometry = [col for col in list(units.keys()) if 'mag' in units[col]] + rv = [col for col in list(units.keys()) if 'rv' in col.lower()] + vsini = [col for col in list(units.keys()) if 'sin' in col.lower()] + sptype = [col for col in list(units.keys()) if col.lower()=='sp' or col.lower()=='sptype'] + fund = [col for col in list(units.keys()) if 'logg' in col.lower() or 'teff' in col.lower()] ff.close() url.close() @@ -371,14 +370,14 @@ def xmatch(source1,source2,output_file=None,tol=1.,**kwargs): if i[0] in units1: ff.write(units1[i[0]]) elif i[0] in units2_: ff.write(units2_[i[0]]) else: - raise ValueError,'this cannot be' + raise ValueError('this cannot be') if nr<(len(dtypes)-1): ff.write('\t') ff.write('\n') ff.write('\t'.join(['---']*len(dtypes))) ff.write('\n') - for row1,row2 in itertools.izip(cat1,cat2): + for row1,row2 in zip(cat1,cat2): ff.write('\t'.join([str(x) for x in row1])+'\t') ff.write('\t'.join([str(x) for x in row2])+'\n') @@ -546,7 +545,7 @@ def get_photometry(ID=None,extra_fields=['_r','_RAJ2000','_DEJ2000'],take_mean=F #-- convert the measurement to a common unit. if to_units and master is not None: #-- prepare columns to extend to basic master - dtypes = [('cwave','f8'),('cmeas','f8'),('e_cmeas','f8'),('cunit','a50')] + dtypes = [('cwave','f8'),('cmeas','f8'),('e_cmeas','f8'),('cunit','U50')] cols = [[],[],[],[]] #-- forget about 'nan' errors for the moment no_errors = np.isnan(master['e_meas']) @@ -556,15 +555,23 @@ def get_photometry(ID=None,extra_fields=['_r','_RAJ2000','_DEJ2000'],take_mean=F for i in range(len(master)): to_units_ = to_units+'' try: - value,e_value = conversions.convert(master['unit'][i],to_units,master['meas'][i],master['e_meas'][i],photband=master['photband'][i]) + value, e_value = conversions.convert(master['unit'][i], + to_units, + master['meas'][i], + master['e_meas'][i], + photband=master['photband'][i]) except ValueError: # calibrations not available, or its a color # if it is a magnitude color, try converting it to a flux ratio if 'mag' in master['unit'][i]: try: - value,e_value = conversions.convert('mag_color','flux_ratio',master['meas'][i],master['e_meas'][i],photband=master['photband'][i]) + value, e_value = conversions.convert('mag_color', + 'flux_ratio', + master['meas'][i], + master['e_meas'][i], + photband=master['photband'][i]) to_units_ = 'flux_ratio' except ValueError: - value,e_value = np.nan,np.nan + value, e_value = np.nan, np.nan # else, we are powerless... else: value,e_value = np.nan,np.nan @@ -759,7 +766,7 @@ def tsv2recarray(filename): @return: catalog data columns, units, comments @rtype: record array, dict, list of str """ - data,comms = ascii.read2array(filename,dtype=np.str,splitchar='\t',return_comments=True) + data, comms = ascii.read2array(filename,dtype=np.str,splitchar='\t',return_comments=True) results = None units = {} #-- retrieve the data and put it into a record array @@ -771,19 +778,21 @@ def tsv2recarray(filename): # themselves (so called vectors). In those cases, we interpret # the contents as a long string formats = np.zeros_like(data[0]) + for line in comms: line = line.split('\t') + if len(line)<3: continue for i,key in enumerate(data[0]): if key == line[1] and line[0]=='Column': # this is the line with information - formats[i] = line[2].replace('(','').replace(')','').lower() - if formats[i][0].isdigit(): formats[i] = 'a100' + formats[i] = line[2].replace('(','').replace(')','').lower().replace('a', 'U') + if formats[i][0].isdigit(): formats[i] = 'U100' elif 'f' in formats[i]: formats[i] = 'f8' # floating point elif 'i' in formats[i]: formats[i] = 'f8' # integer, but make it float to contain nans elif 'e' in formats[i]: formats[i] = 'f8' # exponential #-- see remark about the nans a few lines down - if formats[i][0]=='a': - formats[i] = 'a'+str(int(formats[i][1:])+3) + if formats[i][0]=='U': + formats[i] = 'U'+str(int(formats[i][1:])+3) #-- define dtypes for record array dtypes = np.dtype([(i,j) for i,j in zip(data[0],formats)]) #-- remove spaces or empty values @@ -941,8 +950,8 @@ def vizier2phot(source,results,units,master=None,e_flag='e_',q_flag='q_',extra_f e_flag = cat_info.get(source,'e_flag') #-- basic dtypes - dtypes = [('meas','f8'),('e_meas','f8'),('flag','a20'), - ('unit','a30'),('photband','a30'),('source','a50')] + dtypes = [('meas','f8'),('e_meas','f8'),('flag','U20'), + ('unit','U30'),('photband','U30'),('source','U50')] #-- extra can be added: names = list(results.dtype.names) @@ -1068,8 +1077,8 @@ def vizier2fund(source,results,units,master=None,e_flag='e_',q_flag='q_',extra_f e_flag = cat_info_fund.get(source,'e_flag') #-- basic dtypes - dtypes = [('meas','f8'),('e_meas','f8'),('q_meas','f8'),('unit','a30'), - ('source','a50'),('name','a50')] + dtypes = [('meas','f8'),('e_meas','f8'),('q_meas','f8'),('unit','U30'), + ('source','U50'),('name','U50')] #-- extra can be added: names = list(results.dtype.names) @@ -1127,7 +1136,7 @@ def catalog2bibcode(catalog): N = max(2,len(catalog)-1) catalog = "/".join(catalog[:N]) base_url = "http://cdsarc.u-strasbg.fr/viz-bin/Cat?{0}".format(catalog) - url = urllib.URLopener() + url = urllib.request.URLopener() filen,msg = url.retrieve(base_url) code = None @@ -1150,7 +1159,7 @@ def bibcode2bibtex(bibcode): @rtype: str """ base_url = "http://adsabs.harvard.edu/cgi-bin/nph-bib_query?bibcode={0}&data_type=BIBTEX&db_key=AST&nocookieset=1".format(bibcode) - url = urllib.URLopener() + url = urllib.request.URLopener() filen,msg = url.retrieve(base_url) bibtex = [] @@ -1232,7 +1241,7 @@ def _get_URI(name=None,ID=None,ra=None,dec=None,radius=20., if out_all: base_url += '&-out.all' if out_max: base_url += '&-out.max=%s'%(out_max) if radius: base_url += '&-c.rs=%s'%(radius) - if ID is not None and ra is None: base_url += '&-c=%s'%(urllib.quote(ID)) + if ID is not None and ra is None: base_url += '&-c=%s'%(urllib.parse.quote(ID)) if ra is not None: base_url += '&-c.ra=%s&-c.dec=%s'%(ra,dec) logger.debug(base_url) #print base_url diff --git a/catalogs/vizier_cats_fund.cfg b/catalogs/vizier_cats_fund.cfg index e45ac28d2..2f37dfab8 100644 --- a/catalogs/vizier_cats_fund.cfg +++ b/catalogs/vizier_cats_fund.cfg @@ -22,10 +22,10 @@ logRad = logRad Mass = Mass logg = logg -[B/pastel/pastel] # The Pastel Catalogue -Teff = Teff -logg = logg -_[Fe/H]_ = [Fe/H] +#[B/pastel/pastel] # The Pastel Catalogue +#Teff = Teff +#logg = logg +#_[Fe/H]_ = [Fe/H] [J/A+AS/117/227/table4] # Dwarf effective temperatures (Alonso 1996) E(B-V) = E(B-V) @@ -40,11 +40,11 @@ logg = logg Diam = Diam E(B-V) = E(B-V) -[J/A+A/512/A54] # Casagrande 2008 -_[Fe/H]_ = [Fe/H] -Teff = Teff -Diam = theta -E(B-V) = E(B-V) +#[J/A+A/512/A54] # Casagrande 2008 +#_[Fe/H]_ = [Fe/H] +#Teff = Teff +#Diam = theta +#E(B-V) = E(B-V) [J/A+A/487/373] # Sousa 2008 Teff = Teff @@ -88,15 +88,15 @@ Vturb = Vtur [I/311/hip2] # Van Leeuwen parallaxes Plx = Plx -[J/A+A/530/A138/catalog] # Geneva-Copenhagen survey re-analysis (Casagrande+, 2011) +[J/A+A/530/A138/catalog] # Geneva-Copenhagen survey re-analysis (Casagrande+, 2011) Teff = Teff logg = logg _[Fe/H]_ = [Fe/H] _[M/H]_ = [M/H] E(B-V) = E(B-V) -[II/300/jsdc] # JMMC Stellar Diameters Catalogue - JSDC (Lafrasse+, 2010) +[II/300/jsdc] # JMMC Stellar Diameters Catalogue - JSDC (Lafrasse+, 2010) # do not use Teff and logg, they are adopted from SpType and thus very unprecise LDD = Diam #Teff = Teff -#logg = logg \ No newline at end of file +#logg = logg diff --git a/catalogs/vizier_cats_phot.cfg b/catalogs/vizier_cats_phot.cfg index a6d740498..f7c1bc90f 100644 --- a/catalogs/vizier_cats_phot.cfg +++ b/catalogs/vizier_cats_phot.cfg @@ -10,7 +10,7 @@ [II/169/main] # Rufener Geneva photometric -Vmag = GENEVA.V +Vmag = GENEVA.V U-B = GENEVA.U-B V-B = GENEVA.V-B V1-B = GENEVA.V1-B @@ -20,7 +20,7 @@ G-B = GENEVA.G-B bibcode = 1988csmg.book.....R [J/A+A/499/967/stars] # Cuypers 2009 Gamma Doradus stars -Vmag = GENEVA.V +Vmag = GENEVA.V Umag = GENEVA.U V1mag= GENEVA.V1 B1mag= GENEVA.B1 @@ -28,28 +28,28 @@ B2mag= GENEVA.B2 Gmag = GENEVA.G [II/246/out] # 2MASS point source -Jmag = 2MASS.J +Jmag = 2MASS.J Hmag = 2MASS.H Kmag = 2MASS.KS [II/281/2mass6x] # 2MASS 6X Point Source Working Database / Catalog (Cutri+ 2006) -Jmag = 2MASS.J +Jmag = 2MASS.J Hmag = 2MASS.H Kmag = 2MASS.KS bibcode = 2012yCat.2281....0C [J/AJ/121/3160/table4]# JHK photometry near the Trapezium region (Carpenter+, 2001) -Jmag = 2MASS.J +Jmag = 2MASS.J Hmag = 2MASS.H Kmag = 2MASS.KS [J/AJ/124/1001/table3] # JHKs photometry of Cha I variables (Carpenter, 2002) -Jmag = 2MASS.J +Jmag = 2MASS.J Hmag = 2MASS.H Ksmag = 2MASS.KS [J/AJ/124/1001/table5] # JHKs photometry of Cha I variables (Carpenter, 2002) -Jmag = 2MASS.J +Jmag = 2MASS.J Hmag = 2MASS.H Ksmag = 2MASS.KS @@ -90,7 +90,7 @@ F2740 = TD1.2740 bibcode = 1976A&A....49..389H [II/97/ans] # ANS satellite -15N = ANS.15N +15N = ANS.15N 15W = ANS.15W 18 = ANS.18 22 = ANS.22 @@ -107,7 +107,7 @@ BTmag = TYCHO2.BT VTmag = TYCHO2.VT Hpmag = HIPPARCOS.HP -[V/137C/XHIP] # Extended Hipparcos Compilation (XHIP) (Anderson+, 2012) +[V/137C/XHIP] # Extended Hipparcos Compilation (XHIP) (Anderson+, 2012) Umag = JOHNSON.U Bmag = JOHNSON.B Vmag = JOHNSON.V @@ -139,14 +139,14 @@ bibcode = 2009yCat.2293....0S 4.5mag = IRAC.45 5.8mag = IRAC.58 8.0mag = IRAC.80 -24mag = MIPS.24 +24mag = MIPS.24 [J/ApJS/184/172/catalog] # High- and intermediate-mass YSOs in the LMC (Gruendl+, 2009) 3.6mag = IRAC.36 4.5mag = IRAC.45 5.8mag = IRAC.58 8.0mag = IRAC.80 -24mag = MIPS.24 +24mag = MIPS.24 70mag = MIPS.70 [J/AJ/136/18/table12] # LMC SAGE. New candidate YSOs (Whitney+, 2008) BOTH MAG AND FLUX AVAILABLE @@ -163,7 +163,7 @@ F70 = MIPS.70 [J/ApJ/620/1010/table1] F24um = MIPS.24 -[J/ApJ/655/212/table4] # S3MC IRAC and MIPS photometry (Bolatto+, 2007) +[J/ApJ/655/212/table4] # S3MC IRAC and MIPS photometry (Bolatto+, 2007) FB = JOHNSON.B FV = JOHNSON.V FI = JOHNSON.I @@ -175,19 +175,19 @@ F24 = MIPS.24 F70 = MIPS.70 [II/294/sdss7] # SDSS -umag = SDSS7.U +umag = SDSS7.U gmag = SDSS7.G rmag = SDSS7.R imag = SDSS7.I zmag = SDSS7.Z [II/306/sdss8] # SDSS -upmag = SDSS.U +upmag = SDSS.U gpmag = SDSS.G rpmag = SDSS.R ipmag = SDSS.I zpmag = SDSS.Z -# umag = SDSS.U +# umag = SDSS.U # gmag = SDSS.G # rmag = SDSS.R # imag = SDSS.I @@ -220,9 +220,9 @@ M = JOHNSON.M N = JOHNSON.N H = JOHNSON.H -[J/A+A/320/185/table6] # UBVRIcJHKL photometry in Lup (Wichmann+ 1997) +[J/A+A/320/185/table6] # UBVRIcJHKL photometry in Lup (Wichmann+ 1997) Vmag = JOHNSON.V -Vmag = COUSINS.V +#Vmag = COUSINS.V B-V = JOHNSON.B-V U-B = JOHNSON.U-B V-Rc = COUSINS.V-R @@ -263,12 +263,12 @@ N-V = JOHNSON.N-V bibcode = 2002yCat.2237....0D [II/193/catalog] # Mermilliod -V = JOHNSON.V +V = JOHNSON.V B-V = JOHNSON.B-V U-B = JOHNSON.U-B [II/168/ubvmeans] # Mermilliod 2 -Vmag = JOHNSON.V +Vmag = JOHNSON.V B-V = JOHNSON.B-V U-B = JOHNSON.U-B bibcode = 2006yCat.2168....0M @@ -305,7 +305,7 @@ Jmag = JOHNSON.J Hmag = JOHNSON.H Kmag = JOHNSON.K -[J/AJ/123/855/table1] # Magellanic Clouds Photometric Survey: the SMC (Zaritsky+, 2002) +[J/AJ/123/855/table1] # Magellanic Clouds Photometric Survey: the SMC (Zaritsky+, 2002) Umag = JOHNSON.U Bmag = JOHNSON.B Vmag = JOHNSON.V @@ -331,7 +331,7 @@ Kmag = JOHNSON.K Jmag = JOHNSON.J Hmag = JOHNSON.H Kmag = JOHNSON.K - + [I/304/out] # CMC r'mag = SDSS.RP bibcode = 2008ApJS..175..297A @@ -343,60 +343,60 @@ S140 = AKARI.WIDEL S160 = AKARI.N160 bibcode = 2007PASJ...59S.369M -[II/297/irc] # AKARI IRC +[II/297/irc] # AKARI IRC S09 = AKARI.S9W S18 = AKARI.L18W [II/215/catalog] # Hauck's Stromgren -Vmag = STROMGREN.Y +Vmag = STROMGREN.Y b-y = STROMGREN.B-Y m1 = STROMGREN.M1 c1 = STROMGREN.C1 Beta = STROMGREN.HBN-HBW [V/130/gcs3] # Geneva-Copenhagen Survey of Solar Neighbourhood III -Vmag = STROMGREN.Y +Vmag = STROMGREN.Y b-y = STROMGREN.B-Y m1 = STROMGREN.M1 c1 = STROMGREN.C1 [J/A+A/528/A148/tables] # uvbybeta photometry of early type stars (Handler 2011) -Vmag = STROMGREN.Y +Vmag = STROMGREN.Y b-y = STROMGREN.B-Y m1 = STROMGREN.M1 c1 = STROMGREN.C1 beta = STROMGREN.HBN-HBW [J/A+A/426/827/table4] # ubvyHb photometry in NGC 1817/1807 (Balaguer-Nunez 2004) -Vmag = STROMGREN.Y +Vmag = STROMGREN.Y b-y = STROMGREN.B-Y m1 = STROMGREN.M1 c1 = STROMGREN.C1 Hb = STROMGREN.HBN-HBW [J/A+AS/128/265/table2] # ubvy-beta photometry of CP2 stars (Masana 1998) -Vmag = STROMGREN.Y +Vmag = STROMGREN.Y (b-y) = STROMGREN.B-Y m1 = STROMGREN.M1 c1 = STROMGREN.C1 Hbeta = STROMGREN.HBN-HBW [J/A+A/373/625/table2] # uvbyB photometry of lambda Bootis stars (Paunzen 2001) -Vmag = STROMGREN.Y +Vmag = STROMGREN.Y b-y = STROMGREN.B-Y m1 = STROMGREN.M1 c1 = STROMGREN.C1 beta = STROMGREN.HBN-HBW -[J/A+AS/104/557/table3b] # uvby-beta photometry in star-forming regions (Terranegra+ 1994) -Vmag = STROMGREN.Y +[J/A+AS/104/557/table3b] # uvby-beta photometry in star-forming regions (Terranegra+ 1994) +Vmag = STROMGREN.Y (b-y) = STROMGREN.B-Y c1 = STROMGREN.C1 m1 = STROMGREN.M1 beta = STROMGREN.HBN-HBW [J/A+A/352/600/table3] # O&B stars Hbeta photometry I (Kaltcheva 1999) -Vmag = STROMGREN.Y +Vmag = STROMGREN.Y (b-y)= STROMGREN.B-Y m1 = STROMGREN.M1 c1 = STROMGREN.C1 @@ -405,36 +405,36 @@ c1 = STROMGREN.C1 Hbeta= STROMGREN.HBN-HBW [J/A+A/352/605/table3] # O&B stars Hbeta photometry II (Kaltcheva 1999) -Vmag = STROMGREN.Y +Vmag = STROMGREN.Y (b-y)= STROMGREN.B-Y m1 = STROMGREN.M1 c1 = STROMGREN.C1 -[J/A+A/352/600/table4] # O&B stars Hbeta photometry II (Kaltcheva 1999) -Hbeta= STROMGREN.HBN-HBW +#[J/A+A/352/600/table4] # O&B stars Hbeta photometry II (Kaltcheva 1999) +#Hbeta= STROMGREN.HBN-HBW [J/PASP/105/36/table2] # All-sky uvby photometry of speckle binaries (Sowell 1993) -Vmag = STROMGREN.Y +Vmag = STROMGREN.Y (b-y)= STROMGREN.B-Y m1 = STROMGREN.M1 c1 = STROMGREN.C1 [II/78/data] # uvby-Beta Phot 398 members of Visual Multiple Systems (Olsen 1982) -Vmag = STROMGREN.Y +Vmag = STROMGREN.Y b-y = STROMGREN.B-Y m1 = STROMGREN.M1 c1 = STROMGREN.C1 beta = STROMGREN.HBN-HBW [II/33/data] # uvby-beta Photometry for Bright O-G0 Souther Stars (Gronbech 1976-77) -Vmag = STROMGREN.Y +Vmag = STROMGREN.Y b-y = STROMGREN.B-Y m1 = STROMGREN.M1 c1 = STROMGREN.C1 beta = STROMGREN.HBN-HBW [J/A+AS/112/95/table] # uvby-beta photometry in Cen-Cru-Mus-Cha (Corradi+, 1995) -Vmag = STROMGREN.Y +Vmag = STROMGREN.Y b-y = STROMGREN.B-Y m1 = STROMGREN.M1 c1 = STROMGREN.C1 @@ -474,11 +474,11 @@ E = MSX.E #FSIIIE = MSX.E [II/247/machovar] # Macho variables -Vmag = MACHO.V +Vmag = MACHO.V Rmag = MACHO.R [II/183A/table2] # Landolt -Vmag = LANDOLT.V +Vmag = LANDOLT.V B-V = LANDOLT.B-V U-B = LANDOLT.U-B V-R = LANDOLT.V-R @@ -525,7 +525,7 @@ F24 = MIPS.24 F70 = MIPS.70 F160 = MIPS.160 -[J/ApJ/647/1180/table4] # Infrared photometry of Taurus SFR (Luhman+, 2006) +[J/ApJ/647/1180/table4] # Infrared photometry of Taurus SFR (Luhman+, 2006) _[3.6]_ = IRAC.36 _[4.5]_ = IRAC.45 _[5.8]_ = IRAC.58 @@ -553,7 +553,7 @@ _[24]_ = MIPS.24 52-99 = JOHNSON.52-99 52-110 = JOHNSON.52-110 -[II/75/mean] # Homegeneous RI magnitudes in Kron system (Jasniewicz 1982) +[II/75/mean] # Homegeneous RI magnitudes in Kron system (Jasniewicz 1982) Rmag = KRON.R R-I = KRON.R-I @@ -565,7 +565,7 @@ R-I = KRON.R-I # C(38-41) = DDO.38-41 # C(35-38) = DDO.35-38 -[II/164/mean] # DDO Photoelectric Photometric Catalog (Mermilliod+ 1989) +[II/164/mean] # DDO Photoelectric Photometric Catalog (Mermilliod+ 1989) m48 = DDO.48 C(48-51) = DDO.48-51 C(45-48) = DDO.45-48 @@ -587,7 +587,7 @@ m1550 = OAO2.155 m1430 = OAO2.143 m1330 = OAO2.133 -[J/ApJS/175/277/maps] # Submillimeter-Continuum SCUBA detections (Di Francesco+, 2008) +[J/ApJS/175/277/maps] # Submillimeter-Continuum SCUBA detections (Di Francesco+, 2008) F850 = SCUBA.850WB F450 = SCUBA.450WB @@ -614,12 +614,12 @@ Jmag = WFCAM.J Hmag = WFCAM.H Kmag = WFCAM.K -[J/MNRAS/397/1685/cleancat] #Galactic plane IPHAS-POSSI proper motion survey (Deacon+, 2009) +[J/MNRAS/397/1685/cleancat] #Galactic plane IPHAS-POSSI proper motion survey (Deacon+, 2009) r'mag = IPHAS.RP i'mag = IPHAS.IP Hamag = IPHAS.HA -[J/A+A/380/609/table2] # Southern emission-line stars multiphotometry (de Winter+, 2001) +[J/A+A/380/609/table2] # Southern emission-line stars multiphotometry (de Winter+, 2001) Vwmag = WALRAVEN.V V-Bw = WALRAVEN.V-B B-Uw = WALRAVEN.B-U @@ -627,22 +627,48 @@ U-Ww = WALRAVEN.U-W B-Lw = WALRAVEN.B-L Vmag = JOHNSON.V -[J/A+A/380/609/table3] # Southern emission-line stars multiphotometry (de Winter+, 2001) +[J/A+A/380/609/table3] # Southern emission-line stars multiphotometry (de Winter+, 2001) Vmag = JOHNSON.V -Vmag = COUSINS.V +#Vmag = COUSINS.V B-V = JOHNSON.B-V U-B = JOHNSON.U-B V-Rc = COUSINS.V-R V-Ic = COUSINS.V-I -[J/A+A/380/609/table4] # Southern emission-line stars multiphotometry (de Winter+, 2001) +[J/A+A/380/609/table4] # Southern emission-line stars multiphotometry (de Winter+, 2001) Jmag = SAAO.J Hmag = SAAO.H Kmag = SAAO.K Lmag = SAAO.L +[II/312/ais] #GALEX-DR5 (GR5) sources from AIS and MIS (Bianchi+ 2011) +FUV = GALEX.FUV +NUV = GALEX.NUV + +[I/337/gaia] #Gaia DR1 (Gaia Collaboration, 2016) + = GAIA.G + +[I/320/spm4] #SPM 4.0 Catalog (Girard+, 2011) +Bmag = JOHNSON.B +Vmag = JOHNSON.V + +[I/275/ac2002] #The AC 2000.2 Catalogue (Urban+ 2001) +Vmag = TYCHO2.VT + +[II/336/apass9] # AAVSO Photometric All Sky Survey (APASS) DR9 (Henden+, 2016) +Bmag = JOHNSON.B +Vmag = JOHNSON.V +B-V = JOHNSON.B-V + +[I/305/out] #The Guide Star Catalog, Version 2.3.2 (GSC2.3) (STScI, 2006) +Umag = JOHNSON.U +Bmag = JOHNSON.B + +[J/MNRAS/463/4210/ucac4rpm] #All-sky catalog of solar type dwarfs (Nascimbeni+, 2016) +Vmag = JOHNSON.V +Bmag = JOHNSON.B -#[J/ApJS/169/401/ChaMP] #ChaMP X-ray point source catalog (Kim+, 2007) +#[J/ApJS/169/401/ChaMP] #ChaMP X-ray point source catalog (Kim+, 2007) #BNET #S1Net #S2Net @@ -650,4 +676,4 @@ Lmag = SAAO.L #HNet #BcNet #ScNet -#HcNet \ No newline at end of file +#HcNet diff --git a/config.py b/config.py index cb497da01..46f5c0c5c 100644 --- a/config.py +++ b/config.py @@ -3,18 +3,18 @@ Global configuration of the IvS package. Usage: $ python config.py compile + $ python config.py recompile """ import os -import collections -import ast import sys import glob as glob_module #-- You can add directories here, but be sure that the relative paths within # those directories are correct! -data_dirs = [os.getenv('ivsdata'),'/STER/pieterd/IVSDATA/', '/STER/kristofs/IVSdata','/STER/jorisv/IVSDATA/', - '/STER/kenneth/Python_repository/','/home/ben/public_html/opacities','/STER/michelh/IVSDATA/'] +data_dirs = [os.getenv('ivsdata'),'/STER/pieterd/IVSDATA/', '/STER/kristofs/IVSdata','/STER/jorisv/IVSDATA/', + '/STER/kenneth/Python_repository/','/home/ben/public_html/opacities','/STER/michelh/IVSDATA/', + '/STER/mike/IVSDATA/', '/STER/anae/IVSDATA/'] ivs_dirs = dict(coralie='/STER/coralie/', hermes='/STER/mercator/hermes/') @@ -46,7 +46,7 @@ def get_datafile(relative_path,basename): else: str_data_dirs = ", ".join([idir for idir in data_dirs if idir is not None]) relative_file = os.path.join(relative_path,basename) - raise IOError, "File %s not found in any of the specified data directories %s"%(relative_file,str_data_dirs) + raise IOError("File %s not found in any of the specified data directories %s"%(relative_file,str_data_dirs)) return filename @@ -79,6 +79,7 @@ def glob(relative_path,arg='*'): from ivs.aux import loggers import shutil import time + import glob logger = loggers.get_basic_logger() to_install = ['spectra/pyrotin4', @@ -92,25 +93,30 @@ def glob(relative_path,arg='*'): compiler = sys.argv[2] else: compiler = 'gfortran' - if sys.argv[1]=='compile': + if 'compile' in sys.argv[1]: answer = 'y' for name in to_install: #-- break up fortran filepath in file and directory name direc,pname = os.path.dirname(name),os.path.basename(name) - if not os.path.isfile(name+'.so'): + if not os.path.isfile(name+'.so') or sys.argv[1] == 'recompile': #-- build the command to compile the program and log it to # the user cmd = 'f2py --fcompiler=%s -c %s.f -m %s'%(compiler,os.path.join(direc,pname),pname) logger.info('Compiling %s: %s'%(pname.upper(),cmd)) - if answer!='Y': - answer = raw_input('Continue? [Y/n] ') + if answer.lower()!='y': + answer = input('Continue? [Y/n] ') if answer.lower()=='n': continue #-- call the compiling command p = subprocess.check_output(cmd,shell=True)#,stdout=devnull) #-- check if compilation went fine - if os.path.isfile(pname+'.so'): - shutil.move(pname+'.so',name+'.so') + # find compiled file name + compiled_filename = list(filter(os.path.isfile, glob.glob('./'+pname+'*.so'))) + # if it exists move it to to appropriate dir + # and change the compiled name e.g. deeming.cpython-36m-x86_64-linux-gnu.so + # to e.g. deeming.so + if compiled_filename: + shutil.move(compiled_filename[0],name+'.so') logger.info('... succeeded') else: logger.error('FAILED') diff --git a/coordinates/vectors.py b/coordinates/vectors.py index f4484e977..eddd1c17a 100644 --- a/coordinates/vectors.py +++ b/coordinates/vectors.py @@ -51,7 +51,7 @@ def cart2spher_coord(x,y,z): phi = np.arctan2(y,x) theta = np.arctan2(np.sqrt(x**2+y**2),z) return rho,phi,theta - + def rotate(x,y,theta,x0=0.,y0=0.): """ Rotate counter clockwise. @@ -63,31 +63,35 @@ def rotate(x,y,theta,x0=0.,y0=0.): #{ Coordinate vector transformations -def spher2cart((r,phi,theta),(a_r,a_phi,a_theta)): +def spher2cart(spherical_coord, a_spherical_coord): """ theta is angle from z-axis (colatitude) phi is longitude - + E.g. http://www.engin.brown.edu/courses/en3/Notes/Vector_Web2/Vectors6a/Vectors6a.htm - + >>> np.random.seed(111) >>> r,phi,theta = np.random.uniform(low=-1,high=1,size=(3,2)) >>> a_r,a_phi,a_theta = np.random.uniform(low=-1,high=1,size=(3,2)) >>> a_x,a_y,a_z = spher2cart((r,phi,theta),(a_r,a_phi,a_theta)) """ + (r,phi,theta) = spherical_coord + (a_r,a_phi,a_theta) = a_spherical_coord ax = sin(theta)*cos(phi)*a_r + cos(theta)*cos(phi)*a_theta - sin(phi)*a_phi ay = sin(theta)*sin(phi)*a_r + cos(theta)*sin(phi)*a_theta + cos(phi)*a_phi az = cos(theta) *a_r - sin(theta) *a_theta return ax,ay,az - -def cart2spher((x0,y0,z0),(x1,y1,z1)): + +def cart2spher(cartesian_coordinats_0, cartesian_coordinats_1): """ theta is angle from z-axis (colatitude) phi is longitude - + return r,phi,theta """ + (x0,y0,z0) = cartesian_coordinats_0 + (x1,y1,z1) = cartesian_coordinats_1 r,phi,theta = cart2spher_coord(x0,y0,z0) ar = sin(theta)*cos(phi)*x1 + sin(theta)*sin(phi)*y1 + cos(theta)*z1 atheta = cos(theta)*cos(phi)*x1 + cos(theta)*sin(phi)*y1 - sin(theta) *z1 @@ -101,25 +105,25 @@ def cart2spher((x0,y0,z0),(x1,y1,z1)): def norm(vec): """ Euclidic norm of a vector (or of a grid of vectors) - + Input vectors should be numpy arrays. """ return sqrt((vec**2).sum(axis=0)) - + def angle(vec1,vec2): """ Compute angle between two vectors (or between two grids of vectors). - + Input vectors should be numpy arrays. """ return np.arccos( (vec1*vec2).sum(axis=0) / (norm(vec1)*norm(vec2))) - - + + def cos_angle(vec1,vec2): """ Compute cosine of angle between two vectors (or between two grids of vectors). - + Input vectors should be numpy arrays. """ return (vec1*vec2).sum(axis=0) / (norm(vec1)*norm(vec2)) @@ -130,4 +134,4 @@ def cos_angle(vec1,vec2): if __name__=="__main__": import doctest import pylab as pl - doctest.testmod() \ No newline at end of file + doctest.testmod() diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000..b5293242b --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = python -msphinx +SPHINXPROJ = SphinxDocumentation +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/source/README.rst b/docs/source/README.rst new file mode 100644 index 000000000..2574ade8e --- /dev/null +++ b/docs/source/README.rst @@ -0,0 +1 @@ +.. include:: ../../readme.rst \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 000000000..1605fe10f --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Sphinx Documentation documentation build configuration file, created by +# sphinx-quickstart on Thu Nov 30 20:15:48 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('../..')) + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.autodoc'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'Sphinx Documentation' +copyright = '2017, Nicola' +author = 'Nicola' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.0.1' +# The full version, including alpha/beta/rc tags. +release = '0.0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +#html_theme = 'alabaster' + + +# on_rtd is whether we are on readthedocs.org +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +if not on_rtd: # only import and set the theme if we're building docs locally + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + '**': [ + 'about.html', + 'navigation.html', + 'relations.html', # needs 'show_related': True theme option to display + 'searchbox.html', + 'donate.html', + ] +} + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'SphinxDocumentationdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'SphinxDocumentation.tex', 'Sphinx Documentation Documentation', + 'Nicola', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'sphinxdocumentation', 'Sphinx Documentation Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'SphinxDocumentation', 'Sphinx Documentation Documentation', + author, 'SphinxDocumentation', 'One line description of project.', + 'Miscellaneous'), +] + + + diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 000000000..d02af71bc --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,19 @@ +.. Sphinx Documentation documentation master file, created by + sphinx-quickstart on Thu Nov 30 20:15:48 2017. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Sphinx Documentation's documentation! +================================================ + +.. toctree:: + + README + sed/index + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/source/sed/HD180642.phot b/docs/source/sed/HD180642.phot new file mode 100644 index 000000000..bb50db687 --- /dev/null +++ b/docs/source/sed/HD180642.phot @@ -0,0 +1,62 @@ +# meas e_meas flag unit photband source _r _RAJ2000 _DEJ2000 cwave cmeas e_cmeas cunit color include +#float64 float64 |S20 |S30 |S30 |S50 float64 float64 float64 float64 float64 float64 |S50 bool bool +7.823 0.02 nan mag WISE.W3 wise_prelim_p3as_psd 0.112931 2.667e-05 2.005e-05 123337 4.83862e-17 8.91306e-19 erg/s/cm2/AA 0 1 +7.744 0.179 nan mag WISE.W4 wise_prelim_p3as_psd 0.112931 2.667e-05 2.005e-05 222532 4.06562e-18 6.70278e-19 erg/s/cm2/AA 0 1 +7.77 0.023 nan mag WISE.W1 wise_prelim_p3as_psd 0.112931 2.667e-05 2.005e-05 33791.9 6.378e-15 1.3511e-16 erg/s/cm2/AA 0 1 +7.803 0.02 nan mag WISE.W2 wise_prelim_p3as_psd 0.112931 2.667e-05 2.005e-05 46293 1.82691e-15 3.36529e-17 erg/s/cm2/AA 0 1 +8.505 0.016 nan mag TYCHO2.BT I/259/tyc2 0.042 7.17e-06 1.15e-06 4204.4 2.76882e-12 4.08029e-14 erg/s/cm2/AA 0 1 +8.296 0.013 nan mag TYCHO2.VT I/259/tyc2 0.042 7.17e-06 1.15e-06 5321.86 1.93604e-12 2.31811e-14 erg/s/cm2/AA 0 1 +8.27 nan nan mag JOHNSON.V II/168/ubvmeans 0.01 1.7e-07 3.15e-06 5504.67 1.80578e-12 1.80578e-13 erg/s/cm2/AA 0 1 +0.22 nan nan mag JOHNSON.B-V II/168/ubvmeans 0.01 1.7e-07 3.15e-06 nan 1.40749 0.140749 flux_ratio 1 0 +-0.66 nan nan mag JOHNSON.U-B II/168/ubvmeans 0.01 1.7e-07 3.15e-06 nan 1.21491 0.121491 flux_ratio 1 0 +8.49 nan nan mag JOHNSON.B II/168/ubvmeans 0.01 1.7e-07 3.15e-06 4448.06 2.54162e-12 2.54162e-13 erg/s/cm2/AA 0 1 +7.83 nan nan mag JOHNSON.U II/168/ubvmeans 0.01 1.7e-07 3.15e-06 3641.75 3.08783e-12 3.08783e-13 erg/s/cm2/AA 0 1 +2.601 nan nan mag STROMGREN.HBN-HBW J/A+A/528/A148/tables 0.54 -5.983e-05 -0.00013685 nan 1.66181 0.166181 flux_ratio 1 0 +-0.043 nan nan mag STROMGREN.M1 J/A+A/528/A148/tables 0.54 -5.983e-05 -0.00013685 nan 0.961281 0.0961281 flux_ratio 1 0 +8.221 nan nan mag STROMGREN.Y J/A+A/528/A148/tables 0.54 -5.983e-05 -0.00013685 5477.32 1.88222e-12 1.88222e-13 erg/s/cm2/AA 0 1 +0.009 nan nan mag STROMGREN.C1 J/A+A/528/A148/tables 0.54 -5.983e-05 -0.00013685 nan 0.93125 0.093125 flux_ratio 1 0 +0.238 nan nan mag STROMGREN.B-Y J/A+A/528/A148/tables 0.54 -5.983e-05 -0.00013685 nan 1.28058 0.128058 flux_ratio 1 0 +8.459 nan nan mag STROMGREN.B J/A+A/528/A148/tables 0.54 -5.983e-05 -0.00013685 4671.2 2.41033e-12 2.41033e-13 erg/s/cm2/AA 0 1 +8.654 nan nan mag STROMGREN.V J/A+A/528/A148/tables 0.54 -5.983e-05 -0.00013685 4108.07 2.96712e-12 2.96712e-13 erg/s/cm2/AA 0 1 +8.858 nan nan mag STROMGREN.U J/A+A/528/A148/tables 0.54 -5.983e-05 -0.00013685 3462.92 3.40141e-12 3.40141e-13 erg/s/cm2/AA 0 1 +7.82 0.01 nan mag JOHNSON.J J/PASP/120/1128/catalog 0.02 1.017e-05 3.15e-06 12487.8 2.36496e-13 2.36496e-15 erg/s/cm2/AA 0 1 +7.79 0.01 nan mag JOHNSON.K J/PASP/120/1128/catalog 0.02 1.017e-05 3.15e-06 21951.2 3.24868e-14 3.24868e-16 erg/s/cm2/AA 0 1 +7.83 0.04 nan mag JOHNSON.H J/PASP/120/1128/catalog 0.02 1.017e-05 3.15e-06 16464.4 8.64659e-14 3.18552e-15 erg/s/cm2/AA 0 1 +8.3451 0.0065 nan mag HIPPARCOS.HP I/239/hip_main 0.036 2.17e-06 1.15e-06 5275.11 1.91003e-12 1.91003e-14 erg/s/cm2/AA 0 1 +8.525 0.011 nan mag TYCHO2.BT I/239/hip_main 0.036 2.17e-06 1.15e-06 4204.4 2.71829e-12 2.754e-14 erg/s/cm2/AA 0 1 +8.309 0.012 nan mag TYCHO2.VT I/239/hip_main 0.036 2.17e-06 1.15e-06 5321.86 1.913e-12 2.11433e-14 erg/s/cm2/AA 0 1 +8.02 0.057 nan mag COUSINS.I II/271A/patch2 0.2 -4.983e-05 2.315e-05 7884.05 7.51152e-13 3.94347e-14 erg/s/cm2/AA 0 1 +8.287 0.056 nan mag JOHNSON.V II/271A/patch2 0.2 -4.983e-05 2.315e-05 5504.67 1.77773e-12 9.16914e-14 erg/s/cm2/AA 0 1 +8.47 nan nan mag USNOB1.B1 I/284/out 0.026 7.17e-06 1.5e-07 4448.06 2.57935e-12 7.73805e-13 erg/s/cm2/AA 0 1 +8.19 nan nan mag USNOB1.R1 I/284/out 0.026 7.17e-06 1.5e-07 6939.52 1.02601e-12 3.07803e-13 erg/s/cm2/AA 0 1 +8.491 0.012 nan mag JOHNSON.B I/280B/ascc 0.036 2.17e-06 2.15e-06 4448.06 2.53928e-12 2.80651e-14 erg/s/cm2/AA 0 1 +8.274 0.013 nan mag JOHNSON.V I/280B/ascc 0.036 2.17e-06 2.15e-06 5504.67 1.79914e-12 2.15419e-14 erg/s/cm2/AA 0 1 +7.816 0.023 nan mag 2MASS.J II/246/out 0.118 -2.783e-05 1.715e-05 12412.1 2.28049e-13 4.83093e-15 erg/s/cm2/AA 0 1 +7.792 0.021 nan mag 2MASS.KS II/246/out 0.118 -2.783e-05 1.715e-05 21909.2 3.26974e-14 6.32423e-16 erg/s/cm2/AA 0 1 +7.825 0.042 nan mag 2MASS.H II/246/out 0.118 -2.783e-05 1.715e-05 16497.1 8.48652e-14 3.28288e-15 erg/s/cm2/AA 0 1 +8.272 0.017 nan mag GENEVA.V GCPD nan nan nan 5482.6 1.88047e-12 2.94435e-14 erg/s/cm2/AA 0 1 +1.8 0.004 nan mag GENEVA.G-B GCPD nan nan nan nan 0.669837 0.00669837 flux_ratio 1 0 +1.384 0.004 nan mag GENEVA.V1-B GCPD nan nan nan nan 0.749504 0.00749504 flux_ratio 1 0 +0.85 0.004 nan mag GENEVA.B1-B GCPD nan nan nan nan 1.05773 0.0105773 flux_ratio 1 0 +1.517 0.004 nan mag GENEVA.B2-B GCPD nan nan nan nan 0.946289 0.00946289 flux_ratio 1 0 +0.668 0.004 nan mag GENEVA.V-B GCPD nan nan nan nan 0.726008 0.00726008 flux_ratio 1 0 +0.599 0.004 nan mag GENEVA.U-B GCPD nan nan nan nan 1.13913 0.0113913 flux_ratio 1 0 +7.604 0.0174642 nan mag GENEVA.B GCPD nan nan nan 4200.85 2.59014e-12 4.16629e-14 erg/s/cm2/AA 0 1 +9.404 0.0179165 nan mag GENEVA.G GCPD nan nan nan 5765.89 1.73497e-12 2.863e-14 erg/s/cm2/AA 0 1 +8.988 0.0179165 nan mag GENEVA.V1 GCPD nan nan nan 5395.63 1.94132e-12 3.20351e-14 erg/s/cm2/AA 0 1 +8.454 0.0179165 nan mag GENEVA.B1 GCPD nan nan nan 4003.78 2.73968e-12 4.52092e-14 erg/s/cm2/AA 0 1 +9.121 0.0179165 nan mag GENEVA.B2 GCPD nan nan nan 4477.56 2.45102e-12 4.0446e-14 erg/s/cm2/AA 0 1 +8.203 0.0179165 nan mag GENEVA.U GCPD nan nan nan 3421.62 2.95052e-12 4.86885e-14 erg/s/cm2/AA 0 1 +8.27 nan nan mag JOHNSON.V GCPD nan nan nan 5504.67 1.80578e-12 1.80578e-13 erg/s/cm2/AA 0 1 +0.22 nan nan mag JOHNSON.B-V GCPD nan nan nan nan 1.40749 0.140749 flux_ratio 1 0 +-0.66 nan nan mag JOHNSON.U-B GCPD nan nan nan nan 1.21491 0.121491 flux_ratio 1 0 +8.49 nan nan mag JOHNSON.B GCPD nan nan nan 4448.06 2.54162e-12 2.54162e-13 erg/s/cm2/AA 0 1 +7.83 nan nan mag JOHNSON.U GCPD nan nan nan 3641.75 3.08783e-12 3.08783e-13 erg/s/cm2/AA 0 1 +-0.035 nan nan mag STROMGREN.M1 GCPD nan nan nan nan 0.954224 0.0954224 flux_ratio 1 0 +0.031 nan nan mag STROMGREN.C1 GCPD nan nan nan nan 0.91257 0.091257 flux_ratio 1 0 +0.259 nan nan mag STROMGREN.B-Y GCPD nan nan nan nan 1.25605 0.125605 flux_ratio 1 0 +-0.009 0.0478853 nan mag 2MASS.J-H II/246/out 0.118 -2.783e-05 1.715e-05 nan 2.68719 0.118516 flux_ratio 1 0 +-0.033 0.0469574 nan mag 2MASS.KS-H II/246/out 0.118 -2.783e-05 1.715e-05 nan 0.385286 0.0166634 flux_ratio 1 0 +-0.01 0.0412311 nan mag JOHNSON.J-H J/PASP/120/1128/catalog 0.02 1.017e-05 3.15e-06 nan 2.73514 0.103867 flux_ratio 1 0 +-0.04 0.0412311 nan mag JOHNSON.K-H J/PASP/120/1128/catalog 0.02 1.017e-05 3.15e-06 nan 0.375718 0.014268 flux_ratio 1 0 +0.209 0.0206155 nan mag TY]CHO2.BT-VT I/259/tyc2 0.042 7.17e-06 1.15e-06 nan 1.43014 0.027155 flux_ratio 1 0 \ No newline at end of file diff --git a/docs/source/sed/builder.rst b/docs/source/sed/builder.rst new file mode 100644 index 000000000..599d1488d --- /dev/null +++ b/docs/source/sed/builder.rst @@ -0,0 +1,481 @@ +To construct an SED, use the SED class. The functions defined in this module +are mainly convenience functions specifically for that class, but can be used +outside of the SED class if you know what you're doing. + +1. Retrieving and plotting photometry of a target +================================================= + +>>> mysed = SED('HD180642') +>>> mysed.get_photometry() +>>> mysed.plot_data() + +and call Pylab's ``show`` function to show to the screen: + +]]include figure]]ivs_sed_builder_example_photometry.png] + +Catch IndexErrors and TypeErrors in case no photometry is found. + +You can give a **search radius** to ``get_photometry`` via the keyword ``radius``. +The default value is 10 arcseconds for stars dimmer than 6th magnitude, and 60 +arcseconds for brighter stars. The best value of course depends on the density +of the field. + +>>> mysed.get_photometry(radius=5.) + +If your star's name is not recognised by any catalog, you can give coordinates +to look for photometry. In that case, the ID of the star given in the ``SED`` +command will not be used to search photometry (only to save the phot file): + +>>> mysed.get_photometry(ra=289.31167983,dec=1.05941685) + +Note that ``ra`` and ``dec`` are given in **degrees**. + +You best **switch on the logger** (see L{ivs.aux.loggers.get_basic_logger}) to see the progress: +sometimes, access to catalogs can take a long time (the GATOR sources are +typically slow). If one of the ``gator``, ``vizier`` or ``gcpd`` is impossibly slow +or the site is down, you can **include/exclude these sources** via the keywords +``include`` or ``exclude``, which take a list of strings (choose from ``gator``, +``vizier`` and/or ``gcpd``). For ViZieR, there is an extra option to change to +another mirror site via + +>>> vizier.change_mirror() + +The L{vizier.change_mirror} function cycles through all the mirrors continuously, +so sooner or later you will end up with the default one and repeat the cycle. + +>>> mysed.get_photometry(exclude=['gator']) + +The results will be written to the file **HD180642.phot**. An example content is: + +.. literalinclude:: HD180642.phot + +Once a .phot file is written and L{get_photometry} is called again for the same +target, the script will **not retrieve the photometry from the internet again**, +but will use the contents of the file instead. The purpose is minimizing network +traffic and maximizing speed. If you want to refresh the search, simply manually +delete the .phot file or set ``force=True`` when calling L{get_photometry}. + +The content of the .phot file is most easily read using the L{ivs.inout.ascii.read2recarray} +function. Be careful, as it contains both absolute fluxes as flux ratios. + +>>> data = ascii.read2recarray('HD180642.phot') + +Notice that in the ``.phot`` files, also a ``comment`` column is added. You can +find translation of some of the flags here (i.e. upper limit, extended source etc..), +or sometimes just additional remarks on variability etc. Not all catalogs have +this feature implemented, so you are still responsible yourself for checking +the quality of the photometry. + +The references to each source are given in the ``bibtex`` column. Simply call + +>>> mysed.save_bibtex() + +to convert those bibcodes to a ``.bib`` file. + +Using L{SED.plot_MW_side} and L{SED.plot_MW_top}, you can make a picture of where +your star is located with respect to the Milky Way and the Sun. With L{SED.plot_finderchart}, +you can check the location of your photometry, and also see if proper motions +etc are available. + +2. Where/what is my target? +=========================== + +To give you some visual information on the target, the following plotting +procedure might be of some help. + +To check whether the downloaded photometry is really belonging to the target, +instead of some neighbouring star (don't forget to set ``radius`` when looking +for photometry!), you can generate a finderchart with the location of the +downloaded photometry overplotted. On top of that, proper motion data is added +when available, as well as radial velocity data. When a distance is available, +the proper motion velocity will be converted to a true tangential velocity. + +>>> p = pl.figure();mysed.plot_finderchart(window_size=1) + +]]include figure]]ivs_sed_builder_finderchart.png] + +To know the location of your target wrt the Milky Way (assuming your target is +in the milky way), you can call + +>>> p = pl.figure();mysed.plot_MW_side() +>>> p = pl.figure();mysed.plot_MW_top() + +]]include figure]]ivs_sed_builder_MWside.png] + +]]include figure]]ivs_sed_builder_MWtop.png] + +3. SED fitting using a grid based approach +========================================== + +3.1 Single star +--------------- + +We make an SED of HD180642 by simply **exploring a whole grid of Kurucz models** +(constructed via L{fit.generate_grid}, iterated over via L{fit.igrid_search} and +evaluated with L{fit.stat_chi2}). The model with the best parameters is picked +out and we make a full SED with those parameters. + +>>> mysed = SED('HD180642') +>>> mysed.get_photometry() + +Now we have collected **all fluxes and colors**, but we do not want to use them +all: first, **fluxes and colors are not independent**, so you probably want to use +either only absolute fluxes or only colors (plus perhaps one absolute flux per +system to compute the angular diameter) (see L{SED.set_photometry_scheme}). Second, +**some photometry is better suited for fitting an SED than others**; typically IR +photometry does not add much to the fitting of massive stars, or it can be +contaminated with circumstellar material. Third, **some photometry is not so +reliable**, i.e. the measurements can have large systematic uncertainties due to +e.g. calibration effects, which are typically not included in the error bars. + +Currently, four standard schemes are implemented, which you can set via L{SED.set_photometry_scheme}: + +1. ``absolute``: use only absolute values +2. ``colors``: use only colors (no angular diameter values calculated) +3. ``combo``: use all colors and one absolute value per photometric system +4. ``irfm``: (infrared flux method) use colors for wavelengths shorter than + infrared wavelengths, and absolute values for systems in the infrared. The + infrared is defined as wavelength longer than 1 micron, but this can be + customized with the keyword ``infrared=(value,unit)`` in + L{SED.set_photometry_scheme}. + +Here, we chose to use colors and one absolute flux per system, but exclude IR +photometry (wavelength range above 2.5 micron), and some systems and colors which +we know are not so trustworthy: + +>>> mysed.set_photometry_scheme('combo') +>>> mysed.exclude(names=['STROMGREN.HBN-HBW','USNOB1','SDSS','DENIS','COUSINS','ANS','TD1'],wrange=(2.5e4,1e10)) + +You can L{include}/L{exclude} photoemtry based on name, wavelength range, source and index, +and only select absolute photometry or colors (L{include_abs},L{include_colors}). +When working in interactive mode, in particular the index is useful. Print the +current set of photometry to the screen with + +>>> print(photometry2str(mysed.master,color=True,index=True)) + +and you will see in green the included photometry, and in red the excluded photometry. +You will see that each column is preceded by an index, you can use these indices +to select/deselect the photometry. + +Speed up the fitting process by copying the model grids to the scratch disk + +>>> model.copy2scratch(z='*') + +Start the grid based fitting process and show some plots. We use 100000 randomly +distributed points over the grid: + +>>> mysed.igrid_search(points=100000) + +Delete the model grids from the scratch disk + +>>> model.clean_scratch() + +and make the plot + +>>> p = pl.figure() +>>> p = pl.subplot(131);mysed.plot_sed() +>>> p = pl.subplot(132);mysed.plot_grid(limit=None) +>>> p = pl.subplot(133);mysed.plot_grid(x='ebv',y='z',limit=None) + +]]include figure]]ivs_sed_builder_example_fitting01.png] + +The grid is a bit too coarse for our liking around the minimum, so we zoom in on +the results: + +>>> teffrange = mysed.results['igrid_search']['CI']['teffL'],mysed.results['igrid_search']['CI']['teffU'] +>>> loggrange = mysed.results['igrid_search']['CI']['loggL'],mysed.results['igrid_search']['CI']['loggU'] +>>> ebvrange = mysed.results['igrid_search']['CI']['ebvL'],mysed.results['igrid_search']['CI']['ebvU'] +>>> mysed.igrid_search(points=100000,teffrange=teffrange,loggrange=loggrange,ebvrange=ebvrange) + +and repeat the plot + +>>> p = pl.figure() +>>> p = pl.subplot(131);mysed.plot_sed(plot_deredded=True) +>>> p = pl.subplot(132);mysed.plot_grid(limit=None) +>>> p = pl.subplot(133);mysed.plot_grid(x='ebv',y='z',limit=None) + +]]include figure]]ivs_sed_builder_example_fitting02.png] + +You can automatically make plots of (most plotting functions take ``colors=True/False`` +as an argument so you can make e.g. the 'color' SED and 'absolute value' SED): + +1. the grid (see L{SED.plot_grid}) +2. the SED (see L{SED.plot_sed}) +3. the fit statistics (see L{SED.plot_chi2}) + +To change the grid, load the L{ivs.sed.model} module and call +L{ivs.sed.model.set_defaults} with appropriate arguments. See that module for +conventions on the grid structure when adding custom grids. + +To add arrays manually, i.e. not from the predefined set of internet catalogs, +use the L{SED.add_photometry_fromarrays} function. + +**Warning**: Be careful when interpreting the Chi2 results. In order to always have a +solution, the chi2 is rescaled so that the minimum equals 1, in the case the +probability of the best chi2-model is zero. The Chi2 rescaling factor I{f} mimicks +a rescaling of all errorbars with a factor I{sqrt(f)}, and does not discriminate +between systems (i.e., **all** errors are blown up). If the errorbars are +underestimated, it could be that the rescaling factor is also wrong, which means +that the true probability region can be larger or smaller! + +3.2 Binary star - ### W.I.P ### +------------------------------- + +The SED class can create SEDs for multiple stars as well. There are 2 options +available, the multiple SED fit which in theory can handle any number of stars, +and the binary SED fit which is for binaries only, and uses the mass of both +components to restrict the radii when combining two model SEDs. + +As an example we take the system PG1104+243, which consists of a subdwarf B star, +and a G2 type mainsequence star. The photometry from the standard catalogues that +are build in this class, is of to low quality, so we use photometry obtained from +U{the subdwarf database}. + +>>> mysed = SED('PG1104+243') + +>>> meas, e_meas, units, photbands, source = ascii.read2array('pg1104+243_sddb.phot', dtype=str) +>>> meas = np.array(meas, dtype=float) +>>> e_meas = np.array(e_meas, dtype=float) +>>> mysed.add_photometry_fromarrays(meas, e_meas, units, photbands, source) + +We use only the absolute fluxes + +>>> mysed.set_photometry_scheme('abs') + +For the main sequence component we use kurucz models with solar metalicity, and +for the sdB component tmap models. And we copy the model grids to the scratch disk +to speed up the process: + +>>> grid1 = dict(grid='kurucz',z=+0.0) +>>> grid2 = dict(grid='tmap') +>>> model.set_defaults_multiple(grid1,grid2) +>>> model.copy2scratch() + +The actual fitting. The second fit starts from the 95% probability intervals of +the first fit. + +>>> teff_ms = (5000,7000) +>>> teff_sdb = (25000,45000) +>>> logg_ms = (4.00,4.50) +>>> logg_sdb = (5.00,6.50) +>>> mysed.igrid_search(masses=(0.47,0.71) ,teffrange=(teff_ms,teff_fix),loggrange=(logg_ms,logg_sdb), ebvrange=(0.00,0.02), zrange=(0,0), points=2000000, type='binary') +>>> mysed.igrid_search(masses=(0.47,0.71) ,points=2000000, type='binary') + +Delete the used models from the scratch disk + +>>> model.clean_scratch() + +Plot the results + +>>> p = pl.figure() +>>> p = pl.subplot(131); mysed.plot_sed(plot_deredded=False) +>>> p = pl.subplot(132); mysed.plot_grid(x='teff', y='logg', limit=0.95) +>>> p = pl.subplot(133); mysed.plot_grid(x='teff-2', y='logg-2', limit=0.95) + +]]include figure]]ivs_sed_builder_example_fittingPG1104+243.png] + +3.3 Saving SED fits +------------------- + +You can save all the data to a multi-extension FITS file via + +>>> mysed.save_fits() + +This FITS file then contains all **measurements** (it includes the .phot file), +the **resulting SED**, the **confidence intervals of all parameters** and also the +**whole fitted grid**: in the above case, the extensions of the FITS file contain +the following information (we print part of each header):: + + EXTNAME = 'DATA ' + XTENSION= 'BINTABLE' / binary table extension + BITPIX = 8 / array data type + NAXIS = 2 / number of array dimensions + NAXIS1 = 270 / length of dimension 1 + NAXIS2 = 67 / length of dimension 2 + TTYPE1 = 'meas ' + TTYPE2 = 'e_meas ' + TTYPE3 = 'flag ' + TTYPE4 = 'unit ' + TTYPE5 = 'photband' + TTYPE6 = 'source ' + TTYPE7 = '_r ' + TTYPE8 = '_RAJ2000' + TTYPE9 = '_DEJ2000' + TTYPE10 = 'cwave ' + TTYPE11 = 'cmeas ' + TTYPE12 = 'e_cmeas ' + TTYPE13 = 'cunit ' + TTYPE14 = 'color ' + TTYPE15 = 'include ' + TTYPE16 = 'synflux ' + TTYPE17 = 'mod_eff_wave' + TTYPE18 = 'chi2 ' + + EXTNAME = 'MODEL ' + XTENSION= 'BINTABLE' / binary table extension + BITPIX = 8 / array data type + NAXIS = 2 / number of array dimensions + NAXIS1 = 24 / length of dimension 1 + NAXIS2 = 1221 / length of dimension 2 + TTYPE1 = 'wave ' + TUNIT1 = 'A ' + TTYPE2 = 'flux ' + TUNIT2 = 'erg/s/cm2/AA' + TTYPE3 = 'dered_flux' + TUNIT3 = 'erg/s/cm2/AA' + TEFFL = 23000.68377498454 + TEFF = 23644.49138963689 + TEFFU = 29999.33189058337 + LOGGL = 3.014445328877565 + LOGG = 4.984788506855546 + LOGGU = 4.996095055525759 + EBVL = 0.4703171900728142 + EBV = 0.4933185398871652 + EBVU = 0.5645144879211454 + ZL = -2.499929434601112 + Z = 0.4332336811329144 + ZU = 0.4999776537652627 + SCALEL = 2.028305798068863E-20 + SCALE = 2.444606991671813E-20 + SCALEU = 2.830281842143698E-20 + LABSL = 250.9613352757437 + LABS = 281.771013453664 + LABSU = 745.3149766772975 + CHISQL = 77.07958742733673 + CHISQ = 77.07958742733673 + CHISQU = 118.8587169011471 + CI_RAWL = 0.9999999513255379 + CI_RAW = 0.9999999513255379 + CI_RAWU = 0.9999999999999972 + CI_RED = 0.5401112973063139 + CI_REDL = 0.5401112973063139 + CI_REDU = 0.9500015229597392 + + EXTNAME = 'IGRID_SEARCH' + XTENSION= 'BINTABLE' / binary table extension + BITPIX = 8 / array data type + NAXIS = 2 / number of array dimensions + NAXIS1 = 80 / length of dimension 1 + NAXIS2 = 99996 / length of dimension 2 + TTYPE1 = 'teff ' + TTYPE2 = 'logg ' + TTYPE3 = 'ebv ' + TTYPE4 = 'z ' + TTYPE5 = 'chisq ' + TTYPE6 = 'scale ' + TTYPE7 = 'escale ' + TTYPE8 = 'Labs ' + TTYPE9 = 'CI_raw ' + TTYPE10 = 'CI_red ' + + +3.4 Loading SED fits ### BROKEN ### +------------------------------------ + +Unfortunately this is not yet working properly! + +Once saved, you can load the contents of the FITS file again into an SED object +via + +>>> mysed = SED('HD180642') +>>> mysed.load_fits() + +and then you can build all the plots again easily. You can of course use the +predefined plotting scripts to start a plot, and then later on change the +properties of the labels, legends etc... for higher quality plots or to better +suit your needs. + +4. Accessing the best fitting full SED model +============================================ + +You can access the full SED model that matches the parameters found by the +fitting routine via: + +>>> wavelength,flux,deredded_flux = mysed.get_best_model() + +Note that this model is retrieved after fitting, and was not in any way used +during the fitting. As a consequence, there could be small differences between +synthetic photometry calculated from this returned model and the synthetic +fluxes stored in ``mysed.results['igrid_search']['synflux']``, which is the +synthetic photometry coming from the interpolation of the grid of pre-interpolated +photometry. See the documentation of L{SED.get_model} for more information. + +5. Radii, distances and luminosities +==================================== + +5.1. Relations between quantities +--------------------------------- + +Most SED grids don't have the radius as a tunable model parameter. The scale +factor, which is available for all fitted models in the grid when at least one +absolute photometric point is included, is directly propertional to the angular +diameter. The following relations hold:: + +>> distance = radius / np.sqrt(scale) +>> radius = distance * np.sqrt(scale) + +Where ``radius`` and ``distance`` have equal units. The true absolute luminosity +(solar units) is related to the absolute luminosity from the SED (solar units) +via the radius (solar units):: + +>> L_abs_true = L_abs * radius**2 + +Finally, the angular diameter can be computed via:: + +>> 2*conversions.convert('sr','mas',scale) + +5.2. Seismic constraints +------------------------ + +If the star shows clear solar-like oscillations, you can use the nu_max give an +independent constraint on the surface gravity, given the effective temperature of +the model (you are free to give errors or not, just keep in mind that in the +former case, you will get an array of Uncertainties rather than floats):: + +>> teff = 4260.,'K' +>> nu_max = 38.90,0.86,'muHz' +>> logg_slo = conversions.derive_logg_slo(teff,nu_max,unit='[cm/s2']) + +If you have the large separation (l=0 modes) and the nu_max, you can get an +estimate of the radius:: + +>> Deltanu0 = 4.80,0.02,'muHz' +>> R_slo = conversions.derive_radius_slo(numax,Deltanu0,teff,unit='Rsol') + +Then from the radius, you can get both the absolute luminosity and distance to +your star. + +5.3. Parallaxes +--------------- + +From the parallax, you can get an estimate of the distance. This is, however, +dependent on some prior assumptions such as the shape of the galaxy and the +distribution of stars. To estimate the probability density value due to +a measured parallax of a star at particular distance, you can call L{distance.distprob}:: + +>> gal_latitude = 0.5 +>> plx = 3.14,0.5 +>> d_prob = distance.distprob(d,gal_latitude,plx) + +Using this distance, you can get an estimate for the radius of your star, and +thus also the absolute luminosity. + +5.4: Reddening constraints +-------------------------- + +An estimate of the reddening of a star at a particular distance may be obtained +with the L{extinctionmodels.findext} function. There are currently three +reddening maps available: Drimmel, Marshall and Arenou:: + +>> lng,lat = 120.,-0.5 +>> dist = 100. # pc +>> Rv = 3.1 +>> EBV = extinctionmodels.findext(lng,lat,model='drimmel',distance=dist)/Rv +>> EBV = extinctionmodels.findext(lng,lat,model='marshall',distance=dist)/Rv +>> EBV = extinctionmodels.findext(lng,lat,model='arenou',distance=dist)/Rv + +Reference/API +============= + +.. automodule:: sed.builder + :members: diff --git a/docs/source/sed/index.rst b/docs/source/sed/index.rst new file mode 100644 index 000000000..481f48e8e --- /dev/null +++ b/docs/source/sed/index.rst @@ -0,0 +1,6 @@ +SED documentation +================= + +.. toctree:: + + builder \ No newline at end of file diff --git a/docs/source/sed/model.rst b/docs/source/sed/model.rst new file mode 100644 index 000000000..43ad6d7e1 --- /dev/null +++ b/docs/source/sed/model.rst @@ -0,0 +1,2 @@ +.. automodule:: sed.model + :members: \ No newline at end of file diff --git a/gui/gui.py b/gui/gui.py new file mode 100644 index 000000000..2f091c113 --- /dev/null +++ b/gui/gui.py @@ -0,0 +1,790 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'gui.ui' +# +# Created by: PyQt5 UI code generator 5.6 +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore, QtGui, QtWidgets + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(1257, 842) + MainWindow.setIconSize(QtCore.QSize(22, 22)) + self.MainWindowFrame = QtWidgets.QWidget(MainWindow) + self.MainWindowFrame.setAutoFillBackground(False) + self.MainWindowFrame.setObjectName("MainWindowFrame") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.MainWindowFrame) + self.horizontalLayout.setObjectName("horizontalLayout") + self.SelectedData = QtWidgets.QTabWidget(self.MainWindowFrame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.SelectedData.sizePolicy().hasHeightForWidth()) + self.SelectedData.setSizePolicy(sizePolicy) + self.SelectedData.setMinimumSize(QtCore.QSize(300, 0)) + self.SelectedData.setObjectName("SelectedData") + self.SelectedDataFrame = QtWidgets.QWidget() + self.SelectedDataFrame.setObjectName("SelectedDataFrame") + self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.SelectedDataFrame) + self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_3.setObjectName("verticalLayout_3") + spacerItem = QtWidgets.QSpacerItem(20, 3, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) + self.verticalLayout_3.addItem(spacerItem) + self.label = QtWidgets.QLabel(self.SelectedDataFrame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) + self.label.setSizePolicy(sizePolicy) + self.label.setObjectName("label") + self.verticalLayout_3.addWidget(self.label) + self.label_2 = QtWidgets.QLabel(self.SelectedDataFrame) + self.label_2.setObjectName("label_2") + self.verticalLayout_3.addWidget(self.label_2) + self.Selectedpushbutton = QtWidgets.QPushButton(self.SelectedDataFrame) + palette = QtGui.QPalette() + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(247, 170, 171)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Light, brush) + brush = QtGui.QBrush(QtGui.QColor(251, 212, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Midlight, brush) + brush = QtGui.QBrush(QtGui.QColor(123, 85, 85)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Dark, brush) + brush = QtGui.QBrush(QtGui.QColor(165, 113, 114)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Mid, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Text, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.BrightText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(247, 170, 171)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Shadow, brush) + brush = QtGui.QBrush(QtGui.QColor(251, 212, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.AlternateBase, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipBase, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(247, 170, 171)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Light, brush) + brush = QtGui.QBrush(QtGui.QColor(251, 212, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Midlight, brush) + brush = QtGui.QBrush(QtGui.QColor(123, 85, 85)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Dark, brush) + brush = QtGui.QBrush(QtGui.QColor(165, 113, 114)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Mid, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Text, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.BrightText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(247, 170, 171)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Shadow, brush) + brush = QtGui.QBrush(QtGui.QColor(251, 212, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.AlternateBase, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipBase, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipText, brush) + brush = QtGui.QBrush(QtGui.QColor(123, 85, 85)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(247, 170, 171)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Light, brush) + brush = QtGui.QBrush(QtGui.QColor(251, 212, 213)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Midlight, brush) + brush = QtGui.QBrush(QtGui.QColor(123, 85, 85)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Dark, brush) + brush = QtGui.QBrush(QtGui.QColor(165, 113, 114)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Mid, brush) + brush = QtGui.QBrush(QtGui.QColor(123, 85, 85)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Text, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.BrightText, brush) + brush = QtGui.QBrush(QtGui.QColor(123, 85, 85)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush) + brush = QtGui.QBrush(QtGui.QColor(247, 170, 171)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(247, 170, 171)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Shadow, brush) + brush = QtGui.QBrush(QtGui.QColor(247, 170, 171)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.AlternateBase, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipBase, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipText, brush) + self.Selectedpushbutton.setPalette(palette) + self.Selectedpushbutton.setObjectName("Selectedpushbutton") + self.verticalLayout_3.addWidget(self.Selectedpushbutton) + spacerItem1 = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) + self.verticalLayout_3.addItem(spacerItem1) + self.SelectedDataTable = QtWidgets.QTableView(self.SelectedDataFrame) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.SelectedDataTable.sizePolicy().hasHeightForWidth()) + self.SelectedDataTable.setSizePolicy(sizePolicy) + self.SelectedDataTable.setFocusPolicy(QtCore.Qt.NoFocus) + self.SelectedDataTable.setFrameShape(QtWidgets.QFrame.NoFrame) + self.SelectedDataTable.setFrameShadow(QtWidgets.QFrame.Plain) + self.SelectedDataTable.setLineWidth(0) + self.SelectedDataTable.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) + self.SelectedDataTable.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.SelectedDataTable.setAlternatingRowColors(True) + self.SelectedDataTable.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self.SelectedDataTable.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems) + self.SelectedDataTable.setObjectName("SelectedDataTable") + self.SelectedDataTable.horizontalHeader().setCascadingSectionResizes(False) + self.SelectedDataTable.horizontalHeader().setDefaultSectionSize(100) + self.SelectedDataTable.horizontalHeader().setMinimumSectionSize(1) + self.SelectedDataTable.horizontalHeader().setSortIndicatorShown(False) + self.SelectedDataTable.horizontalHeader().setStretchLastSection(True) + self.SelectedDataTable.verticalHeader().setVisible(False) + self.SelectedDataTable.verticalHeader().setHighlightSections(False) + self.SelectedDataTable.verticalHeader().setSortIndicatorShown(False) + self.verticalLayout_3.addWidget(self.SelectedDataTable) + self.SelectedData.addTab(self.SelectedDataFrame, "") + self.horizontalLayout.addWidget(self.SelectedData) + self.GuiTabs = QtWidgets.QTabWidget(self.MainWindowFrame) + self.GuiTabs.setObjectName("GuiTabs") + self.FilterData = QtWidgets.QWidget() + self.FilterData.setObjectName("FilterData") + self.verticalLayout = QtWidgets.QVBoxLayout(self.FilterData) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setObjectName("verticalLayout") + self.SearchBox = QtWidgets.QFrame(self.FilterData) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.SearchBox.sizePolicy().hasHeightForWidth()) + self.SearchBox.setSizePolicy(sizePolicy) + self.SearchBox.setFrameShape(QtWidgets.QFrame.NoFrame) + self.SearchBox.setFrameShadow(QtWidgets.QFrame.Raised) + self.SearchBox.setLineWidth(0) + self.SearchBox.setObjectName("SearchBox") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.SearchBox) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem2) + self.ObjLabel = QtWidgets.QLabel(self.SearchBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.ObjLabel.sizePolicy().hasHeightForWidth()) + self.ObjLabel.setSizePolicy(sizePolicy) + self.ObjLabel.setLayoutDirection(QtCore.Qt.LeftToRight) + self.ObjLabel.setObjectName("ObjLabel") + self.horizontalLayout_2.addWidget(self.ObjLabel) + self.ObjLineEdit = QtWidgets.QLineEdit(self.SearchBox) + self.ObjLineEdit.setClearButtonEnabled(True) + self.ObjLineEdit.setObjectName("ObjLineEdit") + self.horizontalLayout_2.addWidget(self.ObjLineEdit) + self.ObserverLabel = QtWidgets.QLabel(self.SearchBox) + self.ObserverLabel.setObjectName("ObserverLabel") + self.horizontalLayout_2.addWidget(self.ObserverLabel) + self.ObserverLineEdit = QtWidgets.QLineEdit(self.SearchBox) + self.ObserverLineEdit.setClearButtonEnabled(True) + self.ObserverLineEdit.setObjectName("ObserverLineEdit") + self.horizontalLayout_2.addWidget(self.ObserverLineEdit) + self.ProgLabel = QtWidgets.QLabel(self.SearchBox) + self.ProgLabel.setObjectName("ProgLabel") + self.horizontalLayout_2.addWidget(self.ProgLabel) + self.ProgSpinBox = QtWidgets.QSpinBox(self.SearchBox) + self.ProgSpinBox.setAcceptDrops(False) + self.ProgSpinBox.setInputMethodHints(QtCore.Qt.ImhDigitsOnly) + self.ProgSpinBox.setWrapping(False) + self.ProgSpinBox.setFrame(True) + self.ProgSpinBox.setButtonSymbols(QtWidgets.QAbstractSpinBox.UpDownArrows) + self.ProgSpinBox.setCorrectionMode(QtWidgets.QAbstractSpinBox.CorrectToNearestValue) + self.ProgSpinBox.setMinimum(0) + self.ProgSpinBox.setMaximum(200) + self.ProgSpinBox.setProperty("value", 0) + self.ProgSpinBox.setObjectName("ProgSpinBox") + self.horizontalLayout_2.addWidget(self.ProgSpinBox) + spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem3) + self.SearchPushButton = QtWidgets.QPushButton(self.SearchBox) + palette = QtGui.QPalette() + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(157, 218, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Light, brush) + brush = QtGui.QBrush(QtGui.QColor(206, 236, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Midlight, brush) + brush = QtGui.QBrush(QtGui.QColor(78, 109, 127)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Dark, brush) + brush = QtGui.QBrush(QtGui.QColor(105, 145, 170)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Mid, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Text, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.BrightText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(157, 218, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Shadow, brush) + brush = QtGui.QBrush(QtGui.QColor(206, 236, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.AlternateBase, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipBase, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(157, 218, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Light, brush) + brush = QtGui.QBrush(QtGui.QColor(206, 236, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Midlight, brush) + brush = QtGui.QBrush(QtGui.QColor(78, 109, 127)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Dark, brush) + brush = QtGui.QBrush(QtGui.QColor(105, 145, 170)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Mid, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Text, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.BrightText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(157, 218, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Shadow, brush) + brush = QtGui.QBrush(QtGui.QColor(206, 236, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.AlternateBase, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipBase, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipText, brush) + brush = QtGui.QBrush(QtGui.QColor(78, 109, 127)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(157, 218, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Light, brush) + brush = QtGui.QBrush(QtGui.QColor(206, 236, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Midlight, brush) + brush = QtGui.QBrush(QtGui.QColor(78, 109, 127)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Dark, brush) + brush = QtGui.QBrush(QtGui.QColor(105, 145, 170)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Mid, brush) + brush = QtGui.QBrush(QtGui.QColor(78, 109, 127)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Text, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.BrightText, brush) + brush = QtGui.QBrush(QtGui.QColor(78, 109, 127)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush) + brush = QtGui.QBrush(QtGui.QColor(157, 218, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(157, 218, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Shadow, brush) + brush = QtGui.QBrush(QtGui.QColor(157, 218, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.AlternateBase, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipBase, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipText, brush) + self.SearchPushButton.setPalette(palette) + self.SearchPushButton.setObjectName("SearchPushButton") + self.horizontalLayout_2.addWidget(self.SearchPushButton) + spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem4) + self.verticalLayout.addWidget(self.SearchBox) + self.FilterAddButton = QtWidgets.QPushButton(self.FilterData) + palette = QtGui.QPalette() + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(198, 255, 208)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Light, brush) + brush = QtGui.QBrush(QtGui.QColor(226, 255, 231)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Midlight, brush) + brush = QtGui.QBrush(QtGui.QColor(99, 127, 104)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Dark, brush) + brush = QtGui.QBrush(QtGui.QColor(132, 170, 139)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Mid, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Text, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.BrightText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(198, 255, 208)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Shadow, brush) + brush = QtGui.QBrush(QtGui.QColor(226, 255, 231)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.AlternateBase, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipBase, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(198, 255, 208)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Light, brush) + brush = QtGui.QBrush(QtGui.QColor(226, 255, 231)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Midlight, brush) + brush = QtGui.QBrush(QtGui.QColor(99, 127, 104)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Dark, brush) + brush = QtGui.QBrush(QtGui.QColor(132, 170, 139)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Mid, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Text, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.BrightText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(198, 255, 208)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Shadow, brush) + brush = QtGui.QBrush(QtGui.QColor(226, 255, 231)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.AlternateBase, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipBase, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipText, brush) + brush = QtGui.QBrush(QtGui.QColor(99, 127, 104)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(198, 255, 208)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Light, brush) + brush = QtGui.QBrush(QtGui.QColor(226, 255, 231)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Midlight, brush) + brush = QtGui.QBrush(QtGui.QColor(99, 127, 104)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Dark, brush) + brush = QtGui.QBrush(QtGui.QColor(132, 170, 139)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Mid, brush) + brush = QtGui.QBrush(QtGui.QColor(99, 127, 104)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Text, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.BrightText, brush) + brush = QtGui.QBrush(QtGui.QColor(99, 127, 104)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush) + brush = QtGui.QBrush(QtGui.QColor(198, 255, 208)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(198, 255, 208)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Shadow, brush) + brush = QtGui.QBrush(QtGui.QColor(198, 255, 208)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.AlternateBase, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipBase, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipText, brush) + self.FilterAddButton.setPalette(palette) + self.FilterAddButton.setAutoFillBackground(False) + self.FilterAddButton.setFlat(False) + self.FilterAddButton.setObjectName("FilterAddButton") + self.verticalLayout.addWidget(self.FilterAddButton) + spacerItem5 = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) + self.verticalLayout.addItem(spacerItem5) + self.FiltertableView = QtWidgets.QTableView(self.FilterData) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.FiltertableView.sizePolicy().hasHeightForWidth()) + self.FiltertableView.setSizePolicy(sizePolicy) + self.FiltertableView.setFocusPolicy(QtCore.Qt.NoFocus) + self.FiltertableView.setAcceptDrops(False) + self.FiltertableView.setFrameShape(QtWidgets.QFrame.StyledPanel) + self.FiltertableView.setFrameShadow(QtWidgets.QFrame.Sunken) + self.FiltertableView.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustIgnored) + self.FiltertableView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.FiltertableView.setAlternatingRowColors(True) + self.FiltertableView.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + self.FiltertableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.FiltertableView.setSortingEnabled(True) + self.FiltertableView.setObjectName("FiltertableView") + self.FiltertableView.horizontalHeader().setCascadingSectionResizes(False) + self.FiltertableView.horizontalHeader().setSortIndicatorShown(True) + self.FiltertableView.horizontalHeader().setStretchLastSection(True) + self.FiltertableView.verticalHeader().setVisible(False) + self.FiltertableView.verticalHeader().setHighlightSections(False) + self.FiltertableView.verticalHeader().setSortIndicatorShown(False) + self.verticalLayout.addWidget(self.FiltertableView) + self.GuiTabs.addTab(self.FilterData, "") + self.PlotSpectra = QtWidgets.QWidget() + self.PlotSpectra.setObjectName("PlotSpectra") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.PlotSpectra) + self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.PlotButton = QtWidgets.QPushButton(self.PlotSpectra) + palette = QtGui.QPalette() + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(198, 255, 208)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Light, brush) + brush = QtGui.QBrush(QtGui.QColor(226, 255, 231)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Midlight, brush) + brush = QtGui.QBrush(QtGui.QColor(99, 127, 104)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Dark, brush) + brush = QtGui.QBrush(QtGui.QColor(132, 170, 139)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Mid, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Text, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.BrightText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(198, 255, 208)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Shadow, brush) + brush = QtGui.QBrush(QtGui.QColor(226, 255, 231)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.AlternateBase, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipBase, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(198, 255, 208)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Light, brush) + brush = QtGui.QBrush(QtGui.QColor(226, 255, 231)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Midlight, brush) + brush = QtGui.QBrush(QtGui.QColor(99, 127, 104)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Dark, brush) + brush = QtGui.QBrush(QtGui.QColor(132, 170, 139)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Mid, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Text, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.BrightText, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(198, 255, 208)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Shadow, brush) + brush = QtGui.QBrush(QtGui.QColor(226, 255, 231)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.AlternateBase, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipBase, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipText, brush) + brush = QtGui.QBrush(QtGui.QColor(99, 127, 104)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, brush) + brush = QtGui.QBrush(QtGui.QColor(198, 255, 208)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Light, brush) + brush = QtGui.QBrush(QtGui.QColor(226, 255, 231)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Midlight, brush) + brush = QtGui.QBrush(QtGui.QColor(99, 127, 104)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Dark, brush) + brush = QtGui.QBrush(QtGui.QColor(132, 170, 139)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Mid, brush) + brush = QtGui.QBrush(QtGui.QColor(99, 127, 104)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Text, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.BrightText, brush) + brush = QtGui.QBrush(QtGui.QColor(99, 127, 104)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush) + brush = QtGui.QBrush(QtGui.QColor(198, 255, 208)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) + brush = QtGui.QBrush(QtGui.QColor(198, 255, 208)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Shadow, brush) + brush = QtGui.QBrush(QtGui.QColor(198, 255, 208)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.AlternateBase, brush) + brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipBase, brush) + brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) + brush.setStyle(QtCore.Qt.SolidPattern) + palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipText, brush) + self.PlotButton.setPalette(palette) + self.PlotButton.setObjectName("PlotButton") + self.verticalLayout_2.addWidget(self.PlotButton) + self.matplotlib = QtWidgets.QWidget(self.PlotSpectra) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.matplotlib.sizePolicy().hasHeightForWidth()) + self.matplotlib.setSizePolicy(sizePolicy) + self.matplotlib.setAutoFillBackground(False) + self.matplotlib.setObjectName("matplotlib") + self.mpl_layout = QtWidgets.QVBoxLayout(self.matplotlib) + self.mpl_layout.setContentsMargins(0, 0, 0, 0) + self.mpl_layout.setObjectName("mpl_layout") + self.verticalLayout_2.addWidget(self.matplotlib) + self.GuiTabs.addTab(self.PlotSpectra, "") + self.horizontalLayout.addWidget(self.GuiTabs) + MainWindow.setCentralWidget(self.MainWindowFrame) + self.menuBar = QtWidgets.QMenuBar(MainWindow) + self.menuBar.setGeometry(QtCore.QRect(0, 0, 1257, 29)) + self.menuBar.setObjectName("menuBar") + self.menuFile = QtWidgets.QMenu(self.menuBar) + self.menuFile.setObjectName("menuFile") + MainWindow.setMenuBar(self.menuBar) + self.statusBar = QtWidgets.QStatusBar(MainWindow) + self.statusBar.setObjectName("statusBar") + MainWindow.setStatusBar(self.statusBar) + self.actionExit = QtWidgets.QAction(MainWindow) + self.actionExit.setMenuRole(QtWidgets.QAction.TextHeuristicRole) + self.actionExit.setObjectName("actionExit") + self.actionLoad_Data = QtWidgets.QAction(MainWindow) + self.actionLoad_Data.setObjectName("actionLoad_Data") + self.actionExit_2 = QtWidgets.QAction(MainWindow) + self.actionExit_2.setObjectName("actionExit_2") + self.menuFile.addAction(self.actionExit_2) + self.menuBar.addAction(self.menuFile.menuAction()) + + self.retranslateUi(MainWindow) + self.SelectedData.setCurrentIndex(0) + self.GuiTabs.setCurrentIndex(0) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) + self.label.setText(_translate("MainWindow", "

Highlighted files can also be removed

")) + self.label_2.setText(_translate("MainWindow", "

using DELETE key.

")) + self.Selectedpushbutton.setText(_translate("MainWindow", "Remove highlighted files")) + self.SelectedData.setTabText(self.SelectedData.indexOf(self.SelectedDataFrame), _translate("MainWindow", "Selected Data")) + self.ObjLabel.setText(_translate("MainWindow", "Object")) + self.ObserverLabel.setText(_translate("MainWindow", "Observer")) + self.ProgLabel.setText(_translate("MainWindow", "Prog ID")) + self.ProgSpinBox.setSpecialValueText(_translate("MainWindow", "None")) + self.SearchPushButton.setText(_translate("MainWindow", "Search")) + self.FilterAddButton.setText(_translate("MainWindow", " Add highlighted choices to the sidebar")) + self.GuiTabs.setTabText(self.GuiTabs.indexOf(self.FilterData), _translate("MainWindow", "Filter Data")) + self.PlotButton.setText(_translate("MainWindow", "Plot")) + self.GuiTabs.setTabText(self.GuiTabs.indexOf(self.PlotSpectra), _translate("MainWindow", "View Spectra")) + self.menuFile.setTitle(_translate("MainWindow", "F&ile")) + self.actionExit.setText(_translate("MainWindow", "&Exit")) + self.actionLoad_Data.setText(_translate("MainWindow", "&Load Data")) + self.actionExit_2.setText(_translate("MainWindow", "&Exit")) + diff --git a/gui/gui.ui b/gui/gui.ui new file mode 100644 index 000000000..c9c65384f --- /dev/null +++ b/gui/gui.ui @@ -0,0 +1,2151 @@ + + + MainWindow + + + + 0 + 0 + 1257 + 842 + + + + MainWindow + + + + 22 + 22 + + + + + false + + + + + + + 0 + 0 + + + + + 300 + 0 + + + + 0 + + + + Selected Data + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 3 + + + + + + + + + 0 + 0 + + + + <html><head/><body><p align="center">Highlighted files can also be removed</p></body></html> + + + + + + + <html><head/><body><p align="center">using DELETE key.</p></body></html> + + + + + + + + + + + + 0 + 0 + 0 + + + + + + + 247 + 170 + 171 + + + + + + + 255 + 255 + 255 + + + + + + + 251 + 212 + 213 + + + + + + + 123 + 85 + 85 + + + + + + + 165 + 113 + 114 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 170 + 171 + + + + + + + 0 + 0 + 0 + + + + + + + 251 + 212 + 213 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 0 + 0 + 0 + + + + + + + 247 + 170 + 171 + + + + + + + 255 + 255 + 255 + + + + + + + 251 + 212 + 213 + + + + + + + 123 + 85 + 85 + + + + + + + 165 + 113 + 114 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 170 + 171 + + + + + + + 0 + 0 + 0 + + + + + + + 251 + 212 + 213 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 123 + 85 + 85 + + + + + + + 247 + 170 + 171 + + + + + + + 255 + 255 + 255 + + + + + + + 251 + 212 + 213 + + + + + + + 123 + 85 + 85 + + + + + + + 165 + 113 + 114 + + + + + + + 123 + 85 + 85 + + + + + + + 255 + 255 + 255 + + + + + + + 123 + 85 + 85 + + + + + + + 247 + 170 + 171 + + + + + + + 247 + 170 + 171 + + + + + + + 0 + 0 + 0 + + + + + + + 247 + 170 + 171 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + Remove highlighted files + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 5 + + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectItems + + + false + + + 100 + + + 1 + + + false + + + true + + + false + + + false + + + false + + + + + + + + + + + 0 + + + + Filter Data + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + 0 + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Qt::LeftToRight + + + Object + + + + + + + true + + + + + + + Observer + + + + + + + true + + + + + + + Prog ID + + + + + + + false + + + Qt::ImhDigitsOnly + + + false + + + true + + + QAbstractSpinBox::UpDownArrows + + + None + + + QAbstractSpinBox::CorrectToNearestValue + + + 0 + + + 200 + + + 0 + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + + + + + + 0 + 0 + 0 + + + + + + + 157 + 218 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 206 + 236 + 255 + + + + + + + 78 + 109 + 127 + + + + + + + 105 + 145 + 170 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 157 + 218 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 206 + 236 + 255 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 0 + 0 + 0 + + + + + + + 157 + 218 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 206 + 236 + 255 + + + + + + + 78 + 109 + 127 + + + + + + + 105 + 145 + 170 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 157 + 218 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 206 + 236 + 255 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 78 + 109 + 127 + + + + + + + 157 + 218 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 206 + 236 + 255 + + + + + + + 78 + 109 + 127 + + + + + + + 105 + 145 + 170 + + + + + + + 78 + 109 + 127 + + + + + + + 255 + 255 + 255 + + + + + + + 78 + 109 + 127 + + + + + + + 157 + 218 + 255 + + + + + + + 157 + 218 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 157 + 218 + 255 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + Search + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + 0 + 0 + 0 + + + + + + + 198 + 255 + 208 + + + + + + + 255 + 255 + 255 + + + + + + + 226 + 255 + 231 + + + + + + + 99 + 127 + 104 + + + + + + + 132 + 170 + 139 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 198 + 255 + 208 + + + + + + + 0 + 0 + 0 + + + + + + + 226 + 255 + 231 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 0 + 0 + 0 + + + + + + + 198 + 255 + 208 + + + + + + + 255 + 255 + 255 + + + + + + + 226 + 255 + 231 + + + + + + + 99 + 127 + 104 + + + + + + + 132 + 170 + 139 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 198 + 255 + 208 + + + + + + + 0 + 0 + 0 + + + + + + + 226 + 255 + 231 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 99 + 127 + 104 + + + + + + + 198 + 255 + 208 + + + + + + + 255 + 255 + 255 + + + + + + + 226 + 255 + 231 + + + + + + + 99 + 127 + 104 + + + + + + + 132 + 170 + 139 + + + + + + + 99 + 127 + 104 + + + + + + + 255 + 255 + 255 + + + + + + + 99 + 127 + 104 + + + + + + + 198 + 255 + 208 + + + + + + + 198 + 255 + 208 + + + + + + + 0 + 0 + 0 + + + + + + + 198 + 255 + 208 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + false + + + Add highlighted choices to the sidebar + + + false + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 5 + + + + + + + + + 0 + 0 + + + + Qt::NoFocus + + + false + + + QFrame::StyledPanel + + + QFrame::Sunken + + + QAbstractScrollArea::AdjustIgnored + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + true + + + false + + + true + + + true + + + false + + + false + + + false + + + + + + + + View Spectra + + + + + + + + + + + 0 + 0 + 0 + + + + + + + 198 + 255 + 208 + + + + + + + 255 + 255 + 255 + + + + + + + 226 + 255 + 231 + + + + + + + 99 + 127 + 104 + + + + + + + 132 + 170 + 139 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 198 + 255 + 208 + + + + + + + 0 + 0 + 0 + + + + + + + 226 + 255 + 231 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 0 + 0 + 0 + + + + + + + 198 + 255 + 208 + + + + + + + 255 + 255 + 255 + + + + + + + 226 + 255 + 231 + + + + + + + 99 + 127 + 104 + + + + + + + 132 + 170 + 139 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 198 + 255 + 208 + + + + + + + 0 + 0 + 0 + + + + + + + 226 + 255 + 231 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + + 99 + 127 + 104 + + + + + + + 198 + 255 + 208 + + + + + + + 255 + 255 + 255 + + + + + + + 226 + 255 + 231 + + + + + + + 99 + 127 + 104 + + + + + + + 132 + 170 + 139 + + + + + + + 99 + 127 + 104 + + + + + + + 255 + 255 + 255 + + + + + + + 99 + 127 + 104 + + + + + + + 198 + 255 + 208 + + + + + + + 198 + 255 + 208 + + + + + + + 0 + 0 + 0 + + + + + + + 198 + 255 + 208 + + + + + + + 255 + 255 + 220 + + + + + + + 0 + 0 + 0 + + + + + + + + Plot + + + + + + + + 0 + 0 + + + + false + + + + + + + + + + + + + + 0 + 0 + 1257 + 29 + + + + + F&ile + + + + + + + + + &Exit + + + QAction::TextHeuristicRole + + + + + &Load Data + + + + + &Exit + + + + + + diff --git a/gui/main.py b/gui/main.py new file mode 100644 index 000000000..4df144e6e --- /dev/null +++ b/gui/main.py @@ -0,0 +1,188 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import sys +# import numpy as np +import pandas as pd + +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar +from matplotlib.figure import Figure +from PyQt5.QtWidgets import QApplication, QMainWindow +from PyQt5 import QtCore +from ivs.gui.gui import Ui_MainWindow +from ivs.inout.fits import read_spectrum +from ivs.gui.models import PandasModel, SelectionModel + + +class MainWindow(QMainWindow, Ui_MainWindow): + """ + Main GUI window: used to interactively view HERMES spectra. + + Command line usage: $ python main.py + + Current functionality: + - Use filter tab to select HERMES spectra + - Use plot tab to display matplotlib plot of highlighted spectra + + TODO: + - I/O functions for subselection + - Implement more filter functions for overview table (RA,DEC,TIME) + - Add FITS header/stellar/etc info into new panel below MPL canvas + - Integrate with SIMBAD (for search, expanded stellar info, etc) + - Integrate fitting/science functions into GUI + (i.e. interactive lambda selection for profile fitting, etc) + """ + def __init__(self, parent=None): + # Initialise the Main window + QMainWindow.__init__(self, parent=parent) + self.ui = Ui_MainWindow() + self.ui.setupUi(self) + + self.selected_data = [] + + # Load HERMES overview TSV file + self.load_overview(obs_only=True) + # Initialise Filter/Data overview + self.filtertab(self.overview) + # Initialise MPL figure and toolbar + self.add_mpl() + + # Connect Main window buttons to relevant functions + self.ui.FilterAddButton.clicked.connect(lambda: self.filter_add()) + self.ui.PlotButton.clicked.connect(lambda: self.update_plot()) + self.ui.SearchPushButton.clicked.connect(lambda: self.Objectfilter()) + self.ui.Selectedpushbutton.clicked.connect(lambda: self.Remove_selected()) + + def keyPressEvent(self, event): + + # Delete key used to remove highlighted spectra from selected sidebar + if event.key() == QtCore.Qt.Key_Delete: + self.Remove_selected() + + def Remove_selected(self): + # Get highlighted rows + idx = self.ui.SelectedDataTable.selectionModel().selectedRows() + + # Retrieve filename corresponding to highlighted rows + # ( TODO probably a nicer way to do this?) + filenames = [] + for idxxx in idx: + filenames.append(self.selectionmodel.data( + self.selectionmodel.index(idxxx.row(), 0))) + + # Iterate over highlighted files, remove rom selection list + for filename in filenames: + if len(self.selected_data): + if filename in self.selected_data: + self.selected_data.remove(filename) + continue + + # Regenerate selection model and tableview + self.selectionmodel = SelectionModel(self.selected_data) + self.ui.SelectedDataTable.setModel(self.selectionmodel) + + def load_overview(self, obs_only): + + # Load hermes tsv overview into pandas frame + self.overview = pd.read_csv('/STER/mercator/hermes/HermesFullDataOverview.tsv', + sep='\t', skiprows=2, header=None, + names=['unseq', 'prog_id', 'obsmode', 'bvcor', + 'observer', 'object', 'ra', 'dec', + 'bjd', 'exptime', 'pmtotal', 'date-avg', + 'airmass', 'filename']) + + # obsmode = HRF_OBJ + if obs_only: + self.overview = self.overview[self.overview['obsmode'] == 'HRF_OBJ'] + + def add_mpl(self): + # Initial MPL figure and toolbar + self.fig = Figure() + self.ax = self.fig.add_subplot(111) + self.canvas = FigureCanvas(self.fig) + self.toolbar = NavigationToolbar(self.canvas, self.ui.matplotlib, + coordinates=True) + # Add MPL canvas and toolbar to Main window + self.ui.mpl_layout.addWidget(self.toolbar) + self.ui.mpl_layout.addWidget(self.canvas) + self.canvas.draw() + + def update_plot(self): + + try: + # Retrieve first highlighted row from tableview + idx = self.ui.SelectedDataTable.selectionModel().selectedRows()[0] + self.fig.clf() + self.ax = self.fig.add_subplot(111) + filename = self.selectionmodel.data( + self.selectionmodel.index(idx.row(), 0)) + # Defaulting to 'log_merged_c' due to incomplete '_cf' + if 'raw' in filename: + extname = 'HRF_OBJ_ext_CosmicsRemoved_log_merged_c' + filename = filename.replace('raw', 'reduced') + filename = filename.replace('HRF_OBJ', extname) + # Load wav and flux of corresponding spectrum + wav, flux = read_spectrum(filename) + + # Refresh and replot figure + self.ax = self.fig.add_subplot(111) + self.ax.plot(wav, flux) + self.ax.set_title(filename) + self.canvas.draw() + except(AttributeError, IndexError): + pass + except(FileNotFoundError): + error_text = 'FileNotFoundError: \"' + filename + '\" not found...' + self.ui.statusBar.showMessage(error_text, 3000) + + def filtertab(self, input): + # Initialise overview table from pandas dataframe, and show the dialog + self.model = PandasModel(input) + self.ui.FiltertableView.setModel(self.model) + + def filter_add(self): + idx = self.ui.FiltertableView.selectionModel().selectedRows() + skiptotal = 0 + for idxxx in idx: + filename = self.model.data(self.model.index(idxxx.row(), 13)) + + if len(self.selected_data): + if filename in self.selected_data: + skiptotal += 1 + continue + self.selected_data.append(filename) + if len(self.selected_data): + self.selectionmodel = SelectionModel(self.selected_data) + self.ui.SelectedDataTable.setModel(self.selectionmodel) + + if skiptotal: + if skiptotal == len(idx): + statustext = str(skiptotal) + ' spectra already added...' + else: + statustext = (str(skiptotal) + ' spectra already added. ' + + str(len(idx)-skiptotal) + + ' new spectra added...') + elif len(idx): + statustext = str(len(idx))+' spectra addded...' + else: + statustext = '' + self.ui.statusBar.showMessage(statustext, 3000) + + def Objectfilter(self): + newdata = self.overview + if len(self.ui.ObjLineEdit.text()): + newdata = newdata[newdata['object'].str.contains( + self.ui.ObjLineEdit.text(), na=False, case=False)] + if len(self.ui.ObserverLineEdit.text()): + newdata = newdata[newdata['observer'].str.contains( + self.ui.ObserverLineEdit.text(), na=False, case=False)] + if self.ui.ProgSpinBox.value(): + newdata = newdata[newdata['prog_id'] == self.ui.ProgSpinBox.value()] + self.filtertab(newdata) + +if __name__ == "__main__": + app = QApplication(sys.argv) + w = MainWindow() + w.show() + sys.exit(app.exec_()) diff --git a/gui/models.py b/gui/models.py new file mode 100644 index 000000000..b4fe2b528 --- /dev/null +++ b/gui/models.py @@ -0,0 +1,78 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QAbstractTableModel, Qt +from PyQt5 import QtCore + + +class PandasModel(QAbstractTableModel): + """ + Class to populate a table view with a pandas dataframe + """ + def __init__(self, data, parent=None): + QAbstractTableModel.__init__(self, parent) + self._data = data + + def rowCount(self, parent=None): + return self._data.shape[0] + + def columnCount(self, parent=None): + return self._data.shape[1] + + def data(self, index, role=Qt.DisplayRole): + if index.isValid(): + if role == Qt.DisplayRole: + return str(self._data.iloc[index.row(), index.column()]) + return None + + def headerData(self, col, orientation, role): + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + return self._data.columns[col] + return None + + def flags(self, index): + flags = super(self.__class__, self).flags(index) + flags |= QtCore.Qt.ItemIsEditable + flags |= QtCore.Qt.ItemIsSelectable + flags |= QtCore.Qt.ItemIsEnabled + flags |= QtCore.Qt.ItemIsDragEnabled + flags |= QtCore.Qt.ItemIsDropEnabled + return flags + + def sort(self, Ncol, order): + """Sort table by given column number. + """ + try: + self.layoutAboutToBeChanged.emit() + self._data = self._data.sort_values(self._data.columns[Ncol], + ascending=not order) + self.layoutChanged.emit() + except Exception as e: + print(e) + + +class SelectionModel(QAbstractTableModel): + """ + Simple class to populate a table view with Selected data + """ + def __init__(self, list, parent=None): + QAbstractTableModel.__init__(self, parent) + self.headers = ['Filename'] + self.list = list + + def rowCount(self, parent=None): + return len(self.list) + + def columnCount(self, parent=None): + return 1 + + def data(self, index, role=Qt.DisplayRole): + if index.isValid(): + if role == Qt.DisplayRole: + return self.list[index.row()] + return None + + def headerData(self, col, orientation, role): + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + return self.headers[col] + return None diff --git a/inout/ascii.py b/inout/ascii.py index 688c5db83..4c70f8023 100644 --- a/inout/ascii.py +++ b/inout/ascii.py @@ -15,11 +15,11 @@ def read2list(filename,**kwargs): """ Load an ASCII file to list of lists. - + The comments and data go to two different lists. - + Also opens gzipped files. - + @param filename: name of file with the data @type filename: string @keyword commentchar: character(s) denoting comment rules @@ -38,64 +38,64 @@ def read2list(filename,**kwargs): splitchar = kwargs.get('splitchar',None) skip_empty = kwargs.get('skip_empty',True) skip_lines = kwargs.get('skip_lines',0) - + if os.path.splitext(filename)[1] == '.gz': - ff = gzip.open(filename) + ff = gzip.open(filename,mode='rt',encoding='utf-8') else: - ff = open(filename) - + ff = open(filename,encoding='utf-8') + data = [] # data comm = [] # comments - + line_nr = -1 - + #-- fixwidth split or character split? if splitchar is None or isinstance(splitchar,str): fixwidth = False else: fixwidth = True - + while 1: # might call read several times for a file line = ff.readline() if not line: break # end of file line_nr += 1 - if line_nr}. - + If you want to return a list of the columns instead of rows, just do - + C{>>> col1,col2,col3 = ascii.read2array(myfile).T} - + @param filename: name of file with the data @type filename: string @keyword dtype: type of numpy array (default: float) @@ -114,19 +114,19 @@ def read2array(filename,**kwargs): def read2recarray(filename,**kwargs): """ Load ASCII file to a numpy record array. - + For a list of extra keyword arguments, see C{}. - + IF dtypes is None, we have some room to automatically detect the contents of the columns. This is not implemented yet. - + the keyword 'dtype' should be equal to a list of tuples, e.g. - - C{dtype = [('col1','a10'),('col2','>f4'),..]} - + + C{dtype = [('col1','U10'),('col2','>f4'),..]} + @param filename: name of file with the data @type filename: string - @keyword dtype: dtypes of record array + @keyword dtype: dtypes of record array @type dtype: list of tuples @keyword return_comments: flag to return comments (default: False) @type return_comments: bool @@ -136,10 +136,10 @@ def read2recarray(filename,**kwargs): dtype = kwargs.get('dtype',None) return_comments = kwargs.get('return_comments',False) splitchar = kwargs.get('splitchar',None) - + #-- first read in as a normal array data,comm = read2list(filename,**kwargs) - + #-- if dtypes is None, we have some room to automatically detect the contents # of the columns. This is not fully implemented yet, and works only # if the second-to-last and last columns of the comments denote the @@ -147,7 +147,8 @@ def read2recarray(filename,**kwargs): if dtype is None: data = np.array(data,dtype=str).T header = comm[-2].replace('|',' ').split() - types = comm[-1].replace('|','').split() + types = comm[-1] + types = re.sub(r'(<|>|\||=)*(S|a)(\d)', r'U\3', types).split() dtype = [(head,typ) for head,typ in zip(header,types)] dtype = np.dtype(dtype) elif isinstance(dtype,list): @@ -157,17 +158,17 @@ def read2recarray(filename,**kwargs): elif isinstance(splitchar,list): types,lengths = fws2info(splitchar) dtype = [] - names = range(300) + names = list(range(300)) for i,(fmt,lng) in enumerate(zip(types,lengths)): if fmt.__name__=='str': dtype.append((str(names[i]),(fmt,lng))) else: dtype.append((str(names[i]),fmt)) dtype = np.dtype(dtype) - + #-- cast all columns to the specified type data = [np.cast[dtype[i]](data[i]) for i in range(len(data))] - + #-- and build the record array data = np.rec.array(data, dtype=dtype) return return_comments and (data,comm) or data @@ -178,14 +179,14 @@ def read2recarray(filename,**kwargs): def write_array(data, filename, **kwargs): """ Save a numpy array to an ASCII file. - + Add comments via keyword comments (a list of strings denoting every comment line). By default, the comment lines will be preceded by the C{commentchar}. If you want to override this behaviour, set C{commentchar=''}. - + If you give a record array, you can simply set C{header} to C{True} to write the header, instead of specifying a list of strings. - + @keyword header: optional header for column names @type header: list of str (or boolean for record arrays) @keyword comments: comment lines @@ -213,25 +214,27 @@ def write_array(data, filename, **kwargs): auto_width = kwargs.get('auto_width',False) formats = kwargs.get('formats',None) # use '%g' or '%f' or '%e' for writing floats automatically from record arrays with auto width - use_float = kwargs.get('use_float','%f') - + use_float = kwargs.get('use_float','%f') + #-- switch to rows first if a list of columns is given if not isinstance(data,np.ndarray): data = np.array(data) if not 'row' in axis0.lower(): data = data.T - + if formats is None: try: - formats = [('S' in str(data[col].dtype) and '%s' or use_float) for col in data.dtype.names] + formats = [('U' in str(data[col].dtype) and '%s' or use_float) for col in data.dtype.names] except TypeError: - formats = [('S' in str(col.dtype) and '%s' or '%s') for col in data.T] + formats = [('U' in str(col.dtype) and '%s' or '%s') for col in data.T] #-- determine width of columns: also take the header label into account col_widths = [] #-- for record arrays if auto_width is True and header==True: for fmt,head in zip(formats,data.dtype.names): - col_widths.append(max([len('%s'%(fmt)%(el)) for el in data[head]]+[len(head)])) + col_widths.append(max([len('%s'%(fmt)%(el)) for el in data[head]] + + [len(head)])) + #-- for normal arrays and specified header elif auto_width is True and header is not None: for i,head in enumerate(header): @@ -240,17 +243,17 @@ def write_array(data, filename, **kwargs): elif auto_width is True and header is not None: for i in range(data.shape[1]): col_widths.append(max([len('%s'%(formats[i])%(el)) for el in data[:,i]])) - + if header is True: col_fmts = [str(data.dtype[i]) for i in range(len(data.dtype))] header = data.dtype.names else: col_fmts = None - + ff = open(filename,mode) if comments is not None: ff.write('\n'.join(comments)+'\n') - + #-- WRITE HEADER #-- when header is desired and automatic width if header is not None and col_widths: @@ -258,13 +261,13 @@ def write_array(data, filename, **kwargs): #-- when header is desired elif header is not None: ff.write('#'+sep.join(header)+'\n') - + #-- WRITE COLUMN FORMATS if col_fmts is not None and col_widths: ff.write('#'+sep.join(['%%%s%ss'%(('s' in fmt and '-' or ''),cw)%(colfmt) for fmt,colfmt,cw in zip(formats,col_fmts,col_widths)])+'\n') elif col_fmts is not None: ff.write('#'+sep.join(['%%%ss'%('s' in fmt and '-' or '')%(colfmt) for fmt,colfmt in zip(formats,col_fmts)])+'\n') - + #-- WRITE DATA #-- with automatic width if col_widths: @@ -275,8 +278,8 @@ def write_array(data, filename, **kwargs): for row in data: ff.write(sep.join(['%s'%(col) for col in row])+'\n') ff.close() - - + + #} @@ -285,7 +288,7 @@ def write_array(data, filename, **kwargs): def fw2info(fixwidth): """ Convert fix width information to python information - + >>> fw2info('F13.4') ([float], [13]) >>> fw2info('I2') @@ -299,10 +302,10 @@ def fw2info(fixwidth): translator['I'] = int translator['F'] = float translator['A'] = str - + numbers = re.compile('[\d]*[\d\.*]+',re.IGNORECASE) formats = re.compile('[a-z]+',re.IGNORECASE) - + numbers = numbers.findall(fixwidth) formats = formats.findall(fixwidth) if len(numbers)==1: @@ -320,7 +323,7 @@ def fws2info(fixwidths): for iinfo in info: length += iinfo[1] length = [0]+[sum(length[:i+1]) for i in range(len(length))] return types,length - + def fw2python(line,fixwidths,missing_value=0): """ @@ -342,7 +345,7 @@ def fw2python(line,fixwidths,missing_value=0): def read_harps(filename): """ Return spectrum and header from a HARPS normalised file. - + @parameter filename: name of the file @type filename: str @return: wavelengths,flux,header @@ -360,17 +363,17 @@ def read_harps(filename): def read_mad(infile,add_dp=False,**kwargs): """ Reads .phot from MADROT or .nad from MAD. - + MAD is the nonadiabatic pulsation code from Marc-Antoine Dupret. - + MADROT is the extension for slowly rotating stars in the traditional approximation. - + This function serves a generic read function, it reads *.phot and *.nad files, both from MAD and MADROT. - + Returns list of dicts - + @param add_dp: add period spacings information @type add_dp: boolean @return: star info, blocks @@ -394,31 +397,31 @@ def read_mad(infile,add_dp=False,**kwargs): def read_photrot(infile,teff=None,logg=None): """ Reads .phot output file of MADROT. - + Returns list of dictionaries - + For quick reading, you can already give a range on teff and logg, and skip the rest of file. - + if there is a NAD file with the same name (but extension .nad instead of .phot), we read in the header of this file too for information on the star:: - - # M/M_sun = 1.40 Teff = 6510.2 Log (L/L_sun) = 0.5168 - # Log g = 4.2748 R/R_sun = 1.4283 age (y) = 2.3477E+08 - # X = 0.70 Z = 0.020 alphaconv = 1.80 overshoot = 0.40 - + + # M/M_sun = 1.40 Teff = 6510.2 Log (L/L_sun) = 0.5168 + # Log g = 4.2748 R/R_sun = 1.4283 age (y) = 2.3477E+08 + # X = 0.70 Z = 0.020 alphaconv = 1.80 overshoot = 0.40 + if the filename is given as C{M1.4_0.020_0.40_0.70_001-0097.phot}, we add information on mass, Z, overshoot, X and evolution number to star_info, except for those quantities already derived from a NAD file. - + One dictionary representes information about one pulsation mode. - + @param teff: range in effective temperature for file selection (value, sigma) @type teff: tuple @param logg: range in surface gravity for file selection (value, sigma) @type logg: tuple """ - dict_keys = ('l', 'm', 'par', 'n', 'f_cd_com', 'f_cd_obs', 'ltilde', 'fT', + dict_keys = ('l', 'm', 'par', 'n', 'f_cd_com', 'f_cd_obs', 'ltilde', 'fT', 'psiT', 'fg', 'psig', 'K', 'omega_puls', 'omega_rot') with open(infile,'r') as f: line = f.readline() @@ -427,7 +430,7 @@ def read_photrot(infile,teff=None,logg=None): logTeff, logg_, Fe_H = line.split() logTeff, logg_, Fe_H = float(logTeff), float(logg_), float(Fe_H) star_info = dict(teff=10**logTeff,logg=logg_,Fe_H=Fe_H) - + #-- read the NAD file if it exists: nadfile = os.path.splitext(infile)[0]+'.nad' if os.path.isfile(nadfile): @@ -435,12 +438,12 @@ def read_photrot(infile,teff=None,logg=None): else: pass # not implemented yet. - + #second line gives number of blocks line = f.readline() nr_b = int(line) blocks = [] - + #-- check for fundamental parameters valid_funds = True if teff is not None: @@ -449,7 +452,7 @@ def read_photrot(infile,teff=None,logg=None): if logg is not None: if not ((logg[0]-logg[1]) <= star_info['logg'] <= (logg[0]+logg[1])): valid_funds = False - print star_info['teff'],teff,valid_funds + print(star_info['teff'],teff,valid_funds) #loop over all blocks and store them pos = 0 fail = False @@ -478,7 +481,7 @@ def read_photrot(infile,teff=None,logg=None): #add spin parameter if not failed if not fail: d['s'] = 2*d['omega_rot']/d['omega_puls'] - + for key_ in ('l', 'm', 'par', 'n'): d[key_]=int(d[key_]) #create the lists to store B d['B']=[] @@ -495,24 +498,24 @@ def read_photrot(infile,teff=None,logg=None): if not fail: blocks.append(d) pos = 0 continue - + logger.debug("Read %d modes from MADROT %s (%d modes failed)"%(len(blocks),infile,nrfails)) - + return star_info,blocks - + def read_phot(photfile,teff=None,logg=None): """ Reads .phot output file of MAD. - + Returns list of dictionaries - + One dictionary representes information about one pulsation mode. """ dict_keys = ('l', 'fT','psiT', 'fg', 'psig') - + blocks = [] - + with open(photfile,'r') as f: fc = f.readlines() #first line gives teff, logg, XYZ, remember this as global information @@ -520,11 +523,11 @@ def read_phot(photfile,teff=None,logg=None): logTeff, logg = fc[0].split()[:2] logTeff, logg = float(logTeff), float(logg) star_info = dict(teff=10**logTeff,logg=logg) - + #second line gives number of blocks nr_b = int(fc[1]) blocks = [] - + #loop over all blocks and store them pos = 0 fail = False @@ -539,18 +542,18 @@ def read_phot(photfile,teff=None,logg=None): block['f_cd_com'] = 0 # no information on frequency block['omega_rot'] = 0 # no rotation blocks.append(block) - + logger.debug("Read %d modes from MAD %s"%(len(blocks),photfile)) return star_info,blocks - + def read_nad(nadfile,only_header=False): """ Reads .nad output file of MAD. - + Returns list of dictionaries One dictionary representes information about one pulsation mode. """ - + dict_keys = {'l':'l', 'm':'m', 'n':'n', @@ -572,19 +575,19 @@ def read_nad(nadfile,only_header=False): 'lifetime (d)':'lifetime', 'Q (d)':'Q'} # for non rotating nad star_info = {} - + blocks = [] myheader = None lines = 0 - + with open(nadfile,'r') as ff: - + while 1: lines += 1 line = ff.readline() if not line: break # end of file, quit if line.isspace(): continue # blank line, continue - + line = line.strip() if line[0]=='#' and lines<=3: # general header containing star info contents = [i for i in line[1:].split()] @@ -619,11 +622,11 @@ def read_nad(nadfile,only_header=False): #-- for rotating AND nonrotating nad files, omega_rot is not available thismode['omega_rot'] = 0 blocks.append(thismode) - + logger.debug("Read %d modes from MAD (nad) %s"%(len(blocks),nadfile)) return star_info,blocks -#} \ No newline at end of file +#} diff --git a/inout/database.py b/inout/database.py index 306905a2e..2a0de3e15 100644 --- a/inout/database.py +++ b/inout/database.py @@ -1,44 +1,43 @@ # -*- coding: utf-8 -*- """ -A simple interface to work with a database saved on the hard disk. +A simple interface to work with a database saved on the hard disk. Author: Robin Lombaert """ -import os -import cPickle +import pickle import time class Database(dict): - + ''' A database class. - - The class creates and manages a dictionary saved to the hard disk. - + + The class creates and manages a dictionary saved to the hard disk. + It functions as a python dictionary with the extra option of synchronizing - the database instance with the dictionary saved on the hard disk. - - No changes will be made to the hard disk copy, unless Database.sync() is + the database instance with the dictionary saved on the hard disk. + + No changes will be made to the hard disk copy, unless Database.sync() is called. - - Note that changes made on a deeper level than the (key,value) pairs of the - Database (for instance in the case where value is a dict() type itself) - will not be automatically taken into account when calling the sync() - method. The key for which the value has been changed on a deeper level has + + Note that changes made on a deeper level than the (key,value) pairs of the + Database (for instance in the case where value is a dict() type itself) + will not be automatically taken into account when calling the sync() + method. The key for which the value has been changed on a deeper level has to be added to the Database.__changed list by calling addChangedKey(key) manually. - + Running the Database.sync() method will not read the database from the hard - disk if no changes were made or if changes were made on a deeper level - only. In order to get the most recent version of the Database, without - having made any changes, use the .read() method. Note that if changes were + disk if no changes were made or if changes were made on a deeper level + only. In order to get the most recent version of the Database, without + having made any changes, use the .read() method. Note that if changes were made on a deeper level, they will be lost. - + Example: - + >>> import os >>> from ivs.inout import database >>> filename = 'mytest.db' @@ -50,7 +49,7 @@ class Database(dict): >>> db2 = database.Database(filename) >>> print db2['test'] 1 - >>> print db2['test2'] + >>> print db2['test2'] robin >>> db2['test'] = 2 >>> db2.sync() @@ -96,240 +95,240 @@ class Database(dict): >>> os.system('rm %s'%filename) 0 ''' - - + + def __init__(self,db_path): - + ''' Initializing a Database class. - - Upon initialization, the class will read the dictionary saved at the + + Upon initialization, the class will read the dictionary saved at the db_path given as a dictionary. - + Note that cPickle is used to write and read these dictionaries. - + If no database exists at db_path, a new dictionary will be created. - + @param db_path: The path to the database on the hard disk. @type db_path: string - + ''' - + super(Database, self).__init__() self.db_path = db_path self.read() self.__changed = [] self.__deleted = [] - - - + + + def __delitem__(self,key): - + ''' Delete a key from the database. - - This deletion is also done in the hard disk version of the database - when the sync() method is called. - + + This deletion is also done in the hard disk version of the database + when the sync() method is called. + This method can be called by using syntax: del db[key] - + @param key: a dict key that will be deleted from the Database in memory @type key: a type valid for a dict key - + ''' - + self.__deleted.append(key) return super(Database,self).__delitem__(key) - - - - + + + + def __setitem__(self,key,value): - + ''' - Set a dict key with value. - + Set a dict key with value. + This change is only added to the database saved on the hard disk when - the sync() method is called. - + the sync() method is called. + The key is added to the Database.__changed list. - + This method can be called by using syntax: db[key] = value - + @param key: a dict key that will be added to the Database in memory @type key: a type valid for a dict key @param key: value of the key to be added @type value: any - + ''' - + self.__changed.append(key) return super(Database,self).__setitem__(key,value) - - - + + + def setdefault(self,key,*args): - + ''' Return key's value, if present. Otherwise add key with value default - and return. - + and return. + Database.__changed is updated with the key if it is not present yet. - + @param key: the key to be returned and/or added. @type key: any valid dict() key - @param args: A default value added to the dict() if the key is not + @param args: A default value added to the dict() if the key is not present. If not specified, default defaults to None. @type args: any type @return: key's value or default - + ''' - - if not self.has_key(key): + + if key not in self: self.__changed.append(key) return super(Database,self).setdefault(key,*args) - - - + + + def pop(self,key,*args): - + ''' - If database has key, remove it from the database and return it, else - return default. - + If database has key, remove it from the database and return it, else + return default. + If both default is not given and key is not in the database, a KeyError - is raised. - - If deletion is successful, this change is only added to the database - saved on the hard disk when the sync() method is called. - + is raised. + + If deletion is successful, this change is only added to the database + saved on the hard disk when the sync() method is called. + The key is added to the Database.__deleted list, if present originally. - + @param key: a dict key that will be removed from the Database in memory @type key: a type valid for a dict key @param args: value of the key to be returned if key not in Database @type args: any @return: value for key, or default - + ''' - - if self.has_key(key): + + if key in self: self.__deleted.append(key) return super(Database,self).pop(key,*args) - - + + def popitem(self): - + ''' Remove and return an arbitrary (key, value) pair from the database. - + A KeyError is raised if the database has an empty dictionary. - - If removal is successful, this change is only added to the database - saved on the hard disk when the sync() method is called. - + + If removal is successful, this change is only added to the database + saved on the hard disk when the sync() method is called. + The removed key is added to the Database.__deleted list. - + @return: (key, value) pair from Database - + ''' - + (key,value) = super(Database,self).popitem() self.__deleted.append(key) return (key,value) - - - + + + def update(self,*args,**kwargs): - + ''' - - Update the database with new entries, as with a dictionary. - + + Update the database with new entries, as with a dictionary. + This update is not synched to the hard disk! Instead Database.__changed includes the changed keys so that the next sync will save these changes to the hard disk. - + @param args: A dictionary type object to update the Database. @type args: dict() @keyword kwargs: Any extra keywords are added as keys with their values. @type kwargs: any type that is allowed as a dict key type. - + ''' - - self.__changed.extend(kwargs.keys()) - self.__changed.extend(args[0].keys()) + + self.__changed.extend(list(kwargs.keys())) + self.__changed.extend(list(args[0].keys())) return super(Database,self).update(*args,**kwargs) - - - + + + def read(self): - + ''' Read the database from the hard disk. - - Whenever called, the database in memory is updated with the version + + Whenever called, the database in memory is updated with the version saved on the hard disk. - + Any changes made outside the session of this Database() instance will - be applied to the database in memory! - - Any changes made to existing keys in current memory before calling + be applied to the database in memory! + + Any changes made to existing keys in current memory before calling read() will be undone! Use sync() instead of read if you want to keep - current changes inside the session. - - If no database is present at the path given to Database() upon + current changes inside the session. + + If no database is present at the path given to Database() upon initialisation, a new Database is made by saving an empty dict() at the - requested location. - - Reading and saving of the database is done by cPickle-ing the dict(). - + requested location. + + Reading and saving of the database is done by cPickle-ing the dict(). + ''' - + try: dbfile = open(self.db_path,'r') while True: try: - db = cPickle.load(dbfile) + db = pickle.load(dbfile) break except ValueError: - print 'Loading database failed: ValueError ~ insecure '+\ - 'string pickle. Waiting 10 seconds and trying again.' + print('Loading database failed: ValueError ~ insecure '+\ + 'string pickle. Waiting 10 seconds and trying again.') time.sleep(10) dbfile.close() self.clear() super(Database,self).update(db) except IOError: - print 'No database present at %s. Creating a new one.'%self.db_path + print('No database present at %s. Creating a new one.'%self.db_path) self.__save() - - - + + + def sync(self): - - ''' - + + ''' + Update the database on the harddisk and in the memory. - - The database is read anew, ie updated with the hard disk version to + + The database is read anew, ie updated with the hard disk version to account for any changes made by a different program. Next, the changes made to the database in memory are applied, before saving the database to the hard disk again. - + Any items deleted from the database in memory will also be deleted from the version saved on the hard disk! - + The keys that are changed explicitly are all listed in self.__changed, - to which entries can be added manually using the addChangedKey method, + to which entries can be added manually using the addChangedKey method, or automatically by calling .update(), .__setitem__() or .setdefault(). - + ''' - + if self.__changed or self.__deleted: - current_db = dict([(k,v) - for k,v in self.items() + current_db = dict([(k,v) + for k,v in list(self.items()) if k in set(self.__changed)]) self.read() self.__deleted = list(set(self.__deleted)) @@ -341,75 +340,74 @@ def sync(self): super(Database,self).update(current_db) self.__save() self.__changed = [] - - - + + + def __save(self): - + ''' - - Save a database. - + + Save a database. + Only called by Database() internally. Use sync() to save the Database to the hard disk. - - Reading and saving of the database is done by cPickle-ing the dict(). - + + Reading and saving of the database is done by cPickle-ing the dict(). + ''' - + dbfile = open(self.db_path,'w') - cPickle.dump(self,dbfile) + pickle.dump(self,dbfile) dbfile.close() - - - + + + def addChangedKey(self,key): - + ''' Add a key to the list of changed keys in the database. - - This is useful if a change was made to an entry on a deeper level, + + This is useful if a change was made to an entry on a deeper level, meaning that the __set__() method of Database() is not called directly. - + If the key is not added to this list manually, it will not make it into the database on the hard disk when calling the sync() method. - + @param key: the key you want to include in the next sync() call. @type key: string - + ''' - + self.__changed.append(key) - - - + + + def getDeletedKeys(self): - + ''' Return a list of all keys that have been deleted from the database in memory. - + @return: list of keys @rtype: list ''' - + return self.__deleted - - - + + + def getChangedKeys(self): - + ''' Return a list of all keys that have been changed in the database in memory. - + @return: list of keys @rtype: list ''' - + return self.__changed - + if __name__ == "__main__": import doctest doctest.testmod() - \ No newline at end of file diff --git a/inout/fits.py b/inout/fits.py index 4b9d5f80c..2a9b06f63 100644 --- a/inout/fits.py +++ b/inout/fits.py @@ -2,7 +2,6 @@ """ Read and write FITS files. """ -import gzip import logging import os import astropy.io.fits as pf @@ -19,7 +18,7 @@ def read_spectrum(filename, return_header=False): """ Read a standard 1D spectrum from the primary HDU of a FITS file. - + @param filename: FITS filename @type filename: str @param return_header: return header information as dictionary @@ -29,7 +28,7 @@ def read_spectrum(filename, return_header=False): """ flux = pf.getdata(filename) header = pf.getheader(filename) - + #-- Make the equidistant wavelengthgrid using the Fits standard info # in the header ref_pix = int(header["CRPIX1"])-1 @@ -40,9 +39,9 @@ def read_spectrum(filename, return_header=False): #-- fix wavelengths for logarithmic sampling if 'ctype1' in header and header['CTYPE1']=='log(wavelength)': wave = np.exp(wave) - + logger.debug('Read spectrum %s'%(filename)) - + if return_header: return wave,flux,header else: @@ -53,14 +52,14 @@ def read_corot(fits_file, return_header=False, type_data='hel', remove_flagged=True): """ Read CoRoT data from a CoRoT FITS file. - + Both SISMO and EXO data are recognised and extracted accordingly. - + type_data is one of: - type_data='raw' - type_data='hel': heliocentric unequidistant - type_data='helreg': heliocentric equidistant - + @param fits_file: CoRoT FITS file name @type fits_file: string @param return_header: return header information as dictionary @@ -84,7 +83,7 @@ def read_corot(fits_file, return_header=False, type_data='hel', if return_header: header = fits_file_[0].header fits_file_.close() - + logger.debug('Read CoRoT SISMO file %s'%(fits_file)) elif fits_file_[0].header['hlfccdid'][0]=='E': times = fits_file_['bintable'].data.field('datehel') @@ -103,7 +102,7 @@ def read_corot(fits_file, return_header=False, type_data='hel', else: flux,error = fits_file_['bintable'].data.field('whiteflux'),fits_file_['bintable'].data.field('whitefluxdev') flags = fits_file_['bintable'].data.field('status') - + # remove flagged datapoints if asked if remove_flagged: keep1 = (flags==0) @@ -111,10 +110,10 @@ def read_corot(fits_file, return_header=False, type_data='hel', logger.info('Remove: flagged (%d) no valid error (%d) datapoints (%d)'%(len(keep1)-keep1.sum(),len(keep2)-keep2.sum(),len(keep1))) keep = keep1 & keep2 times,flux,error,flags = times[keep], flux[keep], error[keep], flags[keep] - + # convert times to heliocentric JD times = conversions.convert('MJD','JD',times,jtype='corot') - + if return_header: return times, flux, error, flags, header else: @@ -124,18 +123,18 @@ def read_corot(fits_file, return_header=False, type_data='hel', def read_fuse(ff,combine=True,return_header=False): """ Read FUSE spectrum. - + Modified JD: JD-2400000.5. - + Do 'EXPEND'-'EXPSTART' - + V_GEOCEN,V_HELIO - + ANO: all night only: data obtained during orbital night (highest SNR when airglow is not an issue) ALL: all: highest SNR with minimal airglow contamination - + Preference of ANO over ALL for science purpose. - + Use TTAGfcal files. """ ff = pf.open(ff) @@ -149,7 +148,7 @@ def read_fuse(ff,combine=True,return_header=False): fluxs.append(ff[seg].data.field('FLUX')) errrs.append(ff[seg].data.field('ERROR')) ff.close() - + if combine: waves = np.hstack(waves) fluxs = np.hstack(fluxs) @@ -158,23 +157,23 @@ def read_fuse(ff,combine=True,return_header=False): waves,fluxs,errrs = waves[sa],fluxs[sa],errrs[sa] keep = fluxs>0 waves,fluxs,errrs = waves[keep],fluxs[keep],errrs[keep] - - + + if return_header: return waves,fluxs,errrs,hdr else: return waves,fluxs,errrs - + def read_iue(filename,return_header=False): """ Read IUE spectrum - + Instrumental profiles: http://starbrite.jpl.nasa.gov/pds/viewInstrumentProfile.jsp?INSTRUMENT_ID=LWR&INSTRUMENT_HOST_ID=IUE - + Better only use .mxlo for reliable absolute calibration!! - + LWP - + - Large-aperture spectral resolution is best between 2700 and 2900 A with an average FWHM of 5.2 A and decreases to approximately 8.0 A on either side of this range. Small-aperture resolution is optimal @@ -182,14 +181,14 @@ def read_iue(filename,return_header=False): 8.1 A at the extreme wavelengths. SWP - + - The best resolution occurs around 1200 A, with a FWHM of 4.6 A in the large aperture and 3.0 A in the small aperture, and gradually worsens towards longer wavelengths: 6.7 A at 1900 A in the large aperture and 6.3 A in the small. On average, the small-aperture resolution is approximately 10% better than the large-aperture resolution. - + """ ff = pf.open(filename) header = ff[0].header @@ -215,7 +214,7 @@ def read_iue(filename,return_header=False): npoints = ff[1].data.field('npoints')[i] startpix = ff[1].data.field('startpix')[i] flux = flux[startpix:startpix+npoints+1] - error = error[startpix:startpix+npoints+1] + error = error[startpix:startpix+npoints+1] quality = quality[startpix:startpix+npoints+1] flux[quality!=0] = 0 nu0 = ff[1].data.field('wavelength')[i] @@ -230,11 +229,11 @@ def read_iue(filename,return_header=False): wavelength = np.hstack(orders_w) flux = np.hstack(orders_f) error = np.hstack(orders_e) - + wavelength = wavelength[flux!=0] error = error[flux!=0] flux = flux[flux!=0] - + logger.info("IUE spectrum %s read"%(filename)) ff.close() if not return_header: @@ -249,7 +248,7 @@ def read_iue(filename,return_header=False): def read2recarray(fits_file,ext=1,return_header=False): """ Read the contents of a FITS file to a record array. - + Should add a test that the strings were not chopped of... """ dtype_translator = dict(L=np.bool,D=np.float64,E=np.float32,J=np.int) @@ -263,14 +262,14 @@ def read2recarray(fits_file,ext=1,return_header=False): dtypes = [] for name,dtype in zip(names,formats): if 'A' in dtype: - dtypes.append((name,'S60')) + dtypes.append((name,'U60')) else: dtypes.append((name,dtype_translator[dtype])) dtypes = np.dtype(dtypes) data = [np.cast[dtypes[i]](data.field(name)) for i,name in enumerate(names)] data = np.rec.array(data,dtype=dtypes) header = {} - for key in ff[ext].header.keys(): + for key in list(ff[ext].header.keys()): if 'TTYPE' in key: continue if 'TUNIT' in key: continue if 'TFORM' in key: continue @@ -292,7 +291,7 @@ def read2recarray(fits_file,ext=1,return_header=False): def write_primary(filename,data=None,header_dict={}): """ Initiate a FITS file by writing to the primary HDU. - + If data is not given, a 1x1 zero array will be added. """ if data is None: @@ -303,19 +302,19 @@ def write_primary(filename,data=None,header_dict={}): hdulist.writeto(filename) hdulist.close() return filename - + def write_recarray(recarr,filename,header_dict={},units={},ext='new',close=True): """ Write or add a record array to a FITS file. - + If 'filename' refers to an existing file, the record array will be added (ext='new') to the HDUlist or replace an existing HDU (ext=integer). Else, a new file will be created. - + Units can be given as a dictionary with keys the same names as the column names of the record array. - + A header_dictionary can be given, it is used to update an existing header or create a new one if the extension is new. """ @@ -325,38 +324,40 @@ def write_recarray(recarr,filename,header_dict={},units={},ext='new',close=True) hdulist = pf.HDUList([pf.PrimaryHDU(primary)]) hdulist.writeto(filename) hdulist.close() - + if is_file or isinstance(filename,str): hdulist = pf.open(filename,mode='update') else: hdulist = filename - - + #-- create the table HDU cols = [] for i,name in enumerate(recarr.dtype.names): format = recarr.dtype[i].str.lower().replace('|','').replace('>','') format = format.replace('b1','L').replace('<','') if 's' in format: # Changes to be compatible with Pyfits version 3.3 - format = format.replace('s','') + 'A' # Changes to be compatible with Pyfits version 3.3 + format = format.replace('s','') + 'A' + if 'u' in format: + format = format.replace('u','') + 'A' + # Changes to be compatible with Pyfits version 3.3 unit = name in units and units[name] or 'NA' cols.append(pf.Column(name=name,format=format,array=recarr[name],unit=unit)) tbhdu = pf.BinTableHDU.from_columns(pf.ColDefs(cols)) - + #-- take care of the header: if len(header_dict): for key in header_dict: - if (len(key)>8) and (not key in tbhdu.header.keys()) and (not key[:9]=='HIERARCH'): + if (len(key)>8) and (not key in list(tbhdu.header.keys())) and (not key[:9]=='HIERARCH'): key_ = 'HIERARCH '+key else: key_ = key tbhdu.header[key_] = header_dict[key] if ext!='new': tbhdu.header['EXTNAME'] = ext - - + + # put it in the right place - extnames = [iext.header['EXTNAME'] for iext in hdulist if ('extname' in iext.header.keys()) or ('EXTNAME' in iext.header.keys())] + extnames = [iext.header['EXTNAME'] for iext in hdulist if ('extname' in list(iext.header.keys())) or ('EXTNAME' in list(iext.header.keys()))] if ext=='new' or not ext in extnames: logger.info('Creating new extension %s'%(ext)) hdulist.append(tbhdu) @@ -364,7 +365,7 @@ def write_recarray(recarr,filename,header_dict={},units={},ext='new',close=True) else: logger.info('Overwriting existing extension %s'%(ext)) hdulist[ext] = tbhdu - + if close: hdulist.close() return filename @@ -374,17 +375,17 @@ def write_recarray(recarr,filename,header_dict={},units={},ext='new',close=True) def write_array(arr,filename,names=(),units=(),header_dict={},ext='new',close=True): """ Write or add an array to a FITS file. - + If 'filename' refers to an existing file, the list of arrays will be added (ext='new') to the HDUlist or replace an existing HDU (ext=integer). Else, a new file will be created. - + Names and units should be given as a list of strings, in the same order as the list of arrays. - + A header_dictionary can be given, it is used to update an existing header or create a new one if the extension is new. - + Instead of writing the file, you can give a hdulist and append to it. Supply a HDUList for 'filename', and set close=False """ @@ -392,12 +393,12 @@ def write_array(arr,filename,names=(),units=(),header_dict={},ext='new',close=Tr primary = np.array([[0]]) hdulist = pf.HDUList([pf.PrimaryHDU(primary)]) hdulist.writeto(filename) - + if isinstance(filename,str): hdulist = pf.open(filename,mode='update') else: hdulist = filename - + #-- create the table HDU cols = [] for i,name in enumerate(names): @@ -413,19 +414,19 @@ def write_array(arr,filename,names=(),units=(),header_dict={},ext='new',close=Tr unit = 'NA' cols.append(pf.Column(name=name,format=format,array=arr[i],unit=unit)) tbhdu = pf.BinTableHDU.from_columns(pf.ColDefs(cols)) # Changes to be compatible with Pyfits version 3.3 - + # put it in the right place if ext=='new' or ext==len(hdulist): hdulist.append(tbhdu) ext = -1 else: hdulist[ext] = tbhdu - + #-- take care of the header: if len(header_dict): for key in header_dict: hdulist[ext].header[key] = header_dict[key] - + if close: hdulist.close() else: diff --git a/inout/hdf5.py b/inout/hdf5.py index 9cb3d139e..f79bfdcc1 100644 --- a/inout/hdf5.py +++ b/inout/hdf5.py @@ -4,7 +4,7 @@ HDF5 is a data model, library, and file format for storing and managing data. It supports an unlimited variety of datatypes, and is designed for flexible and efficient I/O and for -high volume and complex data. HDF5 is portable and is extensible, allowing applications +high volume and complex data. HDF5 is portable and is extensible, allowing applications to evolve in their use of HDF5. The HDF5 Technology suite includes tools and applications for managing, manipulating, viewing, and analyzing data in the HDF5 format. http://alfven.org/wp/hdf5-for-python/ @@ -23,21 +23,21 @@ def read2dict(filename): """ Read the filestructure of a hdf5 file to a dictionary. - + @param filename: the name of the hdf5 file to read @type filename: str @return: dictionary with read filestructure @rtype: dict """ - + if not os.path.isfile(filename): logger.error('The file you try to read does not exist!') raise IOError - + def read_rec(hdf): """ recusively read the hdf5 file """ res = {} - for name,grp in hdf.items(): + for name,grp in list(hdf.items()): #-- read the subgroups and datasets if hasattr(grp, 'items'): # in case of a group, read the group into a new dictionary key @@ -45,17 +45,17 @@ def read_rec(hdf): else: # in case of dataset, read the value res[name] = grp.value - + #-- read all the attributes - for name, atr in hdf.attrs.iteritems(): + for name, atr in hdf.attrs.items(): res[name] = atr - + return res - + hdf = h5py.File(filename, 'r') result = read_rec(hdf) hdf.close() - + return result #} @@ -66,12 +66,12 @@ def write_dict(data, filename, update=True, attr_types=[]): """ Write the content of a dictionary to a hdf5 file. The dictionary can contain other nested dictionaries, this file stucture will be maintained in the saved hdf5 file. - - Pay attention to the fact that the data type of lists might change when writing to + + Pay attention to the fact that the data type of lists might change when writing to hdf5. Lists are stored as numpy arrays, thus all items in a list are converted to the same type: ['bla', 1, 24.5] will become ['bla', '1', '24.5']. Upt till now there is nothing in place to check this, or correct it when reading a hdf5 file. - + @param data: the dictionary to write to file @type data: dict @param filename: the name of the hdf5 file to write to @@ -82,30 +82,30 @@ def write_dict(data, filename, update=True, attr_types=[]): a dataset. (standard everything is saved as dataset.) @type attr_types: List of types """ - + if not update and os.path.isfile(filename): os.remove(filename) - + def save_rec(data, hdf): """ recusively save a dictionary """ - for key in data.keys(): - + for key in list(data.keys()): + if type(data[key]) == dict: # if part is dictionary: add 1 level and save dictionary in new level if not key in hdf: hdf.create_group(key) save_rec(data[key], hdf[key]) - + elif type(data[key]) in attr_types: # save data as attribute hdf.attrs[key] = data[key] - + else: # other data is stored as datasets if key in hdf: del hdf[key] hdf.create_dataset(key, data=data[key]) - + hdf = h5py.File(filename) save_rec(data, hdf) hdf.close() diff --git a/inout/http.py b/inout/http.py index 27df41799..625683941 100644 --- a/inout/http.py +++ b/inout/http.py @@ -1,21 +1,20 @@ """ Read or download files from the internet. """ -import urllib -from ivs.aux import decorators +import urllib.request, urllib.parse, urllib.error #@decorators.retry_http(3) def download(link,filename=None): """ Download a file from a link. - + If you want to download the contents to a file, supply C{filename}. The function will return that filename as a check. - + If you want to read the contents immediately from the url, just give the link, and a fileobject B{and} the url object will be returned. Remember to close the url after finishing reading! - + @parameter link: the url of the file @type link: string @parameter filename: the name of the file to write to (optional) @@ -23,7 +22,7 @@ def download(link,filename=None): @return: output filename(, url object) @rtype: string(, FancyURLopener) """ - url = urllib.FancyURLopener() + url = urllib.request.FancyURLopener() myfile,msg = url.retrieve(link,filename=filename) if filename is not None: url.close() diff --git a/inout/testIO.py b/inout/testIO.py index c681ceea6..8d5afa75c 100644 --- a/inout/testIO.py +++ b/inout/testIO.py @@ -6,74 +6,74 @@ import unittest class HDF5TestCase(unittest.TestCase): - + def testWriteDict(self): """ inout.hdf5.write_dict() """ data = {} data['number'] = 12.30 data['list'] = [45, 78.36] data['string'] = 'This is a piece of text' - + set1 = {} set1['grid'] = np.random.uniform(size=(10,10)) set1['x'] = np.linspace(0,10,num=10) set1['y'] = np.linspace(-5,5,num=10) set1['subset'] = {'w1':10.0, 'w2':12.0, 'w3':13.0} data['set1'] = set1 - + set2 = {} set2['grid'] = np.random.uniform(size=(10,10)) set2['x'] = np.linspace(-10,0,num=10) set2['y'] = np.linspace(0,5,num=10) set2['subset'] = {'w1':1, 'w2':2, 'w3':3, 'label':'again some text'} data['set2'] = set2 - + hdf5.write_dict(data, 'test.hdf5', update=False, attr_types=[float, int, str, list]) - + hdf = h5py.File('test.hdf5', 'r') - + self.assertTrue('set1' in hdf) self.assertTrue('set2' in hdf) self.assertTrue('number' in hdf.attrs) self.assertTrue('string' in hdf.attrs) self.assertTrue('list' in hdf.attrs) - + self.assertEqual(hdf.attrs['number'], data['number']) self.assertEqual(hdf.attrs['string'], data['string']) self.assertListEqual(hdf.attrs['list'].tolist(), data['list']) - + self.assertTrue(np.all(hdf['set1']['grid'].value == set1['grid'])) self.assertTrue(np.all(hdf['set1']['x'].value == set1['x'])) self.assertTrue(np.all(hdf['set1']['y'].value == set1['y'])) - + self.assertEqual(hdf['set1']['subset'].attrs['w1'], 10.0) self.assertEqual(hdf['set1']['subset'].attrs['w2'], 12.0) self.assertEqual(hdf['set1']['subset'].attrs['w3'], 13.0) - + self.assertTrue(np.all(hdf['set2']['grid'].value == set2['grid'])) self.assertTrue(np.all(hdf['set2']['x'].value == set2['x'])) self.assertTrue(np.all(hdf['set2']['y'].value == set2['y'])) - + self.assertEqual(hdf['set2']['subset'].attrs['w1'], 1) self.assertEqual(hdf['set2']['subset'].attrs['w2'], 2) self.assertEqual(hdf['set2']['subset'].attrs['w3'], 3) - + hdf.close() - + if os.path.isfile('test.hdf5'): os.remove('test.hdf5') - + def testReadDict(self): """ inout.hdf5.read2dict() """ - + if os.path.isfile('test.hdf5'): os.remove('test.hdf5') hdf = h5py.File('test.hdf5', 'w') - + hdf.create_group('set1') hdf.create_group('set2') hdf.create_dataset('list', data=[12,124.54]) - + grid1 = np.random.uniform(size=(10,10)) hdf['set1'].create_dataset('grid', data = grid1) hdf['set1'].attrs['w1'] = 'this is a label' @@ -81,30 +81,29 @@ def testReadDict(self): hdf['set1']['subset'].attrs['w1'] = 15.2 hdf['set1']['subset'].attrs['w2'] = -1000000000000 hdf['set1']['subset'].attrs['w3'] = 'yet another text string' - + grid2 = np.random.uniform(size=(10,10)) hdf['set2'].create_dataset('grid', data = grid2) - + hdf.close() - + data = hdf5.read2dict('test.hdf5') - + self.assertTrue('set1' in data) self.assertTrue('set2' in data) self.assertTrue('list' in data) self.assertListEqual(data['list'].tolist(), [12,124.54]) - + self.assertTrue(np.all(data['set1']['grid'] == grid1)) self.assertEqual(data['set1']['w1'], 'this is a label') - + self.assertEqual(data['set1']['subset']['w1'], 15.2) self.assertEqual(data['set1']['subset']['w2'], -1000000000000) self.assertEqual(data['set1']['subset']['w3'], 'yet another text string') self.assertTrue(np.all(data['set2']['grid'] == grid2)) - + if os.path.isfile('test.hdf5'): os.remove('test.hdf5') - - - \ No newline at end of file + + diff --git a/makedoc.py b/makedoc.py index 3e29b4a61..3e2187105 100644 --- a/makedoc.py +++ b/makedoc.py @@ -15,96 +15,87 @@ $:> python makedoc.py -h """ -#-- import necessary modules +# -- import necessary modules import os import subprocess import shutil import glob import webbrowser -import sys import argparse from ivs.aux import termtools -#-- skip documentation generation of the following third party modules: -skip = ['uncertainties','lmfit'] - -#-- do you want to immediately show the contents in your default webbrowser each -# time the documentation is generated (False, 'current tab' or 'new tab')? -parser = argparse.ArgumentParser(description='Build IvS Python repository documentation') -parser.add_argument('-o','--open',action='store_true',help='Open documentation in webbrowser after creation') -parser.add_argument('-v','--verbose',action='store_true',help='Increase output verbosity in Epydoc') -parser.add_argument('-w','--width',type=int,default=75,help='Width of images (percentage)') +# -- skip documentation generation of the following third party modules: +skip = ['lmfit'] + +# -- do you want to immediately show the contents in your default web browser +# each time the documentation is generated +# (False, 'current tab' or 'new tab')? +parser = argparse.ArgumentParser( + description='Build IvS Python repository documentation') +parser.add_argument('-o', '--open', action='store_true', + help='Open documentation in webbrowser after creation') +parser.add_argument('-v', '--verbose', action='store_true', + help='Increase output verbosity in Epydoc') +parser.add_argument('-w', '--width', type=int, default=75, + help='Width of images (percentage)') args = parser.parse_args() -#-- remember the current working directory; you can generate the documentation +# -- remember the current working directory; you can generate the documentation # in any directory if you really want to. this_dir = os.path.dirname(os.path.abspath(__file__)) -#-- collect all files and directories for which to generate documentation -output = subprocess.check_output('git ls-files',shell=True) +# -- collect all files and directories for which to generate documentation +output = subprocess.check_output('git ls-files', shell=True).decode() alldirs = output.strip().split('\n') -#-- we do a quick selection here since we don't want to generate documentation +# -- we do a quick selection here since we don't want to generate documentation # of third-party modules that are installed alongside the repository -alldirs = [ff for ff in alldirs if os.path.splitext(ff)[1]=='.py' and not os.path.basename(ff)[:4]=='test'] +alldirs = [ff for ff in alldirs if os.path.splitext(ff)[1] == '.py' + and not os.path.basename(ff)[:4] == 'test'] + for iskip in skip: - alldirs = [ff for ff in alldirs if not iskip in ff] + alldirs = [ff for ff in alldirs if iskip not in ff] + +# -- build documentation +cmd = 'epydoc --html '+" ".join(alldirs) + ' -o doc/html --parse-only' + \ + ' --graph all {}'.format('-v' if args.verbose else '') -#-- build documentation -cmd = 'epydoc --html '+" ".join(alldirs)+\ - ' -o doc/html --parse-only --graph all {}'.format('-v' if args.verbose else '') print("Building documentation using the command:\n {}".format(cmd)) -flag = subprocess.call(cmd,shell=True) -#-- check if all went well +flag = subprocess.call(cmd, shell=True) + +# -- check if all went well if flag: print("Could not execute command, do you have epydoc installed?") raise SystemExit -#-- Epydoc cannot insert images in the HTML code itself, we do this manually. +# -- Epydoc cannot insert images in the HTML code itself, we do this manually. # for this, we look into the HTML code and replace all ]include figure] # occurrences with the HTML code for image insertion. We copy the html file -# to a temporary copy, change the html code, save it and replace the old file. +# to a temporary copy, change the html code, save it and replace the old file if not os.path.isdir('doc/images'): print("You don't have an image directory; images are not inserted") raise SystemExit -#-- we can't do everything in a generic way just yet, we first treat the -# exception to the general rule. -'''shutil.move('doc/html/ivs.timeseries.freqanalyse-module.html','doc/html/ivs.timeseries.freqanalyse-module_.html') -ff = open('doc/html/ivs.timeseries.freqanalyse-module_.html','r') -oo = open('doc/html/ivs.timeseries.freqanalyse-module.html','w') -ESC = chr(27) -for line in ff.readlines(): - if r'p = pl.ylim(0,0.018)' in line: - oo.write(line+'\n\n') - oo.write(r"
[image example]
"+'\n\n') - #-- save cursor, remove line, print and reset cursor - message = 'Added image to ivs.timeseries.freqanalyse' - termtools.overwrite_line(message) - else: - oo.write(line) -ff.close() -oo.close() -os.remove('doc/html/ivs.timeseries.freqanalyse-module_.html')''' - -#-- now run through all the other ones. We need to explicitly treat line breaks. +# -- now run through all. We need to explicitly treat line breaks. files = sorted(glob.glob('doc/html/*module.html')) -files+= sorted(glob.glob('doc/html/*class.html')) +files += sorted(glob.glob('doc/html/*class.html')) for myfile in files: - shutil.move(myfile,myfile+'_') - ff = open(myfile+'_','r') - oo = open(myfile,'w') + shutil.move(myfile, myfile+'_') + ff = open(myfile+'_', 'r') + oo = open(myfile, 'w') line_break = False for line in ff.readlines(): if ']include figure]' in line or line_break: filename = line.split(']')[-2].strip() - oo.write(r"
[image example]
".format(filename,args.width)+'\n\n') - #-- save cursor, remove line, print and reset cursor - message = 'Added image {} to {}\r'.format(filename,myfile) + oo.write(r"
[image example]
".format(filename, + args.width)+'\n\n') + # -- save cursor, remove line, print and reset cursor + message = 'Added image {} to {}\r'.format(filename, myfile) termtools.overwrite_line(message) oo.write('\n\n') line_break = False @@ -118,8 +109,8 @@ ff.close() oo.close() os.remove(myfile+'_') -#-- that's it! Open the documentation if requested -if os.path.isfile(os.path.join(this_dir,'doc/html/index.html')): +# -- that's it! Open the documentation if requested +if os.path.isfile(os.path.join(this_dir, 'doc/html/index.html')): print("HTML documentation is now available in doc/html") else: print("Something went wrong during documentation generation") diff --git a/observations/barycentric_correction.py b/observations/barycentric_correction.py index 2176c54c4..b5139c1f4 100644 --- a/observations/barycentric_correction.py +++ b/observations/barycentric_correction.py @@ -849,7 +849,7 @@ def precess(ra0, dec0, equinox1, equinox2, doprint=False, fk4=False, radian=Fals ra = ra + (ra < 0.) * 2.0e0 * np.pi if doprint: - print 'Equinox (%.2f): %f,%f' % (equinox2, ra, dec) + print('Equinox (%.2f): %f,%f' % (equinox2, ra, dec)) if scal: ra, dec = ra[0], dec[0] return ra, dec diff --git a/observations/distance.py b/observations/distance.py index eb4aff123..0e16bec1e 100644 --- a/observations/distance.py +++ b/observations/distance.py @@ -56,7 +56,7 @@ Gaussian function. >>> from ivs.sigproc import evaluate ->>> from ivs.units.uncertainties import ufloat +>>> from uncertainties import ufloat Then we can approximate the distance to the star (in pc) as the inverse of the parallax (in arcsec). @@ -107,15 +107,15 @@ def rho(z,z_sun=20.0,hd=31.8,hh=490.,sigma=1.91e-3,f=0.039): """ Stellar galactic density function. - + Galactic coordinate z: self-gravitating isothermal disk plus a Gaussian halo - + See, e.g., Maiz-Apellaniz, Alfaro and Sota 2007/2008 (Poster) - + Other values we found in the literature: - + z_sun,hd,sigma,f = 24.7,34.2,1.62e-3,0.058 - + @param z: galactic coordinate z (parsec) @type z: array or float @param z_sun: Sun's distance above the Galactic plane (20.0 +/- 2.9 pc) @@ -141,11 +141,11 @@ def probability_cd(r,plx): Compute the probability for an object to be at distance r (pc), given its parallax (mas) and error on the parallax (mas) and a constant density function. - + Unnormalised! - + To obtain the probabilty, multiply with a stellar galactic density function. - + @param r: distance (pc) @type r: float/array @param plx: parallax (mas) and error @@ -162,12 +162,12 @@ def distprob(r,theta,plx,**kwargs): """ Compute the probability for an object to be located at a distance r (pc), given its parallax and galactic lattitude. - + theta in degrees plx is a tuple! - + returns (unnormalised) probability density function. - + @param r: distance (pc) @type r: float/array @param theta: galactic latitude (deg) @@ -189,4 +189,4 @@ def distprob(r,theta,plx,**kwargs): import doctest import pylab as pl doctest.testmod() - pl.show() \ No newline at end of file + pl.show() diff --git a/observations/visibility.py b/observations/visibility.py index 61a8f8854..75ef1715a 100644 --- a/observations/visibility.py +++ b/observations/visibility.py @@ -113,7 +113,7 @@ def __set_object(self,objectname): jpos = sesame.search(objectname,db='N')['jpos'] except KeyError: logger.warning('Object %s not found in NED either.'%(objectname)) - raise IOError, 'No coordinates retrieved for object %s.'%(objectname) + raise IOError('No coordinates retrieved for object %s.'%(objectname)) myobject = ephem.readdb("%s,f|M|A0,%s,8.0,2000"%(objectname,','.join(jpos.split()))) return myobject @@ -195,7 +195,7 @@ def set_site(self,sitename='lapalma',sitelat=None,sitelong=None,siteelev=None): if sitename != None: mysite.name = sitename logger.info('Site name set to %s'%(sitename)) - if siteelevation != None: + if siteelev != None: mysite.elevation = siteelev logger.info('Site elevation set to %s'%(sitename)) if sitelat != None: @@ -491,7 +491,7 @@ def visibility(self,multiple=False,midnight=None,airmassmodel='Pickering2002',** #-- the output dictionary self.vis = dict(MJDs=MJDs,dates=dates,alts=alts,airmass=airmass,during_night=during_night,moon_alts=moon_alts,moon_airmass=moon_airmass,moon_separation=moon_separation) - self.vis.update(dict(sun_prevrise=np.array(num2date(self._sun_prevrise),dtype='|S19'),sun_prevset=np.array(num2date(self._sun_prevset),dtype='|S19'),sun_nextrise=np.array(num2date(self._sun_nextrise),dtype='|S19'),sun_nextset=np.array(num2date(self._sun_nextset),dtype='|S19'))) + self.vis.update(dict(sun_prevrise=np.array(num2date(self._sun_prevrise),dtype='U19'),sun_prevset=np.array(num2date(self._sun_prevset),dtype='U19'),sun_nextrise=np.array(num2date(self._sun_nextrise),dtype='U19'),sun_nextset=np.array(num2date(self._sun_nextset),dtype='U19'))) for i,obj in enumerate(self.objects): keep = (self._objectIndices == i) & (during_night==1) & (0<=airmass) & (airmass<=2.5) logger.info('Object %s: %s visible during night time (%.1f} + 3. the L{local effective temperature} 4. the local velocity vector due to rotation 5. the radial velocity 6. the total distorted surface area @@ -43,6 +43,8 @@ >>> T_pole2 = 18850. # polar temperature secondary >>> M1 = 10.3 # primary mass >>> M2 = 5.6 # secondary mass +>>> theta,phi = local.get_grid(20,100,full=True,gtype='spher') +>>> thetas,phis = np.ravel(theta),np.ravel(phi) Note that we actually do not need the masses, since we can derive them from the third kepler law with the other parameters. We do so for convenience. @@ -61,15 +63,22 @@ >>> omega_rot_vec = np.array([0.,0.,-omega_rot]) Derive the shape of the two stars - ->>> 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) ->>> 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) +>>> 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)) +>>> 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)) We focus on the primary, then repeat everything for the secondary: The local surface gravity can only be calculated if we have Cartesian coordinates. >>> x1,y1,z1 = vectors.spher2cart_coord(radius1,phi,theta) ->>> 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) +>>> 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)) >>> Gamma_pole1 = binary_roche_potential_gradient(0,0,r_pole1,q,d,F,norm=True) >>> zeta1 = g_pole1 / Gamma_pole1 >>> dOmega1 = binary_roche_potential_gradient(x1,y1,z1,q,d,F,norm=False) @@ -79,9 +88,10 @@ Now we can, as before, calculate the other local quantities: ->>> areas_local1,cos_gamma1 = surface_elements((radius1,theta,phi),-grav_local1) ->>> teff_local1 = local_temperature(grav1,g_pole1,T_pole1,beta=1.) ->>> ints_local1 = local_intensity(teff_local1,grav1,np.ones_like(cos_gamma1),photband='OPEN.BOL') +>>> areas_local1,cos_gamma1 = local.surface_elements((radius1,[theta,phi]),-grav_local1) +>>> teff_local1 = local.temperature(grav1,g_pole1,T_pole1,beta=1.) +>>> ints_local1 = (local.intensity(teff_local1,grav1,np.ones_like(cos_gamma1),\ + photband='OPEN.BOL')) >>> velo_local1 = np.cross(np.array([x1,y1,z1]).T*to_SI,omega_rot_vec).T @@ -90,14 +100,15 @@ >>> quantities = areas_local1,np.log10(grav1*100),teff_local1,ints_local1 >>> names = 'Area','log g', 'Teff', 'Flux' >>> p = pl.figure() ->>> rows,cols = 2,2 +>>> rows,cols = 2,2 >>> for i,(quantity,name) in enumerate(zip(quantities,names)): ... p = pl.subplot(rows,cols,i+1) ... p = pl.title(name) ... q = quantity.ravel() ... vmin,vmax = q[-np.isnan(q)].min(),q[-np.isnan(q)].max() ... 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() -... p = pl.scatter(phis/pi*180,thetas/pi*180,c=q,edgecolors='none',vmin=vmin,vmax=vmax) +... p = (pl.scatter(phis/pi*180,thetas/pi*180,c=q,edgecolors='none',\ + vmin=vmin,vmax=vmax)) ... p = pl.colorbar() ... p = pl.xlim(0,360) ... p = pl.ylim(180,0) @@ -127,33 +138,39 @@ >>> r_pole = 2.0 # solar radii >>> M = 1.5 # solar mass >>> view_angle = pi/2 # radians ->>> theta,phi = get_grid(20,100,full=True,gtype='spher') +>>> theta,phi = local.get_grid(20,100,full=True,gtype='spher') >>> thetas,phis = np.ravel(theta),np.ravel(phi) Then calculate the shape of this star ->>> radius = np.array([get_fastrot_roche_radius(itheta,r_pole,omega) for itheta in thetas]).reshape(theta.shape) ->>> 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 +>>> radius = (np.array([rotation.get_fastrot_roche_radius(itheta,r_pole,omega)\ + for itheta in thetas]).reshape(theta.shape)) +>>> grav_local = (np.array([rotation.fastrot_roche_surface_gravity(\ + iradius,itheta,iphi,r_pole,omega,M)\ + for iradius,itheta,iphi in\ + zip(radius.ravel(),thetas,phis)]).T) >>> grav_local = np.array([i.reshape(theta.shape) for i in grav_local]) ->>> g_pole = fastrot_roche_surface_gravity(r_pole,0,0,r_pole,omega,M)[-1] +>>> g_pole = rotation.fastrot_roche_surface_gravity(r_pole,0,0,r_pole,omega,M)[-1] >>> grav = vectors.norm(grav_local) and the local quantities ->>> areas_local,cos_gamma = surface_elements((radius,theta,phi),-grav_local) ->>> teff_local = local_temperature(vectors.norm(grav_local),g_pole,T_pole,beta=1.) ->>> ints_local = local_intensity(teff_local,grav,photband='OPEN.BOL') +>>> areas_local,cos_gamma = local.surface_elements((radius,[theta,phi]),-grav_local) +>>> teff_local = local.temperature(vectors.norm(grav_local),g_pole,T_pole,beta=1.) +>>> ints_local = local.intensity(teff_local,grav,photband='OPEN.BOL') >>> x,y,z = vectors.spher2cart_coord(radius.ravel(),phis,thetas) Assume, with a shape of a non-rotating star, that we have a velocity component on the surface of the star: >>> myomega = 0.5 ->>> velo_local = diffrot_velocity((phi,theta,radius*constants.Rsol),myomega,myomega,r_pole,M) +>>> velo_local = rotation.diffrot_velocity((phi,theta,radius*constants.Rsol),myomega,myomega,r_pole,M) Collect all the necessary information in one record array. ->>> starlist = [x,y,z] + [i.ravel() for i in velo_local] + [i.ravel() for i in grav_local] + [teff_local.ravel(),areas_local.ravel()] +>>> starlist = ([x,y,z] + [i.ravel() for i in velo_local] +\ + [i.ravel() for i in grav_local] +\ + [teff_local.ravel(),areas_local.ravel()]) >>> starnames = ['x','y','z','vx','vy','vz','gravx','gravy','gravz','teff','areas'] >>> star = np.rec.array(starlist,names=starnames) @@ -161,7 +178,8 @@ is the radial velocity. >>> view_angle = pi/2 # edge on ->>> mystar = project(star,view_long=(0,0,0),view_lat=(view_angle,0,0),photband='OPEN.BOL',only_visible=True,plot_sort=True) +>>> mystar = (local.project(star,view_long=(0,0,0),view_lat=(view_angle,0,0),\ + photband='OPEN.BOL',only_visible=True,plot_sort=True)) We can calculate the synthetic spectra for all surface elements between 7055 and 7075 angstrom: @@ -169,7 +187,9 @@ >>> loggs = np.log10(np.sqrt(mystar['gravx']**2 + mystar['gravy']**2 + mystar['gravz']**2)*100) >>> iterator = zip(mystar['teff'],loggs,mystar['vx']/1000.) >>> wave_spec = np.linspace(7055,7075,750) ->>> spectra = np.array([spectra_model.get_table(teff=iteff,logg=ilogg,vrad=ivrad,wave=wave_spec)[1] for iteff,ilogg,ivrad in iterator]) +>>> spectra = (np.array([spectra_model.get_table(\ + teff=iteff,logg=ilogg,vrad=ivrad,wave=wave_spec)[1]\ + for iteff,ilogg,ivrad in iterator])) The total observed spectrum is then simply the weighted sum with the local projected intensities: @@ -178,9 +198,13 @@ We compare with the ROTIN package, which assumes a linear limbdarkening law ->>> original = spectra_model.get_table(teff=mystar['teff'][0],logg=loggs[0],wave=wave_spec)[1] ->>> 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] - +>>> original = (spectra_model.get_table(teff=mystar['teff'][0],logg=loggs[0],\ + wave=wave_spec)[1]) +>>> 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]) + And a plot can be made via >>> colors = mystar['eyeflux']/mystar['eyeflux'].max() @@ -190,8 +214,11 @@ >>> p = ax.set_axis_bgcolor('k') >>> p = pl.box(on=False) >>> p = pl.xticks([]);p = pl.yticks([]) ->>> p = pl.scatter(mystar['y'],mystar['z'],c=colors,edgecolors='none',cmap=pl.cm.gray,vmin=0,vmax=1) ->>> 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) +>>> p = (pl.scatter(mystar['y'],mystar['z'],c=colors,edgecolors='none',\ + cmap=pl.cm.gray,vmin=0,vmax=1)) +>>> 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)) >>> ax = pl.axes([0.13,0.1,0.78,0.4]) >>> p = pl.plot(wave_spec,average_spectrum,'k-',lw=2,label='Numerical') >>> p = pl.plot(wave_spec,original,'r-',label='No rotation') @@ -211,13 +238,13 @@ import os import pylab as pl import numpy as np -from numpy import pi,cos,sin,sqrt,nan +from numpy import pi, cos, sin, sqrt, nan from scipy.optimize import newton from scipy.spatial import KDTree try: from scipy.spatial import Delaunay except ImportError: - print "import Error Delaunay" + print("import Error Delaunay") import time from ivs.timeseries import keplerorbit from ivs.units import constants @@ -227,6 +254,7 @@ from ivs.sed import limbdark from ivs.spectra import model as spectra_model from ivs.roche import local +from ivs.roche import rotation from ivs.aux import loggers from ivs.inout import ascii from ivs.inout import fits @@ -235,19 +263,24 @@ #{ Eccentric asynchronous binary Roche potential in spherical coordinates -def binary_roche_potential(r,theta,phi,Phi,q,d,F): + +def print_tester(name): + return name.upper() + + +def binary_roche_potential(r, theta, phi, Phi, q, d, F): """ Unitless eccentric asynchronous Roche potential in spherical coordinates. - + See Wilson, 1979. - + The synchronicity parameter F is 1 for synchronised circular orbits. For pseudo-synchronous eccentrical orbits, it is equal to (Hut, 1981) - + F = sqrt( (1+e)/ (1-e)^3) - + Periastron is reached when d = 1-e. - + @param r: radius of Roche volume at potential Phi (in units of semi-major axis) @type r: float @param theta: colatitude (0 at the pole, pi/2 at the equator) @@ -265,20 +298,21 @@ def binary_roche_potential(r,theta,phi,Phi,q,d,F): @return: residu between Phi and roche potential @rtype: float """ - lam,nu = cos(phi)*sin(theta),cos(theta) + lam, nu = cos(phi)*sin(theta), cos(theta) term1 = 1. / r - term2 = q * ( 1./sqrt(d**2 - 2*lam*d*r + r**2) - lam*r/d**2) + term2 = q * (1./sqrt(d**2 - 2*lam*d*r + r**2) - lam*r/d**2) term3 = 0.5 * F**2 * (q+1) * r**2 * (1-nu**2) return (Phi - (term1 + term2 + term3)) -def binary_roche_potential_gradient(x,y,z,q,d,F,norm=False): + +def binary_roche_potential_gradient(x, y, z, q, d, F, norm=False): """ Gradient of eccenctric asynchronous Roche potential in cartesian coordinates. - + See Phoebe scientific reference, http://phoebe.fiz.uni-lj.si/docs/phoebe_science.ps.gz - + x,y,z,d in real units! (otherwise you have to scale it yourself) - + @param x: x-axis @type x: float' @param y: y-axis @@ -297,34 +331,34 @@ def binary_roche_potential_gradient(x,y,z,q,d,F,norm=False): @rtype: ndarray or float """ r = sqrt(x**2 + y**2 + z**2) - r_= sqrt((d-x)**2 + y**2 + z**2) + r_ = sqrt((d-x)**2 + y**2 + z**2) dOmega_dx = - x / r**3 + q * (d-x) / r_**3 + F**2 * (1+q)*x - q/d**2 - dOmega_dy = - y / r**3 - q * y / r_**3 + F**2 * (1+q)*y - dOmega_dz = - z / r**3 - q * z / r_**3 - - dOmega = np.array([dOmega_dx,dOmega_dy,dOmega_dz]) + dOmega_dy = - y / r**3 - q * y / r_**3 + F**2 * (1+q)*y + dOmega_dz = - z / r**3 - q * z / r_**3 + + dOmega = np.array([dOmega_dx, dOmega_dy, dOmega_dz]) if norm: return vectors.norm(dOmega) else: return dOmega -def binary_roche_surface_gravity(x,y,z,d,omega,M1,M2,a=1.,norm=False): +def binary_roche_surface_gravity(x, y, z, d, omega, M1, M2, norm=False): """ Calculate surface gravity in an eccentric asynchronous binary roche potential. """ q = M2/M1 x_com = q*d/(1+q) - - r = np.array([x,y,z]) - d_cf = np.array([d-x_com,0,0]) - d = np.array([d,0,0]) + + r = np.array([x, y, z]) + d_cf = np.array([d - x_com, 0, 0]) + d = np.array([d, 0, 0]) h = d - r - + term1 = - constants.GG*M1/vectors.norm(r)**3*r term2 = - constants.GG*M2/vectors.norm(h)**3*h term3 = - omega**2 * d_cf - + g_pole = term1 + term2 + term3 if norm: return vectors.norm(g_pole) @@ -332,15 +366,15 @@ def binary_roche_surface_gravity(x,y,z,d,omega,M1,M2,a=1.,norm=False): return g_pole -def get_binary_roche_radius(theta,phi,Phi,q,d,F,r_pole=None): +def get_binary_roche_radius(theta, phi, Phi, q, d, F, r_pole): """ Calculate the eccentric asynchronous binary Roche radius in spherical coordinates. - + This is done via the Newton-Raphson secant method. If r_pole is not given as a starting value, it will be calculated here (slowing down the function). - + If no radius can be calculated for the given coordinates, 'nan' is returned. - + @param theta: colatitude (0 at the pole, pi/2 at the equator) @type theta: float @param phi: longitude (0 in direction of COM) @@ -358,66 +392,65 @@ def get_binary_roche_radius(theta,phi,Phi,q,d,F,r_pole=None): @return r: radius of Roche volume at potential Phi (in units of semi-major axis) @rtype r: float """ - if r_pole is None: - r_pole = newton(binary_roche_potential,1e-5,args=(0,0,Phi,q,ds.min(),F)) try: - r = newton(binary_roche_potential,r_pole,args=(theta,phi,Phi,q,d,F)) - if r<0 or r>d: + r = newton(binary_roche_potential, r_pole, + args=(theta, phi, Phi, q, d, F)) + if r < 0 or r > d: r = nan except RuntimeError: - r = nan + r = nan return r -#} +# } - -def reflection_effect(primary,secondary,theta,phi,A1=1.,A2=1.,max_iter=1): - #-- reflection effect - #-------------------- +def reflection_effect(primary, secondary, theta, phi, A1=1., A2=1., + max_iter=1): + # -- reflection effect + # -------------------- reflection_iter = 0 while (reflection_iter1.005**0.25=1.01) + + # -- adapt the teff only (and when) the increase is more than 1% (=>1.005**0.25=1.01) break_out = True trash,trash2,R1,R2 = stitch_grid(theta,phi,R1.reshape(theta.shape),R2.reshape(theta.shape)) del trash,trash2 R1 = R1.ravel() R2 = R2.ravel() - + if (R1[-np.isnan(R1)]>1.05).any(): - print "Significant reflection effect on primary (max %.3f%%)"%((R1.max()**0.25-1)*100) + print("Significant reflection effect on primary (max %.3f%%)"%((R1.max()**0.25-1)*100)) primary['teff']*= R1**0.25 - primary['flux'] = local_intensity(primary['teff'],primary['grav'],np.ones_like(primary['teff']),photbands=['OPEN.BOL']) + primary['flux'] = local.intensity(primary['teff'],primary['grav'],np.ones_like(primary['teff']),photbands=['OPEN.BOL']) break_out = False else: - print 'Maximum reflection effect on primary: %.3f%%'%((R1.max()**0.25-1)*100) - + print('Maximum reflection effect on primary: %.3f%%'%((R1.max()**0.25-1)*100)) + if (R2[-np.isnan(R2)]>1.05).any(): - print "Significant reflection effect on secondary (max %.3g%%)"%((R2.max()**0.25-1)*100) + print("Significant reflection effect on secondary (max %.3g%%)"%((R2.max()**0.25-1)*100)) secondary['teff']*= R2**0.25 - secondary['flux'] = local_intensity(secondary['teff'],secondary['grav'],np.ones_like(secondary['teff']),photbands=['OPEN.BOL']) + secondary['flux'] = local.intensity(secondary['teff'],secondary['grav'],np.ones_like(secondary['teff']),photbands=['OPEN.BOL']) break_out = False else: - print 'Maximum reflection effect on secondary: %.3g%%'%((R1.max()**0.25-1)*100) - + print('Maximum reflection effect on secondary: %.3g%%'%((R1.max()**0.25-1)*100)) + if break_out: break - - + + reflection_iter += 1 - + #================ START DEBUGGING PLOTS =================== #pl.subplot(411) #pl.scatter(x,y,c=teff_,edgecolors='none',cmap=pl.cm.spectral,vmin=T_pole,vmax=T_pole2) #pl.scatter(x2,y2,c=teff2_,edgecolors='none',cmap=pl.cm.spectral,vmin=T_pole,vmax=T_pole2) #pl.colorbar() - + #pl.subplot(425);pl.title('new primary') #pl.scatter(phis_,thetas_,c=teff_,edgecolors='none',cmap=pl.cm.spectral) #pl.colorbar() #pl.subplot(426);pl.title('new secondary') #pl.scatter(phis_,thetas_,c=teff2_,edgecolors='none',cmap=pl.cm.spectral) #pl.colorbar() - + #pl.subplot(427) #pl.scatter(phis_,thetas_,c=R1,edgecolors='none',cmap=pl.cm.spectral) #pl.colorbar() @@ -484,17 +517,17 @@ def reflection_effect(primary,secondary,theta,phi,A1=1.,A2=1.,max_iter=1): def spectral_synthesis(*stars,**kwargs): """ Generate a synthetic spectrum of one or more stars. - + If you give more than one star, you get more than one synthesized spectrum back. To compute the total spectrum, just add them up. - + WARNING: the spectra are scaled with the value of the projected intensity. This is usually calculated within a specific photometric passband. The total spectrum will thus be dependent on the photometric passband, unless you specified 'OPEN.BOL' (which is the bolometric open filter). If you want to know the RV for a specific line, you might want to look into calculating the projected intensities at specific wavelength intervals. - + @param stars: any number of (projected) star record arrays @type stars: tuple of star record arrays @keyword wave: wavelength template to compute the synthetic spectrum on @@ -503,18 +536,18 @@ def spectral_synthesis(*stars,**kwargs): @rtype: tuple of numpy arrays """ wave = kwargs.get('wave',np.linspace(7050,7080,1000)) - #-- these are the log surface gravity values in cgs [dex] + # -- these are the log surface gravity values in cgs [dex] spectra = [] get_spectrum = spectra_model.get_table for mystar in stars: loggs = np.log10(np.sqrt(mystar['gravx']**2 + mystar['gravy']**2 + mystar['gravz']**2)*100) - iterator = zip(mystar['teff'],loggs,mystar['vx']/1000.) - print "Temperature range:",mystar['teff'].min(),mystar['teff'].max() - print "logg range:",loggs.min(),loggs.max() - #-- retrieve the synthetic spectra for all surface elements, interpolated + iterator = list(zip(mystar['teff'],loggs,mystar['vx']/1000.)) + print("Temperature range:",mystar['teff'].min(),mystar['teff'].max()) + print("logg range:",loggs.min(),loggs.max()) + # -- retrieve the synthetic spectra for all surface elements, interpolated # on the supplied wavelength grid, and taking care of radial velocity shifts spectra_elements = np.array([get_spectrum(teff=iteff,logg=ilogg,vrad=ivrad,wave=wave)[1] for iteff,ilogg,ivrad in iterator]) - #-- and add them up according to the flux projected in the line of sight + # -- and add them up according to the flux projected in the line of sight average_spectrum = np.sum(spectra_elements*mystar['projflux'],axis=0) spectra.append(average_spectrum) return spectra @@ -523,7 +556,7 @@ def spectral_synthesis(*stars,**kwargs): def binary_light_curve_synthesis(**parameters): """ Generate a synthetic light curve of a binary system. - + @keyword name: name of the binary system, used for output @type name: string @keyword direc: directory to put output files @@ -536,7 +569,7 @@ def binary_light_curve_synthesis(**parameters): @parameter tres: number of phase steps to comptue the light curve on @type tres: integer """ - #-- some parameters are optional + # -- some parameters are optional # file output parameters name = parameters.setdefault('name','mybinary') direc = parameters.setdefault('direc','') @@ -559,16 +592,16 @@ def binary_light_curve_synthesis(**parameters): A2= parameters.setdefault('A2',1.) # albedo secondary beta1 = parameters.setdefault('beta1',1.0) # gravity darkening primary beta2 = parameters.setdefault('beta2',1.0) # gravity darkening secondary - - #-- others are mandatory: + + # -- others are mandatory: T_pole = parameters['Tpole1'] # Primary Polar temperature [K] T_pole2 = parameters['Tpole2'] # Secondary Polar temperature [K] P = parameters['P'] # Period [days] asini = parameters['asini'] # total semi-major axis*sini [AU] Phi = parameters['Phi1'] # Gravitational potential of primary [-] Phi2 = parameters['Phi2'] # Gravitational potential of secondary [-] - - #-- derive parameters needed in the calculation + + # -- derive parameters needed in the calculation a = asini/sin(incl/180.*pi) a1 = a / (1.+1./q) a2 = a / (1.+ q) @@ -589,8 +622,8 @@ def binary_light_curve_synthesis(**parameters): parameters['M2'] = M2 parameters['L1'] = L1*a*constants.au/constants.Rsol parameters['L2'] = L2*a*constants.au/constants.Rsol - - #-- calculate the Keplerian orbits of the primary and secondary + + # -- calculate the Keplerian orbits of the primary and secondary if not hasattr(tres,'__iter__'): times = np.linspace(0.5*P,1.5*P,tres) else: @@ -599,7 +632,7 @@ def binary_light_curve_synthesis(**parameters): r2,theta2 = keplerorbit.orbit_in_plane(times,[P,e,a2,gamma],component='secondary') RV1 = keplerorbit.radial_velocity([P,e,a1,pi/2,view_angle,gamma],theta=theta1,itermax=8) RV2 = keplerorbit.radial_velocity([P,e,a2,pi/2,view_angle,gamma],theta=theta2,itermax=8) - + r1 = r1/constants.au r2 = r2/constants.au # put them in cartesian coordinates @@ -609,23 +642,23 @@ def binary_light_curve_synthesis(**parameters): rot_i = -(pi/2 - view_angle) x1o_,z1o = vectors.rotate(x1o,np.zeros_like(y1o),rot_i) x2o_,z2o = vectors.rotate(x2o,np.zeros_like(y2o),rot_i) - - #-- calculate separation at all phases + + # -- calculate separation at all phases ds = np.sqrt( (x1o-x2o)**2 + (y1o-y2o)**2) - - #-- calculate the polar radius and the radius towards L1 at minimum separation + + # -- calculate the polar radius and the radius towards L1 at minimum separation r_pole = newton(binary_roche_potential,1e-5,args=(0,0,Phi,q,ds.min(),F)) r_pole2 = newton(binary_roche_potential,1e-5,args=(0,0,Phi2,q2,ds.min(),F2)) parameters['Rp1'] = r_pole*a*constants.au/constants.Rsol parameters['Rp2'] = r_pole2*a*constants.au/constants.Rsol - - #-- calculate the critical Phis and Roche radius + + # -- calculate the critical Phis and Roche radius phi1_crit = binary_roche_potential(L1,0,0,Phi,q,ds.min(),F) phi2_crit = binary_roche_potential(L1,0,0,Phi2,q2,ds.min(),F2) R_roche1 = a * 0.49 * q2**(2./3.) / (0.6*q2**(2./3.) + np.log(1+q2**(1./3.))) R_roche2 = a * 0.49 * q**(2./3.) / (0.6*q**(2./3.) + np.log(1+q**(1./3.))) - - #-- timescales + + # -- timescales tdyn1 = np.sqrt(2*(r_pole*constants.au)**3/(constants.GG*M1*constants.Msol)) tdyn2 = np.sqrt(2*(r_pole2*constants.au)**3/(constants.GG*M2*constants.Msol)) lumt1 = (M1<2.) and M1**4 or (1.5*M1**3.5) # from mass luminosity relation @@ -634,7 +667,7 @@ def binary_light_curve_synthesis(**parameters): tthr2 = constants.GG * (M2*constants.Msol)**2 / (r_pole2*constants.au*lumt2*constants.Lsol) tnuc1 = 7e9 * M1 / lumt1 tnuc2 = 7e9 * M2 / lumt2 - + logger.info('=================================================') logger.info('GENERAL SYSTEM AND COMPONENT PROPERTIES') logger.info('=================================================') @@ -663,8 +696,8 @@ def binary_light_curve_synthesis(**parameters): logger.info('TTHERM primary = %.3g yr'%(tthr1/(24*3600*365))) logger.info('TTHERM secondary = %.3g yr'%(tthr2/(24*3600*365))) logger.info('=================================================') - - #-- construct the grid to calculate stellar shapes + + # -- construct the grid to calculate stellar shapes if hasattr(res,'__iter__'): mygrid = local.get_grid(res[0],res[1],gtype=gtype) theta,phi = mygrid[:2] @@ -672,14 +705,14 @@ def binary_light_curve_synthesis(**parameters): mygrid = local.get_grid(res,gtype=gtype) theta,phi = mygrid[:2] thetas,phis = np.ravel(theta),np.ravel(phi) - + light_curve = np.zeros_like(times) RV1_corr = np.zeros_like(times) RV2_corr = np.zeros_like(times) to_SI = a*constants.au to_CGS = a*constants.au*100. scale_factor = a*constants.au/constants.Rsol - + fitsfile = os.path.join(direc,'%s.fits'%(name)) if direc is not None and os.path.isfile(fitsfile): os.remove(fitsfile) @@ -691,24 +724,24 @@ def binary_light_curve_synthesis(**parameters): outputfile_secn = os.path.join(direc,'%s_secondary.fits'%(name)) outputfile_prim = fits.write_primary(outputfile_prim,header_dict=parameters) outputfile_secn = fits.write_primary(outputfile_secn,header_dict=parameters) - + ext_dict = {} for di,d in enumerate(ds): report = "STEP %04d"%(di) - - #-- this is the angular velocity due to rotation and orbit + + # -- this is the angular velocity due to rotation and orbit # you get the rotation period of the star via 2pi/omega_rot (in sec) omega_rot = F * 2*pi/P_ * 1/d**2 * sqrt( (1+e)*(1-e)) omega_rot_vec = np.array([0.,0.,-omega_rot]) - + if e>0 or di==0: - #-- compute the star's radius and surface gravity + # -- compute the star's radius and surface gravity out = [[get_binary_roche_radius(itheta,iphi,Phi=Phi,q=q,d=d,F=F,r_pole=r_pole), 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)] rprim,rsec = np.array(out).T - - #-- for the primary - #------------------ + + # -- for the primary + # ------------------ radius = rprim.reshape(theta.shape) this_r_pole = get_binary_roche_radius(0,0,Phi=Phi,q=q,d=d,F=F,r_pole=r_pole) x,y,z = vectors.spher2cart_coord(radius,phi,theta) @@ -717,8 +750,8 @@ def binary_light_curve_synthesis(**parameters): zeta = g_pole / Gamma_pole dOmega = binary_roche_potential_gradient(x,y,z,q,d,F,norm=False) grav_local = dOmega*zeta - - #-- here we can compute local quantities: surface gravity, area, + + # -- here we can compute local quantities: surface gravity, area, # effective temperature, flux and velocity grav_local = np.array([i.reshape(theta.shape) for i in grav_local]) grav = vectors.norm(grav_local) @@ -726,8 +759,8 @@ def binary_light_curve_synthesis(**parameters): teff_local = local.temperature(grav,g_pole,T_pole,beta=beta1) ints_local = local.intensity(teff_local,grav,np.ones_like(cos_gamma),photband='OPEN.BOL') velo_local = np.cross(np.array([x,y,z]).T*to_SI,omega_rot_vec).T - - #-- here we can compute the global quantities: total surface area + + # -- here we can compute the global quantities: total surface area # and luminosity lumi_prim = 4*pi*(ints_local*areas_local*to_CGS**2).sum()/constants.Lsol_cgs area_prim = 4*areas_local.sum()*to_CGS**2/(4*pi*constants.Rsol_cgs**2) @@ -741,9 +774,9 @@ def binary_light_curve_synthesis(**parameters): ext_dict['loggp1'] = np.log10(g_pole*100) ext_dict['LUMI1'] = lumi_prim ext_dict['SURF1'] = area_prim - - #-- for the secondary - #-------------------- + + # -- for the secondary + # -------------------- radius2 = rsec.reshape(theta.shape) this_r_pole2 = get_binary_roche_radius(itheta,iphi,Phi=Phi2,q=q2,d=d,F=F2,r_pole=r_pole2) x2,y2,z2 = vectors.spher2cart_coord(radius2,phi,theta) @@ -752,17 +785,17 @@ def binary_light_curve_synthesis(**parameters): zeta2 = g_pole2 / Gamma_pole2 dOmega2 = binary_roche_potential_gradient(x2,y2,z2,q2,d,F2,norm=False) grav_local2 = dOmega2*zeta2 - - #-- here we can compute local quantities: : surface gravity, area, - # effective temperature, flux and velocity + + # -- here we can compute local quantities: : surface gravity, area, + # effective temperature, flux and velocity grav_local2 = np.array([i.reshape(theta.shape) for i in grav_local2]) grav2 = vectors.norm(grav_local2) areas_local2,cos_gamma2 = local.surface_elements((radius2,mygrid),-grav_local2,gtype=gtype) teff_local2 = local.temperature(grav2,g_pole2,T_pole2,beta=beta2) ints_local2 = local.intensity(teff_local2,grav2,np.ones_like(cos_gamma2),photband='OPEN.BOL') velo_local2 = np.cross(np.array([x2,y2,z2]).T*to_SI,omega_rot_vec).T - - #-- here we can compute the global quantities: total surface area + + # -- here we can compute the global quantities: total surface area # and luminosity lumi_sec = 4*pi*(ints_local2*areas_local2*to_CGS**2).sum()/constants.Lsol_cgs area_sec = 4*areas_local2.sum()*to_CGS**2/(4*pi*constants.Rsol_cgs**2) @@ -776,31 +809,31 @@ def binary_light_curve_synthesis(**parameters): ext_dict['loggp2'] = np.log10(g_pole2*100) ext_dict['LUMI2'] = lumi_sec ext_dict['SURF2'] = area_sec - + #================ START DEBUGGING PLOTS =================== #plot_quantities(phi,theta,np.log10(grav2*100.),areas_local2,np.arccos(cos_gamma2)/pi*180,teff_local2,ints_local2, # names=['grav','area','angle','teff','ints'],rows=2,cols=3) #pl.show() #================ END DEBUGGING PLOTS =================== - - #-- stitch the grid! + + # -- stitch the grid! theta_,phi_,radius,gravx,gravy,gravz,grav,areas,teff,ints,vx,vy,vz = \ local.stitch_grid(theta,phi,radius,grav_local[0],grav_local[1],grav_local[2], grav,areas_local,teff_local,ints_local,velo_local[0],velo_local[1],velo_local[2], seamless=False,gtype=gtype, vtype=['scalar','x','y','z','scalar','scalar','scalar','scalar','vx','vy','vz']) - #-- stitch the grid! + # -- stitch the grid! theta2_,phi2_,radius2,gravx2,gravy2,gravz2,grav2,areas2,teff2,ints2,vx2,vy2,vz2 = \ local.stitch_grid(theta,phi,radius2,grav_local2[0],grav_local2[1],grav_local2[2], grav2,areas_local2,teff_local2,ints_local2,velo_local2[0],velo_local2[1],velo_local2[2], seamless=False,gtype=gtype, vtype=['scalar','x','y','z','scalar','scalar','scalar','scalar','vx','vy','vz']) - - #-- vectors and coordinates in original frame + + # -- vectors and coordinates in original frame x_of,y_of,z_of = vectors.spher2cart_coord(radius.ravel(),phi_.ravel(),theta_.ravel()) x2_of,y2_of,z2_of = vectors.spher2cart_coord(radius2.ravel(),phi2_.ravel(),theta2_.ravel()) - x2_of = -x2_of - #-- store information on primary and secondary in a record array + x2_of = -x2_of + # -- store information on primary and secondary in a record array primary = np.rec.fromarrays([theta_.ravel(),phi_.ravel(),radius.ravel(), x_of,y_of,z_of, vx.ravel(),vy.ravel(),vz.ravel(), @@ -811,26 +844,29 @@ def binary_light_curve_synthesis(**parameters): 'vx','vy','vz', 'gravx','gravy','gravz','grav', 'areas','teff','flux']) - - secondary = np.rec.fromarrays([theta2_.ravel(),phi2_.ravel(),radius2.ravel(), - x2_of,y2_of,z2_of, - vx2.ravel(),-vy2.ravel(),vz2.ravel(), - -gravx2.ravel(),gravy2.ravel(),gravz2.ravel(),grav2.ravel(), - areas2.ravel(),teff2.ravel(),ints2.ravel()], - names=['theta','phi','r', - 'x','y','z', - 'vx','vy','vz', - 'gravx','gravy','gravz','grav', - 'areas','teff','flux']) - - #-- take care of the reflection effect + + secondary = np.rec.fromarrays([theta2_.ravel(), phi2_.ravel(), + radius2.ravel(), x2_of, y2_of, + z2_of, vx2.ravel(), -vy2.ravel(), + vz2.ravel(), -gravx2.ravel(), + gravy2.ravel(), gravz2.ravel(), + grav2.ravel(), areas2.ravel(), + teff2.ravel(), ints2.ravel()], + names=['theta', 'phi', 'r', 'x', 'y', + 'z', 'vx', 'vy', 'vz', + 'gravx', 'gravy', 'gravz', + 'grav', 'areas', 'teff', + 'flux'] + ) + + # -- take care of the reflection effect primary,secondary = reflection_effect(primary,secondary,theta,phi, A1=A1,A2=A2,max_iter=max_iter_reflection) - #-- now compute the integrated intensity in the line of sight: - #------------------------------------------------------------- + # -- now compute the integrated intensity in the line of sight: + # ------------------------------------------------------------- rot_theta = np.arctan2(y1o[di],x1o[di]) - #-- if we want to save the binary to a file, we'd better want it in some + # -- if we want to save the binary to a file, we'd better want it in some # real units, and the entire star, instead of just the projected star: if direc is not None: prim = local.project(primary,view_long=(rot_theta,x1o[di],y1o[di]), @@ -839,7 +875,7 @@ def binary_light_curve_synthesis(**parameters): secn = local.project(secondary,view_long=(rot_theta,x2o[di],y2o[di]), view_lat=(view_angle,0,0),photband=photband, only_visible=False,plot_sort=False,scale_factor=scale_factor) - #-- calculate center-of-mass (is this correct?) + # -- calculate center-of-mass (is this correct?) com_x = (x1o[di] + q*x2o[di]) / (1.0+q) com_y = (y1o[di] + q*y2o[di]) / (1.0+q) com = np.array([com_x,com_y,0.]) @@ -848,13 +884,13 @@ def binary_light_curve_synthesis(**parameters): com[0],com[2] = vectors.rotate(com[0],com[2],rot_i) 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]) 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]) - #-- only close the file every 20 cycles (for speed) + # -- only close the file every 20 cycles (for speed) if direc is not None and (di%20==0): close = True else: close = False # and append to primary HDUList outputfile_prim = fits.write_recarray(prim,outputfile_prim,close=close,header_dict=prim_header) outputfile_secn = fits.write_recarray(secn,outputfile_secn,close=close,header_dict=secn_header) - + prim = local.project(primary,view_long=(rot_theta,x1o[di],y1o[di]), view_lat=(view_angle,0,0),photband=photband, only_visible=True,plot_sort=True) @@ -863,8 +899,8 @@ def binary_light_curve_synthesis(**parameters): only_visible=True,plot_sort=True) prim['vx'] = -prim['vx'] + RV1[di]*1000. secn['vx'] = -secn['vx'] + RV2[di]*1000. - - #-- the total intensity is simply the sum of the projected intensities + + # -- the total intensity is simply the sum of the projected intensities # over all visible meshpoints. To calculate the visibility, we # we collect the Y-Z coordinates in one array for easy matching in # the KDTree @@ -879,8 +915,8 @@ def binary_light_curve_synthesis(**parameters): front_component = 2 report += ' Secondary in front' coords_front = np.column_stack([front['y'],front['z']]) - coords_back = np.column_stack([back['y'],back['z']]) - + coords_back = np.column_stack([back['y'],back['z']]) + if gtype!='delaunay': # now find the coordinates of the front component closest to the # the coordinates of the back component @@ -900,13 +936,13 @@ def binary_light_curve_synthesis(**parameters): report += ' during eclipse' else: report += ' outside eclipse' - - #-- so now we can easily compute the total intensity as the sum of + + # -- so now we can easily compute the total intensity as the sum of # all visible meshpoints: total_intensity = front['projflux'].sum() + back['projflux'][-in_eclipse].sum() light_curve[di] = total_intensity report += "---> Total intensity: %g "%(total_intensity) - + if di==0: ylim_lc = (0.95*min(prim['projflux'].sum(),secn['projflux'].sum()),1.2*(prim['projflux'].sum()+secn['projflux'].sum())) back['projflux'][in_eclipse] = 0 @@ -914,8 +950,8 @@ def binary_light_curve_synthesis(**parameters): back['vx'][in_eclipse] = 0 back['vy'][in_eclipse] = 0 back['vz'][in_eclipse] = 0 - - #-- now calculate the *real* observed radial velocity and projected intensity + + # -- now calculate the *real* observed radial velocity and projected intensity if front_component==1: RV1_corr[di] = np.average(front['vx']/1000.,weights=front['projflux']) RV2_corr[di] = np.average(back['vx'][-in_eclipse]/1000.,weights=back['projflux'][-in_eclipse]) @@ -923,21 +959,21 @@ def binary_light_curve_synthesis(**parameters): back_cmap = pl.cm.cool_r else: RV2_corr[di] = np.average(front['vx']/1000.,weights=front['projflux']) - RV1_corr[di] = np.average(back['vx'][-in_eclipse]/1000.,weights=back['projflux'][-in_eclipse]) + RV1_corr[di] = np.average(back['vx'][-in_eclipse]/1000.,weights=back['projflux'][-in_eclipse]) front_cmap = pl.cm.cool_r back_cmap = pl.cm.hot report += 'RV1=%.3f, RV2=%.3f'%(RV1_corr[di],RV2_corr[di]) logger.info(report) - + #================ START DEBUGGING PLOTS =================== if direc is not None: - #-- first calculate the size of the picture, and the color scales + # -- first calculate the size of the picture, and the color scales if di==0: size_x = 1.2*(max(prim['y'].ptp(),secn['y'].ptp())/2. + max(ds)) size_y = 1.2*(max(prim['z'].ptp(),secn['z'].ptp())/2. + max(ds) * cos(view_angle)) - vmin_image,vmax_image = 0,max([front['eyeflux'].max(),back['eyeflux'].max()]) + vmin_image,vmax_image = 0,max([front['eyeflux'].max(),back['eyeflux'].max()]) size_top = 1.2*(max(prim['x'].ptp(),secn['x'].ptp())/2. + max(ds)) - + pl.figure(figsize=(16,11)) pl.subplot(221,aspect='equal');pl.title('line of sight intensity') pl.scatter(back['y'],back['z'],c=back['eyeflux'],edgecolors='none',cmap=back_cmap) @@ -946,8 +982,8 @@ def binary_light_curve_synthesis(**parameters): pl.ylim(-size_y,size_y) pl.xlabel('X [semi-major axis]') pl.ylabel('Z [semi-major axis]') - - #-- line-of-sight velocity of the system + + # -- line-of-sight velocity of the system pl.subplot(222,aspect='equal');pl.title('line of sight velocity') if di==0: vmin_rv = min((prim['vx'].min()/1000.+RV1.min()),(prim['vx'].min()/1000.+RV2.min())) @@ -960,8 +996,8 @@ def binary_light_curve_synthesis(**parameters): pl.ylim(-size_y,size_y) pl.xlabel('X [semi-major axis]') pl.ylabel('Z [semi-major axis]') - - #-- top view of the system + + # -- top view of the system pl.subplot(223,aspect='equal');pl.title('Top view') pl.scatter(prim['x'],prim['y'],c=prim['eyeflux'],edgecolors='none',cmap=pl.cm.hot) pl.scatter(secn['x'],secn['y'],c=secn['eyeflux'],edgecolors='none',cmap=pl.cm.cool_r) @@ -969,8 +1005,8 @@ def binary_light_curve_synthesis(**parameters): pl.ylim(-size_top,size_top) pl.xlabel('X [semi-major axis]') pl.ylabel('Y [semi-major axis]') - - #-- light curve and radial velocity curve + + # -- light curve and radial velocity curve pl.subplot(224);pl.title('light curve and RV curve') pl.plot(times[:di+1],2.5*np.log10(light_curve[:di+1]),'k-',label=photband) pl.plot(times[di],2.5*np.log10(light_curve[di]),'ko',ms=10) @@ -978,7 +1014,7 @@ def binary_light_curve_synthesis(**parameters): pl.ylim(2.5*np.log10(ylim_lc[0]),2.5*np.log10(ylim_lc[1])) pl.ylabel('Flux [erg/s/cm2/A/sr]') pl.legend(loc='lower left',prop=dict(size='small')) - + pl.twinx(pl.gca()) # primary radial velocity (numerical, kepler and current) pl.plot(times[:di+1],RV1_corr[:di+1],'b-',lw=2,label='Numeric 1') @@ -997,8 +1033,8 @@ def binary_light_curve_synthesis(**parameters): pl.legend(loc='upper left',prop=dict(size='small')) pl.savefig(os.path.join(direc,'%s_los_%04d'%(name,di)),facecolor='0.75') pl.close() - - #-- REAL IMAGE picture + + # -- REAL IMAGE picture pl.figure(figsize=(7,size_y/size_x*7)) ax = pl.axes([0,0,1,1]) ax.set_aspect('equal') @@ -1012,8 +1048,8 @@ def binary_light_curve_synthesis(**parameters): pl.savefig(os.path.join(direc,'%s_image_%04d'%(name,di)),facecolor='k') pl.close() #================ END DEBUGGING PLOTS =================== - - #-- make sure to have everything + + # -- make sure to have everything if direc is not None: outputfile_prim.close() outputfile_secn.close() @@ -1023,7 +1059,6 @@ def binary_light_curve_synthesis(**parameters): import doctest doctest.testmod() pl.show() - + logger = loggers.get_basic_logger("") import sys - \ No newline at end of file diff --git a/roche/local.py b/roche/local.py index 5085005fd..985fc7cdb 100644 --- a/roche/local.py +++ b/roche/local.py @@ -8,7 +8,7 @@ try: from scipy.spatial import Delaunay except ImportError: - print 'No Delaunay' + print('No Delaunay') from ivs.sed import limbdark from ivs.coordinates import vectors @@ -19,9 +19,9 @@ def get_grid(*args,**kwargs): """ Construct a coordinate grid - + If you give two resolutions, the first is for theta, the second for phi - + @param args: one or two integers indicating number of grid points in theta and phi direction @type args: integer @@ -31,14 +31,14 @@ def get_grid(*args,**kwargs): """ gtype = kwargs.get('gtype','spherical') full = kwargs.get('full',False) - + if 'spher' in gtype.lower(): if len(args)==1: res1 = res2 = args[0] # same resolution for both coordinates else: res1,res2 = args # different resolution - + dtheta = pi/res1 # step size coordinate 1 dphi = pi/res2 # step size coordinate 2 - + #-- full grid or only one quadrant if full: theta0,thetan = dtheta, pi-dtheta @@ -46,17 +46,17 @@ def get_grid(*args,**kwargs): else: theta0,thetan = dtheta/4,pi/2-dtheta/4 phi0,phin = 0,pi-dphi - + theta,phi = np.mgrid[theta0:thetan:res1*1j,phi0:phin:res2*1j] return theta,phi - + elif 'cil' in gtype.lower(): if len(args)==1: res1 = res2 = args[0] # same resolution for both coordinates else: res1,res2 = args # different resolution - + dsintheta = 2./res1 # step size sin(theta) dphi = pi/res2 # step size coordinate 2 - + #-- full grid or only one quadrant if full: sintheta0,sinthetan = -1+dsintheta, 1.-dsintheta @@ -64,15 +64,15 @@ def get_grid(*args,**kwargs): else: sintheta0,sinthetan = dsintheta,1-dsintheta phi0,phin = 0,pi-dphi - + sintheta,phi = np.mgrid[sintheta0:sinthetan:res1*1j,phi0:phin:res2*1j] return np.arccos(sintheta),phi - + elif 'delaunay' in gtype.lower(): - #-- resolution doesn't matter that much anymore + #-- resolution doesn't matter that much anymore if len(args)==1: res1 = res2 = args[0] # same resolution for both coordinates else: res1,res2 = args # different resolution - + u,v = np.random.uniform(size=res1*res2),np.random.uniform(size=res1*res2) phi = 2*pi*u theta = np.arccos(2*v-1) @@ -80,48 +80,48 @@ def get_grid(*args,**kwargs): x = sin(theta)*sin(phi) y = sin(theta)*cos(phi) z = cos(theta) - + points = np.array([x,y,z]).T grid = Delaunay(points) centers = np.zeros((len(grid.convex_hull),3)) for i,indices in enumerate(grid.convex_hull): centers[i] = [x[indices].sum()/3,y[indices].sum()/3,z[indices].sum()/3] theta,phi = np.arccos(centers[:,2]),np.arctan2(centers[:,1],centers[:,0])+pi - + return theta,phi,grid - + elif 'tri' in gtype.lower(): - #-- resolution doesn't matter that much anymore + #-- resolution doesn't matter that much anymore if len(args)==1: res1 = res2 = args[0] # same resolution for both coordinates else: res1,res2 = args # different resolution - + u,v = np.random.uniform(size=res1*res2),np.random.uniform(size=res1*res2) phi = 2*pi*u theta = np.arccos(2*v-1) return theta,phi - - - - - - + + + + + + def stitch_grid(theta,phi,*quant,**kwargs): """ Stitch a grid together that was originally defined on 1 quadrant. - + We add the three other quandrants. """ seamless = kwargs.get('seamless',False) vtype = kwargs.get('vtype',['scalar' for i in quant]) gtype = kwargs.get('gtype','spher') ravel = kwargs.get('ravel',False) - + if gtype == 'spher': #-- basic coordinates alltheta = np.vstack([np.hstack([theta,theta]),np.hstack([theta+pi/2,theta+pi/2])]) allphi = np.vstack([np.hstack([phi,phi+pi]),np.hstack([phi,phi+pi])]) - + #-- all other quantities allquan = [] for i,iquant in enumerate(quant): @@ -143,9 +143,9 @@ def stitch_grid(theta,phi,*quant,**kwargs): allquan.append(np.vstack([np.hstack([top1,top2]),np.hstack([bot1,bot2])])) elif vtype[i]=='vz': allquan.append(np.vstack([np.hstack([top1,top2]),np.hstack([-bot1,-bot2])])) - + out = [alltheta,allphi]+allquan - + #-- for plotting reasons, remove the vertical seam and the the bottom hole. if seamless: #-- vertical seam @@ -154,20 +154,20 @@ def stitch_grid(theta,phi,*quant,**kwargs): #-- fill bottom hole out = [np.vstack([i,i[0]]) for i in out] out[0][-1] += pi - + #-- ravel to 1d arrays if asked for if ravel: out = [i.ravel() for i in out] else: out = [theta,phi] + list(quant) - + return out #} def surface_normals(r,phi,theta,grid,gtype='spher'): """ Numerically compute surface normals of a grid (in absence of analytical alternative). - + Also computes the surface elements, making L{surface_elements} obsolete. """ if gtype=='spher': @@ -177,11 +177,11 @@ def surface_normals(r,phi,theta,grid,gtype='spher'): elif gtype=='triangular': #-- compute the angle between the surface normal and the radius vector x,y,z = vectors.spher2cart_coord(r,phi,theta) - + centers = np.zeros((len(grid.convex_hull),3)) normals = np.zeros((len(grid.convex_hull),3)) sizes = np.zeros(len(grid.convex_hull)) - + #vertx,verty,vertz = points.T #-- compute centers,normals and sizes @@ -198,67 +198,69 @@ def surface_normals(r,phi,theta,grid,gtype='spher'): side1 = [x[indices[1]]-x[indices[0]],y[indices[1]]-y[indices[0]],z[indices[1]]-z[indices[0]]] side2 = [x[indices[2]]-x[indices[0]],y[indices[2]]-y[indices[0]],z[indices[2]]-z[indices[0]]] normals[i] = np.cross(side1,side2) - + #-- make sure the normal is pointed outwards normal_r,normal_phi,normal_theta = vectors.cart2spher(centers.T,normals.T) normal_r = np.abs(normal_r) centers_sph = vectors.cart2spher_coord(*centers.T) normals = np.array(vectors.spher2cart(centers_sph,(normal_r,normal_phi,normal_theta))) - + #-- normalise and compute angles normals_T = normals.T normals = normals_T / vectors.norm(normals_T) #cos_gamma = vectors.cos_angle(a,normals) - print centers.shape,sizes.shape,normals.shape + print(centers.shape,sizes.shape,normals.shape) return centers, sizes, normals#, cos_gamma #{ Derivation of local quantities -def surface_elements((r,mygrid),(surfnormal_x,surfnormal_y,surfnormal_z),gtype='spher'): +def surface_elements(radius_and_mygrid, surface_normals_xyz, gtype='spher'): """ Compute surface area of elements in a grid. - + theta,phi must be generated like mgrid(theta_range,phi_range) - + usually, the surfnormals are acquired via differentiation of a gravity potential, and is then equal to the *negative* of the local surface gravity. """ + (r,mygrid) = radius_and_mygrid + (surfnormal_x,surfnormal_y,surfnormal_z) = surface_normals_xyz theta,phi = mygrid[:2] if gtype=='spher': #-- compute the grid size at each location dtheta = theta[1:]-theta[:-1] dtheta = np.vstack([dtheta,dtheta[-1]]) - + dphi = phi[:,1:]-phi[:,:-1] dphi = np.column_stack([dphi,dphi[:,-1]]) #-- compute the angle between the surface normal and the radius vector x,y,z = vectors.spher2cart_coord(r,phi,theta) - + a = np.array([x,y,z]) b = np.array([surfnormal_x,surfnormal_y,surfnormal_z]) - + cos_gamma = vectors.cos_angle(a,b) - + return r**2 * sin(theta) * dtheta * dphi / cos_gamma, cos_gamma - + elif gtype=='delaunay': #-- compute the angle between the surface normal and the radius vector x,y,z = vectors.spher2cart_coord(r,phi,theta) a = np.array([x,y,z]) - b = np.array([surfnormal_x,surfnormal_y,surfnormal_z]) + b = np.array([surfnormal_x,surfnormal_y,surfnormal_z]) cos_gamma = vectors.cos_angle(a,b) - + delaunay_grid = mygrid[2] sizes = np.zeros(len(delaunay_grid.convex_hull)) points = delaunay_grid.points vertx,verty,vertz = points.T - + #from enthought.mayavi import mlab #mlab.figure() #mlab.triangular_mesh(vertx,verty,vertz,delaunay_grid.convex_hull,scalars=np.ones_like(vertx),colormap='gray',representation='wireframe') #mlab.points3d(x/r,y/r,z/r,scale_factor=0.02) - + centers = np.zeros((len(delaunay_grid.convex_hull),3)) for i,indices in enumerate(delaunay_grid.convex_hull): #centers[i] = [vertx[indices].sum()/3,verty[indices].sum()/3,vertz[indices].sum()/3] @@ -267,19 +269,19 @@ def surface_elements((r,mygrid),(surfnormal_x,surfnormal_y,surfnormal_z),gtype=' c = sqrt((vertx[indices[1]]-vertx[indices[2]])**2 + (verty[indices[1]]-verty[indices[2]])**2 + (vertz[indices[1]]-vertz[indices[2]])**2) s = 0.5*(a+b+c) sizes[i] = sqrt( s*(s-a)*(s-b)*(s-c)) - + #theta,phi = np.arccos(centers[:,2]),np.arctan2(centers[:,1],centers[:,0])+pi #mlab.points3d(centers[:,0],centers[:,1],centers[:,2],sizes,scale_factor=0.05,scale_mode='none',colormap='RdBu') #mlab.show() - + #pl.show() - + return sizes*r**2, cos_gamma def temperature(surface_gravity,g_pole,T_pole,beta=1.): """ Calculate local temperature. - + beta is gravity darkening parameter. """ Grav = abs(surface_gravity/g_pole)**beta @@ -289,25 +291,25 @@ def temperature(surface_gravity,g_pole,T_pole,beta=1.): def intensity(teff,grav,mu=None,photband='OPEN.BOL'): """ Calculate local intensity. - + beta is gravity darkening parameter. """ if mu is None: mu = np.ones_like(teff) if (teff<3500).any() or np.isnan(teff).any(): - print 'WARNING: point outside of grid, minimum temperature is 3500K' + print('WARNING: point outside of grid, minimum temperature is 3500K') teff = np.where((teff<3500) | np.isnan(teff),3500,teff) if (grav<0.01).any() or np.isnan(grav).any(): - print 'WARNING: point outside of grid, minimum gravity is 0 dex' + print('WARNING: point outside of grid, minimum gravity is 0 dex') grav = np.where((np.log10(grav*100)<0.) | np.isnan(grav),0.01,grav) 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())]) return intens.reshape(teff.shape) - + def projected_intensity(teff,gravity,areas,line_of_sight,photband='OPEN.BOL'): """ Compute projected intensity in the line of sight. - + gravity is vector directed inwards in the star line of sight is vector. """ @@ -329,32 +331,32 @@ def project(star,view_long=(0,0,0),view_lat=(pi/2,0,0),photband='OPEN.BOL', only_visible=False,plot_sort=False,scale_factor=1.): """ Project and transform coordinates and vectors to align with the line-of-sight. - + Parameter C{star} should be a record array containing fields 'teff','gravx', 'gravy','gravz','areas','vx','vy','vz' - + and either you suply ('r','theta','phi') or ('x','y','z') - + The XY direction is then the line-of-sight, and the YZ plane is the plane of the sky. - + An extra column 'projflux' and 'eyeflux' will be added. Projected flux takes care of limb darkening, and projected surface area. Eye flux only takes care of limbdarkening, and should only be used for plotting reasons. - + view_long[0] of 0 means looking in the XY line, pi means looking in the YX line. view_lat[0] of pi/2 means edge on, 0 or pi is pole-on. - + This function updates all Cartesian coordinates present in the star, but not the polar coordinates! The projected fluxes are added as a field 'projflux' to the returned record array. - + If you set 'only_visible' to True, only the information on the visible parts of the star will be contained. - + If you set 'plot_sort' to True, the arrays will be returned in a sorted order, where the areas at the back come first. This is especially handy for plotting. - + @parameters star: record array containing all necessary information on the star @type star: numpy record array @@ -380,7 +382,7 @@ def project(star,view_long=(0,0,0),view_lat=(pi/2,0,0),photband='OPEN.BOL', x,y,z = vectors.spher2cart_coord(star['r'].ravel(),star['phi'].ravel(),star['theta'].ravel()) else: x,y,z = star['x'].ravel(),star['y'].ravel(),star['z'].ravel(), - + #-- first we rotate in the XY plane (only for surface coordinates is the # coordinate zeropoint important, the rest are vectors!): x,y = vectors.rotate(x,y,view_long[0],x0=view_long[1],y0=view_long[2]) @@ -397,7 +399,7 @@ def project(star,view_long=(0,0,0),view_lat=(pi/2,0,0),photband='OPEN.BOL', view_vector = np.array([1.,0,0])#np.array([-sin(pi/2),0,-cos(pi/2)]) grav_local = np.array([gravx,gravy,gravz]) proj_flux,mus = projected_intensity(teff,grav_local,areas,view_vector,photband=photband) - + #-- we now construct a copy of the star record array with the changed # coordinates new_star = star.copy() @@ -412,10 +414,10 @@ def project(star,view_long=(0,0,0),view_lat=(pi/2,0,0),photband='OPEN.BOL', new_star = pl.mlab.rec_append_fields(new_star,'projflux',proj_flux) new_star = pl.mlab.rec_append_fields(new_star,'eyeflux',proj_flux/areas) new_star = pl.mlab.rec_append_fields(new_star,'mu',mus) - + #-- clip visible areas and sort in plotting order if necessary if only_visible: new_star = new_star[-np.isnan(new_star['projflux'])] if plot_sort: new_star = new_star[np.argsort(new_star['x'])] - return new_star \ No newline at end of file + return new_star diff --git a/roche/rotation.py b/roche/rotation.py index fd468fd2f..01c380481 100644 --- a/roche/rotation.py +++ b/roche/rotation.py @@ -70,9 +70,9 @@ The total and projected luminosity can then be calculated the following way: ->>> print pi*(ints_local*areas_local*constants.Rsol_cgs**2).sum()/constants.Lsol_cgs +>>> print(pi*(ints_local*areas_local*constants.Rsol_cgs**2).sum()/constants.Lsol_cgs) 0.992471247895 ->>> print pi*np.nansum(intens_proj*areas_local*constants.Rsol_cgs**2)/constants.Lsol_cgs +>>> print(pi*np.nansum(intens_proj*areas_local*constants.Rsol_cgs**2)/constants.Lsol_cgs) 0.360380373413 Now make some plots showing the local quantities: @@ -80,7 +80,7 @@ >>> quantities = areas_local,np.log10(grav*100),teff_local,ints_local,intens_proj,angles/pi*180,radius >>> names = 'Area','log g', 'Teff', 'Flux', 'Proj. flux', 'Angle' >>> p = pl.figure() ->>> rows,cols = 2,3 +>>> rows,cols = 2,3 >>> for i,(quantity,name) in enumerate(zip(quantities,names)): ... p = pl.subplot(rows,cols,i+1) ... p = pl.title(name) @@ -202,7 +202,7 @@ >>> quantities = areas_local,np.log10(grav*100),teff_local,ints_local,intens_proj,angles/pi*180,radius >>> names = 'Area','log g', 'Teff', 'Flux', 'Proj. flux', 'Angle' >>> p = pl.figure() ->>> rows,cols = 2,3 +>>> rows,cols = 2,3 >>> for i,(quantity,name) in enumerate(zip(quantities,names)): ... p = pl.subplot(rows,cols,i+1) ... p = pl.title(name) @@ -246,13 +246,13 @@ def fastrot_roche_surface_gravity(r,theta,phi,r_pole,omega,M,norm=False): """ Calculate components of the local surface gravity of the fast rotating Roche model. - + Input units are solar units. Output units are SI. Omega is fraction of critical velocity. - + See Cranmer & Owocki, Apj (1995) - + @param r: radius of the surface element to calculate the surface gravity @type r: float/ndarray @param theta: colatitude of surface element @@ -276,7 +276,7 @@ def fastrot_roche_surface_gravity(r,theta,phi,r_pole,omega,M,norm=False): grav_r = GG*M/r_pole**2 * (-1./x**2 + 8./27.*x*omega**2*sin(theta)**2) #-- calculate theta-component of local gravity grav_th = GG*M/r_pole**2 * (8./27.*x*omega**2*sin(theta)*cos(theta)) - + grav = np.array([grav_r,grav_th]) #-- now we transform to spherical coordinates grav = np.array(vectors.spher2cart( (r,phi,theta),(grav[0],0.,grav[1]) )) @@ -289,7 +289,7 @@ def fastrot_roche_surface_gravity(r,theta,phi,r_pole,omega,M,norm=False): def get_fastrot_roche_radius(theta,r_pole,omega): """ Calculate Roche radius for a fast rotating star. - + @param theta: angle from rotation axis @type theta: float @param r_pole: polar radius in solar units @@ -305,23 +305,23 @@ def get_fastrot_roche_radius(theta,r_pole,omega): if np.isinf(Rstar) or sin(theta)<1e-10: Rstar = r_pole return Rstar - + def critical_angular_velocity(M,R_pole,units='Hz'): """ Compute the critical angular velocity (Hz). - + Definition taken from Cranmer and Owocki, 1995 and equal to - + Omega_crit = sqrt( 8GM / 27Rp**3 ) - + Example usage (includes conversion to period in days): - + >>> Omega = critical_angular_velocity(1.,1.) >>> P = 2*pi/Omega >>> P = conversions.convert('s','d',P) - >>> print 'Critical rotation period of the Sun: %.3f days'%(P) - Critical rotation period of the Sun: 0.213 days - + >>> print('Critical rotation period of the Sun: %.3f days'%(P)) + Critical rotation period of the Sun: 0.213 days) + @param M: mass (solar masses) @type M: float @param R_pole: polar radius (solar radii) @@ -341,22 +341,22 @@ def critical_angular_velocity(M,R_pole,units='Hz'): def critical_velocity(M,R_pole,units='km/s',definition=1): """ Compute the critical velocity (km/s) - + Definition 1 from Cranmer and Owocki, 1995: - + v_c = 2 pi R_eq(omega_c) * omega_c - + Definition 2 from Townsend 2004: - + v_c = sqrt ( 2GM/3Rp ) - + which both amount to the same value: - + >>> critical_velocity(1.,1.,definition=1) 356.71131858379499 >>> critical_velocity(1.,1.,definition=2) 356.71131858379488 - + @param M: mass (solar masses) @type M: float @param R_pole: polar radius (solar radii) @@ -374,7 +374,7 @@ def critical_velocity(M,R_pole,units='km/s',definition=1): elif definition==2: veq = np.sqrt( 2*constants.GG * M*constants.Msol / (3*R_pole*constants.Rsol)) veq = conversions.convert('m/s',units,veq) - + return veq @@ -384,21 +384,21 @@ def critical_velocity(M,R_pole,units='km/s',definition=1): def diffrot_roche_potential(r,theta,r_pole,M,omega_eq,omega_pole): """ Definition of Roche potential due to differentially rotating star - + We first solve the cubic equation - + M{re/rp = 1 + f (x^2 + x + 1)/(6x^2)} - + where M{f = re^3 Omega_e^2 / (G M)} and M{x = Omega_e / Omega_p} - + This transforms to solving - + M{re^3 + b * re + c = 0} - + where M{b = -1 / (aXrp) and c = 1/(aX),} and M{a = Omega_e^2/(GM) and X = (x^2 + x + 1)/(6x^2)} - + @param r: radius of the surface element to calculate the surface gravity @type r: float/ndarray @param theta: colatitude of surface element @@ -415,12 +415,12 @@ def diffrot_roche_potential(r,theta,r_pole,M,omega_eq,omega_pole): @rtype: float/ndarray """ GG = constants.GG_sol - + Omega_crit = sqrt(8*GG*M/ (27*r_pole**3)) omega_eq = omega_eq*Omega_crit omega_pole = omega_pole*Omega_crit x = omega_eq / omega_pole - + #-- find R_equator solving a cubic equation: a = omega_eq**2/(GG*M) X = (x**2+x+1)/(6*x**2) @@ -435,7 +435,7 @@ def diffrot_roche_potential(r,theta,r_pole,M,omega_eq,omega_pole): x2 = -1./3. * ( om2*c1 + om1*c2 ) x3 = -1./3. * ( om1*c1 + om2*c2 ) re = x2.real - + # ratio of centrifugal to gravitational force at the equator f = re**3 * omega_eq**2 / (GG*M) # ratio Re/Rp @@ -453,9 +453,9 @@ def diffrot_roche_potential(r,theta,r_pole,M,omega_eq,omega_pole): def diffrot_roche_surface_gravity(r,theta,phi,r_pole,M,omega_eq,omega_pole,norm=False): """ Surface gravity from differentially rotation Roche potential. - + Magnitude is OK, please carefully check direction. - + @param r: radius of the surface element to calculate the surface gravity @type r: float/ndarray @param theta: colatitude of surface element @@ -480,7 +480,7 @@ def diffrot_roche_surface_gravity(r,theta,phi,r_pole,M,omega_eq,omega_pole,norm= omega_eq = omega_eq*Omega_crit omega_pole = omega_pole*Omega_crit x = omega_eq / omega_pole - + #-- find R_equator solving a cubic equation: a = omega_eq**2/(GG*M) X = (x**2+x+1)/(6*x**2) @@ -495,7 +495,7 @@ def diffrot_roche_surface_gravity(r,theta,phi,r_pole,M,omega_eq,omega_pole,norm= x2 = -1./3. * ( om2*c1 + om1*c2 ) x3 = -1./3. * ( om1*c1 + om2*c2 ) re = x2.real - + # ratio of centrifugal to gravitational force at the equator f = re**3 * omega_eq**2 / (GG*M) # ratio Re/Rp @@ -509,16 +509,16 @@ def diffrot_roche_surface_gravity(r,theta,phi,r_pole,M,omega_eq,omega_pole,norm= y = r/r_pole grav_th = (6*alpha*y**7*sinth**5 + 4*beta*y**5*sinth**3 + 2*gamma*y**3*sinth)*cos(theta) grav_r = 7*alpha/r_pole*y**6*sinth**6 + 5*beta/r_pole*y**4*sinth**4 + 3*gamma/r_pole*y**2*sinth**2 - 1./r_pole - + fr = 6*alpha*y**7*sinth**4 + 4*beta*y**5*sinth**2 + 2*gamma*y**3 magn = GG*M/r**2 * sqrt(cos(theta)**2 + (1-fr)**2 *sin(theta)**2) magn_fake = np.sqrt(grav_r**2+(grav_th/r)**2) grav_r,grav_th = grav_r/magn_fake*magn,(grav_th/r)/magn_fake*magn - + grav = np.array([grav_r*constants.Rsol,grav_th*constants.Rsol]) #-- now we transform to spherical coordinates grav = vectors.spher2cart( (r,phi,theta),(grav[0],0.,grav[1]) ) - + if norm: return vectors.norm(grav) else: @@ -528,7 +528,7 @@ def diffrot_roche_surface_gravity(r,theta,phi,r_pole,M,omega_eq,omega_pole,norm= def get_diffrot_roche_radius(theta,r_pole,M,omega_eq,omega_pole): """ Calculate Roche radius for a differentially rotating star. - + @param theta: angle from rotation axis @type theta: float @param r_pole: polar radius in solar units @@ -545,21 +545,21 @@ def get_diffrot_roche_radius(theta,r_pole,M,omega_eq,omega_pole): try: r = newton(diffrot_roche_potential,r_pole,args=(theta,r_pole,M,omega_eq,omega_pole)) except RuntimeError: - r = np.nan + r = np.nan return r def diffrot_law(omega_eq,omega_pole,theta): """ Evaluate a differential rotation law of the form Omega = b1+b2*omega**2 - + The relative differential rotation rate is the ratio of the rotational shear to the equatorial velocity - + alpha = omega_eq - omega_pole / omega_eq - + The units of angular velocity you put in, you get out (i.e. in terms of the critical angular velocity or not). - + @param omega_eq: equatorial angular velocity @type omega_eq: float @param omega_pole: polar angular velocity @@ -578,7 +578,7 @@ def diffrot_law(omega_eq,omega_pole,theta): def diffrot_velocity(coordinates,omega_eq,omega_pole,R_pole,M): """ Calculate the velocity vector of every surface element. - + @param coordinates: polar coordinates of stellar surface (phi,theta,radius) make sure the radius is in SI units! @type coordinates: 3xN array @@ -599,7 +599,7 @@ def diffrot_velocity(coordinates,omega_eq,omega_pole,R_pole,M): omega_local = diffrot_law(omega_eq,omega_pole,theta)*Omega_crit #-- direction of local angular velocity in Cartesian coordinates (directed in upwards z) omega_local_vec = np.array([np.zeros_like(omega_local),np.zeros_like(omega_local),omega_local]).T - + x,y,z = vectors.spher2cart_coord(radius,phi,theta) surface_element = np.array([x,y,z]).T diff --git a/roche/test_binary.py b/roche/test_binary.py new file mode 100644 index 000000000..79989d816 --- /dev/null +++ b/roche/test_binary.py @@ -0,0 +1,95 @@ +"""Module test_binary - """ + +import binary +import logging +import numpy as np + +logging.basicConfig(level=logging.DEBUG) +no_assert = 'There is currently no Assert check implemented' + +# global test values +x = 1 +y = 1 +z = 1 +r = 1 +r_pole = 1 +theta = 0 +phi = 0 +Phi = 0 +d = 1 +F = 1 +omega = 1 +M1 = 1 +M2 = 1 +q = 1 +booleans = [True, False] +star = np.rec.fromarrays([[1,1,1,1]]*16, names=['theta', 'phi', 'r', 'x', + 'y', 'z', 'vx', 'vy', 'vz', + 'gravx', 'gravy', 'gravz', + 'grav', 'areas', 'teff', + 'flux'] + ) + + +class TestRochePotential(object): + + def test_print_tester(self): + name = 'abc' + assert binary.print_tester(name) == 'ABC' + + def test_binary_roche_potential(self): + log = logging.getLogger('roche_potential') + + binary.binary_roche_potential(r, theta, phi, Phi, q, d, F) + log.debug(no_assert) + # print(no_assert) + + def test_binary_roche_potential_gradient(self): + log = logging.getLogger('roche_potential_gradient') + + for norm in booleans: + binary.binary_roche_potential_gradient(x, y, z, q, d, F, norm) + + log.debug('For case norm = {}'.format(norm)) + log.debug(no_assert) + # log.debug() + + def test_binary_roche_surface_gravity(self): + log = logging.getLogger('roche_surface_gravity') + + for norm in booleans: + binary.binary_roche_surface_gravity(x, y, z, d, omega, M1, M2, + norm) + log.debug('For case norm = {}'.format(norm)) + log.debug(no_assert) + + def test_get_binary_roche_radius(self): + log = logging.getLogger('get_binary_roche_radius') + + binary.get_binary_roche_radius(theta, phi, Phi, q, d, F, r_pole) + log.debug(no_assert) + + def test_reflection_effect(self): + log = logging.getLogger('reflection_effect') + + binary.reflection_effect(star, star, theta, phi) + lof.debug(no_assert) + + def test_spectral_synthesis(test): + log = logging.getLogger('spectral_synthesis') + + binary.spectral_synthesis(star) + lof.debug(no_assert) + + def test_binary_light_curve_synthesis(test): + log = logging.getLogger('binary_light_curve_synthesis') + parameters = {} + parameters['Tpole1'] = 1 # Primary Polar temperature [K] + parameters['Tpole2'] = 1 # Secondary Polar temperature [K] + parameters['P'] = 1 # Period [days] + parameters['asini'] = 1 # total semi-major axis*sini [AU] + parameters['Phi1'] = 0 # Gravitational potential of primary [-] + parameters['Phi2'] = 0 + + binary.binary_light_curve_synthesis(parameters=parameters) + lof.debug(no_assert) diff --git a/sed/builder.py b/sed/builder.py index 9f7a44b87..28c681204 100644 --- a/sed/builder.py +++ b/sed/builder.py @@ -1,562 +1,8 @@ # -*- coding: utf-8 -*- """ SED builder program. - -To construct an SED, use the SED class. The functions defined in this module -are mainly convenience functions specifically for that class, but can be used -outside of the SED class if you know what you're doing. - -Table of contents: - - 1. Retrieving and plotting photometry of a target - 2. Where/what is my target? - 3. SED fitting using a grid based approach - - Binar star ### W.I.P ### - - Saving SED fits - - Loading SED fits ### BROKEN ### - 4. Accessing the best fitting full SED model - 5. Radii, distances and luminosities - - Relations between quantities - - Parallaxes - - Seismic constraints - - Reddening constraints - - Evolutionary constraints - -Section 1. Retrieving and plotting photometry of a target -========================================================= - ->>> mysed = SED('HD180642') ->>> mysed.get_photometry() ->>> mysed.plot_data() - -and call Pylab's C{show} function to show to the screen: - -]]include figure]]ivs_sed_builder_example_photometry.png] - -Catch IndexErrors and TypeErrors in case no photometry is found. - -You can give a B{search radius} to C{get_photometry} via the keyword C{radius}. -The default value is 10 arcseconds for stars dimmer than 6th magnitude, and 60 -arcseconds for brighter stars. The best value of course depends on the density -of the field. - ->>> mysed.get_photometry(radius=5.) - -If your star's name is not recognised by any catalog, you can give coordinates -to look for photometry. In that case, the ID of the star given in the C{SED} -command will not be used to search photometry (only to save the phot file): - ->>> mysed.get_photometry(ra=289.31167983,dec=1.05941685) - -Note that C{ra} and C{dec} are given in B{degrees}. - -You best B{switch on the logger} (see L{ivs.aux.loggers.get_basic_logger}) to see the progress: -sometimes, access to catalogs can take a long time (the GATOR sources are -typically slow). If one of the C{gator}, C{vizier} or C{gcpd} is impossibly slow -or the site is down, you can B{include/exclude these sources} via the keywords -C{include} or C{exclude}, which take a list of strings (choose from C{gator}, -C{vizier} and/or C{gcpd}). For ViZieR, there is an extra option to change to -another mirror site via - ->>> vizier.change_mirror() - -The L{vizier.change_mirror} function cycles through all the mirrors continuously, -so sooner or later you will end up with the default one and repeat the cycle. - ->>> mysed.get_photometry(exclude=['gator']) - -The results will be written to the file B{HD180642.phot}. An example content is:: - - # meas e_meas flag unit photband source _r _RAJ2000 _DEJ2000 cwave cmeas e_cmeas cunit color include - #float64 float64 |S20 |S30 |S30 |S50 float64 float64 float64 float64 float64 float64 |S50 bool bool - 7.823 0.02 nan mag WISE.W3 wise_prelim_p3as_psd 0.112931 2.667e-05 2.005e-05 123337 4.83862e-17 8.91306e-19 erg/s/cm2/AA 0 1 - 7.744 0.179 nan mag WISE.W4 wise_prelim_p3as_psd 0.112931 2.667e-05 2.005e-05 222532 4.06562e-18 6.70278e-19 erg/s/cm2/AA 0 1 - 7.77 0.023 nan mag WISE.W1 wise_prelim_p3as_psd 0.112931 2.667e-05 2.005e-05 33791.9 6.378e-15 1.3511e-16 erg/s/cm2/AA 0 1 - 7.803 0.02 nan mag WISE.W2 wise_prelim_p3as_psd 0.112931 2.667e-05 2.005e-05 46293 1.82691e-15 3.36529e-17 erg/s/cm2/AA 0 1 - 8.505 0.016 nan mag TYCHO2.BT I/259/tyc2 0.042 7.17e-06 1.15e-06 4204.4 2.76882e-12 4.08029e-14 erg/s/cm2/AA 0 1 - 8.296 0.013 nan mag TYCHO2.VT I/259/tyc2 0.042 7.17e-06 1.15e-06 5321.86 1.93604e-12 2.31811e-14 erg/s/cm2/AA 0 1 - 8.27 nan nan mag JOHNSON.V II/168/ubvmeans 0.01 1.7e-07 3.15e-06 5504.67 1.80578e-12 1.80578e-13 erg/s/cm2/AA 0 1 - 0.22 nan nan mag JOHNSON.B-V II/168/ubvmeans 0.01 1.7e-07 3.15e-06 nan 1.40749 0.140749 flux_ratio 1 0 - -0.66 nan nan mag JOHNSON.U-B II/168/ubvmeans 0.01 1.7e-07 3.15e-06 nan 1.21491 0.121491 flux_ratio 1 0 - 8.49 nan nan mag JOHNSON.B II/168/ubvmeans 0.01 1.7e-07 3.15e-06 4448.06 2.54162e-12 2.54162e-13 erg/s/cm2/AA 0 1 - 7.83 nan nan mag JOHNSON.U II/168/ubvmeans 0.01 1.7e-07 3.15e-06 3641.75 3.08783e-12 3.08783e-13 erg/s/cm2/AA 0 1 - 2.601 nan nan mag STROMGREN.HBN-HBW J/A+A/528/A148/tables 0.54 -5.983e-05 -0.00013685 nan 1.66181 0.166181 flux_ratio 1 0 - -0.043 nan nan mag STROMGREN.M1 J/A+A/528/A148/tables 0.54 -5.983e-05 -0.00013685 nan 0.961281 0.0961281 flux_ratio 1 0 - 8.221 nan nan mag STROMGREN.Y J/A+A/528/A148/tables 0.54 -5.983e-05 -0.00013685 5477.32 1.88222e-12 1.88222e-13 erg/s/cm2/AA 0 1 - 0.009 nan nan mag STROMGREN.C1 J/A+A/528/A148/tables 0.54 -5.983e-05 -0.00013685 nan 0.93125 0.093125 flux_ratio 1 0 - 0.238 nan nan mag STROMGREN.B-Y J/A+A/528/A148/tables 0.54 -5.983e-05 -0.00013685 nan 1.28058 0.128058 flux_ratio 1 0 - 8.459 nan nan mag STROMGREN.B J/A+A/528/A148/tables 0.54 -5.983e-05 -0.00013685 4671.2 2.41033e-12 2.41033e-13 erg/s/cm2/AA 0 1 - 8.654 nan nan mag STROMGREN.V J/A+A/528/A148/tables 0.54 -5.983e-05 -0.00013685 4108.07 2.96712e-12 2.96712e-13 erg/s/cm2/AA 0 1 - 8.858 nan nan mag STROMGREN.U J/A+A/528/A148/tables 0.54 -5.983e-05 -0.00013685 3462.92 3.40141e-12 3.40141e-13 erg/s/cm2/AA 0 1 - 7.82 0.01 nan mag JOHNSON.J J/PASP/120/1128/catalog 0.02 1.017e-05 3.15e-06 12487.8 2.36496e-13 2.36496e-15 erg/s/cm2/AA 0 1 - 7.79 0.01 nan mag JOHNSON.K J/PASP/120/1128/catalog 0.02 1.017e-05 3.15e-06 21951.2 3.24868e-14 3.24868e-16 erg/s/cm2/AA 0 1 - 7.83 0.04 nan mag JOHNSON.H J/PASP/120/1128/catalog 0.02 1.017e-05 3.15e-06 16464.4 8.64659e-14 3.18552e-15 erg/s/cm2/AA 0 1 - 8.3451 0.0065 nan mag HIPPARCOS.HP I/239/hip_main 0.036 2.17e-06 1.15e-06 5275.11 1.91003e-12 1.91003e-14 erg/s/cm2/AA 0 1 - 8.525 0.011 nan mag TYCHO2.BT I/239/hip_main 0.036 2.17e-06 1.15e-06 4204.4 2.71829e-12 2.754e-14 erg/s/cm2/AA 0 1 - 8.309 0.012 nan mag TYCHO2.VT I/239/hip_main 0.036 2.17e-06 1.15e-06 5321.86 1.913e-12 2.11433e-14 erg/s/cm2/AA 0 1 - 8.02 0.057 nan mag COUSINS.I II/271A/patch2 0.2 -4.983e-05 2.315e-05 7884.05 7.51152e-13 3.94347e-14 erg/s/cm2/AA 0 1 - 8.287 0.056 nan mag JOHNSON.V II/271A/patch2 0.2 -4.983e-05 2.315e-05 5504.67 1.77773e-12 9.16914e-14 erg/s/cm2/AA 0 1 - 8.47 nan nan mag USNOB1.B1 I/284/out 0.026 7.17e-06 1.5e-07 4448.06 2.57935e-12 7.73805e-13 erg/s/cm2/AA 0 1 - 8.19 nan nan mag USNOB1.R1 I/284/out 0.026 7.17e-06 1.5e-07 6939.52 1.02601e-12 3.07803e-13 erg/s/cm2/AA 0 1 - 8.491 0.012 nan mag JOHNSON.B I/280B/ascc 0.036 2.17e-06 2.15e-06 4448.06 2.53928e-12 2.80651e-14 erg/s/cm2/AA 0 1 - 8.274 0.013 nan mag JOHNSON.V I/280B/ascc 0.036 2.17e-06 2.15e-06 5504.67 1.79914e-12 2.15419e-14 erg/s/cm2/AA 0 1 - 7.816 0.023 nan mag 2MASS.J II/246/out 0.118 -2.783e-05 1.715e-05 12412.1 2.28049e-13 4.83093e-15 erg/s/cm2/AA 0 1 - 7.792 0.021 nan mag 2MASS.KS II/246/out 0.118 -2.783e-05 1.715e-05 21909.2 3.26974e-14 6.32423e-16 erg/s/cm2/AA 0 1 - 7.825 0.042 nan mag 2MASS.H II/246/out 0.118 -2.783e-05 1.715e-05 16497.1 8.48652e-14 3.28288e-15 erg/s/cm2/AA 0 1 - 8.272 0.017 nan mag GENEVA.V GCPD nan nan nan 5482.6 1.88047e-12 2.94435e-14 erg/s/cm2/AA 0 1 - 1.8 0.004 nan mag GENEVA.G-B GCPD nan nan nan nan 0.669837 0.00669837 flux_ratio 1 0 - 1.384 0.004 nan mag GENEVA.V1-B GCPD nan nan nan nan 0.749504 0.00749504 flux_ratio 1 0 - 0.85 0.004 nan mag GENEVA.B1-B GCPD nan nan nan nan 1.05773 0.0105773 flux_ratio 1 0 - 1.517 0.004 nan mag GENEVA.B2-B GCPD nan nan nan nan 0.946289 0.00946289 flux_ratio 1 0 - 0.668 0.004 nan mag GENEVA.V-B GCPD nan nan nan nan 0.726008 0.00726008 flux_ratio 1 0 - 0.599 0.004 nan mag GENEVA.U-B GCPD nan nan nan nan 1.13913 0.0113913 flux_ratio 1 0 - 7.604 0.0174642 nan mag GENEVA.B GCPD nan nan nan 4200.85 2.59014e-12 4.16629e-14 erg/s/cm2/AA 0 1 - 9.404 0.0179165 nan mag GENEVA.G GCPD nan nan nan 5765.89 1.73497e-12 2.863e-14 erg/s/cm2/AA 0 1 - 8.988 0.0179165 nan mag GENEVA.V1 GCPD nan nan nan 5395.63 1.94132e-12 3.20351e-14 erg/s/cm2/AA 0 1 - 8.454 0.0179165 nan mag GENEVA.B1 GCPD nan nan nan 4003.78 2.73968e-12 4.52092e-14 erg/s/cm2/AA 0 1 - 9.121 0.0179165 nan mag GENEVA.B2 GCPD nan nan nan 4477.56 2.45102e-12 4.0446e-14 erg/s/cm2/AA 0 1 - 8.203 0.0179165 nan mag GENEVA.U GCPD nan nan nan 3421.62 2.95052e-12 4.86885e-14 erg/s/cm2/AA 0 1 - 8.27 nan nan mag JOHNSON.V GCPD nan nan nan 5504.67 1.80578e-12 1.80578e-13 erg/s/cm2/AA 0 1 - 0.22 nan nan mag JOHNSON.B-V GCPD nan nan nan nan 1.40749 0.140749 flux_ratio 1 0 - -0.66 nan nan mag JOHNSON.U-B GCPD nan nan nan nan 1.21491 0.121491 flux_ratio 1 0 - 8.49 nan nan mag JOHNSON.B GCPD nan nan nan 4448.06 2.54162e-12 2.54162e-13 erg/s/cm2/AA 0 1 - 7.83 nan nan mag JOHNSON.U GCPD nan nan nan 3641.75 3.08783e-12 3.08783e-13 erg/s/cm2/AA 0 1 - -0.035 nan nan mag STROMGREN.M1 GCPD nan nan nan nan 0.954224 0.0954224 flux_ratio 1 0 - 0.031 nan nan mag STROMGREN.C1 GCPD nan nan nan nan 0.91257 0.091257 flux_ratio 1 0 - 0.259 nan nan mag STROMGREN.B-Y GCPD nan nan nan nan 1.25605 0.125605 flux_ratio 1 0 - -0.009 0.0478853 nan mag 2MASS.J-H II/246/out 0.118 -2.783e-05 1.715e-05 nan 2.68719 0.118516 flux_ratio 1 0 - -0.033 0.0469574 nan mag 2MASS.KS-H II/246/out 0.118 -2.783e-05 1.715e-05 nan 0.385286 0.0166634 flux_ratio 1 0 - -0.01 0.0412311 nan mag JOHNSON.J-H J/PASP/120/1128/catalog 0.02 1.017e-05 3.15e-06 nan 2.73514 0.103867 flux_ratio 1 0 - -0.04 0.0412311 nan mag JOHNSON.K-H J/PASP/120/1128/catalog 0.02 1.017e-05 3.15e-06 nan 0.375718 0.014268 flux_ratio 1 0 - 0.209 0.0206155 nan mag TYCHO2.BT-VT I/259/tyc2 0.042 7.17e-06 1.15e-06 nan 1.43014 0.027155 flux_ratio 1 0 - -Once a .phot file is written and L{get_photometry} is called again for the same -target, the script will B{not retrieve the photometry from the internet again}, -but will use the contents of the file instead. The purpose is minimizing network -traffic and maximizing speed. If you want to refresh the search, simply manually -delete the .phot file or set C{force=True} when calling L{get_photometry}. - -The content of the .phot file is most easily read using the L{ivs.inout.ascii.read2recarray} -function. Be careful, as it contains both absolute fluxes as flux ratios. - ->>> data = ascii.read2recarray('HD180642.phot') - -Notice that in the C{.phot} files, also a C{comment} column is added. You can -find translation of some of the flags here (i.e. upper limit, extended source etc..), -or sometimes just additional remarks on variability etc. Not all catalogs have -this feature implemented, so you are still responsible yourself for checking -the quality of the photometry. - -The references to each source are given in the C{bibtex} column. Simply call - ->>> mysed.save_bibtex() - -to convert those bibcodes to a C{.bib} file. - -Using L{SED.plot_MW_side} and L{SED.plot_MW_top}, you can make a picture of where -your star is located with respect to the Milky Way and the Sun. With L{SED.plot_finderchart}, -you can check the location of your photometry, and also see if proper motions -etc are available. - -Section 2. Where/what is my target? -=================================== - -To give you some visual information on the target, the following plotting -procedure might be of some help. - -To check whether the downloaded photometry is really belonging to the target, -instead of some neighbouring star (don't forget to set C{radius} when looking -for photometry!), you can generate a finderchart with the location of the -downloaded photometry overplotted. On top of that, proper motion data is added -when available, as well as radial velocity data. When a distance is available, -the proper motion velocity will be converted to a true tangential velocity. - ->>> p = pl.figure();mysed.plot_finderchart(window_size=1) - -]]include figure]]ivs_sed_builder_finderchart.png] - -To know the location of your target wrt the Milky Way (assuming your target is -in the milky way), you can call - ->>> p = pl.figure();mysed.plot_MW_side() ->>> p = pl.figure();mysed.plot_MW_top() - -]]include figure]]ivs_sed_builder_MWside.png] - -]]include figure]]ivs_sed_builder_MWtop.png] - -Section 3. SED fitting using a grid based approach -================================================== - -Subsection 3.1 Single star --------------------------- - -We make an SED of HD180642 by simply B{exploring a whole grid of Kurucz models} -(constructed via L{fit.generate_grid}, iterated over via L{fit.igrid_search} and -evaluated with L{fit.stat_chi2}). The model with the best parameters is picked -out and we make a full SED with those parameters. - ->>> mysed = SED('HD180642') ->>> mysed.get_photometry() - -Now we have collected B{all fluxes and colors}, but we do not want to use them -all: first, B{fluxes and colors are not independent}, so you probably want to use -either only absolute fluxes or only colors (plus perhaps one absolute flux per -system to compute the angular diameter) (see L{SED.set_photometry_scheme}). Second, -B{some photometry is better suited for fitting an SED than others}; typically IR -photometry does not add much to the fitting of massive stars, or it can be -contaminated with circumstellar material. Third, B{some photometry is not so -reliable}, i.e. the measurements can have large systematic uncertainties due to -e.g. calibration effects, which are typically not included in the error bars. - -Currently, four standard schemes are implemented, which you can set via L{SED.set_photometry_scheme}: - - 1. C{absolute}: use only absolute values - 2. C{colors}: use only colors (no angular diameter values calculated) - 3. C{combo}: use all colors and one absolute value per photometric system - 4. C{irfm}: (infrared flux method) use colors for wavelengths shorter than - infrared wavelengths, and absolute values for systems in the infrared. The - infrared is defined as wavelength longer than 1 micron, but this can be - customized with the keyword C{infrared=(value,unit)} in - L{SED.set_photometry_scheme}. - -Here, we chose to use colors and one absolute flux per system, but exclude IR -photometry (wavelength range above 2.5 micron), and some systems and colors which -we know are not so trustworthy: - ->>> mysed.set_photometry_scheme('combo') ->>> mysed.exclude(names=['STROMGREN.HBN-HBW','USNOB1','SDSS','DENIS','COUSINS','ANS','TD1'],wrange=(2.5e4,1e10)) - -You can L{include}/L{exclude} photoemtry based on name, wavelength range, source and index, -and only select absolute photometry or colors (L{include_abs},L{include_colors}). -When working in interactive mode, in particular the index is useful. Print the -current set of photometry to the screen with - ->>> print(photometry2str(mysed.master,color=True,index=True)) - -and you will see in green the included photometry, and in red the excluded photometry. -You will see that each column is preceded by an index, you can use these indices -to select/deselect the photometry. - -Speed up the fitting process by copying the model grids to the scratch disk - ->>> model.copy2scratch(z='*') - -Start the grid based fitting process and show some plots. We use 100000 randomly -distributed points over the grid: - ->>> mysed.igrid_search(points=100000) - -Delete the model grids from the scratch disk - ->>> model.clean_scratch() - -and make the plot - ->>> p = pl.figure() ->>> p = pl.subplot(131);mysed.plot_sed() ->>> p = pl.subplot(132);mysed.plot_grid(limit=None) ->>> p = pl.subplot(133);mysed.plot_grid(x='ebv',y='z',limit=None) - -]]include figure]]ivs_sed_builder_example_fitting01.png] - -The grid is a bit too coarse for our liking around the minimum, so we zoom in on -the results: - ->>> teffrange = mysed.results['igrid_search']['CI']['teffL'],mysed.results['igrid_search']['CI']['teffU'] ->>> loggrange = mysed.results['igrid_search']['CI']['loggL'],mysed.results['igrid_search']['CI']['loggU'] ->>> ebvrange = mysed.results['igrid_search']['CI']['ebvL'],mysed.results['igrid_search']['CI']['ebvU'] ->>> mysed.igrid_search(points=100000,teffrange=teffrange,loggrange=loggrange,ebvrange=ebvrange) - -and repeat the plot - ->>> p = pl.figure() ->>> p = pl.subplot(131);mysed.plot_sed(plot_deredded=True) ->>> p = pl.subplot(132);mysed.plot_grid(limit=None) ->>> p = pl.subplot(133);mysed.plot_grid(x='ebv',y='z',limit=None) - -]]include figure]]ivs_sed_builder_example_fitting02.png] - -You can automatically make plots of (most plotting functions take C{colors=True/False} -as an argument so you can make e.g. the 'color' SED and 'absolute value' SED): - - 1. the grid (see L{SED.plot_grid}) - 2. the SED (see L{SED.plot_sed}) - 3. the fit statistics (see L{SED.plot_chi2}) - -To change the grid, load the L{ivs.sed.model} module and call -L{ivs.sed.model.set_defaults} with appropriate arguments. See that module for -conventions on the grid structure when adding custom grids. - -To add arrays manually, i.e. not from the predefined set of internet catalogs, -use the L{SED.add_photometry_fromarrays} function. - -B{Warning}: Be careful when interpreting the Chi2 results. In order to always have a -solution, the chi2 is rescaled so that the minimum equals 1, in the case the -probability of the best chi2-model is zero. The Chi2 rescaling factor I{f} mimicks -a rescaling of all errorbars with a factor I{sqrt(f)}, and does not discriminate -between systems (i.e., B{all} errors are blown up). If the errorbars are -underestimated, it could be that the rescaling factor is also wrong, which means -that the true probability region can be larger or smaller! - -Subsection 3.2 Binary star - ### W.I.P ### ------------------------------------------- - -The SED class can create SEDs for multiple stars as well. There are 2 options -available, the multiple SED fit which in theory can handle any number of stars, -and the binary SED fit which is for binaries only, and uses the mass of both -components to restrict the radii when combining two model SEDs. - -As an example we take the system PG1104+243, which consists of a subdwarf B star, -and a G2 type mainsequence star. The photometry from the standard catalogues that -are build in this class, is of to low quality, so we use photometry obtained from -U{the subdwarf database}. - ->>> mysed = SED('PG1104+243') - ->>> meas, e_meas, units, photbands, source = ascii.read2array('pg1104+243_sddb.phot', dtype=str) ->>> meas = np.array(meas, dtype=float) ->>> e_meas = np.array(e_meas, dtype=float) ->>> mysed.add_photometry_fromarrays(meas, e_meas, units, photbands, source) - -We use only the absolute fluxes - ->>> mysed.set_photometry_scheme('abs') - -For the main sequence component we use kurucz models with solar metalicity, and -for the sdB component tmap models. And we copy the model grids to the scratch disk -to speed up the process: - ->>> grid1 = dict(grid='kurucz',z=+0.0) ->>> grid2 = dict(grid='tmap') ->>> model.set_defaults_multiple(grid1,grid2) ->>> model.copy2scratch() - -The actual fitting. The second fit starts from the 95% probability intervals of -the first fit. - ->>> teff_ms = (5000,7000) ->>> teff_sdb = (25000,45000) ->>> logg_ms = (4.00,4.50) ->>> logg_sdb = (5.00,6.50) ->>> mysed.igrid_search(masses=(0.47,0.71) ,teffrange=(teff_ms,teff_fix),loggrange=(logg_ms,logg_sdb), ebvrange=(0.00,0.02), zrange=(0,0), points=2000000, type='binary') ->>> mysed.igrid_search(masses=(0.47,0.71) ,points=2000000, type='binary') - -Delete the used models from the scratch disk - ->>> model.clean_scratch() - -Plot the results - ->>> p = pl.figure() ->>> p = pl.subplot(131); mysed.plot_sed(plot_deredded=False) ->>> p = pl.subplot(132); mysed.plot_grid(x='teff', y='logg', limit=0.95) ->>> p = pl.subplot(133); mysed.plot_grid(x='teff-2', y='logg-2', limit=0.95) - -]]include figure]]ivs_sed_builder_example_fittingPG1104+243.png] - -Subsection 3.3 Saving SED fits ------------------------------- - -You can save all the data to a multi-extension FITS file via - ->>> mysed.save_fits() - -This FITS file then contains all B{measurements} (it includes the .phot file), -the B{resulting SED}, the B{confidence intervals of all parameters} and also the -B{whole fitted grid}: in the above case, the extensions of the FITS file contain -the following information (we print part of each header):: - - EXTNAME = 'DATA ' - XTENSION= 'BINTABLE' / binary table extension - BITPIX = 8 / array data type - NAXIS = 2 / number of array dimensions - NAXIS1 = 270 / length of dimension 1 - NAXIS2 = 67 / length of dimension 2 - TTYPE1 = 'meas ' - TTYPE2 = 'e_meas ' - TTYPE3 = 'flag ' - TTYPE4 = 'unit ' - TTYPE5 = 'photband' - TTYPE6 = 'source ' - TTYPE7 = '_r ' - TTYPE8 = '_RAJ2000' - TTYPE9 = '_DEJ2000' - TTYPE10 = 'cwave ' - TTYPE11 = 'cmeas ' - TTYPE12 = 'e_cmeas ' - TTYPE13 = 'cunit ' - TTYPE14 = 'color ' - TTYPE15 = 'include ' - TTYPE16 = 'synflux ' - TTYPE17 = 'mod_eff_wave' - TTYPE18 = 'chi2 ' - - EXTNAME = 'MODEL ' - XTENSION= 'BINTABLE' / binary table extension - BITPIX = 8 / array data type - NAXIS = 2 / number of array dimensions - NAXIS1 = 24 / length of dimension 1 - NAXIS2 = 1221 / length of dimension 2 - TTYPE1 = 'wave ' - TUNIT1 = 'A ' - TTYPE2 = 'flux ' - TUNIT2 = 'erg/s/cm2/AA' - TTYPE3 = 'dered_flux' - TUNIT3 = 'erg/s/cm2/AA' - TEFFL = 23000.68377498454 - TEFF = 23644.49138963689 - TEFFU = 29999.33189058337 - LOGGL = 3.014445328877565 - LOGG = 4.984788506855546 - LOGGU = 4.996095055525759 - EBVL = 0.4703171900728142 - EBV = 0.4933185398871652 - EBVU = 0.5645144879211454 - ZL = -2.499929434601112 - Z = 0.4332336811329144 - ZU = 0.4999776537652627 - SCALEL = 2.028305798068863E-20 - SCALE = 2.444606991671813E-20 - SCALEU = 2.830281842143698E-20 - LABSL = 250.9613352757437 - LABS = 281.771013453664 - LABSU = 745.3149766772975 - CHISQL = 77.07958742733673 - CHISQ = 77.07958742733673 - CHISQU = 118.8587169011471 - CI_RAWL = 0.9999999513255379 - CI_RAW = 0.9999999513255379 - CI_RAWU = 0.9999999999999972 - CI_RED = 0.5401112973063139 - CI_REDL = 0.5401112973063139 - CI_REDU = 0.9500015229597392 - - EXTNAME = 'IGRID_SEARCH' - XTENSION= 'BINTABLE' / binary table extension - BITPIX = 8 / array data type - NAXIS = 2 / number of array dimensions - NAXIS1 = 80 / length of dimension 1 - NAXIS2 = 99996 / length of dimension 2 - TTYPE1 = 'teff ' - TTYPE2 = 'logg ' - TTYPE3 = 'ebv ' - TTYPE4 = 'z ' - TTYPE5 = 'chisq ' - TTYPE6 = 'scale ' - TTYPE7 = 'escale ' - TTYPE8 = 'Labs ' - TTYPE9 = 'CI_raw ' - TTYPE10 = 'CI_red ' - - -Subsection 3.4 Loading SED fits ### BROKEN ### ------------------------------------------------ - -Unfortunately this is not yet working properly! - -Once saved, you can load the contents of the FITS file again into an SED object -via - ->>> mysed = SED('HD180642') ->>> mysed.load_fits() - -and then you can build all the plots again easily. You can of course use the -predefined plotting scripts to start a plot, and then later on change the -properties of the labels, legends etc... for higher quality plots or to better -suit your needs. - -Section 4. Accessing the best fitting full SED model -==================================================== - -You can access the full SED model that matches the parameters found by the -fitting routine via: - ->>> wavelength,flux,deredded_flux = mysed.get_best_model() - -Note that this model is retrieved after fitting, and was not in any way used -during the fitting. As a consequence, there could be small differences between -synthetic photometry calculated from this returned model and the synthetic -fluxes stored in C{mysed.results['igrid_search']['synflux']}, which is the -synthetic photometry coming from the interpolation of the grid of pre-interpolated -photometry. See the documentation of L{SED.get_model} for more information. - -Section 5. Radii, distances and luminosities -============================================ - -Subsection 4.1. Relations between quantities --------------------------------------------- - -Most SED grids don't have the radius as a tunable model parameter. The scale -factor, which is available for all fitted models in the grid when at least one -absolute photometric point is included, is directly propertional to the angular -diameter. The following relations hold:: - - >> distance = radius / np.sqrt(scale) - >> radius = distance * np.sqrt(scale) - -Where C{radius} and C{distance} have equal units. The true absolute luminosity -(solar units) is related to the absolute luminosity from the SED (solar units) -via the radius (solar units):: - - >> L_abs_true = L_abs * radius**2 - -Finally, the angular diameter can be computed via:: - - >> 2*conversions.convert('sr','mas',scale) - -Subsection 4.2. Seismic constraints ------------------------------------ - -If the star shows clear solar-like oscillations, you can use the nu_max give an -independent constraint on the surface gravity, given the effective temperature of -the model (you are free to give errors or not, just keep in mind that in the -former case, you will get an array of Uncertainties rather than floats):: - - >> teff = 4260.,'K' - >> nu_max = 38.90,0.86,'muHz' - >> logg_slo = conversions.derive_logg_slo(teff,nu_max,unit='[cm/s2']) - -If you have the large separation (l=0 modes) and the nu_max, you can get an -estimate of the radius:: - - >> Deltanu0 = 4.80,0.02,'muHz' - >> R_slo = conversions.derive_radius_slo(numax,Deltanu0,teff,unit='Rsol') - -Then from the radius, you can get both the absolute luminosity and distance to -your star. - -Subsection 4.3. Parallaxes --------------------------- - -From the parallax, you can get an estimate of the distance. This is, however, -dependent on some prior assumptions such as the shape of the galaxy and the -distribution of stars. To estimate the probability density value due to -a measured parallax of a star at particular distance, you can call L{distance.distprob}:: - - >> gal_latitude = 0.5 - >> plx = 3.14,0.5 - >> d_prob = distance.distprob(d,gal_latitude,plx) - -Using this distance, you can get an estimate for the radius of your star, and -thus also the absolute luminosity. - -Subsection 4.4: Reddening constraints -------------------------------------- - -An estimate of the reddening of a star at a particular distance may be obtained -with the L{extinctionmodels.findext} function. There are currently three -reddening maps available: Drimmel, Marshall and Arenou:: - - >> lng,lat = 120.,-0.5 - >> dist = 100. # pc - >> Rv = 3.1 - >> EBV = extinctionmodels.findext(lng,lat,model='drimmel',distance=dist)/Rv - >> EBV = extinctionmodels.findext(lng,lat,model='marshall',distance=dist)/Rv - >> EBV = extinctionmodels.findext(lng,lat,model='arenou',distance=dist)/Rv - - """ + import re import sys import time @@ -565,19 +11,21 @@ import itertools import glob import json +import matplotlib.pyplot as plt import pylab as pl from matplotlib import mlab -from PIL import Image +try: + from PIL import Image +except ImportError: + print("The PIL package is discontinued") import numpy as np import scipy.stats -from scipy.interpolate import Rbf import astropy.io.fits as pf from ivs import config from ivs.aux import numpy_ext from ivs.aux import termtools -from ivs.aux.decorators import memoized,clear_memoization from ivs.inout import ascii from ivs.inout import fits from ivs.inout import hdf5 @@ -595,97 +43,109 @@ from ivs.catalogs import corot from ivs.units import conversions from ivs.units import constants -from ivs.units.uncertainties import unumpy,ufloat -from ivs.units.uncertainties.unumpy import sqrt as usqrt -from ivs.units.uncertainties.unumpy import tan as utan +from uncertainties import unumpy, ufloat +from uncertainties.unumpy import sqrt as usqrt +from uncertainties.unumpy import tan as utan from ivs.sigproc import evaluate +from numpy.lib.recfunctions import append_fields try: - from ivs.stellar_evolution import evolutionmodels #This module has now been removed, perhaps future re-implementation if demanded + # This module has now been removed, + # perhaps future re-implementation if demanded + from ivs.stellar_evolution import evolutionmodels except ImportError: - print("Warning: The ivs.stellar_evolution module has been removed from the repository as of 03.11.2017.") - print(" Stellar evolution models are no longer available to use.") + print("Warning: The ivs.stellar_evolution module has been removed from the" + "repository as of 03.11.2017.") + print("Stellar evolution models are no longer available to use.") logger = logging.getLogger("SED.BUILD") -#logger.setLevel(10) +# logger.setLevel(10) + -def fix_master(master,e_default=None): +def fix_master(master, e_default=None): """ Clean/extend/fix record array received from C{get_photometry}. - This function does a couple of things: - 1. Adds common but uncatalogized colors like 2MASS.J-H if not already - present. WARNING: these colors can mix values from the same system - but from different catalogs! - 2. Removes photometry for which no calibration is available - 3. Adds a column 'color' with flag False denoting absolute flux measurement - and True denoting color. - 4. Adds a column 'include' with flag True meaning the value will be + 1. Adds common but uncatalogized colors like 2MASS.J-H if not + already present. WARNING: these colors can mix values from the same + system but from different catalogs! + 2. Removes photometry for which no calibration is available + 3. Adds a column 'color' with flag False denoting absolute flux + measurement and True denoting color. + 4. Adds a column 'include' with flag True meaning the value will be included in the fit - 5. Sets some default errors to photometric values, for which we know + 5. Sets some default errors to photometric values, for which we know that the catalog values are not trustworthy. - 6. Sets a lower limit to the allowed errors of 1%. Errors below this - value are untrostworthy because the calibration error is larger than that. - 7. USNOB1 and ANS photometry are set the have a minimum error of 30% + 6. Sets a lower limit to the allowed errors of 1%. Errors below this + value are untrustworthy because the calibration error is larger than + that. + 7. USNOB1 and ANS photometry are set the have a minimum error of 30% due to uncertainties in response curves. - @param master: record array containing photometry. This should have the fields - 'meas','e_meas','unit','photband','source','_r','_RAJ2000','DEJ2000', - 'cmeas','e_cmeas','cwave','cunit' - @type master: record array - @param e_default: default error for measurements without errors - @type e_default: float - @return: record array extended with fields 'include' and 'color', and with + :param master: record array containing photometry. This should have the + fields ``meas``, ``e_meas``, ``unit``, ``photband``, ``source``, ``_r``, + ``_RAJ2000``, ``DEJ2000``, ``cmeas``, ``e_cmeas``, ``cwave``, ``cunit`` + :type master: record array + :param e_default: default error for measurements without errors + :type e_default: float + :return: record array extended with fields 'include' and 'color', and with rows added (see above description) - @rtype: numpy recard array + :rtype: numpy recard array """ - #-- we recognize uncalibrated stuff as those for which no absolute flux was - # obtained, and remove them: + # -- we recognize uncalibrated stuff as those for which no absolute flux + # was obtained, and remove them: master = master[~np.isnan(master['cmeas'])] cats = np.array([ph.split('.')[0] for ph in master['source']]) set_cats = sorted(set(cats)) - #-- add common uncatalogized colors: + # -- add common uncatalogized colors: columns = list(master.dtype.names) - #-- if the separate bands are available (and not the color itself), + # -- if the separate bands are available (and not the color itself), # calculate the colors here. We need to do this for every separate # system! add_rows = [] for cat in set_cats: - master_ = master[cats==cat] - - for color in ['2MASS.J-H','2MASS.KS-H','TD1.1565-1965','TD1.2365-1965','TD1.2365-2740', - 'JOHNSON.J-H','JOHNSON.K-H','JOHNSON.B-V','JOHNSON.U-B','JOHNSON.R-V','JOHNSON.I-V', - 'GALEX.FUV-NUV','TYCHO2.BT-VT','WISE.W1-W2','WISE.W3-W2','WISE.W4-W3', - 'SDSS.U-G','SDSS.G-R','SDSS.R-I','SDSS.R-Z', - 'UVEX.U-G','UVEX.G-R','UVEX.R-I','UVEX.HA-I', - 'DENIS.I-J','DENIS.KS-J', - 'ANS.15N-15W','ANS.15W-18','ANS.18-22','ANS.22-25','ANS.25-33']: - #-- get the filter system and the separate bands for these colors - system,band = color.split('.') - band0,band1 = band.split('-') - band0,band1 = '%s.%s'%(system,band0),'%s.%s'%(system,band1) - - if band0 in master_['photband'] and band1 in master_['photband'] and not color in master_['photband']: - #-- where are the bands located? + master_ = master[cats == cat] + + for color in ['2MASS.J-H', '2MASS.KS-H', 'TD1.1565-1965', + 'TD1.2365-1965', 'TD1.2365-2740', 'JOHNSON.J-H', + 'JOHNSON.K-H', 'JOHNSON.B-V', 'JOHNSON.U-B', + 'JOHNSON.R-V', 'JOHNSON.I-V', 'GALEX.FUV-NUV', + 'TYCHO2.BT-VT', 'WISE.W1-W2', 'WISE.W3-W2', 'WISE.W4-W3', + 'SDSS.U-G', 'SDSS.G-R', 'SDSS.R-I', 'SDSS.R-Z', + 'UVEX.U-G', 'UVEX.G-R', 'UVEX.R-I', 'UVEX.HA-I', + 'DENIS.I-J', 'DENIS.KS-J', 'ANS.15N-15W', 'ANS.15W-18', + 'ANS.18-22', 'ANS.22-25', 'ANS.25-33']: + + # -- get the filter system and the separate bands for these colors + system, band = color.split('.') + band0, band1 = band.split('-') + band0, band1 = '%s.%s' % (system, band0), '%s.%s' % (system, band1) + + if (band0 in master_['photband'] + and band1 in master_['photband'] + and color not in master_['photband']): + + # -- where are the bands located? index0 = list(master_['photband']).index(band0) index1 = list(master_['photband']).index(band1) - #-- start a new row to add the color + # -- start a new row to add the color row = list(master_[index1]) row[columns.index('photband')] = color row[columns.index('cwave')] = np.nan - #-- it could be a magnitude difference - if master_['unit'][index0]=='mag': - row[columns.index('meas')] = master_['meas'][index0]-master_['meas'][index1] + # -- it could be a magnitude difference + if master_['unit'][index0] == 'mag': + row[columns.index('meas')] = (master_['meas'][index0] + - master_['meas'][index1]) row[columns.index('e_meas')] = np.sqrt(master_['e_meas'][index0]**2+master_['e_meas'][index1]**2) - #-- error will not always be available... + # -- error will not always be available... try: row[columns.index('cmeas')],row[columns.index('e_cmeas')] = conversions.convert('mag_color','flux_ratio',row[columns.index('meas')],row[columns.index('e_meas')],photband=color) except AssertionError: row[columns.index('cmeas')] = conversions.convert('mag_color','flux_ratio',row[columns.index('meas')],photband=color) row[columns.index('e_cmeas')] = np.nan - #-- or it could be a flux ratio + # -- or it could be a flux ratio else: row[columns.index('meas')] = master_['meas'][index0]/master_['meas'][index1] row[columns.index('e_meas')] = np.sqrt(((master_['e_meas'][index0]/master_['meas'][index0])**2+(master_['e_meas'][index1]/master_['meas'][index1])**2)*row[columns.index('meas')]**2) @@ -694,7 +154,7 @@ def fix_master(master,e_default=None): add_rows.append(tuple(row)) master = numpy_ext.recarr_addrows(master,add_rows) - #-- add an extra column with a flag to distinguish colors from absolute + # -- add an extra column with a flag to distinguish colors from absolute # fluxes, and a column with flags to include/exclude photometry # By default, exclude photometry if the effective wavelength is above # 150 mum or if the measurement is nan @@ -706,13 +166,13 @@ def fix_master(master,e_default=None): else: iscolor = [filters.is_color(photband) for photband in master['photband']] master['color'] = iscolor - #-- add an extra column with indices to distinguish different data sets, e.g. based + # -- add an extra column with indices to distinguish different data sets, e.g. based # on photometric phase. if not 'phase' in master.dtype.names: extra_cols = [[0]*len(master['meas'])] dtypes = [('phase',np.int)] master = numpy_ext.recarr_addcols(master,extra_cols,dtypes) - #-- set default errors if no errors are available and set really really + # -- set default errors if no errors are available and set really really # small errors to some larger default value if e_default is not None: no_error = np.isnan(master['e_cmeas']) @@ -720,14 +180,14 @@ def fix_master(master,e_default=None): small_error = master['e_cmeas']<(0.01*np.abs(master['cmeas'])) master['e_cmeas'][small_error] = 0.01*np.abs(master['cmeas'][small_error]) - #-- the measurements from USNOB1 are not that good because the response + # -- the measurements from USNOB1 are not that good because the response # curve is approximated by the JOHNSON filter. Set the errors to min 30% # The same holds for ANS: here, the transmission curves are very uncertain for i,photband in enumerate(master['photband']): if 'USNOB1' in photband or 'ANS' in photband: master['e_cmeas'][i] = max(0.30*master['cmeas'][i],master['e_cmeas'][i]) - #-- remove negative fluxes + # -- remove negative fluxes master = master[master['cmeas']>0] return master @@ -749,50 +209,50 @@ def decide_phot(master,names=None,wrange=None,sources=None,indices=None,ptype='a Some examples: - 1. Exclude all measurements:: + 1 Exclude all measurements:: - >> decide_phot(master,wrange=(-np.inf,+np.inf),ptype='all',include=False) + >> decide_phot(master,wrange=(-np.inf,+np.inf),ptype='all',include=False) - 2. Include all TD1 fluxes and colors:: + 2 Include all TD1 fluxes and colors:: - >> decide_phot(master,names=['TD1'],ptype='all',include=True) + >> decide_phot(master,names=['TD1'],ptype='all',include=True) - 3. Include all V band measurements from all systems (but not the colors):: + 3 Include all V band measurements from all systems (but not the colors):: - >> decide_phot(master,names=['.V'],ptype='abs',include=True) + >> decide_phot(master,names=['.V'],ptype='abs',include=True) - 4. Include all Geneva colors and exclude Geneva magnitudes:: + 4 Include all Geneva colors and exclude Geneva magnitudes:: - >> decide_phot(master,names=['GENEVA'],ptype='col',include=True) - >> decide_phot(master,names=['GENEVA'],ptype='abs',include=False) + >> decide_phot(master,names=['GENEVA'],ptype='col',include=True) + >> decide_phot(master,names=['GENEVA'],ptype='abs',include=False) - 5. Exclude all infrared measurements beyond 1 micron:: + 5 Exclude all infrared measurements beyond 1 micron:: - >> decide_phot(master,wrange=(1e4,np.inf),ptype='all',include=False) + >> decide_phot(master,wrange=(1e4,np.inf),ptype='all',include=False) - 6. Include all AKARI measurements below 10 micron:: + 6 Include all AKARI measurements below 10 micron:: - >> decide_phot(master,names=['AKARI'],wrange=(-np.inf,1e5),ptype='all',include=True) + >> decide_phot(master,names=['AKARI'],wrange=(-np.inf,1e5),ptype='all',include=True) - @param master: record array containing all photometry - @type master: numpy record array - @param names: strings excerpts to match filters - @type names: list of strings - @param wrange: wavelength range (most likely angstrom) to include/exclude - @type wrange: 2-tuple (start wavelength,end wavelength) - @param sources: list of sources - @type sources: list of strings - @param indices: list of indices (integers) - @type indices: list of integers - @param ptype: type of photometry to include/exclude: absolute values, colors + :param master: record array containing all photometry + :type master: numpy record array + :param names: strings excerpts to match filters + :type names: list of strings + :param wrange: wavelength range (most likely angstrom) to include/exclude + :type wrange: 2-tuple (start wavelength,end wavelength) + :param sources: list of sources + :type sources: list of strings + :param indices: list of indices (integers) + :type indices: list of integers + :param ptype: type of photometry to include/exclude: absolute values, colors or both - @type ptype: string, one of 'abs','col','all' - @param include: flag setting exclusion or inclusion - @type include: boolean - @return: master record array with adapted 'include' flags - @rtype: numpy record array + :type ptype: string, one of 'abs','col','all' + :param include: flag setting exclusion or inclusion + :type include: boolean + :return: master record array with adapted 'include' flags + :rtype: numpy record array """ - #-- exclude/include passbands based on their names + # -- exclude/include passbands based on their names if names is not None: logger.info('%s photometry based on photband containining one of %s'%((include and 'Include' or "Exclude"),names)) for index,photband in enumerate(master['photband']): @@ -801,7 +261,7 @@ def decide_phot(master,names=None,wrange=None,sources=None,indices=None,ptype='a if ptype=='all' or (ptype=='abs' and -master['color'][index]) or (ptype=='col' and master['color'][index]): master['include'][index] = include break - #-- exclude/include colors based on their wavelength + # -- exclude/include colors based on their wavelength if wrange is not None: logger.info('%s photometry based on photband wavelength between %s'%((include and 'Include' or "Exclude"),wrange)) for index,photband in enumerate(master['photband']): @@ -813,10 +273,10 @@ def decide_phot(master,names=None,wrange=None,sources=None,indices=None,ptype='a cwave1 = filters.eff_wave('%s.%s'%(system,band1)) if (wrange[0]5000 #all_teffs = all_teffs[keep] #all_radii = all_radii[keep] #all_loggs = all_loggs[keep] - ##-- make linear interpolation model between all modelpoints + ## -- make linear interpolation model between all modelpoints #mygrid = Rbf(np.log10(all_teffs),all_loggs,all_radii,function='linear') #logger.info('Interpolation of Schaller 1992 evolutionary tracks to compute radii') #return mygrid @@ -914,12 +376,12 @@ def photometry2str(master,comment='',sort='photband',color=False,index=False): #""" #Retrieve radii from stellar evolutionary tracks from Schaller 1992. - #@param teffs: model effective temperatures - #@type teffs: numpy array - #@param loggs: model surface gravities - #@type loggs: numpy array - #@return: model radii (solar units) - #@rtype: numpy array + #:param teffs: model effective temperatures + #:type teffs: numpy array + #:param loggs: model surface gravities + #:type loggs: numpy array + #:return: model radii (solar units) + #:rtype: numpy array #""" #mygrid = get_schaller_grid() #radii = mygrid(np.log10(teffs),loggs) @@ -933,7 +395,7 @@ def photometry2str(master,comment='',sort='photband',color=False,index=False): #""" - ##-- compute distance up to 25 kpc, which is about the maximum distance from + ## -- compute distance up to 25 kpc, which is about the maximum distance from ## earth to the farthest side of the Milky Way galaxy ## rescale to set the maximum to 1 #d = np.logspace(np.log10(0.1),np.log10(25000),100000) @@ -942,17 +404,17 @@ def photometry2str(master,comment='',sort='photband',color=False,index=False): #dprob = dprob / dprob.max() #else: #dprob = np.ones_like(d) - ##-- compute the radii for the computed models, and convert to parsec + ## -- compute the radii for the computed models, and convert to parsec ##radii = np.ones(len(teffs[-n:])) #radii = get_radii(teffs[-n:],loggs[-n:]) #radii = conversions.convert('Rsol','pc',radii) #d_models = radii/np.sqrt(scales[-n:]) - ##-- we set out of boundary values to zero + ## -- we set out of boundary values to zero #if plx is not None: #dprob_models = np.interp(d_models,d[-n:],dprob[-n:],left=0,right=0) #else: #dprob_models = np.ones_like(d_models) - ##-- reset the radii to solar units for return value + ## -- reset the radii to solar units for return value #radii = conversions.convert('pc','Rsol',radii) #return (d_models,dprob_models,radii),(d,dprob) @@ -967,18 +429,20 @@ class SED(object): The most important attributes of SED are: - 1. C{sed.ID}: star's identification (str) - 2. C{sed.photfile}: name of the file containing all photometry (str) - 3. C{sed.info}: star's information from Simbad (dict) - 4. C{sed.master}: photometry data (record array) - 5. C{sed.results}: results and summary of the fitting process (dict) + 1 C{sed.ID}: star's identification (str) + 2 C{sed.photfile}: name of the file containing all photometry (str) + 3 C{sed.info}: star's information from Simbad (dict) + 4 C{sed.master}: photometry data (record array) + 5 C{sed.results}: results and summary of the fitting process (dict) - After fitting, e.g. via calling L{igrid_search}, you can call L{get_model} - to retrieve the full SED matching the best fitting parameters (or, rather, - closely matching them, see the documentation). + After fitting, e.g. via calling :class:`igrid_search`, you can call + :class:`get_model` to retrieve the full SED matching the best fitting + parameters (or, rather, closely matching them, see the documentation). """ - def __init__(self,ID=None,photfile=None,plx=None,load_fits=True,load_hdf5=True,label=''): + + def __init__(self, ID=None, photfile=None, plx=None, load_fits=True, + load_hdf5=True, label=''): """ Initialize SED class. @@ -996,24 +460,24 @@ def __init__(self,ID=None,photfile=None,plx=None,load_fits=True,load_hdf5=True,l The C{ID} variable is used internally to look up data, so it should be something SIMBAD understands and that designates the target. - @param plx: parallax (and error) of the object - @type plx: tuple (plx,e_plx) + :param plx: parallax (and error) of the object + :type plx: tuple (plx,e_plx) """ self.ID = ID self.label = label self.info = {} - #-- the file containing photometry should have the following name. We + # -- the file containing photometry should have the following name. We # save photometry to a file to avoid having to download photometry # each time from the internet if photfile is None: - self.photfile = '%s.phot'%(ID).replace(' ','_') - #-- keep information on the star from SESAME, but override parallax with + self.photfile = '%s.phot' % (ID).replace(' ', '_') + # -- keep information on the star from SESAME, but override parallax with # the value from Van Leeuwen's new reduction. Set the galactic # coordinates, which are not available from SESAME. else: self.photfile = photfile - #-- load information from the photometry file if it exists + # -- load information from the photometry file if it exists if not os.path.isfile(self.photfile): try: self.info = sesame.search(os.path.basename(ID),fix=True) @@ -1024,7 +488,7 @@ def __init__(self,ID=None,photfile=None,plx=None,load_fits=True,load_hdf5=True,l logger.info('Star %s recognised by NED'%(os.path.basename(ID))) except KeyError: logger.warning('Star %s not recognised by NED'%(os.path.basename(ID))) - #-- final attempt: if it's a CoRoT star, try the catalog + # -- final attempt: if it's a CoRoT star, try the catalog if 'corot' in ID.lower() and not 'galpos' in self.info: corot_id = int("".join([char for char in ID if char.isdigit()])) try: @@ -1043,11 +507,11 @@ def __init__(self,ID=None,photfile=None,plx=None,load_fits=True,load_hdf5=True,l else: self.load_photometry() logger.info('Photometry loaded from file') - #-- if no ID was given, set the official name as the ID. + # -- if no ID was given, set the official name as the ID. if self.ID is None: self.ID = os.path.splitext(os.path.basename(self.photfile))[0]#self.info['oname'] logger.info('Name from file used to set ID of object') - #--load information from the FITS file if it exists + # --load information from the FITS file if it exists self.results = {} self.constraints = {} if load_fits: @@ -1055,7 +519,7 @@ def __init__(self,ID=None,photfile=None,plx=None,load_fits=True,load_hdf5=True,l if load_hdf5: self.load_hdf5() - #-- prepare for information on fitting processes + # -- prepare for information on fitting processes self.CI_limit = 0.95 def __repr__(self): @@ -1069,14 +533,14 @@ def __str__(self): Human readable string representation of an SED object. """ txt = [] - #-- object designation + # -- object designation txt.append("Object identification: {:s}".format(self.ID)) if hasattr(self,'info') and self.info and 'oname' in self.info: txt.append("Official designation: {:s}".format(self.info['oname'])) - #-- additional info + # -- additional info for key in sorted(self.info.keys()): if isinstance(self.info[key],dict): - txt.append(" {:10s} = ".format(key)+", ".join(["{}: {}".format(i,j) for i,j in self.info[key].iteritems()])) + txt.append(" {:10s} = ".format(key)+", ".join(["{}: {}".format(i,j) for i,j in self.info[key].items()])) else: txt.append(" {:10s} = {}".format(key,self.info[key])) @@ -1101,8 +565,8 @@ def get_photometry(self,radius=None,ra=None,dec=None, For bright stars, you can set radius a bit higher... - @param radius: search radius (arcseconds) - @type radius: float. + :param radius: search radius (arcseconds) + :type radius: float. """ if radius is None: if 'mag.V.v' in self.info and self.info['mag.V.v']<6.: @@ -1110,7 +574,7 @@ def get_photometry(self,radius=None,ra=None,dec=None, else: radius = 10. if not os.path.isfile(self.photfile) or force: - #-- get and fix photometry. Set default errors to 1%, and set + # -- get and fix photometry. Set default errors to 1%, and set # USNOB1 errors to 3% if ra is None and dec is None: master = crossmatch.get_photometry(ID=os.path.basename(self.ID),radius=radius, @@ -1124,12 +588,12 @@ def get_photometry(self,radius=None,ra=None,dec=None, master['_RAJ2000'] -= self.info['jradeg'] master['_DEJ2000'] -= self.info['jdedeg'] - #-- fix the photometry: set default errors to 2% and print it to the + # -- fix the photometry: set default errors to 2% and print it to the # screen self.master = fix_master(master,e_default=0.1) logger.info('\n'+photometry2str(master)) - #-- write to file + # -- write to file self.save_photometry() def get_spectrophotometry(self,directory=None,force_download=False): @@ -1146,12 +610,12 @@ def get_spectrophotometry(self,directory=None,force_download=False): if not os.path.isdir(directory): os.mkdir(directory) - #-- add spectrophotometric filters to the set + # -- add spectrophotometric filters to the set photbands = filters.add_spectrophotometric_filters(R=200,lambda0=950,lambdan=3350) if hasattr(self,'master') and self.master is not None and not force_download: if any(['BOXCAR' in photband for photband in self.master['photband']]): return None - #-- FUSE spectra + # -- FUSE spectra fuse_direc = os.path.join(directory,'FUSE') iue_direc = os.path.join(directory,'IUE') if not os.path.isdir(fuse_direc) or force_download: @@ -1160,25 +624,25 @@ def get_spectrophotometry(self,directory=None,force_download=False): out1 = [] else: out1 = glob.glob(fuse_direc+'/*') - #-- IUE spectra + # -- IUE spectra if not os.path.isdir(iue_direc) or force_download: out2 = vizier.get_IUE_spectra(ID=os.path.basename(self.ID),directory=iue_direc,select='lo') if out2 is None: out2 = [] else: out2 = glob.glob(iue_direc+'/*') - #-- read them in to combine + # -- read them in to combine list_of_spectra = [fits.read_fuse(ff)[:3] for ff in out1] list_of_spectra+= [fits.read_iue(ff)[:3] for ff in out2] - #-- combine + # -- combine wave,flux,err,nspec = tools.combine(list_of_spectra) - #-- add to the master + # -- add to the master N = len(flux) units = ['erg/s/cm2/AA']*N source = ['specphot']*N self.add_photometry_fromarrays(flux,err,units,photbands,source,flags=nspec) - #-- write to file + # -- write to file self.master = fix_master(self.master,e_default=0.1) logger.info('\n'+photometry2str(self.master)) self.save_photometry() @@ -1234,30 +698,29 @@ def include_abs(self,names=None,wrange=None,sources=None,indices=None): def set_photometry_scheme(self,scheme,infrared=(1,'micron')): """ Set a default scheme of colors/absolute values to fit the SED. - Possible values: - 1. scheme = 'abs': means excluding all colors, including all absolute values - 2. scheme = 'color': means including all colors, excluding all absolute values - 3. scheme = 'combo': means inculding all colors, and one absolute value per - system (the one with the smallest relative error) - 4. scheme = 'irfm': means mimic infrared flux method: choose absolute values - in the infrared (define with C{infrared}), and colours in the optical + 1 ``abs``: means excluding all colors, including all absolute values + 2 ``color``: means including all colors, excluding all absolute values + 3 ``combo``: means inculding all colors, and one absolute value per + system (the one with the smallest relative error) + 4 ``irfm``: means mimic infrared flux method: choose absolute values + in the infrared (define with C{infrared}), and colours in the optical - @param infrared: definition of start of infrared for infrared flux method - @type infrared: tuple (value , unit ) + :param infrared: definition of start of infrared for infrared flux method + :type infrared: tuple (value , unit ) """ - #-- only absolute values: real SED fitting + # -- only absolute values: real SED fitting if 'abs' in scheme.lower(): self.master['include'][self.master['color']] = False - self.master['include'][-self.master['color']] = True + self.master['include'][~self.master['color']] = True logger.info('Fitting procedure will use only absolute fluxes (%d)'%(sum(self.master['include']))) - #-- only colors: color fitting + # -- only colors: color fitting elif 'col' in scheme.lower(): self.master['include'][self.master['color']] = True - self.master['include'][-self.master['color']] = False + self.master['include'][~self.master['color']] = False logger.info('Fitting procedure will use only colors (%d)'%(sum(self.master['include']))) - #-- combination: all colors and one absolute value per system + # -- combination: all colors and one absolute value per system elif 'com' in scheme.lower(): self.master['include'][self.master['color'] & self.master['include']] = True systems = np.array([photband.split('.')[0] for photband in self.master['photband']]) @@ -1270,12 +733,12 @@ def set_photometry_scheme(self,scheme,infrared=(1,'micron')): self.master['include'][keep] = False self.master['include'][index] = True logger.info('Fitting procedure will use colors + one absolute flux for each system (%d)'%(sum(self.master['include']))) - #-- infrared flux method scheme + # -- infrared flux method scheme elif 'irfm' in scheme.lower(): - #-- check which measurements are in the infrared + # -- check which measurements are in the infrared for i,meas in enumerate(self.master): - #-- if measurement is in the infrared and it is a color, remove it (only use absolute values in IR) - #-- for colors, make sure *both* wavelengths are checked + # -- if measurement is in the infrared and it is a color, remove it (only use absolute values in IR) + # -- for colors, make sure *both* wavelengths are checked if meas['color']: bands = filters.get_color_photband(meas['photband']) eff_waves = filters.eff_wave(bands) @@ -1284,13 +747,13 @@ def set_photometry_scheme(self,scheme,infrared=(1,'micron')): is_infrared = conversions.Unit(*infrared) 1.0: CI_limit = self.CI_limit - #-- the chi2 grid is ordered: where is the value closest to the CI limit? + # -- the chi2 grid is ordered: where is the value closest to the CI limit? region = self.results[mtype]['grid']['ci_'+chi2_type]<=CI_limit if sum(region)==0: raise ValueError("No models in the sample have a chi2_{} below the limit {}. Try increasing the number of models, increasing the CI_limit or choose different photometry.".format(chi2_type,CI_limit)) - #-- now compute the confidence intervals + # -- now compute the confidence intervals cilow, cihigh, value = [],[],[] for name in grid_results.dtype.names: cilow.append(grid_results[name][region].min()) @@ -1696,16 +1162,16 @@ def store_confidence_intervals(self, mtype='igrid_search', name=None, value=None self.results[mtype]['CI'] with as key for the value the name, for cilow: name_l and for cihigh: name_u. - @param mtype: the search type - @type mtype: str - @param name: names of the parameters - @type name: array - @param value: best fit values - @type value: array - @param cilow: lower CI limit - @type cilow: array - @param cihigh: upper CI limit - @type cihigh: array + :param mtype: the search type + :type mtype: str + :param name: names of the parameters + :type name: array + :param value: best fit values + :type value: array + :param cilow: lower CI limit + :type cilow: array + :param cihigh: upper CI limit + :type cihigh: array """ if not 'CI' in self.results[mtype]: self.results[mtype]['CI'] = {} @@ -1736,19 +1202,22 @@ def igrid_search(self,points=100000,teffrange=None,loggrange=None,ebvrange=None, """ if CI_limit is None or CI_limit > 1.0: CI_limit = self.CI_limit - #-- set defaults limits + # -- set defaults limits ranges = self.generate_ranges(teffrange=teffrange,\ loggrange=loggrange,ebvrange=ebvrange,\ zrange=zrange,rvrange=rvrange,vradrange=vradrange) - #-- grid search on all include data: extract the best CHI2 + # -- grid search on all include data: extract the best CHI2 include_grid = self.master['include'] logger.info('The following measurements are included in the fitting process:\n%s'%(photometry2str(self.master[include_grid]))) - #-- an initial check on the conditions for exclusion of a parameter from interpolation + # -- an initial check on the conditions for exclusion of a parameter from interpolation + exclude = {} if not exc_interpolpar == None: for var in exc_interpolpar: if ranges[var+'range'][0] != ranges[var+'range'][1]: - raise IOError, 'Exclusion of parameters from interpolation is only possible if the lower and upper ranges of those ranges are equal to an actual grid point.' - #-- build the grid, run over the grid and calculate the CHI2 + raise IOError('Exclusion of parameters from interpolation is only possible if the lower and upper ranges of those ranges are equal to an actual grid point.') + exclude[var+'_skip'] = '' + del ranges[var+'range'] + # -- build the grid, run over the grid and calculate the CHI2 pars = fit.generate_grid_pix(self.master['photband'][include_grid],points=points,**ranges) pars['exc_interpolpar'] = exc_interpolpar #for var in ['teff','logg','ebv','z','rv','vrad']: @@ -1760,32 +1229,33 @@ def igrid_search(self,points=100000,teffrange=None,loggrange=None,ebvrange=None, self.master['photband'][include_grid],**pars) fitres = dict(chisq=chisqs, scale=scales, escale=e_scales, labs=lumis) pars.pop('exc_interpolpar') - #-- collect all the results in a record array + # -- collect all the results in a record array self.collect_results(grid=pars, fitresults=fitres, mtype='igrid_search') - #-- do the statistics + # -- do the statistics self.calculate_statistics(df=df, ranges=ranges, mtype='igrid_search') - #-- compute the confidence intervals + # -- compute the confidence intervals ci = self.calculate_confidence_intervals(mtype='igrid_search',chi2_type='red',CI_limit=CI_limit) self.store_confidence_intervals(mtype='igrid_search', **ci) - #-- remember the best model - if set_model: self.set_best_model() + # -- remember the best model + + if set_model: self.set_best_model(**exclude) def generate_fit_param(self, start_from='igrid_search', **pars): """ generates a dictionary with parameter information that can be handled by fit.iminimize """ result = dict() - for key in pars.keys(): + for key in list(pars.keys()): if re.search("range$", key): result[key[0:-5]+"_min"] = pars[key][0] result[key[0:-5]+"_max"] = pars[key][1] - #-- if min == max: fix the parameter and force value = min + # -- if min == max: fix the parameter and force value = min if np.allclose([pars[key][0]], [pars[key][1]]): result[key[0:-5]+"_vary"] = False result[key[0:-5]+"_value"] = pars[key][0] else: result[key[0:-5]+"_vary"] = True else: - #-- Store the startvalue. If None, look in start_from for a new startvalue. + # -- Store the startvalue. If None, look in start_from for a new startvalue. if pars[key] != None and not key+"_value" in result: result[key+"_value"] = pars[key] elif pars[key] == None and not key+"_value" in result: @@ -1795,10 +1265,10 @@ def generate_fit_param(self, start_from='igrid_search', **pars): def calculate_iminimize_CI(self, mtype='iminimize', CI_limit=0.66, **kwargs): - #-- Get the best fit parameters and ranges + # -- Get the best fit parameters and ranges pars = {} skip = ['scale', 'chisq', 'nfev', 'labs', 'ci_raw', 'ci_red', 'scale', 'escale'] - for name in self.results[mtype]['CI'].keys(): + for name in list(self.results[mtype]['CI'].keys()): name = re.sub('_[u|l]$', '', name) if not name in pars and not name in skip: pars[name] = self.results[mtype]['CI'][name] @@ -1807,14 +1277,14 @@ def calculate_iminimize_CI(self, mtype='iminimize', CI_limit=0.66, **kwargs): pars.update(kwargs) pars = self.generate_fit_param(**pars) - #-- calculate the confidence intervalls + # -- calculate the confidence intervalls include_grid = self.master['include'] ci = fit.calculate_iminimize_CI(self.master['cmeas'][include_grid], self.master['e_cmeas'][include_grid], self.master['photband'][include_grid], CI_limit=CI_limit,constraints=self.constraints, **pars) - #-- Add the scale factor + # -- Add the scale factor ci['name'] = np.append(ci['name'], 'scale') ci['value'] = np.append(ci['value'], self.results[mtype]['grid']['scale'][-1]) ci['cilow'] = np.append(ci['cilow'], min(self.results[mtype]['grid']['scale'])) @@ -1826,10 +1296,10 @@ def calculate_iminimize_CI(self, mtype='iminimize', CI_limit=0.66, **kwargs): def calculate_iminimize_CI2D(self,xpar, ypar, mtype='iminimize', limits=None, res=10, **kwargs): - #-- get the best fitting parameters + # -- get the best fitting parameters pars = {} skip = ['scale', 'chisq', 'nfev', 'labs', 'ci_raw', 'ci_red', 'scale', 'escale'] - for name in self.results[mtype]['CI'].keys(): + for name in list(self.results[mtype]['CI'].keys()): name = re.sub('_[u|l]$', '', name) if not name in pars and not name in skip: pars[name] = self.results[mtype]['CI'][name] @@ -1840,7 +1310,7 @@ def calculate_iminimize_CI2D(self,xpar, ypar, mtype='iminimize', limits=None, re logger.info('Calculating 2D confidence intervalls for %s--%s'%(xpar,ypar)) - #-- calculate the confidence intervalls + # -- calculate the confidence intervalls include_grid = self.master['include'] x,y,chi2, raw, red = fit.calculate_iminimize_CI2D(self.master['cmeas'][include_grid], self.master['e_cmeas'][include_grid], @@ -1848,7 +1318,7 @@ def calculate_iminimize_CI2D(self,xpar, ypar, mtype='iminimize', limits=None, re xpar, ypar, limits=limits, res=res, constraints=self.constraints, **pars) - #-- store the CI + # -- store the CI if not 'CI2D' in self.results[mtype]: self.results[mtype]['CI2D'] = {} self.results[mtype]['CI2D'][xpar+"-"+ypar] = dict(x=x, y=y, ci_chi2=chi2,\ ci_raw=raw, ci_red=red) @@ -1857,7 +1327,7 @@ def calculate_iminimize_CI2D(self,xpar, ypar, mtype='iminimize', limits=None, re def _get_imin_ci(self, mtype='iminimize',**ranges): """ returns ci information for store_confidence_intervals """ names, values, cil, cih = [],[],[],[] - for key in ranges.keys(): + for key in list(ranges.keys()): name = key[0:-5] names.append(name) values.append(self.results[mtype]['grid'][name][-1]) @@ -1877,7 +1347,7 @@ def iminimize(self, teff=None, logg=None, ebv=None, z=0, rv=3.1, vrad=0, teffran Basic minimizer method for SED fitting implemented using the lmfit library from sigproc.fit """ - #-- set defaults limits and starting values + # -- set defaults limits and starting values ranges = self.generate_ranges(teffrange=teffrange,loggrange=loggrange,\ ebvrange=ebvrange,zrange=zrange,rvrange=rvrange,\ vradrange=vradrange, start_from=start_from) @@ -1885,32 +1355,32 @@ def iminimize(self, teff=None, logg=None, ebv=None, z=0, rv=3.1, vrad=0, teffran rv=rv, vrad=vrad, start_from=start_from, **ranges) fitkws = dict(distance=distance) if distance != None else dict() - #-- grid search on all include data: extract the best CHI2 + # -- grid search on all include data: extract the best CHI2 include_grid = self.master['include'] logger.info('The following measurements are included in the fitting process:\n%s'% \ (photometry2str(self.master[include_grid]))) - #-- pass all ranges and starting values to the fitter + # -- pass all ranges and starting values to the fitter grid, chisq, nfev, scale, lumis = fit.iminimize(self.master['cmeas'][include_grid], self.master['e_cmeas'][include_grid], self.master['photband'][include_grid], fitkws=fitkws, points=points,**pars) logger.info('Minimizer Succes with startpoints=%s, chi2=%s, nfev=%s'%(len(chisq), chisq[0], nfev[0])) - #-- handle the results + # -- handle the results fitres = dict(chisq=chisq, nfev=nfev, scale=scale, labs=lumis) self.collect_results(grid=grid, fitresults=fitres, mtype='iminimize', selfact='chisq') - #-- Do the statistics + # -- Do the statistics self.calculate_statistics(df=5, ranges=ranges, mtype='iminimize', selfact='chisq') - #-- Store the confidence intervals + # -- Store the confidence intervals ci = self._get_imin_ci(mtype='iminimize',**ranges) self.store_confidence_intervals(mtype='iminimize', **ci) if calc_ci: self.calculate_iminimize_CI(mtype='iminimize', CI_limit=CI_limit) - #-- remember the best model + # -- remember the best model if set_model: self.set_best_model(mtype='iminimize') @@ -1921,7 +1391,7 @@ def imc(self,teffrange=None,loggrange=None,ebvrange=None,zrange=None,start_from= ebvrange=ebvrange,zrange=zrange,distribution=distribution,\ start_from=start_from) - #-- grid search on all include data: extract the best CHI2 + # -- grid search on all include data: extract the best CHI2 include = self.master['include'] meas = self.master['cmeas'][include] if disturb: @@ -1931,13 +1401,13 @@ def imc(self,teffrange=None,loggrange=None,ebvrange=None,zrange=None,start_from= photbands = self.master['photband'][include] logger.info('The following measurements are included in the fitting process:\n%s'%(photometry2str(self.master[include]))) - #-- generate initial guesses + # -- generate initial guesses teffs,loggs,ebvs,zs,radii = fit.generate_grid(self.master['photband'][include],type='single',points=points+25,**limits) NrPoints = len(teffs)>points and points or len(teffs) firstoutput = np.zeros((len(teffs)-NrPoints,9)) output = np.zeros((NrPoints,9)) - #-- fit the original data a number of times + # -- fit the original data a number of times for i,(teff,logg,ebv,z) in enumerate(zip(teffs[NrPoints:],loggs[NrPoints:],ebvs[NrPoints:],zs[NrPoints:])): try: fittedpars,warnflag = fit.iminimize2(meas,emeas,photbands,teff=teff,logg=logg,ebv=ebv,z=z,fitmethod=fitmethod) @@ -1950,7 +1420,7 @@ def imc(self,teffrange=None,loggrange=None,ebvrange=None,zrange=None,start_from= logger.info("{0}/{1} fits on original failed (max iter)".format(sum(firstoutput[:,0]==2),firstoutput.shape[0])) logger.info("{0}/{1} fits on original data failed (outside of grid)".format(sum(firstoutput[:,0]==3),firstoutput.shape[0])) - #-- retrieve the best fitting result and make it the first entry of output + # -- retrieve the best fitting result and make it the first entry of output keep = (firstoutput[:,0]==0) & (firstoutput[:,1]>0) best = firstoutput[keep,-2].argmin() output[-1,:] = firstoutput[keep][best,:] @@ -1959,7 +1429,7 @@ def imc(self,teffrange=None,loggrange=None,ebvrange=None,zrange=None,start_from= #factor = np.sqrt(output[-1,5]/len(meas)) #print factor - #-- now do the actual Monte Carlo simulation + # -- now do the actual Monte Carlo simulation for i,(teff,logg,ebv,z) in enumerate(zip(teffs[:NrPoints-1],loggs[:NrPoints-1],ebvs[:NrPoints-1],zs[:NrPoints-1])): newmeas = meas + np.random.normal(scale=emeas) #*factor) try: @@ -1973,11 +1443,11 @@ def imc(self,teffrange=None,loggrange=None,ebvrange=None,zrange=None,start_from= logger.info("{0}/{1} MC simulations failed (max iter)".format(sum(output[:,0]==2),NrPoints)) logger.info("{0}/{1} MC simulations failed (outside of grid)".format(sum(output[:,0]==3),NrPoints)) - #-- remove nonsense results + # -- remove nonsense results keep = (output[:,0]==0) & (output[:,1]>0) output = output[keep] output = np.rec.fromarrays(output[:,1:-1].T,names=['teff','logg','ebv','z','labs','chisq','scale']) - #-- derive confidence intervals and median values + # -- derive confidence intervals and median values #print np.median(output[:,1:],axis=0) self.results['imc'] = {} @@ -2012,36 +1482,56 @@ def set_best_model(self,mtype='igrid_search',law='fitzpatrick2004',**kwargs): """ logger.info('Interpolating approximate full SED of best model') - #-- synthetic flux + # -- synthetic flux include = self.master['include'] synflux = np.zeros(len(self.master['photband'])) keep = (self.master['cwave']<1.6e6) | np.isnan(self.master['cwave']) keep = keep & include - if ('igrid_search' in mtype) | ('iminimize' in mtype): #mtype in ['igrid_search', 'iminimize']: - #-- get the metallicity right - files = model.get_file(z='*') - if type(files) == str: files = [files] #files needs to be a list! - metals = np.array([pf.getheader(ff)['Z'] for ff in files]) - metals = metals[np.argmin(np.abs(metals-self.results[mtype]['CI']['z']))] - scale = self.results[mtype]['CI']['scale'] - #-- get (approximated) reddened and unreddened model - wave,flux = model.get_table(teff=self.results[mtype]['CI']['teff'], - logg=self.results[mtype]['CI']['logg'], - ebv=self.results[mtype]['CI']['ebv'], - z=metals, - law=law) - wave_ur,flux_ur = model.get_table(teff=self.results[mtype]['CI']['teff'], + # -- get the metallicity right + # files = model.get_file(z='*') + # print('FILE', kwargs) + #files needs to be a list! + if 'z_skip' not in kwargs: + files = model.get_file(z='*') + if type(files) == str: files = [files] + metals = np.array([pf.getheader(ff)['Z'] for ff in files]) + metals = metals[np.argmin(np.abs(metals-self.results[mtype]['CI']['z']))] + + scale = self.results[mtype]['CI']['scale'] + # -- get (approximated) reddened and unreddened model + wave,flux = model.get_table(teff=self.results[mtype]['CI']['teff'], logg=self.results[mtype]['CI']['logg'], - ebv=0, + ebv=self.results[mtype]['CI']['ebv'], z=metals, law=law) - #-- get synthetic photometry - synflux_,Labs = model.get_itable(teff=self.results[mtype]['CI']['teff'], - logg=self.results[mtype]['CI']['logg'], - ebv=self.results[mtype]['CI']['ebv'], - z=self.results[mtype]['CI']['z'], - photbands=self.master['photband'][keep]) + wave_ur,flux_ur = model.get_table(teff=self.results[mtype]['CI']['teff'], + logg=self.results[mtype]['CI']['logg'], + ebv=0, + z=metals, + law=law) + # -- get synthetic photometry + synflux_,Labs = model.get_itable(teff=self.results[mtype]['CI']['teff'], + logg=self.results[mtype]['CI']['logg'], + ebv=self.results[mtype]['CI']['ebv'], + z=self.results[mtype]['CI']['z'], + photbands=self.master['photband'][keep]) + else: + scale = self.results[mtype]['CI']['scale'] + # -- get (approximated) reddened and unreddened model + wave,flux = model.get_table(teff=self.results[mtype]['CI']['teff'], + logg=self.results[mtype]['CI']['logg'], + ebv=self.results[mtype]['CI']['ebv'], + law=law) + wave_ur,flux_ur = model.get_table(teff=self.results[mtype]['CI']['teff'], + logg=self.results[mtype]['CI']['logg'], + ebv=0,law=law) + # -- get synthetic photometry + synflux_,Labs = model.get_itable(teff=self.results[mtype]['CI']['teff'], + logg=self.results[mtype]['CI']['logg'], + ebv=self.results[mtype]['CI']['ebv'], + photbands=self.master['photband'][keep],**kwargs) + flux,flux_ur = flux*scale,flux_ur*scale @@ -2053,7 +1543,7 @@ def set_best_model(self,mtype='igrid_search',law='fitzpatrick2004',**kwargs): # photbands=self.master['photband']) synflux[~self.master['color']] *= scale chi2 = (self.master['cmeas']-synflux)**2/self.master['e_cmeas']**2 - #-- calculate effective wavelengths of the photometric bands via the model + # -- calculate effective wavelengths of the photometric bands via the model # values eff_waves = filters.eff_wave(self.master['photband'],model=(wave,flux)) self.results[mtype]['model'] = wave,flux,flux_ur @@ -2069,14 +1559,14 @@ def set_model(self,wave,flux,label='manual',unique_phase=None): The label will be used to store the model in the C{results} attribute. - @param wave: wavelength array (angstrom) - @type wave: ndarray - @param flux: flux array (erg/s/cm2/AA) - @type flux: ndarray - @param label: key used to store the model and synthetic photometry in C{results} - @type label:s str + :param wave: wavelength array (angstrom) + :type wave: ndarray + :param flux: flux array (erg/s/cm2/AA) + :type flux: ndarray + :param label: key used to store the model and synthetic photometry in C{results} + :type label:s str """ - #-- necessary information + # -- necessary information photbands = self.master['photband'] is_color = self.master['color'] if unique_phase != None: @@ -2085,7 +1575,7 @@ def set_model(self,wave,flux,label='manual',unique_phase=None): include = self.master['include'] synflux = np.zeros(len(photbands)) - #-- compute synthetic photometry + # -- compute synthetic photometry synflux[(~is_color) & include] = model.synthetic_flux(wave,flux,photbands[(~is_color) & include]) synflux[is_color & include] = model.synthetic_color(wave,flux,photbands[is_color & include]) chi2 = (self.master['cmeas']-synflux)**2/self.master['e_cmeas']**2 @@ -2125,24 +1615,24 @@ def sample_gridsearch(self,mtype='igrid_search',NrSamples=1,df=None,selfact='chi An array of length "NrSamples" containing 'grid-indices' is returned, so the actual parameter values of the corresponding model can be retrieved from the results dictionary. - @param NrSamples: the number of samples you wish to draw - @type NrSamples: int + :param NrSamples: the number of samples you wish to draw + :type NrSamples: int """ - #-- this function is only checked to work with the results of an igrid_search + # -- this function is only checked to work with the results of an igrid_search if not 'igrid_search' in mtype: return None ranges = self.generate_ranges(teffrange=None,loggrange=None,ebvrange=None,\ zrange=None,rvrange=None,vradrange=None,start_from=mtype) - #-- If nessessary calculate degrees of freedom from the ranges + # -- If nessessary calculate degrees of freedom from the ranges if df == None and ranges != None: df,df_info = self.calculateDF(**ranges) elif df == None: logger.warning('Cannot compute degrees of freedom!!! CHI2 might not make sense. (using df=5)') df = 5 - #-- Do the statistics + # -- Do the statistics N = sum(self.master['include']) k = N-df if k<=0: @@ -2151,14 +1641,14 @@ def sample_gridsearch(self,mtype='igrid_search',NrSamples=1,df=None,selfact='chi logger.info('Statistics based on df={0} and Nobs={1}'.format(df,N)) factor = max(self.results[mtype]['grid'][selfact][-1]/k,1) - #-- Compute the pdf and cdf + # -- Compute the pdf and cdf probdensfunc = scipy.stats.distributions.chi2.pdf(self.results[mtype]['grid'][selfact]/factor,k) cumuldensfunc = probdensfunc.cumsum() - #-- Uniformly sample the cdf, to get a sampling according to the pdf + # -- Uniformly sample the cdf, to get a sampling according to the pdf sample = pl.uniform(cumuldensfunc[0],cumuldensfunc[-1],NrSamples) indices = np.zeros(NrSamples,int) - for i in xrange(NrSamples): + for i in range(NrSamples): indices[i] = (abs(cumuldensfunc-sample[i])).argmin() return indices @@ -2170,14 +1660,14 @@ def chi2(self,select=None,reduced=False,label='igrid_search'): This function is not called anywhere in the builder. """ - #-- select photometry + # -- select photometry master = self.master.copy() keep = np.ones(len(master),bool) if isinstance(select,dict): for key in select: keep = keep & (master[key]==select[key]) master = master[keep] - #-- compute synthetic phtoometry + # -- compute synthetic phtoometry photbands = master['photband'] is_color = master['color'] synflux = np.zeros(len(photbands)) @@ -2199,23 +1689,23 @@ def add_constraint_distance(self,distance=None,mtype='igrid_search',**kwargs): Compute radii, absolute luminosities and masses, and add them to the results. - Extra kwargs go to L{get_distance_from_plx}. + Extra kwargs go to :class:`get_distance_from_plx`. B{Warning:} after calling this function, the C{labs} column in the grid is actually absolutely calibrated and reflects the true absolute luminosity instead of the absolute luminosity assuming 1 solar radius. - @param distance: distance in solar units and error - @type distance: tuple (float,float) - @param mtype: type of results to add the information to - @type mtype: str + :param distance: distance in solar units and error + :type distance: tuple (float,float) + :param mtype: type of results to add the information to + :type mtype: str """ if distance is None: kwargs['lutz_kelker'] = False # we can't handle asymmetric error bars kwargs['unit'] = 'Rsol' distance = self.get_distance_from_plx(**kwargs) - #-- compute the radius, absolute luminosity and mass: note that there is + # -- compute the radius, absolute luminosity and mass: note that there is # an uncertainty on the scaling factor *and* on the distance! scale = conversions.unumpy.uarray([self.results[mtype]['grid']['scale'],\ self.results[mtype]['grid']['escale']]) @@ -2225,7 +1715,7 @@ def add_constraint_distance(self,distance=None,mtype='igrid_search',**kwargs): mass = conversions.derive_mass((self.results[mtype]['grid']['logg'],'[cm/s2]'),\ (radius,'Rsol'),unit='Msol') - #-- store the results in the grid + # -- store the results in the grid mass,e_mass = conversions.unumpy.nominal_values(mass),\ conversions.unumpy.std_devs(mass) radius,e_radius = conversions.unumpy.nominal_values(radius),\ @@ -2233,17 +1723,17 @@ def add_constraint_distance(self,distance=None,mtype='igrid_search',**kwargs): labs,e_labs = conversions.unumpy.nominal_values(labs),\ conversions.unumpy.std_devs(labs) self.results[mtype]['grid']['labs'] = labs # Labs is already there, overwrite - #-- check if the others are already in there or not: + # -- check if the others are already in there or not: labels,data = ['e_labs','radius','e_radius','mass','e_mass'],\ [e_labs,radius,e_radius,mass,e_mass] if 'e_labs' in self.results[mtype]['grid'].dtype.names: for idata,ilabel in zip(data,labels): self.results[mtype]['grid'][ilabel] = idata else: - self.results[mtype]['grid'] = pl.mlab.rec_append_fields(self.results[mtype]['grid'],\ - labels,data) + self.results[mtype]['grid'] = append_fields(self.results[mtype]['grid'],\ + labels,data,asrecarray=False) - #-- update the confidence intervals + # -- update the confidence intervals self.calculate_confidence_intervals(mtype=mtype) logger.info('Added constraint: distance (improved luminosity, added info on radius and mass)') @@ -2251,25 +1741,25 @@ def add_constraint_slo(self,numax,Deltanu0,mtype='igrid_search',chi2_type='red') """ Use diagnostics from solar-like oscillations to put additional constraints on the parameters. - If the results are constrained with the distance before L{add_constraint_distance}, + If the results are constrained with the distance before :class:`add_constraint_distance`, then these results are combined with the SLO constraints. - @param numax: frequency of maximum amplitude - @type numax: 3-tuple (value,error,unit) - @param Deltanu0: large separation (l=0) - @type Deltanu0: 3-tuple (value,error,unit) - @param chi2_type: type of chi2 (raw or reduced) - @type chi2_type: str ('raw' or 'red') + :param numax: frequency of maximum amplitude + :type numax: 3-tuple (value,error,unit) + :param Deltanu0: large separation (l=0) + :type Deltanu0: 3-tuple (value,error,unit) + :param chi2_type: type of chi2 (raw or reduced) + :type chi2_type: str ('raw' or 'red') """ grid = self.results[mtype]['grid'] - #-- we need the teffs, so that we can compute the logg and radius using + # -- we need the teffs, so that we can compute the logg and radius using # the solar-like oscillations information. teff = grid['teff'] logg = grid['logg'] pvalues = [1-grid['ci_'+chi2_type]] names = ['ci_'+chi2_type+'_phot'] - #-- we *always* have a logg, that's the way SEDs work: + # -- we *always* have a logg, that's the way SEDs work: logg_slo = conversions.derive_logg_slo((teff,'K'),numax) logg_slo,e_logg_slo = conversions.unumpy.nominal_values(logg_slo),\ conversions.unumpy.std_devs(logg_slo) @@ -2279,7 +1769,7 @@ def add_constraint_slo(self,numax,Deltanu0,mtype='igrid_search',chi2_type='red') pvalues.append(logg_prob) names.append('ci_logg_slo') - #-- but we only sometimes have a radius (viz. when the distance is + # -- but we only sometimes have a radius (viz. when the distance is # known). There is an uncertainty on that radius! radius_slo = conversions.derive_radius_slo(numax,Deltanu0,(teff,'K'),unit='Rsol') radius_slo,e_radius_slo = conversions.unumpy.nominal_values(radius_slo),\ @@ -2292,7 +1782,7 @@ def add_constraint_slo(self,numax,Deltanu0,mtype='igrid_search',chi2_type='red') radius_prob = scipy.stats.distributions.norm.sf(abs(radius_slo-radius)/total_error) pvalues.append(radius_prob) names.append('ci_radius_slo') - #-- combined standard deviation and mean for two populations with + # -- combined standard deviation and mean for two populations with # possibly zero intersection (wiki: standard deviation) #total_error = np.sqrt( (e_radius_slo**2+e_radius**2)/2. + (radius-radius_slo)**2/4.) total_mean = (radius+radius_slo)/2. @@ -2309,29 +1799,29 @@ def add_constraint_slo(self,numax,Deltanu0,mtype='igrid_search',chi2_type='red') (radius_slo,e_radius_slo,'Rsol'),unit='Msol') mass,e_mass = conversions.unumpy.nominal_values(mass),\ conversions.unumpy.std_devs(mass) - #-- now we can also derive the distance: + # -- now we can also derive the distance: scale = conversions.unumpy.uarray([self.results[mtype]['grid']['scale'],\ self.results[mtype]['grid']['escale']]) distance = radius_slo/conversions.sqrt(scale) distance,e_distance = conversions.unumpy.nominal_values(distance),\ conversions.unumpy.std_devs(distance) - grid = pl.mlab.rec_append_fields(grid,['radius','e_radius','e_labs','mass','e_mass','distance','e_distance'],\ - [radius_slo,e_radius_slo,e_labs,mass,e_mass,distance,e_distance]) + grid = append_fields(grid,['radius','e_radius','e_labs','mass','e_mass','distance','e_distance'],\ + [radius_slo,e_radius_slo,e_labs,mass,e_mass,distance,e_distance],asrecarray=False) logger.info('Added constraint: {0:s} via slo, improved luminosity'.format(', '.join(['radius','e_radius','e_labs','mass','e_mass']))) - #-- combine p values using Fisher's method + # -- combine p values using Fisher's method combined_pvals = evaluate.fishers_method(pvalues) - #-- add the information to the grid - self.results[mtype]['grid'] = pl.mlab.rec_append_fields(grid,\ - names,pvalues) + # -- add the information to the grid + self.results[mtype]['grid'] = append_fields(grid,\ + names,pvalues,asrecarray=False) - #-- and replace the original confidence intervals, and re-order + # -- and replace the original confidence intervals, and re-order self.results[mtype]['grid']['ci_'+chi2_type] = 1-combined_pvals sa = np.argsort(self.results[mtype]['grid']['ci_'+chi2_type])[::-1] self.results[mtype]['grid'] = self.results[mtype]['grid'][sa] - #-- update the confidence intervals + # -- update the confidence intervals self.calculate_confidence_intervals(mtype=mtype) logger.info('Added constraint: {0:s} via slo and replaced ci_{1:s} with combined CI'.format(', '.join(names),chi2_type)) @@ -2353,29 +1843,29 @@ def add_constraint_reddening(self,distance=None,ebv=None,e_ebv=0.1,Rv=3.1,\ C{upper_limit=True}. You can change this behaviour by setting C{model} manually. - @param distance: distance and uncertainty in parsec - @type distance: tuple (float,float) - @param ebv: E(B-V) reddening in magnitude - @type ebv: float - @param e_ebv: error on the reddening in percentage - @type e_ebv: float - @param model: model reddening maps - @type model: str - @param mtype: type of results to add the information to - @type mtype: str - @param upper_limit: consider the E(B-V) value as an upper limit - @type upper_limit: bool - """ - #-- for upper limits on E(B-V), we best use Schlegel maps by default, + :param distance: distance and uncertainty in parsec + :type distance: tuple (float,float) + :param ebv: E(B-V) reddening in magnitude + :type ebv: float + :param e_ebv: error on the reddening in percentage + :type e_ebv: float + :param model: model reddening maps + :type model: str + :param mtype: type of results to add the information to + :type mtype: str + :param upper_limit: consider the E(B-V) value as an upper limit + :type upper_limit: bool + """ + # -- for upper limits on E(B-V), we best use Schlegel maps by default, # otherwise we best use Drimmel maps. if model is None and upper_limit: model = 'schlegel' elif model is None: model = 'drimmel' - #-- if I need to figure out the reddening myself, I need the distance! + # -- if I need to figure out the reddening myself, I need the distance! if ebv is None and distance is None: distance = self.get_distance_from_plx(lutz_kelker=False,unit='pc') - #-- let me figure out the reddening if you didn't give it: + # -- let me figure out the reddening if you didn't give it: if ebv is None: gal = self.info['galpos'] ebv = extinctionmodels.findext(gal[0], gal[1], model=model, distance=distance[0])/Rv @@ -2386,20 +1876,20 @@ def add_constraint_reddening(self,distance=None,ebv=None,e_ebv=0.1,Rv=3.1,\ e_ebv = 0.1*ebv grid = self.results[mtype]['grid'] ebvs = grid['ebv'] - #-- probabilities are differently calculated depending on upper limit. + # -- probabilities are differently calculated depending on upper limit. if not upper_limit: ebv_prob = scipy.stats.distributions.norm.cdf(abs(ebv-ebvs)/e_ebv) - #-- combine p values using Fisher's method + # -- combine p values using Fisher's method combined_pvals = evaluate.fishers_method([1-grid['ci_'+chi2_type],ebv_prob]) grid['ci_'+chi2_type] = 1-combined_pvals else: grid['ci_'+chi2_type] = np.where(ebvs<=ebv,grid['ci_'+chi2_type],1.) - #-- and replace the original confidence intervals, and re-order + # -- and replace the original confidence intervals, and re-order sa = np.argsort(grid['ci_'+chi2_type])[::-1] self.results[mtype]['grid'] = grid[sa] - #-- update the confidence intervals + # -- update the confidence intervals self.calculate_confidence_intervals(mtype=mtype) logger.info('Added constraint: E(B-V)={0}+/-{1}'.format(ebv,e_ebv)) @@ -2420,17 +1910,17 @@ def add_constraint_mass(self,mass,mtype='igrid_search',chi2_type='red'): masses = grid['mass'] if normal: mass_prob = scipy.stats.distributions.norm.cdf(abs(mass[0]-masses)/mass[1]) - #-- combine p values using Fisher's method + # -- combine p values using Fisher's method combined_pvals = evaluate.fishers_method([1-grid['ci_'+chi2_type],mass_prob]) grid['ci_'+chi2_type] = 1-combined_pvals else: grid['ci_'+chi2_type] = np.where((masses<=mass[0]) | (mass[1]<=masses),1.,grid['ci_'+chi2_type]) - #-- and replace the original confidence intervals, and re-order + # -- and replace the original confidence intervals, and re-order sa = np.argsort(grid['ci_'+chi2_type])[::-1] self.results[mtype]['grid'] = grid[sa] - #-- update the confidence intervals + # -- update the confidence intervals self.calculate_confidence_intervals(mtype=mtype) logger.info('Added constraint: mass={0}+/-{1}'.format(*mass)) @@ -2442,25 +1932,25 @@ def add_constraint_evolution_models(self,models='siess2000',\ """ grid = self.results[mtype]['grid'] - #-- make sure y's and ylabels are iterable + # -- make sure y's and ylabels are iterable if isinstance(ylabels,str): ylabels = [ylabels] - #-- cycle over all yvalues and compute the pvalues + # -- cycle over all yvalues and compute the pvalues pvalues = [1-grid['ci_'+chi2_type]] add_info = [] - #-- create the evolutionary grid and interpolate the stellar evolutinary + # -- create the evolutionary grid and interpolate the stellar evolutinary # grid on the SED-integrated grid points output = np.array([evolutionmodels.get_itable(iteff,ilogg,iz) for iteff,ilogg,iz in zip(grid['teff'],grid['logg'],grid['z'])]) output = np.rec.fromarrays(output.T,names=['age','labs','radius']) for label in ylabels: y_interpolated = output[label] add_info.append(y_interpolated) - #-- only add the contraint when it is possible to do so. + # -- only add the contraint when it is possible to do so. if not label in grid.dtype.names: logger.info("Cannot put constraint on {} (not a parameter)".format(label)) continue y_computed = grid[label] - #-- error on the y-value: either it is computed, it is given or it is + # -- error on the y-value: either it is computed, it is given or it is # assumed it is 10% of the value if e_y is None and ('e_'+label) in grid.dtype.names: e_y = np.sqrt(grid['e_'+label]**2+(0.1*y_interpolated)**2) @@ -2470,7 +1960,7 @@ def add_constraint_evolution_models(self,models='siess2000',\ pl.figure() pl.subplot(221) pl.title(label) - pl.scatter(y_computed,y_interpolated,c=y_prob,edgecolors='none',cmap=pl.cm.spectral) + pl.scatter(y_computed,y_interpolated,c=y_prob,edgecolors='none',cmap=plt.cm.Spectral) pl.plot([pl.xlim()[0],pl.xlim()[1]],[pl.xlim()[0],pl.xlim()[1]],'r-',lw=2) pl.xlim(pl.xlim()) pl.ylim(pl.xlim()) @@ -2479,11 +1969,11 @@ def add_constraint_evolution_models(self,models='siess2000',\ pl.colorbar() pl.subplot(223) pl.title('y_interpolated') - pl.scatter(grid['teff'],grid['logg'],c=y_interpolated,edgecolors='none',cmap=pl.cm.spectral) + pl.scatter(grid['teff'],grid['logg'],c=y_interpolated,edgecolors='none',cmap=plt.cm.Spectral) pl.colorbar() pl.subplot(224) pl.title('y_computed') - pl.scatter(grid['teff'],grid['logg'],c=y_computed,edgecolors='none',cmap=pl.cm.spectral) + pl.scatter(grid['teff'],grid['logg'],c=y_computed,edgecolors='none',cmap=plt.cm.Spectral) pl.colorbar() pvalues.append(y_prob) @@ -2491,53 +1981,53 @@ def add_constraint_evolution_models(self,models='siess2000',\ pl.subplot(221) pl.title('p1') sa = np.argsort(pvalues[0]) - pl.scatter(grid['labs'][sa],grid['radius'][sa],c=pvalues[0][sa],edgecolors='none',cmap=pl.cm.spectral) + pl.scatter(grid['labs'][sa],grid['radius'][sa],c=pvalues[0][sa],edgecolors='none',cmap=plt.cm.Spectral) pl.colorbar() pl.xlabel('labs') pl.ylabel('radius') pl.subplot(222) pl.title('p2') sa = np.argsort(pvalues[1]) - pl.scatter(grid['labs'][sa],grid['radius'][sa],c=pvalues[1][sa],edgecolors='none',cmap=pl.cm.spectral) + pl.scatter(grid['labs'][sa],grid['radius'][sa],c=pvalues[1][sa],edgecolors='none',cmap=plt.cm.Spectral) pl.colorbar() pl.xlabel('labs') pl.ylabel('radius') pl.subplot(223) pl.title('p3') sa = np.argsort(pvalues[2]) - pl.scatter(grid['labs'][sa],grid['radius'][sa],c=pvalues[2][sa],edgecolors='none',cmap=pl.cm.spectral) + pl.scatter(grid['labs'][sa],grid['radius'][sa],c=pvalues[2][sa],edgecolors='none',cmap=plt.cm.Spectral) pl.colorbar() pl.xlabel('labs') pl.ylabel('radius') - #-- combine p values using Fisher's method + # -- combine p values using Fisher's method combined_pvals = evaluate.fishers_method(pvalues) pl.subplot(224) pl.title('pcombined') sa = np.argsort(combined_pvals) - pl.scatter(grid['labs'][sa],grid['radius'][sa],c=combined_pvals[sa],edgecolors='none',cmap=pl.cm.spectral) + pl.scatter(grid['labs'][sa],grid['radius'][sa],c=combined_pvals[sa],edgecolors='none',cmap=plt.cm.Spectral) pl.colorbar() pl.xlabel('labs') pl.ylabel('radius') pl.show() - #-- add the information to the grid (assume that if there one label + # -- add the information to the grid (assume that if there one label # not in there already, none of them are if not 'c_'+ylabels[0] in grid.dtype.names: - self.results[mtype]['grid'] = pl.mlab.rec_append_fields(grid,\ - ['c_'+ylabel for ylabel in ylabels],add_info) - #-- if they are already in there, overwrite! + self.results[mtype]['grid'] = append_fields(grid,\ + ['c_'+ylabel for ylabel in ylabels],add_info,asrecarray=False) + # -- if they are already in there, overwrite! else: for info,ylabel in zip(add_info,ylabels): self.results[mtype]['grid']['c_'+ylabel] = add_info - #-- and replace the original confidence intervals, and re-order + # -- and replace the original confidence intervals, and re-order self.results[mtype]['grid']['ci_'+chi2_type] = 1-combined_pvals sa = np.argsort(self.results[mtype]['grid']['ci_'+chi2_type])[::-1] self.results[mtype]['grid'] = self.results[mtype]['grid'][sa] - #-- update the confidence intervals + # -- update the confidence intervals self.calculate_confidence_intervals(mtype=mtype) logger.info('Added constraint: {0:s} via stellar models and replaced ci_{1:s} with combined CI'.format(', '.join(ylabels),chi2_type)) @@ -2599,7 +2089,7 @@ def plot_grid(self,x='teff',y='logg',ptype='ci_red',mtype='igrid_search',limit=0 ]]include figure]]ivs_sed_builder_plot_grid_01.png] """ - #-- if no distance is set, derive the most likely distance from the plx: + # -- if no distance is set, derive the most likely distance from the plx: #if d is None: #try: #d = self.get_distance_from_plx() @@ -2608,7 +2098,7 @@ def plot_grid(self,x='teff',y='logg',ptype='ci_red',mtype='igrid_search',limit=0 #logger.info('Distance to {0} unknown'.format(self.ID)) #if isinstance(d,tuple): #d = d[0][np.argmax(d[1])] - ##-- it's possible that we still don't have a distance + ## -- it's possible that we still don't have a distance #if d is not None: #logger.info('Assumed distance to {0} = {1:.3e} pc'.format(self.ID,d)) #radius = d*np.sqrt(self.results[mtype]['grid']['scale']) @@ -2616,14 +2106,14 @@ def plot_grid(self,x='teff',y='logg',ptype='ci_red',mtype='igrid_search',limit=0 #labs = np.log10(self.results[mtype]['grid']['labs']*radius**2) # in [Lsol] #mass = conversions.derive_mass((self.results[mtype]['grid']['logg'].copy(),'[cm/s2]'),\ #(radius,'Rsol'),unit='Msol') - #-- compute angular diameter + # -- compute angular diameter theta = 2*conversions.convert('sr','mas',self.results[mtype]['grid']['scale']) if limit is not None: region = self.results[mtype]['grid']['ci_red']<=limit else: region = self.results[mtype]['grid']['ci_red']1e-4 pl.plot(d,dprob,'k-') pl.grid() @@ -3126,11 +2618,11 @@ def plot_grid_model(self,ptype='prob'): tp_sa = np.argsort(total_prob)[::-1] if ptype=='prob': pl.scatter(self.results['igrid_search']['grid']['teff'][cutlogg][-n:][tp_sa],self.results['igrid_search']['grid']['logg'][cutlogg][-n:][tp_sa], - c=total_prob[tp_sa],edgecolors='none',cmap=pl.cm.spectral, + c=total_prob[tp_sa],edgecolors='none',cmap=plt.cm.Spectral, vmin=total_prob.min(),vmax=total_prob.max()) elif ptype=='radii': pl.scatter(self.results['igrid_search']['grid']['teff'][cutlogg][-n:][tp_sa],self.results['igrid_search']['grid']['logg'][cutlogg][-n:][tp_sa], - c=radii,edgecolors='none',cmap=pl.cm.spectral, + c=radii,edgecolors='none',cmap=plt.cm.Spectral, vmin=radii.min(),vmax=radii.max()) pl.xlim(self.results['igrid_search']['grid']['teff'][region].max(),self.results['igrid_search']['grid']['teff'][region].min()) pl.ylim(self.results['igrid_search']['grid']['logg'][region].max(),self.results['igrid_search']['grid']['logg'][region].min()) @@ -3159,7 +2651,7 @@ def plot_MW_side(self): gal = list(self.info['galpos']) if gal[0]>180: gal[0] = gal[0] - 360. - #-- boundaries of ESO image + # -- boundaries of ESO image pl.imshow(im,extent=[startm,endm,startv,endv],origin='lower') pl.plot(gal[0],gal[1],'rx',ms=15,mew=2) pl.xlim(startm,endm) @@ -3180,7 +2672,7 @@ def plot_MW_top(self): pl.plot(2800,1720,'ro',ms=10) pl.plot([2800,-5000*np.sin(gal[0]/180.*np.pi)+2800],[1720,5000*np.cos(gal[0]/180.*np.pi)+1720],'r-',lw=2) - #-- necessary information + # -- necessary information if 'igrid_search' in self.results and 'd_mod' in self.results['igrid_search']: (d_models,d_prob_models,radii) = self.results['igrid_search']['d_mod'] (d,dprob) = self.results['igrid_search']['d'] @@ -3202,7 +2694,7 @@ def plot_MW_top(self): pl.ylim(ylims) @standalone_figure - def plot_finderchart(self,cmap_photometry=pl.cm.spectral,window_size=5.): + def plot_finderchart(self,cmap_photometry=plt.cm.Spectral,window_size=5.): """ Size is x and y width in arcminutes """ @@ -3221,7 +2713,7 @@ def plot_finderchart(self,cmap_photometry=pl.cm.spectral,window_size=5.): set_systems = sorted(list(set(systems))) color_cycle = itertools.cycle([cmap_photometry(j) for j in np.linspace(0, 1.0, len(set_systems))]) for system in set_systems: - color = color_cycle.next() + color = next(color_cycle) keep = systems==system if sum(keep): pl.plot(toplot['_RAJ2000'][keep][0]*60, @@ -3252,8 +2744,8 @@ def plot_finderchart(self,cmap_photometry=pl.cm.spectral,window_size=5.): e_max_distance = abs(max_distance - d[np.argmin(np.abs(dprob-0.5*max(dprob)))]) elif 'plx' in self.info and 'v' in self.info['plx'] and 'e' in self.info['plx']: plx,eplx = self.info['plx']['v'],self.info['plx']['e'] - dist = 1000./ufloat((plx,eplx)) - max_distance,e_max_distance = dist.nominal_value,dist.std_dev() + dist = 1000./ufloat(plx,eplx) + max_distance,e_max_distance = dist.nominal_value,dist.std_dev else: max_distance = 1000. e_max_distance = 100. @@ -3263,9 +2755,9 @@ def plot_finderchart(self,cmap_photometry=pl.cm.spectral,window_size=5.): max_distance = conversions.convert('pc','km',max_distance,e_max_distance) ppm_ra = conversions.convert('mas/yr','rad/s',*ppm_ra) ppm_de = conversions.convert('mas/yr','rad/s',*ppm_de) - max_distance = unumpy.uarray([max_distance[0],max_distance[1]]) - x = unumpy.uarray([ppm_ra[0],ppm_ra[1]]) - y = unumpy.uarray([ppm_de[0],ppm_de[1]]) + max_distance = unumpy.uarray(*[max_distance[0],max_distance[1]]) + x = unumpy.uarray(*[ppm_ra[0],ppm_ra[1]]) + y = unumpy.uarray(*[ppm_de[0],ppm_de[1]]) velocity = max_distance*utan( usqrt(x**2+y**2)) @@ -3312,27 +2804,29 @@ def save_photometry(self,photfile=None): """ Save master photometry to a file. - @param photfile: name of the photfile. Defaults to C{starname.phot}. - @type photfile: str + :param photfile: name of the photfile. Defaults to C{starname.phot}. + :type photfile: str """ - #-- write to file + # -- write to file if photfile is not None: self.photfile = photfile logger.info('Save photometry to file %s'%(self.photfile)) - #-- add some comments + # -- add some comments if self.ID: if not 'bibcode' in self.master.dtype.names: self.master = crossmatch.add_bibcodes(self.master) if not 'comments' in self.master.dtype.names: self.master = vizier.quality_check(self.master,self.ID) - ascii.write_array(self.master,self.photfile,header=True,auto_width=True,use_float='%g',comments=['#'+json.dumps(self.info)]) + ascii.write_array(self.master, self.photfile, header=True, + auto_width=True, use_float='%g', + comments=['#'+json.dumps(self.info)]) def load_photometry(self,photfile=None): """ Load the contents of the photometry file to the master record. - @param photfile: name of the photfile. Defaults to the value of C{self.photfile}. - @type photfile: str + :param photfile: name of the photfile. Defaults to the value of C{self.photfile}. + :type photfile: str """ if photfile is not None: self.photfile = photfile @@ -3371,10 +2865,10 @@ def save_fits(self,filename=None,overwrite=True): 6) imc (CI from monte carlo = actual monte carlo samples) 7) synflux_imc (integrated synthetic fluxes for best model from monte carlo) - @param filename: name of SED FITS file - @type filename: string - @param overwrite: overwrite old FITS file if true - @type overwrite: boolean + :param filename: name of SED FITS file + :type filename: string + :param overwrite: overwrite old FITS file if true + :type overwrite: boolean Example usage: @@ -3388,7 +2882,7 @@ def save_fits(self,filename=None,overwrite=True): os.remove(filename) logger.info('Old FITS file removed') - #-- write primary header + # -- write primary header #prim_header = {} #for key in self.info: #if not (isinstance(self.info[key],float) or isinstance(self.info[key],str)): @@ -3396,11 +2890,11 @@ def save_fits(self,filename=None,overwrite=True): #prim_header[key] = self.info[key] #fits.write_recarray(np.array([[0]]),filename,header_dict=prim_header,ext=0) - #-- write master data + # -- write master data master = self.master.copy() fits.write_recarray(master,filename,header_dict=dict(extname='data')) - #-- write the rest + # -- write the rest for mtype in self.results:#['igrid_search','imc']: eff_waves,synflux,photbands = self.results[mtype]['synflux'] chi2 = self.results[mtype]['chi2'] @@ -3427,7 +2921,7 @@ def save_fits(self,filename=None,overwrite=True): headerdict = results_modeldict.copy() key = 'model{}'.format(index) headerdict['extname'] = key+'_'+mtype - if key in self.results[mtype].keys(): + if key in list(self.results[mtype].keys()): fits.write_array(list(self.results[mtype][key]),filename, names=('wave','flux','dered_flux'), units=('AA','erg/s/cm2/AA','erg/s/cm2/AA'), @@ -3460,10 +2954,10 @@ def save_fits(self,filename=None,overwrite=True): #6) imc (CI from monte carlo = actual monte carlo samples) #7) synflux_imc (integrated synthetic fluxes for best model from monte carlo) - #@param filename: name of SED FITS file - #@type filename: string - #@rtype: bool - #@return: true if Fits file could be loaded + #:param filename: name of SED FITS file + #:type filename: string + #:rtype: bool + #:return: true if Fits file could be loaded #""" #if filename is None: #filename = os.path.splitext(self.photfile)[0]+'.fits' @@ -3472,21 +2966,21 @@ def save_fits(self,filename=None,overwrite=True): #return False #ff = pf.open(filename) - ##-- observed photometry + ## -- observed photometry #fields = ff['data'].columns.names #master = np.rec.fromarrays([ff['data'].data.field(field) for field in fields],names=','.join(fields)) - ##-- remove the whitespace in columns with strings added by fromarrays + ## -- remove the whitespace in columns with strings added by fromarrays #for i,name in enumerate(master.dtype.names): #if master.dtype[i].str.count('S'): #for j,element in enumerate(master[name]): #master[name][j] = element.strip() #self.master = master - ##-- add dictionary that will contain the results + ## -- add dictionary that will contain the results #if not hasattr(self,'results'): #self.results = {} - ##-- grid search and MC results + ## -- grid search and MC results #mtypes = [ext.header['extname'] for ext in ff[1:]] #mtypes = list(set(mtypes) - set(['data'])) @@ -3529,7 +3023,7 @@ def save_fits(self,filename=None,overwrite=True): #continue #self.results[mtype]['CI'] = {} #for key in headerkeys: - ##-- we want to have the same types as the original: numpy.float64 --> np.array([..])[0] + ## -- we want to have the same types as the original: numpy.float64 --> np.array([..])[0] #self.results[mtype]['CI'][key.lower()] = np.array([ff[mtype].header[key]])[0] #except KeyError: #continue @@ -3563,10 +3057,10 @@ def load_fits(self,filename=None): 6) imc (CI from monte carlo = actual monte carlo samples) 7) synflux_imc (integrated synthetic fluxes for best model from monte carlo) - @param filename: name of SED FITS file - @type filename: string - @rtype: bool - @return: true if Fits file could be loaded + :param filename: name of SED FITS file + :type filename: string + :rtype: bool + :return: true if Fits file could be loaded """ if filename is None: filename = os.path.splitext(self.photfile)[0]+'.fits' @@ -3575,22 +3069,22 @@ def load_fits(self,filename=None): return False ff = pf.open(filename) - #-- observed photometry + # -- observed photometry fields = ff['data'].columns.names master = np.rec.fromarrays([ff['data'].data.field(field) for field in fields],names=','.join(fields)) - #-- remove the whitespace in columns with strings added by fromarrays + # -- remove the whitespace in columns with strings added by fromarrays for i,name in enumerate(master.dtype.names): if master.dtype[i].str.count('S'): for j,element in enumerate(master[name]): master[name][j] = element.strip() self.master = master - #-- add dictionary that will contain the results + # -- add dictionary that will contain the results if not hasattr(self,'results'): self.results = {} - #-- grid search and MC results + # -- grid search and MC results mtypes = [ext.header['extname'] for ext in ff[1:]] mtypes = list(set(mtypes) - set(['data','DATA'])) @@ -3608,7 +3102,7 @@ def load_fits(self,filename=None): if 'factor' in ff[mtype].header: self.results[mtype]['factor'] = np.array([ff[mtype].header['factor']])[0] - headerkeys = ff[mtype].header.keys() #ascardlist().keys() + headerkeys = list(ff[mtype].header.keys()) #ascardlist().keys() for key in headerkeys[::-1]: for badkey in ['xtension','bitpix','naxis','pcount','gcount','tfields','ttype','tform','tunit','factor','extname']: if key.lower().count(badkey): @@ -3616,10 +3110,10 @@ def load_fits(self,filename=None): continue self.results[mtype]['CI'] = {} for key in headerkeys: - #-- we want to have the same types as the original: numpy.float64 --> np.array([..])[0] + # -- we want to have the same types as the original: numpy.float64 --> np.array([..])[0] self.results[mtype]['CI'][key.lower()] = np.array([ff[mtype].header[key]])[0] - except KeyError,msg: - print msg + except KeyError as msg: + print(msg) continue else: splitted = mtype.lower().split('_',1) #lstrip('synflux_').lstrip('model_') @@ -3644,17 +3138,18 @@ def save_hdf5(self, filename=None, update=True): This way of saving is more thorough that save_fits(), fx. the CI2D confidence intervals are not save to a fits file, but are saved to a hdf5 file. Currently the following data is saved to HDF5 file: - - sed.master (photometry) - - sed.results (results from all fitting methods) - - sed.constraints (extra constraints on the fits) - @param filename: name of SED FITS file - @type filename: string - @param update: if True, an existing file will be updated with the current information, if - False, an existing fill be overwritten - @type update: bool - @return: the name of the output HDF5 file. - @rtype: string + - sed.master (photometry) + - sed.results (results from all fitting methods) + - sed.constraints (extra constraints on the fits) + + :param filename: name of SED FITS file + :type filename: string + :param update: if True, an existing file will be updated with the current information, if + False, an existing fill be overwritten + :type update: bool + :return: the name of the output HDF5 file. + :rtype: string """ if filename is None: @@ -3674,10 +3169,10 @@ def load_hdf5(self,filename=None): """ Load a previously made SED from HDF5 file. - @param filename: name of SED FITS file - @type filename: string - @return: True if HDF5 file could be loaded - @rtype: bool + :param filename: name of SED FITS file + :type filename: string + :return: True if HDF5 file could be loaded + :rtype: bool """ if filename is None: filename = os.path.splitext(self.photfile)[0]+'.hdf5' @@ -3692,7 +3187,7 @@ def load_hdf5(self,filename=None): self.constraints = data.get('constraints', {}) logger.info('Loaded previous results from HDF5 file: %s'%(filename)) - logger.debug('Loaded following datasets from HDF5 file:\n %s'%(data.keys())) + logger.debug('Loaded following datasets from HDF5 file:\n %s'%(list(data.keys()))) return True def save_bibtex(self): @@ -3709,14 +3204,14 @@ def save_summary(self,filename=None,CI_limit=None,method='igrid_search',chi2type """ Save a summary of the results to an ASCII file. """ - #-- open the summary file to write the results + # -- open the summary file to write the results if filename is None: filename = os.path.splitext(self.photfile)[0]+'.sum' if CI_limit is None: CI_limit = self.CI_limit - #-- gather the results: + # -- gather the results: grid_results = self.results[method]['grid'] start_CI = np.argmin(np.abs(grid_results[chi2type]-CI_limit)) factor = self.results[method]['factor'] @@ -3728,7 +3223,7 @@ def save_summary(self,filename=None,CI_limit=None,method='igrid_search',chi2type grid_results[name][start_CI:].max() names += [name+'_l',name,name+'_u'] results += [lv,cv,uv] - #-- write the used photometry to a file + # -- write the used photometry to a file include_grid = self.master['include'] photbands = ":".join(self.master[include_grid]['photband']) references = ",".join(self.master[include_grid]['bibcode']) @@ -3746,27 +3241,29 @@ def save_summary(self,filename=None,CI_limit=None,method='igrid_search',chi2type logger.info('Saved summary to {0}'.format(filename)) - def save_important_info(self,filename=None,CI_limit=None,method='igrid_search',chi2type='ci_red'): + def save_important_info(self, filename=None, CI_limit=None, + method='igrid_search', chi2type='ci_red', + exclude=set()): """ Save a summary of the results to an ASCII file. """ - #-- open the summary file to write the results + # -- open the summary file to write the results if filename is None: filename = os.path.splitext(self.photfile)[0]+'.sum' if CI_limit is None: CI_limit = self.CI_limit - #-- gather the results: + # -- gather the results: grid_results = self.results[method]['grid'] start_CI = np.argmin(np.abs(grid_results[chi2type]-CI_limit)) factor = self.results[method]['factor'] #names = ['scaling_factor','chi2_type','ci_limit'] results = [factor,chi2type,CI_limit*100] - print 'Metallicity:' - print grid_results['z'][-1] - wanted_names = ['ebv','logg','teff','z','chisq'] - for name in wanted_names: + + wanted_names = {'ebv','logg','teff','z','chisq'} + result_names = wanted_names.difference(exclude) + for name in result_names: lv,cv,uv = grid_results[name][start_CI:].min(),\ grid_results[name][-1],\ grid_results[name][start_CI:].max() @@ -3815,7 +3312,7 @@ def constraints2str(self): Summarizes all constraints in a string. """ res = "" - for key in self.constraints.keys(): + for key in list(self.constraints.keys()): res += "Using constraint: %s = %s\n"%(key, self.constraints[key]) res = res[:-1] return res @@ -3837,7 +3334,7 @@ def igrid_search(self,points=100000,teffrange=None,loggrange=None,ebvrange=None, if CI_limit is None or CI_limit > 1.0: CI_limit = self.CI_limit - #-- set defaults limits + # -- set defaults limits ranges = self.generate_ranges(teffrange=teffrange[0],\ loggrange=loggrange[0],ebvrange=ebvrange[0],\ zrange=zrange[0],rvrange=rvrange[0],vradrange=vradrange[0], @@ -3846,7 +3343,7 @@ def igrid_search(self,points=100000,teffrange=None,loggrange=None,ebvrange=None, z2range=zrange[1],rv2range=rvrange[1],vrad2range=vradrange[1], rad2range=radrange[1]) - #-- grid search on all include data: extract the best CHI2 + # -- grid search on all include data: extract the best CHI2 include_grid = self.master['include'] logger.info('The following measurements are included in the fitting process:\n%s'%\ (photometry2str(self.master[include_grid]))) @@ -3854,7 +3351,7 @@ def igrid_search(self,points=100000,teffrange=None,loggrange=None,ebvrange=None, logger.info('The following constraints are included in the fitting process:\n%s'%\ (self.constraints2str())) - #-- build the grid, run over the grid and calculate the CHI2 + # -- build the grid, run over the grid and calculate the CHI2 masses = self.constraints.get('masses', None) pars = fit.generate_grid_pix(self.master['photband'][include_grid], masses=masses, points=points, **ranges) @@ -3865,18 +3362,18 @@ def igrid_search(self,points=100000,teffrange=None,loggrange=None,ebvrange=None, **pars) fitres = dict(chisq=chisqs, scale=scales, escale=escales, labs=lumis) - #-- collect all the results in a record array + # -- collect all the results in a record array self.collect_results(grid=pars, fitresults=fitres, mtype='igrid_search') - #-- do the statistics + # -- do the statistics self.calculate_statistics(df=df, ranges=ranges, mtype='igrid_search') - #-- compute the confidence intervals + # -- compute the confidence intervals ci = self.calculate_confidence_intervals(mtype='igrid_search',chi2_type='red',\ CI_limit=CI_limit) self.store_confidence_intervals(mtype='igrid_search', **ci) - #-- remember the best model + # -- remember the best model if set_model: self.set_best_model() def generate_fit_param(self, start_from='igrid_search', **pars): @@ -3886,12 +3383,12 @@ def generate_fit_param(self, start_from='igrid_search', **pars): masses = self.constraints.get('masses', None) result = super(BinarySED, self).generate_fit_param(start_from=start_from, **pars) - #-- Couple ebv and rv of both components + # -- Couple ebv and rv of both components result['ebv2_expr'] = 'ebv' result['rv2_expr'] = 'rv' if masses != None: - #-- Couple the radii to the masses + # -- Couple the radii to the masses G, Msol, Rsol = constants.GG_cgs, constants.Msol_cgs, constants.Rsol_cgs result['rad_value'] = np.sqrt(G*masses[0]*Msol/10**result['logg_value']) result['rad2_value'] = np.sqrt(G*masses[1]*Msol/10**result['logg2_value']) @@ -3926,34 +3423,34 @@ def iminimize(self, teff=(None,None), logg=(None,None), ebv=(None,None), z=(None ebv2=ebv[1],z2=z[1],rv2=rv[1],vrad2=vrad[1], \ start_from=start_from, **ranges) - #-- Print the used data and constraints + # -- Print the used data and constraints include_grid = self.master['include'] logger.info('The following measurements are included in the fitting process:\n%s'%\ (photometry2str(self.master[include_grid]))) logger.info('The following constraints are included in the fitting process:\n%s'%\ (self.constraints2str())) - #-- pass all ranges and starting values to the fitter + # -- pass all ranges and starting values to the fitter kick_list = ['teff', 'logg', 'teff2', 'logg2', 'ebv'] grid, chisq, nfev, scale, lumis = fit.iminimize(self.master['cmeas'][include_grid], self.master['e_cmeas'][include_grid], self.master['photband'][include_grid], points=points, kick_list=kick_list, constraints=self.constraints, **pars) logger.info('Minimizer Succes with startpoints=%s, chi2=%s, nfev=%s'%(len(chisq), chisq[0], nfev[0])) - #-- handle the results + # -- handle the results fitres = dict(chisq=chisq, nfev=nfev, scale=scale, labs=lumis) self.collect_results(grid=grid, fitresults=fitres, mtype='iminimize', selfact='chisq') - #-- Do the statistics + # -- Do the statistics self.calculate_statistics(df=5, ranges=ranges, mtype='iminimize', selfact='chisq') - #-- Store the confidence intervals + # -- Store the confidence intervals ci = self._get_imin_ci(mtype='iminimize',**ranges) self.store_confidence_intervals(mtype='iminimize', **ci) if calc_ci: self.calculate_iminimize_CI(mtype='iminimize', CI_limit=CI_limit) - #-- remember the best model + # -- remember the best model if set_model: self.set_best_model(mtype='iminimize') @@ -3995,7 +3492,7 @@ def set_best_model(self,mtype='igrid_search',law='fitzpatrick2004', **kwargs): """ logger.info('Interpolating approximate full SED of best model') - #-- synthetic flux + # -- synthetic flux include = self.master['include'] synflux = np.zeros(len(self.master['photband'])) keep = (self.master['cwave']<1.6e6) | np.isnan(self.master['cwave']) @@ -4004,7 +3501,7 @@ def set_best_model(self,mtype='igrid_search',law='fitzpatrick2004', **kwargs): if mtype in ['igrid_search', 'iminimize']: scale = self.results[mtype]['CI']['scale'] - #-- get (approximated) reddened and unreddened model + # -- get (approximated) reddened and unreddened model wave,flux = model.get_table_multiple(teff=(self.results[mtype]['CI']['teff'],self.results[mtype]['CI']['teff2']), logg=(self.results[mtype]['CI']['logg'],self.results[mtype]['CI']['logg2']), ebv=(self.results[mtype]['CI']['ebv'],self.results[mtype]['CI']['ebv2']), @@ -4015,9 +3512,9 @@ def set_best_model(self,mtype='igrid_search',law='fitzpatrick2004', **kwargs): ebv=(0,0), radius=(self.results[mtype]['CI']['rad'],self.results[mtype]['CI']['rad2']), law=law) - #-- get synthetic photometry + # -- get synthetic photometry pars = {} - for key in self.results[mtype]['CI'].keys(): + for key in list(self.results[mtype]['CI'].keys()): if not key[-2:] == '_u' and not key[-2:] == '_l': pars[key] = self.results[mtype]['CI'][key] synflux_,pars = model.get_itable(photbands=self.master['photband'][keep], **pars) @@ -4027,7 +3524,7 @@ def set_best_model(self,mtype='igrid_search',law='fitzpatrick2004', **kwargs): synflux[-self.master['color']] *= scale chi2 = (self.master['cmeas']-synflux)**2/self.master['e_cmeas']**2 - #-- calculate effective wavelengths of the photometric bands via the model + # -- calculate effective wavelengths of the photometric bands via the model # values eff_waves = filters.eff_wave(self.master['photband'],model=(wave,flux)) self.results[mtype]['model'] = wave,flux,flux_ur @@ -4067,12 +3564,12 @@ def set_constraints(self, **kwargs): #self.constraints['distance'] = kwargs['distance'] if 'deltaTeff' in kwargs: deltaTeff = kwargs.get('deltaTeff') - print deltaTeff + print(deltaTeff) for i in range(len(deltaTeff)): self.constraints['delta{}Teff'.format(i+1)] = deltaTeff[i] if 'deltaLogg' in kwargs: deltaLogg = kwargs.get('deltaLogg') - print deltaLogg + print(deltaLogg) for i in range(len(deltaTeff)): self.constraints['delta{}Logg'.format(i+1)] = deltaLogg[i] @@ -4081,7 +3578,7 @@ def constraints2str(self): Summarizes all constraints in a string. """ res = "" - for key in self.constraints.keys(): + for key in list(self.constraints.keys()): res += "Using constraint: %s = %s\n"%(key, self.constraints[key]) res = res[:-1] return res @@ -4102,7 +3599,7 @@ def igrid_search(self,points=100000,teffrange=None,loggrange=None,ebvrange=None, if CI_limit is None or CI_limit > 1.0: CI_limit = self.CI_limit - ##-- the data sets + ## -- the data sets include_grid = self.master['include'] logger.info('The following measurements are included in the fitting process:\n%s'%\ (photometry2str(self.master[include_grid]))) @@ -4112,14 +3609,14 @@ def igrid_search(self,points=100000,teffrange=None,loggrange=None,ebvrange=None, logger.info('Data sets with the following phase indices are included in the fitting process:\n%s'%\ (str(unique_phases))) - ##-- set defaults limits + ## -- set defaults limits ranges = self.generate_ranges(teffrange=teffrange,loggrange=loggrange,ebvrange=ebvrange,zrange=zrange,rvrange=rvrange,vradrange=vradrange) - ##-- the constraints used in the fit + ## -- the constraints used in the fit logger.info('The following constraints are included in the fitting process:\n%s'%\ (self.constraints2str())) - ##-- build the grid, run over the grid and calculate the CHI2 + ## -- build the grid, run over the grid and calculate the CHI2 newkwargs = ranges.copy() newkwargs.update(self.constraints) pars = fit.generate_grid_pix_pulsating2(self.master['photband'][include_grid],\ @@ -4133,32 +3630,32 @@ def igrid_search(self,points=100000,teffrange=None,loggrange=None,ebvrange=None, constraints=self.constraints,stat_func=fit.stat_chi2b,\ **pars) - ##-- collect all the results in record arrays, remove the points that fall + ## -- collect all the results in record arrays, remove the points that fall # out of the model grid (the nan in chisqs), do the statistics and # compute the confidence intervals for i in range(len(unique_phases)): fitres = dict(chisq=chisqs[:,i], scale=scales[:,i], escale=escales[:,i], labs=lumis[:,i]) - partpars = pars.fromkeys(pars.keys()) - for key in pars.keys(): + partpars = pars.fromkeys(list(pars.keys())) + for key in list(pars.keys()): partpars[key] = pars[key][:,i] self.collect_results(grid=partpars, fitresults=fitres, mtype='igrid_search_{}'.format(unique_phases[i])) - ##-- do the statistics + ## -- do the statistics self.calculate_statistics(df=df, ranges=ranges, mtype='igrid_search_{}'.format(unique_phases[i])) - ##-- compute the confidence intervals + ## -- compute the confidence intervals ci = self.calculate_confidence_intervals(mtype='igrid_search_{}'.format(unique_phases[i]),\ chi2_type='red',CI_limit=CI_limit) self.store_confidence_intervals(mtype='igrid_search_{}'.format(unique_phases[i]), **ci) - ##-- remember the best model + ## -- remember the best model if set_model: self.set_best_model(mtype='igrid_search_{}'.format(unique_phases[i])) - ##-- collect the results of the all-inclusive fit + ## -- collect the results of the all-inclusive fit index = len(unique_phases) fitres = dict(chisq=chisqs[:,index], scale=scales[:,index], escale=escales[:,index], labs=lumis[:,index]) - allpars = pars.fromkeys(pars.keys()) - for key in pars.keys(): + allpars = pars.fromkeys(list(pars.keys())) + for key in list(pars.keys()): allpars[key] = pars[key][:,0] for i in range(len(unique_phases)-1): fitres['scale{}'.format(i+1)] = scales[:,index+i+1] @@ -4171,15 +3668,15 @@ def igrid_search(self,points=100000,teffrange=None,loggrange=None,ebvrange=None, self.collect_results(grid=allpars, fitresults=fitres, mtype='igrid_search_all') - ##-- do the statistics of the all-inclusive fit + ## -- do the statistics of the all-inclusive fit self.calculate_statistics(df=df, ranges=ranges, mtype='igrid_search_all') - ##-- compute the confidence intervals of the all-inclusive fit + ## -- compute the confidence intervals of the all-inclusive fit ci = self.calculate_confidence_intervals(mtype='igrid_search_all',\ chi2_type='red',CI_limit=CI_limit) self.store_confidence_intervals(mtype='igrid_search_all', **ci) - ##-- remember the best model + ## -- remember the best model if set_model: self.set_best_model(mtype='igrid_search_all') def generate_fit_param(self, start_from='igrid_search', **pars): @@ -4213,7 +3710,7 @@ def set_best_model(self,mtype='igrid_search',law='fitzpatrick2004',**kwargs): if '_all' in mtype: logger.info('Interpolating approximate full SED of best model at additional phases.') - #-- synthetic flux + # -- synthetic flux include = self.master['include'] phases = self.master['phase'] unique_phases = np.unique(phases) @@ -4222,7 +3719,7 @@ def set_best_model(self,mtype='igrid_search',law='fitzpatrick2004',**kwargs): keep = keep & include if ('igrid_search' in mtype) | ('iminimize' in mtype): #mtype in ['igrid_search', 'iminimize']: - #-- get the metallicity right + # -- get the metallicity right files = model.get_file(z='*') if type(files) == str: files = [files] #files needs to be a list! metals = np.array([pf.getheader(ff)['Z'] for ff in files]) @@ -4231,7 +3728,7 @@ def set_best_model(self,mtype='igrid_search',law='fitzpatrick2004',**kwargs): for i in range(len(unique_phases)-1): keep = keep & (phases == unique_phases[i+1]) scale = self.results[mtype]['CI']['scale{}'.format(i+1)] - #-- get (approximated) reddened and unreddened model + # -- get (approximated) reddened and unreddened model wave,flux = model.get_table(teff=self.results[mtype]['CI']['teff{}'.format(i+1)], logg=self.results[mtype]['CI']['logg{}'.format(i+1)], ebv=self.results[mtype]['CI']['ebv'], @@ -4242,7 +3739,7 @@ def set_best_model(self,mtype='igrid_search',law='fitzpatrick2004',**kwargs): ebv=0, z=metals, law=law) - #-- get synthetic photometry + # -- get synthetic photometry synflux_,Labs = model.get_itable(teff=self.results[mtype]['CI']['teff{}'.format(i+1)], logg=self.results[mtype]['CI']['logg{}'.format(i+1)], ebv=self.results[mtype]['CI']['ebv'], @@ -4259,7 +3756,7 @@ def set_best_model(self,mtype='igrid_search',law='fitzpatrick2004',**kwargs): # photbands=self.master['photband']) synflux[-self.master['color'] & keep] *= scale chi2 = (self.master['cmeas']-synflux)**2/self.master['e_cmeas']**2 - #-- calculate effective wavelengths of the photometric bands via the model + # -- calculate effective wavelengths of the photometric bands via the model # values eff_waves = filters.eff_wave(self.master['photband'],model=(wave,flux)) self.results[mtype]['model{}'.format(i+1)] = wave,flux,flux_ur @@ -4299,7 +3796,7 @@ def plot_sed(self,colors=False,mtype='igrid_search',plot_redded=True,plot_deredd def plot_sed_getcolors(master,color_dict=None): myphotbands = [iphotb.split('.')[1] for iphotb in master['photband'][master['color']]] - if not myphotbands: #-- If there are no colours none can be returned (added by joris 30-01-2012) + if not myphotbands: # -- If there are no colours none can be returned (added by joris 30-01-2012) return [],[],[],None if color_dict is None: color_dict = {myphotbands[0]:0} @@ -4315,26 +3812,26 @@ def plot_sed_getcolors(master,color_dict=None): x__,y__,e_y__,color_dict = plot_sed_getcolors(self.master) - #-- get the color cycle + # -- get the color cycle systems = np.array([system.split('.')[0] for system in self.master['photband']],str) set_systems = sorted(list(set(systems))) if ('absolutesymbolcolor' in kwargs) and (kwargs.pop('absolutesymbolcolor') == True): sortedphotsystems,plotcolorvalues = filters.get_plotsymbolcolorinfo() selectedcolorinds = np.array([np.where(sortedphotsystems == system)[0][0] for system in set_systems]) - color_cycle = itertools.cycle([pl.cm.spectral(j) for j in plotcolorvalues[selectedcolorinds]]) + color_cycle = itertools.cycle([plt.cm.Spectral(j) for j in plotcolorvalues[selectedcolorinds]]) else: - color_cycle = itertools.cycle([pl.cm.spectral(j) for j in np.linspace(0, 1.0, len(set_systems))]) + color_cycle = itertools.cycle([plt.cm.Spectral(j) for j in np.linspace(0, 1.0, len(set_systems))]) - #-- for plotting reasons, we translate every color to an integer + # -- for plotting reasons, we translate every color to an integer for system in set_systems: - color = color_cycle.next() + color = next(color_cycle) keep = (systems==system) & (self.master['color']==colors) if not plot_unselected: keep = keep & self.master['include'] if phase: keep = keep & (self.master['phase'] == uniquephase) - #-- synthetic: + # -- synthetic: if sum(keep) and mtype in self.results and 'synflux' in self.results[mtype]: if colors: x,y,e_y,color_dict = plot_sed_getcolors(self.master[keep],color_dict) @@ -4342,15 +3839,15 @@ def plot_sed_getcolors(master,color_dict=None): else: x = self.results[mtype]['synflux'][0][keep] y = self.results[mtype]['synflux'][1][keep] - #-- convert to correct units + # -- convert to correct units y = conversions.convert('erg/s/cm2/AA',flux_units,y,wave=(x,'AA')) x = conversions.convert('AA',wave_units,x) pl.plot(x,y,'x',ms=10,mew=2,alpha=0.75,color=color,**kwargs) - #-- include: + # -- include: keep = (systems==system) & (self.master['color']==colors) & self.master['include'] & (self.master['phase'] == uniquephase) if sum(keep): if colors: - #-- translate every color to an integer + # -- translate every color to an integer x,y,e_y,color_dict = plot_sed_getcolors(self.master[keep],color_dict) else: if mtype in self.results and 'synflux' in self.results[mtype]: @@ -4366,7 +3863,7 @@ def plot_sed_getcolors(master,color_dict=None): p = pl.errorbar(x,y,yerr=e_y,fmt='o',label=system, capsize=10,ms=7,color=color,**kwargs) - #-- exclude: + # -- exclude: label = np.any(keep) and '_nolegend_' or system keep = (systems==system) & (self.master['color']==colors) & -self.master['include'] if phase: @@ -4385,14 +3882,14 @@ def plot_sed_getcolors(master,color_dict=None): pl.errorbar(x,y,yerr=e_y,fmt='o',label=label, capsize=10,ms=7,mew=2,color=color,mfc='1',mec=color,**kwargs) - #-- only set logarithmic scale if absolute fluxes are plotted + # -- only set logarithmic scale if absolute fluxes are plotted # and only plot the real model then if not colors: pl.gca().set_xscale('log',nonposx='clip') pl.gca().set_yscale('log',nonposy='clip') pl.gca().set_autoscale_on(False) - #-- the model + # -- the model if phase & (uniquephase != 0): modelname = 'model{}'.format(uniquephase) else: @@ -4412,7 +3909,7 @@ def plot_sed_getcolors(master,color_dict=None): pl.ylabel(conversions.unit2texlabel(flux_units,full=True)) pl.xlabel('wavelength [{0}]'.format(conversions.unit2texlabel(wave_units))) else: - xlabels = color_dict.keys() + xlabels = list(color_dict.keys()) xticks = [color_dict[key] for key in xlabels] pl.xticks(xticks,xlabels,rotation=90) pl.ylabel(r'Flux ratio') @@ -4441,12 +3938,12 @@ def plot_sed_getcolors(master,color_dict=None): loc,xycoords='axes fraction') logger.info('Plotted SED as %s'%(colors and 'colors' or 'absolute fluxes')) teff = "%d" % teff - logg = "%.2f" % logg - ebv = "%.3f" % ebv - metallicity = "%.3f" % met - '''f = open('/home/anae/python/SEDfitting/Bastars_info.txt', 'a') - f.writelines(str(teff)+'\t'+str(logg)+'\t'+str(ebv)+'\n'+'\t'+str(metallicity)+'\n') - f.closed''' + logg = "%.2f" % logg + ebv = "%.3f" % ebv + metallicity = "%.3f" % met + '''f = open('/home/anae/python/SEDfitting/Bastars_info.txt', 'a') + f.writelines(str(teff)+'\t'+str(logg)+'\t'+str(ebv)+'\n'+'\t'+str(metallicity)+'\n') + f.closed''' class Calibrator(SED): @@ -4456,7 +3953,7 @@ class Calibrator(SED): def __init__(self,ID=None,photfile=None,plx=None,load_fits=False,label='',library='calspec'): names,fits_files,phot_files = model.read_calibrator_info(library=library) index = names.index(ID) - #--retrieve fitsfile information + # --retrieve fitsfile information if library in ['ngsl','calspec']: fits_file = pf.open(fits_files[index]) wave = fits_file[1].data.field('wavelength') @@ -4464,7 +3961,7 @@ def __init__(self,ID=None,photfile=None,plx=None,load_fits=False,label='',librar fits_file.close() elif library=='stelib': wave,flux = fits.read_spectrum(fits_files[index]) - #--photfile: + # --photfile: photfile = phot_files[index] super(Calibrator,self).__init__(photfile=photfile,plx=plx,load_fits=load_fits,label=label) self.set_model(wave,flux) @@ -4482,19 +3979,19 @@ def __init__(self,targets,**kwargs): instances. The SED must exist! That is, for each ID, there must be a phot or FITS file. """ - #-- we don't want to load the FITS files by default because they + # -- we don't want to load the FITS files by default because they # can eat up memory kwargs.setdefault('load_fits',False) self.targets = [] self.seds = [] - #-- create all SEDs + # -- create all SEDs for target in targets: - #-- perhaps we gave an ID: in that case, create the SED instance + # -- perhaps we gave an ID: in that case, create the SED instance if not isinstance(target,SED): mysed = SED(target,**kwargs) if not mysed.has_photfile(): raise ValueError("No phot file found for {}".format(target)) - #-- perhaps already an SED object: then do nothing + # -- perhaps already an SED object: then do nothing else: mysed = target self.seds.append(mysed) @@ -4520,63 +4017,63 @@ def __getitem__(self,key): Allows integer indexing, slicing, indexing with integer and boolean arrays. """ - #-- via slicing + # -- via slicing if isinstance(key,slice): return SampleSEDs([self[ii] for ii in range(*key.indices(len(self)))]) - #-- via an integer + # -- via an integer elif isinstance(key,int): return self.seds[key] else: - #-- try to make the input an array + # -- try to make the input an array try: key = np.array(key) except: raise TypeError("Cannot use instance of type {} for indexing".format(type(key))) - #-- integer array slicing + # -- integer array slicing if key.dtype==np.dtype(int): return SampleSEDs([self[ii] for ii in key]) - #-- boolean array slicing + # -- boolean array slicing elif key.dtype==np.dtype(bool): return SampleSEDs([self[ii] for ii in range(len(key)) if key[ii]]) - #-- that's all I can come up with + # -- that's all I can come up with else: raise TypeError("Cannot use arrays of type {} for indexing".format(key.dtype)) def summarize(self): - #-- collect the names of all the different sources + # -- collect the names of all the different sources sources = {} for sed in self.seds: - #-- collect the different sources + # -- collect the different sources these_sources = list(set(sed.master['source'])) for source in these_sources: if not source in sources: sources[source] = [] - #-- now for each source, collect the names of the passbands + # -- now for each source, collect the names of the passbands keep = sed.master['source']==source sources[source] += list(set(sed.master[keep]['photband'])) sources[source] = sorted(list(set(sources[source]))) - #-- next step: for each source, create a record array where the columns + # -- next step: for each source, create a record array where the columns # are the different photbands. Fill in the values for the photbands # for each target when possible. summary = [] source_names = sorted(sources.keys()) for source in source_names: - #-- create the record array + # -- create the record array data = np.zeros((len(self.targets),2*len(sources[source]))) # but remember to have errors with the photbands names = [] for photband in sources[source]: names += [photband,'e_'+photband] data = np.rec.fromarrays(data.T,names=names) - #-- fill in the values + # -- fill in the values for i,sed in enumerate(self.seds): for photband in sources[source]: keep = (sed.master['source']==source) & (sed.master['photband']==photband) - #-- fill in nans for value and error when not present + # -- fill in nans for value and error when not present if not sum(keep): data[photband][i] = np.nan data['e_'+photband][i] = np.nan - #-- otherwise give the first value you've found + # -- otherwise give the first value you've found else: data[photband][i] = sed.master[keep]['cmeas'][0] data['e_'+photband][i] = sed.master[keep]['e_cmeas'][0] @@ -4601,9 +4098,9 @@ def get_data(self,source,photband,label=None): else: synflux_label = [] for targetname,sed in zip(self.targets,self.seds): - #-- for the source, collect the names of the passbands + # -- for the source, collect the names of the passbands keep = (sed.master['source']==source) & (sed.master['photband']==photband) - #-- is there a model SED for which we want to retrieve synthetic photometry? + # -- is there a model SED for which we want to retrieve synthetic photometry? if label is not None: if sum(keep)==0: synflux = [0] @@ -4611,13 +4108,13 @@ def get_data(self,source,photband,label=None): synflux = [sed.results[label]['synflux'][1][keep][0]] else: synflux = [] - #-- if no data on the matter is present, put zero values everywhere + # -- if no data on the matter is present, put zero values everywhere if sum(keep)==0: records.append([targetname]+list(np.zeros(len(sed.master.dtype.names)))+synflux) else: records.append([targetname]+list(sed.master[keep][0])+synflux) - #-- make the thing into a record array - dtypes = np.dtype([('targetname','|S25')] + sed.master.dtype.descr + synflux_label) + # -- make the thing into a record array + dtypes = np.dtype([('targetname','U25')] + sed.master.dtype.descr + synflux_label) output = np.rec.fromrecords(records,names=dtypes.names) output = np.array(output,dtype=dtypes) return output @@ -4657,17 +4154,17 @@ def get_confidence_interval(self,parameter='teff',mtype='igrid_search'): mysed.get_photometry(units=units) mysed.plot_data() pl.show() - answer = raw_input('Keep photometry file %s (y/N)'%(mysed.photfile)) + answer = input('Keep photometry file %s (y/N)'%(mysed.photfile)) if not 'y' in answer.lower(): os.unlink(mysed.photfile) logger.info('Removed %s'%(mysed.photfile)) raise SystemExit - #-- clean up + # -- clean up if os.path.isfile('HD180642.fits'): os.remove('HD180642.fits') raise SystemExit - #-- PCA analysis + # -- PCA analysis master['include'] = True exclude(master,names=['STROMGREN.HBN-HBW','USNOB1'],wrange=(1.5e4,np.inf)) do_pca = False @@ -4676,7 +4173,7 @@ def get_confidence_interval(self,parameter='teff',mtype='igrid_search'): if sum(keep)>2: do_pca = True logger.info("Start of PCA analysis to find fundamental parameters") - colors,index = np.unique1d(master['photband'][include_pca],return_index=True) + colors,index = np.unique(master['photband'][include_pca],return_index=True) A,grid = fit.get_PCA_grid(colors,ebvrange=(0,0.5),res=10) P,T,(means,stds) = fit.get_PCA(A) calib = fit.calibrate_PCA(T,grid,function='linear') @@ -4685,14 +4182,14 @@ def get_confidence_interval(self,parameter='teff',mtype='igrid_search'): teff,logg,ebv = pars[0] logger.info("PCA result: teff=%.0f, logg=%.2f, E(B-V)=%.3f"%(teff,logg,ebv)) - #-- find angular diameter + # -- find angular diameter logger.info('Estimation of angular diameter') iflux = model.get_itable(teff=teff,logg=logg,ebv=ebv,photbands=master['photband'][include_grid]) scale_pca = np.median(master['cmeas'][include_grid]/iflux) angdiam = 2*np.arctan(np.sqrt(scale_pca))/np.pi*180*3600*1000 logger.info('Angular diameter = %.3f mas'%(angdiam)) - #-- get model + # -- get model wave_pca,flux_pca = model.get_table(teff=teff,logg=logg,ebv=ebv,law='fitzpatrick1999') wave_ur_pca,flux_ur_pca = model.get_table(teff=teff,logg=logg,ebv=0,law='fitzpatrick1999') @@ -4703,7 +4200,7 @@ def get_confidence_interval(self,parameter='teff',mtype='igrid_search'): toplot = master[-master['color']] systems = np.array([system.split('.')[0] for system in toplot['photband']],str) set_systems = sorted(list(set(systems))) - pl.gca().set_color_cycle([pl.cm.spectral(i) for i in np.linspace(0, 1.0, len(set_systems))]) + pl.gca().set_color_cycle([plt.cm.Spectral(i) for i in np.linspace(0, 1.0, len(set_systems))]) for system in set_systems: keep = systems==system pl.errorbar(master['cwave'][keep],master['cmeas'][keep], diff --git a/sed/creategrids.py b/sed/creategrids.py index f95575108..ef5d7e67b 100644 --- a/sed/creategrids.py +++ b/sed/creategrids.py @@ -35,22 +35,22 @@ def get_responses(responses=None,add_spectrophotometry=False,wave=(0,np.inf)): """ Get a list of response functions and their information. - + You can specify bandpass systems (e.g. GENEVA) and then all Geveva filters will be collected. You can also specific specific bandpasses (e.g. GENEVA.V), in which case only that one will be returned. The default C{None} returns all systems except some Hubble ones. - + You can also set responses to C{None} and give a wavelength range for filter selection. - + Example input for C{responses} are: - + >>> responses = ['GENEVA.V','2MASS.J'] >>> responses = ['GENEVA','2MASS.J'] >>> responses = ['BOXCAR','STROMGREN'] >>> responses = None - + @param responses: a list of filter systems of passbands @type responses: list of str @param add_spectrophotometry: add spectrophotometric filters to the filter list @@ -61,7 +61,7 @@ def get_responses(responses=None,add_spectrophotometry=False,wave=(0,np.inf)): #-- add spectrophometric filtes when asked or when BOXCAR is in responses if add_spectrophotometry or (responses is not None and any(['BOXCAR' in i for i in responses])): bands = filters.add_spectrophotometric_filters() - + #-- if no responses are given, select using wavelength range if responses is None: responses = filters.list_response(wave_range=(wave[0],wave[-1])) @@ -80,9 +80,9 @@ def get_responses(responses=None,add_spectrophotometry=False,wave=(0,np.inf)): #filter_info = filters.get_info(responses) #responses = filter_info['photband'] responses = [resp for resp in responses if not (('ACS' in resp) or ('WFPC' in resp) or ('STIS' in resp) or ('ISOCAM' in resp) or ('NICMOS' in resp))] - + logger.info('Selected response curves: {}'.format(', '.join(responses))) - + return responses #} @@ -94,20 +94,20 @@ def calc_limbdark_grid(responses=None,vrads=[0],ebvs=[0],zs=[0.],\ outfile=None,force=False,**kwargs): """ Calculate a grid of limb-darkening coefficients. - + You need to specify a list of response curves, and a grid of radial velocity (C{vrads}), metallicity (C{z}) and reddening (C{ebvs}) points. You can choose which C{law} to fit and with which C{fitmethod}. Extra kwargs specify the properties of the atmosphere grid to be used. - + If you give a gridfile that already exists, the current file is simply updated with the new passbands, i.e. all overlapping response curves will not be recomputed. Unless you set C{force=True}, in which case previous calculations will be overwritten. You'd probably only want to update or overwrite existing files if you use the same C{vrads}, C{ebvs}, C{zs} etc... - + The generated FITS file has the following structure: - + 1. The primary HDU is empty. The primary header contains only the fit routine (FIT), LD law (LAW) and used grid (GRID) 2. Each table extension has the name of the photometric passband (e.g. @@ -115,19 +115,19 @@ def calc_limbdark_grid(responses=None,vrads=[0],ebvs=[0],zs=[0.],\ Teff, logg, ebv, vrad, z (the grid points) and a1, a2, a3, a4 (the limb darkening coefficients) and Imu1 (the intensity in the center of the disk) and SRS, dint (fit evaluation parameters, see L{ivs.sed.limbdark}). - Although the system and filter can be derived from the extension name, + Although the system and filter can be derived from the extension name, there are also separate entries in the header for "SYSTEM" and "FILTER". - + Example usage: - + >>> calc_limbdark_grid(['MOST.V','GENEVA'],vrads=[0],ebvs=[0.06],zs=[0,0],\ ... law='claret',fitmethod='equidist_r_leastsq',outfile='HD261903.fits') - - + + """ #-- collect response curves from user input photbands = get_responses(responses) - + if outfile is None: outfile = '{}_{}_{}.fits'.format(kwargs.get('grid',limbdark.defaults['grid']),ld_law,fitmethod) #-- add the possibility to update an existing file if it already exists. @@ -138,13 +138,13 @@ def calc_limbdark_grid(responses=None,vrads=[0],ebvs=[0],zs=[0.],\ hdulist = pf.HDUList([]) hdulist.append(pf.PrimaryHDU(np.array([[0,0]]))) existing_bands = [] - + #-- update the header with some information on the fitting parameters hd = hdulist[0].header hd.update('FIT', fitmethod, 'FIT ROUTINE') hd.update('LAW', ld_law, 'FITTED LD LAW') hd.update('GRID', kwargs.get('grid',limbdark.defaults['grid']), 'GRID') - + #-- cycle through all the bands and compute the limb darkening coefficients for i,photband in enumerate(photbands): if photband in existing_bands and not force: @@ -175,7 +175,7 @@ def calc_limbdark_grid(responses=None,vrads=[0],ebvs=[0],zs=[0.],\ logger.info("Forced overwrite of {}".format(photband)) else: hdulist.append(newtable) - + #-- clean up if os.path.isfile(outfile): hdulist.close() @@ -191,16 +191,16 @@ def calc_integrated_grid(threads=1,ebvs=None,law='fitzpatrick2004',Rv=3.1, units='Flambda',responses=None,update=False,add_spectrophotometry=False,**kwargs): """ Integrate an entire SED grid over all passbands and save to a FITS file. - + The output file can be used to fit SEDs more efficiently, since integration over the passbands has already been carried out. - + WARNING: this function can take a loooooong time to compute! - + Extra keywords can be used to specify the grid. - + @param threads: number of threads - @type threads; integer, 'max', 'half' or 'safe' + @type threads; integer, 'max', 'half' or 'safe' @param ebvs: reddening parameters to include @type ebvs: numpy array @param law: interstellar reddening law to use @@ -214,10 +214,10 @@ def calc_integrated_grid(threads=1,ebvs=None,law='fitzpatrick2004',Rv=3.1, @param update: if true append to existing FITS file, otherwise overwrite possible existing file. @type update: boolean - """ + """ if ebvs is None: ebvs = np.r_[0:4.01:0.01] - + #-- select number of threads if threads=='max': threads = cpu_count() @@ -229,7 +229,7 @@ def calc_integrated_grid(threads=1,ebvs=None,law='fitzpatrick2004',Rv=3.1, if threads > len(ebvs): threads = len(ebvs) logger.info('Threads: %s'%(threads)) - + #-- set the parameters for the SED grid model.set_defaults(**kwargs) #-- get the dimensions of the grid: both the grid points, but also @@ -240,7 +240,7 @@ def calc_integrated_grid(threads=1,ebvs=None,law='fitzpatrick2004',Rv=3.1, # also get the information on those filters responses = get_responses(responses=responses,\ add_spectrophotometry=add_spectrophotometry,wave=wave) - + #-- definition of one process: def do_ebv_process(ebvs,arr,responses): logger.debug('EBV: %s-->%s (%d)'%(ebvs[0],ebvs[-1],len(ebvs))) @@ -250,7 +250,7 @@ def do_ebv_process(ebvs,arr,responses): synflux = model.synthetic_flux(wave,flux_,responses,units=units) arr.append([np.concatenate(([ebv],synflux))]) logger.debug("Finished EBV process (len(arr)=%d)"%(len(arr))) - + #-- do the calculations c0 = time.time() output = np.zeros((len(teffs)*len(ebvs),4+len(responses))) @@ -261,11 +261,11 @@ def do_ebv_process(ebvs,arr,responses): for i,(teff,logg) in enumerate(zip(teffs,loggs)): if i>0: logger.info('%s %s %s %s: ET %d seconds'%(teff,logg,i,len(teffs),(time.time()-c0)/i*(len(teffs)-i))) - + #-- get model SED and absolute luminosity wave,flux = model.get_table(teff=teff,logg=logg) Labs = model.luminosity(wave,flux) - + #-- threaded calculation over all E(B-V)s processes = [] manager = Manager() @@ -276,7 +276,7 @@ def do_ebv_process(ebvs,arr,responses): all_processes[-1].start() for p in all_processes: p.join() - + try: #-- collect the results and add them to 'output' arr = np.vstack([row for row in arr]) @@ -290,7 +290,7 @@ def do_ebv_process(ebvs,arr,responses): logger.debug('Exception: %s'%(sys.exc_info()[1])) exceptions = exceptions + 1 exceptions_logs.append(sys.exc_info()[1]) - + #-- make FITS columns gridfile = model.get_file() if os.path.isfile(os.path.basename(gridfile)): @@ -318,7 +318,7 @@ def do_ebv_process(ebvs,arr,responses): cols = [pf.Column(name=name,format='E',array=hdulist[1].data.field(name)) for name in names] for i,photband in enumerate(responses): cols.append(pf.Column(name=photband,format='E',array=output[4+i])) - + #-- make FITS extension and write grid/reddening specifications to header table = pf.new_table(pf.ColDefs(cols)) table.header.update('gridfile',os.path.basename(gridfile)) @@ -331,7 +331,7 @@ def do_ebv_process(ebvs,arr,responses): table.header.update('FLUXTYPE',units) table.header.update('REDLAW',law,'interstellar reddening law') table.header.update('RV',Rv,'interstellar reddening parameter') - + #-- make/update complete FITS file if not update or not os.path.isfile(outfile): if os.path.isfile(outfile): @@ -347,11 +347,11 @@ def do_ebv_process(ebvs,arr,responses): hdulist.flush() hdulist.close() logger.info("Appended output to %s"%(outfile)) - + logger.warning('Encountered %s exceptions!'%(exceptions)) for i in exceptions_logs: - print 'ERROR' - print i + print('ERROR') + print(i) def update_grid(gridfile,responses,threads=10): """ @@ -363,7 +363,7 @@ def update_grid(gridfile,responses,threads=10): responses = sorted(list(set(responses) - existing_responses)) if not len(responses): hdulist.close() - print "No new responses to do" + print("No new responses to do") return None law = hdulist[1].header['REDLAW'] units = hdulist[1].header['FLUXTYPE'] @@ -374,13 +374,13 @@ def update_grid(gridfile,responses,threads=10): rvs = hdulist[1].data.field('rv') vrads = hdulist[1].data.field('vrad') names = hdulist[1].columns.names - + N = len(teffs) index = np.arange(N) - + output = np.zeros((len(responses),len(teffs))) - print N - + print(N) + #--- PARALLEL PROCESS def do_process(teffs,loggs,ebvs,zs,rvs,index,arr): output = np.zeros((len(responses)+1,len(teffs))) @@ -389,7 +389,7 @@ def do_process(teffs,loggs,ebvs,zs,rvs,index,arr): for i,(teff,logg,ebv,z,rv,ind) in enumerate(zip(teffs,loggs,ebvs,zs,rvs,index)): if i%100==0: dt = time.time()-c0 - print "ETA",index[0],(N-i)/100.*dt/3600.,'hr' + print("ETA",index[0],(N-i)/100.*dt/3600.,'hr') c0 = time.time() #-- get model SED and absolute luminosity model.set_defaults(z=z) @@ -402,10 +402,10 @@ def do_process(teffs,loggs,ebvs,zs,rvs,index,arr): arr.append(output) #--- PARALLEL PROCESS c0 = time.time() - + manager = Manager() arr = manager.list([]) - + all_processes = [] for j in range(threads): all_processes.append(Process(target=do_process,args=(teffs[j::threads],\ @@ -417,7 +417,7 @@ def do_process(teffs,loggs,ebvs,zs,rvs,index,arr): all_processes[-1].start() for p in all_processes: p.join() - + output = np.hstack([res for res in arr]) del arr sa = np.argsort(output[0]) @@ -443,7 +443,7 @@ def fix_grid(grid): cols = [pf.Column(name=name,format='E',array=hdulist[1].data.field(name)) for name in names] N = len(hdulist[1].data) - keys = [key.lower() for key in hdulist[1].header.keys()] + keys = [key.lower() for key in list(hdulist[1].header.keys())] if not 'z' in names: z = hdulist[1].header['z'] @@ -457,7 +457,7 @@ def fix_grid(grid): cols.append(pf.Column(name='vrad',format='E',array=np.ones(N)*vrad)) else: logger.info("Radial velocity already in there") - + fix_rv = False if not 'rv' in names: if 'rv' in keys: @@ -473,23 +473,23 @@ def fix_grid(grid): logger.info('Correcting interstellar Rv with {}'.format(rv)) else: logger.info("Interstellar Rv already in there") - + table = pf.new_table(pf.ColDefs(cols)) if fix_rv: table.data.field('rv')[:] = rv - fake_keys = [key.lower() for key in table.header.keys()] + fake_keys = [key.lower() for key in list(table.header.keys())] fake_keys.append('use_scratch') # Don't know why this is nessessary but it doesn't work otherwise (JV 23.7.13) !!! - for key in hdulist[1].header.keys(): + for key in list(hdulist[1].header.keys()): if not key.lower() in fake_keys: if len(key)>8: key = 'HIERARCH '+key table.header.update(key,hdulist[1].header[key]) hdulist[1] = table - print "Axis:" + print("Axis:") for name in hdulist[1].columns.names: if name.islower() and not name=='labs': ax = np.unique(hdulist[1].data.field(name)) - print name,len(ax),min(ax),max(ax) + print(name,len(ax),min(ax),max(ax)) teffs = hdulist[1].data.field('teff') loggs = hdulist[1].data.field('logg') @@ -500,15 +500,15 @@ def fix_grid(grid): #plt.figure() #plt.title(logg) #plt.plot(teffs[keep],ebvs[keep],'ko',ms=2) - + keep = hdulist[1].data.field('teff')>0 logger.info('Removing {}/{} false entries'.format(sum(-keep),len(keep))) #print len(ff[1].data),sum(keep) hdulist[1].data = hdulist[1].data[keep] hdulist.close() - -#} - + +#} + if __name__=="__main__": logger = loggers.get_basic_logger() imethod,iargs,ikwargs = argkwargparser.parse() diff --git a/sed/decorators.py b/sed/decorators.py index 53fc0e258..38cfd8e84 100644 --- a/sed/decorators.py +++ b/sed/decorators.py @@ -7,7 +7,7 @@ import numpy as np import pylab as pl from multiprocessing import Manager,Process,cpu_count -import model +from . import model from ivs.units import conversions from ivs.units import constants @@ -16,17 +16,17 @@ def parallel_gridsearch(fctn): """ Decorator to run SED grid fitting in parallel. - + This splits up the effective temperature range between teffrange[0] and teffrange[1] in 'threads' parts. - + This must decorate a 'make_parallel' decorator. """ @functools.wraps(fctn) def globpar(*args,**kwargs): - #-- construct a manager to collect all calculations - manager = Manager() - arr = manager.list([]) + #-- construct a manager to collect all calculations + manager = Manager() + arr = manager.list([]) all_processes = [] #-- get information on threading threads = kwargs.pop('threads',1) @@ -38,7 +38,7 @@ def globpar(*args,**kwargs): threads = cpu_count()-1 threads = int(threads) index = np.arange(len(args[-1])) - + #-- distribute the periodogram calcs over different threads, and wait for i in range(threads): #-- extend the arguments to include the parallel array, and split @@ -47,29 +47,29 @@ def globpar(*args,**kwargs): myargs = tuple(list(args[:3]) + [args[j][i::threads] for j in range(3,len(args))] + [arr] ) kwargs['index'] = index[i::threads] logger.debug("parallel: starting process %s"%(i)) - p = Process(target=fctn, args=myargs, kwargs=kwargs) + p = Process(target=fctn, args=myargs, kwargs=kwargs) p.start() all_processes.append(p) - - for p in all_processes: p.join() - - logger.debug("parallel: all processes ended") - + + for p in all_processes: p.join() + + logger.debug("parallel: all processes ended") + #-- join all periodogram pieces - chisqs = np.hstack([output[0] for output in arr]) - scales = np.hstack([output[1] for output in arr]) + chisqs = np.hstack([output[0] for output in arr]) + scales = np.hstack([output[1] for output in arr]) e_scales = np.hstack([output[2] for output in arr]) lumis = np.hstack([output[3] for output in arr]) index = np.hstack([output[4] for output in arr]) sa = np.argsort(index) return chisqs[sa],scales[sa],e_scales[sa],lumis[sa]#,index[sa] - + return globpar def iterate_gridsearch(fctn): """ Decorator to run SED iteratively and zooming in on the minimum. - + iterations: number of iterative zoom-ins increase: increase in number of grid points in each search (1 means no increase) size: speed of zoomin: the higher, the slower @@ -79,7 +79,7 @@ def globpar(*args,**kwargs): iterations = kwargs.pop('iterations',1) increase = kwargs.pop('increase',1) speed = kwargs.pop('speed',2) - + N = 0 for nr_iter in range(iterations): data_ = fctn(*args,**kwargs) @@ -89,27 +89,27 @@ def globpar(*args,**kwargs): startN = len(data) else: data = np.core.records.fromrecords(data.tolist()+data_.tolist(),dtype=data.dtype) - + #-- select next stage best = np.argmin(data['chisq']) limit = data['chisq'][best]+speed*0.5**nr_iter*data['chisq'][best] - + kwargs['teffrange'] = (data['teff'][data['chisq']<=limit]).min(),(data['teff'][data['chisq']<=limit]).max() kwargs['loggrange'] = (data['logg'][data['chisq']<=limit]).min(),(data['logg'][data['chisq']<=limit]).max() kwargs['ebvrange'] = (data['ebv'][data['chisq']<=limit]).min(),(data['ebv'][data['chisq']<=limit]).max() kwargs['zrange'] = (data['z'][data['chisq']<=limit]).min(),(data['z'][data['chisq']<=limit]).max() kwargs['points'] = increase**(nr_iter+1)*startN - + logger.info('Best parameters (stage %d/%d): teff=%.0f logg=%.3f E(B-V)=%.3f Z=%.2f (CHI2=%g, cutoff=%g)'\ %(nr_iter+1,iterations,data['teff'][best],data['logg'][best],\ data['ebv'][best],data['z'][best],data['chisq'][best],limit)) - + return data - + return globpar - - + + def standalone_figure(fctn): """ Accept 'savefig' as an extra keyword. If it is given, start a new figure and @@ -132,27 +132,27 @@ def dofig(*args,**kwargs): #-- start figure if savefig: pl.figure() - out = fctn(*args,**kwargs) + out = fctn(*args,**kwargs) #-- end figure if savefig: pl.savefig(savefig) pl.close() return out - + return dofig - - - + + + def blackbody_input(fctn): """ Prepare input and output for blackbody-like functions. - + If the user gives wavelength units and Flambda units, we only need to convert everything to SI (and back to the desired units in the end). - + If the user gives frequency units and Fnu units, we only need to convert everything to SI ( and back to the desired units in the end). - + If the user gives wavelength units and Fnu units, we need to convert the wavelengths first to frequency. """ @@ -181,10 +181,10 @@ def dobb(x,T,**kwargs): #-- correct for rad if x_unit_type=='frequency': x /= (2*np.pi) - print y_unit_type + print(y_unit_type) #-- run function - I = fctn((x,x_unit_type),T) - + I = fctn((x,x_unit_type),T) + #-- prepare output disc_integrated = kwargs.get('disc_integrated',True) ang_diam = kwargs.get('ang_diam',None) @@ -195,5 +195,5 @@ def dobb(x,T,**kwargs): I *= scale I = conversions.convert(curr_conv,flux_units,I) return I - - return dobb \ No newline at end of file + + return dobb diff --git a/sed/distance.py b/sed/distance.py index 455ff3ac4..a73e09d26 100644 --- a/sed/distance.py +++ b/sed/distance.py @@ -12,15 +12,15 @@ def rho(z,z_sun=20.0,hd=31.8,hh=490.,sigma=1.91e-3,f=0.039): """ Stellar galactic density function. - + Galactic coordinate z: self-gravitating isothermal disk plus a Gaussian halo - + See, e.g., Maiz-Apellaniz, Alfaro and Sota 2007/2008 (Poster) - + Other values we found in the literature: - + z_sun,hd,sigma,f = 24.7,34.2,1.62e-3,0.058 - + @param z: galactic coordinate z (parsec) @type z: array or float @param z_sun: Sun's distance above the Galactic plane (20.0 +/- 2.9 pc) @@ -46,11 +46,11 @@ def probability_cd(r,plx,e_plx): Compute the probability for an object to be at distance r (pc), given its parallax (mas) and error on the parallax (mas) and a constant density function. - + Unnormalised! - + To obtain the probabilty, multiply with a stellar galactic density function. - + @param r: distance (pc) @type r: float/array @param plx: parallax (mas) @@ -69,10 +69,10 @@ def distprob(r,theta,plx,**kwargs): """ Compute the probability for an object to be located at a distance r (pc), given its parallax and galactic lattitude. - + theta in degrees plx is a tuple! - + returns (unnormalised) probability density function. """ z = r*sin(theta/180.*pi) diff --git a/sed/extinctionmodels.py b/sed/extinctionmodels.py index 2a555782c..7d7df5890 100644 --- a/sed/extinctionmodels.py +++ b/sed/extinctionmodels.py @@ -27,80 +27,80 @@ def findext(lng, lat, model='drimmel', distance=None, **kwargs): """ Get the "model" extinction at a certain longitude and latitude. - + Find the predicted V-band extinction (Av) based on three dimensional models of the galactic interstellar extinction. The user can choose between different models by setting the model keyword: - + 1) "arenou": model from Arenou et al. (1992). 2) "schlegel": model from Schlegel et al. (1998) 3) "drimmel": model from Drimmel et al. (2003) 4) "marshall": model from Marshall et al. (2006) - + example useage: - + 1. Find the total galactic extinction for a star at galactic lattitude 10.2 and longitude 59.0 along the line of sight, as given by the model of Arenou et al. (1992) - + >>> lng = 10.2 >>> lat = 59.0 >>> av = findext(lng, lat, model='arenou') >>> print("Av at lng = %.2f, lat = %.2f is %.2f magnitude" %(lng, lat, av)) Av at lng = 10.20, lat = 59.00 is 0.05 magnitude - + 2. Find the extinction for a star at galactic lattitude 107.05 and longitude -34.93 and a distance of 144.65 parsecs, as given by the model of Arenou et al. (1992) - + >>> lng = 107.05 >>> lat = -34.93 >>> dd = 144.65 >>> av = findext(lng, lat, distance = dd, model='arenou') >>> print("Av at lng = %.2f, lat = %.2f and distance = %.2f parsecs is %.2f magnitude" %(lng, lat, dd, av)) Av at lng = 107.05, lat = -34.93 and distance = 144.65 parsecs is 0.15 magnitude - + 3. Find the Marschall extinction for a star at galactic longitude 10.2 and latitude 9.0. If the distance is not given, we return the complete extinction along the line of sight (i.e. put the star somewhere out of the galaxy). - + >>> lng = 10.2 >>> lat = 9.0 >>> av = findext(lng, lat, model='marshall') >>> print("Av at lng = %.2f, lat = %.2f is %.2f magnitude" %(lng, lat, av)) Av at lng = 10.20, lat = 9.00 is 10.67 magnitude - + 4. Find the Marshall extinction for a star at galactic lattitude 271.05 and longitude -4.93 and a distance of 144.65 parsecs, but convert to Av using Rv = 2.5 instead of Rv = 3.1 - + >>> lng = 271.05 >>> lat = -4.93 >>> dd = 144.65 >>> av = findext(lng, lat, distance = dd, model='marshall', Rv=2.5) >>> print("Av at lng = %.2f, lat = %.2f and distance = %.2f parsecs is %.2f magnitude" %(lng, lat, dd, av)) Av at lng = 271.05, lat = -4.93 and distance = 144.65 parsecs is 13.95 magnitude - + 5. Find the extinction for a star at galactic longitude 10.2 and latitude 9.0, using the schlegel model, using Rv=2.9 instead of Rv=3.1 - + >>> lng = 58.2 >>> lat = 24.0 >>> distance = 848. >>> av = findext(lng, lat, distance=distance) >>> print("Av at lng = %.2f, lat = %.2f is %.2f magnitude" %(lng, lat, av)) Av at lng = 58.20, lat = 24.00 is 0.12 magnitude - + REMARKS: a) Schlegel actually returns E(B-V), this value is then converted to Av (the desired value for Rv can be set as a keyword; standard sets Rv=3.1) b) Schlegel is very dubious for latitudes between -5 and 5 degrees c) Marschall actually returns Ak, this value is then converted to Av (the reddening law and Rv can be set as keyword; standard sets Rv=3.1, redlaw='cardelli1989') d) Marschall is only available for certain longitudes and latitudes: 0 < lng < 100 or 260 < lng < 360 and -10 < lat < 10 - + @param lng: Galactic Longitude (in degrees) @type lng: float @param lat: Galactic Lattitude (in degrees) @@ -112,7 +112,7 @@ def findext(lng, lat, model='drimmel', distance=None, **kwargs): @return: The extinction in Johnson V-band @rtype: float """ - + if model.lower() == 'drimmel': av = findext_drimmel(lng, lat, distance=distance, **kwargs) elif model.lower() == 'marshall' or model.lower() == 'marschall': @@ -123,10 +123,10 @@ def findext(lng, lat, model='drimmel', distance=None, **kwargs): av = findext_schlegel(lng, lat, distance=distance, **kwargs) return(av) -#} +#} # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #{ Arenou 3D extinction model -# (based on Arenou et al, "A tridimensional model of the +# (based on Arenou et al, "A tridimensional model of the # galactic interstellar extinction" published in Astronomy and # Astrophysics (ISSN 0004-6361), vol. 258, no. 1, p. 104-111.) # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -136,33 +136,33 @@ def findext_arenou(ll, bb, distance=None, redlaw='cardelli1989', Rv=3.1, norm='A Find the predicted V-band extinction (Av) according to the 3D model for galactic extinction of Arenou et al, "Modelling the Galactic interstellar extinction distribution in three - dimensions", Arenou et al, "A tridimensional model of the + dimensions", Arenou et al, "A tridimensional model of the galactic interstellar extinction" published in Astronomy and Astrophysics (ISSN 0004-6361), vol. 258, no. 1, p. 104-111, 1992 - + Example usage: - + 1. Find the extinction for a star at galactic lattitude 10.2 and longitude 59.0. If the distance is not given, we use the maximal distance r0 for that line of sight, as given in the Appendix of Arenou et al. (1992) - + >>> lng = 10.2 >>> lat = 59.0 >>> av = findext_arenou(lng, lat) >>> print("Av at lng = %.2f, lat = %.2f is %.2f magnitude" %(lng, lat, av)) Av at lng = 10.20, lat = 59.00 is 0.05 magnitude - + 2. Find the extinction for a star at galactic lattitude 107.05 and longitude -34.93 and a distance of 144.65 parsecs - + >>> lng = 107.05 >>> lat = -34.93 >>> dd = 144.65 >>> av = findext_arenou(lng, lat, distance = dd) >>> print("Av at lng = %.2f, lat = %.2f and distance = %.2f parsecs is %.2f magnitude" %(lng, lat, dd, av)) Av at lng = 107.05, lat = -34.93 and distance = 144.65 parsecs is 0.15 magnitude - + @param ll: Galactic Longitude (in degrees) @type ll: float @param bb: Galactic Lattitude (in degrees) @@ -179,11 +179,11 @@ def findext_arenou(ll, bb, distance=None, redlaw='cardelli1989', Rv=3.1, norm='A logger.error("galactic longitude outside [0,360] degrees") elif distance < 0 and distance is not None: logger.error("distance is negative") - + # find the Arenou paramaters in the Appendix of Arenou et al. (1992) alpha, beta, gamma, rr0, saa = _getarenouparams(ll, bb) logger.info("Arenou params: alpha = %.2f, beta = %.2f, gamma = %.2f, r0 = %.2f and saa = %.2f" %(alpha, beta, gamma, rr0, saa)) - + # compute the visual extinction from the Arenou paramaters using Equation 5 # and 5bis if distance is None: @@ -194,17 +194,17 @@ def findext_arenou(ll, bb, distance=None, redlaw='cardelli1989', Rv=3.1, norm='A av = alpha*distance + beta*distance**2. else: av = alpha*rr0 + beta*rr0**2. + (distance-rr0)*gamma - + #-- Marshall is standard in Ak, but you can change this: redwave, redflux = get_law(redlaw,Rv=Rv,norm=norm,photbands=['JOHNSON.V']) - + return av/redflux[0] def _getarenouparams(ll,bb): """ Input: galactic coordinates Output: Arenou 1992 alpha, beta, gamma, rr0, saa - + @param ll: Galactic Longitude (in degrees) @type ll: float @param bb: Galactic Lattitude (in degrees) @@ -236,13 +236,13 @@ def _getarenouparams(ll,bb): alpha = 2.12696 ; beta = -6.05682 ; rr0 = 0.056 ; saa = 14 elif 330 <= ll < 360: alpha = 2.34636 ; beta = -8.17784 ; rr0 = 0.052 ; saa = 16 - + if -60 < bb < -45: gamma = 0 - if 0 <= ll < 30: + if 0 <= ll < 30: alpha = 2.77060 ; beta = -9.52310 ; rr0 = 0.145 ; saa = 16 elif 30 <= ll < 60: - alpha = 1.96533 ; beta = -9.52310 ; rr0 = 0.174 ; saa = 06 + alpha = 1.96533 ; beta = -9.52310 ; rr0 = 0.174 ; saa = 6 elif 60 <= ll < 110: alpha = 1.93622 ; beta = -13.31757 ; rr0 = 0.073 ; saa = 26 elif 110 <= ll < 180: @@ -259,17 +259,17 @@ def _getarenouparams(ll,bb): alpha = 1.72679 ; beta = -6.05085 ; rr0 = 0.143 ; saa = 7 elif 330 <= ll < 360: alpha = 1.88890 ; beta = -5.51861 ; rr0 = 0.171 ; saa = 14 - + if -45 < bb < -30: gamma = 0 if 0 <= ll < 30: - alpha = 1.98973 ; beta = -4.86206 ; rr0 = 0.205 ; saa = 6 + alpha = 1.98973 ; beta = -4.86206 ; rr0 = 0.205 ; saa = 6 elif 30 <= ll < 60: - alpha = 1.49901 ; beta = -3.75837 ; rr0 = 0.199 ; saa = 16 + alpha = 1.49901 ; beta = -3.75837 ; rr0 = 0.199 ; saa = 16 elif 60 <= ll < 90: - alpha = 0.90091 ; beta = -1.30459 ; rr0 = 0.329 ; saa = 73 + alpha = 0.90091 ; beta = -1.30459 ; rr0 = 0.329 ; saa = 73 elif 90 <= ll < 120: - alpha = 1.94200 ; beta = -6.26833 ; rr0 = 0.155 ; saa = 18 + alpha = 1.94200 ; beta = -6.26833 ; rr0 = 0.155 ; saa = 18 elif 120 <= ll < 160: alpha = -0.37804 ; beta = 10.77372 ; rr0 = 0.210 ; saa = 100 elif 160 <= ll < 200: @@ -284,64 +284,64 @@ def _getarenouparams(ll,bb): alpha = 2.50487 ; beta = -9.63106 ; rr0 = 0.145 ; saa = 28 elif 330 <= ll < 360: alpha = 2.44394 ; beta = -9.17612 ; rr0 = 0.133 ; saa = 7 - + if -30 < bb < -15: gamma = 0 if 0 <= ll < 20: alpha = 2.82440 ; beta = -4.78217 ; rr0 = 0.295 ; saa = 32 elif 20 <= ll < 40: - alpha = 3.84362 ; beta = -8.04690 ; rr0 = 0.239 ; saa = 46 + alpha = 3.84362 ; beta = -8.04690 ; rr0 = 0.239 ; saa = 46 elif 40 <= ll < 80: - alpha = 0.60365 ; beta = 0.07893 ; rr0 = 0.523 ; saa = 22 - elif 80 <= ll < 100: - alpha = 0.58307 ; beta = -0.21053 ; rr0 = 0.523 ; saa = 53 - elif 100 <= ll < 120: - alpha = 2.03861 ; beta = -4.40843 ; rr0 = 0.231 ; saa = 60 + alpha = 0.60365 ; beta = 0.07893 ; rr0 = 0.523 ; saa = 22 + elif 80 <= ll < 100: + alpha = 0.58307 ; beta = -0.21053 ; rr0 = 0.523 ; saa = 53 + elif 100 <= ll < 120: + alpha = 2.03861 ; beta = -4.40843 ; rr0 = 0.231 ; saa = 60 elif 120 <= ll < 140: - alpha = 1.14271 ; beta = -1.35635 ; rr0 = 0.421 ; saa = 34 + alpha = 1.14271 ; beta = -1.35635 ; rr0 = 0.421 ; saa = 34 elif 140 <= ll < 160: - alpha = 0.79908 ; beta = 1.48074 ; rr0 = 0.513 ; saa = 61 + alpha = 0.79908 ; beta = 1.48074 ; rr0 = 0.513 ; saa = 61 elif 160 <= ll < 180: - alpha = 0.94260 ; beta = 8.16346 ; rr0 = 0.441 ; saa = 42 + alpha = 0.94260 ; beta = 8.16346 ; rr0 = 0.441 ; saa = 42 elif 180 <= ll < 200: - alpha = 1.66398 ; beta = 0.26775 ; rr0 = 0.523 ; saa = 42 - elif 200 <= ll < 220: - alpha = 1.08760 ; beta = -1.02443 ; rr0 = 0.523 ; saa = 45 + alpha = 1.66398 ; beta = 0.26775 ; rr0 = 0.523 ; saa = 42 + elif 200 <= ll < 220: + alpha = 1.08760 ; beta = -1.02443 ; rr0 = 0.523 ; saa = 45 elif 220 <= ll < 240: - alpha = 1.20087 ; beta = -2.45407 ; rr0 = 0.245 ; saa = 6 + alpha = 1.20087 ; beta = -2.45407 ; rr0 = 0.245 ; saa = 6 elif 240 <= ll < 260: - alpha = 1.13147 ; beta = -1.87916 ; rr0 = 0.301 ; saa = 16 + alpha = 1.13147 ; beta = -1.87916 ; rr0 = 0.301 ; saa = 16 elif 260 <= ll < 280: - alpha = 0.97804 ; beta = -2.92838 ; rr0 = 0.338 ; saa = 21 + alpha = 0.97804 ; beta = -2.92838 ; rr0 = 0.338 ; saa = 21 elif 290 <= ll < 300: - alpha = 1.40086 ; beta = -1.12403 ; rr0 = 0.523 ; saa = 19 + alpha = 1.40086 ; beta = -1.12403 ; rr0 = 0.523 ; saa = 19 elif 300 <= ll < 320: - alpha = 2.06355 ; beta = -3.68278 ; rr0 = 0.280 ; saa = 42 - elif 320 <= ll < 340: - alpha = 1.59260 ; beta = -2.18754 ; rr0 = 0.364 ; saa = 15 + alpha = 2.06355 ; beta = -3.68278 ; rr0 = 0.280 ; saa = 42 + elif 320 <= ll < 340: + alpha = 1.59260 ; beta = -2.18754 ; rr0 = 0.364 ; saa = 15 elif 310 <= ll < 360: alpha = 1.45589 ; beta = -1.90598 ; rr0 = 0.382 ; saa = 21 - + if -15 < bb < -5: gamma = 0 if 0 <= ll < 10: - alpha = 2.56438 ; beta = -2.31586 ; rr0 = 0.554 ; saa = 37 + alpha = 2.56438 ; beta = -2.31586 ; rr0 = 0.554 ; saa = 37 elif 10 <= ll < 20: - alpha = 3.24095 ; beta = -2.78217 ; rr0 = 0.582 ; saa = 38 + alpha = 3.24095 ; beta = -2.78217 ; rr0 = 0.582 ; saa = 38 elif 20 <= ll < 30: alpha = 2.95627 ; beta = -2.57422 ; rr0 = 0.574 ; saa = 41 ; gamma = 0.08336 elif 30 <= ll < 40: - alpha = 1.85158 ; beta = -0.67716 ; rr0 = 1.152 ; saa = 4 + alpha = 1.85158 ; beta = -0.67716 ; rr0 = 1.152 ; saa = 4 elif 40 <= ll < 50: - alpha = 1.60770 ; beta = 0.35279 ; rr0 = 0.661 ; saa = 24 + alpha = 1.60770 ; beta = 0.35279 ; rr0 = 0.661 ; saa = 24 elif 50 <= ll < 60: alpha = 0.69920 ; beta = -0.09146 ; rr0 = 0.952 ; saa = 2 ; gamma = 0.12839 elif 60 <= ll < 80: alpha = 1.36189 ; beta = -1.05290 ; rr0 = 0.647 ; saa = 45 ; gamma = 0.16258 elif 80 <= ll < 90: - alpha = 0.33179 ; beta = 0.37629 ; rr0 = 1.152 ; saa = 62 + alpha = 0.33179 ; beta = 0.37629 ; rr0 = 1.152 ; saa = 62 elif 90 <= ll < 100: - alpha = 1.70303 ; beta = -0.75246 ; rr0 = 1.132 ; saa = 31 + alpha = 1.70303 ; beta = -0.75246 ; rr0 = 1.132 ; saa = 31 elif 100 <= ll < 110: alpha = 1.97414 ; beta = -1.59784 ; rr0 = 0.618 ; saa = 35 ; gamma = 0.12847 elif 110 <= ll < 120: @@ -349,25 +349,25 @@ def _getarenouparams(ll,bb): elif 120 <= ll < 130: alpha = 1.69495 ; beta = -1.00071 ; rr0 = 0.847 ; saa = 28 ; gamma = 0.08567 elif 130 <= ll < 140: - alpha = 1.51449 ; beta = -0.08441 ; rr0 = 0.897 ; saa = 12 + alpha = 1.51449 ; beta = -0.08441 ; rr0 = 0.897 ; saa = 12 elif 140 <= ll < 150: - alpha = 1.87894 ; beta = -0.73314 ; rr0 = 1.152 ; saa = 23 + alpha = 1.87894 ; beta = -0.73314 ; rr0 = 1.152 ; saa = 23 elif 150 <= ll < 160: alpha = 1.43670 ; beta = 0.67706 ; rr0 = 0.778 ; saa = 3 ; gamma = 0.42624 elif 160 <= ll < 180: - alpha = 6.84802 ; beta = -5.06864 ; rr0 = 0.676 ; saa = 50 + alpha = 6.84802 ; beta = -5.06864 ; rr0 = 0.676 ; saa = 50 elif 180 <= ll < 190: alpha = 4.16321 ; beta = -5.80016 ; rr0 = 0.359 ; saa = 51 ; gamma = 0.60387 elif 190 <= ll < 200: - alpha = 0.78135 ; beta = -0.27826 ; rr0 = 1.152 ; saa = 4 + alpha = 0.78135 ; beta = -0.27826 ; rr0 = 1.152 ; saa = 4 elif 200 <= ll < 210: - alpha = 0.85535 ; beta = 0.20848 ; rr0 = 1.152 ; saa = 17 + alpha = 0.85535 ; beta = 0.20848 ; rr0 = 1.152 ; saa = 17 elif 210 <= ll < 220: - alpha = 0.52521 ; beta = 0.65726 ; rr0 = 1.152 ; saa = 7 + alpha = 0.52521 ; beta = 0.65726 ; rr0 = 1.152 ; saa = 7 elif 220 <= ll < 230: alpha = 0.88376 ; beta = -0.44519 ; rr0 = 0.993 ; saa = 40 ; gamma = 0.06013 elif 230 <= ll < 240: - alpha = 0.42228 ; beta = 0.26304 ; rr0 = 0.803 ; saa = 26 + alpha = 0.42228 ; beta = 0.26304 ; rr0 = 0.803 ; saa = 26 elif 240 <= ll < 250: alpha = 0.71318 ; beta = -0.67229 ; rr0 = 0.530 ; saa = 55 ; gamma = 0.20994 elif 250 <= ll < 260: @@ -375,24 +375,24 @@ def _getarenouparams(ll,bb): elif 260 <= ll < 270: alpha = 0.91519 ; beta = -0.39690 ; rr0 = 1.152 ; saa = 48 ; gamma = 0.01961 elif 270 <= ll < 280: - alpha = 0.85791 ; beta = -0.29115 ; rr0 = 1.152 ; saa = 19 + alpha = 0.85791 ; beta = -0.29115 ; rr0 = 1.152 ; saa = 19 elif 280 <= ll < 290: - alpha = 1.44226 ; beta = -1.09775 ; rr0 = 0.657 ; saa = 39 + alpha = 1.44226 ; beta = -1.09775 ; rr0 = 0.657 ; saa = 39 elif 290 <= ll < 300: - alpha = 2.55486 ; beta = -1.68293 ; rr0 = 0.759 ; saa = 31 + alpha = 2.55486 ; beta = -1.68293 ; rr0 = 0.759 ; saa = 31 elif 300 <= ll < 310: - alpha = 3.18047 ; beta = -2.69796 ; rr0 = 0.589 ; saa = 40 + alpha = 3.18047 ; beta = -2.69796 ; rr0 = 0.589 ; saa = 40 elif 210 <= ll < 320: - alpha = 2.11235 ; beta = -1.77506 ; rr0 = 0.595 ; saa = 29 + alpha = 2.11235 ; beta = -1.77506 ; rr0 = 0.595 ; saa = 29 elif 320 <= ll < 330: - alpha = 1.75884 ; beta = -1.38574 ; rr0 = 0.635 ; saa = 25 + alpha = 1.75884 ; beta = -1.38574 ; rr0 = 0.635 ; saa = 25 elif 330 <= ll < 340: alpha = 1.97257 ; beta = -1.55545 ; rr0 = 0.634 ; saa = 34 ; gamma = 0.00043 elif 340 <= ll < 350: alpha = 1.41497 ; beta = -1.05722 ; rr0 = 0.669 ; saa = 46 ; gamma = 0.03264 elif 350 <= ll < 360: alpha = 1.17795 ; beta = -0.95012 ; rr0 = 0.620 ; saa = 46 ; gamma = 0.03339 - + if -5 < bb < 5: gamma = 0 if 0 <= ll < 10: @@ -400,247 +400,247 @@ def _getarenouparams(ll,bb): elif 10 <= ll < 20: alpha = 3.14461 ; beta = -1.01140 ; rr0 = 1.555 ; saa = 42 elif 20 <= ll < 30: - alpha = 4.26624 ; beta = -1.61242 ; rr0 = 1.323 ; saa = 34 + alpha = 4.26624 ; beta = -1.61242 ; rr0 = 1.323 ; saa = 34 elif 30 <= ll < 40: - alpha = 2.54447 ; beta = -0.12771 ; rr0 = 1.300 ; saa = 30 + alpha = 2.54447 ; beta = -0.12771 ; rr0 = 1.300 ; saa = 30 elif 40 <= ll < 50: - alpha = 2.27030 ; beta = -0.68720 ; rr0 = 1.652 ; saa = 52 ; gamma = 0.04928 + alpha = 2.27030 ; beta = -0.68720 ; rr0 = 1.652 ; saa = 52 ; gamma = 0.04928 elif 50 <= ll < 60: - alpha = 1.34359 ; beta = -0.05416 ; rr0 = 2.000 ; saa = 32 + alpha = 1.34359 ; beta = -0.05416 ; rr0 = 2.000 ; saa = 32 elif 60 <= ll < 70: - alpha = 1.76327 ; beta = -0.26407 ; rr0 = 2.000 ; saa = 37 + alpha = 1.76327 ; beta = -0.26407 ; rr0 = 2.000 ; saa = 37 elif 70 <= ll < 80: - alpha = 2.20666 ; beta = -0.41651 ; rr0 = 2.000 ; saa = 36 + alpha = 2.20666 ; beta = -0.41651 ; rr0 = 2.000 ; saa = 36 elif 80 <= ll < 90: - alpha = 1.50130 ; beta = -0.09855 ; rr0 = 1.475 ; saa = 45 + alpha = 1.50130 ; beta = -0.09855 ; rr0 = 1.475 ; saa = 45 elif 90 <= ll < 100: - alpha = 2.43965 ; beta = -0.82128 ; rr0 = 1.485 ; saa = 36 ; gamma = 0.01959 + alpha = 2.43965 ; beta = -0.82128 ; rr0 = 1.485 ; saa = 36 ; gamma = 0.01959 elif 100 <= ll < 110: - alpha = 3.35775 ; beta = -1.16400 ; rr0 = 0.841 ; saa = 35 ; gamma = 0.00298 + alpha = 3.35775 ; beta = -1.16400 ; rr0 = 0.841 ; saa = 35 ; gamma = 0.00298 elif 110 <= ll < 120: - alpha = 2.60621 ; beta = -0.68687 ; rr0 = 1.897 ; saa = 36 + alpha = 2.60621 ; beta = -0.68687 ; rr0 = 1.897 ; saa = 36 elif 120 <= ll < 130: - alpha = 2.90112 ; beta = -0.97988 ; rr0 = 1.480 ; saa = 32 + alpha = 2.90112 ; beta = -0.97988 ; rr0 = 1.480 ; saa = 32 elif 130 <= ll < 140: - alpha = 2.55377 ; beta = -0.71214 ; rr0 = 1.793 ; saa = 38 + alpha = 2.55377 ; beta = -0.71214 ; rr0 = 1.793 ; saa = 38 elif 140 <= ll < 150: - alpha = 3.12598 ; beta = -1.21437 ; rr0 = 1.287 ; saa = 23 ; gamma = 0.15298 + alpha = 3.12598 ; beta = -1.21437 ; rr0 = 1.287 ; saa = 23 ; gamma = 0.15298 elif 150 <= ll < 160: - alpha = 3.66930 ; beta = -2.29731 ; rr0 = 0.799 ; saa = 40 ; gamma = 0.33473 + alpha = 3.66930 ; beta = -2.29731 ; rr0 = 0.799 ; saa = 40 ; gamma = 0.33473 elif 160 <= ll < 170: - alpha = 2.15465 ; beta = -0.70690 ; rr0 = 1.524 ; saa = 37 ; gamma = 0.14017 + alpha = 2.15465 ; beta = -0.70690 ; rr0 = 1.524 ; saa = 37 ; gamma = 0.14017 elif 170 <= ll < 180: - alpha = 1.82465 ; beta = -0.60223 ; rr0 = 1.515 ; saa = 29 ; gamma = 0.20730 + alpha = 1.82465 ; beta = -0.60223 ; rr0 = 1.515 ; saa = 29 ; gamma = 0.20730 elif 180 <= ll < 190: - alpha = 1.76269 ; beta = -0.35945 ; rr0 = 2.000 ; saa = 28 ; gamma = 0.08052 + alpha = 1.76269 ; beta = -0.35945 ; rr0 = 2.000 ; saa = 28 ; gamma = 0.08052 elif 190 <= ll < 200: - alpha = 1.06085 ; beta = -0.14211 ; rr0 = 2.000 ; saa = 48 + alpha = 1.06085 ; beta = -0.14211 ; rr0 = 2.000 ; saa = 48 elif 200 <= ll < 210: - alpha = 1.21333 ; beta = -0.23225 ; rr0 = 2.000 ; saa = 57 + alpha = 1.21333 ; beta = -0.23225 ; rr0 = 2.000 ; saa = 57 elif 210 <= ll < 220: - alpha = 0.58326 ; beta = -0.06097 ; rr0 = 2.000 ; saa = 30 ; gamma = 0.36962 + alpha = 0.58326 ; beta = -0.06097 ; rr0 = 2.000 ; saa = 30 ; gamma = 0.36962 elif 220 <= ll < 230: - alpha = 0.74200 ; beta = -0.19293 ; rr0 = 1.923 ; saa = 48 ; gamma = 0.07459 + alpha = 0.74200 ; beta = -0.19293 ; rr0 = 1.923 ; saa = 48 ; gamma = 0.07459 elif 230 <= ll < 240: - alpha = 0.67520 ; beta = -0.21041 ; rr0 = 1.604 ; saa = 49 ; gamma = 0.16602 + alpha = 0.67520 ; beta = -0.21041 ; rr0 = 1.604 ; saa = 49 ; gamma = 0.16602 elif 240 <= ll < 250: - alpha = 0.62609 ; beta = -0.25312 ; rr0 = 1.237 ; saa = 73 ; gamma = 0.14437 + alpha = 0.62609 ; beta = -0.25312 ; rr0 = 1.237 ; saa = 73 ; gamma = 0.14437 elif 250 <= ll < 260: - alpha = 0.61415 ; beta = -0.13788 ; rr0 = 2.000 ; saa = 42 ; gamma = 0.26859 + alpha = 0.61415 ; beta = -0.13788 ; rr0 = 2.000 ; saa = 42 ; gamma = 0.26859 elif 260 <= ll < 270: - alpha = 0.58108 ; beta = 0.01195 ; rr0 = 2.000 ; saa = 40 ; gamma = 0.07661 + alpha = 0.58108 ; beta = 0.01195 ; rr0 = 2.000 ; saa = 40 ; gamma = 0.07661 elif 270 <= ll < 280: - alpha = 0.68352 ; beta = -0.10743 ; rr0 = 2.000 ; saa = 50 ; gamma = 0.00849 + alpha = 0.68352 ; beta = -0.10743 ; rr0 = 2.000 ; saa = 50 ; gamma = 0.00849 elif 280 <= ll < 290: - alpha = 0.61747 ; beta = 0.02675 ; rr0 = 2,000 ; saa = 49 + alpha = 0.61747 ; beta = 0.02675 ; rr0 = 2,000 ; saa = 49 elif 290 <= ll < 300: - alpha = 0.06827 ; beta = -0.26290 ; rr0 = 2.000 ; saa = 44 + alpha = 0.06827 ; beta = -0.26290 ; rr0 = 2.000 ; saa = 44 elif 300 <= ll < 310: - alpha = 1.53631 ; beta = -0.36833 ; rr0 = 2.000 ; saa = 37 ; gamma = 0.02960 + alpha = 1.53631 ; beta = -0.36833 ; rr0 = 2.000 ; saa = 37 ; gamma = 0.02960 elif 210 <= ll < 320: - alpha = 1.94300 ; beta = -0.71445 ; rr0 = 1.360 ; saa = 36 ; gamma = 0.15643 + alpha = 1.94300 ; beta = -0.71445 ; rr0 = 1.360 ; saa = 36 ; gamma = 0.15643 elif 320 <= ll < 330: - alpha = 1.22185 ; beta = -0.40185 ; rr0 = 1.520 ; saa = 48 ; gamma = 0.07354 + alpha = 1.22185 ; beta = -0.40185 ; rr0 = 1.520 ; saa = 48 ; gamma = 0.07354 elif 330 <= ll < 340: - alpha = 1.79429 ; beta = -0.48657 ; rr0 = 1.844 ; saa = 43 + alpha = 1.79429 ; beta = -0.48657 ; rr0 = 1.844 ; saa = 43 elif 340 <= ll < 350: - alpha = 2.29545 ; beta = -0.84096 ; rr0 = 1.365 ; saa = 32 + alpha = 2.29545 ; beta = -0.84096 ; rr0 = 1.365 ; saa = 32 elif 350 <= ll < 360: alpha = 2.07408 ; beta = -0.64745 ; rr0 = 1.602 ; saa = 36 ; gamma = 0.12750 - - + + if 5 < bb < 15: gamma = 0 if 0 <= ll < 10: - alpha = 2.94213 ; beta = -2.09158 ; rr0 = 0.703 ; saa = 41 ; gamma = 0.05490 + alpha = 2.94213 ; beta = -2.09158 ; rr0 = 0.703 ; saa = 41 ; gamma = 0.05490 elif 10 <= ll < 30: - alpha = 3.04627 ; beta = 7.71159 ; rr0 = 0.355 ; saa = 45 + alpha = 3.04627 ; beta = 7.71159 ; rr0 = 0.355 ; saa = 45 elif 30 <= ll < 40: alpha = 3.78033 ; beta = -3.91956 ; rr0 = 0.482 ; saa = 42 elif 40 <= ll < 50: - alpha = 2.18119 ; beta = -2.4050 ; rr0 = 0.453 ; saa = 27 + alpha = 2.18119 ; beta = -2.4050 ; rr0 = 0.453 ; saa = 27 elif 50 <= ll < 60: - alpha = 1.45372 ; beta = -0.49522 ; rr0 = 1.152 ; saa = 31 + alpha = 1.45372 ; beta = -0.49522 ; rr0 = 1.152 ; saa = 31 elif 60 <= ll < 70: alpha = 1.05051 ; beta = -1.01704 ; rr0 = 0.516 ; saa = 2 elif 70 <= ll < 80: - alpha = 0.48416 ; beta = -0.27182 ; rr0 = 0.891 ; saa = 94 ; gamma = 0.08639 + alpha = 0.48416 ; beta = -0.27182 ; rr0 = 0.891 ; saa = 94 ; gamma = 0.08639 elif 80 <= ll < 90: - alpha = 0.61963 ; beta = 0.41697 ; rr0 = 1.152 ; saa = 35 ; gamma = 0.47171 + alpha = 0.61963 ; beta = 0.41697 ; rr0 = 1.152 ; saa = 35 ; gamma = 0.47171 elif 90 <= ll < 100: - alpha = 4.40348 ; beta = -2.95011 ; rr0 = 0.745 ; saa = 52 + alpha = 4.40348 ; beta = -2.95011 ; rr0 = 0.745 ; saa = 52 elif 100 <= ll < 120: - alpha = 2.50938 ; beta = -0.56541 ; rr0 = 1.152 ; saa = 27 + alpha = 2.50938 ; beta = -0.56541 ; rr0 = 1.152 ; saa = 27 elif 120 <= ll < 130: - alpha = 0.44180 ; beta = 1.58923 ; rr0 = 0.949 ; saa = 4 + alpha = 0.44180 ; beta = 1.58923 ; rr0 = 0.949 ; saa = 4 elif 130 <= ll < 140: - alpha = 3.96081 ; beta = -3.37374 ; rr0 = 0.587 ; saa = 40 ; gamma = 0.34109 + alpha = 3.96081 ; beta = -3.37374 ; rr0 = 0.587 ; saa = 40 ; gamma = 0.34109 elif 140 <= ll < 160: - alpha = 2.53335 ; beta = -0.40541 ; rr0 = 1.152 ; saa = 38 + alpha = 2.53335 ; beta = -0.40541 ; rr0 = 1.152 ; saa = 38 elif 160 <= ll < 170: - alpha = 2.03760 ; beta = -0.66136 ; rr0 = 1.152 ; saa = 23 + alpha = 2.03760 ; beta = -0.66136 ; rr0 = 1.152 ; saa = 23 elif 170 <= ll < 200: alpha = 1.06946 ; beta = -0.87395 ; rr0 = 0.612 ; saa = 29 ; gamma = 0.29230 elif 200 <= ll < 210: alpha = 0.86348 ; beta = -0.65870 ; rr0 = 0.655 ; saa = 79 ; gamma = 0.09089 elif 210 <= ll < 230: - alpha = 0.30117 ; beta = -0.16136 ; rr0 = 0.933 ; saa = 17 ; gamma = 0.07495 + alpha = 0.30117 ; beta = -0.16136 ; rr0 = 0.933 ; saa = 17 ; gamma = 0.07495 elif 230 <= ll < 240: - alpha = 0.75171 ; beta = -0.57143 ; rr0 = 0.658 ; saa = 12 ; gamma = 0.00534 + alpha = 0.75171 ; beta = -0.57143 ; rr0 = 0.658 ; saa = 12 ; gamma = 0.00534 elif 240 <= ll < 250: - alpha = 1.97427 ; beta = -2.02654 ; rr0 = 0.487 ; saa = 67 + alpha = 1.97427 ; beta = -2.02654 ; rr0 = 0.487 ; saa = 67 elif 250 <= ll < 260: - alpha = 1.25208 ; beta = -1.47763 ; rr0 = 0.424 ; saa = 19 ; gamma = 0.09089 + alpha = 1.25208 ; beta = -1.47763 ; rr0 = 0.424 ; saa = 19 ; gamma = 0.09089 elif 260 <= ll < 270: - alpha = 0.89448 ; beta = -0.43870 ; rr0 = 1.019 ; saa = 5 + alpha = 0.89448 ; beta = -0.43870 ; rr0 = 1.019 ; saa = 5 elif 270 <= ll < 280: - alpha = 0.81141 ; beta = -0.51001 ; rr0 = 0.795 ; saa = 27 ; gamma = 0.03505 + alpha = 0.81141 ; beta = -0.51001 ; rr0 = 0.795 ; saa = 27 ; gamma = 0.03505 elif 280 <= ll < 290: - alpha = 0.83781 ; beta = -0.44138 ; rr0 = 0.949 ; saa = 50 ; gamma = 0.02820 + alpha = 0.83781 ; beta = -0.44138 ; rr0 = 0.949 ; saa = 50 ; gamma = 0.02820 elif 290 <= ll < 300: - alpha = 1.10600 ; beta = -0.86263 ; rr0 = 0.641 ; saa = 28 ; gamma = 0.03402 + alpha = 1.10600 ; beta = -0.86263 ; rr0 = 0.641 ; saa = 28 ; gamma = 0.03402 elif 300 <= ll < 310: - alpha = 1.37040 ; beta = -1.02779 ; rr0 = 0.667 ; saa = 28 ; gamma = 0.05608 + alpha = 1.37040 ; beta = -1.02779 ; rr0 = 0.667 ; saa = 28 ; gamma = 0.05608 elif 310 <= ll < 320: - alpha = 1.77590 ; beta = -1.26951 ; rr0 = 0.699 ; saa = 37 ; gamma = 0.06972 + alpha = 1.77590 ; beta = -1.26951 ; rr0 = 0.699 ; saa = 37 ; gamma = 0.06972 elif 320 <= ll < 330: - alpha = 1.20865 ; beta = -0.70679 ; rr0 = 0.855 ; saa = 35 ; gamma = 0.02902 + alpha = 1.20865 ; beta = -0.70679 ; rr0 = 0.855 ; saa = 35 ; gamma = 0.02902 elif 330 <= ll < 340: - alpha = 2.28830 ; beta = -1.71890 ; rr0 = 0.666 ; saa = 42 ; gamma = 0.22887 + alpha = 2.28830 ; beta = -1.71890 ; rr0 = 0.666 ; saa = 42 ; gamma = 0.22887 elif 340 <= ll < 350: - alpha = 3.26278 ; beta = -0.94181 ; rr0 = 1.152 ; saa = 38 + alpha = 3.26278 ; beta = -0.94181 ; rr0 = 1.152 ; saa = 38 elif 350 <= ll < 360: alpha = 2.58100 ; beta = -1.69237 ; rr0 = 0.763 ; saa = 53 - + if 15 < bb < 30: gamma = 0 if 0 <= ll < 20: - alpha = 6.23279 ; beta = -10.30384 ; rr0 = 0.302 ; saa = 42 + alpha = 6.23279 ; beta = -10.30384 ; rr0 = 0.302 ; saa = 42 elif 20 <= ll < 40: alpha = -4.47693 ; beta = -7.28366 ; rr0 = 0.307 ; saa = 29 elif 40 <= ll < 60 : - alpha = 1.22938 ; beta = -1.19030 ; rr0 = 0.516 ; saa = 5 + alpha = 1.22938 ; beta = -1.19030 ; rr0 = 0.516 ; saa = 5 elif 60 <= ll < 80 : - alpha = 0.84291 ; beta = -1.59338 ; rr0 = 0.265 ; saa = 4 + alpha = 0.84291 ; beta = -1.59338 ; rr0 = 0.265 ; saa = 4 elif 80 <= ll < 100 : - alpha = 0.23996 ; beta = 0.06304 ; rr0 = 0.523 ; saa = 32 + alpha = 0.23996 ; beta = 0.06304 ; rr0 = 0.523 ; saa = 32 elif 100 <= ll < 140 : - alpha = 0.40062 ; beta = -1.75628 ; rr0 = 0.114 ; saa = 16 + alpha = 0.40062 ; beta = -1.75628 ; rr0 = 0.114 ; saa = 16 elif 140 <= ll < 180 : - alpha = 0.56898 ; beta = -0.53331 ; rr0 = 0.523 ; saa = 41 + alpha = 0.56898 ; beta = -0.53331 ; rr0 = 0.523 ; saa = 41 elif 180 <= ll < 200 : - alpha = -0.95721 ; beta = 11.6917 ; rr0 = 0.240 ; saa = 2 + alpha = -0.95721 ; beta = 11.6917 ; rr0 = 0.240 ; saa = 2 elif 200 <= ll < 220 : alpha = -0.19051 ; beta = 1.45670 ; rr0 = 0.376 ; saa = 1 elif 220 <= ll < 240 : - alpha = 2.31305 ; beta = -7.82531 ; rr0 = 0.148 ; saa = 95 + alpha = 2.31305 ; beta = -7.82531 ; rr0 = 0.148 ; saa = 95 elif 240 <= ll < 260: - alpha = 1.39169 ; beta = -1.72984 ; rr0 = 0.402 ; saa = 6 + alpha = 1.39169 ; beta = -1.72984 ; rr0 = 0.402 ; saa = 6 elif 260 <= ll < 260: - alpha = 1.59418 ; beta = -1.28296 ; rr0 = 0.523 ; saa = 36 + alpha = 1.59418 ; beta = -1.28296 ; rr0 = 0.523 ; saa = 36 elif 280 <= ll < 300 : - alpha = 1.57082 ; beta = -197295 ; rr0 = 0.398 ; saa = 10 + alpha = 1.57082 ; beta = -197295 ; rr0 = 0.398 ; saa = 10 elif 300 <= ll < 320 : - alpha = 1.95998 ; beta = -3.26159 ; rr0 = 0.300 ; saa = 11 + alpha = 1.95998 ; beta = -3.26159 ; rr0 = 0.300 ; saa = 11 elif 320 <= ll < 340: - alpha = 2.59567 ; beta = -4.84133 ; rr0 = 0.168 ; saa = 37 + alpha = 2.59567 ; beta = -4.84133 ; rr0 = 0.168 ; saa = 37 elif 340 <= ll < 360: alpha = 5.30273 ; beta = -7.43033 ; rr0 = 0.357 ; saa = 37 - + if 30 < bb < 45: gamma = 0 if 0 <= ll < 20: - alpha = 2.93960 ; beta = -6.48019 ; rr0 = 0.227 ; saa = 77 + alpha = 2.93960 ; beta = -6.48019 ; rr0 = 0.227 ; saa = 77 elif 20 <= ll < 50: - alpha = 1.65864 ; beta = -9.99317 ; rr0 = 0.083 ; saa = 99 + alpha = 1.65864 ; beta = -9.99317 ; rr0 = 0.083 ; saa = 99 elif 50 <= ll < 80: - alpha = 1.71831 ; beta = -7.25286 ; rr0 = 0.118 ; saa = 28 + alpha = 1.71831 ; beta = -7.25286 ; rr0 = 0.118 ; saa = 28 elif 80 <= ll < 110: - alpha = 1.33617 ; beta = -10.39799 ; rr0 = 0.064 ; saa = 99 + alpha = 1.33617 ; beta = -10.39799 ; rr0 = 0.064 ; saa = 99 elif 110 <= ll < 160: alpha = -0.31330 ; beta = 1.35622 ; rr0 = 0.329 ; saa = 24 elif 160 <= ll < 190: - alpha = 1.51984 ; beta = -8.69502 ; rr0 = 0.087 ; saa = 99 + alpha = 1.51984 ; beta = -8.69502 ; rr0 = 0.087 ; saa = 99 elif 190 <= ll < 220: - alpha = -0.50758 ; beta = 4.73320 ; rr0 = 0.250 ; saa = 78 + alpha = -0.50758 ; beta = 4.73320 ; rr0 = 0.250 ; saa = 78 elif 220 <= ll < 250: - alpha = 1.25864 ; beta = -12.59627 ; rr0 = 0.050 ; saa = 70 + alpha = 1.25864 ; beta = -12.59627 ; rr0 = 0.050 ; saa = 70 elif 250 <= ll < 280: - alpha = 1.54243 ; beta = -3.75065 ; rr0 = 0.205 ; saa = 10 + alpha = 1.54243 ; beta = -3.75065 ; rr0 = 0.205 ; saa = 10 elif 280 <= ll < 320: - alpha = 2.72258 ; beta = -7.47806 ; rr0 = 0.182 ; saa = 5 + alpha = 2.72258 ; beta = -7.47806 ; rr0 = 0.182 ; saa = 5 elif 320 <= ll < 340: - alpha = 2.81435 ; beta = -5.52139 ; rr0 = 0.255 ; saa = 10 + alpha = 2.81435 ; beta = -5.52139 ; rr0 = 0.255 ; saa = 10 elif 340 <= ll < 360: - alpha = 2.23818 ; beta = 0.81772 ; rr0 = 0.329 ; saa = 19 - + alpha = 2.23818 ; beta = 0.81772 ; rr0 = 0.329 ; saa = 19 + if 45 < bb < 60: gamma = 0 if 0 <= ll < 60: - alpha = 1.38587 ; beta = -9.06536 ; rr0 = 0.076 ; saa = 3 + alpha = 1.38587 ; beta = -9.06536 ; rr0 = 0.076 ; saa = 3 elif 60 <= ll < 90: - alpha = 2.28570 ; beta = -9.88812 ; rr0 = 0.116 ; saa = 3 + alpha = 2.28570 ; beta = -9.88812 ; rr0 = 0.116 ; saa = 3 elif 90 <= ll < 110: - alpha = 1.36385 ; beta = -8.10127 ; rr0 = 0.084 ; saa = 4 + alpha = 1.36385 ; beta = -8.10127 ; rr0 = 0.084 ; saa = 4 elif 110 <= ll < 170: - alpha = 0.05943 ; beta = -1.08126 ; rr0 = 0.027 ; saa = 50 + alpha = 0.05943 ; beta = -1.08126 ; rr0 = 0.027 ; saa = 50 elif 170 <= ll < 200: - alpha = 1.40171 ; beta = -3.21783 ; rr0 = 0.218 ; saa = 99 + alpha = 1.40171 ; beta = -3.21783 ; rr0 = 0.218 ; saa = 99 elif 200 <= ll < 230: - alpha = 0.14718 ; beta = 3.92670 ; rr0 = 0.252 ; saa = 14 + alpha = 0.14718 ; beta = 3.92670 ; rr0 = 0.252 ; saa = 14 elif 230 <= ll < 290: - alpha = 0.57124 ; beta = -4.30242 ; rr0 = 0.066 ; saa = 10 + alpha = 0.57124 ; beta = -4.30242 ; rr0 = 0.066 ; saa = 10 elif 290 <= ll < 330: - alpha = 3.69891 ; beta = -19.62204 ; rr0 = 0.094 ; saa = 5 + alpha = 3.69891 ; beta = -19.62204 ; rr0 = 0.094 ; saa = 5 elif 330 <= ll < 360: alpha = 1.19563 ; beta = -0.45043 ; rr0 = 0.252 ; saa = 9 - + if 60 < bb < 90: gamma = 0 if 0 <= ll < 30: alpha = 0.69443 ; beta = -0.27600 ; rr0 = 0.153 ; saa = 99 elif 30 <= ll < 60: - alpha = 1.11811 ; beta = 0.71179 ; rr0 = 0.085 ; saa = 73 + alpha = 1.11811 ; beta = 0.71179 ; rr0 = 0.085 ; saa = 73 elif 60 <= ll < 90: - alpha = 1.10427 ; beta = -2.37654 ; rr0 = 0.123 ; saa = 99 + alpha = 1.10427 ; beta = -2.37654 ; rr0 = 0.123 ; saa = 99 elif 90 <= ll < 120: - alpha = -0.42211 ; beta = 5.24037 ; rr0 = 0.184 ; saa = 12 + alpha = -0.42211 ; beta = 5.24037 ; rr0 = 0.184 ; saa = 12 elif 120 <= ll < 150: - alpha = 0.87576 ; beta = -4.38033 ; rr0 = 0.100 ; saa = 35 + alpha = 0.87576 ; beta = -4.38033 ; rr0 = 0.100 ; saa = 35 elif 150 <= ll < 180: - alpha = 1.27477 ; beta = -4.98307 ; rr0 = 0.128 ; saa = 72 + alpha = 1.27477 ; beta = -4.98307 ; rr0 = 0.128 ; saa = 72 elif 180 <= ll < 210: - alpha = 1.19512 ; beta = -6.58464 ; rr0 = 0.091 ; saa = 49 + alpha = 1.19512 ; beta = -6.58464 ; rr0 = 0.091 ; saa = 49 elif 210 <= ll < 240: - alpha = 0.97581 ; beta = -4.89869 ; rr0 = 0.100 ; saa = 95 + alpha = 0.97581 ; beta = -4.89869 ; rr0 = 0.100 ; saa = 95 elif 240 <= ll < 270: - alpha = 0.54379 ; beta = -0.84403 ; rr0 = 0.207 ; saa = 35 + alpha = 0.54379 ; beta = -0.84403 ; rr0 = 0.207 ; saa = 35 elif 270 <= ll < 300: alpha = 0.85054 ; beta = 13.01249 ; rr0 = 0.126 ; saa = 39 elif 300 <= ll < 330: alpha = 0.74347 ; beta = 1.39825 ; rr0 = 0.207 ; saa = 10 elif 330 <= ll < 360: - alpha = 0.77310 ; beta = -4.45005 ; rr0 = 0.087 ; saa = 16 + alpha = 0.77310 ; beta = -4.45005 ; rr0 = 0.087 ; saa = 16 return alpha, beta, gamma, rr0, saa @@ -656,7 +656,7 @@ def get_marshall_data(): """ Read in the Marshall data """ - + #data_ma, units_ma, comments_ma = vizier.search("J/A+A/453/635") filen = config.get_datafile('catalogs','extinction_marshall.tsv') data_ma, units_ma, comments_ma = vizier.tsv2recarray(filen) @@ -667,47 +667,47 @@ def findext_marshall(ll, bb, distance=None, redlaw='cardelli1989', Rv=3.1, norm= Find the V-band extinction according to the reddening model of Marshall et al. (2006) published in Astronomy and Astrophysics, Volume 453, Issue 2, July II 2006, pp.635-651 - + The band in which the extinction is calculated is actually optional, and given with the keyword C{norm}. If you set C{norm='Av'}, you get visual extinction (in JOHNSON.V) band, if you set C{norm='Ak'}, you get infrared extinction (in JOHNSON.K). If you want the extinction in different passbands, you can set them via C{norm='GENEVA.V'}. So, C{norm='Av'} is equivalent to setting C{norm='JOHNSON.V'}. - + Example usage: - + 1. Find the extinction for a star at galactic longitude 10.2 and latitude 9.0. If the distance is not given, we return the complete extinction along the line of sight (i.e. put the star somewhere out of the galaxy). - + >>> lng = 10.2 >>> lat = 9.0 >>> ak = findext_marshall(lng, lat, norm='Ak') >>> print("Ak at lng = %.2f, lat = %.2f is %.2f magnitude" %(lng, lat, ak)) Ak at lng = 10.20, lat = 9.00 is 0.15 magnitude - + 2. Find the extinction for a star at galactic lattitude 271.05 and longitude -4.93 and a distance of 144.65 parsecs - + >>> lng = 271.05 >>> lat = -4.93 >>> dd = 144.65 >>> ak = findext_marshall(lng, lat, distance = dd, norm='Ak') >>> print("Ak at lng = %.2f, lat = %.2f and distance = %.2f parsecs is %.2f magnitude" %(lng, lat, dd, ak)) Ak at lng = 271.05, lat = -4.93 and distance = 144.65 parsecs is 0.02 magnitude - + 3. The extinction for galactic longitude 10.2 and latitude 59.0 can not be calculated using the Marshall models, because they are only valid at certain lng and lat (0 < lng < 100 or 260 < lng < 360, -10 < lat < 10) - + >>> lng = 10.2 >>> lat = 59.0 >>> ak = findext_marshall(lng, lat, norm='Ak') >>> print(ak) None - + @param ll: Galactic Longitude (in degrees) should be between 0 and 100 or 260 and 360 degrees @type ll: float @@ -722,11 +722,11 @@ def findext_marshall(ll, bb, distance=None, redlaw='cardelli1989', Rv=3.1, norm= @return: The extinction in K-band @rtype: float """ - + # get Marshall data data_ma, units_ma, comments_ma = get_marshall_data() - - # Check validity of the coordinates + + # Check validity of the coordinates if (ll > 100. and ll < 260.) or ll < 0 or ll > 360: Av = None logger.error("Galactic longitude invalid") @@ -735,15 +735,15 @@ def findext_marshall(ll, bb, distance=None, redlaw='cardelli1989', Rv=3.1, norm= Av = None logger.error("Galactic lattitude invalid") return Av - + # Find the galactic lattitude and longitude of the model, closest to your star dist = np.sqrt((data_ma.GLAT - bb)**2. + (data_ma.GLON - ll)**2.) kma = np.where(dist == dist.min()) - + if min(dist) > .5: logger.error("Could not find a good model value") return Av - + # find the correct index for the distance nb = data_ma.nb[kma] rr = [] ; e_rr = [] ; ext = [] ; e_ext = [] @@ -752,8 +752,8 @@ def findext_marshall(ll, bb, distance=None, redlaw='cardelli1989', Rv=3.1, norm= e_rr.append(data_ma["e_r%i"%i][kma]) ext.append(data_ma["ext%i"%i][kma]) e_ext.append(data_ma["e_ext%i"%i][kma]) - rr = np.array(rr)[:,0] ; e_rr = np.array(e_rr)[:,0] ; ext = np.array(ext)[:,0] ; e_ext = np.array(e_ext)[:,0] - + rr = np.array(rr)[:,0] ; e_rr = np.array(e_rr)[:,0] ; ext = np.array(ext)[:,0] ; e_ext = np.array(e_ext)[:,0] + # Interpolate linearly in distance. If beyond furthest bin, keep that value. if distance is None: logger.info("No distance given") @@ -761,7 +761,7 @@ def findext_marshall(ll, bb, distance=None, redlaw='cardelli1989', Rv=3.1, norm= ak = ext[-1] else: dd = distance/1e3 - + if dd < min(rr): logger.info("Distance below distance to first bin") ak = (dd/rr[0])*ext[0] @@ -772,7 +772,7 @@ def findext_marshall(ll, bb, distance=None, redlaw='cardelli1989', Rv=3.1, norm= else: logger.info("Interpolating linearly") ak = sc.interp(dd, rr, ext) - + #-- Marshall is standard in Ak, but you can change this: #redwave, redflux = get_law(redlaw,Rv=Rv,wave_units='micron',norm='Av', wave=array([0.54,2.22])) redwave, redflux = get_law(redlaw,Rv=Rv,norm=norm,photbands=['JOHNSON.K']) @@ -821,25 +821,25 @@ def findext_marshall(ll, bb, distance=None, redlaw='cardelli1989', Rv=3.1, norm= def findext_drimmel(lng, lat, distance=None, rescaling=True, redlaw='cardelli1989', Rv=3.1, norm='Av',**kwargs): """ - Procedure to retrieve the absorption in V from three-dimensional + Procedure to retrieve the absorption in V from three-dimensional grids, based on the Galactic dust distribution of Drimmel & Spergel. - + Example usage: - + 1. Find the extinction for a star at galactic longitude 10.2 and latitude 9.0. If the distance is not given, we return the complete extinction along the line of sight (i.e. put the star somewhere out of the galaxy). - + >>> lng = 10.2 >>> lat = 9.0 >>> av = findext_drimmel(lng, lat) >>> print("Av at lng = %.2f, lat = %.2f is %.2f magnitude" %(lng, lat, av)) Av at lng = 10.20, lat = 9.00 is 1.24 magnitude - + 2. Find the extinction for a star at galactic lattitude 107.05 and longitude -34.93 and a distance of 144.65 parsecs - + >>> lng = 271.05 >>> lat = -4.93 >>> dd = 144.65 @@ -862,17 +862,17 @@ def findext_drimmel(lng, lat, distance=None, rescaling=True, # Constants deg2rad = pi/180. # convert degrees to rads nsky = 393216 # number of COBE pixels - + # Sun's coordinates (get from dprms) xsun = -8.0 zsun = 0.015 - + # if distance is not given, make it large and put it in kiloparsec if distance is None: d = 1e10 else: d = distance/1e3 - + # build skymaps of rescaling parameters for each component dfac = ones(nsky) sfac = ones(nsky) @@ -883,17 +883,17 @@ def findext_drimmel(lng, lat, distance=None, rescaling=True, sfac[indx] = rfac[indx] indx = where(ncomp == 3) lfac[indx] = rfac[indx] - + # define abs num = array(d).size out = zeros(num) avloc = zeros(num) abspir = zeros(num) absdisk = zeros(num) - + l = lng*deg2rad # [radians] b = lat*deg2rad # [radians] - + # Now for UIDL code: # -find the index of the corresponding COBE pixel # dimensions of sixpack = 768 x 512 (= 393216) @@ -911,13 +911,13 @@ def findext_drimmel(lng, lat, distance=None, rescaling=True, dmax = min([dmax,(14.9999/abs(cos(l)) - xsun/cos(l))]) if sin(l) != 0.: dmax = min([dmax, 14.9999/abs(sin(l))]) - + # replace distance with dmax when greater r = array([d]) indx = where(array([d]) > dmax)[0] if len(indx) > 0: r[indx] = dmax - + # heliocentric cartesian coordinates x = array([r*cos(b)*cos(l)]) y = array([r*cos(b)*sin(l)]) @@ -928,7 +928,7 @@ def findext_drimmel(lng, lat, distance=None, rescaling=True, ni = len(i) j = where(logical_or(abs(x) >= 1., abs(y) >= 2.))[0] nj = len(j) - + if ni > 0: # define the local grid dx = 0.02 @@ -942,18 +942,18 @@ def findext_drimmel(lng, lat, distance=None, rescaling=True, xi = x[i]/dx + float(nx - 1)/2. yj = y[i]/dy + float(ny - 1)/2. zk = z[i]/dz + float(nz - 1)/2. - + # interpolate avloc[i] = _trint(avori2, xi, yj, zk, missing=0.) - + # for stars in Solar neighborhood k = where(logical_and(abs(x) < 0.75, abs(y) < 0.75))[0] nk = len(k) m = where(logical_or(abs(x) >= 0.75, abs(y) >= 0.75))[0] nm = len(m) - + if nk > 0: - + # define the local grid dx = 0.05 dy = 0.05 @@ -966,14 +966,14 @@ def findext_drimmel(lng, lat, distance=None, rescaling=True, xi = x[k]/dx + float(nx - 1)/2. yj = y[k]/dy + float(ny - 1)/2. zk = z[k]/dz + float(nz - 1)/2. - + # trilinear_interpolate absdisk[k] = _trint(avdloc,xi,yj,zk,missing=0.) - + # galacto-centric cartesian x = x + xsun - #larger orion arm grid + #larger orion arm grid if nj > 0: # calculate the allowed maximum distance for larger orion grid dmax = 100. @@ -985,19 +985,19 @@ def findext_drimmel(lng, lat, distance=None, rescaling=True, dmax = min([dmax, 1.374999/abs(cos(l))]) if sin(l) != 0.: dmax = min([dmax, 3.749999/abs(sin(l))]) - + # replace distance with dmax when greater r1 = array([d]) indx = where(array([d]) >= dmax)[0] n = len(indx) if n > 0: r1[indx] = dmax - + # galactocentric centric cartesian coordinates x1 = array([r1*cos(b)*cos(l) + xsun]) y1 = array([r1*cos(b)*sin(l)]) z1 = array([r1*sin(b) + zsun]) - + # define the grid dx = 0.05 dy = 0.05 @@ -1005,15 +1005,15 @@ def findext_drimmel(lng, lat, distance=None, rescaling=True, nx = 76 ny = 151 nz = 51 - + # grid indices xi = x1[j]/dx + 2.5*float(nx - 1) yj = y1[j]/dy + float(ny - 1)/2. zk = z1[j]/dz + float(nz - 1)/2. - + # trilinear_interpolate avloc[j] = _trint(avori,xi,yj,zk,missing=0.) - + # define the grid dx = 0.2 dy = 0.2 @@ -1037,10 +1037,10 @@ def findext_drimmel(lng, lat, distance=None, rescaling=True, out = (dfac[tblindex]*absdisk + sfac[tblindex]*abspir + lfac[tblindex]*avloc).flatten() else: out = (absdisk + abspir + avloc).flatten() - + #-- Marshall is standard in Ak, but you can change this: redwave, redflux = get_law(redlaw,Rv=Rv,norm=norm,photbands=['JOHNSON.V']) - + return(out/redflux[0]) def _pix2xy(pixel, resolution, sixpack=0): @@ -1062,10 +1062,10 @@ def _pix2xy(pixel, resolution, sixpack=0): newshape.append() pixel.shape = newshape pix_sz = (pixel) - + if max(pixel) > 6*4**(resolution-1): raise('Maximum pixel number too large for resolution') - + # set up flag values for RASTR data = [-1] face = -1 @@ -1163,9 +1163,9 @@ def _ll2uv(lng, lat): """ Convert longitude, latitude to unit vectors SMOLDERS SEAL OF APPROVAL - + @param lng : galactic longitude - @type lng : float + @type lng : float @param lat : galactic lattitude @type lat : float @return : unitvector @@ -1297,15 +1297,15 @@ def _ll2pix(lng, lat, res=9): """ _ll2pix is a python function to convert galactic coordinates to DIRBE pixels (coorconv([lng,lat], infmt='L', outfmt='P', inco='G', outco='R9')) - + Input @param lng : galactic longitude - @type lng : float + @type lng : float @param lat : galactic lattitude @type lat : float @return: output coordinate array @rtype: ndarray - + (Note: The default coordinate system for pixels is ecliptic.) """ # COMMON sky_conv_com, e2g,e2q,g2e,g2q,q2e,q2g,load_flag @@ -1367,14 +1367,14 @@ def _lb2xy_schlegel(ll, bb): """ Converts coordinates in lattitude and longitude to coordinates in x and y pixel coordinates. - + The x and y coordinates you find with these formulas are not the coordinates you can read in ds9, but 1 smaller. Hence (x+1, y+1) are the coordinates you find in DS9. - + Input @param ll : galactic longitude - @type ll : float + @type ll : float @param bb : galactic lattitude @type bb : float @return: output coordinate array @@ -1384,16 +1384,16 @@ def _lb2xy_schlegel(ll, bb): if bb <= 0 : hs = -1. elif bb > 0: hs = +1. - + yy = 2048 * sqrt(1. - hs * sin(bb*deg2rad)) * cos(ll*deg2rad) + 2047.5 xx = -2048 * hs * sqrt(1 - hs * sin(bb*deg2rad)) * sin(ll*deg2rad) + 2047.5 - + return xx, yy def findext_schlegel(ll, bb, distance = None, redlaw='cardelli1989', Rv=3.1, norm='Av',**kwargs): """ Get the "Schlegel" extinction at certain longitude and latitude - + This function returns the E(B-V) maps of Schlegel et al. (1998), depending on wether the distance is given or not, the E(B-V) value is corrected. If distance is set we use the distance-corrected @@ -1402,7 +1402,7 @@ def findext_schlegel(ll, bb, distance = None, redlaw='cardelli1989', Rv=3.1, nor E(B-V) = E(B-V)_maps * (1 - exp(-10 * r * sin(|b|))) where E(B-V) is the value to be used and E(B-V)_maps the value as found with the Schlegel dust maps - + Then we convert the E(B-V) to Av. Standard we use Av = E(B-V)*Rv with Rv=3.1, but the value of Rv can be given as a keyword. ! WARNING: the schlegel maps are not usefull when |b| < 5 degrees ! @@ -1411,11 +1411,11 @@ def findext_schlegel(ll, bb, distance = None, redlaw='cardelli1989', Rv=3.1, nor dd = distance if distance is not None: dd = distance/1.e3 # convert to kpc - - + + # first get the right pixel coordinates xx, yy = _lb2xy_schlegel(ll,bb) - + # read in the right map: if bb <= 0: data, mask = get_schlegel_data_south() @@ -1424,51 +1424,50 @@ def findext_schlegel(ll, bb, distance = None, redlaw='cardelli1989', Rv=3.1, nor if abs(bb) < 10.: logger.warning("Schlegel is not good for lattitudes > 10 degrees") - + # the xy-coordinates are: xl = floor(xx) yl = floor(yy) xh = xl + 1. yh = yl + 1. - + # the weights are just the distances to the points w1 = (xl-xx)**2 + (yl-yy)**2 w2 = (xl-xx)**2 + (yh-yy)**2 w3 = (xh-xx)**2 + (yl-yy)**2 w4 = (xh-xx)**2 + (yh-yy)**2 - + # the values of these points are: v1 = data[xl, yl] v2 = data[xl, yh] v3 = data[xh, yl] - v4 = data[xh, yh] + v4 = data[xh, yh] f1 = mask[xl, yl] f2 = mask[xl, yh] f3 = mask[xh, yl] f4 = mask[xh, yh] - + ebv = (w1*v1 + w2*v2 + w3*v3 + w4*v4) / (w1 + w2 + w3 + w4) - + # Check flags at the right pixels logger.info("flag of pixel 1 is: %i" %f1) logger.info("flag of pixel 2 is: %i" %f2) logger.info("flag of pixel 3 is: %i" %f3) logger.info("flag of pixel 4 is: %i" %f4) - + if dd is not None: ebv = ebv * (1. - exp(-10. * dd * sin(abs(bb*deg2rad)))) - + # if Rv is given, by definition we find Av = Ebv*Rv av = ebv*Rv - + #-- Marshall is standard in Ak, but you can change this: redwave, redflux = get_law(redlaw,Rv=Rv,norm=norm,photbands=['JOHNSON.V']) - + return av/redflux[0] - + #} if __name__ == "__main__": - print findext_marshall(10.2,9.) - print findext_marshall(10.2,9.,norm='Ak') - + print(findext_marshall(10.2,9.)) + print(findext_marshall(10.2,9.,norm='Ak')) diff --git a/sed/filters.py b/sed/filters.py index aa1a90620..825a2f72b 100644 --- a/sed/filters.py +++ b/sed/filters.py @@ -318,11 +318,9 @@ """ import os import glob -#import astropy.io.fits as pf import logging import numpy as np -from ivs import config from ivs.aux.decorators import memoized from ivs.aux import decorators from ivs.aux import loggers @@ -340,19 +338,19 @@ def get_response(photband): """ Retrieve the response curve of a photometric system 'SYSTEM.FILTER' - + OPEN.BOL represents a bolometric open filter. - + Example usage: - + >>> p = pl.figure() >>> for band in ['J','H','KS']: ... p = pl.plot(*get_response('2MASS.%s'%(band))) - + If you defined a custom filter with the same name as an existing one and you want to use that one in the future, set C{prefer_file=False} in the C{custom_filters} module dictionary. - + @param photband: photometric passband @type photband: str ('SYSTEM.FILTER') @return: (wavelength [A], response) @@ -361,7 +359,7 @@ def get_response(photband): photband = photband.upper() prefer_file = custom_filters['_prefer_file'] if photband=='OPEN.BOL': - return np.array([1,1e10]),np.array([1/(1e10-1),1/(1e10-1)]) + return np.array([1,1e10]),np.array([1/(1e10-1),1/(1e10-1)]) #-- either get from file or get from dictionary photfile = os.path.join(basedir,'filters',photband) photfile_is_file = os.path.isfile(photfile) @@ -375,14 +373,14 @@ def get_response(photband): elif photfile_is_file: wave, response = ascii.read2array(photfile).T[:2] else: - raise IOError,('{0} does not exist {1}'.format(photband,custom_filters.keys())) + raise IOError('{0} does not exist {1}'.format(photband,list(custom_filters.keys()))) sa = np.argsort(wave) return wave[sa],response[sa] def create_custom_filter(wave,peaks,range=(3000,4000),sigma=3.): """ Create a custom filter as a sum of Gaussians. - + @param wave: wavelength to evaluate the profile on @type wave: ndarray @param peaks: heights of the peaks @@ -405,7 +403,7 @@ def create_custom_filter(wave,peaks,range=(3000,4000),sigma=3.): def add_custom_filter(wave,response,**kwargs): """ Add a custom filter to the set of predefined filters. - + Extra keywords are: 'eff_wave', 'type', 'vegamag', 'vegamag_lit', @@ -414,10 +412,10 @@ def add_custom_filter(wave,response,**kwargs): 'Flam0', 'Flam0_units', 'Flam0_lit', 'Fnu0', 'Fnu0_units', 'Fnu0_lit', 'source' - + default C{type} is 'CCD'. default C{photband} is 'CUSTOM.FILTER' - + @param wave: wavelength (angstrom) @type wave: ndarray @param response: response @@ -432,7 +430,7 @@ def add_custom_filter(wave,response,**kwargs): #-- check if the filter already exists: photfile = os.path.join(basedir,'filters',photband) if os.path.isfile(photfile) and not kwargs['force']: - raise ValueError,'bandpass {0} already exists'.format(photfile) + raise ValueError('bandpass {0} already exists'.format(photfile)) elif photband in custom_filters: logger.debug('Overwriting previous definition of {0}'.format(photband)) custom_filters[photband] = dict(response=(wave,response)) @@ -455,7 +453,7 @@ def add_custom_filter(wave,response,**kwargs): def set_prefer_file(prefer_file=True): """ Set whether to prefer files or custom filters when both exist. - + @param prefer_file: boolean @type prefer_file: bool """ @@ -488,12 +486,12 @@ def add_spectrophotometric_filters(R=200.,lambda0=950.,lambdan=3350.): def list_response(name='*',wave_range=(-np.inf,+np.inf)): """ List available response curves. - + Specify a glob string C{name} and/or a wavelength range to make a selection of all available curves. If nothing is supplied, all curves will be returned. - + @param name: list all curves containing this string - @type name: str + @type name: str @param wave_range: list all curves within this wavelength range (A) @type wave_range: (float, float) @return: list of curve files @@ -505,7 +503,7 @@ def list_response(name='*',wave_range=(-np.inf,+np.inf)): else: name_ = name curve_files = sorted(glob.glob(os.path.join(basedir,'filters',name_.upper()))) - curve_files = sorted(curve_files+[key for key in custom_filters.keys() if ((name in key) and not (key=='_prefer_file'))]) + curve_files = sorted(curve_files+[key for key in list(custom_filters.keys()) if ((name in key) and not (key=='_prefer_file'))]) curve_files = [cf for cf in curve_files if not ('HUMAN' in cf or 'EYE' in cf) ] #-- select in correct wavelength range curve_files = [os.path.basename(curve_file) for curve_file in curve_files if (wave_range[0]<=eff_wave(os.path.basename(curve_file))<=wave_range[1])] @@ -518,7 +516,7 @@ def list_response(name='*',wave_range=(-np.inf,+np.inf)): def is_color(photband): """ Return true if the photometric passband is actually a color. - + @param photband: name of the photometric passband @type photband: string @return: True or False @@ -535,7 +533,7 @@ def is_color(photband): def get_color_photband(photband): """ Retrieve the photometric bands from color - + @param photband: name of the photometric passband @type photband: string @return: tuple of strings @@ -556,16 +554,16 @@ def get_color_photband(photband): def make_color(photband): """ Make a color from a color name and fluxes. - + You get two things: a list of photbands that are need to construct the color, and a function which you need to pass fluxes to compute the color. - + >>> bands, func = make_color('JOHNSON.B-V') >>> print(bands) ('JOHNSON.B', 'JOHNSON.V') >>> print(func(2,3.)) 0.666666666667 - + @return: photbands, function to construct color @rtype: tuple,callable """ @@ -586,17 +584,17 @@ def make_color(photband): def eff_wave(photband,model=None,det_type=None): """ Return the effective wavelength of a photometric passband. - + The effective wavelength is defined as the average wavelength weighed with the response curve. - + >>> eff_wave('2MASS.J') 12393.093155655277 - + If you give model fluxes as an extra argument, the wavelengths will take - these into account to calculate the `true' effective wavelength (e.g., + these into account to calculate the `true' effective wavelength (e.g., Van Der Bliek, 1996), eq 2. - + @param photband: photometric passband @type photband: str ('SYSTEM.FILTER') or array/list of str @param model: model wavelength and fluxes @@ -604,10 +602,10 @@ def eff_wave(photband,model=None,det_type=None): @return: effective wavelength [A] @rtype: float or numpy array """ - + #-- if photband is a string, it's the name of a photband: put it in a container # but unwrap afterwards - if isinstance(photband,unicode): + if isinstance(photband,str): photband = str(photband) if isinstance(photband,str): single_band = True @@ -615,7 +613,7 @@ def eff_wave(photband,model=None,det_type=None): #-- else, it is a container else: single_band = False - + my_eff_wave = [] for iphotband in photband: try: @@ -637,28 +635,28 @@ def eff_wave(photband,model=None,det_type=None): is_response = response>1e-10 start_response,end_response = wave[is_response].min(),wave[is_response].max() fluxm = np.sqrt(10**np.interp(np.log10(wave),np.log10(model[0]),np.log10(model[1]))) - + if det_type=='CCD': this_eff_wave = np.sqrt(np.trapz(wave*fluxm*response,x=wave) / np.trapz(fluxm*response/wave,x=wave)) elif det_type=='BOL': - this_eff_wave = np.sqrt(np.trapz(fluxm*response,x=wave) / np.trapz(fluxm*response/wave**2,x=wave)) + this_eff_wave = np.sqrt(np.trapz(fluxm*response,x=wave) / np.trapz(fluxm*response/wave**2,x=wave)) #-- if the photband is not defined: except IOError: this_eff_wave = np.nan my_eff_wave.append(this_eff_wave) - + if single_band: my_eff_wave = my_eff_wave[0] else: my_eff_wave = np.array(my_eff_wave,float) - + return my_eff_wave @memoized def get_info(photbands=None): """ Return a record array containing all filter information. - + The record arrays contains following columns: - photband - eff_wave @@ -669,7 +667,7 @@ def get_info(photbands=None): - Flam0, Flam0_units, Flam0_lit - Fnu0, Fnu0_units, Fnu0_lit, - source - + @param photbands: list of photbands to get the information from. The input order is equal to the output order. If C{None}, all filters are returned. @type photbands: iterable container (list, tuple, 1Darray) @@ -683,7 +681,7 @@ def get_info(photbands=None): if 'zp' in custom_filters[iph]: zp = np.hstack([zp,custom_filters[iph]['zp']]) zp = zp[np.argsort(zp['photband'])] - + #-- list photbands in order given, and remove those that do not have # zeropoints etc. if photbands is not None: @@ -691,7 +689,7 @@ def get_info(photbands=None): zp = zp[order] keep = (zp['photband']==photbands) zp = zp[keep] - + return zp @@ -701,10 +699,10 @@ def get_info(photbands=None): def update_info(zp=None): """ Update information in zeropoint file, e.g. after calibration. - + Call first L{ivs.sed.model.calibrate} without arguments, and pass the output to this function. - + @param zp: updated contents from C{zeropoints.dat} @type zp: recarray """ @@ -721,7 +719,7 @@ def update_info(zp=None): logger.info('No new calibrations; previous information on existing response curves is copied') else: logger.info('Received new calibrations contents of zeropoints.dat will be updated') - + #-- update info on previously non existing response curves new_zp = np.zeros(len(resp_files),dtype=zp.dtype) logger.info('Found {} new response curves, adding them with default information'.format(len(resp_files))) @@ -738,7 +736,31 @@ def update_info(zp=None): zp = np.hstack([zp,new_zp]) sa = np.argsort(zp['photband']) ascii.write_array(zp[sa],'zeropoints.dat',header=True,auto_width=True,comments=['#'+line for line in comms[:-2]],use_float='%g') - + + +def get_plotsymbolcolorinfo(): + """ + Return the arrays needed to always plot the same photometric system with the same color. + """ + photsystem_file = os.path.join(os.path.dirname(os.path.abspath(__file__)),'list_photsystems_sorted.dat') + plotcolorvalues_file = os.path.join(os.path.dirname(os.path.abspath(__file__)),'plotcolorvalues.dat') + try: + sortedphotsystems = ascii.read2array(photsystem_file,dtype='str') + except IOError: + logger.info('Loading of {} file failed. No fixed symbol color for each photometric system possible.'.format(photsystem_file)) + try: + plotcolorvalues = ascii.read2array(plotcolorvalues_file) + except IOError: + logger.info('Loading of {} file failed. No fixed symbol color for each photometric system possible.'.format(plotcolorvalues_file)) + + try: + if len(sortedphotsystems) == len(plotcolorvalues): + return sortedphotsystems.ravel(),plotcolorvalues.ravel() + else: + raise IndexError + print('{} should be of equal length as {}.'.format(plotcolorvalues_file,photsystem_file)) + except NameError: + return None,None if __name__=="__main__": @@ -748,7 +770,7 @@ def update_info(zp=None): import doctest doctest.testmod() pl.show() - + else: import itertools responses = list_response() @@ -766,7 +788,7 @@ def update_info(zp=None): if not hasattr(pl.gca(),'color_cycle'): color_cycle = itertools.cycle([pl.cm.spectral(j) for j in np.linspace(0, 1.0, nr_filters)]) p = pl.gca().color_cycle = color_cycle - color = pl.gca().color_cycle.next() + color = next(pl.gca().color_cycle) p = pl.title(resp.split('.')[0]) # get the response curve and plot it wave,trans = get_response(resp) diff --git a/sed/fit.py b/sed/fit.py index 0d42b6100..917e4780a 100644 --- a/sed/fit.py +++ b/sed/fit.py @@ -17,7 +17,7 @@ from ivs.statistics import pca from ivs.sed import model from ivs.sed import filters -from ivs.sed.decorators import iterate_gridsearch,parallel_gridsearch +from ivs.sed.decorators import parallel_gridsearch from ivs.sigproc import fit as sfit from ivs.aux import numpy_ext from ivs.aux import progressMeter @@ -173,7 +173,7 @@ def get_PCA_parameters(obsT,calib,P,means,stds,e_obsT=None,mc=None): if mc is not None: if e_obsT is None: e_obsT = 0.01*obsT[0] - obsT_ = np.array([obsT[0]+np.random.normal(size=len(obsT[0]),scale=e_obsT) for i in xrange(mc)]) + obsT_ = np.array([obsT[0]+np.random.normal(size=len(obsT[0]),scale=e_obsT) for i in range(mc)]) obsT_[0] = obsT[0] obsT = obsT_ @@ -211,6 +211,7 @@ def stat_chi2(meas,e_meas,colors,syn,full_output=False, **kwargs): @rtype: float,float,float """ #-- if syn represents only one measurement + #syn*=2 if len(syn.shape)==1: if sum(~colors) > 0: if 'distance' in kwargs: @@ -260,14 +261,12 @@ def generate_grid_single_pix(photbands, points=None, clear_memory=True, **kwargs """ Generate a grid of parameters. """ - #-- Find the parameters provided and store them separately. ranges, parameters = {}, [] - for key in kwargs.keys(): + for key in list(kwargs.keys()): if re.search('range$', key): ranges[key] = kwargs.pop(key) parameters.append(re.sub('range$', '', key)) - #-- report on the received grid if not kwargs: logger.info('Received grid (%s)'%model.defaults2str()) @@ -279,7 +278,8 @@ def generate_grid_single_pix(photbands, points=None, clear_memory=True, **kwargs model._get_pix_grid(photbands,teffrange=(-inf,inf), loggrange=(-inf,inf),ebvrange=(-inf,inf), zrange=(-inf,inf),rvrange=(-inf,inf),vradrange=(0,0), - include_Labs=True,clear_memory=clear_memory,**kwargs) + include_Labs=True,clear_memory=clear_memory, + variables=parameters, **kwargs) #-- we first generate random teff-logg coordinates, since the grid is # not exactly convex in these parameters. We assume it is for all the @@ -350,8 +350,8 @@ def generate_grid_single_pix(photbands, points=None, clear_memory=True, **kwargs if name in out_dict_: out_dict[name] = out_dict_[name] else: - out_dict[name] = np.array([ranges[name+'range'][0] for i in out_dict['teff']]) - + out_dict[name] = np.array([ranges[name+'range'][0] + for i in out_dict_['teff']]) return out_dict @@ -379,7 +379,7 @@ def generate_grid_pix(photbands, points=None, clear_memory=False,**kwargs): #-- Find all ranges and the number of components radiusrange = [] ranges, parameters, components = {}, set(), set() - for key in kwargs.keys(): + for key in list(kwargs.keys()): if re.search('range$', key) and not re.search('^rad\d?',key): ranges[key] = kwargs.pop(key) name, comp = re.findall('(.*?)(\d?)range$', key)[0] @@ -407,13 +407,13 @@ def generate_grid_pix(photbands, points=None, clear_memory=False,**kwargs): #-- prepare a permutation so different blocks are not clustered together permutation = np.random.permutation(len(grid_['teff'])) - for key in grid_.keys(): + for key in list(grid_.keys()): npoints = min(npoints,len(grid_[key])) pars[key+comp] = grid_[key][permutation] #-- The generate_grid_single_pix method does not guarantee the number of points. # So force that all arrays have the same length. - for key in pars.keys(): + for key in list(pars.keys()): pars[key] = pars[key][:npoints] #-- Check that ebv, z and rv is the same for each component @@ -434,7 +434,6 @@ def generate_grid_pix(photbands, points=None, clear_memory=False,**kwargs): if radiusrange == []: radiusrange = [(0.1,10) for i in components] for i, comp in enumerate(components): pars['rad'+comp] = np.random.uniform(low=radiusrange[i][0], high=radiusrange[i][1], size=npoints) - return pars @@ -934,7 +933,7 @@ def igrid_search(meas,e_meas,photbands,*args,**kwargs): p = progressMeter.ProgressMeter(total=N) #-- run over the grid, retrieve synthetic fluces and compare with # observations. - for n,pars in enumerate(itertools.izip(*args)): + for n,pars in enumerate(zip(*args)): if index is None: p.update(1) syn_flux,Labs = model_func(*pars,photbands=photbands,**kwargs) chisqs[n],scales[n],e_scales[n] = stat_func(meas,e_meas,colors,syn_flux, **fitkws) @@ -954,7 +953,7 @@ def create_parameter_dict(**pars): #-- Find all the parameters first parnames = set() atributes = set() - for key in pars.keys(): + for key in list(pars.keys()): name, att = re.findall("(.*)_([a-zA-Z]+)$", key)[0] parnames.add(name) atributes.add(att) @@ -966,7 +965,7 @@ def create_parameter_dict(**pars): result[att] = np.array([None for i in parnames]) #-- read the attributes - for key in pars.keys(): + for key in list(pars.keys()): name, att = re.findall("(.*)_([a-zA-Z]+)$", key)[0] result[att][parnames == name] = pars[key] @@ -1036,7 +1035,7 @@ def iminimize(meas,e_meas,photbands, points=None, return_minimizer=False,**kwarg fitmodel = kwargs.pop('model_func',_iminimize_model) residuals = kwargs.pop('res_func',_iminimize_residuals) epsfcn = kwargs.pop('epsfcn', 0.0005)# using ~3% step to derive jacobian. - + fitkws_old = kwargs.pop('fitkws', None) #-- get the parameters parameters = create_parameter_dict(**kwargs) @@ -1214,7 +1213,7 @@ def residual_single(parameters): c0 = time.time() teffs,loggs,ebvs,zs,radii = generate_grid(photbands,teffrange=(5000,5800),loggrange=(4.20,4.70),zrange=(0,0),ebvrange=(0.05,0.08), grid='kurucz',points=10000) - print 'Time: %i'%(time.time()-c0) + print('Time: %i'%(time.time()-c0)) plt.figure(2) plt.scatter(teffs,loggs,c=ebvs,s=(zs+5)*10,edgecolors='none',cmap=plt.cm.spectral) @@ -1244,5 +1243,5 @@ def residual_single(parameters): calib = calibrate_PCA(T,grid,function='linear') sample_index = int(np.random.uniform(high=len(A))) sample = 10**A[sample_index] - print [bla[sample_index] for bla in grid] - print get_PCA_parameters(sample,calib,P,means,stds) + print([bla[sample_index] for bla in grid]) + print(get_PCA_parameters(sample,calib,P,means,stds)) diff --git a/sed/limbdark.py b/sed/limbdark.py index f7740bb6e..570b38a5e 100644 --- a/sed/limbdark.py +++ b/sed/limbdark.py @@ -114,53 +114,55 @@ import itertools import astropy.io.fits as pf import numpy as np -from scipy.optimize import leastsq,fmin +from scipy.optimize import leastsq, fmin from scipy.interpolate import splrep, splev from scipy.interpolate import LinearNDInterpolator -from Scientific.Functions.Interpolation import InterpolatingFunction +# from Scientific.Functions.Interpolation import InterpolatingFunction from ivs.aux import loggers from ivs.sed import reddening from ivs.sed import model from ivs.spectra import tools from ivs.units import constants -from ivs.aux.decorators import memoized,clear_memoization +from ivs.aux.decorators import memoized, clear_memoization from ivs import config logger = logging.getLogger("SED.LIMBDARK") logger.addHandler(loggers.NullHandler()) -#-- default values for grids -defaults = dict(grid='kurucz',odfnew=False,z=+0.0,vturb=2, - alpha=False,nover=False) -#-- relative location of the grids +# -- default values for grids +defaults = dict(grid='kurucz', odfnew=False, z=+0.0, vturb=2, + alpha=False, nover=False) +# -- relative location of the grids + basedir = 'ldtables' -#{ Interface to the library +# { Interface to the library + def set_defaults(**kwargs): """ Set defaults of this module - + If you give no keyword arguments, the default values will be reset. """ clear_memoization(keys=['ivs.sed.ld']) if not kwargs: - kwargs = dict(grid='kurucz',odfnew=True,z=+0.0,vturb=2, - alpha=False,nover=False, # KURUCZ - He=97, # WD - t=1.0,a=0.0,c=0.5,m=1.0,co=1.05) # MARCS and COMARCS - + kwargs = dict(grid='kurucz', odfnew=True, z=+0.0, vturb=2, + alpha=False, nover=False, # KURUCZ + He=97, # WD + t=1.0, a=0.0, c=0.5, m=1.0, co=1.05) # MARCS and COMARCS + for key in kwargs: + if key in defaults: defaults[key] = kwargs[key] - logger.info('Set %s to %s'%(key,kwargs[key])) - + logger.info('Set %s to %s' % (key, kwargs[key])) def get_gridnames(): """ Return a list of available grid names. - + @return: list of grid names @rtype: list of str """ @@ -170,7 +172,7 @@ def get_gridnames(): def get_grid_dimensions(**kwargs): """ Retrieve the possible effective temperatures and gravities from a grid. - + @rtype: (ndarray,ndarray) @return: effective temperatures, gravities """ @@ -182,27 +184,27 @@ def get_grid_dimensions(**kwargs): teffs.append(float(mod.header['TEFF'])) loggs.append(float(mod.header['LOGG'])) ff.close() - return np.array(teffs),np.array(loggs) + return np.array(teffs), np.array(loggs) @memoized -def get_grid_mesh(wave=None,teffrange=None,loggrange=None,**kwargs): +def get_grid_mesh(wave=None, teffrange=None, loggrange=None, **kwargs): """ Return InterpolatingFunction spanning the available grid of atmosphere models. - + WARNING: the grid must be entirely defined on a mesh grid, but it does not need to be equidistant. - + It is thus the user's responsibility to know whether the grid is evenly spaced in logg and teff (e.g. this is not so for the CMFGEN models). - + You can supply your own wavelength range, since the grid models' resolution are not necessarily homogeneous. If not, the first wavelength array found in the grid will be used as a template. - + It might take a long a time and cost a lot of memory if you load the entire grid. Therefor, you can also set range of temperature and gravity. - + @param wave: wavelength to define the grid on @type wave: ndarray @param teffrange: starting and ending of the grid in teff @@ -213,56 +215,58 @@ def get_grid_mesh(wave=None,teffrange=None,loggrange=None,**kwargs): function @rtype: (3x1Darray,3Darray,interp_func) """ - #-- get the dimensions of the grid - teffs,loggs = get_grid_dimensions(**kwargs) - #-- build flux grid, assuming a perfectly sampled grid (needs not to be + # -- get the dimensions of the grid + teffs, loggs = get_grid_dimensions(**kwargs) + # -- build flux grid, assuming a perfectly sampled grid (needs not to be # equidistant) if teffrange is not None: - sa = (teffrange[0]<=teffs) & (teffs<=teffrange[1]) + sa = (teffrange[0] <= teffs) & (teffs <= teffrange[1]) teffs = teffs[sa] if loggrange is not None: - sa = (loggrange[0]<=loggs) & (loggs<=loggrange[1]) + sa = (loggrange[0] <= loggs) & (loggs <= loggrange[1]) loggs = loggs[sa] - - #-- run over teff and logg, and interpolate the models onto the supplied + + # -- run over teff and logg, and interpolate the models onto the supplied # wavelength range + gridfile = get_file(**kwargs) - + if wave is not None: - table = np.zeros((len(teffs),len(wave),18)) + table = np.zeros((len(teffs), len(wave), 18)) ff = pf.open(gridfile) - for i,(teff,logg) in enumerate(zip(teffs,loggs)): - mod_name = "T%05d_logg%01.02f" %(teff,logg) + for i, (teff, logg) in enumerate(zip(teffs, loggs)): + mod_name = "T%05d_logg%01.02f" % (teff, logg) mod = ff[mod_name] imu = np.array(mod.columns.names[1:], float) - itable = np.array(mod.data.tolist())[:,1:] + itable = np.array(mod.data.tolist())[:, 1:] iwave = mod.data.field('wavelength') - #-- if there is no wavelength range given, we assume that + # -- if there is no wavelength range given, we assume that # the whole grid has the same resolution, and the first # wave-array will be used as a template if wave is None: wave = iwave - table = np.zeros((len(teffs),len(wave),len(imu))) + table = np.zeros((len(teffs), len(wave), len(imu))) try: table[i] = itable except: - for j,jimu in enumerate(imu): - table[i,:,j] = np.interp(wave,iwave,itable[:,j]) + for j, jimu in enumerate(imu): + table[i, :, j] = np.interp(wave, iwave, itable[:, j]) ff.close() - table_grid = LinearNDInterpolator(np.array([np.log10(teffs),loggs]).T,table) - return imu,wave,teffs,loggs,table,table_grid + table_grid = LinearNDInterpolator(np.array([np.log10(teffs), loggs]).T, + table) + return imu, wave, teffs, loggs, table, table_grid -def get_file(integrated=False,**kwargs): +def get_file(integrated=False, **kwargs): """ Retrieve the filename containing the specified SED grid. - + The keyword arguments are specific to the kind of grid you're using. - + Basic keywords are 'grid' for the name of the grid, and 'z' for metallicity. For other keywords, see the source code. - + Available grids and example keywords: - grid='kurucz93': * metallicity (z): m01 is -0.1 log metal abundance relative to solar (solar abundances from Anders and Grevesse 1989) @@ -271,7 +275,7 @@ def get_file(integrated=False,**kwargs): * turbulent velocity (vturb): vturb in km/s * nover= True means no overshoot * odfnew=True means no overshoot but with better opacities and abundances - + @param integrated: choose integrated version of the grid @type integrated: boolean @keyword grid: gridname (default Kurucz) @@ -279,70 +283,71 @@ def get_file(integrated=False,**kwargs): @return: gridfile @rtype: str """ - #-- possibly you give a filename - grid = kwargs.get('grid',defaults['grid']) + # -- possibly you give a filename + grid = kwargs.get('grid', defaults['grid']) if os.path.isfile(grid): return grid - - #-- general - z = kwargs.get('z',defaults['z']) - #-- only for Kurucz - vturb = int(kwargs.get('vturb',defaults['vturb'])) - odfnew = kwargs.get('odfnew',defaults['odfnew']) - alpha = kwargs.get('alpha',defaults['alpha']) - nover = kwargs.get('nover',defaults['nover']) - - #-- figure out what grid to use - if grid=='kurucz': - if not isinstance(z,str): z = '%.1f'%(z) - if not isinstance(vturb,str): vturb = '%d'%(vturb) + + # -- general + z = kwargs.get('z', defaults['z']) +# -- only for Kurucz + vturb = int(kwargs.get('vturb', defaults['vturb'])) + odfnew = kwargs.get('odfnew', defaults['odfnew']) + alpha = kwargs.get('alpha', defaults['alpha']) + nover = kwargs.get('nover', defaults['nover']) + + # -- figure out what grid to use + if grid == 'kurucz': + if not isinstance(z, str): + z = '%.1f' % (z) + if not isinstance(vturb, str): + vturb = '%d' % (vturb) if not alpha and not nover and not odfnew: - basename = 'kurucz93_z%s_k%s_ld.fits'%(z,vturb) + basename = 'kurucz93_z%s_k%s_ld.fits' % (z, vturb) elif alpha and odfnew: - basename = 'kurucz93_z%s_ak%sodfnew_ld.fits'%(z,vturb) + basename = 'kurucz93_z%s_ak%sodfnew_ld.fits' % (z, vturb) elif odfnew: - basename = 'kurucz93_z%s_k%sodfnew_ld.fits'%(z,vturb) + basename = 'kurucz93_z%s_k%sodfnew_ld.fits' % (z, vturb) elif nover: - basename = 'kurucz93_z%s_k%snover_ld.fits'%(z,vturb) + basename = 'kurucz93_z%s_k%snover_ld.fits' % (z, vturb) else: basename = grid - - #-- retrieve the absolute path of the file and check if it exists: - if not '*' in basename: + +# -- retrieve the absolute path of the file and check if it exists: + if '*' not in basename: if integrated: - grid = config.get_datafile(basedir,'i'+basename) + grid = config.get_datafile(basedir, 'i'+basename) else: - grid = config.get_datafile(basedir,basename) - #-- we could also ask for a list of files, when wildcards are given: + grid = config.get_datafile(basedir, basename) + # -- we could also ask for a list of files, when wildcards are given: else: - grid = config.glob(basedir,'i'+basename) + grid = config.glob(basedir, 'i'+basename) if integrated: - grid = config.glob(basedir,'i'+basename) + grid = config.glob(basedir, 'i'+basename) else: - grid = config.glob(basedir,basename) - logger.debug('Selected %s'%(grid)) - - return grid + grid = config.glob(basedir, basename) + logger.debug('Selected %s' % (grid)) + return grid -def get_table(teff=None,logg=None,ebv=None,vrad=None,star=None, - wave_units='AA',flux_units='erg/cm2/s/AA/sr',**kwargs): +def get_table(teff=None, logg=None, ebv=None, vrad=None, star=None, + wave_units='AA', flux_units='erg/cm2/s/AA/sr', **kwargs): """ Retrieve the specific intensity of a model atmosphere. - + ebv is reddening vrad is radial velocity: positive is redshift, negative is blueshift (km/s!) - + extra kwargs are for reddening - + You get limb angles, wavelength and a table. The shape of the table is (N_wave,N_mu). - + WARNING: wave and flux units cannot be specificed for the moment. - + >>> mu,wave,table = get_table(10000,4.0) - + >>> p = pl.figure() >>> ax1 = pl.subplot(221) >>> p = pl.title('E(B-V)=0, vrad=0') @@ -351,9 +356,9 @@ def get_table(teff=None,logg=None,ebv=None,vrad=None,star=None, >>> p = pl.loglog(wave,table) >>> p = pl.xlim(700,15000) >>> p = pl.ylim(1e3,1e8) - + >>> mu,wave,table = get_table(10000,4.0,ebv=0.5) - + >>> p = pl.subplot(222,sharex=ax1,sharey=ax1) >>> p = pl.title('E(B-V)=0.5, vrad=0') >>> ax = pl.gca() @@ -361,9 +366,9 @@ def get_table(teff=None,logg=None,ebv=None,vrad=None,star=None, >>> p = pl.loglog(wave,table) >>> p = pl.xlim(700,15000) >>> p = pl.ylim(1e3,1e8) - + >>> mu,wave,table = get_table(10000,4.0,vrad=-1000.) - + >>> p = pl.subplot(223,sharex=ax1,sharey=ax1) >>> p = pl.title('E(B-V)=0., vrad=-10000.') >>> ax = pl.gca() @@ -371,9 +376,9 @@ def get_table(teff=None,logg=None,ebv=None,vrad=None,star=None, >>> p = pl.loglog(wave,table) >>> p = pl.xlim(700,15000) >>> p = pl.ylim(1e3,1e8) - + >>> mu,wave,table = get_table(10000,4.0,vrad=-1000.,ebv=0.5) - + >>> p = pl.subplot(224,sharex=ax1,sharey=ax1) >>> p = pl.title('E(B-V)=0.5, vrad=-10000.') >>> ax = pl.gca() @@ -381,14 +386,14 @@ def get_table(teff=None,logg=None,ebv=None,vrad=None,star=None, >>> p = pl.loglog(wave,table) >>> p = pl.xlim(700,15000) >>> p = pl.ylim(1e3,1e8) - + >>> mu,wave,table = get_table(10050,4.12) - + >>> p = pl.figure() >>> pl.gca().set_color_cycle([pl.cm.spectral(i) for i in np.linspace(0,1,len(mu))]) >>> p = pl.loglog(wave,table) - - + + @param teff: effective temperature (K) @type teff: float @param logg: log surface gravity (cgs, dex) @@ -400,85 +405,84 @@ def get_table(teff=None,logg=None,ebv=None,vrad=None,star=None, @return: mu angles, wavelengths, table (Nwave x Nmu) @rtype: array, array, array """ - #-- get the FITS-file containing the tables + # -- get the FITS-file containing the tables gridfile = get_file(**kwargs) - - #-- read the file: + + # -- read the file: ff = pf.open(gridfile) - + teff = float(teff) logg = float(logg) - - #-- if we have a grid model, no need for interpolation + + # -- if we have a grid model, no need for interpolation try: - #-- extenstion name as in fits files prepared by Steven - mod_name = "T%05d_logg%01.02f" %(teff,logg) + # -- extenstion name as in fits files prepared by Steven + mod_name = "T%05d_logg%01.02f" % (teff, logg) mod = ff[mod_name] mu = np.array(mod.columns.names[1:], float) - table = np.array(mod.data.tolist())[:,1:] + table = np.array(mod.data.tolist())[:, 1:] wave = mod.data.field('wavelength') - logger.debug('Model LD taken directly from file (%s)'%(os.path.basename(gridfile))) + logger.debug('Model LD taken directly from file (%s)' % + (os.path.basename(gridfile))) except KeyError: - mu,wave,teffs,loggs,flux,flux_grid = get_grid_mesh(**kwargs) - logger.debug('Model LD interpolated from grid %s (%s)'%(os.path.basename(gridfile),kwargs)) + mu, wave, teffs, loggs, flux, flux_grid = get_grid_mesh(**kwargs) + logger.debug('Model LD interpolated from grid %s (%s)' + % (os.path.basename(gridfile), kwargs)) wave = wave + 0. - table = flux_grid(np.log10(teff),logg) + 0. - - ff.close() - - #-- velocity shift if necessary - if vrad is not None and vrad!=0: - cc = constants.cc/1000. #speed of light in km/s - for i in range(len(mu)): - flux_shift = tools.doppler_shift(wave,vrad,flux=table[:,i]) - table[:,i] = flux_shift - 5.*vrad/cc*table[:,i] - - #-- redden if necessary - if ebv is not None and ebv>0: - for i in range(len(mu)): - table[:,i] = reddening.redden(table[:,i],wave=wave,ebv=ebv,rtype='flux',**kwargs) - - #-- that's it! - return mu,wave,table - - + table = flux_grid(np.log10(teff), logg) + 0. + ff.close() + # -- velocity shift if necessary + if vrad is not None and vrad != 0: + cc = constants.cc / 1000. # speed of light in km/s + for i in range(len(mu)): + flux_shift = tools.doppler_shift(wave, vrad, flux=table[:, i]) + table[:, i] = flux_shift - 5. * vrad / cc * table[:, i] + # -- redden if necessary + if ebv is not None and ebv > 0: + for i in range(len(mu)): + table[:, i] = reddening.redden( + table[:, i], wave=wave, ebv=ebv, rtype='flux', **kwargs) + # -- that's it! + return mu, wave, table -def get_itable2(teff=None,logg=None,theta=None,mu=1,photbands=None,absolute=False,**kwargs): +def get_itable(teff=None, logg=None, theta=None, mu=1, photbands=None, + absolute=False, **kwargs): """ mu=1 is center of disk """ if theta is not None: mu = np.cos(theta) - + try: - out = get_ld_grid(photbands,integrated=True,**kwargs)(teff,logg) + out = get_ld_grid(photbands, integrated=True, **kwargs)(teff, logg) except ValueError: - print 'Used teff and logg',teff,logg + print('Used teff and logg', teff, logg) raise - a1x_,a2x_,a3x_,a4x_, I_x1 = out.reshape((len(photbands),5)).T - Imu = ld_eval(mu,[a1x_,a2x_,a3x_,a4x_]) + a1x_, a2x_, a3x_, a4x_, I_x1 = out.reshape((len(photbands), 5)).T + Imu = ld_eval(mu, [a1x_, a2x_, a3x_, a4x_]) if absolute: - return Imu*I_x1 + return Imu * I_x1 else: return Imu + @memoized -def _get_itable_markers(photband,gridfile, - teffrange=(-np.inf,np.inf),loggrange=(-np.inf,np.inf)): +def _get_itable_markers(photband, gridfile, teffrange=(-np.inf, np.inf), + loggrange=(-np.inf, np.inf)): """ Get a list of markers to more easily retrieve integrated fluxes. """ clear_memoization() - + ff = pf.open(gridfile) ext = ff[photband] columns = ext.columns.names - names = ['teff','logg'] + names = ['teff', 'logg'] grid = [ext.data.field('teff'), ext.data.field('logg')] if 'ebv' in columns: @@ -487,49 +491,49 @@ def _get_itable_markers(photband,gridfile, if 'vrad' in columns: names.append('vrad') grid.append(ext.data.field('vrad')) - + grid_axes = [np.sort(list(set(i))) for i in grid] - - #-- we construct an array representing the teff-logg-ebv content, but - # in one number: 50000400 means: + + # -- we construct an array representing the teff-logg-ebv content, but + # in one number: 50000400 means: # T=50000,logg=4.0 N = len(grid[0]) - markers = np.zeros(N,float) - gridpnts = np.zeros((N,len(grid))) - pars = np.zeros((N,5)) - - for i,entries in enumerate(zip(*grid)): - markers[i] = encode(**{key:entry for key,entry in zip(names,entries)}) - gridpnts[i]= entries + markers = np.zeros(N, float) + gridpnts = np.zeros((N, len(grid))) + pars = np.zeros((N, 5)) + + for i, entries in enumerate(zip(*grid)): + markers[i] = encode( + **{key: entry for key, entry in zip(names, entries)}) + gridpnts[i] = entries pars[i] = list(ext.data[i][-5:]) ff.close() sa = np.argsort(markers) - print 'read in gridfile',gridfile - pars[:,-1] = np.log10(pars[:,-1]) - return markers[sa],grid_axes,gridpnts[sa],pars[sa] - - + print('read in gridfile', gridfile) + pars[:, -1] = np.log10(pars[:, -1]) + return markers[sa], grid_axes, gridpnts[sa], pars[sa] -def get_limbdarkening(teff=None,logg=None,ebv=None,vrad=None,z=None,photbands=None,normalised=False,**kwargs): +def get_limbdarkening(teff=None, logg=None, ebv=None, vrad=None, z=None, + photbands=None, normalised=False, **kwargs): """ Retrieve a limb darkening law for a specific star and specific bandpass. - - Possibility to include reddening via EB-V parameter. If not given, + + Possibility to include reddening via EB-V parameter. If not given, reddening will not be performed... - + You choose your own reddening function. - + See e.g. Heyrovsky et al., 2007 - + If you specify one angle (mu in radians), it will take the closest match from the grid. - + Mu = cos(theta) where theta is the angle between the surface and the line of sight. mu=1 means theta=0 means center of the star. - + Example usage: - + >>> teff,logg = 5000,4.5 >>> mu,intensities = get_limbdarkening(teff=teff,logg=logg,photbands=['JOHNSON.V']) @@ -550,26 +554,29 @@ def get_limbdarkening(teff=None,logg=None,ebv=None,vrad=None,z=None,photbands=No """ if z is None: z = defaults['z'] - #-- retrieve model atmosphere for a given teff and logg - mus,wave,table = get_table(teff=teff,logg=logg,ebv=ebv,vrad=vrad,z=z,**kwargs) - #-- compute intensity over the stellar disk, and normalise - intensities = np.zeros((len(mus),len(photbands))) + # -- retrieve model atmosphere for a given teff and logg + mus, wave, table = get_table(teff=teff, logg=logg, ebv=ebv, vrad=vrad, + z=z, **kwargs) + # -- compute intensity over the stellar disk, and normalise + intensities = np.zeros((len(mus), len(photbands))) for i in range(len(mus)): - intensities[i] = model.synthetic_flux(wave,table[:,i],photbands) + intensities[i] = model.synthetic_flux(wave, table[:, i], photbands) if normalised: - intensities/= intensities.max(axis=0) - #-- or compute the intensity only for one angle: + intensities /= intensities.max(axis=0) + # -- or compute the intensity only for one angle: logger.info('Calculated LD') - return mus,intensities + return mus, intensities + -def get_coeffs(teff,logg,photband,ebv=None,vrad=None,law='claret',fitmethod='equidist_r_leastsq'): +def get_coeffs(teff, logg, photband, ebv=None, vrad=None, law='claret', + fitmethod='equidist_r_leastsq'): """ Retrieve limb darkening coefficients on the fly. - + This is particularly useful if you only need a few and speed is not really a problem (although this function is nearly instantaneous, looping over it will expose that it's actually pretty slow). - + @keyword teff: effective temperature @type teff: float @keyword logg: logarithmic gravity (cgs) @@ -581,33 +588,34 @@ def get_coeffs(teff,logg,photband,ebv=None,vrad=None,law='claret',fitmethod='equ @keyword vrad: radial velocity (+ is redshift, - is blueshift) @type vrad: float """ - mu,intensities = get_limbdarkening(teff=teff,logg=logg,ebv=ebv,vrad=vrad,photbands=[photband]) + mu, intensities = get_limbdarkening(teff=teff, logg=logg, ebv=ebv, + vrad=vrad, photbands=[photband]) norm_factor = intensities.max(axis=0) - coeffs,ssr,idiff = fit_law(mu,intensities[:,0]/norm_factor,law=law,fitmethod=fitmethod) - return coeffs,norm_factor,ssr,idiff - - + coeffs, ssr, idiff = fit_law(mu, intensities[:, 0] / norm_factor, law=law, + fitmethod=fitmethod) + return coeffs, norm_factor, ssr, idiff def get_ld_grid_dimensions(**kwargs): """ Returns the gridpoints of the limbdarkening grid (not unique values). """ - #-- get the FITS-file containing the tables + # -- get the FITS-file containing the tables gridfile = get_file(**kwargs) - #-- the teff and logg range is the same for every extension, so just + # -- the teff and logg range is the same for every extension, so just # take the first one ff = pf.open(gridfile) - teff_,logg_ = ff[1].data.field('Teff'),ff[1].data.field('logg') + teff_, logg_ = ff[1].data.field('Teff'), ff[1].data.field('logg') ff.close() - return teff_,logg_ + return teff_, logg_ + -def intensity_moment(coeffs,ll=0,**kwargs): +def intensity_moment(coeffs, ll=0, **kwargs): """ Calculate the intensity moment (see Townsend 2003, eq. 39). - + Test analytical versus numerical implementation: - + #>>> photband = 'JOHNSON.V' #>>> l,logg = 2.,4.0 #>>> gridfile = 'tables/ikurucz93_z0.0_k2_ld.fits' @@ -617,7 +625,7 @@ def intensity_moment(coeffs,ll=0,**kwargs): #>>> for teff in np.linspace(9000,12000,19): #... a1x,a2x,a3x,a4x,I_x1 = get_itable(gridfile,teff=teff,logg=logg,mu=1,photband=photband,evaluate=False) #... coeffs = [a1x,a2x,a3x,a4x,I_x1] - #... Ix_mu = ld_claret(mu,coeffs) + #... Ix_mu = ld_claret(mu, coeffs) #... Ilx1 = I_x1*np.trapz(P_l*mu*Ix_mu,x=mu) #... a0x = 1 - a1x - a2x - a3x - a4x #... limb_coeffs = [a0x,a1x,a2x,a3x,a4x] @@ -632,8 +640,8 @@ def intensity_moment(coeffs,ll=0,**kwargs): #... check.append(abs(Ilx1-Ilx3)<0.1) #>>> np.all(np.array(check)) #True - - + + @parameter teff: effecitve temperature (K) @type teff: float @parameter logg: log of surface gravity (cgs) @@ -647,261 +655,287 @@ def intensity_moment(coeffs,ll=0,**kwargs): @type full_output: boolean @return: intensity moment """ - full_output = kwargs.get('full_output',False) - #-- notation of Townsend 2002: coeffs in code are hat coeffs in the paper + full_output = kwargs.get('full_output', False) + # -- notation of Townsend 2002: coeffs in code are hat coeffs in the paper # (for i=1..4 they are the same) - #-- get the LD coefficients at the given temperature and logg - a1x_,a2x_,a3x_,a4x_, I_x1 = coeffs + # -- get the LD coefficients at the given temperature and logg + a1x_, a2x_, a3x_, a4x_, I_x1 = coeffs a0x_ = 1 - a1x_ - a2x_ - a3x_ - a4x_ - limb_coeffs = np.array([a0x_,a1x_,a2x_,a3x_,a4x_]) - - #-- compute intensity moment via helper coefficients - int_moms = np.array([_I_ls(ll,1 + r/2.) for r in range(0,5,1)]) - #int_moms = np.outer(int_moms,np.ones(1)) + limb_coeffs = np.array([a0x_, a1x_, a2x_, a3x_, a4x_]) + + # -- compute intensity moment via helper coefficients + int_moms = np.array([_I_ls(ll, 1 + r/2.) for r in range(0, 5, 1)]) + # int_moms = np.outer(int_moms,np.ones(1)) I_lx = I_x1 * (limb_coeffs * int_moms).sum(axis=0) - #-- return value depends on keyword + # -- return value depends on keyword if full_output: - return I_x1,limb_coeffs,int_moms - else: + return I_x1, limb_coeffs, int_moms + else: return I_lx +# } +# { Limbdarkening laws -#} -#{ Limbdarkening laws - -def ld_eval(mu,coeffs): +def ld_eval(mu, coeffs): """ Evaluate Claret's limb darkening law. """ - return ld_claret(mu,coeffs) - -def ld_claret(mu,coeffs): + return ld_claret(mu, coeffs) + + +def ld_claret(mu, coeffs): """ Claret's limb darkening law. """ - a1,a2,a3,a4 = coeffs - Imu = 1-a1*(1-mu**0.5)-a2*(1-mu)-a3*(1-mu**1.5)-a4*(1-mu**2.) + a1, a2, a3, a4 = coeffs + Imu = 1-a1*(1-mu**0.5)-a2*(1-mu)-a3*(1-mu**1.5)-a4*(1-mu**2.) return Imu - -def ld_linear(mu,coeffs): + + +def ld_linear(mu, coeffs): """ Linear or linear cosine law """ return 1-coeffs[0]*(1-mu) - -def ld_nonlinear(mu,coeffs): + + +def ld_nonlinear(mu, coeffs): """ Nonlinear or logarithmic law """ return 1-coeffs[0]*(1-mu)-coeffs[1]*mu*np.log(mu) -def ld_logarithmic(mu,coeffs): + +def ld_logarithmic(mu, coeffs): """ Nonlinear or logarithmic law """ - return ld_nonlinear(mu,coeffs) + return ld_nonlinear(mu, coeffs) -def ld_quadratic(mu,coeffs): + +def ld_quadratic(mu, coeffs): """ Quadratic law """ return 1-coeffs[0]*(1-mu)-coeffs[1]*(1-mu)**2.0 -def ld_uniform(mu,coeffs): + +def ld_uniform(mu, coeffs): """ Uniform law. """ - return 1. + return 1. -def ld_power(mu,coeffs): + +def ld_power(mu, coeffs): """ Power law. """ return mu**coeffs[0] - -#} -#{ Fitting routines (thanks to Steven Bloemen) +# } + +# { Fitting routines (thanks to Steven Bloemen) -def fit_law(mu,Imu,law='claret',fitmethod='equidist_r_leastsq'): + +def fit_law(mu, Imu, law='claret', fitmethod='equidist_r_leastsq'): """ Fit an LD law to a sampled set of limb angles/intensities. - + In my (Pieter) experience, C{fitmethod='equidist_r_leastsq' seems appropriate for the Kurucz models. - + Make sure the intensities are normalised! - + >>> mu,intensities = get_limbdarkening(teff=10000,logg=4.0,photbands=['JOHNSON.V'],normalised=True) >>> p = pl.figure() >>> p = pl.plot(mu,intensities[:,0],'ko-') >>> cl,ssr,rdiff = fit_law(mu,intensities[:,0],law='claret') - + >>> mus = np.linspace(0,1,1000) >>> Imus = ld_claret(mus,cl) >>> p = pl.plot(mus,Imus,'r-',lw=2) - - - + + + @return: coefficients, sum of squared residuals,relative flux difference between prediction and model integrated intensity @rtype: array, float, float """ - #-- prepare array for coefficients and set the initial guess - Ncoeffs = dict(claret=4,linear=1,nonlinear=2,logarithmic=2,quadratic=2, + # -- prepare array for coefficients and set the initial guess + Ncoeffs = dict(claret=4, linear=1, nonlinear=2, logarithmic=2, quadratic=2, power=1) + c0 = np.zeros(Ncoeffs[law]) c0[0] = 0.6 - #-- do the fitting - if fitmethod=='leastsq': - (csol, ierr) = leastsq(ldres_leastsq, c0, args=(mu,Imu,law)) - elif fitmethod=='fmin': - csol = fmin(ldres_fmin, c0, maxiter=1000, maxfun=2000,args=(mu,Imu,law),disp=0) - elif fitmethod=='equidist_mu_leastsq': + # -- do the fitting + if fitmethod == 'leastsq': + (csol, ierr) = leastsq(ldres_leastsq, c0, args=(mu, Imu, law)) + elif fitmethod == 'fmin': + csol = fmin(ldres_fmin, c0, maxiter=1000, maxfun=2000, + args=(mu, Imu, law), disp=0) + elif fitmethod == 'equidist_mu_leastsq': mu_order = np.argsort(mu) - tck = splrep(mu[mu_order],Imu[mu_order],s=0.0, k=2) - mu_spl = np.linspace(mu[mu_order][0],1,5000) - Imu_spl = splev(mu_spl,tck,der=0) - (csol, ierr) = leastsq(ldres_leastsq, c0, args=(mu_spl,Imu_spl,law)) - elif fitmethod=='equidist_r_leastsq': + tck = splrep(mu[mu_order], Imu[mu_order], s=0.0, k=2) + mu_spl = np.linspace(mu[mu_order][0], 1, 5000) + Imu_spl = splev(mu_spl, tck, der=0) + (csol, ierr) = leastsq(ldres_leastsq, c0, args=(mu_spl, Imu_spl, law)) + elif fitmethod == 'equidist_r_leastsq': mu_order = np.argsort(mu) - tck = splrep(mu[mu_order],Imu[mu_order],s=0., k=2) - r_spl = np.linspace(mu[mu_order][0],1,5000) + tck = splrep(mu[mu_order], Imu[mu_order], s=0., k=2) + r_spl = np.linspace(mu[mu_order][0], 1, 5000) mu_spl = np.sqrt(1-r_spl**2) - Imu_spl = splev(mu_spl,tck,der=0) - (csol,ierr) = leastsq(ldres_leastsq, c0, args=(mu_spl,Imu_spl,law)) - elif fitmethod=='equidist_mu_fmin': + Imu_spl = splev(mu_spl, tck, der=0) + (csol, ierr) = leastsq(ldres_leastsq, c0, args=(mu_spl, Imu_spl, law)) + elif fitmethod == 'equidist_mu_fmin': mu_order = np.argsort(mu) - tck = splrep(mu[mu_order],Imu[mu_order],k=2, s=0.0) - mu_spl = np.linspace(mu[mu_order][0],1,5000) - Imu_spl = splev(mu_spl,tck,der=0) - csol = fmin(ldres_fmin, c0, maxiter=1000, maxfun=2000,args=(mu_spl,Imu_spl,law),disp=0) - elif fitmethod=='equidist_r_fmin': + tck = splrep(mu[mu_order], Imu[mu_order], k=2, s=0.0) + mu_spl = np.linspace(mu[mu_order][0], 1, 5000) + Imu_spl = splev(mu_spl, tck, der=0) + csol = fmin(ldres_fmin, c0, maxiter=1000, maxfun=2000, + args=(mu_spl, Imu_spl, law), disp=0) + elif fitmethod == 'equidist_r_fmin': mu_order = np.argsort(mu) - tck = splrep(mu[mu_order],Imu[mu_order],k=2, s=0.0) - r_spl = np.linspace(mu[mu_order][0],1,5000) + tck = splrep(mu[mu_order], Imu[mu_order], k=2, s=0.0) + r_spl = np.linspace(mu[mu_order][0], 1, 5000) mu_spl = np.sqrt(1-r_spl**2) - Imu_spl = splev(mu_spl,tck,der=0) - csol = fmin(ldres_fmin, c0, maxiter=1000, maxfun=2000,args=(mu_spl,Imu_spl,law),disp=0) + Imu_spl = splev(mu_spl, tck, der=0) + csol = fmin(ldres_fmin, c0, maxiter=1000, maxfun=2000, + args=(mu_spl, Imu_spl, law), disp=0) else: raise ValueError("Fitmethod {} not recognised".format(fitmethod)) - myfit = globals()['ld_%s'%(law)](mu,csol) - res = np.sum(Imu - myfit)**2 - int1,int2 = np.trapz(Imu,x=mu),np.trapz(myfit,x=mu) + myfit = globals()['ld_%s' % (law)](mu, csol) + res = np.sum(Imu - myfit)**2 + int1, int2 = np.trapz(Imu, x=mu), np.trapz(myfit, x=mu) dflux = (int1-int2)/int1 - return csol,res,dflux - - -def fit_law_to_grid(photband,vrads=[0],ebvs=[0],zs=[0], - law='claret',fitmethod='equidist_r_leastsq',**kwargs): + return csol, res, dflux + + +def fit_law_to_grid(photband, vrads=[0], ebvs=[0], zs=[0], law='claret', + fitmethod='equidist_r_leastsq', **kwargs): """ + Gets the grid and fits LD law to all the models. - + Does not use mu=0 point in the fitting process. """ teffs, loggs = get_grid_dimensions(**kwargs) - teffs=teffs - loggs=loggs + teffs = teffs + loggs = loggs grid_pars = [] grid_coeffs = [] Imu1s = [] logger.info('Fitting photband {}'.format(photband)) for teff_, logg_ in zip(teffs, loggs): - for ebv_,vrad_,z_ in itertools.product(ebvs,vrads,zs): - logger.info('teff={}, logg={}, ebv={}, vrad={}, z={}'.format(teff_, logg_,ebv_,vrad_,z_)) - mu, Imu = get_limbdarkening(teff=teff_, logg=logg_, ebv=ebv_,vrad=vrad_,z=z_,photbands=[photband],**kwargs) + for ebv_, vrad_, z_ in itertools.product(ebvs, vrads, zs): + logger.info('teff={}, logg={}, ebv={}, vrad={}, z={}'.format( + teff_, logg_, ebv_, vrad_, z_)) + mu, Imu = get_limbdarkening(teff=teff_, logg=logg_, ebv=ebv_, + vrad=vrad_, z=z_, photbands=[photband], + **kwargs) Imu1 = Imu.max() - Imu = Imu[:,0]/Imu1 - coeffs,res,dflux = fit_law(mu[mu>0], Imu[mu>0],law=law,fitmethod=fitmethod) + Imu = Imu[:, 0] / Imu1 + coeffs, res, dflux = fit_law(mu[mu > 0], Imu[mu > 0], law=law, + fitmethod=fitmethod) grid_coeffs.append(coeffs) - grid_pars.append([teff_,logg_,ebv_,vrad_,z_]) - Imu1s.append([Imu1,res,dflux]) - #- wrap up results in nice arrays + grid_pars.append([teff_, logg_, ebv_, vrad_, z_]) + Imu1s.append([Imu1, res, dflux]) + # - wrap up results in nice arrays grid_pars = np.array(grid_pars) grid_coeffs = np.array(grid_coeffs) Imu1s = np.array(Imu1s) - + return grid_pars, grid_coeffs, Imu1s - -def generate_grid(photbands,vrads=[0],ebvs=[0],zs=[0], - law='claret',fitmethod='equidist_r_leastsq',outfile='mygrid.fits',**kwargs): - + + +def generate_grid(photbands, vrads=[0], ebvs=[0], zs=[0], law='claret', + fitmethod='equidist_r_leastsq', outfile='mygrid.fits', + **kwargs): + if os.path.isfile(outfile): - hdulist = pf.open(outfile,mode='update') + hdulist = pf.open(outfile, mode='update') existing_bands = [ext.header['extname'] for ext in hdulist[1:]] else: hdulist = pf.HDUList([]) - hdulist.append(pf.PrimaryHDU(np.array([[0,0]]))) + hdulist.append(pf.PrimaryHDU(np.array([[0, 0]]))) existing_bands = [] hd = hdulist[0].header hd.update('FIT', fitmethod, 'FIT ROUTINE') hd.update('LAW', law, 'FITTED LD LAW') - hd.update('GRID', kwargs.get('grid',defaults['grid']), 'GRID') - + hd.update('GRID', kwargs.get('grid', defaults['grid']), 'GRID') + for photband in photbands: if photband in existing_bands: logger.info('BAND {} already exists: skipping'.format(photband)) continue - pars,coeffs,Imu1s = fit_law_to_grid(photband,vrads=vrads,ebvs=ebvs,zs=zs,**kwargs) + pars, coeffs, Imu1s = fit_law_to_grid(photband, vrads=vrads, ebvs=ebvs, + zs=zs, **kwargs) cols = [] - cols.append(pf.Column(name='Teff', format='E', array=pars[:,0])) - cols.append(pf.Column(name="logg", format='E', array=pars[:,1])) - cols.append(pf.Column(name="ebv" , format='E', array=pars[:,2])) - cols.append(pf.Column(name="vrad", format='E', array=pars[:,3])) - cols.append(pf.Column(name="z" , format='E', array=pars[:,4])) + cols.append(pf.Column(name='Teff', format='E', array=pars[:, 0])) + cols.append(pf.Column(name="logg", format='E', array=pars[:, 1])) + cols.append(pf.Column(name="ebv", format='E', array=pars[:, 2])) + cols.append(pf.Column(name="vrad", format='E', array=pars[:, 3])) + cols.append(pf.Column(name="z", format='E', array=pars[:, 4])) for col in range(coeffs.shape[1]): - cols.append(pf.Column(name='a{:d}'.format(col+1), format='E', array=coeffs[:,col])) - cols.append(pf.Column(name='Imu1', format='E', array=Imu1s[:,0])) - cols.append(pf.Column(name='SRS', format='E', array=Imu1s[:,1])) - cols.append(pf.Column(name='dint', format='E', array=Imu1s[:,2])) + cols.append(pf.Column(name='a{:d}'.format(col+1), format='E', + array=coeffs[:, col])) + cols.append(pf.Column(name='Imu1', format='E', array=Imu1s[:, 0])) + cols.append(pf.Column(name='SRS', format='E', array=Imu1s[:, 1])) + cols.append(pf.Column(name='dint', format='E', array=Imu1s[:, 2])) newtable = pf.new_table(pf.ColDefs(cols)) newtable.header.update('EXTNAME', photband, "SYSTEM.FILTER") - newtable.header.update('SYSTEM', photband.split('.')[0], 'PASSBAND SYSTEM') - newtable.header.update('FILTER', photband.split('.')[1], 'PASSBAND FILTER') + newtable.header.update('SYSTEM', photband.split('.')[0], + 'PASSBAND SYSTEM') + newtable.header.update('FILTER', photband.split('.')[1], + 'PASSBAND FILTER') hdulist.append(newtable) - + if os.path.isfile(outfile): hdulist.close() else: hdulist.writeto(outfile) -#} +# } -#{ Internal functions +# { Internal functions def _r(mu): """ Convert mu to r coordinates """ return np.sqrt(1.-mu**2.) - + + def _mu(r_): """ Convert r to mu coordinates """ return np.sqrt(1.-r_**2.) - + + def ldres_fmin(coeffs, mu, Imu, law): """ Residual function for Nelder-Mead optimizer. """ - return sum((Imu - globals()['ld_%s'%(law)](mu,coeffs))**2) - + return sum((Imu - globals()['ld_%s' % (law)](mu, coeffs))**2) + + def ldres_leastsq(coeffs, mu, Imu, law): """ Residual function for Levenberg-Marquardt optimizer. """ - return Imu - globals()['ld_%s'%(law)](mu,coeffs) + return Imu - globals()['ld_%s' % (law)](mu, coeffs) + -def _I_ls(ll,ss): +def _I_ls(ll, ss): """ Limb darkening moments (Dziembowski 1977, Townsend 2002) - + Recursive implementation. - + >>> _I_ls(0,0.5) 0.6666666666666666 >>> _I_ls(1,0.5) @@ -909,20 +943,21 @@ def _I_ls(ll,ss): >>> _I_ls(2,0.5) 0.09523809523809523 """ - if ll==0: + if ll == 0: return 1./(1.+ss) - elif ll==1: + elif ll == 1: return 1./(2.+ss) else: - return (ss-ll+2)/(ss+ll-2+3.)*_I_ls(ll-2,ss) + return (ss-ll+2)/(ss+ll-2+3.)*_I_ls(ll-2, ss) + @memoized -def get_ld_grid(photband,**kwargs): +def get_ld_grid(photband, **kwargs): """ Retrieve an interpolating grid for the LD coefficients - + Check outcome: - + #>>> bands = ['GENEVA.U', 'GENEVA.B', 'GENEVA.G', 'GENEVA.V'] #>>> f_ld_grid = get_ld_grid(bands) #>>> ff = pf.open(_atmos['file']) @@ -931,9 +966,9 @@ def get_ld_grid(photband,**kwargs): #>>> all(ff['GENEVA.G'].data[257][2:]==f_ld_grid(ff['GENEVA.G'].data[257][0],ff['GENEVA.G'].data[257][1])[10:15]) #True #>>> ff.close() - + #Make some plots: - + #>>> photband = ['GENEVA.V'] #>>> f_ld = get_ld_grid(photband) #>>> logg = 4.0 @@ -950,46 +985,53 @@ def get_ld_grid(photband,**kwargs): #... p = subplot(223);p = title('Without absolute intensity') #... p = plot(mu,ld_eval(mu,[a1x,a2x,a3x,a4x]),'-') #... p = subplot(224);p = title('With absolute intensity') - #... p = plot(mu,I_x1*ld_eval(mu,[a1x,a2x,a3x,a4x]),'-') - + #... p = plot(mu,I_x1*ld_eval(mu,[a1x,a2x,a3x,a4x]),'-') + """ - #-- retrieve the grid points (unique values) - teffs,loggs = get_ld_grid_dimensions(**kwargs) - teffs_grid = np.sort(np.unique1d(teffs)) - loggs_grid = np.sort(np.unique1d(loggs)) - coeff_grid = np.zeros((len(teffs_grid),len(loggs_grid),5*len(photband))) - - #-- get the FITS-file containing the tables + # -- retrieve the grid points (unique values) + teffs, loggs = get_ld_grid_dimensions(**kwargs) + teffs_grid = np.sort(np.unique(teffs)) + loggs_grid = np.sort(np.unique(loggs)) + coeff_grid = np.zeros((len(teffs_grid), len(loggs_grid), 5*len(photband))) + + # -- get the FITS-file containing the tables gridfile = get_file(**kwargs) - #-- fill the grid + # -- fill the grid ff = pf.open(gridfile) - for pp,iband in enumerate(photband): + for pp, iband in enumerate(photband): teffs = ff[iband].data.field('Teff') loggs = ff[iband].data.field('logg') - for ii,(iteff,ilogg) in enumerate(zip(teffs,loggs)): - indext = np.searchsorted(teffs_grid,iteff) - indexg = np.searchsorted(loggs_grid,ilogg) - #-- array and list are added for backwards compatibility with some + for ii, (iteff, ilogg) in enumerate(zip(teffs, loggs)): + indext = np.searchsorted(teffs_grid, iteff) + indexg = np.searchsorted(loggs_grid, ilogg) + # -- array and list are added for backwards compatibility with some # pyfits versions - coeff_grid[indext,indexg,5*pp:5*(pp+1)] = np.array(list(ff[iband].data[ii]))[2:] + coeff_grid[indext, indexg, 5*pp:5*(pp+1)] = np.array( + list(ff[iband].data[ii]))[2:] ff.close() - #-- make an interpolating function - f_ld_grid = InterpolatingFunction([teffs_grid,loggs_grid],coeff_grid) + # -- make an interpolating function + f_ld_grid = InterpolatingFunction([teffs_grid, loggs_grid], coeff_grid) + # TODO: replace InterpolatingFunction + # print('ffffffffffffff') + # print(np.size(np.array([teffs_grid,loggs_grid]).T, size(coeff_grid))) + # f_ld_grid = LinearNDInterpolator(np.array([teffs_grid,loggs_grid]).T,coeff_grid) + return f_ld_grid - + @memoized -def _get_itable_markers(photband,gridfile, - teffrange=(-np.inf,np.inf),loggrange=(-np.inf,np.inf)): +def _get_itable_markers(photband, gridfile, teffrange=(-np.inf, np.inf), + loggrange=(-np.inf, np.inf)): """ + Get a list of markers to more easily retrieve integrated fluxes. """ clear_memoization() - + ff = pf.open(gridfile) ext = ff[photband] columns = ext.columns.names - names = ['teff','logg'] + names = ['teff', 'logg'] grid = [ext.data.field('teff'), ext.data.field('logg')] if 'ebv' in columns: @@ -998,31 +1040,32 @@ def _get_itable_markers(photband,gridfile, if 'vrad' in columns: names.append('vrad') grid.append(ext.data.field('vrad')) - + grid_axes = [np.sort(list(set(i))) for i in grid] - - #-- we construct an array representing the teff-logg-ebv content, but - # in one number: 50000400 means: + + # -- we construct an array representing the teff-logg-ebv content, but + # in one number: 50000400 means: # T=50000,logg=4.0 N = len(grid[0]) - markers = np.zeros(N,float) - gridpnts = np.zeros((N,len(grid))) - pars = np.zeros((N,5)) - - for i,entries in enumerate(zip(*grid)): - markers[i] = encode(**{key:entry for key,entry in zip(names,entries)}) - gridpnts[i]= entries + markers = np.zeros(N, float) + gridpnts = np.zeros((N, len(grid))) + pars = np.zeros((N, 5)) + + for i, entries in enumerate(zip(*grid)): + markers[i] = encode( + **{key: entry for key, entry in zip(names, entries)}) + gridpnts[i] = entries pars[i] = list(ext.data[i][-5:]) ff.close() sa = np.argsort(markers) - print 'read in gridfile',gridfile - pars[:,-1] = np.log10(pars[:,-1]) - return markers[sa],grid_axes,gridpnts[sa],pars[sa] + print('read in gridfile', gridfile) + pars[:, -1] = np.log10(pars[:, -1]) + return markers[sa], grid_axes, gridpnts[sa], pars[sa] + +# } -#} - -if __name__=="__main__": +if __name__ == "__main__": import sys if not sys.argv[1:]: import pylab as pl @@ -1030,12 +1073,15 @@ def _get_itable_markers(photband,gridfile, doctest.testmod() pl.show() else: - #from ivs.aux import argkwargparser - #method,args,kwargs = argkwargparser.parse() - #print("Calling {} with args=({}) and kwargs=({})".format(method,args,kwargs) - photbands = ['JOHNSON.U','JOHNSON.B','JOHNSON.V','KEPLER.V','COROT.SIS','COROT.EXO'] - photbands+= ['2MASS.J','2MASS.H','2MASS.KS'] - #generate_grid(photbands,vrads=np.arange(-500,501,50),ebvs=np.arange(0,2.005,0.01),law='claret',outfile='claret.fits') - #generate_grid(photbands,vrads=np.arange(-500,501,50),ebvs=np.arange(0,2.005,0.01),law='linear',outfile='linear.fits') - generate_grid(['MOST.V','IRAC.36','COROT.EXO'],vrads=[0],ebvs=[0.06],zs=[0,0], - law='claret',fitmethod='equidist_r_leastsq',outfile='HD261903.fits') + # from ivs.aux import argkwargparser + # method,args,kwargs = argkwargparser.parse() + # print("Calling {} with args=({}) and kwargs=({})".format(method,args, + # kwargs) + photbands = ['JOHNSON.U', 'JOHNSON.B', 'JOHNSON.V', 'KEPLER.V', + 'COROT.SIS', 'COROT.EXO'] + photbands += ['2MASS.J', '2MASS.H', '2MASS.KS'] + # generate_grid(photbands,vrads=np.arange(-500,501,50),ebvs=np.arange(0,2.005,0.01),law='claret',outfile='claret.fits') + # generate_grid(photbands,vrads=np.arange(-500,501,50),ebvs=np.arange(0,2.005,0.01),law='linear',outfile='linear.fits') + generate_grid(['MOST.V', 'IRAC.36', 'COROT.EXO'], vrads=[0], + ebvs=[0.06], zs=[0, 0], law='claret', + fitmethod='equidist_r_leastsq', outfile='HD261903.fits') diff --git a/sed/list_photsystems.dat b/sed/list_photsystems.dat new file mode 100644 index 000000000..0fc73262f --- /dev/null +++ b/sed/list_photsystems.dat @@ -0,0 +1,71 @@ +2MASS +ACSHRC +ACSSBC +ACSWFC +AKARI +ALMA +ANS +APEX +ARGUE +BESSEL +BESSELL +BRITE +CAMELOT +CAMELOT-BR +CAMELOT-JOHNSON +CAMELOT-SDSS +CAMELOT-STROMGREN +COROT +COUSINS +DDO +DENIS +DIRBE +EEV4280 +ESOIR +GAIA +GALEX +GENEVA +HIPPARCOS +IPHAS +IRAC +IRAS +ISOCAM +JOHNSON +KEPLER +KRON +LANDOLT +MAIA +MIPS +MOST +MSX +NARROW +NICMOS +OAO2 +OPEN +PACS +PLAVI +SAAO +SCUBA +SDSS +SLOAN +SPIRE +STEBBINS +STISCCD +STISFUV +STISNUV +STROMGREN +SUPRIMECAM +TD1 +TYCHO +TYCHO2 +ULTRACAM +USNOB1 +UVEX +VILNIUS +VISIR +WALRAVEN +WFCAM +WFPC2 +WISE +WIYN09HARRIS +WOOD diff --git a/sed/list_photsystems_sorted.dat b/sed/list_photsystems_sorted.dat new file mode 100644 index 000000000..5836fa826 --- /dev/null +++ b/sed/list_photsystems_sorted.dat @@ -0,0 +1,71 @@ +JOHNSON +2MASS +WISE +STROMGREN +SPIRE +GENEVA +AKARI +IRAS +TYCHO2 +SAAO +VILNIUS +MSX +COUSINS +DIRBE +HIPPARCOS +USNOB1 +ESOIR +SDSS +TD1 +GALEX +ANS +SCUBA +VISIR +IRAC +DENIS +APEX +PACS +MIPS +GAIA +ALMA +MAIA +KEPLER +ULTRACAM +MOST +COROT +IPHAS +DDO +BRITE +ACSHRC +ACSSBC +ACSWFC +ARGUE +BESSEL +BESSELL +CAMELOT +CAMELOT-BR +CAMELOT-JOHNSON +CAMELOT-SDSS +CAMELOT-STROMGREN +EEV4280 +ISOCAM +KRON +LANDOLT +NARROW +NICMOS +OAO2 +OPEN +PLAVI +SLOAN +STEBBINS +STISCCD +STISFUV +STISNUV +SUPRIMECAM +TYCHO +UVEX +WALRAVEN +WFCAM +WFPC2 +WIYN09HARRIS +WOOD diff --git a/sed/model.py b/sed/model.py index 3bbbf639a..e379010db 100644 --- a/sed/model.py +++ b/sed/model.py @@ -15,7 +15,7 @@ Section 1.1 Available grids --------------------------- - + - kurucz: The Kurucz model grids, (default setting) reference: Kurucz 1993, yCat, 6039, 0 - metallicity (z): m01 is -0.1 log metal abundance relative to solar (solar abundances from Anders and Grevesse 1989) - metallicity (z): p01 is +0.1 log metal abundance relative to solar (solar abundances from Anders and Grevesse 1989) @@ -23,12 +23,12 @@ - turbulent velocity (vturb): vturb in km/s - nover= True means no overshoot - odfnew=True means no overshoot but with better opacities and abundances - + - tmap: NLTE grids computed for sdB stars with the Tubingen NLTE Model Atmosphere package. No further parameters are available. Reference: - Werner et al. 2003, - - + Werner et al. 2003, + + Section 1.2 Plotting the domains of all spectral grids ------------------------------------------------------ @@ -40,8 +40,9 @@ Preparation of the plot: set the color cycle of the current axes to the spectral color cycle. + >>> from cycler import cycler >>> p = pl.figure(figsize=(10,8)) - >>> color_cycle = [pl.cm.spectral(j) for j in np.linspace(0, 1.0, len(grids))] + >>> color_cycle = cycler(color=[pl.cm.Spectral(j) for j in np.linspace(0, 1.0, len(grids))]) >>> p = pl.gca().set_color_cycle(color_cycle) To plot all the grid points, we run over all grid names (which are strings), and @@ -108,7 +109,7 @@ >>> copy2scratch() You have to do this every time you change a grid setting. This function creates a -directory named 'your_username' on the scratch disk and works from there. So you +directory named 'your_username' on the scratch disk and works from there. So you won`t disturbed other users. After the fitting process use the function @@ -286,12 +287,9 @@ """ import re import os -import sys import glob import logging -import copy import astropy.io.fits as pf -import time import numpy as np try: from scipy.interpolate import LinearNDInterpolator @@ -301,7 +299,6 @@ from Scientific.Functions.Interpolation import InterpolatingFunction new_scipy = False from scipy.interpolate import interp1d -from multiprocessing import Process,Manager,cpu_count from ivs import config from ivs.units import conversions @@ -312,11 +309,9 @@ import functools from ivs.aux import numpy_ext from ivs.sed import filters -from ivs.inout import ascii from ivs.inout import fits from ivs.sigproc import interpol -from ivs.catalogs import sesame -import reddening +from ivs.sed import reddening import getpass import shutil @@ -345,19 +340,19 @@ def set_defaults(*args,**kwargs): """ Set defaults of this module - + If you give no keyword arguments, the default values will be reset. """ clear_memoization(keys=['ivs.sed.model']) #-- these are the default defaults if not kwargs: kwargs = __defaults__.copy() - + for key in kwargs: if key in defaults: defaults[key] = kwargs[key] - logger.info('Set %s to %s'%(key,kwargs[key])) - + logger.info('Set %s to %s'%(key,kwargs[key])) + def set_defaults_multiple(*args): """ @@ -369,20 +364,20 @@ def set_defaults_multiple(*args): for key in arg: if key in defaults_multiple[i]: defaults_multiple[i][key] = arg[key] - logger.info('Set %s to %s (star %d)'%(key,arg[key],i)) + logger.info('Set %s to %s (star %d)'%(key,arg[key],i)) def copy2scratch(**kwargs): """ Copy the grids to the scratch directory to speed up the fitting process. Files are placed in the directory: /scratch/uname/ where uname is your username. - + This function checks the grids that are set with the functions set_defaults() and set_defaults_multiple(). Every time a grid setting is changed, this function needs to be called again. - + Don`t forget to remove the files from the scratch directory after the fitting process is completed with clean_scratch() - + It is possible to give z='*' and Rv='*' as an option; when you do that, the grids with all z, Rv values are copied. Don't forget to add that option to clean_scratch too! """ @@ -391,12 +386,12 @@ def copy2scratch(**kwargs): if not os.path.isdir('/scratch/%s/'%(uname)): os.makedirs('/scratch/%s/'%(uname)) scratchdir = '/scratch/%s/'%(uname) - + #-- we have defaults for the single and multiple grid defaults_ = [] defaults_.append(defaults) defaults_.extend(defaults_multiple) - + #-- now run over the defaults for the single and multiple grid, and # copy the necessary files to the scratch disk for default in defaults_: @@ -446,7 +441,7 @@ def clean_scratch(**kwargs): defaults_ = [] defaults_.append(defaults) defaults_.extend(defaults_multiple) - + for default in defaults_: if default['use_scratch']: originalDefaults = {} @@ -455,7 +450,7 @@ def clean_scratch(**kwargs): originalDefaults[key] = default[key] default[key] = kwargs[key] logger.debug('Using provided value for {0:s}={1:s} when deleting from scratch'.format(key,str(kwargs[key]))) - + #if z is not None: #previous_z = default['z'] #default['z'] @@ -466,14 +461,14 @@ def clean_scratch(**kwargs): if os.path.isfile(ifname): logger.info('Removed file: %s'%(ifname)) os.remove(ifname) - + fname = get_file(integrated=True,**default) if isinstance(fname,str): fname = [fname] for ifname in fname: if os.path.isfile(ifname): logger.info('Removed file: %s'%(ifname)) - os.remove(ifname) + os.remove(ifname) default['use_scratch'] = False for key in kwargs: if key in default: @@ -497,10 +492,10 @@ def defaults_multiple2str(): def get_gridnames(grid=None): """ Return a list of available grid names. - + If you specificy the grid's name, you get two lists: one with all available original, non-integrated grids, and one with the pre-calculated photometry. - + @parameter grid: name of the type of grid (optional) @type grid: string @return: list of grid names @@ -522,12 +517,12 @@ def get_gridnames(grid=None): def get_file(integrated=False,**kwargs): """ Retrieve the filename containing the specified SED grid. - + The keyword arguments are specific to the kind of grid you're using. - + Basic keywords are 'grid' for the name of the grid, and 'z' for metallicity. For other keywords, see the source code. - + Available grids and example keywords: - grid='kurucz93': - metallicity (z): m01 is -0.1 log metal abundance relative to solar (solar abundances from Anders and Grevesse 1989) @@ -549,9 +544,9 @@ def get_file(integrated=False,**kwargs): - grid='tkachenko': metallicity z - grid='nemo': convection theory and metallicity (CM=Canuto and Mazzitelli 1991), (CGM=Canuto,Goldman,Mazzitelli 1996), (MLT=mixinglengththeory a=0.5) - - grid='marcsjorissensp': high resolution spectra from 4000 to 25000 A of (online available) MARCS grid computed by A. Jorissen + - grid='marcsjorissensp': high resolution spectra from 4000 to 25000 A of (online available) MARCS grid computed by A. Jorissen with turbospectrum v12.1.1 in late 2012, then converted to the Kurucz wavelength grid (by S. Bloemen and M. Hillen). - + @param integrated: choose integrated version of the gridcopy2scratch @type integrated: boolean @keyword grid: gridname (default Kurucz) @@ -559,7 +554,7 @@ def get_file(integrated=False,**kwargs): @return: gridfile @rtype: str """ - + #-- possibly you give a filename grid = kwargs.get('grid',defaults['grid']) use_scratch = kwargs.get('use_scratch',defaults['use_scratch']) @@ -570,11 +565,13 @@ def get_file(integrated=False,**kwargs): return os.path.join(os.path.dirname(grid),basename[0]=='i' and basename or 'i'+basename) logging.debug('Returning grid path: '+grid) return grid - + grid = grid.lower() - + #-- general + z = kwargs.get('z',defaults['z']) + # z = defaults['z'] Rv = kwargs.get('Rv',defaults['Rv']) #-- only for Kurucz vturb = int(kwargs.get('vturb',defaults['vturb'])) @@ -591,7 +588,7 @@ def get_file(integrated=False,**kwargs): co= kwargs.get('co',defaults['co']) #-- only for Nemo ct = kwargs.get('ct','mlt') - + #-- figure out what grid to use if grid=='fastwind': basename = 'fastwind_sed.fits' @@ -611,9 +608,9 @@ def get_file(integrated=False,**kwargs): elif odfnew: basename = 'kurucz93_z%s_k%sodfnew_sed%s.fits'%(z,vturb,postfix) elif nover: - basename = 'kurucz93_z%s_k%snover_sed%s.fits'%(z,vturb,postfix) + basename = 'kurucz93_z%s_k%snover_sed%s.fits'%(z,vturb,postfix) elif grid=='cmfgen': - basename = 'cmfgen_sed.fits' + basename = 'cmfgen_sed.fits' elif grid=='sdb_uli': if not isinstance(z,str): z = '%.1f'%(z) if not isinstance(He,str): He = '%d'%(He) @@ -629,7 +626,7 @@ def get_file(integrated=False,**kwargs): postfix = '' basename = 'SED_WD_Koester_DA%s.fits'%(postfix) elif grid=='wd_db': - basename = 'SED_WD_Koester_DB.fits' + basename = 'SED_WD_Koester_DB.fits' elif grid=='marcs': if not isinstance(z,str): z = '%.1f'%(z) if not isinstance(t,str): t = '%.1f'%(t) @@ -638,19 +635,29 @@ def get_file(integrated=False,**kwargs): basename = 'marcsp_z%st%s_a%s_c%s_sed.fits'%(z,t,a,c) elif grid=='marcs2': basename = 'marcsp2_z0.00t2.0_m.1.0c0.00_sed.fits' + elif grid == 'marcsana': + # if not isinstance(z,str): z = '%.2f'%(z) + + if z == '*': + basename = 'MARCS_SED_Ana_z*.fits' + else: + if isinstance(z,str): + z = float(z) + basename = 'MARCS_SED_Ana_z{:+.2f}.fits'.format(z) + # basename = 'MARCS_SED_Ana_z+0.25.fits' elif grid=='comarcs': if not isinstance(z,str): z = '%.2f'%(z) if not isinstance(co,str): co = '%.2f'%(co) if not isinstance(m,str): m = '%.1f'%(m) - basename = 'comarcsp_z%sco%sm%sxi2.50_sed.fits'%(z,co,m) + basename = 'comarcsp_z%sco%sm%sxi2.50_sed.fits'%(z,co,m) elif grid=='stars': - basename = 'kurucz_stars_sed.fits' + basename = 'kurucz_stars_sed.fits' elif grid=='tlusty': if not isinstance(z,str): z = '%.2f'%(z) - basename = 'tlusty_z%s_sed.fits'%(z) + basename = 'tlusty_z%s_sed.fits'%(z) elif grid=='uvblue': if not isinstance(z,str): z = '%.1f'%(z) - basename = 'uvblue_z%s_k2_sed.fits'%(z) + basename = 'uvblue_z%s_k2_sed.fits'%(z) elif grid=='atlas12': if not isinstance(z,str): z = '%.1f'%(z) basename = 'atlas12_z%s_sed.fits'%(z) @@ -674,7 +681,7 @@ def get_file(integrated=False,**kwargs): basename = 'Heber2000_B_h909_extended.fits' #only 1 metalicity elif grid=='hebersdb': basename = 'Heber2000_sdB_h909_extended.fits' #only 1 metalicity - + elif grid=='tmapsdb': # grids for sdB star fitting (JorisV) if integrated: @@ -704,7 +711,7 @@ def get_file(integrated=False,**kwargs): else: postfix = '' basename = 'kurucz_pAGB_z%s_sed%s.fits'%(z,postfix) - + elif grid=='tmaptest': """ Grids exclusively for testing purposes""" if integrated: @@ -713,8 +720,8 @@ def get_file(integrated=False,**kwargs): postfix+= Rv else: postfix = '' - basename = 'TMAP2012_SEDtest%s.fits'%(postfix) #only available for 1 metalicity - + basename = 'TMAP2012_SEDtest%s.fits'%(postfix) #only available for 1 metalicity + elif grid=='kurucztest': """ Grids exclusively for testing purposes""" if not isinstance(z,str): z = '%.1f'%(z) @@ -724,7 +731,7 @@ def get_file(integrated=False,**kwargs): postfix+= Rv else: postfix = '' - basename = 'kurucz_SEDtest_z%s%s.fits'%(z,postfix) #only available for 1 metalicity + basename = 'kurucz_SEDtest_z%s%s.fits'%(z,postfix) #only available for 1 metalicity elif grid=='marcsjorissensp': if not isinstance(z,str): z = '%.2f'%(z) if not isinstance(a,str): a = '%.2f'%(a) @@ -751,13 +758,13 @@ def get_file(integrated=False,**kwargs): if integrated: grid = glob.glob(scratchdir+'i'+basename) else: - grid = glob.glob(scratchdir+basename) + grid = glob.glob(scratchdir+basename) else: if integrated: grid = config.glob(basedir,'i'+basename) else: - grid = config.glob(basedir,basename) - + grid = config.glob(basedir,basename) + #grid.sort() logger.debug('Returning grid path(s): %s'%(grid)) return grid @@ -766,13 +773,13 @@ def get_file(integrated=False,**kwargs): def _blackbody_input(fctn): """ Prepare input and output for blackbody-like functions. - + If the user gives wavelength units and Flambda units, we only need to convert everything to SI (and back to the desired units in the end). - + If the user gives frequency units and Fnu units, we only need to convert everything to SI ( and back to the desired units in the end). - + If the user gives wavelength units and Fnu units, we need to convert the wavelengths first to frequency. """ @@ -808,8 +815,8 @@ def dobb(x,T,**kwargs): else: to_kwargs['wave'] = (x,conversions._conventions[curr_conv]['length']) #-- run function - I = fctn((x,x_unit_type),T) - + I = fctn((x,x_unit_type),T) + #-- prepare output disc_integrated = kwargs.get('disc_integrated',True) ang_diam = kwargs.get('ang_diam',None) @@ -820,7 +827,7 @@ def dobb(x,T,**kwargs): I *= scale I = conversions.convert(curr_conv,flux_units,I,**to_kwargs) return I - + return dobb @@ -832,49 +839,49 @@ def dobb(x,T,**kwargs): def blackbody(x,T,wave_units='AA',flux_units='erg/s/cm2/AA',disc_integrated=True,ang_diam=None): """ Definition of black body curve. - + To get them into the same units as the Kurucz disc-integrated SEDs, they are multiplied by sqrt(2*pi) (set C{disc_integrated=True}). - + You can only give an angular diameter if disc_integrated is True. - + To convert the scale parameter back to mas, simply do: - + ang_diam = 2*conversions.convert('sr','mas',scale) - + See decorator L{blackbody_input} for details on how the input parameters are handled: the user is free to choose wavelength or frequency units, choose *which* wavelength or frequency units, and can even mix them. To be sure that everything is handled correctly, we need to do some preprocessing and unit conversions. - + Be careful when, e.g. during fitting, scale contains an error: be sure to set the option C{unpack=True} in the L{conversions.convert} function! - + >>> x = np.linspace(2.3595,193.872,500) >>> F1 = blackbody(x,280.,wave_units='AA',flux_units='Jy',ang_diam=(1.,'mas')) >>> F2 = rayleigh_jeans(x,280.,wave_units='micron',flux_units='Jy',ang_diam=(1.,'mas')) >>> F3 = wien(x,280.,wave_units='micron',flux_units='Jy',ang_diam=(1.,'mas')) - - + + >>> p = pl.figure() >>> p = pl.subplot(121) >>> p = pl.plot(x,F1) >>> p = pl.plot(x,F2) >>> p = pl.plot(x,F3) - - + + >>> F1 = blackbody(x,280.,wave_units='AA',flux_units='erg/s/cm2/AA',ang_diam=(1.,'mas')) >>> F2 = rayleigh_jeans(x,280.,wave_units='micron',flux_units='erg/s/cm2/AA',ang_diam=(1.,'mas')) >>> F3 = wien(x,280.,wave_units='micron',flux_units='erg/s/cm2/AA',ang_diam=(1.,'mas')) - + >>> p = pl.subplot(122) >>> p = pl.plot(x,F1) >>> p = pl.plot(x,F2) >>> p = pl.plot(x,F3) - + @param: wavelength @type: ndarray @param T: temperature, unit @@ -890,7 +897,7 @@ def blackbody(x,T,wave_units='AA',flux_units='erg/s/cm2/AA',disc_integrated=True @return: intensity @rtype: array """ - x,x_unit_type = x + x,x_unit_type = x #-- make the appropriate black body if x_unit_type=='frequency': # frequency units factor = 2.0 * constants.hh / constants.cc**2 @@ -909,15 +916,15 @@ def blackbody(x,T,wave_units='AA',flux_units='erg/s/cm2/AA',disc_integrated=True def rayleigh_jeans(x,T,wave_units='AA',flux_units='erg/s/cm2/AA',disc_integrated=True,ang_diam=None): """ Rayleigh-Jeans approximation of a black body. - + Valid at long wavelengths. - + For input details, see L{blackbody}. - + @return: intensity @rtype: array """ - x,x_unit_type = x + x,x_unit_type = x #-- now make the appropriate model if x_unit_type=='frequency': # frequency units factor = 2.0 * constants.kB*T / constants.cc**2 @@ -934,15 +941,15 @@ def rayleigh_jeans(x,T,wave_units='AA',flux_units='erg/s/cm2/AA',disc_integrated def wien(x,T,wave_units='AA',flux_units='erg/s/cm2/AA',disc_integrated=True,ang_diam=None): """ Wien approximation of a black body. - + Valid at short wavelengths. - + For input details, see L{blackbody}. - + @return: intensity @rtype: array """ - x,x_unit_type = x + x,x_unit_type = x #-- now make the appropriate model if x_unit_type=='frequency': # frequency units factor = 2.0 * constants.hh / constants.cc**2 @@ -961,23 +968,23 @@ def get_table_single(teff=None,logg=None,ebv=None,rad=None,star=None, wave_units='AA',flux_units='erg/s/cm2/AA/sr',**kwargs): """ Retrieve the spectral energy distribution of a model atmosphere. - + Wavelengths in A (angstrom) Fluxes in Ilambda = ergs/cm2/s/AA/sr, except specified via 'units', - + If you give 'units', and /sr is not included, you are responsible yourself for giving an extra keyword with the angular diameter C{ang_diam}, or other possibilities offered by the C{units.conversions.convert} function. - + Possibility to redden the fluxes according to the reddening parameter EB_V. - + Extra kwargs can specify the grid type. Extra kwargs can specify constraints on the size of the grid to interpolate. Extra kwargs can specify reddening law types. Extra kwargs can specify information for conversions. - + Example usage: - + >>> from pylab import figure,gca,subplot,title,gcf,loglog >>> p = figure(figsize=(10,6)) >>> p=gcf().canvas.set_window_title('Test of ') @@ -1005,9 +1012,9 @@ def get_table_single(teff=None,logg=None,ebv=None,rad=None,star=None, >>> p = loglog(*get_table(grid='KURUCZ',teff=11000,logg=4.0,wave_units='micron',flux_units='Jy/sr'),**dict(label='11000')) >>> p = pl.xlabel('Wavelength [micron]');p = pl.ylabel('Flux [Jy/sr]') >>> p = pl.legend(loc='upper right',prop=dict(size='small')) - + ]]include figure]]ivs_sed_model_comparison.png] - + @param teff: effective temperature @type teff: float @param logg: logarithmic gravity (cgs) @@ -1021,10 +1028,9 @@ def get_table_single(teff=None,logg=None,ebv=None,rad=None,star=None, @return: wavelength,flux @rtype: (ndarray,ndarray) """ - #-- get the FITS-file containing the tables gridfile = get_file(**kwargs) - + #-- read the file: ff = pf.open(gridfile) #-- a possible grid is the one where only selected stellar models are @@ -1053,22 +1059,22 @@ def get_table_single(teff=None,logg=None,ebv=None,rad=None,star=None, logger.debug('Model SED interpolated from grid %s (%s)'%(os.path.basename(gridfile),kwargs)) wave = wave + 0. flux = flux_grid(np.log10(teff),logg) + 0. - + #-- convert to arrays wave = np.array(wave,float) flux = np.array(flux,float) #-- redden if necessary if ebv is not None and ebv>0: - if 'wave' in kwargs.keys(): + if 'wave' in list(kwargs.keys()): removed = kwargs.pop('wave') flux = reddening.redden(flux,wave=wave,ebv=ebv,rtype='flux',**kwargs) if flux_units!='erg/s/cm2/AA/sr': flux = conversions.convert('erg/s/cm2/AA/sr',flux_units,flux,wave=(wave,'AA'),**kwargs) if wave_units!='AA': wave = conversions.convert('AA',wave_units,wave,**kwargs) - + ff.close() - + if rad != None: flux = rad**2 * flux @@ -1080,23 +1086,23 @@ def get_itable_single(teff=None,logg=None,ebv=0,z=0,rad=None,photbands=None, """ Retrieve the spectral energy distribution of a model atmosphere in photometric passbands. - + Wavelengths in A (angstrom). If you set 'wavelengths' to None, no effective wavelengths will be calculated. Otherwise, the effective wavelength is calculated taking the model flux into account. Fluxes in Ilambda = ergs/cm2/s/AA/sr, except specified via 'units', - + If you give 'units', and /sr is not included, you are responsible yourself for giving an extra keyword with the angular diameter C{ang_diam}, or other possibilities offered by the C{units.conversions.convert} function. - + Possibility to redden the fluxes according to the reddening parameter EB_V. - + Extra kwargs can specify the grid type. Extra kwargs can specify constraints on the size of the grid to interpolate. Extra kwargs can specify reddening law types. Extra kwargs can specify information for conversions. - + @param teff: effective temperature @type teff: float @param logg: logarithmic gravity (cgs) @@ -1119,7 +1125,12 @@ def get_itable_single(teff=None,logg=None,ebv=0,z=0,rad=None,photbands=None, logger.debug('vrad is NOT taken into account when interpolating in get_itable()') if 'rv' in kwargs: logger.debug('Rv is NOT taken into account when interpolating in get_itable()') - + # TODO: when skip_z is True, the default of z should be changed to the + # input default + # skip_z = False + # if 'z' in kwargs: + # skip_z = True + if photbands is None: raise ValueError('no photometric passbands given') ebvrange = kwargs.pop('ebvrange',(-np.inf,np.inf)) @@ -1129,7 +1140,12 @@ def get_itable_single(teff=None,logg=None,ebv=0,z=0,rad=None,photbands=None, #c0 = time.time() #c1 = time.time() - c0 #-- retrieve structured information on the grid (memoized) - markers,(g_teff,g_logg,g_ebv,g_z),gpnts,ext = _get_itable_markers(photbands,ebvrange=ebvrange,zrange=zrange, + # if skip_z: + # markers,(g_teff,g_logg,g_ebv),gpnts,ext = _get_itable_markers(photbands,ebvrange=ebvrange,zrange=zrange, + # include_Labs=True,clear_memory=clear_memory,**kwargs) + # else: + + markers,(g_teff,g_logg,g_ebv, g_z),gpnts,ext = _get_itable_markers(photbands,ebvrange=ebvrange,zrange=zrange, include_Labs=True,clear_memory=clear_memory,**kwargs) #c2 = time.time() - c0 - c1 #-- if we have a grid model, no need for interpolation @@ -1137,6 +1153,7 @@ def get_itable_single(teff=None,logg=None,ebv=0,z=0,rad=None,photbands=None, input_code = float('%3d%05d%03d%03d'%(int(round((z+5)*100)),int(round(teff)),int(round(logg*100)),int(round(ebv*100)))) index = markers.searchsorted(input_code) output_code = markers[index] + #-- if not available, go on and interpolate! # we raise a KeyError for symmetry with C{get_table}. if not input_code==output_code: @@ -1172,11 +1189,11 @@ def get_itable_single(teff=None,logg=None,ebv=0,z=0,rad=None,photbands=None, #-- iterates over df-1 values (df=degrees of freedom): we know that the # grid is ordered via z in the last part (about twice as fast). # Reducing the grid size to 2 increases the speed again with a factor 2. - + #-- if metallicity needs to be interpolated if not (z in g_z): fluxes = np.zeros((2,2,2,2,len(photbands)+1)) - for i,j,k in itertools.product(xrange(2),xrange(2),xrange(2)): + for i,j,k in itertools.product(range(2),range(2),range(2)): input_code = float('%3d%05d%03d%03d'%(int(round((zs_subgrid[i]+5)*100)),\ int(round(teffs_subgrid[j])),\ int(round(loggs_subgrid[k]*100)),\ @@ -1186,11 +1203,11 @@ def get_itable_single(teff=None,logg=None,ebv=0,z=0,rad=None,photbands=None, myf = InterpolatingFunction([zs_subgrid,np.log10(teffs_subgrid), loggs_subgrid,ebvs_subgrid],np.log10(fluxes),default=-100*np.ones_like(fluxes.shape[1])) flux = 10**myf(z,np.log10(teff),logg,ebv) + 0. - + #-- if only teff,logg and ebv need to be interpolated (faster) else: fluxes = np.zeros((2,2,2,len(photbands)+1)) - for i,j in itertools.product(xrange(2),xrange(2)): + for i,j in itertools.product(range(2),range(2)): input_code = float('%3d%05d%03d%03d'%(int(round((z+5)*100)),\ int(round(teffs_subgrid[i])),\ int(round(loggs_subgrid[j]*100)),\ @@ -1256,24 +1273,24 @@ def get_itable_single(teff=None,logg=None,ebv=0,z=0,rad=None,photbands=None, if np.any(np.isinf(flux)): flux = np.zeros(fluxes.shape[-1]) #return flux[:-1],flux[-1]#,np.array([c1_,c2,c3]) - + #-- convert to arrays: remember that last column of the fluxes is actually # absolute luminosity flux,Labs = np.array(flux[:-1],float),flux[-1] - + #-- Take radius into account when provided if rad != None: flux,Labs = flux*rad**2, Labs*rad**2 - + if flux_units!='erg/s/cm2/AA/sr': flux = conversions.nconvert('erg/s/cm2/AA/sr',flux_units,flux,photband=photbands,**kwargs) - + if wave_units is not None: model = get_table(teff=teff,logg=logg,ebv=ebv,**kwargs) wave = filters.eff_wave(photbands,model=model) if wave_units !='AA': wave = wave = conversions.convert('AA',wave_units,wave,**kwargs) - + return wave,flux,Labs else: return flux,Labs @@ -1283,20 +1300,20 @@ def get_itable(photbands=None, wave_units=None, flux_units='erg/s/cm2/AA/sr', """ Retrieve the integrated spectral energy distribution of a combined model atmosphere. - + >>> teff1,teff2 = 20200,5100 >>> logg1,logg2 = 4.35,2.00 >>> ebv = 0.2,0.2 >>> photbands = ['JOHNSON.U','JOHNSON.V','2MASS.J','2MASS.H','2MASS.KS'] - + >>> wave1,flux1 = get_table(teff=teff1,logg=logg1,ebv=ebv[0]) >>> wave2,flux2 = get_table(teff=teff2,logg=logg2,ebv=ebv[1]) >>> wave3,flux3 = get_table_multiple(teff=(teff1,teff2),logg=(logg1,logg2),ebv=ebv,radius=[1,20]) - + >>> iwave1,iflux1,iLabs1 = get_itable(teff=teff1,logg=logg1,ebv=ebv[0],photbands=photbands,wave_units='AA') >>> iflux2,iLabs2 = get_itable(teff=teff2,logg=logg2,ebv=ebv[1],photbands=photbands) >>> iflux3,iLabs3 = get_itable_multiple(teff=(teff1,teff2),logg=(logg1,logg2),z=(0,0),ebv=ebv,radius=[1,20.],photbands=photbands) - + >>> p = pl.figure() >>> p = pl.gcf().canvas.set_window_title('Test of ') >>> p = pl.loglog(wave1,flux1,'r-') @@ -1305,7 +1322,7 @@ def get_itable(photbands=None, wave_units=None, flux_units='erg/s/cm2/AA/sr', >>> p = pl.loglog(iwave1,iflux2*20**2,'bo',ms=10) >>> p = pl.loglog(wave3,flux3,'k-',lw=2) >>> p = pl.loglog(iwave1,iflux3,'kx',ms=10,mew=2) - + @param teff: effective temperature @type teff: tuple floats @param logg: logarithmic gravity (cgs) @@ -1329,50 +1346,50 @@ def get_itable(photbands=None, wave_units=None, flux_units='erg/s/cm2/AA/sr', """ #-- Find the parameters provided and store them separately. values, parameters, components = {}, set(), set() - for key in kwargs.keys(): + for key in list(kwargs.keys()): if re.search("^(teff|logg|ebv|z|rad)\d?$", key): par, comp = re.findall("^(teff|logg|ebv|z|rad)(\d?)$", key)[0] values[key] = kwargs.pop(key) parameters.add(par) components.add(comp) - + #-- If there is only one component, we can directly return the result if len(components) == 1: kwargs.update(values) return get_itable_single(photbands=photbands,wave_units=wave_units, flux_units=flux_units,**kwargs) - + #-- run over all fluxes and sum them, we do not need to multiply with the radius # as the radius is provided as an argument to itable_single_pix. - fluxes, Labs = [],[] + fluxes, Labs = [],[] for i, (comp, grid) in enumerate(zip(components,defaults_multiple)): trash = grid.pop('z',0.0), grid.pop('Rv',0.0) kwargs_ = kwargs kwargs_.update(grid) for par in parameters: kwargs_[par] = values[par+comp] if par+comp in values else values[par] - + f,L = get_itable_single(photbands=photbands,wave_units=None,**kwargs_) - + fluxes.append(f) Labs.append(L) - + fluxes = np.sum(fluxes,axis=0) Labs = np.sum(Labs,axis=0) - + if flux_units!='erg/s/cm2/AA/sr': fluxes = np.array([conversions.convert('erg/s/cm2/AA/sr',flux_units,fluxes[i],photband=photbands[i]) for i in range(len(fluxes))]) - + if wave_units is not None: model = get_table_multiple(teff=teff,logg=logg,ebv=ebv, grids=grids,**kwargs) wave = filters.eff_wave(photbands,model=model) if wave_units !='AA': wave = wave = conversions.convert('AA',wave_units,wave) return wave,fluxes,Labs - + return fluxes,Labs - - + + ##-- set default parameters #if grids is None: #grids = [defaults_multiple[i] for i in range(len(teff))] @@ -1393,7 +1410,7 @@ def get_itable(photbands=None, wave_units=None, flux_units='erg/s/cm2/AA/sr', #Labs = np.sum(Labs) #if flux_units!='erg/s/cm2/AA/sr': #fluxes = np.array([conversions.convert('erg/s/cm2/AA/sr',flux_units,fluxes[i],photband=photbands[i]) for i in range(len(fluxes))]) - + #if wave_units is not None: #model = get_table_multiple(teff=teff,logg=logg,ebv=ebv, grids=grids,**kwargs) #wave = filters.eff_wave(photbands,model=model) @@ -1406,50 +1423,50 @@ def get_itable_single_pix(teff=None,logg=None,ebv=None,z=0,rv=3.1,vrad=0,photban wave_units=None,flux_units='erg/s/cm2/AA/sr',**kwargs): """ Super fast grid interpolator. - - Possible kwargs are teffrange,loggrange etc.... that are past on to + + Possible kwargs are teffrange,loggrange etc.... that are past on to L{_get_pix_grid}. You should probably use these options when you want to interpolate in many variables; supplying these ranges will make the grid smaller and thus decrease memory usage. - + It is possible to fix C{teff}, C{logg}, C{ebv}, C{z}, C{rv} and/or C{vrad} to one value, in which case it B{has} to be a point in the grid. If you want to retrieve a list of fluxes with the same ebv value that is not in the grid, you need to give an array with all equal values. The reason is that the script can try to minimize the number of interpolations, by fixing a - variable on a grid point. The fluxes on the other gridpoints will then not - be interpolated over. These parameter also have to be listed with the + variable on a grid point. The fluxes on the other gridpoints will then not + be interpolated over. These parameter also have to be listed with the additional C{exc_interpolpar} keyword. - + >>> teffs = np.linspace(5000,7000,100) >>> loggs = np.linspace(4.0,4.5,100) >>> ebvs = np.linspace(0,1,100) >>> zs = np.linspace(-0.5,0.5,100) >>> rvs = np.linspace(2.2,5.0,100) - + >>> set_defaults(grid='kurucz2') >>> flux,labs = get_itable_pix(teffs,loggs,ebvs,zs,rvs,photbands=['JOHNSON.V']) - + >>> names = ['teffs','loggs','ebvs','zs','rvs'] >>> p = pl.figure() >>> for i in range(len(names)): ... p = pl.subplot(2,3,i+1) ... p = pl.plot(locals()[names[i]],flux[0],'k-') ... p = pl.xlabel(names[i]) - - + + Thanks to Steven Bloemen for the core implementation of the interpolation algorithm. - + The addition of the exc_interpolpar keyword was done by Michel Hillen (Jan 2016). """ - + #-- setup some standard values when they are not provided ebv = np.array([0 for i in teff]) if ebv is None else ebv z = np.array([0.for i in teff]) if z is None else z rv = np.array([3.1 for i in teff]) if rv is None else rv vrad = np.array([0 for i in teff]) if vrad is None else vrad - + #for var in ['teff','logg','ebv','z','rv','vrad']: #if not hasattr(locals()[var],'__iter__'): #print var, locals()[var] @@ -1457,6 +1474,7 @@ def get_itable_single_pix(teff=None,logg=None,ebv=None,z=0,rv=3.1,vrad=0,photban #print locals() vrad = 0 N = 1 + variables = [] clear_memory = kwargs.pop('clear_memory',False) #variables = kwargs.pop('variables',['teff','logg','ebv','z','rv','vrad']) # !!! for var in ['teff','logg','ebv','z','rv','vrad']: # !!! @@ -1464,55 +1482,64 @@ def get_itable_single_pix(teff=None,logg=None,ebv=None,z=0,rv=3.1,vrad=0,photban kwargs.setdefault(var+'range',(locals()[var],locals()[var])) else: N = len(locals()[var]) - + variables.append(var) + + + #-- retrieve structured information on the grid (memoized) axis_values,gridpnts,pixelgrid,cols = _get_pix_grid(photbands, - include_Labs=True,clear_memory=clear_memory,**kwargs) # !!! - - #-- Remove parameters from the grid if it is requested that these should not be interpolated - #-- (with the exc_interpolpar keyword). This can only work if the requested values of + include_Labs=True, variables=variables, + clear_memory=clear_memory,**kwargs) # !!! + + #-- Remove parameters from the grid if it is requested that these should not be interpolated + #-- (with the exc_interpolpar keyword). This can only work if the requested values of #-- these parameters all correspond to a single point in the original grid! #-- we check whether this condition is fulfilled - #-- if not, then the parameter is not excluded from the interpolation - #-- and a warning is raised to the log - for var in kwargs.get('exc_interpolpar',[]): # e.g. for Kurucz, var can be anything in ['teff','logg','ebv','z'] - # retrieve the unique values in var - var_uniquevalue = np.unique(np.array(locals()[var])) - # if there is more than one unique value in var, then our condition is not fulfilled - if len(var_uniquevalue) > 1: - logger.warning('{} is requested to be excluded from interpolation, although fluxes for more than one value are requested!?'.format(var)) - else: - # retrieve the index of var in the 'pixelgrid' and 'cols' arrays of the original grid - var_index = np.where(cols == var)[0] - # retrieve the index of the unique value in the original grid - var_uniquevalue_index = np.where(axis_values[var_index] == var_uniquevalue[0])[0] - # if the unique value does not correspond to a grid point of the original grid, then we only raise a warning - if len(var_uniquevalue_index) == 0: - logger.warning('{} can only be excluded from interpolation, as requested, if its values are all equal to an actual grid point!'.format(var)) - else: - # remove var from the list of variables in the original grid - trash = axis_values.pop(var_index) - cols = np.delete(cols,[var_index]) - # since we do not know the axis of var in advance, we devise a clever way to - # bring it to the first axis by transposing the array - indices = [x for x in range(pixelgrid.ndim)] - indices.remove(var_index) - indices.insert(0,var_index) - pixelgrid = np.transpose(pixelgrid, indices) - # now we select the subgrid corresponding to the requested value of var - pixelgrid = pixelgrid[var_uniquevalue_index[0]] - + #-- if not, then the parameter is not excluded from the interpolation + # #-- and a warning is raised to the log + # for var in kwargs.get('exc_interpolpar',[]): # e.g. for Kurucz, var can be anything in ['teff','logg','ebv','z'] + # # retrieve the unique values in var + # var_uniquevalue = np.unique(np.array(locals()[var])) + # # if there is more than one unique value in var, then our condition is not fulfilled + # if len(var_uniquevalue) > 1: + # logger.warning('{} is requested to be excluded from interpolation, although fluxes for more than one value are requested!?'.format(var)) + # else: + # # retrieve the index of var in the 'pixelgrid' and 'cols' arrays of the original grid + # print(cols) + # print(var) + # var_index = np.where(cols == var)[0] + # # retrieve the index of the unique value in the original grid + # print(var_index) + # print(axis_values[var_index]) + # print(var_uniquevalue[0]) + # var_uniquevalue_index = np.where(axis_values[var_index] == var_uniquevalue[0])[0] + # # if the unique value does not correspond to a grid point of the original grid, then we only raise a warning + # if len(var_uniquevalue_index) == 0: + # logger.warning('{} can only be excluded from interpolation, as requested, if its values are all equal to an actual grid point!'.format(var)) + # else: + # # remove var from the list of variables in the original grid + # trash = axis_values.pop(var_index) + # cols = np.delete(cols,[var_index]) + # # since we do not know the axis of var in advance, we devise a clever way to + # # bring it to the first axis by transposing the array + # indices = [x for x in range(pixelgrid.ndim)] + # indices.remove(var_index) + # indices.insert(0,var_index) + # pixelgrid = np.transpose(pixelgrid, indices) + # # now we select the subgrid corresponding to the requested value of var + # pixelgrid = pixelgrid[var_uniquevalue_index[0]] + #-- prepare input: values = np.zeros((len(cols),N)) for i,col in enumerate(cols): values[i] = locals()[col] pars = 10**interpol.interpolate(values,axis_values,pixelgrid) flux,Labs = pars[:-1],pars[-1] - + #-- Take radius into account when provided if 'rad' in kwargs: flux,Labs = flux*kwargs['rad']**2, Labs*kwargs['rad']**2 - + #-- change flux and wavelength units if needed if flux_units!='erg/s/cm2/AA/sr': flux = conversions.nconvert('erg/s/cm2/AA/sr',flux_units,flux,photband=photbands,**kwargs) @@ -1524,7 +1551,7 @@ def get_itable_single_pix(teff=None,logg=None,ebv=None,z=0,rv=3.1,vrad=0,photban return wave,flux,Labs else: return flux,Labs - + def get_itable_pix(photbands=None, wave_units=None, flux_units='erg/s/cm2/AA/sr', grids=None, **kwargs): """ @@ -1532,7 +1559,7 @@ def get_itable_pix(photbands=None, wave_units=None, flux_units='erg/s/cm2/AA/sr' """ #-- Find the parameters provided and store them separately. values, parameters, components = {}, set(), set() - for key in kwargs.keys(): + for key in list(kwargs.keys()): if re.search("^(teff|logg|ebv|z|rv|vrad|rad)\d?$", key): par, comp = re.findall("^(teff|logg|ebv|z|rv|vrad|rad)(\d?)$", key)[0] values[key] = kwargs.pop(key) @@ -1545,31 +1572,31 @@ def get_itable_pix(photbands=None, wave_units=None, flux_units='erg/s/cm2/AA/sr' flux_units=flux_units,**kwargs) #-- run over all fluxes and sum them, we do not need to multiply with the radius # as the radius is provided as an argument to itable_single_pix. - fluxes, Labs = [],[] + fluxes, Labs = [],[] for i, (comp, grid) in enumerate(zip(components,defaults_multiple)): trash = grid.pop('z',0.0), grid.pop('Rv',0.0) kwargs_ = kwargs kwargs_.update(grid) for par in parameters: kwargs_[par] = values[par+comp] if par+comp in values else values[par] - + f,L = get_itable_single_pix(photbands=photbands,wave_units=None,**kwargs_) - + fluxes.append(f) Labs.append(L) fluxes = np.sum(fluxes,axis=0) Labs = np.sum(Labs,axis=0) - + if flux_units!='erg/s/cm2/AA/sr': fluxes = np.array([conversions.convert('erg/s/cm2/AA/sr',flux_units,fluxes[i],photband=photbands[i]) for i in range(len(fluxes))]) - + if wave_units is not None: model = get_table_multiple(teff=teff,logg=logg,ebv=ebv, grids=grids,**kwargs) wave = filters.eff_wave(photbands,model=model) if wave_units !='AA': wave = conversions.convert('AA',wave_units,wave) return wave,fluxes,Labs - return fluxes,Labs + return fluxes,Labs #def get_table_multiple(teff=None,logg=None,ebv=None,radius=None, @@ -1577,22 +1604,22 @@ def get_itable_pix(photbands=None, wave_units=None, flux_units='erg/s/cm2/AA/sr' def get_table(wave_units='AA',flux_units='erg/cm2/s/AA/sr',grids=None,full_output=False,**kwargs): """ Retrieve the spectral energy distribution of a combined model atmosphere. - + Example usage: - + >>> teff1,teff2 = 20200,5100 >>> logg1,logg2 = 4.35,2.00 >>> wave1,flux1 = get_table(teff=teff1,logg=logg1,ebv=0.2) >>> wave2,flux2 = get_table(teff=teff2,logg=logg2,ebv=0.2) >>> wave3,flux3 = get_table_multiple(teff=(teff1,teff2),logg=(logg1,logg2),ebv=(0.2,0.2),radius=[1,20]) - + >>> p = pl.figure() >>> p = pl.gcf().canvas.set_window_title('Test of ') >>> p = pl.loglog(wave1,flux1,'r-') >>> p = pl.loglog(wave2,flux2,'b-') >>> p = pl.loglog(wave2,flux2*20**2,'b--') >>> p = pl.loglog(wave3,flux3,'k-',lw=2) - + @param teff: effective temperature @type teff: tuple floats @param logg: logarithmic gravity (cgs) @@ -1613,34 +1640,34 @@ def get_table(wave_units='AA',flux_units='erg/cm2/s/AA/sr',grids=None,full_outpu @rtype: (ndarray,ndarray) """ values, parameters, components = {}, set(), set() - for key in kwargs.keys(): + for key in list(kwargs.keys()): if re.search("^(teff|logg|ebv|z|rad)\d?$", key): par, comp = re.findall("^(teff|logg|ebv|z|rad)(\d?)$", key)[0] values[key] = kwargs.pop(key) parameters.add(par) components.add(comp) - + #-- If there is only one components we can directly return the result if len(components) == 1: kwargs.update(values) return get_table_single(wave_units=wave_units, flux_units=flux_units, full_output=full_output,**kwargs) - + #-- Run over all fluxes and sum them, we do not need to multiply with the radius # as the radius is provided as an argument to get_table_single. - waves, fluxes = [],[] + waves, fluxes = [],[] for i, (comp, grid) in enumerate(zip(components,defaults_multiple)): trash = grid.pop('z',0.0), grid.pop('Rv',0.0) kwargs_ = kwargs.copy() kwargs_.update(grid) for par in parameters: kwargs_[par] = values[par+comp] if par+comp in values else values[par] - + w,f = get_table_single(**kwargs_) - + waves.append(w) fluxes.append(f) - + ##-- set default parameters #if grids is None: #grids = [defaults_multiple[i] for i in range(len(teff))] @@ -1654,7 +1681,7 @@ def get_table(wave_units='AA',flux_units='erg/cm2/s/AA/sr',grids=None,full_outpu #iwave,iflux = get_table(teff=iteff,logg=ilogg,ebv=iebv,**mykwargs) #waves.append(iwave) #fluxes.append(iflux) - + #-- what's the total wavelength range? Merge all wavelength arrays and # remove double points waves_ = np.sort(np.hstack(waves)) @@ -1667,7 +1694,7 @@ def get_table(wave_units='AA',flux_units='erg/cm2/s/AA/sr',grids=None,full_outpu fluxes_ = [] else: fluxes_ = 0. - + ##-- interpolate onto common grid in log! #for i,(wave,flux) in enumerate(zip(waves,fluxes)): #intf = interp1d(np.log10(wave),np.log10(flux),kind='linear') @@ -1682,7 +1709,7 @@ def get_table(wave_units='AA',flux_units='erg/cm2/s/AA/sr',grids=None,full_outpu fluxes_.append(10**intf(np.log10(waves_))) else: fluxes_ += 10**intf(np.log10(waves_)) - + if flux_units!='erg/cm2/s/AA/sr': fluxes_ = conversions.convert('erg/s/cm2/AA/sr',flux_units,fluxes_,wave=(waves_,'AA'),**kwargs) if wave_units!='AA': @@ -1690,11 +1717,11 @@ def get_table(wave_units='AA',flux_units='erg/cm2/s/AA/sr',grids=None,full_outpu #-- where the fluxes are zero, log can do weird if full_output: fluxes_ = np.vstack(fluxes_) - keep = -np.isnan(np.sum(fluxes_,axis=0)) + keep = ~np.isnan(np.sum(fluxes_,axis=0)) waves_ = waves_[keep] fluxes_ = fluxes_[:,keep] else: - keep = -np.isnan(fluxes_) + keep = ~np.isnan(fluxes_) waves_ = waves_[keep] fluxes_ = fluxes_[keep] return waves_,fluxes_ @@ -1703,9 +1730,9 @@ def get_table(wave_units='AA',flux_units='erg/cm2/s/AA/sr',grids=None,full_outpu def get_grid_dimensions(**kwargs): """ Retrieve possible effective temperatures and gravities from a grid. - + E.g. kurucz, sdB, fastwind... - + @rtype: (ndarray,ndarray) @return: effective temperatures, gravities """ @@ -1717,12 +1744,12 @@ def get_grid_dimensions(**kwargs): teffs.append(float(mod.header['TEFF'])) loggs.append(float(mod.header['LOGG'])) ff.close() - + #-- maybe the fits extensions are not in right order... matrix = np.vstack([np.array(teffs),np.array(loggs)]).T matrix = numpy_ext.sort_order(matrix,order=[0,1]) teffs,loggs = matrix.T - + return teffs,loggs @@ -1732,9 +1759,9 @@ def get_igrid_dimensions(**kwargs): """ Retrieve possible effective temperatures, surface gravities and reddenings from an integrated grid. - + E.g. kurucz, sdB, fastwind... - + @rtype: (ndarray,ndarray,ndarray) @return: effective temperatures, surface, gravities, E(B-V)s """ @@ -1744,10 +1771,10 @@ def get_igrid_dimensions(**kwargs): loggs = ff[1].data.field('LOGG') ebvs = ff[1].data.field('EBV') ff.close() - + #correct = (teffs==14000) & (loggs==2.0) #teffs[correct] = 12000 - + return teffs,loggs,ebvs @@ -1760,24 +1787,24 @@ def get_igrid_dimensions(**kwargs): def get_grid_mesh(wave=None,teffrange=None,loggrange=None,**kwargs): """ Return InterpolatingFunction spanning the available grid of atmosphere models. - + WARNING: the grid must be entirely defined on a mesh grid, but it does not need to be equidistant. - + It is thus the user's responsibility to know whether the grid is evenly spaced in logg and teff (e.g. this is not so for the CMFGEN models). - + You can supply your own wavelength range, since the grid models' resolution are not necessarily homogeneous. If not, the first wavelength array found in the grid will be used as a template. - + It might take a long a time and cost a lot of memory if you load the entire grid. Therefor, you can also set range of temperature and gravity. - + WARNING: 30000,50000 did not work out for FASTWIND, since we miss a model! - + Example usage: - + @param wave: wavelength to define the grid on @type wave: ndarray @param teffrange: starting and ending of the grid in teff @@ -1798,7 +1825,7 @@ def get_grid_mesh(wave=None,teffrange=None,loggrange=None,**kwargs): if loggrange is not None: sa = (loggrange[0]<=loggs) & (loggs<=loggrange[1]) loggs = loggs[sa] - + #-- ScientificPython interface if not new_scipy: logger.warning('SCIENTIFIC PYTHON') @@ -1837,7 +1864,7 @@ def get_grid_mesh(wave=None,teffrange=None,loggrange=None,**kwargs): ff.close() flux_grid = InterpolatingFunction([np.log10(teffs),loggs],flux) logger.info('Constructed SED interpolation grid') - + #-- Scipy interface else: logger.warning('SCIPY') @@ -1861,7 +1888,7 @@ def get_grid_mesh(wave=None,teffrange=None,loggrange=None,**kwargs): try: flux[i] = flux_ except: - flux[i] = np.interp(wave,wave_,flux_) + flux[i] = np.interp(wave,wave_,flux_) ff.close() flux_grid = LinearNDInterpolator(np.array([np.log10(teffs),loggs]).T,flux) return wave,teffs,loggs,flux,flux_grid @@ -1873,7 +1900,7 @@ def get_grid_mesh(wave=None,teffrange=None,loggrange=None,**kwargs): def list_calibrators(library='calspec'): """ Print and return the list of calibrators - + @parameter library: name of the library (calspec, ngsl, stelib) @type library: str @return: list of calibrator names @@ -1881,7 +1908,7 @@ def list_calibrators(library='calspec'): """ files = config.glob(os.path.join(caldir,library),'*.fits') targname = dict(calspec='targetid',ngsl='targname',stelib='object')[library] - + names = [] for ff in files: name = pf.getheader(ff)[targname] @@ -1891,20 +1918,20 @@ def list_calibrators(library='calspec'): #else: names.append(name) return names - + def get_calibrator(name='alpha_lyr',version=None,wave_units=None,flux_units=None,library='calspec'): """ Retrieve a calibration SED - + If C{version} is None, get the last version. - + Example usage: - + >>> wave,flux = get_calibrator(name='alpha_lyr') >>> wave,flux = get_calibrator(name='alpha_lyr',version='003') - + @param name: calibrator name @type name: str @param version: version of the calibration file @@ -1919,7 +1946,7 @@ def get_calibrator(name='alpha_lyr',version=None,wave_units=None,flux_units=None #-- collect calibration files files = config.glob(os.path.join(caldir,library),'*.fits') targname = dict(calspec='targetid',ngsl='targname',stelib='object')[library] - + calfile = None for ff in files: #-- check if the name matches with the given one @@ -1940,18 +1967,18 @@ def get_calibrator(name='alpha_lyr',version=None,wave_units=None,flux_units=None else: raise ValueError("Don't know what to do with files from library {}".format(library)) fits_file.close() - + if calfile is None: - raise ValueError, 'Calibrator %s (version=%s) not found'%(name,version) - + raise ValueError('Calibrator %s (version=%s) not found'%(name,version)) + if flux_units is not None: flux = conversions.convert('erg/s/cm2/AA',flux_units,flux,wave=(wave,'AA')) if wave_units is not None: wave = conversions.convert('AA',wave_units,wave) - - + + logger.info('Calibrator %s selected'%(calfile)) - + return wave,flux @@ -1974,26 +2001,26 @@ def read_calibrator_info(library='ngsl'): names.append(line[0]) fits_files.append(fits_file) phot_files.append(phot_file) - + return names,fits_files,phot_files def calibrate(): """ Calibrate photometry. - + Not finished! - + ABmag = -2.5 Log F_nu - 48.6 with F_nu in erg/s/cm2/Hz Flux computed as 10**(-(meas-mag0)/2.5)*F0 Magnitude computed as -2.5*log10(Fmeas/F0) F0 = 3.6307805477010029e-20 erg/s/cm2/Hz - + STmag = -2.5 Log F_lam - 21.10 with F_lam in erg/s/cm2/AA Flux computed as 10**(-(meas-mag0)/2.5)*F0 Magnitude computed as -2.5*log10(Fmeas/F0) F0 = 3.6307805477010028e-09 erg/s/cm2/AA - + Vegamag = -2.5 Log F_lam - C with F_lam in erg/s/cm2/AA Flux computed as 10**(-meas/2.5)*F0 Magnitude computed as -2.5*log10(Fmeas/F0) @@ -2003,27 +2030,27 @@ def calibrate(): #-- get calibrator wave,flux = get_calibrator(name='alpha_lyr') zp = filters.get_info() - + #-- calculate synthetic fluxes syn_flux = synthetic_flux(wave,flux,zp['photband']) syn_flux_fnu = synthetic_flux(wave,flux,zp['photband'],units='Fnu') Flam0_lit = conversions.nconvert(zp['Flam0_units'],'erg/s/cm2/AA',zp['Flam0'],photband=zp['photband']) Fnu0_lit = conversions.nconvert(zp['Fnu0_units'],'erg/s/cm2/Hz',zp['Fnu0'],photband=zp['photband']) - + #-- we have Flam0 but not Fnu0: compute Fnu0 keep = (zp['Flam0_lit']==1) & (zp['Fnu0_lit']==0) Fnu0 = conversions.nconvert(zp['Flam0_units'],'erg/s/cm2/Hz',zp['Flam0'],photband=zp['photband']) zp['Fnu0'][keep] = Fnu0[keep] zp['Fnu0_units'][keep] = 'erg/s/cm2/Hz' - + #-- we have Fnu0 but not Flam0: compute Flam0 keep = (zp['Flam0_lit']==0) & (zp['Fnu0_lit']==1) Flam0 = conversions.nconvert(zp['Fnu0_units'],'erg/s/cm2/AA',zp['Fnu0'],photband=zp['photband']) - + # set everything in correct units for convenience: Flam0 = conversions.nconvert(zp['Flam0_units'],'erg/s/cm2/AA',zp['Flam0']) Fnu0 = conversions.nconvert(zp['Fnu0_units'],'erg/s/cm2/Hz',zp['Fnu0']) - + #-- as a matter of fact, set Flam0 and Fnu for all the stuff for which we # have no literature values keep = (zp['Flam0_lit']==0) & (zp['Fnu0_lit']==0) @@ -2031,96 +2058,96 @@ def calibrate(): zp['Flam0_units'][keep] = 'erg/s/cm2/AA' zp['Fnu0'][keep] = syn_flux_fnu[keep] zp['Fnu0_units'][keep] = 'erg/s/cm2/Hz' - + keep = np.array(['DENIS' in photb and True or False for photb in zp['photband']]) - + #-- we have no Flam0, only ZP vegamags keep = (zp['vegamag_lit']==1) & (zp['Flam0_lit']==0) zp['Flam0'][keep] = syn_flux[keep] zp['Flam0_units'][keep] = 'erg/s/cm2/AA' - + #-- we have no Flam0, no ZP vegamas but STmags keep = (zp['STmag_lit']==1) & (zp['Flam0_lit']==0) m_vega = 2.5*np.log10(F0ST/syn_flux) + zp['STmag'] zp['vegamag'][keep] = m_vega[keep] - + #-- we have no Fnu0, no ZP vegamas but ABmags keep = (zp['ABmag_lit']==1) & (zp['Flam0_lit']==0) F0AB_lam = conversions.convert('erg/s/cm2/Hz','erg/s/cm2/AA',F0AB,photband=zp['photband']) m_vega = 2.5*np.log10(F0AB_lam/syn_flux) + zp['ABmag'] zp['vegamag'][keep] = m_vega[keep] - + #-- set the central wavelengths of the bands set_wave = np.isnan(zp['eff_wave']) zp['eff_wave'][set_wave] = filters.eff_wave(zp['photband'][set_wave]) - + return zp - + #} -#{ Synthetic photometry +#{ Synthetic photometry def synthetic_flux(wave,flux,photbands,units=None): """ Extract flux measurements from a synthetic SED (Fnu or Flambda). - + The fluxes below 4micron are calculated assuming PHOTON-counting detectors (e.g. CCDs). - + Flam = int(P_lam * f_lam * lam, dlam) / int(P_lam * lam, dlam) - + When otherwise specified, we assume ENERGY-counting detectors (e.g. bolometers) - + Flam = int(P_lam * f_lam, dlam) / int(P_lam, dlam) - + Where P_lam is the total system dimensionless sensitivity function, which is normalised so that the maximum equals 1. Also, f_lam is the SED of the object, in units of energy per time per unit area per wavelength. - + The PHOTON-counting part of this routine has been thoroughly checked with respect to johnson UBV, geneva and stromgren filters, and only gives offsets with respect to the Kurucz integrated files (.geneva and stuff on his websites). These could be due to different normalisation. - + You can also readily integrate in Fnu instead of Flambda by suppling a list of strings to 'units'. This should have equal length of photbands, and should contain the strings 'flambda' and 'fnu' corresponding to each filter. In that case, the above formulas reduce to - + Fnu = int(P_nu * f_nu / nu, dnu) / int(P_nu / nu, dnu) - - and - + + and + Fnu = int(P_nu * f_nu, dnu) / int(P_nu, dnu) - + Small note of caution: P_nu is not equal to P_lam according to Maiz-Apellaniz, he states that P_lam = P_nu/lambda. But in the definition we use above here, it *is* the same! - + The model fluxes should B{always} be given in Flambda (erg/s/cm2/AA). The program will convert them to Fnu where needed. - + The output is a list of numbers, equal in length to the 'photband' inputs. The units of the output are erg/s/cm2/AA where Flambda was given, and erg/s/cm2/Hz where Fnu was given. - + The difference is only marginal for 'blue' bands. For example, integrating 2MASS in Flambda or Fnu is only different below the 1.1% level: - + >>> wave,flux = get_table(teff=10000,logg=4.0) >>> energys = synthetic_flux(wave,flux,['2MASS.J','2MASS.J'],units=['flambda','fnu']) >>> e0_conv = conversions.convert('erg/s/cm2/AA','erg/s/cm2/Hz',energys[0],photband='2MASS.J') >>> np.abs(energys[1]-e0_conv)/energys[1]<0.012 True - + But this is not the case for IRAS.F12: - + >>> energys = synthetic_flux(wave,flux,['IRAS.F12','IRAS.F12'],units=['flambda','fnu']) >>> e0_conv = conversions.convert('erg/s/cm2/AA','erg/s/cm2/Hz',energys[0],photband='IRAS.F12') >>> np.abs(energys[1]-e0_conv)/energys[1]>0.1 True - + If you have a spectrum in micron vs Jy and want to calculate the synthetic fluxes in Jy, a little bit more work is needed to get everything in the right units. In the following example, we first generate a constant flux @@ -2128,33 +2155,33 @@ def synthetic_flux(wave,flux,photbands,units=None): wavelengths (this is no approximation) and convert wavelength to angstrom. Next, we compute the synthetic fluxes in the IRAS band in Fnu, and finally convert the outcome (in erg/s/cm2/Hz) to Jansky. - + >>> wave,flux = np.linspace(0.1,200,10000),np.ones(10000) >>> flam = conversions.convert('Jy','erg/s/cm2/AA',flux,wave=(wave,'micron')) >>> lam = conversions.convert('micron','AA',wave) >>> energys = synthetic_flux(lam,flam,['IRAS.F12','IRAS.F25','IRAS.F60','IRAS.F100'],units=['Fnu','Fnu','Fnu','Fnu']) >>> energys = conversions.convert('erg/s/cm2/Hz','Jy',energys) - + You are responsible yourself for having a response curve covering the model fluxes! - + Now. let's put this all in practice in a more elaborate example: we want to check if the effective wavelength is well defined. To do that we will: - + 1. construct a model (black body) 2. make our own weird, double-shaped filter (CCD-type and BOL-type detector) 3. compute fluxes in Flambda, and convert to Fnu via the effective wavelength 4. compute fluxes in Fnu, and compare with step 3. - + In an ideal world, the outcome of step (3) and (4) must be equal: - + Step (1): We construct a black body model. - - + + WARNING: OPEN.BOL only works in Flambda for now. - + See e.g. Maiz-Apellaniz, 2006. - + @param wave: model wavelengths (angstrom) @type wave: ndarray @param flux: model fluxes (erg/s/cm2/AA) @@ -2165,16 +2192,16 @@ def synthetic_flux(wave,flux,photbands,units=None): @type units: list of strings or str @return: model fluxes (erg/s/cm2/AA or erg/s/cm2/Hz) @rtype: ndarray - """ + """ if isinstance(units,str): units = [units]*len(photbands) energys = np.zeros(len(photbands)) - + #-- only keep relevant information on filters: filter_info = filters.get_info() keep = np.searchsorted(filter_info['photband'],photbands) filter_info = filter_info[keep] - + for i,photband in enumerate(photbands): #if filters.is_color waver,transr = filters.get_response(photband) @@ -2203,7 +2230,7 @@ def synthetic_flux(wave,flux,photbands,units=None): wave_ = wave__ #-- interpolate response curve onto model grid transr = np.interp(wave_,waver,transr,left=0,right=0) - + #-- integrated flux: different for bolometers and CCDs #-- WE WORK IN FLAMBDA if units is None or ((units is not None) and (units[i].upper()=='FLAMBDA')): @@ -2213,7 +2240,7 @@ def synthetic_flux(wave,flux,photbands,units=None): energys[i] = np.trapz(flux_*transr,x=wave_)/np.trapz(transr,x=wave_) elif filter_info['type'][i]=='CCD': energys[i] = np.trapz(flux_*transr*wave_,x=wave_)/np.trapz(transr*wave_,x=wave_) - + #-- we work in FNU elif units[i].upper()=='FNU': #-- convert wavelengths to frequency, Flambda to Fnu @@ -2229,8 +2256,8 @@ def synthetic_flux(wave,flux,photbands,units=None): elif filter_info['type'][i]=='CCD': energys[i] = np.trapz(flux_f*transr/freq_,x=wave_)/np.trapz(transr/freq_,x=wave_) else: - raise ValueError,'units %s not understood'%(units) - + raise ValueError('units %s not understood'%(units)) + #-- that's it! return energys @@ -2238,7 +2265,7 @@ def synthetic_flux(wave,flux,photbands,units=None): def synthetic_color(wave,flux,colors,units=None): """ Construct colors from a synthetic SED. - + @param wave: model wavelengths (angstrom) @type wave: ndarray @param flux: model fluxes (erg/s/cm2/AA) @@ -2252,7 +2279,7 @@ def synthetic_color(wave,flux,colors,units=None): """ if units is None: units = [None for color in colors] - + syn_colors = np.zeros(len(colors)) for i,(color,unit) in enumerate(zip(colors,units)): #-- retrieve the passbands necessary to construct the color, and the @@ -2262,24 +2289,24 @@ def synthetic_color(wave,flux,colors,units=None): fluxes = synthetic_flux(wave,flux,photbands,units=unit) #-- construct the color syn_colors[i] = color_func(*list(fluxes)) - - return syn_colors - + + return syn_colors + def luminosity(wave,flux,radius=1.): """ Calculate the bolometric luminosity of a model SED. - + Flux should be in cgs per unit wavelength (same unit as wave). The latter is integrated out, so it is of no importance. After integration, flux, should have units erg/s/cm2. - + Returned luminosity is in solar units. - + If you give radius=1 and want to correct afterwards, multiply the obtained Labs with radius**2. - + @param wave: model wavelengths @type wave: ndarray @param flux: model fluxes (Flam) @@ -2305,6 +2332,11 @@ def _get_itable_markers(photbands, """ if clear_memory: clear_memoization(keys=['ivs.sed.model']) + # Possibility to not fetch all grid files when not needed + # does not work currently + # if 'z_skip' in kwargs: + # gridfiles = get_file(integrated=True,**kwargs) + # else: gridfiles = get_file(z='*',integrated=True,**kwargs) if isinstance(gridfiles,str): gridfiles = [gridfiles] @@ -2315,7 +2347,6 @@ def _get_itable_markers(photbands, gridpnts = [] grid_z = [] markers = [] - #-- collect information for gridfile in gridfiles: ff = pf.open(gridfile) @@ -2323,41 +2354,45 @@ def _get_itable_markers(photbands, z = ff[1].header['z'] if z>> wave = np.r_[1e3:1e5:10] >>> wave,mag = get_law('cardelli1989',wave=wave,Rv=3.1) - + @param name: name of the interstellar law @type name: str, one of the functions defined here @param norm: type of normalisation of the curve @@ -151,18 +151,18 @@ def get_law(name,norm='E(B-V)',wave_units='AA',photbands=None,**kwargs): #-- get the inputs wave_ = kwargs.pop('wave',None) Rv = kwargs.setdefault('Rv',3.1) - + #-- get the curve wave,mag = globals()[name.lower()](**kwargs) wave_orig,mag_orig = wave.copy(),mag.copy() - + #-- interpolate on user defined grid if wave_ is not None: if wave_units != 'AA': wave_ = conversions.convert(wave_units,'AA',wave_) mag = np.interp(wave_,wave,mag,right=0) wave = wave_ - + #-- pick right normalisation: convert to A(lambda)/Av if needed if norm.lower()=='e(b-v)': mag *= Rv @@ -176,34 +176,34 @@ def get_law(name,norm='E(B-V)',wave_units='AA',photbands=None,**kwargs): norm_reddening = model.synthetic_flux(wave_orig,mag_orig,[norm])[0] logger.info('Normalisation via %s: Av/%s = %.6g'%(norm,norm,1./norm_reddening)) mag /= norm_reddening - + #-- maybe we want the curve in photometric filters if photbands is not None: mag = model.synthetic_flux(wave,mag,photbands) wave = filters.get_info(photbands)['eff_wave'] - - + + #-- set the units of the wavelengths if wave_units != 'AA' and photbands is not None: wave = conversions.convert('AA',wave_units,wave) - + return wave,mag def redden(flux,wave=None,photbands=None,ebv=0.,rtype='flux',law='cardelli1989',**kwargs): """ Redden flux or magnitudes - + The reddening parameters C{ebv} means E(B-V). - + If it is negative, we B{deredden}. - + If you give the keyword C{wave}, it is assumed that you want to (de)redden a B{model}, i.e. a spectral energy distribution. - + If you give the keyword C{photbands}, it is assumed that you want to (de)redden B{photometry}, i.e. integrated fluxes. - + @param flux: fluxes to (de)redden (magnitudes if C{rtype='mag'}) @type flux: ndarray (floats) @param wave: wavelengths matching the fluxes (or give C{photbands}) @@ -219,7 +219,7 @@ def redden(flux,wave=None,photbands=None,ebv=0.,rtype='flux',law='cardelli1989', """ if photbands is not None: wave = filters.get_info(photbands)['eff_wave'] - + old_settings = np.seterr(all='ignore') wave, reddeningMagnitude = get_law(law,wave=wave,**kwargs) @@ -238,7 +238,7 @@ def redden(flux,wave=None,photbands=None,ebv=0.,rtype='flux',law='cardelli1989', def deredden(flux,wave=None,photbands=None,ebv=0.,rtype='flux',**kwargs): """ Deredden flux or magnitudes. - + @param flux: fluxes to (de)redden (NOT magnitudes) @type flux: ndarray (floats) @param wave: wavelengths matching the fluxes (or give C{photbands}) @@ -253,7 +253,7 @@ def deredden(flux,wave=None,photbands=None,ebv=0.,rtype='flux',**kwargs): @rtype: ndarray (floats) """ return redden(flux,wave=wave,photbands=photbands,ebv=-ebv,rtype=rtype,**kwargs) - + #} @@ -262,16 +262,16 @@ def deredden(flux,wave=None,photbands=None,ebv=0.,rtype='flux',**kwargs): def chiar2006(Rv=3.1,curve='ism',**kwargs): """ Extinction curve at infrared wavelengths from Chiar and Tielens (2006) - + We return A(lambda)/E(B-V), by multiplying A(lambda)/Av with Rv. - + This is only defined for Rv=3.1. If it is different, this will raise an AssertionError - + Extra kwags are to catch unwanted keyword arguments. - + UNCERTAIN NORMALISATION - + @param Rv: Rv @type Rv: float @param curve: extinction curve @@ -280,7 +280,7 @@ def chiar2006(Rv=3.1,curve='ism',**kwargs): @rtype: (ndarray,ndarray) """ source = os.path.join(basename,'Chiar2006.red') - + #-- check Rv assert(Rv==3.1) wavelengths,gc,ism = ascii.read2array(source).T @@ -291,7 +291,7 @@ def chiar2006(Rv=3.1,curve='ism',**kwargs): alam_ak = ism[keep] wavelengths = wavelengths[keep] else: - raise ValueError,'no curve %s'%(curve) + raise ValueError('no curve %s'%(curve)) alam_aV = alam_ak * 0.09 #plot(1/wavelengths,alam_aV,'o-') return wavelengths*1e4,alam_aV @@ -302,13 +302,13 @@ def chiar2006(Rv=3.1,curve='ism',**kwargs): def fitzpatrick1999(Rv=3.1,**kwargs): """ From Fitzpatrick 1999 (downloaded from ASAGIO database) - + This function returns A(lambda)/A(V). - + To get A(lambda)/E(B-V), multiply the return value with Rv (A(V)=Rv*E(B-V)) - + Extra kwags are to catch unwanted keyword arguments. - + @param Rv: Rv (2.1, 3.1 or 5.0) @type Rv: float @return: wavelengths (A), A(lambda)/Av @@ -319,22 +319,22 @@ def fitzpatrick1999(Rv=3.1,**kwargs): myfile = os.path.join(basename,filename) wave,alam_ebv = ascii.read2array(myfile).T alam_av = alam_ebv/Rv - + logger.info('Fitzpatrick1999 curve with Rv=%.2f'%(Rv)) - + return wave,alam_av @memoized def fitzpatrick2004(Rv=3.1,**kwargs): """ From Fitzpatrick 2004 (downloaded from FTP) - + This function returns A(lambda)/A(V). - + To get A(lambda)/E(B-V), multiply the return value with Rv (A(V)=Rv*E(B-V)) - + Extra kwags are to catch unwanted keyword arguments. - + @param Rv: Rv (2.1, 3.1 or 5.0) @type Rv: float @return: wavelengths (A), A(lambda)/Av @@ -343,9 +343,9 @@ def fitzpatrick2004(Rv=3.1,**kwargs): filename = 'Fitzpatrick2004_Rv_%.1f.red'%(Rv) myfile = os.path.join(basename,filename) wave_inv,elamv_ebv = ascii.read2array(myfile,skip_lines=15).T - + logger.info('Fitzpatrick2004 curve with Rv=%.2f'%(Rv)) - + return 1e4/wave_inv[::-1],((elamv_ebv+Rv)/Rv)[::-1] @@ -353,9 +353,9 @@ def fitzpatrick2004(Rv=3.1,**kwargs): def donnell1994(**kwargs): """ Small improvement on Cardelli 1989 by James E. O'Donnell (1994). - + Extra kwags are to catch unwanted keyword arguments. - + @keyword Rv: Rv @type Rv: float @keyword wave: wavelengths to compute the curve on @@ -370,17 +370,17 @@ def donnell1994(**kwargs): def cardelli1989(Rv=3.1,curve='cardelli',wave=None,**kwargs): """ Construct extinction laws from Cardelli (1989). - + Improvement in optical by James E. O'Donnell (1994) - + wavelengths in Angstrom! - + This function returns A(lambda)/A(V). - + To get A(lambda)/E(B-V), multiply the return value with Rv (A(V)=Rv*E(B-V)) - + Extra kwags are to catch unwanted keyword arguments. - + @param Rv: Rv @type Rv: float @param curve: extinction curve @@ -392,17 +392,17 @@ def cardelli1989(Rv=3.1,curve='cardelli',wave=None,**kwargs): """ if wave is None: wave = np.r_[100.:100000.:10] - + all_x = 1./(wave/1.0e4) alam_aV = np.zeros_like(all_x) - + #-- infrared infrared = all_x<1.1 x = all_x[infrared] ax = +0.574*x**1.61 bx = -0.527*x**1.61 alam_aV[infrared] = ax + bx/Rv - + #-- optical optical = (1.1<=all_x) & (all_x<3.3) x = all_x[optical] @@ -418,9 +418,9 @@ def cardelli1989(Rv=3.1,curve='cardelli',wave=None,**kwargs): bx = 1.952*y + 2.908*y**2 - 3.989*y**3 - 7.985*y**4 \ + 11.102*y**5 + 5.491*y**6 -10.805*y**7 + 3.347*y**8 else: - raise ValueError,'curve %s not found'%(curve) + raise ValueError('curve %s not found'%(curve)) alam_aV[optical] = ax + bx/Rv - + #-- ultraviolet ultraviolet = (3.3<=all_x) & (all_x<8.0) x = all_x[ultraviolet] @@ -431,16 +431,16 @@ def cardelli1989(Rv=3.1,curve='cardelli',wave=None,**kwargs): ax = +1.752 - 0.316*x - 0.104 / ((x-4.67)**2 + 0.341) + Fax bx = -3.090 + 1.825*x + 1.206 / ((x-4.62)**2 + 0.263) + Fbx alam_aV[ultraviolet] = ax + bx/Rv - + #-- far UV fuv = 8.0<=all_x x = all_x[fuv] ax = -1.073 - 0.628*(x-8) + 0.137*(x-8)**2 - 0.070*(x-8)**3 bx = 13.670 + 4.257*(x-8) - 0.420*(x-8)**2 + 0.374*(x-8)**3 alam_aV[fuv] = ax + bx/Rv - + logger.info('%s curve with Rv=%.2f'%(curve.title(),Rv)) - + return wave,alam_aV @@ -448,13 +448,13 @@ def cardelli1989(Rv=3.1,curve='cardelli',wave=None,**kwargs): def seaton1979(Rv=3.1,wave=None,**kwargs): """ Extinction curve from Seaton, 1979. - + This function returns A(lambda)/A(V). - + To get A(lambda)/E(B-V), multiply the return value with Rv (A(V)=Rv*E(B-V)) - + Extra kwags are to catch unwanted keyword arguments. - + @param Rv: Rv @type Rv: float @param wave: wavelengths to compute the curve on @@ -463,33 +463,33 @@ def seaton1979(Rv=3.1,wave=None,**kwargs): @rtype: (ndarray,ndarray) """ if wave is None: wave = np.r_[1000.:10000.:10] - + all_x = 1e4/(wave) alam_aV = np.zeros_like(all_x) - + #-- far infrared x_ = np.r_[1.0:2.8:0.1] X_ = np.array([1.36,1.44,1.84,2.04,2.24,2.44,2.66,2.88,3.14,3.36,3.56,3.77,3.96,4.15,4.26,4.40,4.52,4.64]) fir = all_x<=2.7 alam_aV[fir] = np.interp(all_x[fir][::-1],x_,X_,left=0)[::-1] - + #-- infrared infrared = (2.70<=all_x) & (all_x<3.65) x = all_x[infrared] alam_aV[infrared] = 1.56 + 1.048*x + 1.01 / ( (x-4.60)**2 + 0.280) - + #-- optical optical = (3.65<=all_x) & (all_x<7.14) x = all_x[optical] alam_aV[optical] = 2.29 + 0.848*x + 1.01 / ( (x-4.60)**2 + 0.280) - + #-- ultraviolet ultraviolet = (7.14<=all_x) & (all_x<=10) x = all_x[ultraviolet] alam_aV[ultraviolet] = 16.17 - 3.20*x + 0.2975*x**2 - + logger.info('Seaton curve with Rv=%.2f'%(Rv)) - + return wave,alam_aV/Rv #} @@ -498,4 +498,4 @@ def seaton1979(Rv=3.1,wave=None,**kwargs): import doctest import pylab as pl doctest.testmod() - pl.show() \ No newline at end of file + pl.show() diff --git a/sed/testSED.py b/sed/testSED.py index c03f8533c..4b48fad1d 100644 --- a/sed/testSED.py +++ b/sed/testSED.py @@ -9,8 +9,6 @@ from ivs.sed import fit, model, builder, filters from ivs.units import constants from ivs.catalogs import sesame -from ivs.aux import loggers -from ivs.units import constants from matplotlib import mlab import unittest @@ -22,17 +20,17 @@ noMock = True # Set at True to skip integration tests. -noIntegration = False +noIntegration = False class SEDTestCase(unittest.TestCase): """Add some extra usefull assertion methods to the testcase class""" - + def create_patch(self, name, method, **kwargs): patcher = patch.object(name, method, **kwargs) thing = patcher.start() self.addCleanup(patcher.stop) return thing - + def assertArrayEqual(self, l1, l2, msg=None): msg_ = "Array is not equal to expected array: %s != %s"%(l1,l2) if msg != None: msg_ = msg @@ -40,23 +38,23 @@ def assertArrayEqual(self, l1, l2, msg=None): self.assertEqual(l1.dtype, l2.dtype, msg=msg_) for f1, f2 in zip(l1,l2): self.assertEqual(f1,f2, msg=msg_) - + def assertArrayAlmostEqual(self, l1,l2,places=None,delta=None, msg=None): msg_ = "assertArrayAlmostEqual Failed: %s != %s"%(str(l1),str(l2)) - if msg != None: msg_ = msg_ + msg + if msg != None: msg_ = msg_ + msg for f1, f2 in zip(l1, l2): self.assertAlmostEqual(f1,f2,places=places, delta=delta, msg=msg_) - + def assertAllBetween(self, l, low, high, msg=None): self.assertGreaterEqual(min(l), low, msg=msg) self.assertLessEqual(max(l), high, msg=msg) - + def assertAllBetweenDiff(self, l, low, high, places=4, msg=None): self.assertGreaterEqual(min(l), low, msg=msg) self.assertLessEqual(max(l), high, msg=msg) lr = np.round(l, decimals=places) self.assertTrue(len(np.unique(lr)) > 1, msg="All elements are equal: %s"%(l)) - + def assertInList(self, el, lst, msg=None): msg_ = "The given element %s is not in list %s"%(el, lst) if msg != None: msg_=msg @@ -67,7 +65,7 @@ def assertInList(self, el, lst, msg=None): except Exception: res.append(el == l) self.assertTrue(any(res), msg=msg_) - + def assert_mock_args_in_last_call(self, mck, args=None, kwargs=None, msg=None): args_, kwargs_ = mck.call_args if args != None: @@ -76,7 +74,7 @@ def assert_mock_args_in_last_call(self, mck, args=None, kwargs=None, msg=None): 'Argument %s was not in call to %s (called with args: %s)'%(arg, mck, args_) self.assertInList(arg, args_, msg=msg_) if kwargs != None: - for key in kwargs.keys(): + for key in list(kwargs.keys()): msg_ = msg if msg != None else \ 'Key Word Argument %s=%s was not in call to %s (called with args: %s)'% \ (key, kwargs[key], mck, kwargs_) @@ -85,10 +83,10 @@ def assert_mock_args_in_last_call(self, mck, args=None, kwargs=None, msg=None): self.assertEqual(kwargs[key], kwargs_[key], msg_) except Exception: self.assertArrayAlmostEqual(kwargs[key], kwargs_[key], places=5) - + class ModelTestCase(SEDTestCase): - + @classmethod def setUpClass(ModelTestCase): """ Setup the tmap grid as it is smaller and thus faster as kurucz""" @@ -96,11 +94,11 @@ def setUpClass(ModelTestCase): grid1 = dict(grid='tmaptest') grid2 = dict(grid='tmaptest') model.set_defaults_multiple(grid1,grid2) - model.copy2scratch(z='*', Rv='*') - - def setUp(self): + model.copy2scratch(z='*', Rv='*') + + def setUp(self): self.photbands = ['STROMGREN.U', '2MASS.H'] - + def testGetItablePixSingle(self): """ model.get_itable_pix() single case """ bgrid = {'teff': array([ 5500., 6874., 9645., 7234., 5932.]), @@ -108,18 +106,18 @@ def testGetItablePixSingle(self): 'ebv': array([ 0.0018, 0.0077, 0.0112, 0.0046, 0.0110]), 'rv': array([ 2.20, 2.40, 2.60, 2.80, 3.00]), 'z': array([ -0.5, -0.4, -0.3, -0.2, 0.0])} - + flux_,Labs_ = model.get_itable_pix(photbands=self.photbands, **bgrid) - + flux = [[3255462., 13286738., 53641850., 16012786., 4652578.69189492], [967634., 1262321., 1789486., 1336016., 1066763.]] - Labs = [0.819679, 1.997615, 7.739527, 2.450019, 1.107916] - + Labs = [0.819679, 1.997615, 7.739527, 2.450019, 1.107916] + self.assertArrayAlmostEqual(flux_[0],flux[0],delta=100) self.assertArrayAlmostEqual(flux_[1],flux[1],delta=100) - self.assertArrayAlmostEqual(Labs_,Labs,places=3) - - + self.assertArrayAlmostEqual(Labs_,Labs,places=3) + + def testGetItablePixBinary(self): """ model.get_itable_pix() multiple case """ bgrid = {'teff': array([ 22674., 21774., 22813., 29343., 28170.]), @@ -130,20 +128,20 @@ def testGetItablePixBinary(self): 'logg2': array([ 4.67, 4.92, 5.46, 4.96, 4.85]), 'ebv2': array([ 0.0018, 0.0077, 0.0112, 0.0046, 0.0110]), 'rad2': array([ 6.99, 9.61, 9.55, 6.55, 3.99])} - + flux_,Labs_ = model.get_itable_pix(photbands=self.photbands, **bgrid) - + flux = [[1.69858e+11, 2.88402e+11, 1.98190e+11, 1.00088e+11, 1.39618e+11], [5.78019e+08, 1.04221e+09, 7.42561e+08, 3.63444e+08, 5.52846e+08]] Labs = [67046.5663, 115877.0362, 81801.3768, 39791.7467, 56369.3989] - + self.assertArrayAlmostEqual(flux_[0],flux[0],delta=0.01e+11) self.assertArrayAlmostEqual(flux_[1],flux[1],delta=0.01e+09) self.assertArrayAlmostEqual(Labs_,Labs,places=3) - + def testGetItableSingle(self): """ model.get_itable() single case """ - + flux_,Labs_ = model.get_itable(photbands=self.photbands, teff=6874, logg=4.21, ebv=0.0077, z=-0.2) flux = [13428649.32576484, 1271090.21316342] @@ -153,25 +151,25 @@ def testGetItableSingle(self): self.assertAlmostEqual(flux_[1],flux[1], delta=100) self.assertAlmostEqual(Labs_,Labs, delta=100) - + def testGetItableBinary(self): """ model.get_itable() multiple case """ - + flux_,Labs_ = model.get_itable(photbands=self.photbands, teff=25000, logg=5.12, ebv=0.001, teff2=33240, logg2=5.86, ebv2=0.001) - + flux = [3.31834622e+09, 1.25032866e+07] Labs = 1277.54498257 - + self.assertAlmostEqual(flux_[0],flux[0], delta=100) self.assertAlmostEqual(flux_[1],flux[1], delta=100) self.assertAlmostEqual(Labs_,Labs, delta=100) def testGetTable(self): """ model.get_table() single case """ - + wave, flux = model.get_table(teff=6874, logg=4.21, ebv=0.0) - + self.assertEqual(len(wave), len(flux)) self.assertEqual(len(wave), 1221) self.assertAlmostEqual(wave[0], 90.9, delta=0.1) @@ -182,14 +180,14 @@ def testGetTable(self): self.assertAlmostEqual(flux[-1], 0.020199, delta=0.0001) self.assertAlmostEqual(flux[400], 24828659.5845, delta=0.0001) self.assertAlmostEqual(flux[800], 1435461.60457, delta=0.0001) - + def testGetTableBinary(self): """ model.get_table() multiple case """ - - wave,flux = model.get_table(teff=25000, logg=5.12, ebv=0.001, + + wave,flux = model.get_table(teff=25000, logg=5.12, ebv=0.001, teff2=33240, logg2=5.86, ebv2=0.001) - - + + self.assertEqual(len(wave), len(flux)) self.assertEqual(len(wave), 123104) self.assertAlmostEqual(wave[0], 1000, delta=0.1) @@ -200,9 +198,9 @@ def testGetTableBinary(self): self.assertAlmostEqual(flux[-1], 2457826.26898, delta=0.0001) self.assertAlmostEqual(flux[40000], 141915936.111, delta=0.001) self.assertAlmostEqual(flux[80000], 12450102.801, delta=0.001) - + class PixFitTestCase(SEDTestCase): - + @classmethod def setUpClass(PixFitTestCase): """ Setup the tmap grid as it is smaller and thus faster as kurucz""" @@ -211,32 +209,32 @@ def setUpClass(PixFitTestCase): grid2 = dict(grid='tmaptest') model.set_defaults_multiple(grid1,grid2) model.copy2scratch(z='*', Rv='*') - - + + def setUp(self): self.photbands = ['STROMGREN.U', '2MASS.H'] - + def testGenerateGridPixSingle(self): """ fit.generate_grid_pix() single case """ grid = fit.generate_grid_single_pix(self.photbands,teffrange=(5000,10000), loggrange=(3.50,4.50),ebvrange=(0.0, 0.012),zrange=(-0.5,0.0), rvrange=(-inf,inf),points=50) - + self.assertTrue('teff' in grid) self.assertTrue('logg' in grid) self.assertTrue('ebv' in grid) self.assertTrue('rv' in grid) self.assertTrue('z' in grid) - + self.assertFalse('teff2' in grid) self.assertFalse('rad' in grid) - + self.assertAllBetweenDiff(grid['teff'], 5000, 10000, places=-2) self.assertAllBetweenDiff(grid['logg'], 3.5, 4.5, places=1) self.assertAllBetweenDiff(grid['ebv'], 0.0, 0.012, places=3) self.assertAllBetweenDiff(grid['rv'], 2.1, 3.1, places=1) self.assertAllBetweenDiff(grid['z'], -0.5, 0.0, places=1) - + def testGenerateGridPixBinary(self): """ fit.generate_grid_pix() binary case """ grid = fit.generate_grid_pix(self.photbands,teffrange=(5000,10000), @@ -244,7 +242,7 @@ def testGenerateGridPixBinary(self): logg2range=(4.50, 6.50), ebvrange=(0.0, 0.012), rvrange=(2.1,3.1), rv2range=(2.1,3.1), masses=[0.47*1.63, 0.47], points=50) - + self.assertTrue('teff' in grid) self.assertTrue('logg' in grid) self.assertTrue('teff2' in grid) @@ -253,36 +251,36 @@ def testGenerateGridPixBinary(self): self.assertTrue('ebv2' in grid) self.assertTrue('rad' in grid) self.assertTrue('rad2' in grid) - self.assertFalse('z' in grid) - self.assertFalse('z2' in grid) - - self.assertListEqual(grid['ebv'].tolist(), grid['ebv2'].tolist(), + self.assertFalse('z' in grid) + self.assertFalse('z2' in grid) + + self.assertListEqual(grid['ebv'].tolist(), grid['ebv2'].tolist(), msg="ebv should be the same for both components.") self.assertListEqual(grid['rv'].tolist(), grid['rv2'].tolist(), msg="Rv should be the same for both components.") - + self.assertAllBetweenDiff(grid['teff'], 5000, 10000, places=-2) self.assertAllBetweenDiff(grid['logg'], 3.5, 4.5, places=1) self.assertAllBetweenDiff(grid['ebv'], 0.0, 0.012, places=3) self.assertAllBetweenDiff(grid['rv'], 2.1, 3.1, places=1) self.assertAllBetweenDiff(grid['teff2'], 30000, 40000, places=-2) self.assertAllBetweenDiff(grid['logg2'], 4.5, 6.5, places=1) - + G, Msol, Rsol = constants.GG_cgs, constants.Msol_cgs, constants.Rsol_cgs rad = np.sqrt(G*0.47*1.63*Msol/10**grid['logg'])/Rsol rad2 = np.sqrt(G*0.47*Msol/10**grid['logg2'])/Rsol - + self.assertListEqual(rad.tolist(), grid['rad'].tolist()) self.assertListEqual(rad2.tolist(), grid['rad2'].tolist()) - - + + def testGenerateGridPixMultiple(self): """ fit.generate_grid_pix() multiple case """ grid = fit.generate_grid_pix(self.photbands,teffrange=(5000,10000), teff2range=(20000, 40000), loggrange=(3.5,4.5),radrange=(0.1,1.0), logg2range=(4.50, 6.50), ebvrange=(0.0, 0.012),rad2range=(1.0,10.0), points=50) - + self.assertTrue('teff' in grid) self.assertTrue('logg' in grid) self.assertTrue('teff2' in grid) @@ -291,13 +289,13 @@ def testGenerateGridPixMultiple(self): self.assertTrue('ebv2' in grid) self.assertTrue('rad' in grid) self.assertTrue('rad2' in grid) - self.assertFalse('z' in grid) - self.assertFalse('z2' in grid) - self.assertFalse('rv' in grid) - self.assertFalse('rv2' in grid) - + self.assertFalse('z' in grid) + self.assertFalse('z2' in grid) + self.assertFalse('rv' in grid) + self.assertFalse('rv2' in grid) + self.assertListEqual(grid['ebv'].tolist(), grid['ebv2'].tolist()) - + self.assertAllBetweenDiff(grid['teff'], 5000, 10000, places=-2) self.assertAllBetweenDiff(grid['logg'], 3.5, 4.5, places=1) self.assertAllBetweenDiff(grid['ebv'], 0.0, 0.012, places=3) @@ -305,7 +303,7 @@ def testGenerateGridPixMultiple(self): self.assertAllBetweenDiff(grid['teff2'], 20000, 40000, places=-2) self.assertAllBetweenDiff(grid['logg2'], 4.5, 6.5, places=1) self.assertAllBetweenDiff(grid['rad2'], 1.0, 10.0, places=0) - + @unittest.skipIf(noMock, "Mock not installed") def testiGridSearch(self): """ fit.igrid_search_pix() """ @@ -324,50 +322,50 @@ def testiGridSearch(self): chisqs = array([260.1680, 255.4640, 251.1565, 221.6586, 233.4854]) scales = array([3.9450e-22, 4.2714e-22, 3.8880e-22, 2.2365e-22, 2.4783e-22]) e_scales = array([1.7789e-22, 1.9005e-22, 1.7449e-22, 1.0679e-22, 1.1745e-22]) - + mock_model = self.create_patch(model, 'get_itable_pix', return_value=(syn_flux, lumis)) mock_color = self.create_patch(filters, 'is_color', return_value=False) mock_stat = self.create_patch(fit, 'stat_chi2', return_value=(chisqs,scales,e_scales)) - + chisqs_,scales_,e_scales_,lumis_ = fit.igrid_search_pix(meas, emeas, photbands, model_func=model.get_itable_pix, stat_func=fit.stat_chi2, **grid) - + self.assertListEqual(chisqs.tolist(), chisqs_.tolist()) self.assertListEqual(scales_.tolist(), scales.tolist()) self.assertListEqual(e_scales_.tolist(), e_scales.tolist()) self.assertListEqual(lumis_.tolist(), lumis.tolist()) - + self.assert_mock_args_in_last_call(mock_model, kwargs=grid) self.assert_mock_args_in_last_call(mock_model, kwargs={'photbands':photbands}) - + mock_stat.assert_called() - + def testCreateParameterDict(self): """ fit.create_parameter_dict() """ - + pars = dict(teff_value=5000, teff_min=4000, teff_max=6000, logg_value=4.5, logg_min=3.5, logg_max=5.5, ebv_value=0.001, ebv_min=0.0, ebv_max=0.01, ebv_vary=False, z_value=0, z_vary=False, rad_expr='G*sqrt(2*logg)/m') - + parameters = fit.create_parameter_dict(**pars) - + self.assertTrue('value' in parameters) self.assertTrue('min' in parameters) self.assertTrue('max' in parameters) self.assertTrue('vary' in parameters) self.assertTrue('expr' in parameters) - + names = parameters['names'] self.assertTrue('teff' in names) self.assertTrue('logg' in names) self.assertTrue('ebv' in names) self.assertTrue('z' in names) self.assertTrue('rad' in names) - - for key in parameters.keys(): + + for key in list(parameters.keys()): self.assertTrue(type(parameters[key]) == np.ndarray) - + self.assertEqual(parameters['value'][names == 'teff'], 5000) self.assertEqual(parameters['value'][names == 'logg'], 4.5) self.assertEqual(parameters['value'][names == 'ebv'], 0.001) @@ -380,55 +378,55 @@ def testCreateParameterDict(self): self.assertEqual(parameters['max'][names == 'ebv'], 0.01) self.assertEqual(parameters['vary'][names == 'ebv'], False) self.assertEqual(parameters['expr'][names == 'rad'], 'G*sqrt(2*logg)/m') - - - + + + class MinimizeFitTestCase(SEDTestCase): - + def testCreateParameterDict(self): """ fit.create_parameter_dict() """ pars = {'logg_value': 4.0, 'vrad_vary': False, 'z_value': -0.3, 'vrad_value': 0, 'logg_vary': True, 'rv_min': 2.1, 'vrad_min': 0, 'vrad_max': 0, 'z_max': 0.0, 'ebv_max': 0.015, 'teff_value': 6000, 'z_vary': True, 'rv_value': 2.4, - 'teff_vary': True, 'logg_min': 3.5, 'rv_max': 3.1, 'z_min': -0.5, + 'teff_vary': True, 'logg_min': 3.5, 'rv_max': 3.1, 'z_min': -0.5, 'ebv_min': 0.005, 'teff_min': 5000, 'logg_max': 4.5, 'ebv_value': 0.007, 'teff_max': 7000, 'rv_vary': True, 'ebv_vary': True} - exp = {'max': array([3.1, 7000, 4.5, 0.015, 0, 0.0], dtype=object), - 'vary': array([True, True, True, True, False, True], dtype=object), - 'names': array(['rv', 'teff', 'logg', 'ebv', 'vrad', 'z'], dtype='|S4'), + exp = {'max': array([3.1, 7000, 4.5, 0.015, 0, 0.0], dtype=object), + 'vary': array([True, True, True, True, False, True], dtype=object), + 'names': array(['rv', 'teff', 'logg', 'ebv', 'vrad', 'z'], dtype='U4'), 'value': array([2.4, 6000, 4.0, 0.007, 0, -0.3], dtype=object), 'min': array([2.1, 5000, 3.5, 0.005, 0, -0.5], dtype=object)} - + res = fit.create_parameter_dict(**pars) - + self.assertArrayEqual(res['max'], exp['max']) self.assertArrayEqual(res['vary'], exp['vary']) self.assertArrayEqual(res['min'], exp['min']) self.assertArrayEqual(res['names'], exp['names']) self.assertArrayEqual(res['value'], exp['value']) - + @unittest.skipIf(noMock, "Mock not installed") def testiMinimize(self): """ fit.iminimize() normal mocked """ - + gm_return = (['minimizer'], ['startpars'], ['newmodels'], ['chisqrs']) gifm_return = (['chisqr'], ['nfev'], ['scale'], ['labs'], ['grid']) cpd_return = dict(names=['teff'], value=[5000], min=[4000], max=[6000], vary=[True]) - + mock_sfit_m = self.create_patch(sigproc.fit, 'minimize', return_value='minimizer') mock_sfit_gm = self.create_patch(sigproc.fit, 'grid_minimize', return_value=gm_return) mock_sfit_sp = self.create_patch(sigproc.fit.Function, 'setup_parameters') mock_fit_gifm = self.create_patch(fit, '_get_info_from_minimizer', return_value=gifm_return) mock_fit_cpd = self.create_patch(fit, 'create_parameter_dict', return_value=cpd_return) - + meas = array([1.25e9, 2.34e8]) emeas = array([1.25e7, 2.34e6]) photbands = array(['J', 'C']) - + #-- 1 point grid, chisqr, nfev, scale, lumis = fit.iminimize(meas,emeas,photbands, points=None, epsfcn=0.01 ) - + self.assert_mock_args_in_last_call(mock_sfit_sp, kwargs=cpd_return) self.assert_mock_args_in_last_call(mock_sfit_m, args=[photbands, meas], kwargs=dict(epsfcn=0.01, weights=1/emeas)) self.assert_mock_args_in_last_call(mock_fit_gifm, args=[['minimizer'], photbands, meas, emeas]) @@ -437,27 +435,27 @@ def testiMinimize(self): self.assertListEqual(nfev,['nfev']) self.assertListEqual(scale,['scale']) self.assertListEqual(lumis,['labs']) - + @unittest.skipIf(noMock, "Mock not installed") def testiMinimizeGrid(self): """ fit.iminimize() grid mocked """ gm_return = (['minimizer'], ['startpars'], ['newmodels'], ['chisqrs']) gifm_return = (['chisqr'], ['nfev'], ['scale'], ['labs'], ['grid']) cpd_return = dict(names=['teff'], value=[5000], min=[4000], max=[6000], vary=[True]) - + mock_sfit_m = self.create_patch(sigproc.fit, 'minimize', return_value='minimizer') mock_sfit_gm = self.create_patch(sigproc.fit, 'grid_minimize', return_value=gm_return) mock_sfit_sp = self.create_patch(sigproc.fit.Function, 'setup_parameters') mock_fit_gifm = self.create_patch(fit, '_get_info_from_minimizer', return_value=gifm_return) mock_fit_cpd = self.create_patch(fit, 'create_parameter_dict', return_value=cpd_return) - + meas = array([1.25e9, 2.34e8]) emeas = array([1.25e7, 2.34e6]) photbands = array(['J', 'C']) - + #-- 10 point grid, chisqr, nfev, scale, lumis = fit.iminimize(meas,emeas,photbands, points=10, epsfcn=0.01 ) - + self.assert_mock_args_in_last_call(mock_sfit_sp, kwargs=cpd_return) self.assert_mock_args_in_last_call(mock_sfit_gm, args=[photbands, meas], kwargs=dict(epsfcn=0.01, weights=1/emeas, points=10)) self.assert_mock_args_in_last_call(mock_fit_gifm, args=[['minimizer'], photbands, meas, emeas]) @@ -466,38 +464,37 @@ def testiMinimizeGrid(self): self.assertListEqual(nfev,['nfev']) self.assertListEqual(scale,['scale']) self.assertListEqual(lumis,['labs']) - + class BuilderTestCase(SEDTestCase): - + @classmethod def setUpClass(BuilderTestCase): if not noMock: sesame.search = mock.Mock(return_value={'plx':(0.0,0.0)}) - builder.SED.load_photometry = mock.Mock(return_value=None) - - + builder.SED.load_photometry = mock.Mock(return_value=None) + def setUp(self): self.sed = builder.SED(ID='TEST',load_fits=False) self.sed.master = {} self.sed.results = {'igrid_search':{}} - + def testInit(self): """ builder.sed.__init__() ToDo """ self.assertTrue(self.sed.ID == 'TEST') - + def testCollectResults(self): """ builder.sed.collect_results() """ bgrid = {'teff': array([ 22674., 21774., 22813., 29343., 28170.]), 'logg': array([ 5.75, 6.07, 6.03, 6.38, 5.97]), 'ebv': array([ 0.0018, 0.0077, 0.0112, 0.0046, 0.0110]), 'rv': array([ 2.20, 2.40, 2.60, 2.80, 3.00]), - 'z': array([0,0,0,0,0])} - fgrid = {'chisq': array([1.,3.,2.,0.1,np.nan])} - + 'z': array([0,0,0,0,0])} + fgrid = {'chisq': array([1.,3.,2.,0.1,np.nan])} + self.sed.collect_results(grid=bgrid, fitresults=fgrid, mtype='igrid_search', selfact='chisq') res = self.sed.results['igrid_search']['grid'] - + self.assertListEqual(res['chisq'].tolist(), [3.0, 2.0, 1.0, 0.1]) self.assertListEqual(res['teff'].tolist(), [21774.,22813.,22674.,29343.]) self.assertListEqual(res['logg'].tolist(), [6.07,6.03,5.75,6.38]) @@ -506,19 +503,19 @@ def testCollectResults(self): self.assertListEqual(res['z'].tolist(), [0,0,0,0]) self.assertListEqual(res['ci_raw'].tolist(), [0.,0.,0.,0.]) self.assertListEqual(res['ci_red'].tolist(), [0.,0.,0.,0.]) - + def testCalculateDegreesOfFreedom(self): """ builder.sed.calculateDF() """ ranges = {'teffrange': (5000,6000), 'teff2range': (20000,50000), 'loggrange': (4.5,4.5), 'logg2range': (4.5,5.5), 'ebvrange': (0.0,0.01), 'ebv2range': (0.0,0.01), 'zrange': (-0.5,0.5)} - + df, dfinfo = self.sed.calculateDF(**ranges) - + self.assertEqual(df, 6) self.assertTrue('ebv' in dfinfo) self.assertFalse('ebv2' in dfinfo) - + @unittest.skipIf(noMock, "Mock not installed") def testCalculateStatistics(self): """ builder.sed.calculate_statistics()""" @@ -527,40 +524,40 @@ def testCalculateStatistics(self): array([ 5.75, 6.07, 6.03, 6.38, 5.97]), array([ 0.0018, 0.0077, 0.0112, 0.0046, 0.0110]), array([ 2.20, 2.40, 2.60, 2.80, 3.00]), - array([0,0,0,0,0]), + array([0,0,0,0,0]), array([1.,3.,2.,0.1,10.0])] master = np.rec.fromarrays(grid,dtype=dtypes) master = mlab.rec_append_fields(master, 'ci_raw', np.zeros(len(master))) master = mlab.rec_append_fields(master, 'ci_red', np.zeros(len(master))) self.sed.results['igrid_search']['grid'] = master self.sed.master['include'] = [True,True,False,True,True] - + with mock.patch.object(builder.SED, 'calculateDF', return_value=5) as mock_method: self.sed.calculate_statistics(df=5) - + res = self.sed.results['igrid_search'] raw = [0.6826894, 0.9167354, 0.8427007, 0.2481703, 0.9984345] red = [0.2481703, 0.4161175, 0.3452791, 0.0796556, 0.6826894] - + self.assertFalse(mock_method.called) self.assertArrayAlmostEqual(res['grid']['ci_raw'].tolist(), raw, places=5) self.assertArrayAlmostEqual(res['grid']['ci_red'].tolist(), red, places=5) self.assertEqual(res['factor'], 10.0) - - + + def testCalculateConfidenceIntervals(self): """ builder.sed.calculate_confidence_intervals() ToDo """ pass - + def testGenerateRanges(self): """ builder.sed.generate_ranges() ToDo """ pass - - + + @unittest.skipIf(noMock, "Mock not installed") def testiGridSearch(self): """ builder.sed.igrid_search() mocked """ - + ranges = {'teffrange':(20000,30000), 'loggrange':(5.5,6.5)} grid = {'teff': array([ 22674., 21774., 22813., 29343., 28170.]), 'logg': array([ 5.75, 6.07, 6.03, 6.38, 5.97])} @@ -576,10 +573,10 @@ def testiGridSearch(self): mock_sed_sci = self.create_patch(builder.SED, 'store_confidence_intervals') mock_sed_sbm = self.create_patch(builder.SED, 'set_best_model') mock_p2s = self.create_patch(builder, 'photometry2str', return_value='TEST') - + self.sed.master = {'include':0, 0:0, 'cmeas':[0,0], 'e_cmeas':[0,0], 'photband':[0,0]} self.sed.igrid_search(teffrange=(10,20), loggrange=(4.5,4.5), ebvrange=(0,0.1), zrange=(0,0),rvrange=(3.1,3.1),vradrange=(0,0), df=4, set_model=True) - + mock_sed_gr.assert_called_with(teffrange=(10,20), loggrange=(4.5,4.5), ebvrange=(0,0.1), zrange=(0,0),rvrange=(3.1,3.1),vradrange=(0,0)) mock_fit_ggp.assert_called() mock_fit_isp.assert_called() @@ -591,16 +588,16 @@ def testiGridSearch(self): self.assert_mock_args_in_last_call(mock_sed_sci, kwargs=ci) mock_sed_cci.assert_called() mock_sed_sbm.assert_called() - + class XIntegrationTestCase(SEDTestCase): - + photbands = ['STROMGREN.U', 'STROMGREN.B', 'STROMGREN.V', 'STROMGREN.Y', '2MASS.H', '2MASS.J', '2MASS.KS'] measHot = None measCold = None measBin = None - + @classmethod def setUpClass(cls): if not noMock: @@ -611,40 +608,40 @@ def setUpClass(cls): model.copy2scratch(z='*', Rv='*') measCold = model.get_itable_pix(photbands=cls.photbands, teff=array([6000]), \ logg=array([4.0]),ebv=array([0.01]), rv=array([2.8]), z=array([-0.25]))[0][:,0] - + np.random.seed(111) cls.measCold = measCold - + # ==== HOT model ==== model.set_defaults(grid='tmaptest') model.copy2scratch(z='*', Rv='*') measHot = model.get_itable_pix(photbands=cls.photbands, teff=array([30000]), \ logg=array([5.5]),ebv=array([0.01]), rv=3.1, z=0.0)[0][:,0] - + np.random.seed(111) cls.measHot = measHot - + # ==== BINARY model ==== grid1 = dict(grid='kurucztest') grid2 = dict(grid='tmaptest') model.set_defaults_multiple(grid1,grid2) model.clean_scratch(z='*', Rv='*') model.copy2scratch(z='*', Rv='*') - + G, Msol, Rsol = constants.GG_cgs, constants.Msol_cgs, constants.Rsol_cgs masses = [0.85, 0.50] rad = array([np.sqrt(G*masses[0]*Msol/10**4.0)/Rsol]) rad2 = array([np.sqrt(G*masses[1]*Msol/10**5.5)/Rsol]) - + measBin = model.get_itable_pix(photbands=cls.photbands, teff=array([6000]), \ logg=array([4.0]),ebv=array([0.01]), teff2=array([30000]), logg2=array([5.5]), ebv2=array([0.01]), rad=rad, rad2=rad2)[0][:,0] - + np.random.seed(111) cls.measBin = measBin cls.masses = masses - + @unittest.skipIf(noIntegration, "Integration tests are skipped.") def testiGrid_searchSingleCold(self): """ INTEGRATION igrid_search single star (kurucz)""" @@ -655,15 +652,15 @@ def testiGrid_searchSingleCold(self): units = ['erg/s/cm2/AA' for i in meas] source = ['SYNTH' for i in meas] sed.add_photometry_fromarrays(meas, emeas, units, self.photbands, source) - + model.set_defaults(grid='kurucztest') model.copy2scratch(z='*', Rv='*') - + np.random.seed(111) - sed.igrid_search(points=100000,teffrange=(5000, 7000),loggrange=(3.5, 4.5), + sed.igrid_search(points=100000,teffrange=(5000, 7000),loggrange=(3.5, 4.5), ebvrange=(0.005, 0.015),zrange=(-0.5,0.0),rvrange=(2.1,3.1), vradrange=(0,0),df=None,CI_limit=0.95,set_model=True) - + self.assertAlmostEqual(sed.results['igrid_search']['CI']['teff'], 6000, delta=50) self.assertAlmostEqual(sed.results['igrid_search']['CI']['logg'], 3.98, delta=0.1) self.assertAlmostEqual(sed.results['igrid_search']['CI']['ebv'], 0.011, delta=0.02) @@ -679,16 +676,16 @@ def testiGrid_searchSingleCold(self): self.assertAlmostEqual(sed.results['igrid_search']['CI']['ebv_u'], 0.015, delta=0.02) self.assertAlmostEqual(sed.results['igrid_search']['CI']['rv_u'], 3.1, delta=0.1) self.assertAlmostEqual(sed.results['igrid_search']['CI']['z_u'], -0.05, delta=0.1) - + # check that the best model is stored self.assertTrue('model' in sed.results['igrid_search']) self.assertTrue('synflux' in sed.results['igrid_search']) self.assertTrue('chi2' in sed.results['igrid_search']) self.assertEqual(len(sed.results['igrid_search']['model']), 3, msg='stored model has wrong number of collumns (should be 3)') self.assertEqual(len(sed.results['igrid_search']['synflux']), 3, msg='stored synflux has wrong number of collumns (should be 3)') - - - @unittest.skipIf(noIntegration, "Integration tests are skipped.") + + + @unittest.skipIf(noIntegration, "Integration tests are skipped.") def testiGrid_searchSingleHot(self): """ INTEGRATION igrid_search single star (tmap) """ sed = builder.SED(ID='TEST', load_fits=False) @@ -698,15 +695,15 @@ def testiGrid_searchSingleHot(self): units = ['erg/s/cm2/AA' for i in meas] source = ['SYNTH' for i in meas] sed.add_photometry_fromarrays(meas, emeas, units, self.photbands, source) - + model.set_defaults(grid='tmaptest') model.copy2scratch(z='*', Rv='*') - + np.random.seed(111) - sed.igrid_search(points=100000,teffrange=(25000, 35000),loggrange=(5.0, 6.0), + sed.igrid_search(points=100000,teffrange=(25000, 35000),loggrange=(5.0, 6.0), ebvrange=(0.005, 0.015),zrange=(0,0),rvrange=(3.1,3.1), vradrange=(0,0),df=None,CI_limit=0.95,set_model=True) - + self.assertAlmostEqual(sed.results['igrid_search']['CI']['teff'], 30200, delta=250) self.assertAlmostEqual(sed.results['igrid_search']['CI']['logg'], 5.67, delta=0.1) self.assertAlmostEqual(sed.results['igrid_search']['CI']['ebv'], 0.0078, delta=0.02) @@ -716,15 +713,15 @@ def testiGrid_searchSingleHot(self): self.assertAlmostEqual(sed.results['igrid_search']['CI']['teff_u'], 31623, delta=250) self.assertAlmostEqual(sed.results['igrid_search']['CI']['logg_u'], 6.0, delta=0.1) self.assertAlmostEqual(sed.results['igrid_search']['CI']['ebv_u'], 0.015, delta=0.02) - + # check that the best model is stored self.assertTrue('model' in sed.results['igrid_search']) self.assertTrue('synflux' in sed.results['igrid_search']) self.assertTrue('chi2' in sed.results['igrid_search']) self.assertEqual(len(sed.results['igrid_search']['model']), 3, msg='stored model has wrong number of collumns (should be 3)') self.assertEqual(len(sed.results['igrid_search']['synflux']), 3, msg='stored synflux has wrong number of collumns (should be 3)') - - @unittest.skipIf(noIntegration, "Integration tests are skipped.") + + @unittest.skipIf(noIntegration, "Integration tests are skipped.") def testiGrid_searchBinary(self): """ INTEGRATION igrid_search binary star (kurucz-tmap) """ sed = builder.BinarySED(ID='TEST', load_fits=False) @@ -734,11 +731,11 @@ def testiGrid_searchBinary(self): units = ['erg/s/cm2/AA' for i in meas] source = ['SYNTH' for i in meas] sed.add_photometry_fromarrays(meas, emeas, units, self.photbands, source) - + grid1 = dict(grid='kurucztest') grid2 = dict(grid='tmaptest') model.set_defaults_multiple(grid1,grid2) - + np.random.seed(111) sed.igrid_search(points=100000,teffrange=[(5000, 7000),(25000, 35000)], loggrange=[(3.5, 4.5),(5.0, 6.0)], @@ -747,14 +744,14 @@ def testiGrid_searchBinary(self): radrange=[(0,10),(0,10)], masses=self.masses, df=None,CI_limit=0.95,set_model=False) - + teff = sed.results['igrid_search']['CI']['teff'] logg = sed.results['igrid_search']['CI']['logg'] ebv = sed.results['igrid_search']['CI']['ebv'] teff2 = sed.results['igrid_search']['CI']['teff2'] logg2 = sed.results['igrid_search']['CI']['logg2'] ebv2 = sed.results['igrid_search']['CI']['ebv2'] - + self.assertAlmostEqual(sed.results['igrid_search']['CI']['teff'], 6134, delta=50) self.assertAlmostEqual(sed.results['igrid_search']['CI']['logg'], 4.38, delta=0.25) self.assertAlmostEqual(sed.results['igrid_search']['CI']['ebv'], 0.008, delta=0.002) @@ -773,7 +770,7 @@ def testiGrid_searchBinary(self): self.assertAlmostEqual(sed.results['igrid_search']['CI']['teff2_u'], 33025, delta=250) self.assertAlmostEqual(sed.results['igrid_search']['CI']['logg2_u'], 5.99, delta=0.1) self.assertAlmostEqual(sed.results['igrid_search']['CI']['ebv2_u'], 0.015, delta=0.002) - + @unittest.skipIf(noIntegration, "Integration tests are skipped.") def testiMinimizeSingleCold(self): """ INTEGRATION iminimize single star (kurucz) """ @@ -784,42 +781,42 @@ def testiMinimizeSingleCold(self): units = ['erg/s/cm2/AA' for i in meas] source = ['SYNTH' for i in meas] sed.add_photometry_fromarrays(meas, emeas, units, self.photbands, source) - + model.set_defaults(grid='kurucztest') model.copy2scratch(z='*', Rv='*') - - np.random.seed(111) + + np.random.seed(111) sed.iminimize(teff=6000, logg=4.0, ebv=0.007, z=-0.3, rv=2.4, vrad=0, teffrange=(5000, 7000),loggrange=(3.5, 4.5),zrange=(-0.5,0.0), ebvrange=(0.005, 0.015), rvrange=(2.1,3.1),vradrange=(0,0), points=None,df=None,CI_limit=0.60,calc_ci=True, set_model=True) - + self.assertAlmostEqual(sed.results['iminimize']['CI']['teff'], 6036, delta=50) self.assertAlmostEqual(sed.results['iminimize']['CI']['logg'], 4.19, delta=0.1) self.assertAlmostEqual(sed.results['iminimize']['CI']['ebv'], 0.015, delta=0.02) self.assertAlmostEqual(sed.results['iminimize']['CI']['z'], -0.21, delta=0.1) self.assertAlmostEqual(sed.results['iminimize']['CI']['rv'], 2.1, delta=0.3) self.assertAlmostEqual(sed.results['iminimize']['CI']['scale'], 1, delta=0.5) - + self.assertAlmostEqual(sed.results['iminimize']['CI']['teff_l'], 6025, delta=50) self.assertAlmostEqual(sed.results['iminimize']['CI']['teff_u'], 6036, delta=50) self.assertAlmostEqual(sed.results['iminimize']['CI']['scale_l'], 1, delta=0.5) self.assertAlmostEqual(sed.results['iminimize']['CI']['scale_u'], 1, delta=0.5) - + self.assertEqual(sed.results['iminimize']['grid']['teffstart'][0], 6000) self.assertEqual(sed.results['iminimize']['grid']['loggstart'][0], 4.0) self.assertEqual(sed.results['iminimize']['grid']['ebvstart'][0], 0.007) self.assertEqual(sed.results['iminimize']['grid']['zstart'][0], -0.3) self.assertEqual(sed.results['iminimize']['grid']['rvstart'][0], 2.4) self.assertAlmostEqual(sed.results['iminimize']['grid']['chisq'][0], 3.9, delta=1) - + self.assertTrue('model' in sed.results['iminimize']) self.assertTrue('synflux' in sed.results['iminimize']) self.assertTrue('chi2' in sed.results['iminimize']) self.assertEqual(len(sed.results['iminimize']['model']), 3, msg='stored model has wrong number of collumns (should be 3)') self.assertEqual(len(sed.results['iminimize']['synflux']), 3, msg='stored synflux has wrong number of collumns (should be 3)') - - + + @unittest.skipIf(noIntegration, "Integration tests are skipped.") def testiMinimizeSingleHot(self): """ INTEGRATION iminimize single star (tmap) """ @@ -830,21 +827,21 @@ def testiMinimizeSingleHot(self): units = ['erg/s/cm2/AA' for i in meas] source = ['SYNTH' for i in meas] sed.add_photometry_fromarrays(meas, emeas, units, self.photbands, source) - + model.set_defaults(grid='tmaptest') model.copy2scratch(z='*', Rv='*') - + np.random.seed(111) sed.iminimize(teff=27000, logg=5.1, ebv=0.01, z=0, rv=3.1, vrad=0, - teffrange=(25000, 35000),loggrange=(5.0, 6.0), + teffrange=(25000, 35000),loggrange=(5.0, 6.0), ebvrange=(0.005, 0.015),zrange=(0,0),rvrange=(3.1,3.1), vradrange=(0,0),df=None,CI_limit=0.95,set_model=False) - + self.assertAlmostEqual(sed.results['iminimize']['CI']['teff'], 30250, delta=100) self.assertAlmostEqual(sed.results['iminimize']['CI']['logg'], 5.66, delta=0.1) self.assertAlmostEqual(sed.results['iminimize']['CI']['ebv'], 0.008, delta=0.02) self.assertAlmostEqual(sed.results['iminimize']['grid']['chisq'][0], 3.8, delta=1) - + @unittest.skipIf(noIntegration, "Integration tests are skipped.") def testiMinimizeBinary(self): @@ -860,18 +857,18 @@ def testiMinimizeBinary(self): np.random.seed(111) sed.iminimize(teff=(5300,29000), logg=(4.0,5.3), ebv=(0.01,0.01), z=(0,0), rv=(3.1,3.1), - vrad=(0,0),teffrange=[(5000,7000),(25000, 35000)],loggrange=[(3.5,4.5), + vrad=(0,0),teffrange=[(5000,7000),(25000, 35000)],loggrange=[(3.5,4.5), (5.0, 6.0)], ebvrange=[(0.01,0.01),(0.01, 0.01)] ,zrange=[(0,0),(0,0)], rvrange=[(3.1,3.1),(3.1,3.1)], vradrange=[(0,0),(0,0)], df=2, CI_limit=None, masses = self.masses,calc_ci=False, set_model=True) - + self.assertAlmostEqual(sed.results['iminimize']['CI']['teff'], 6023, delta=100) self.assertAlmostEqual(sed.results['iminimize']['CI']['logg'], 3.95, delta=0.1) self.assertAlmostEqual(sed.results['iminimize']['CI']['ebv'], 0.01, delta=0.02) self.assertAlmostEqual(sed.results['iminimize']['CI']['teff2'], 30506, delta=100) self.assertAlmostEqual(sed.results['iminimize']['CI']['logg2'], 5.47, delta=0.1) self.assertAlmostEqual(sed.results['iminimize']['CI']['scale'], 0.9, delta=0.5) - + self.assertTrue('model' in sed.results['iminimize']) self.assertTrue('synflux' in sed.results['iminimize']) self.assertTrue('chi2' in sed.results['iminimize']) @@ -879,20 +876,5 @@ def testiMinimizeBinary(self): self.assertEqual(len(sed.results['iminimize']['synflux']), 3, msg='stored synflux has wrong number of collumns (should be 3)') - - - - - - - - - - - - - - - - - +if __name__ == "__main__": + unittest.main() diff --git a/sed/test_builder.py b/sed/test_builder.py new file mode 100644 index 000000000..de493da3f --- /dev/null +++ b/sed/test_builder.py @@ -0,0 +1,18 @@ +""" +Module to test builder.py + +These units test are writting to be used by pytest. +""" + +import builder + + +class TestSEDobject(object): + + def setup_method(self): + ID = 'HD_35155' + print('Setting up SED object with ID = {}'.format(ID)) + self.sed = builder.SED(ID) + + def test_init(self): + assert isinstance(self.sed, builder.SED) diff --git a/sed/zeropoints.dat b/sed/zeropoints.dat index 4c38e5222..a19274cca 100644 --- a/sed/zeropoints.dat +++ b/sed/zeropoints.dat @@ -20,13 +20,13 @@ # UVEX is defined as 0 magnitude in all bands (Paul Groot, priv. comm.) # WISE zeropoints defined in Wright 2010 ""zeropoints on the Vega system in the WISE passbands of Flam = 8.180e−15, 2.415e−15, 6.515e−17 & 5.090e−18 W/cm2/µm, which convert"" # STROMGREN calibration from Maiz-Apellaniz (2007) (used to be from synphot user guide and earlier from Asagio A&A card) -# ----> used to be STROMGREN U 3.25e-09 0 0 0 1.288 1.06679608688e-08 -# STROMGREN V 7.18e-09 0 0 0 0.040 7.57261249786e-09 -# STROMGREN B 5.81e-09 0 0 0 -0.120 5.11028586313e-09 +# ----> used to be STROMGREN U 3.25e-09 0 0 0 1.288 1.06679608688e-08 +# STROMGREN V 7.18e-09 0 0 0 0.040 7.57261249786e-09 +# STROMGREN B 5.81e-09 0 0 0 -0.120 5.11028586313e-09 # STROMGREN Y 3.70e-09 0 0 0 -0.123 3.20363998209e-09 -# ----> SYNPHOT: STROMGREN U 3.25e-09 0 0 0 1.459 1.24876700135e-08 -# STROMGREN V 7.18e-09 0 0 0 0.206 8.82359841247e-09 -# STROMGREN B 5.81e-09 0 0 0 0.045 5.949016791e-09 +# ----> SYNPHOT: STROMGREN U 3.25e-09 0 0 0 1.459 1.24876700135e-08 +# STROMGREN V 7.18e-09 0 0 0 0.206 8.82359841247e-09 +# STROMGREN B 5.81e-09 0 0 0 0.045 5.949016791e-09 # STROMGREN Y 3.70e-09 0 0 0 0.038 3.71572620327e-09 # ----> MAIZ-APELLANIZ: STROMGREN U - 0 0 0 1.432 - # STROMGREN V - 0 0 0 0.179 - @@ -49,459 +49,459 @@ # THE FOLLOWING SYSTEMS ARE VERY UNCERTAIN #- JOHNSON #================================= -#photband eff_wave type vegamag vegamag_lit ABmag ABmag_lit STmag STmag_lit Flam0 Flam0_units Flam0_lit Fnu0 Fnu0_units Fnu0_lit source -#f8 |S3 >f8 int32 >f8 int32 >f8 int32 >f8 |S50 int32 >f8 |S50 int32 |S100 - 2MASS.H 16497.1 CCD 0.009 1 nan 0 nan 0 1.13535e-10 erg/s/cm2/AA 0 1.03036e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - 2MASS.J 12412.1 CCD -0.021 1 nan 0 nan 0 3.11048e-10 erg/s/cm2/AA 0 1.59361e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - 2MASS.KS 21909.2 CCD 0 1 nan 0 nan 0 4.27871e-11 erg/s/cm2/AA 0 6.68263e-21 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - ACSHRC.F220W 2255.32 CCD nan 0 nan 0 nan 0 4.53976e-09 erg/s/cm2/AA 0 7.69904e-21 erg/s/cm2/Hz 0 nan - ACSHRC.F250W 2715.77 CCD nan 0 nan 0 nan 0 3.72224e-09 erg/s/cm2/AA 0 9.0493e-21 erg/s/cm2/Hz 0 nan - ACSHRC.F330W 3362.73 CCD nan 0 nan 0 nan 0 3.24002e-09 erg/s/cm2/AA 0 1.22087e-20 erg/s/cm2/Hz 0 nan - ACSHRC.F344N 3433.77 CCD nan 0 nan 0 nan 0 3.19801e-09 erg/s/cm2/AA 0 1.25767e-20 erg/s/cm2/Hz 0 nan - ACSHRC.F435W 4311.02 CCD nan 0 nan 0 nan 0 6.34395e-09 erg/s/cm2/AA 0 3.90888e-20 erg/s/cm2/Hz 0 nan - ACSHRC.F475W 4775.76 CCD nan 0 nan 0 nan 0 5.20376e-09 erg/s/cm2/AA 0 3.99348e-20 erg/s/cm2/Hz 0 nan - ACSHRC.F502N 5022.89 CCD nan 0 nan 0 nan 0 4.65992e-09 erg/s/cm2/AA 0 3.92133e-20 erg/s/cm2/Hz 0 nan - ACSHRC.F550M 5579.84 CCD nan 0 nan 0 nan 0 3.41888e-09 erg/s/cm2/AA 0 3.555e-20 erg/s/cm2/Hz 0 nan - ACSHRC.F555W 5355.96 CCD nan 0 nan 0 nan 0 3.81922e-09 erg/s/cm2/AA 0 3.68717e-20 erg/s/cm2/Hz 0 nan - ACSHRC.F606W 5887.96 CCD nan 0 nan 0 nan 0 2.91419e-09 erg/s/cm2/AA 0 3.40628e-20 erg/s/cm2/Hz 0 nan - ACSHRC.F625W 6295.53 CCD nan 0 nan 0 nan 0 2.3697e-09 erg/s/cm2/AA 0 3.11722e-20 erg/s/cm2/Hz 0 nan - ACSHRC.F658N 6583.73 CCD nan 0 nan 0 nan 0 1.77539e-09 erg/s/cm2/AA 0 2.5677e-20 erg/s/cm2/Hz 0 nan - ACSHRC.F660N 6599.04 CCD nan 0 nan 0 nan 0 1.93351e-09 erg/s/cm2/AA 0 2.80836e-20 erg/s/cm2/Hz 0 nan - ACSHRC.F775W 7665.09 CCD nan 0 nan 0 nan 0 1.30147e-09 erg/s/cm2/AA 0 2.54134e-20 erg/s/cm2/Hz 0 nan - ACSHRC.F814W 8115.33 CCD nan 0 nan 0 nan 0 1.11694e-09 erg/s/cm2/AA 0 2.45097e-20 erg/s/cm2/Hz 0 nan - ACSHRC.F850LP 9145.23 CCD nan 0 nan 0 nan 0 8.0509e-10 erg/s/cm2/AA 0 2.25021e-20 erg/s/cm2/Hz 0 nan - ACSHRC.F892N 8916.08 CCD nan 0 nan 0 nan 0 8.73963e-10 erg/s/cm2/AA 0 2.31804e-20 erg/s/cm2/Hz 0 nan - ACSSBC.F115LP 1406.06 CCD nan 0 nan 0 nan 0 4.52309e-09 erg/s/cm2/AA 0 3.15346e-21 erg/s/cm2/Hz 0 nan - ACSSBC.F122M 1273.65 CCD nan 0 nan 0 nan 0 1.78382e-09 erg/s/cm2/AA 0 1.07417e-21 erg/s/cm2/Hz 0 nan - ACSSBC.F125LP 1437.54 CCD nan 0 nan 0 nan 0 5.10956e-09 erg/s/cm2/AA 0 3.64435e-21 erg/s/cm2/Hz 0 nan - ACSSBC.F140LP 1527 CCD nan 0 nan 0 nan 0 6.32305e-09 erg/s/cm2/AA 0 4.94628e-21 erg/s/cm2/Hz 0 nan - ACSSBC.F150LP 1610.66 CCD nan 0 nan 0 nan 0 6.59853e-09 erg/s/cm2/AA 0 5.70815e-21 erg/s/cm2/Hz 0 nan - ACSSBC.F165LP 1757.9 CCD nan 0 nan 0 nan 0 6.23655e-09 erg/s/cm2/AA 0 6.42689e-21 erg/s/cm2/Hz 0 nan - ACSWFC.F435W 4317.42 CCD nan 0 nan 0 nan 0 6.41395e-09 erg/s/cm2/AA 0 3.96998e-20 erg/s/cm2/Hz 0 nan - ACSWFC.F475W 4744.37 CCD nan 0 nan 0 nan 0 5.29277e-09 erg/s/cm2/AA 0 4.00885e-20 erg/s/cm2/Hz 0 nan - ACSWFC.F502N 5023 CCD nan 0 nan 0 nan 0 4.66007e-09 erg/s/cm2/AA 0 3.92177e-20 erg/s/cm2/Hz 0 nan - ACSWFC.F550M 5581.24 CCD nan 0 nan 0 nan 0 3.41655e-09 erg/s/cm2/AA 0 3.55448e-20 erg/s/cm2/Hz 0 nan - ACSWFC.F555W 5359.57 CCD nan 0 nan 0 nan 0 3.81192e-09 erg/s/cm2/AA 0 3.68525e-20 erg/s/cm2/Hz 0 nan - ACSWFC.F606W 5917.68 CCD nan 0 nan 0 nan 0 2.87124e-09 erg/s/cm2/AA 0 3.38912e-20 erg/s/cm2/Hz 0 nan - ACSWFC.F625W 6310.47 CCD nan 0 nan 0 nan 0 2.35216e-09 erg/s/cm2/AA 0 3.10892e-20 erg/s/cm2/Hz 0 nan - ACSWFC.F658N 6583.95 CCD nan 0 nan 0 nan 0 1.77549e-09 erg/s/cm2/AA 0 2.56823e-20 erg/s/cm2/Hz 0 nan - ACSWFC.F660N 6599.43 CCD nan 0 nan 0 nan 0 1.93353e-09 erg/s/cm2/AA 0 2.80924e-20 erg/s/cm2/Hz 0 nan - ACSWFC.F775W 7693.02 CCD nan 0 nan 0 nan 0 1.28672e-09 erg/s/cm2/AA 0 2.53085e-20 erg/s/cm2/Hz 0 nan - ACSWFC.F814W 8059.77 CCD nan 0 nan 0 nan 0 1.13432e-09 erg/s/cm2/AA 0 2.4528e-20 erg/s/cm2/Hz 0 nan - ACSWFC.F850LP 9054.79 CCD nan 0 nan 0 nan 0 8.21948e-10 erg/s/cm2/AA 0 2.25165e-20 erg/s/cm2/Hz 0 nan - AKARI.L15 156841 BOL nan 0 nan 0 nan 0 2.00796e-14 erg/s/cm2/AA 0 1.67264e-22 erg/s/cm2/Hz 0 nan - AKARI.L18W 186321 BOL nan 0 nan 0 nan 0 1.03826e-14 erg/s/cm2/AA 0 1.23954e-22 erg/s/cm2/Hz 0 nan - AKARI.L24 229866 BOL nan 0 nan 0 nan 0 4.33162e-15 erg/s/cm2/AA 0 7.69291e-23 erg/s/cm2/Hz 0 nan - AKARI.N160 1.61215e+06 BOL nan 0 nan 0 nan 0 1.71999e-18 erg/s/cm2/AA 0 1.49962e-24 erg/s/cm2/Hz 0 nan - AKARI.N2 23493.5 BOL nan 0 nan 0 nan 0 3.19441e-11 erg/s/cm2/AA 0 6.0077e-21 erg/s/cm2/Hz 0 nan - AKARI.N3 31990.5 BOL nan 0 nan 0 nan 0 9.98454e-12 erg/s/cm2/AA 0 3.45587e-21 erg/s/cm2/Hz 0 nan - AKARI.N4 43480.4 BOL nan 0 nan 0 nan 0 3.0884e-12 erg/s/cm2/AA 0 1.97489e-21 erg/s/cm2/Hz 0 nan - AKARI.N60 649413 BOL nan 0 nan 0 nan 0 6.83648e-17 erg/s/cm2/AA 0 9.75466e-24 erg/s/cm2/Hz 0 nan - AKARI.S11 105329 BOL nan 0 nan 0 nan 0 9.78412e-14 erg/s/cm2/AA 0 3.69336e-22 erg/s/cm2/Hz 0 nan - AKARI.S7 71510.5 BOL nan 0 nan 0 nan 0 4.45022e-13 erg/s/cm2/AA 0 7.71253e-22 erg/s/cm2/Hz 0 nan - AKARI.S9W 87264.6 BOL nan 0 nan 0 nan 0 2.10269e-13 erg/s/cm2/AA 0 5.49591e-22 erg/s/cm2/Hz 0 nan - AKARI.WIDEL 1.45288e+06 BOL nan 0 nan 0 nan 0 2.67019e-18 erg/s/cm2/AA 0 1.90997e-24 erg/s/cm2/Hz 0 nan - AKARI.WIDES 836666 BOL nan 0 nan 0 nan 0 2.61537e-17 erg/s/cm2/AA 0 6.32301e-24 erg/s/cm2/Hz 0 nan - ANS.15N 1545 CCD -0.675376 0 nan 0 0 1 6.76317e-09 erg/s/cm2/AA 0 5.3858e-21 erg/s/cm2/Hz 0 Wesselius1980 - ANS.15W 1549 CCD -0.687251 0 nan 0 0 1 6.83755e-09 erg/s/cm2/AA 0 5.48931e-21 erg/s/cm2/Hz 0 Wesselius1980 - ANS.18 1805.68 CCD -0.568371 0 nan 0 0 1 6.12842e-09 erg/s/cm2/AA 0 6.66704e-21 erg/s/cm2/Hz 0 Wesselius1980 - ANS.22 2199.22 CCD nan 0 nan 0 nan 0 4.73981e-09 erg/s/cm2/AA 0 7.6507e-21 erg/s/cm2/Hz 0 nan - ANS.25 2493 CCD -0.0606239 0 nan 0 0 1 3.83928e-09 erg/s/cm2/AA 0 7.93652e-21 erg/s/cm2/Hz 0 Wesselius1980 - ANS.33 3295.35 CCD 0.102499 0 nan 0 0 1 3.3037e-09 erg/s/cm2/AA 0 1.19709e-20 erg/s/cm2/Hz 0 Wesselius1980 - APEX.LABOCA 8.78475e+06 CCD nan 0 nan 0 nan 0 nan erg/s/cm2/AA 0 nan erg/s/cm2/Hz 0 nan - ARGUE.R 6874.8 CCD 0.285 1 nan 0 nan 0 1.80973e-09 erg/s/cm2/AA 0 2.84994e-20 erg/s/cm2/Hz 0 GCPD - ARGUE.R8 8485.73 CCD 0.303 1 nan 0 nan 0 1.00692e-09 erg/s/cm2/AA 0 2.39435e-20 erg/s/cm2/Hz 0 GCPD - ARGUE.R9 8459.11 CCD 0.321 1 nan 0 nan 0 1.00354e-09 erg/s/cm2/AA 0 2.37691e-20 erg/s/cm2/Hz 0 GCPD - BALLOON.UV 2000.00 CCD nan 0 nan 0 nan 0 nan erg/s/cm2/AA 0 nan erg/s/cm2/Hz 0 nan - BESSEL.H 16441.7 CCD nan 0 nan 0 nan 0 1.15039e-10 erg/s/cm2/AA 0 1.03731e-20 erg/s/cm2/Hz 0 nan - BESSEL.J 12347.7 CCD nan 0 nan 0 nan 0 3.14613e-10 erg/s/cm2/AA 0 1.59997e-20 erg/s/cm2/Hz 0 nan - BESSEL.K 22090.6 CCD nan 0 nan 0 nan 0 3.97709e-11 erg/s/cm2/AA 0 6.47594e-21 erg/s/cm2/Hz 0 nan - BESSEL.L 34799.6 CCD nan 0 nan 0 nan 0 7.17418e-12 erg/s/cm2/AA 0 2.89794e-21 erg/s/cm2/Hz 0 nan - BESSEL.LPRIME 38275.4 CCD nan 0 nan 0 nan 0 4.95932e-12 erg/s/cm2/AA 0 2.42343e-21 erg/s/cm2/Hz 0 nan - BESSEL.M 47317.2 CCD nan 0 nan 0 nan 0 2.1794e-12 erg/s/cm2/AA 0 1.6276e-21 erg/s/cm2/Hz 0 nan - BESSELL.B 4386.01 CCD -0.104 1 nan 0 nan 0 6.28778e-09 erg/s/cm2/AA 0 4.01367e-20 erg/s/cm2/Hz 0 Bessel1990 - BESSELL.BW 4392.42 CCD nan 0 nan 0 nan 0 6.24145e-09 erg/s/cm2/AA 0 4.02194e-20 erg/s/cm2/Hz 0 nan - BESSELL.I 8035.48 CCD 0.443 1 nan 0 nan 0 1.13147e-09 erg/s/cm2/AA 0 2.4206e-20 erg/s/cm2/Hz 0 Bessel1990 - BESSELL.R 6526.85 CCD 0.193 1 nan 0 nan 0 2.1693e-09 erg/s/cm2/AA 0 3.02812e-20 erg/s/cm2/Hz 0 Bessel1990 - BESSELL.U 3592.66 CCD 0.79 1 nan 0 nan 0 4.00565e-09 erg/s/cm2/AA 0 1.74442e-20 erg/s/cm2/Hz 0 Bessel1990 - BESSELL.V 5490.66 CCD 0.008 1 nan 0 nan 0 3.60833e-09 erg/s/cm2/AA 0 3.65041e-20 erg/s/cm2/Hz 0 Bessel1990 - BRITE.B 4246.37 CCD nan 0 nan 0 nan 0 6.88517e-09 erg/s/cm2/AA 0 4.13434e-20 erg/s/cm2/Hz 0 nan - BRITE.R 6236.81 CCD nan 0 nan 0 nan 0 2.43832e-09 erg/s/cm2/AA 0 3.14827e-20 erg/s/cm2/Hz 0 nan - CAMELOT-BR.GAMMA 4355.23 CCD nan 0 nan 0 nan 0 6.24148e-09 erg/s/cm2/AA 0 3.98091e-20 erg/s/cm2/Hz 0 nan - CAMELOT-JOHNSON.I 8706.7 CCD nan 0 nan 0 nan 0 9.24657e-10 erg/s/cm2/AA 0 2.33794e-20 erg/s/cm2/Hz 0 nan - CAMELOT-JOHNSON.R 6401.3 CCD nan 0 nan 0 nan 0 2.26677e-09 erg/s/cm2/AA 0 3.10012e-20 erg/s/cm2/Hz 0 nan - CAMELOT-JOHNSON.U 3656.13 CCD nan 0 nan 0 nan 0 4.24922e-09 erg/s/cm2/AA 0 1.97848e-20 erg/s/cm2/Hz 0 nan - CAMELOT-JOHNSON.V 5408.35 CCD nan 0 nan 0 nan 0 3.75051e-09 erg/s/cm2/AA 0 3.73656e-20 erg/s/cm2/Hz 0 nan - CAMELOT-SDSS.G 4758.55 CCD nan 0 nan 0 nan 0 5.25556e-09 erg/s/cm2/AA 0 3.95548e-20 erg/s/cm2/Hz 0 nan - CAMELOT-SDSS.I 7700.34 CCD nan 0 nan 0 nan 0 1.28391e-09 erg/s/cm2/AA 0 2.53939e-20 erg/s/cm2/Hz 0 nan - CAMELOT-SDSS.R 6216.92 CCD nan 0 nan 0 nan 0 2.46367e-09 erg/s/cm2/AA 0 3.23409e-20 erg/s/cm2/Hz 0 nan - CAMELOT-SDSS.U 3495.6 CCD nan 0 nan 0 nan 0 3.48873e-09 erg/s/cm2/AA 0 1.45908e-20 erg/s/cm2/Hz 0 nan - CAMELOT-SDSS.Z 9637.14 CCD nan 0 nan 0 nan 0 6.98857e-10 erg/s/cm2/AA 0 2.16414e-20 erg/s/cm2/Hz 0 nan - CAMELOT-STROMGREN.B 4654.33 CCD nan 0 nan 0 nan 0 5.829e-09 erg/s/cm2/AA 0 4.21096e-20 erg/s/cm2/Hz 0 nan - CAMELOT-STROMGREN.U 3469.19 CCD nan 0 nan 0 nan 0 3.15342e-09 erg/s/cm2/AA 0 1.26812e-20 erg/s/cm2/Hz 0 nan - CAMELOT-STROMGREN.V 4119.79 CCD nan 0 nan 0 nan 0 7.42511e-09 erg/s/cm2/AA 0 4.20674e-20 erg/s/cm2/Hz 0 nan - CAMELOT-STROMGREN.Y 5449.51 CCD nan 0 nan 0 nan 0 3.66098e-09 erg/s/cm2/AA 0 3.63177e-20 erg/s/cm2/Hz 0 nan - CAMELOT.IZ1 8782.93 CCD nan 0 nan 0 nan 0 9.02218e-10 erg/s/cm2/AA 0 2.32128e-20 erg/s/cm2/Hz 0 nan - CAMELOT.IZ2 8772.48 CCD nan 0 nan 0 nan 0 9.05126e-10 erg/s/cm2/AA 0 2.32321e-20 erg/s/cm2/Hz 0 nan - COROT.EXO 6651.03 CCD nan 0 nan 0 nan 0 2.23245e-09 erg/s/cm2/AA 0 3.3517e-20 erg/s/cm2/Hz 0 nan - COROT.SIS 6578.73 CCD nan 0 nan 0 nan 0 2.30564e-09 erg/s/cm2/AA 0 3.38325e-20 erg/s/cm2/Hz 0 nan - COUSINS.I 7884.05 CCD 0.017 1 nan 0 nan 0 1.19379e-09 erg/s/cm2/AA 0 2.46938e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - COUSINS.R 6499.87 CCD 0.03 1 nan 0 nan 0 2.19313e-09 erg/s/cm2/AA 0 3.06728e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - COUSINS.V 5504.67 CCD 0.026 1 nan 0 nan 0 3.58315e-09 erg/s/cm2/AA 0 3.69669e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - DDO.35 3458.23 CCD nan 0 nan 0 nan 0 3.17443e-09 erg/s/cm2/AA 0 1.27115e-20 erg/s/cm2/Hz 0 nan - DDO.38 3820.74 CCD nan 0 nan 0 nan 0 5.37509e-09 erg/s/cm2/AA 0 2.6729e-20 erg/s/cm2/Hz 0 nan - DDO.41 4164.08 CCD nan 0 nan 0 nan 0 7.71911e-09 erg/s/cm2/AA 0 4.46681e-20 erg/s/cm2/Hz 0 nan - DDO.42 4255.76 CCD nan 0 nan 0 nan 0 7.42918e-09 erg/s/cm2/AA 0 4.48765e-20 erg/s/cm2/Hz 0 nan - DDO.45 4520.55 CCD nan 0 nan 0 nan 0 6.29948e-09 erg/s/cm2/AA 0 4.2938e-20 erg/s/cm2/Hz 0 nan - DDO.48 4883.17 CCD nan 0 nan 0 nan 0 4.60439e-09 erg/s/cm2/AA 0 3.66418e-20 erg/s/cm2/Hz 0 nan - DDO.51 5128.43 CCD nan 0 nan 0 nan 0 4.37691e-09 erg/s/cm2/AA 0 3.83867e-20 erg/s/cm2/Hz 0 nan - DENIS.I 7825.32 CCD 0.04 0 nan 0 nan 0 1.2e-08 W/m2/mum 1 2499 Jy 1 Fouque_etal.2000 - DENIS.J 12374 CCD 0.032 0 nan 0 nan 0 3.17e-09 W/m2/mum 1 1595 Jy 1 Fouque_etal.2000 - DENIS.KS 21415 CCD 0.041 0 nan 0 nan 0 4.34e-10 W/m2/mum 1 665 Jy 1 Fouque_etal.2000 - DIRBE.F100 977021 BOL nan 0 nan 0 nan 0 1.43337e-17 erg/s/cm2/AA 0 4.48346e-24 erg/s/cm2/Hz 0 nan - DIRBE.F12 122948 BOL nan 0 nan 0 nan 0 6.51649e-14 erg/s/cm2/AA 0 3.1533e-22 erg/s/cm2/Hz 0 nan - DIRBE.F140 1.47889e+06 BOL nan 0 nan 0 nan 0 2.72082e-18 erg/s/cm2/AA 0 1.94504e-24 erg/s/cm2/Hz 0 nan - DIRBE.F1_25 12666.1 BOL nan 0 nan 0 nan 0 2.93465e-10 erg/s/cm2/AA 0 1.56131e-20 erg/s/cm2/Hz 0 nan - DIRBE.F240 2.47856e+06 BOL nan 0 nan 0 nan 0 3.86145e-19 erg/s/cm2/AA 0 7.24944e-25 erg/s/cm2/Hz 0 nan - DIRBE.F25 207909 BOL nan 0 nan 0 nan 0 7.10896e-15 erg/s/cm2/AA 0 1.00527e-22 erg/s/cm2/Hz 0 nan - DIRBE.F2_2 22201.2 BOL nan 0 nan 0 nan 0 3.92138e-11 erg/s/cm2/AA 0 6.43052e-21 erg/s/cm2/Hz 0 nan - DIRBE.F3_5 35338 BOL nan 0 nan 0 nan 0 6.9399e-12 erg/s/cm2/AA 0 2.8721e-21 erg/s/cm2/Hz 0 nan - DIRBE.F4_9 48826.3 BOL nan 0 nan 0 nan 0 1.94259e-12 erg/s/cm2/AA 0 1.54205e-21 erg/s/cm2/Hz 0 nan - DIRBE.F60 560906 BOL nan 0 nan 0 nan 0 1.41579e-16 erg/s/cm2/AA 0 1.44511e-23 erg/s/cm2/Hz 0 nan - EEV4280.CCD 6003.93 CCD nan 0 nan 0 nan 0 2.60702e-09 erg/s/cm2/AA 0 3.19862e-20 erg/s/cm2/Hz 0 nan - ESOIR.BRG 21610 CCD 0 1 nan 0 nan 0 4.38e-13 W/m2/nm 1 683 Jy 1 van_Der_Bliek_1996 - ESOIR.CO 22910 CCD 0 1 nan 0 nan 0 3.54e-13 W/m2/nm 1 619 Jy 1 van_Der_Bliek_1996 - ESOIR.H0 15398.2 CCD 0 1 nan 0 nan 0 1.37e-12 W/m2/nm 1 1140 Jy 1 van_Der_Bliek_1996 - ESOIR.K0 22210 CCD 0 1 nan 0 nan 0 3.97e-13 W/m2/nm 1 653 Jy 1 van_Der_Bliek_1996 - ESOIR.L0 37060 CCD 0 1 nan 0 nan 0 5.78e-14 W/m2/nm 1 265 Jy 1 van_Der_Bliek_1996 - ESOIR.N 114659 CCD 0 1 nan 0 nan 0 1.29e-15 W/m2/nm 1 41.7 Jy 1 van_Der_Bliek_1996 - ESOIR.N 114659 CCD 0 1 nan 0 nan 0 1.09e-16 W/m2/nm 1 12.3 Jy 1 van_Der_Bliek_1996 - ESOIR.N1 83610 CCD 0 1 nan 0 nan 0 2.5e-15 W/m2/nm 1 57.8 Jy 1 van_Der_Bliek_1996 - ESOIR.N2 97870 CCD 0 1 nan 0 nan 0 1.4e-15 W/m2/nm 1 43.5 Jy 1 van_Der_Bliek_1996 - ESOIR.N3 128190 CCD 0 1 nan 0 nan 0 4.64e-16 W/m2/nm 1 25.3 Jy 1 van_Der_Bliek_1996 - ESOIR.Q0 183618 CCD nan 0 nan 0 nan 0 1.20428e-14 erg/s/cm2/AA 0 1.35437e-22 erg/s/cm2/Hz 0 nan - GAIA.BP 5437.48 CCD 0.03 1 nan 0 nan 0 3.66471e-09 erg/s/cm2/AA 0 3.59649e-20 erg/s/cm2/Hz 0 nan - GAIA.G 6710.93 CCD 0.03 1 nan 0 nan 0 2.18634e-09 erg/s/cm2/AA 0 3.19934e-20 erg/s/cm2/Hz 0 nan - GAIA.RP 7979.59 CCD 0.03 1 nan 0 nan 0 1.19528e-09 erg/s/cm2/AA 0 2.50633e-20 erg/s/cm2/Hz 0 nan - GAIA.RVS 8597.12 CCD 0.03 1 nan 0 nan 0 8.99474e-10 erg/s/cm2/AA 0 2.21745e-20 erg/s/cm2/Hz 0 nan - GALEX.FUV 1538.62 CCD nan 0 0.029 1 nan 0 4.72496e-08 erg/s/cm2/AA 1 3.71398e-20 erg/s/cm2/Hz 0 http://galexgi.gsfc.nasa.gov/docs/galex/FAQ/counts_background.html - GALEX.NUV 2315.66 CCD nan 0 0.095 1 nan 0 2.21466e-08 erg/s/cm2/AA 1 3.91055e-20 erg/s/cm2/Hz 0 http://galexgi.gsfc.nasa.gov/docs/galex/FAQ/counts_background.html - GENEVA.B 4200.85 CCD -0.898 1 nan 0 nan 0 6.51814e-09 erg/s/cm2/AA 0 3.88163e-20 erg/s/cm2/Hz 0 Rufener1988 - GENEVA.B1 4003.78 CCD 0.002 1 nan 0 nan 0 6.58414e-09 erg/s/cm2/AA 0 3.55999e-20 erg/s/cm2/Hz 0 Rufener1988 - GENEVA.B2 4477.56 CCD 0.612 1 nan 0 nan 0 6.20794e-09 erg/s/cm2/AA 0 4.15097e-20 erg/s/cm2/Hz 0 Rufener1988 - GENEVA.G 5765.89 CCD 1.27 1 nan 0 nan 0 3.11095e-09 erg/s/cm2/AA 0 3.44e-20 erg/s/cm2/Hz 0 Rufener1988 - GENEVA.U 3421.62 CCD 0.607 1 nan 0 nan 0 3.22328e-09 erg/s/cm2/AA 0 1.26454e-20 erg/s/cm2/Hz 0 Rufener1988 - GENEVA.V 5482.6 CCD 0.061 1 nan 0 nan 0 3.61964e-09 erg/s/cm2/AA 0 3.65226e-20 erg/s/cm2/Hz 0 Rufener1988 - GENEVA.V1 5395.63 CCD 0.764 1 nan 0 nan 0 3.78179e-09 erg/s/cm2/AA 0 3.69382e-20 erg/s/cm2/Hz 0 Rufener1988 - HIPPARCOS.HP 5275.11 CCD 0.026 1 nan 0 nan 0 4.06145e-09 erg/s/cm2/AA 0 3.67714e-20 erg/s/cm2/Hz 0 ASIAGO(same_as_JOHNSON.V) - IPHAS.HA 6568.06 CCD 0 1 nan 0 nan 0 1.81344e-09 erg/s/cm2/AA 0 2.60944e-20 erg/s/cm2/Hz 0 Drew_etal.2005 - IPHAS.IP 7671.27 CCD 0 1 nan 0 nan 0 1.30528e-09 erg/s/cm2/AA 0 2.55384e-20 erg/s/cm2/Hz 0 Drew_etal.2005 - IPHAS.RP 6216.15 CCD 0 1 nan 0 nan 0 2.47671e-09 erg/s/cm2/AA 0 3.23745e-20 erg/s/cm2/Hz 0 Drew_etal.2005 - IRAC.36 35375.4 BOL nan 0 nan 0 nan 0 nan nan 0 277.5 Jy 1 http://web.ipac.caltech.edu/staff/gillian/cal.html - IRAC.45 44750.7 BOL nan 0 nan 0 nan 0 nan nan 0 179.5 Jy 1 http://web.ipac.caltech.edu/staff/gillian/cal.html - IRAC.58 57019.3 BOL nan 0 nan 0 nan 0 nan nan 0 116.6 Jy 1 http://web.ipac.caltech.edu/staff/gillian/cal.html - IRAC.80 77843.6 BOL nan 0 nan 0 nan 0 nan nan 0 63.1 Jy 1 http://web.ipac.caltech.edu/staff/gillian/cal.html - IRAS.F100 1.01948e+06 BOL nan 0 nan 0 nan 0 1.27666e-17 erg/s/cm2/AA 0 4.21191e-24 erg/s/cm2/Hz 0 nan - IRAS.F12 115980 BOL nan 0 nan 0 nan 0 8.99122e-14 erg/s/cm2/AA 0 3.64825e-22 erg/s/cm2/Hz 0 nan - IRAS.F25 238775 BOL nan 0 nan 0 nan 0 4.60991e-15 erg/s/cm2/AA 0 8.18366e-23 erg/s/cm2/Hz 0 nan - IRAS.F60 614850 BOL nan 0 nan 0 nan 0 1.23233e-16 erg/s/cm2/AA 0 1.3897e-23 erg/s/cm2/Hz 0 nan - ISOCAM.LW1 44997.9 BOL nan 0 nan 0 nan 0 2.7547e-12 erg/s/cm2/AA 0 1.83505e-21 erg/s/cm2/Hz 0 nan - ISOCAM.LW10 116979 BOL nan 0 nan 0 nan 0 8.2868e-14 erg/s/cm2/AA 0 3.47187e-22 erg/s/cm2/Hz 0 nan - ISOCAM.LW2 68135.1 BOL nan 0 nan 0 nan 0 6.55288e-13 erg/s/cm2/AA 0 9.48141e-22 erg/s/cm2/Hz 0 nan - ISOCAM.LW3 144862 BOL nan 0 nan 0 nan 0 2.99587e-14 erg/s/cm2/AA 0 2.02519e-22 erg/s/cm2/Hz 0 nan - ISOCAM.LW4 60078.5 BOL nan 0 nan 0 nan 0 8.90277e-13 erg/s/cm2/AA 0 1.06024e-21 erg/s/cm2/Hz 0 nan - ISOCAM.LW5 67877.6 BOL nan 0 nan 0 nan 0 5.37998e-13 erg/s/cm2/AA 0 8.24742e-22 erg/s/cm2/Hz 0 nan - ISOCAM.LW6 77478.4 BOL nan 0 nan 0 nan 0 3.28415e-13 erg/s/cm2/AA 0 6.50625e-22 erg/s/cm2/Hz 0 nan - ISOCAM.LW7 96797.9 BOL nan 0 nan 0 nan 0 1.40631e-13 erg/s/cm2/AA 0 4.31139e-22 erg/s/cm2/Hz 0 nan - ISOCAM.LW8 113338 BOL nan 0 nan 0 nan 0 7.16443e-14 erg/s/cm2/AA 0 3.0585e-22 erg/s/cm2/Hz 0 nan - ISOCAM.LW9 149177 BOL nan 0 nan 0 nan 0 2.41792e-14 erg/s/cm2/AA 0 1.78663e-22 erg/s/cm2/Hz 0 nan - ISOCAM.SW1 35896.7 BOL nan 0 nan 0 nan 0 6.7493e-12 erg/s/cm2/AA 0 2.83774e-21 erg/s/cm2/Hz 0 nan - ISOCAM.SW10 46333.2 BOL nan 0 nan 0 nan 0 2.37437e-12 erg/s/cm2/AA 0 1.69638e-21 erg/s/cm2/Hz 0 nan - ISOCAM.SW11 42232.8 BOL nan 0 nan 0 nan 0 3.38673e-12 erg/s/cm2/AA 0 2.01309e-21 erg/s/cm2/Hz 0 nan - ISOCAM.SW2 33050.7 BOL nan 0 nan 0 nan 0 8.70066e-12 erg/s/cm2/AA 0 3.16453e-21 erg/s/cm2/Hz 0 nan - ISOCAM.SW3 44382.2 BOL nan 0 nan 0 nan 0 2.91552e-12 erg/s/cm2/AA 0 1.88722e-21 erg/s/cm2/Hz 0 nan - ISOCAM.SW4 27828.9 BOL nan 0 nan 0 nan 0 1.70274e-11 erg/s/cm2/AA 0 4.35452e-21 erg/s/cm2/Hz 0 nan - ISOCAM.SW5 41251.5 BOL nan 0 nan 0 nan 0 4.57532e-12 erg/s/cm2/AA 0 2.42243e-21 erg/s/cm2/Hz 0 nan - ISOCAM.SW6 37000.4 BOL nan 0 nan 0 nan 0 5.71313e-12 erg/s/cm2/AA 0 2.59513e-21 erg/s/cm2/Hz 0 nan - ISOCAM.SW7 30453.6 BOL nan 0 nan 0 nan 0 1.19394e-11 erg/s/cm2/AA 0 3.68221e-21 erg/s/cm2/Hz 0 nan - ISOCAM.SW8 40528.7 BOL nan 0 nan 0 nan 0 3.93782e-12 erg/s/cm2/AA 0 2.15547e-21 erg/s/cm2/Hz 0 nan - ISOCAM.SW9 38650.8 BOL nan 0 nan 0 nan 0 4.75994e-12 erg/s/cm2/AA 0 2.36961e-21 erg/s/cm2/Hz 0 nan - JOHNSON.110 11078.7 CCD 0.039 1 nan 0 nan 0 4.54025e-10 erg/s/cm2/AA 0 1.85576e-20 erg/s/cm2/Hz 0 nan - JOHNSON.33 3366.72 CCD 0.091 1 nan 0 nan 0 3.2259e-09 erg/s/cm2/AA 0 1.22666e-20 erg/s/cm2/Hz 0 nan - JOHNSON.35 3559.9 CCD 0.074 1 nan 0 nan 0 3.13021e-09 erg/s/cm2/AA 0 1.30965e-20 erg/s/cm2/Hz 0 nan - JOHNSON.37 3750.29 CCD 0.082 1 nan 0 nan 0 4.31727e-09 erg/s/cm2/AA 0 2.0565e-20 erg/s/cm2/Hz 0 nan - JOHNSON.40 4058.45 CCD 0.052 1 nan 0 nan 0 7.05664e-09 erg/s/cm2/AA 0 3.85773e-20 erg/s/cm2/Hz 0 nan - JOHNSON.45 4559.97 CCD 0.04 1 nan 0 nan 0 5.95346e-09 erg/s/cm2/AA 0 4.19593e-20 erg/s/cm2/Hz 0 nan - JOHNSON.52 5186.4 CCD 0.039 1 nan 0 nan 0 4.2249e-09 erg/s/cm2/AA 0 3.79162e-20 erg/s/cm2/Hz 0 nan - JOHNSON.58 5832.3 CCD 0.038 1 nan 0 nan 0 3.02678e-09 erg/s/cm2/AA 0 3.42362e-20 erg/s/cm2/Hz 0 nan - JOHNSON.58P 5854.78 CCD nan 0 nan 0 nan 0 2.96211e-09 erg/s/cm2/AA 0 3.39375e-20 erg/s/cm2/Hz 0 nan - JOHNSON.63 6259.5 CCD 0.05 1 nan 0 nan 0 2.27773e-09 erg/s/cm2/AA 0 3.07584e-20 erg/s/cm2/Hz 0 nan - JOHNSON.72 7239.35 CCD 0.031 1 nan 0 nan 0 1.5524e-09 erg/s/cm2/AA 0 2.71174e-20 erg/s/cm2/Hz 0 nan - JOHNSON.80 8002.1 CCD 0.029 1 nan 0 nan 0 1.13226e-09 erg/s/cm2/AA 0 2.41758e-20 erg/s/cm2/Hz 0 nan - JOHNSON.86 8584.69 CCD 0.043 1 nan 0 nan 0 9.12766e-10 erg/s/cm2/AA 0 2.24296e-20 erg/s/cm2/Hz 0 nan - JOHNSON.99 9831.12 CCD 0.028 1 nan 0 nan 0 6.72283e-10 erg/s/cm2/AA 0 2.16627e-20 erg/s/cm2/Hz 0 nan - JOHNSON.B 4448.06 CCD 0.034 1 nan 0 nan 0 6.1307e-09 erg/s/cm2/AA 0 4.02221e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - JOHNSON.H 16464.4 CCD 0.02 1 nan 0 nan 0 1.15039e-10 erg/s/cm2/AA 0 1.03731e-20 erg/s/cm2/Hz 0 Morel_etal.1978 - JOHNSON.I 8778.22 CCD 0.1 1 nan 0 nan 0 9.09942e-10 erg/s/cm2/AA 0 2.31787e-20 erg/s/cm2/Hz 0 Morel_etal.1978 - JOHNSON.J 12487.8 CCD 0.02 1 nan 0 nan 0 3.11763e-10 erg/s/cm2/AA 0 1.60682e-20 erg/s/cm2/Hz 0 Morel_etal.1978 - JOHNSON.K 21951.2 CCD 0.02 1 nan 0 nan 0 4.16588e-11 erg/s/cm2/AA 0 6.65137e-21 erg/s/cm2/Hz 0 Morel_etal.1978 - JOHNSON.L 35395.5 CCD -0.02 1 nan 0 nan 0 6.91788e-12 erg/s/cm2/AA 0 2.87024e-21 erg/s/cm2/Hz 0 Morel_etal.1978 - JOHNSON.M 50311.2 CCD -0.04 1 nan 0 nan 0 1.78777e-12 erg/s/cm2/AA 0 1.498e-21 erg/s/cm2/Hz 0 Morel_etal.1978 - JOHNSON.N 103566 CCD -0.01 1 nan 0 nan 0 1.20556e-13 erg/s/cm2/AA 0 4.18344e-22 erg/s/cm2/Hz 0 Morel_etal.1978 - JOHNSON.R 6939.52 CCD 0.07 1 nan 0 nan 0 1.81615e-09 erg/s/cm2/AA 0 2.88912e-20 erg/s/cm2/Hz 0 Morel_etal.1978 - JOHNSON.U 3641.75 CCD 0.055 1 nan 0 nan 0 3.9779e-09 erg/s/cm2/AA 0 1.81564e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - JOHNSON.V 5504.67 CCD 0.026 1 nan 0 nan 0 3.58315e-09 erg/s/cm2/AA 0 3.69669e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - KEPLER.CH0102 6283.57 CCD nan 0 nan 0 nan 0 2.47062e-09 erg/s/cm2/AA 0 3.47861e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH0304 6298.82 CCD nan 0 nan 0 nan 0 2.45196e-09 erg/s/cm2/AA 0 3.46705e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH0506 6328.91 CCD nan 0 nan 0 nan 0 2.41888e-09 erg/s/cm2/AA 0 3.34082e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH0708 6316.8 CCD nan 0 nan 0 nan 0 2.43212e-09 erg/s/cm2/AA 0 3.34656e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH0910 6329.32 CCD nan 0 nan 0 nan 0 2.41813e-09 erg/s/cm2/AA 0 3.33968e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH1112 6295.8 CCD nan 0 nan 0 nan 0 2.45648e-09 erg/s/cm2/AA 0 3.36052e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH1314 6291.06 CCD nan 0 nan 0 nan 0 2.46033e-09 erg/s/cm2/AA 0 3.35855e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH1516 6335.45 CCD nan 0 nan 0 nan 0 2.41011e-09 erg/s/cm2/AA 0 3.33411e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH1718 6369.76 CCD nan 0 nan 0 nan 0 2.38098e-09 erg/s/cm2/AA 0 3.44538e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH1920 6384.32 CCD nan 0 nan 0 nan 0 2.36547e-09 erg/s/cm2/AA 0 3.3213e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH2122 6386.65 CCD nan 0 nan 0 nan 0 2.36344e-09 erg/s/cm2/AA 0 3.32119e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH2324 6380.94 CCD nan 0 nan 0 nan 0 2.36885e-09 erg/s/cm2/AA 0 3.32248e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH2526 6388.26 CCD nan 0 nan 0 nan 0 2.36216e-09 erg/s/cm2/AA 0 3.4394e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH2728 6323.43 CCD nan 0 nan 0 nan 0 2.43306e-09 erg/s/cm2/AA 0 3.35835e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH2930 6270.79 CCD nan 0 nan 0 nan 0 2.48346e-09 erg/s/cm2/AA 0 3.37513e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH3132 6280.35 CCD nan 0 nan 0 nan 0 2.47236e-09 erg/s/cm2/AA 0 3.36935e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH3334 6314.47 CCD nan 0 nan 0 nan 0 2.43484e-09 erg/s/cm2/AA 0 3.34841e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH3536 6317.17 CCD nan 0 nan 0 nan 0 2.4316e-09 erg/s/cm2/AA 0 3.34624e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH3738 6391.24 CCD nan 0 nan 0 nan 0 2.35609e-09 erg/s/cm2/AA 0 3.31346e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH3940 6366.72 CCD nan 0 nan 0 nan 0 2.38379e-09 erg/s/cm2/AA 0 3.32857e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH4142 6323.59 CCD nan 0 nan 0 nan 0 2.42526e-09 erg/s/cm2/AA 0 3.34533e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH4344 6326 CCD nan 0 nan 0 nan 0 2.42288e-09 erg/s/cm2/AA 0 3.34475e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH4546 6315.58 CCD nan 0 nan 0 nan 0 2.43328e-09 erg/s/cm2/AA 0 3.34663e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH4748 6321.57 CCD nan 0 nan 0 nan 0 2.42697e-09 erg/s/cm2/AA 0 3.34491e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH4950 6310.74 CCD nan 0 nan 0 nan 0 2.43909e-09 erg/s/cm2/AA 0 3.35002e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH5152 6311.78 CCD nan 0 nan 0 nan 0 2.43819e-09 erg/s/cm2/AA 0 3.35049e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH5354 6306.19 CCD nan 0 nan 0 nan 0 2.44324e-09 erg/s/cm2/AA 0 3.35577e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH5556 6288.29 CCD nan 0 nan 0 nan 0 2.46355e-09 erg/s/cm2/AA 0 3.36734e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH5758 6387.78 CCD nan 0 nan 0 nan 0 2.36212e-09 erg/s/cm2/AA 0 3.4387e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH5960 6366.56 CCD nan 0 nan 0 nan 0 2.38288e-09 erg/s/cm2/AA 0 3.32709e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH6162 6323.8 CCD nan 0 nan 0 nan 0 2.42423e-09 erg/s/cm2/AA 0 3.34263e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH6364 6350.55 CCD nan 0 nan 0 nan 0 2.39401e-09 erg/s/cm2/AA 0 3.32607e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH6566 6375.57 CCD nan 0 nan 0 nan 0 2.37437e-09 erg/s/cm2/AA 0 3.44178e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH6768 6332.07 CCD nan 0 nan 0 nan 0 2.42263e-09 erg/s/cm2/AA 0 3.35024e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH6970 6366.04 CCD nan 0 nan 0 nan 0 2.3847e-09 erg/s/cm2/AA 0 3.32959e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH7172 6392.78 CCD nan 0 nan 0 nan 0 2.35601e-09 erg/s/cm2/AA 0 3.31551e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH7374 6333.33 CCD nan 0 nan 0 nan 0 2.41344e-09 erg/s/cm2/AA 0 3.33676e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH7576 6331.63 CCD nan 0 nan 0 nan 0 2.41506e-09 erg/s/cm2/AA 0 3.33653e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH7778 6289.47 CCD nan 0 nan 0 nan 0 2.46149e-09 erg/s/cm2/AA 0 3.35735e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH7980 6303.79 CCD nan 0 nan 0 nan 0 2.44699e-09 erg/s/cm2/AA 0 3.35525e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH8182 6320.95 CCD nan 0 nan 0 nan 0 2.42526e-09 erg/s/cm2/AA 0 3.34399e-20 erg/s/cm2/Hz 0 nan - KEPLER.CH8384 6262.23 CCD nan 0 nan 0 nan 0 2.4925e-09 erg/s/cm2/AA 0 3.37698e-20 erg/s/cm2/Hz 0 nan - KEPLER.V 6416.84 CCD nan 0 nan 0 nan 0 2.44701e-09 erg/s/cm2/AA 0 3.32701e-20 erg/s/cm2/Hz 0 nan - KRON.I 8400.98 CCD 0.24 1 nan 0 nan 0 1.00976e-09 erg/s/cm2/AA 0 2.36427e-20 erg/s/cm2/Hz 0 GCPD - KRON.R 6880.7 CCD 0.15 1 nan 0 nan 0 1.87881e-09 erg/s/cm2/AA 0 2.97657e-20 erg/s/cm2/Hz 0 GCPD - LANDOLT.B2 4420.33 CCD 0.036 1 nan 0 nan 0 6.20617e-09 erg/s/cm2/AA 0 4.02531e-20 erg/s/cm2/Hz 0 Synphot - LANDOLT.B3 4389 CCD 0.036 1 nan 0 nan 0 6.27617e-09 erg/s/cm2/AA 0 4.01142e-20 erg/s/cm2/Hz 0 Synphot - LANDOLT.I 7884.05 CCD 0.028 1 nan 0 nan 0 1.19379e-09 erg/s/cm2/AA 0 2.46938e-20 erg/s/cm2/Hz 0 Synphot - LANDOLT.R 6499.87 CCD 0.038 1 nan 0 nan 0 2.19313e-09 erg/s/cm2/AA 0 3.06728e-20 erg/s/cm2/Hz 0 Synphot - LANDOLT.U 3641.56 CCD 0.046 1 nan 0 nan 0 4.16396e-09 erg/s/cm2/AA 0 1.86244e-20 erg/s/cm2/Hz 0 Synphot - LANDOLT.V 5482.65 CCD 0.026 1 nan 0 nan 0 3.6234e-09 erg/s/cm2/AA 0 3.65447e-20 erg/s/cm2/Hz 0 Synphot - MAIA.GA 4819.68 CCD nan 0 nan 0 nan 0 5.09823e-09 erg/s/cm2/AA 0 3.96263e-20 erg/s/cm2/Hz 0 nan - MAIA.GEO 4884.58 CCD nan 0 nan 0 nan 0 4.89807e-09 erg/s/cm2/AA 0 3.95043e-20 erg/s/cm2/Hz 0 nan - MAIA.REO 6292.35 CCD nan 0 nan 0 nan 0 2.35005e-09 erg/s/cm2/AA 0 3.10787e-20 erg/s/cm2/Hz 0 nan - MAIA.UA 3627.44 CCD nan 0 nan 0 nan 0 3.70755e-09 erg/s/cm2/AA 0 1.63906e-20 erg/s/cm2/Hz 0 nan - MAIA.UEO 3719.35 CCD nan 0 nan 0 nan 0 4.75121e-09 erg/s/cm2/AA 0 2.21986e-20 erg/s/cm2/Hz 0 nan - MIPS.160 1.61607e+06 BOL nan 0 nan 0 nan 0 2.06608e-18 erg/s/cm2/AA 0 1.71612e-24 erg/s/cm2/Hz 0 nan - MIPS.24 243693 BOL nan 0 nan 0 nan 0 nan nan 0 7.17 Jy 1 Rieke_etal.2008 - MIPS.70 699911 BOL nan 0 nan 0 nan 0 nan nan 0 0.778 Jy 1 Rieke_etal.2008 - MOST.V 5229.97 CCD nan 0 nan 0 nan 0 3.61431e-09 erg/s/cm2/AA 0 3.72204e-20 erg/s/cm2/Hz 0 nan - MSX.A 87973.8 BOL nan 0 nan 0 nan 0 2.45715e-13 erg/s/cm2/AA 0 5.89434e-22 erg/s/cm2/Hz 0 nan - MSX.B1 42941.6 BOL nan 0 nan 0 nan 0 3.18487e-12 erg/s/cm2/AA 0 1.95838e-21 erg/s/cm2/Hz 0 nan - MSX.B2 43530.1 BOL nan 0 nan 0 nan 0 3.00872e-12 erg/s/cm2/AA 0 1.90051e-21 erg/s/cm2/Hz 0 nan - MSX.C 122001 BOL nan 0 nan 0 nan 0 5.42559e-14 erg/s/cm2/AA 0 2.67234e-22 erg/s/cm2/Hz 0 nan - MSX.D 147307 BOL nan 0 nan 0 nan 0 2.56529e-14 erg/s/cm2/AA 0 1.84361e-22 erg/s/cm2/Hz 0 nan - MSX.E 218026 BOL nan 0 nan 0 nan 0 5.75741e-15 erg/s/cm2/AA 0 8.87805e-23 erg/s/cm2/Hz 0 nan - NARROW.HA 6568.1 CCD nan 0 nan 0 nan 0 1.81345e-09 erg/s/cm2/AA 0 2.60954e-20 erg/s/cm2/Hz 0 nan - NICMOS.F110W 11235 CCD nan 0 nan 0 nan 0 4.41217e-10 erg/s/cm2/AA 0 1.85271e-20 erg/s/cm2/Hz 0 nan - NICMOS.F160W 16030.4 CCD nan 0 nan 0 nan 0 1.26805e-10 erg/s/cm2/AA 0 1.07823e-20 erg/s/cm2/Hz 0 nan - NICMOS.F187W 18705.6 CCD nan 0 nan 0 nan 0 7.27227e-11 erg/s/cm2/AA 0 8.46589e-21 erg/s/cm2/Hz 0 nan - NICMOS.F190N 19003.4 CCD nan 0 nan 0 nan 0 6.93676e-11 erg/s/cm2/AA 0 8.3559e-21 erg/s/cm2/Hz 0 nan - NICMOS.F205W 20636.1 CCD nan 0 nan 0 nan 0 5.18117e-11 erg/s/cm2/AA 0 7.2629e-21 erg/s/cm2/Hz 0 nan - NICMOS.F222M 22175.1 CCD nan 0 nan 0 nan 0 3.87211e-11 erg/s/cm2/AA 0 6.34656e-21 erg/s/cm2/Hz 0 nan - OAO2.133 1268.15 CCD -0 1 nan 0 nan 0 3.15903e-09 erg/s/cm2/AA 0 2.20993e-21 erg/s/cm2/Hz 0 nan - OAO2.143 1361.6 CCD -0.37 1 nan 0 nan 0 5.13734e-09 erg/s/cm2/AA 0 3.77232e-21 erg/s/cm2/Hz 0 nan - OAO2.155 1543.32 CCD -0.67 1 nan 0 nan 0 6.487e-09 erg/s/cm2/AA 0 5.3105e-21 erg/s/cm2/Hz 0 nan - OAO2.168 1672.04 CCD nan 0 nan 0 nan 0 6.28383e-09 erg/s/cm2/AA 0 5.85365e-21 erg/s/cm2/Hz 0 nan - OAO2.191 1879.85 CCD -0.43 1 nan 0 nan 0 5.75468e-09 erg/s/cm2/AA 0 6.93408e-21 erg/s/cm2/Hz 0 nan - OAO2.203 2030.46 CCD nan 0 nan 0 nan 0 5.31887e-09 erg/s/cm2/AA 0 7.35629e-21 erg/s/cm2/Hz 0 nan - OAO2.238 2378.09 CCD nan 0 nan 0 nan 0 4.154e-09 erg/s/cm2/AA 0 7.8582e-21 erg/s/cm2/Hz 0 nan - OAO2.246 2442.59 CCD -0.08 1 nan 0 nan 0 4.02305e-09 erg/s/cm2/AA 0 8.1017e-21 erg/s/cm2/Hz 0 nan - OAO2.294 2957.78 CCD nan 0 nan 0 nan 0 3.56132e-09 erg/s/cm2/AA 0 1.00481e-20 erg/s/cm2/Hz 0 nan - OAO2.298 2985.6 CCD nan 0 nan 0 nan 0 3.56115e-09 erg/s/cm2/AA 0 1.02962e-20 erg/s/cm2/Hz 0 nan - OAO2.332 3311 CCD nan 0 nan 0 nan 0 3.34208e-09 erg/s/cm2/AA 0 1.20747e-20 erg/s/cm2/Hz 0 nan - OAO2.425 4244.12 CCD nan 0 nan 0 nan 0 6.48667e-09 erg/s/cm2/AA 0 3.94695e-20 erg/s/cm2/Hz 0 nan - OPEN.BOL 1 BOL nan 0 nan 0 nan 0 3.00272e-05 erg/s/cm2/AA 0 9.02168e-21 erg/s/cm2/Hz 0 Bolometric_open_filters - PACS.B 703898 BOL nan 0 nan 0 nan 0 4.89079e-17 erg/s/cm2/AA 0 8.17069e-24 erg/s/cm2/Hz 0 nan - PACS.G 1.00191e+06 BOL nan 0 nan 0 nan 0 1.18461e-17 erg/s/cm2/AA 0 4.01403e-24 erg/s/cm2/Hz 0 nan - PACS.R 1.60218e+06 BOL nan 0 nan 0 nan 0 1.81605e-18 erg/s/cm2/AA 0 1.58757e-24 erg/s/cm2/Hz 0 nan - PLAVI.NIR 7500 CCD 0 1 nan 0 nan 0 1.40025e-09 erg/s/cm2/AA 0 2.61142e-20 erg/s/cm2/Hz 0 nan - PLAVI.SWIR 13000 CCD 0 1 nan 0 nan 0 2.96782e-10 erg/s/cm2/AA 0 1.61897e-20 erg/s/cm2/Hz 0 nan - PLAVI.VIS 5250 CCD 0 1 nan 0 nan 0 4.17261e-09 erg/s/cm2/AA 0 3.78382e-20 erg/s/cm2/Hz 0 nan - SAAO.35 3529.31 CCD nan 0 nan 0 nan 0 3.13125e-09 erg/s/cm2/AA 0 1.30448e-20 erg/s/cm2/Hz 0 nan - SAAO.38 3815 CCD nan 0 nan 0 nan 0 5.29817e-09 erg/s/cm2/AA 0 2.62114e-20 erg/s/cm2/Hz 0 nan - SAAO.41 4164.2 CCD nan 0 nan 0 nan 0 7.6961e-09 erg/s/cm2/AA 0 4.45415e-20 erg/s/cm2/Hz 0 nan - SAAO.42 4257.77 CCD nan 0 nan 0 nan 0 7.39116e-09 erg/s/cm2/AA 0 4.46831e-20 erg/s/cm2/Hz 0 nan - SAAO.45 4520.67 CCD nan 0 nan 0 nan 0 6.30332e-09 erg/s/cm2/AA 0 4.29668e-20 erg/s/cm2/Hz 0 nan - SAAO.48 4879.71 CCD nan 0 nan 0 nan 0 4.59875e-09 erg/s/cm2/AA 0 3.65406e-20 erg/s/cm2/Hz 0 nan - SAAO.H 16824.6 CCD 0.054 1 nan 0 nan 0 1.15125e-10 erg/s/cm2/AA 0 1.03864e-20 erg/s/cm2/Hz 0 VerhoelstPHD - SAAO.J 12344.1 CCD 0.054 1 nan 0 nan 0 3.17476e-10 erg/s/cm2/AA 0 1.6101e-20 erg/s/cm2/Hz 0 VerhoelstPHD - SAAO.K 21968.5 CCD 0.014 1 nan 0 nan 0 3.9143e-11 erg/s/cm2/AA 0 6.42907e-21 erg/s/cm2/Hz 0 VerhoelstPHD - SAAO.L 34308.7 CCD 0.032 1 nan 0 nan 0 7.45373e-12 erg/s/cm2/AA 0 2.95196e-21 erg/s/cm2/Hz 0 VerhoelstPHD - SAAO.M 50663.9 CCD nan 0 nan 0 nan 0 1.67784e-12 erg/s/cm2/AA 0 1.43656e-21 erg/s/cm2/Hz 0 nan - SAAO.N 116305 CCD nan 0 nan 0 nan 0 7.19901e-14 erg/s/cm2/AA 0 3.24975e-22 erg/s/cm2/Hz 0 nan - SAAO.Q 219546 CCD nan 0 nan 0 nan 0 5.86648e-15 erg/s/cm2/AA 0 9.44081e-23 erg/s/cm2/Hz 0 nan - SCUBA.350NB 3.45424e+06 BOL nan 0 nan 0 nan 0 1.41979e-19 erg/s/cm2/AA 0 4.20985e-25 erg/s/cm2/Hz 0 nan - SCUBA.450NB 4.45476e+06 BOL nan 0 nan 0 nan 0 1.26491e-19 erg/s/cm2/AA 0 8.34657e-25 erg/s/cm2/Hz 0 nan - SCUBA.450WB 4.56406e+06 BOL nan 0 nan 0 nan 0 1.26491e-19 erg/s/cm2/AA 0 8.80122e-25 erg/s/cm2/Hz 0 nan - SCUBA.750NB 7.457e+06 BOL nan 0 nan 0 nan 0 nan erg/s/cm2/AA 0 nan erg/s/cm2/Hz 0 nan - SCUBA.850NB 8.61883e+06 BOL nan 0 nan 0 nan 0 nan erg/s/cm2/AA 0 nan erg/s/cm2/Hz 0 nan - SCUBA.850WB 8.5886e+06 BOL nan 0 nan 0 nan 0 nan erg/s/cm2/AA 0 nan erg/s/cm2/Hz 0 nan - SDSS.G 4694.35 CCD -0.102146 0 0 1 nan 0 5.46588e-09 erg/s/cm2/AA 0 4.01094e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - SDSS.GP 4758.58 CCD -0.1 1 nan 0 nan 0 5.29168e-09 erg/s/cm2/AA 0 3.952e-20 erg/s/cm2/Hz 0 http://www.ucolick.org/~cnaw/sun.html - SDSS.I 7505.34 CCD 0.356723 0 0 1 nan 0 1.39479e-09 erg/s/cm2/AA 0 2.60652e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - SDSS.IP 7720.93 CCD 0.38 1 nan 0 nan 0 1.28012e-09 erg/s/cm2/AA 0 2.53648e-20 erg/s/cm2/Hz 0 http://www.ucolick.org/~cnaw/sun.html - SDSS.R 6177.92 CCD 0.140706 0 0 1 nan 0 2.51312e-09 erg/s/cm2/AA 0 3.17677e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - SDSS.RP 6260.48 CCD 0.15 1 nan 0 nan 0 2.42517e-09 erg/s/cm2/AA 0 3.15748e-20 erg/s/cm2/Hz 0 http://www.ucolick.org/~cnaw/sun.html - SDSS.U 3522.16 CCD 1.017 0 0.04 1 nan 0 3.58004e-09 erg/s/cm2/AA 0 1.49043e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - SDSS.UP 3551.91 CCD 0.98 1 nan 0 nan 0 3.43499e-09 erg/s/cm2/AA 0 1.44086e-20 erg/s/cm2/Hz 0 http://www.ucolick.org/~cnaw/sun.html - SDSS.Z 8936.17 CCD 0.516673 0 0 1 nan 0 8.40621e-10 erg/s/cm2/AA 0 2.2582e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - SDSS.ZP 10689.4 CCD 0.53 1 nan 0 nan 0 5.27087e-10 erg/s/cm2/AA 0 1.97625e-20 erg/s/cm2/Hz 0 http://www.ucolick.org/~cnaw/sun.html - SLOAN.I 7732.95 CCD nan 0 nan 0 nan 0 1.26651e-09 erg/s/cm2/AA 0 2.52626e-20 erg/s/cm2/Hz 0 nan - SLOAN.R 6218.06 CCD nan 0 nan 0 nan 0 2.45948e-09 erg/s/cm2/AA 0 3.23041e-20 erg/s/cm2/Hz 0 nan - SPIRE.250 2.47208e+06 BOL nan 0 nan 0 nan 0 3.08997e-19 erg/s/cm2/AA 0 6.35209e-25 erg/s/cm2/Hz 0 nan - SPIRE.350 3.46967e+06 BOL nan 0 nan 0 nan 0 1.48846e-19 erg/s/cm2/AA 0 4.31535e-25 erg/s/cm2/Hz 0 nan - SPIRE.500 4.96743e+06 BOL nan 0 nan 0 nan 0 5.0712e-19 erg/s/cm2/AA 0 8.90025e-25 erg/s/cm2/Hz 0 nan - STEBBINS.B 4876.97 CCD nan 0 nan 0 nan 0 4.96752e-09 erg/s/cm2/AA 0 3.93815e-20 erg/s/cm2/Hz 0 nan - STEBBINS.G 5680.69 CCD nan 0 nan 0 nan 0 3.25733e-09 erg/s/cm2/AA 0 3.61377e-20 erg/s/cm2/Hz 0 nan - STEBBINS.I 10233.8 CCD nan 0 nan 0 nan 0 5.93039e-10 erg/s/cm2/AA 0 2.07171e-20 erg/s/cm2/Hz 0 nan - STEBBINS.R 7107.47 CCD nan 0 nan 0 nan 0 1.64931e-09 erg/s/cm2/AA 0 2.77906e-20 erg/s/cm2/Hz 0 nan - STEBBINS.U 3516.34 CCD nan 0 nan 0 nan 0 3.6432e-09 erg/s/cm2/AA 0 1.55943e-20 erg/s/cm2/Hz 0 nan - STEBBINS.V 4213.07 CCD nan 0 nan 0 nan 0 6.48456e-09 erg/s/cm2/AA 0 3.89575e-20 erg/s/cm2/Hz 0 nan - STISCCD.50CCD 5751.92 CCD nan 0 nan 0 nan 0 2.63632e-09 erg/s/cm2/AA 0 2.67482e-20 erg/s/cm2/Hz 0 nan - STISCCD.F28X50LP 7218.91 CCD nan 0 nan 0 nan 0 1.63377e-09 erg/s/cm2/AA 0 2.80609e-20 erg/s/cm2/Hz 0 nan - STISFUV.25MAMA 1369.38 CCD nan 0 nan 0 nan 0 4.03606e-09 erg/s/cm2/AA 0 2.6959e-21 erg/s/cm2/Hz 0 nan - STISFUV.F25LYA 1242.79 CCD nan 0 nan 0 nan 0 9.49493e-10 erg/s/cm2/AA 0 5.43434e-22 erg/s/cm2/Hz 0 nan - STISFUV.F25QTZ 1595.28 CCD nan 0 nan 0 nan 0 6.63231e-09 erg/s/cm2/AA 0 5.62896e-21 erg/s/cm2/Hz 0 nan - STISFUV.F25SRF2 1453.12 CCD nan 0 nan 0 nan 0 5.68859e-09 erg/s/cm2/AA 0 4.07304e-21 erg/s/cm2/Hz 0 nan - STISNUV.25MAMA 2265.03 CCD nan 0 nan 0 nan 0 4.35968e-09 erg/s/cm2/AA 0 7.52623e-21 erg/s/cm2/Hz 0 nan - STISNUV.F25CIII 2011.58 CCD nan 0 nan 0 nan 0 5.49871e-09 erg/s/cm2/AA 0 7.39963e-21 erg/s/cm2/Hz 0 nan - STISNUV.F25CN182 2006.84 CCD nan 0 nan 0 nan 0 5.2484e-09 erg/s/cm2/AA 0 7.08957e-21 erg/s/cm2/Hz 0 nan - STISNUV.F25CN270 2723.15 CCD nan 0 nan 0 nan 0 3.74675e-09 erg/s/cm2/AA 0 9.19837e-21 erg/s/cm2/Hz 0 nan - STISNUV.F25MGII 2874.17 CCD nan 0 nan 0 nan 0 3.67148e-09 erg/s/cm2/AA 0 9.66371e-21 erg/s/cm2/Hz 0 nan - STISNUV.F25QTZ 2361.42 CCD nan 0 nan 0 nan 0 4.32611e-09 erg/s/cm2/AA 0 8.02842e-21 erg/s/cm2/Hz 0 nan - STISNUV.F25SRF2 2306.38 CCD nan 0 nan 0 nan 0 4.36202e-09 erg/s/cm2/AA 0 7.75654e-21 erg/s/cm2/Hz 0 nan - STROMGREN.B 4671.2 CCD 0.018 1 nan 0 nan 0 5.73426e-09 erg/s/cm2/AA 0 4.16761e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - STROMGREN.HBN 4876.97 CCD 2.905 1 nan 0 nan 0 4.52476e-09 erg/s/cm2/AA 0 3.59635e-20 erg/s/cm2/Hz 0 GCPDaverage_USE_ONLY_AS_RATIO!!! - STROMGREN.HBW 4857.03 CCD 0 1 nan 0 nan 0 3.60259e-09 erg/s/cm2/AA 0 2.83176e-20 erg/s/cm2/Hz 0 GCPDaverage_USE_ONLY_AS_RATIO!!! - STROMGREN.U 3462.92 CCD 1.432 1 nan 0 nan 0 3.17731e-09 erg/s/cm2/AA 0 1.27398e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - STROMGREN.V 4108.07 CCD 0.179 1 nan 0 nan 0 7.28342e-09 erg/s/cm2/AA 0 4.10546e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - STROMGREN.Y 5477.32 CCD 0.014 1 nan 0 nan 0 3.60969e-09 erg/s/cm2/AA 0 3.62014e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - SUPRIMECAM.B 4360.93 CCD nan 0 nan 0 nan 0 6.16666e-09 erg/s/cm2/AA 0 3.94497e-20 erg/s/cm2/Hz 0 nan - SUPRIMECAM.GP 4687.73 CCD nan 0 nan 0 nan 0 5.44773e-09 erg/s/cm2/AA 0 3.97427e-20 erg/s/cm2/Hz 0 nan - SUPRIMECAM.IC 7961.95 CCD nan 0 nan 0 nan 0 1.15356e-09 erg/s/cm2/AA 0 2.43927e-20 erg/s/cm2/Hz 0 nan - SUPRIMECAM.IP 7662.5 CCD nan 0 nan 0 nan 0 1.30399e-09 erg/s/cm2/AA 0 2.55385e-20 erg/s/cm2/Hz 0 nan - SUPRIMECAM.NB711 7120.08 CCD nan 0 nan 0 nan 0 1.62955e-09 erg/s/cm2/AA 0 2.75561e-20 erg/s/cm2/Hz 0 nan - SUPRIMECAM.NB816 8150.47 CCD nan 0 nan 0 nan 0 1.06791e-09 erg/s/cm2/AA 0 2.36634e-20 erg/s/cm2/Hz 0 nan - SUPRIMECAM.NB921 9183.24 CCD nan 0 nan 0 nan 0 8.09109e-10 erg/s/cm2/AA 0 2.27598e-20 erg/s/cm2/Hz 0 nan - SUPRIMECAM.RC 6499.24 CCD nan 0 nan 0 nan 0 2.1412e-09 erg/s/cm2/AA 0 3.01692e-20 erg/s/cm2/Hz 0 nan - SUPRIMECAM.RP 6239.61 CCD nan 0 nan 0 nan 0 2.43568e-09 erg/s/cm2/AA 0 3.192e-20 erg/s/cm2/Hz 0 nan - SUPRIMECAM.V 5440.31 CCD nan 0 nan 0 nan 0 3.68087e-09 erg/s/cm2/AA 0 3.69748e-20 erg/s/cm2/Hz 0 nan - SUPRIMECAM.ZP 9184.56 CCD nan 0 nan 0 nan 0 8.01828e-10 erg/s/cm2/AA 0 2.2562e-20 erg/s/cm2/Hz 0 nan - TD1.1565 1565 CCD -0.590882 0 nan 0 0.075 1 6.70429e-09 erg/s/cm2/AA 0 5.47936e-21 erg/s/cm2/Hz 0 Thompson1978 - TD1.1965 1965 CCD -0.439466 0 nan 0 0.075 1 5.83158e-09 erg/s/cm2/AA 0 7.51073e-21 erg/s/cm2/Hz 0 Thompson1978 - TD1.2365 2365 CCD -0.0438487 0 nan 0 0.075 1 4.05079e-09 erg/s/cm2/AA 0 7.55709e-21 erg/s/cm2/Hz 0 Thompson1978 - TD1.2740 2740 CCD 0.0711183 0 nan 0 0.075 1 3.64378e-09 erg/s/cm2/AA 0 9.12394e-21 erg/s/cm2/Hz 0 Thompson1978 - TYCHO.BT 4204.4 CCD 0.029 1 nan 0 nan 0 6.55685e-09 erg/s/cm2/AA 0 3.90875e-20 erg/s/cm2/Hz 0 nan - TYCHO.VT 5321.86 CCD 0.03 1 nan 0 nan 0 3.91305e-09 erg/s/cm2/AA 0 3.70697e-20 erg/s/cm2/Hz 0 nan - TYCHO2.BT 4204.4 CCD 0.069 1 nan 0 nan 0 6.55685e-09 erg/s/cm2/AA 0 3.79664e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - TYCHO2.VT 5321.86 CCD 0.032 1 nan 0 nan 0 3.91305e-09 erg/s/cm2/AA 0 3.70691e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 - ULTRACAM.GP 4781.03 CCD nan 0 nan 0 nan 0 5.18851e-09 erg/s/cm2/AA 0 3.94729e-20 erg/s/cm2/Hz 0 nan - ULTRACAM.IP 7653.5 CCD nan 0 nan 0 nan 0 1.30702e-09 erg/s/cm2/AA 0 2.54605e-20 erg/s/cm2/Hz 0 nan - ULTRACAM.RP 6231.71 CCD nan 0 nan 0 nan 0 2.44207e-09 erg/s/cm2/AA 0 3.14866e-20 erg/s/cm2/Hz 0 nan - ULTRACAM.UP 3507.65 CCD nan 0 nan 0 nan 0 3.4292e-09 erg/s/cm2/AA 0 1.40107e-20 erg/s/cm2/Hz 0 nan - ULTRACAM.ZP 10094.7 CCD nan 0 nan 0 nan 0 6.10665e-10 erg/s/cm2/AA 0 2.05429e-20 erg/s/cm2/Hz 0 nan - USNOB1.B1 4448.06 CCD 0.03 1 nan 0 nan 0 6.1307e-09 erg/s/cm2/AA 0 4.02221e-20 erg/s/cm2/Hz 0 Johnson-like - USNOB1.R1 6939.52 CCD 0.07 1 nan 0 nan 0 1.81615e-09 erg/s/cm2/AA 0 2.88912e-20 erg/s/cm2/Hz 0 Johnson-like - UVEX.G 4857.28 CCD 0 1 nan 0 nan 0 5.05341e-09 erg/s/cm2/AA 0 3.93862e-20 erg/s/cm2/Hz 0 Groot_et_al._2009 - UVEX.HA 6568.17 CCD 0 1 nan 0 nan 0 1.81344e-09 erg/s/cm2/AA 0 2.60949e-20 erg/s/cm2/Hz 0 Groot_et_al._2009 - UVEX.I 7746 CCD 0 1 nan 0 nan 0 1.2915e-09 erg/s/cm2/AA 0 2.54412e-20 erg/s/cm2/Hz 0 Groot_et_al._2009 - UVEX.R 6230.09 CCD 0 1 nan 0 nan 0 2.47016e-09 erg/s/cm2/AA 0 3.23487e-20 erg/s/cm2/Hz 0 Groot_et_al._2009 - UVEX.U 3617.28 CCD 0 1 nan 0 nan 0 4.10664e-09 erg/s/cm2/AA 0 1.82805e-20 erg/s/cm2/Hz 0 Groot_et_al._2009 - VILNIUS.P 3737.53 CCD 1.275 1 nan 0 nan 0 4.32219e-09 erg/s/cm2/AA 0 2.0527e-20 erg/s/cm2/Hz 0 GCPD - VILNIUS.S 6527.78 CCD -0.11 1 nan 0 nan 0 2.02595e-09 erg/s/cm2/AA 0 2.87864e-20 erg/s/cm2/Hz 0 GCPD - VILNIUS.U 3444.61 CCD 1.9 1 nan 0 nan 0 3.19379e-09 erg/s/cm2/AA 0 1.26758e-20 erg/s/cm2/Hz 0 GCPD - VILNIUS.V 5440.21 CCD 0.03 1 nan 0 nan 0 3.6821e-09 erg/s/cm2/AA 0 3.65002e-20 erg/s/cm2/Hz 0 GCPD - VILNIUS.X 4050.48 CCD 0.47 1 nan 0 nan 0 7.13368e-09 erg/s/cm2/AA 0 3.917e-20 erg/s/cm2/Hz 0 GCPD - VILNIUS.Y 4661.8 CCD 0.18 1 nan 0 nan 0 5.73013e-09 erg/s/cm2/AA 0 4.14095e-20 erg/s/cm2/Hz 0 GCPD - VILNIUS.Z 5159.82 CCD 0.085 1 nan 0 nan 0 4.29701e-09 erg/s/cm2/AA 0 3.81592e-20 erg/s/cm2/Hz 0 GCPD - VISIR.ARIII 89951 CCD nan 0 nan 0 nan 0 1.7695e-13 erg/s/cm2/AA 0 4.77575e-22 erg/s/cm2/Hz 0 nan - VISIR.NEII 127947 CCD nan 0 nan 0 nan 0 4.38852e-14 erg/s/cm2/AA 0 2.39638e-22 erg/s/cm2/Hz 0 nan - VISIR.NEII1 122706 CCD nan 0 nan 0 nan 0 5.16538e-14 erg/s/cm2/AA 0 2.59425e-22 erg/s/cm2/Hz 0 nan - VISIR.NEII2 130459 CCD nan 0 nan 0 nan 0 4.06316e-14 erg/s/cm2/AA 0 2.30669e-22 erg/s/cm2/Hz 0 nan - VISIR.PAH1 85975.1 CCD nan 0 nan 0 nan 0 2.10709e-13 erg/s/cm2/AA 0 5.19526e-22 erg/s/cm2/Hz 0 nan - VISIR.PAH2 112676 CCD nan 0 nan 0 nan 0 7.2493e-14 erg/s/cm2/AA 0 3.06998e-22 erg/s/cm2/Hz 0 nan - VISIR.PAH22 117387 CCD nan 0 nan 0 nan 0 6.17297e-14 erg/s/cm2/AA 0 2.83735e-22 erg/s/cm2/Hz 0 nan - VISIR.Q1 177308 CCD nan 0 nan 0 nan 0 1.20084e-14 erg/s/cm2/AA 0 1.25927e-22 erg/s/cm2/Hz 0 nan - VISIR.Q2 187780 CCD nan 0 nan 0 nan 0 9.5266e-15 erg/s/cm2/AA 0 1.12011e-22 erg/s/cm2/Hz 0 svo.cab.inta-csic.es/theory/fps - VISIR.Q3 195196 CCD nan 0 nan 0 nan 0 8.18106e-15 erg/s/cm2/AA 0 1.03976e-22 erg/s/cm2/Hz 0 nan - VISIR.SIC 117818 CCD nan 0 nan 0 nan 0 6.19168e-14 erg/s/cm2/AA 0 2.85719e-22 erg/s/cm2/Hz 0 svo.cab.inta-csic.es/theory/fps - VISIR.SIV 104565 CCD nan 0 nan 0 nan 0 9.6862e-14 erg/s/cm2/AA 0 3.53257e-22 erg/s/cm2/Hz 0 svo.cab.inta-csic.es/theory/fps - VISIR.SIV1 98633.3 CCD nan 0 nan 0 nan 0 1.22904e-13 erg/s/cm2/AA 0 3.98835e-22 erg/s/cm2/Hz 0 nan - WALRAVEN.B 4301.33 CCD nan 0 nan 0 nan 0 1.23e-11 erg/s/cm2/AA 1 7.66328e-23 erg/s/cm2/Hz 0 GCPD - WALRAVEN.L 3836.11 CCD nan 0 nan 0 nan 0 1.52e-11 erg/s/cm2/AA 1 7.46436e-23 erg/s/cm2/Hz 0 GCPD - WALRAVEN.U 3633.35 CCD nan 0 nan 0 nan 0 1.61e-11 erg/s/cm2/AA 1 7.08527e-23 erg/s/cm2/Hz 0 GCPD - WALRAVEN.V 5450.85 CCD nan 0 nan 0 nan 0 6.73e-12 erg/s/cm2/AA 1 6.71388e-23 erg/s/cm2/Hz 0 GCPD - WALRAVEN.W 3254.61 CCD nan 0 nan 0 nan 0 2.12e-11 erg/s/cm2/AA 1 7.48843e-23 erg/s/cm2/Hz 0 GCPD - WFCAM.H 16313 CCD 0 1 nan 0 nan 0 1.14e-09 W/m2/mum 1 1019 Jy 1 Hewett2011 - WFCAM.J 12483 CCD 0 1 nan 0 nan 0 2.94e-09 W/m2/mum 1 1530 Jy 1 Hewett2011 - WFCAM.K 22010 CCD 0 1 nan 0 nan 0 3.89e-10 W/m2/mum 1 631 Jy 1 Hewett2011 - WFCAM.Y 10305 CCD 0 1 nan 0 nan 0 5.71e-09 W/m2/mum 1 2026 Jy 1 Hewett2011 - WFCAM.Z 8817 CCD 0 1 nan 0 nan 0 8.59e-09 W/m2/mum 1 2232 Jy 1 Hewett2011 - WFPC2.F170W 1831.19 CCD nan 0 nan 0 nan 0 5.69812e-09 erg/s/cm2/AA 0 6.32238e-21 erg/s/cm2/Hz 0 nan - WFPC2.F255W 2600.08 CCD nan 0 nan 0 nan 0 3.80452e-09 erg/s/cm2/AA 0 8.57101e-21 erg/s/cm2/Hz 0 nan - WFPC2.F300W 2992.8 CCD nan 0 nan 0 nan 0 3.54536e-09 erg/s/cm2/AA 0 1.02039e-20 erg/s/cm2/Hz 0 nan - WFPC2.F336W 3359.5 CCD nan 0 nan 0 nan 0 3.24276e-09 erg/s/cm2/AA 0 1.21943e-20 erg/s/cm2/Hz 0 nan - WFPC2.F380W 3982.84 CCD nan 0 nan 0 nan 0 6.0436e-09 erg/s/cm2/AA 0 3.23925e-20 erg/s/cm2/Hz 0 nan - WFPC2.F439W 4312.1 CCD nan 0 nan 0 nan 0 6.69427e-09 erg/s/cm2/AA 0 4.14592e-20 erg/s/cm2/Hz 0 nan - WFPC2.F450W 4557.37 CCD nan 0 nan 0 nan 0 5.66762e-09 erg/s/cm2/AA 0 3.92657e-20 erg/s/cm2/Hz 0 nan - WFPC2.F467M 4670.26 CCD nan 0 nan 0 nan 0 5.75909e-09 erg/s/cm2/AA 0 4.18888e-20 erg/s/cm2/Hz 0 nan - WFPC2.F502N 5013.16 CCD nan 0 nan 0 nan 0 4.68588e-09 erg/s/cm2/AA 0 3.92794e-20 erg/s/cm2/Hz 0 nan - WFPC2.F547M 5483.89 CCD nan 0 nan 0 nan 0 3.59897e-09 erg/s/cm2/AA 0 3.63599e-20 erg/s/cm2/Hz 0 nan - WFPC2.F555W 5442.93 CCD nan 0 nan 0 nan 0 3.67703e-09 erg/s/cm2/AA 0 3.68718e-20 erg/s/cm2/Hz 0 nan - WFPC2.F569W 5644.4 CCD nan 0 nan 0 nan 0 3.31373e-09 erg/s/cm2/AA 0 3.55815e-20 erg/s/cm2/Hz 0 nan - WFPC2.F606W 6001.29 CCD nan 0 nan 0 nan 0 2.75401e-09 erg/s/cm2/AA 0 3.33546e-20 erg/s/cm2/Hz 0 nan - WFPC2.F656N 6563.65 CCD nan 0 nan 0 nan 0 1.49986e-09 erg/s/cm2/AA 0 2.15521e-20 erg/s/cm2/Hz 0 nan - WFPC2.F673N 6732.25 CCD nan 0 nan 0 nan 0 1.93684e-09 erg/s/cm2/AA 0 2.9281e-20 erg/s/cm2/Hz 0 nan - WFPC2.F675W 6717.73 CCD nan 0 nan 0 nan 0 1.93418e-09 erg/s/cm2/AA 0 2.90289e-20 erg/s/cm2/Hz 0 nan - WFPC2.F702W 6917.13 CCD nan 0 nan 0 nan 0 1.78478e-09 erg/s/cm2/AA 0 2.82719e-20 erg/s/cm2/Hz 0 nan - WFPC2.F785LP 8686.31 CCD nan 0 nan 0 nan 0 9.14666e-10 erg/s/cm2/AA 0 2.30524e-20 erg/s/cm2/Hz 0 nan - WFPC2.F791W 7872.46 CCD nan 0 nan 0 nan 0 1.20429e-09 erg/s/cm2/AA 0 2.47883e-20 erg/s/cm2/Hz 0 nan - WFPC2.F814W 7995.94 CCD nan 0 nan 0 nan 0 1.16139e-09 erg/s/cm2/AA 0 2.47199e-20 erg/s/cm2/Hz 0 nan - WFPC2.F850LP 9114.01 CCD nan 0 nan 0 nan 0 8.13057e-10 erg/s/cm2/AA 0 2.25387e-20 erg/s/cm2/Hz 0 nan +#photband eff_wave type vegamag vegamag_lit ABmag ABmag_lit STmag STmag_lit Flam0 Flam0_units Flam0_lit Fnu0 Fnu0_units Fnu0_lit source +#f8 |S3 >f8 int32 >f8 int32 >f8 int32 >f8 |S50 int32 >f8 |S50 int32 |S100 + 2MASS.H 16497.1 CCD 0.009 1 nan 0 nan 0 1.13535e-10 erg/s/cm2/AA 0 1.03036e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + 2MASS.J 12412.1 CCD -0.021 1 nan 0 nan 0 3.11048e-10 erg/s/cm2/AA 0 1.59361e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + 2MASS.KS 21909.2 CCD 0 1 nan 0 nan 0 4.27871e-11 erg/s/cm2/AA 0 6.68263e-21 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + ACSHRC.F220W 2255.32 CCD nan 0 nan 0 nan 0 4.53976e-09 erg/s/cm2/AA 0 7.69904e-21 erg/s/cm2/Hz 0 nan + ACSHRC.F250W 2715.77 CCD nan 0 nan 0 nan 0 3.72224e-09 erg/s/cm2/AA 0 9.0493e-21 erg/s/cm2/Hz 0 nan + ACSHRC.F330W 3362.73 CCD nan 0 nan 0 nan 0 3.24002e-09 erg/s/cm2/AA 0 1.22087e-20 erg/s/cm2/Hz 0 nan + ACSHRC.F344N 3433.77 CCD nan 0 nan 0 nan 0 3.19801e-09 erg/s/cm2/AA 0 1.25767e-20 erg/s/cm2/Hz 0 nan + ACSHRC.F435W 4311.02 CCD nan 0 nan 0 nan 0 6.34395e-09 erg/s/cm2/AA 0 3.90888e-20 erg/s/cm2/Hz 0 nan + ACSHRC.F475W 4775.76 CCD nan 0 nan 0 nan 0 5.20376e-09 erg/s/cm2/AA 0 3.99348e-20 erg/s/cm2/Hz 0 nan + ACSHRC.F502N 5022.89 CCD nan 0 nan 0 nan 0 4.65992e-09 erg/s/cm2/AA 0 3.92133e-20 erg/s/cm2/Hz 0 nan + ACSHRC.F550M 5579.84 CCD nan 0 nan 0 nan 0 3.41888e-09 erg/s/cm2/AA 0 3.555e-20 erg/s/cm2/Hz 0 nan + ACSHRC.F555W 5355.96 CCD nan 0 nan 0 nan 0 3.81922e-09 erg/s/cm2/AA 0 3.68717e-20 erg/s/cm2/Hz 0 nan + ACSHRC.F606W 5887.96 CCD nan 0 nan 0 nan 0 2.91419e-09 erg/s/cm2/AA 0 3.40628e-20 erg/s/cm2/Hz 0 nan + ACSHRC.F625W 6295.53 CCD nan 0 nan 0 nan 0 2.3697e-09 erg/s/cm2/AA 0 3.11722e-20 erg/s/cm2/Hz 0 nan + ACSHRC.F658N 6583.73 CCD nan 0 nan 0 nan 0 1.77539e-09 erg/s/cm2/AA 0 2.5677e-20 erg/s/cm2/Hz 0 nan + ACSHRC.F660N 6599.04 CCD nan 0 nan 0 nan 0 1.93351e-09 erg/s/cm2/AA 0 2.80836e-20 erg/s/cm2/Hz 0 nan + ACSHRC.F775W 7665.09 CCD nan 0 nan 0 nan 0 1.30147e-09 erg/s/cm2/AA 0 2.54134e-20 erg/s/cm2/Hz 0 nan + ACSHRC.F814W 8115.33 CCD nan 0 nan 0 nan 0 1.11694e-09 erg/s/cm2/AA 0 2.45097e-20 erg/s/cm2/Hz 0 nan + ACSHRC.F850LP 9145.23 CCD nan 0 nan 0 nan 0 8.0509e-10 erg/s/cm2/AA 0 2.25021e-20 erg/s/cm2/Hz 0 nan + ACSHRC.F892N 8916.08 CCD nan 0 nan 0 nan 0 8.73963e-10 erg/s/cm2/AA 0 2.31804e-20 erg/s/cm2/Hz 0 nan + ACSSBC.F115LP 1406.06 CCD nan 0 nan 0 nan 0 4.52309e-09 erg/s/cm2/AA 0 3.15346e-21 erg/s/cm2/Hz 0 nan + ACSSBC.F122M 1273.65 CCD nan 0 nan 0 nan 0 1.78382e-09 erg/s/cm2/AA 0 1.07417e-21 erg/s/cm2/Hz 0 nan + ACSSBC.F125LP 1437.54 CCD nan 0 nan 0 nan 0 5.10956e-09 erg/s/cm2/AA 0 3.64435e-21 erg/s/cm2/Hz 0 nan + ACSSBC.F140LP 1527 CCD nan 0 nan 0 nan 0 6.32305e-09 erg/s/cm2/AA 0 4.94628e-21 erg/s/cm2/Hz 0 nan + ACSSBC.F150LP 1610.66 CCD nan 0 nan 0 nan 0 6.59853e-09 erg/s/cm2/AA 0 5.70815e-21 erg/s/cm2/Hz 0 nan + ACSSBC.F165LP 1757.9 CCD nan 0 nan 0 nan 0 6.23655e-09 erg/s/cm2/AA 0 6.42689e-21 erg/s/cm2/Hz 0 nan + ACSWFC.F435W 4317.42 CCD nan 0 nan 0 nan 0 6.41395e-09 erg/s/cm2/AA 0 3.96998e-20 erg/s/cm2/Hz 0 nan + ACSWFC.F475W 4744.37 CCD nan 0 nan 0 nan 0 5.29277e-09 erg/s/cm2/AA 0 4.00885e-20 erg/s/cm2/Hz 0 nan + ACSWFC.F502N 5023 CCD nan 0 nan 0 nan 0 4.66007e-09 erg/s/cm2/AA 0 3.92177e-20 erg/s/cm2/Hz 0 nan + ACSWFC.F550M 5581.24 CCD nan 0 nan 0 nan 0 3.41655e-09 erg/s/cm2/AA 0 3.55448e-20 erg/s/cm2/Hz 0 nan + ACSWFC.F555W 5359.57 CCD nan 0 nan 0 nan 0 3.81192e-09 erg/s/cm2/AA 0 3.68525e-20 erg/s/cm2/Hz 0 nan + ACSWFC.F606W 5917.68 CCD nan 0 nan 0 nan 0 2.87124e-09 erg/s/cm2/AA 0 3.38912e-20 erg/s/cm2/Hz 0 nan + ACSWFC.F625W 6310.47 CCD nan 0 nan 0 nan 0 2.35216e-09 erg/s/cm2/AA 0 3.10892e-20 erg/s/cm2/Hz 0 nan + ACSWFC.F658N 6583.95 CCD nan 0 nan 0 nan 0 1.77549e-09 erg/s/cm2/AA 0 2.56823e-20 erg/s/cm2/Hz 0 nan + ACSWFC.F660N 6599.43 CCD nan 0 nan 0 nan 0 1.93353e-09 erg/s/cm2/AA 0 2.80924e-20 erg/s/cm2/Hz 0 nan + ACSWFC.F775W 7693.02 CCD nan 0 nan 0 nan 0 1.28672e-09 erg/s/cm2/AA 0 2.53085e-20 erg/s/cm2/Hz 0 nan + ACSWFC.F814W 8059.77 CCD nan 0 nan 0 nan 0 1.13432e-09 erg/s/cm2/AA 0 2.4528e-20 erg/s/cm2/Hz 0 nan + ACSWFC.F850LP 9054.79 CCD nan 0 nan 0 nan 0 8.21948e-10 erg/s/cm2/AA 0 2.25165e-20 erg/s/cm2/Hz 0 nan + AKARI.L15 156841 BOL nan 0 nan 0 nan 0 2.00796e-14 erg/s/cm2/AA 0 1.67264e-22 erg/s/cm2/Hz 0 nan + AKARI.L18W 186321 BOL nan 0 nan 0 nan 0 1.03826e-14 erg/s/cm2/AA 0 1.23954e-22 erg/s/cm2/Hz 0 nan + AKARI.L24 229866 BOL nan 0 nan 0 nan 0 4.33162e-15 erg/s/cm2/AA 0 7.69291e-23 erg/s/cm2/Hz 0 nan + AKARI.N160 1.61215e+06 BOL nan 0 nan 0 nan 0 1.71999e-18 erg/s/cm2/AA 0 1.49962e-24 erg/s/cm2/Hz 0 nan + AKARI.N2 23493.5 BOL nan 0 nan 0 nan 0 3.19441e-11 erg/s/cm2/AA 0 6.0077e-21 erg/s/cm2/Hz 0 nan + AKARI.N3 31990.5 BOL nan 0 nan 0 nan 0 9.98454e-12 erg/s/cm2/AA 0 3.45587e-21 erg/s/cm2/Hz 0 nan + AKARI.N4 43480.4 BOL nan 0 nan 0 nan 0 3.0884e-12 erg/s/cm2/AA 0 1.97489e-21 erg/s/cm2/Hz 0 nan + AKARI.N60 649413 BOL nan 0 nan 0 nan 0 6.83648e-17 erg/s/cm2/AA 0 9.75466e-24 erg/s/cm2/Hz 0 nan + AKARI.S11 105329 BOL nan 0 nan 0 nan 0 9.78412e-14 erg/s/cm2/AA 0 3.69336e-22 erg/s/cm2/Hz 0 nan + AKARI.S7 71510.5 BOL nan 0 nan 0 nan 0 4.45022e-13 erg/s/cm2/AA 0 7.71253e-22 erg/s/cm2/Hz 0 nan + AKARI.S9W 87264.6 BOL nan 0 nan 0 nan 0 2.10269e-13 erg/s/cm2/AA 0 5.49591e-22 erg/s/cm2/Hz 0 nan + AKARI.WIDEL 1.45288e+06 BOL nan 0 nan 0 nan 0 2.67019e-18 erg/s/cm2/AA 0 1.90997e-24 erg/s/cm2/Hz 0 nan + AKARI.WIDES 836666 BOL nan 0 nan 0 nan 0 2.61537e-17 erg/s/cm2/AA 0 6.32301e-24 erg/s/cm2/Hz 0 nan + ANS.15N 1545 CCD -0.675376 0 nan 0 0 1 6.76317e-09 erg/s/cm2/AA 0 5.3858e-21 erg/s/cm2/Hz 0 Wesselius1980 + ANS.15W 1549 CCD -0.687251 0 nan 0 0 1 6.83755e-09 erg/s/cm2/AA 0 5.48931e-21 erg/s/cm2/Hz 0 Wesselius1980 + ANS.18 1805.68 CCD -0.568371 0 nan 0 0 1 6.12842e-09 erg/s/cm2/AA 0 6.66704e-21 erg/s/cm2/Hz 0 Wesselius1980 + ANS.22 2199.22 CCD nan 0 nan 0 nan 0 4.73981e-09 erg/s/cm2/AA 0 7.6507e-21 erg/s/cm2/Hz 0 nan + ANS.25 2493 CCD -0.0606239 0 nan 0 0 1 3.83928e-09 erg/s/cm2/AA 0 7.93652e-21 erg/s/cm2/Hz 0 Wesselius1980 + ANS.33 3295.35 CCD 0.102499 0 nan 0 0 1 3.3037e-09 erg/s/cm2/AA 0 1.19709e-20 erg/s/cm2/Hz 0 Wesselius1980 + APEX.LABOCA 8.78475e+06 CCD nan 0 nan 0 nan 0 nan erg/s/cm2/AA 0 nan erg/s/cm2/Hz 0 nan + ARGUE.R 6874.8 CCD 0.285 1 nan 0 nan 0 1.80973e-09 erg/s/cm2/AA 0 2.84994e-20 erg/s/cm2/Hz 0 GCPD + ARGUE.R8 8485.73 CCD 0.303 1 nan 0 nan 0 1.00692e-09 erg/s/cm2/AA 0 2.39435e-20 erg/s/cm2/Hz 0 GCPD + ARGUE.R9 8459.11 CCD 0.321 1 nan 0 nan 0 1.00354e-09 erg/s/cm2/AA 0 2.37691e-20 erg/s/cm2/Hz 0 GCPD + BALLOON.UV 2000.00 CCD nan 0 nan 0 nan 0 nan erg/s/cm2/AA 0 nan erg/s/cm2/Hz 0 nan + BESSEL.H 16441.7 CCD nan 0 nan 0 nan 0 1.15039e-10 erg/s/cm2/AA 0 1.03731e-20 erg/s/cm2/Hz 0 nan + BESSEL.J 12347.7 CCD nan 0 nan 0 nan 0 3.14613e-10 erg/s/cm2/AA 0 1.59997e-20 erg/s/cm2/Hz 0 nan + BESSEL.K 22090.6 CCD nan 0 nan 0 nan 0 3.97709e-11 erg/s/cm2/AA 0 6.47594e-21 erg/s/cm2/Hz 0 nan + BESSEL.L 34799.6 CCD nan 0 nan 0 nan 0 7.17418e-12 erg/s/cm2/AA 0 2.89794e-21 erg/s/cm2/Hz 0 nan + BESSEL.LPRIME 38275.4 CCD nan 0 nan 0 nan 0 4.95932e-12 erg/s/cm2/AA 0 2.42343e-21 erg/s/cm2/Hz 0 nan + BESSEL.M 47317.2 CCD nan 0 nan 0 nan 0 2.1794e-12 erg/s/cm2/AA 0 1.6276e-21 erg/s/cm2/Hz 0 nan + BESSELL.B 4386.01 CCD -0.104 1 nan 0 nan 0 6.28778e-09 erg/s/cm2/AA 0 4.01367e-20 erg/s/cm2/Hz 0 Bessel1990 + BESSELL.BW 4392.42 CCD nan 0 nan 0 nan 0 6.24145e-09 erg/s/cm2/AA 0 4.02194e-20 erg/s/cm2/Hz 0 nan + BESSELL.I 8035.48 CCD 0.443 1 nan 0 nan 0 1.13147e-09 erg/s/cm2/AA 0 2.4206e-20 erg/s/cm2/Hz 0 Bessel1990 + BESSELL.R 6526.85 CCD 0.193 1 nan 0 nan 0 2.1693e-09 erg/s/cm2/AA 0 3.02812e-20 erg/s/cm2/Hz 0 Bessel1990 + BESSELL.U 3592.66 CCD 0.79 1 nan 0 nan 0 4.00565e-09 erg/s/cm2/AA 0 1.74442e-20 erg/s/cm2/Hz 0 Bessel1990 + BESSELL.V 5490.66 CCD 0.008 1 nan 0 nan 0 3.60833e-09 erg/s/cm2/AA 0 3.65041e-20 erg/s/cm2/Hz 0 Bessel1990 + BRITE.B 4246.37 CCD nan 0 nan 0 nan 0 6.88517e-09 erg/s/cm2/AA 0 4.13434e-20 erg/s/cm2/Hz 0 nan + BRITE.R 6236.81 CCD nan 0 nan 0 nan 0 2.43832e-09 erg/s/cm2/AA 0 3.14827e-20 erg/s/cm2/Hz 0 nan + CAMELOT-BR.GAMMA 4355.23 CCD nan 0 nan 0 nan 0 6.24148e-09 erg/s/cm2/AA 0 3.98091e-20 erg/s/cm2/Hz 0 nan + CAMELOT-JOHNSON.I 8706.7 CCD nan 0 nan 0 nan 0 9.24657e-10 erg/s/cm2/AA 0 2.33794e-20 erg/s/cm2/Hz 0 nan + CAMELOT-JOHNSON.R 6401.3 CCD nan 0 nan 0 nan 0 2.26677e-09 erg/s/cm2/AA 0 3.10012e-20 erg/s/cm2/Hz 0 nan + CAMELOT-JOHNSON.U 3656.13 CCD nan 0 nan 0 nan 0 4.24922e-09 erg/s/cm2/AA 0 1.97848e-20 erg/s/cm2/Hz 0 nan + CAMELOT-JOHNSON.V 5408.35 CCD nan 0 nan 0 nan 0 3.75051e-09 erg/s/cm2/AA 0 3.73656e-20 erg/s/cm2/Hz 0 nan + CAMELOT-SDSS.G 4758.55 CCD nan 0 nan 0 nan 0 5.25556e-09 erg/s/cm2/AA 0 3.95548e-20 erg/s/cm2/Hz 0 nan + CAMELOT-SDSS.I 7700.34 CCD nan 0 nan 0 nan 0 1.28391e-09 erg/s/cm2/AA 0 2.53939e-20 erg/s/cm2/Hz 0 nan + CAMELOT-SDSS.R 6216.92 CCD nan 0 nan 0 nan 0 2.46367e-09 erg/s/cm2/AA 0 3.23409e-20 erg/s/cm2/Hz 0 nan + CAMELOT-SDSS.U 3495.6 CCD nan 0 nan 0 nan 0 3.48873e-09 erg/s/cm2/AA 0 1.45908e-20 erg/s/cm2/Hz 0 nan + CAMELOT-SDSS.Z 9637.14 CCD nan 0 nan 0 nan 0 6.98857e-10 erg/s/cm2/AA 0 2.16414e-20 erg/s/cm2/Hz 0 nan + CAMELOT-STROMGREN.B 4654.33 CCD nan 0 nan 0 nan 0 5.829e-09 erg/s/cm2/AA 0 4.21096e-20 erg/s/cm2/Hz 0 nan + CAMELOT-STROMGREN.U 3469.19 CCD nan 0 nan 0 nan 0 3.15342e-09 erg/s/cm2/AA 0 1.26812e-20 erg/s/cm2/Hz 0 nan + CAMELOT-STROMGREN.V 4119.79 CCD nan 0 nan 0 nan 0 7.42511e-09 erg/s/cm2/AA 0 4.20674e-20 erg/s/cm2/Hz 0 nan + CAMELOT-STROMGREN.Y 5449.51 CCD nan 0 nan 0 nan 0 3.66098e-09 erg/s/cm2/AA 0 3.63177e-20 erg/s/cm2/Hz 0 nan + CAMELOT.IZ1 8782.93 CCD nan 0 nan 0 nan 0 9.02218e-10 erg/s/cm2/AA 0 2.32128e-20 erg/s/cm2/Hz 0 nan + CAMELOT.IZ2 8772.48 CCD nan 0 nan 0 nan 0 9.05126e-10 erg/s/cm2/AA 0 2.32321e-20 erg/s/cm2/Hz 0 nan + COROT.EXO 6651.03 CCD nan 0 nan 0 nan 0 2.23245e-09 erg/s/cm2/AA 0 3.3517e-20 erg/s/cm2/Hz 0 nan + COROT.SIS 6578.73 CCD nan 0 nan 0 nan 0 2.30564e-09 erg/s/cm2/AA 0 3.38325e-20 erg/s/cm2/Hz 0 nan + COUSINS.I 7884.05 CCD 0.017 1 nan 0 nan 0 1.19379e-09 erg/s/cm2/AA 0 2.46938e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + COUSINS.R 6499.87 CCD 0.03 1 nan 0 nan 0 2.19313e-09 erg/s/cm2/AA 0 3.06728e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + COUSINS.V 5504.67 CCD 0.026 1 nan 0 nan 0 3.58315e-09 erg/s/cm2/AA 0 3.69669e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + DDO.35 3458.23 CCD nan 0 nan 0 nan 0 3.17443e-09 erg/s/cm2/AA 0 1.27115e-20 erg/s/cm2/Hz 0 nan + DDO.38 3820.74 CCD nan 0 nan 0 nan 0 5.37509e-09 erg/s/cm2/AA 0 2.6729e-20 erg/s/cm2/Hz 0 nan + DDO.41 4164.08 CCD nan 0 nan 0 nan 0 7.71911e-09 erg/s/cm2/AA 0 4.46681e-20 erg/s/cm2/Hz 0 nan + DDO.42 4255.76 CCD nan 0 nan 0 nan 0 7.42918e-09 erg/s/cm2/AA 0 4.48765e-20 erg/s/cm2/Hz 0 nan + DDO.45 4520.55 CCD nan 0 nan 0 nan 0 6.29948e-09 erg/s/cm2/AA 0 4.2938e-20 erg/s/cm2/Hz 0 nan + DDO.48 4883.17 CCD nan 0 nan 0 nan 0 4.60439e-09 erg/s/cm2/AA 0 3.66418e-20 erg/s/cm2/Hz 0 nan + DDO.51 5128.43 CCD nan 0 nan 0 nan 0 4.37691e-09 erg/s/cm2/AA 0 3.83867e-20 erg/s/cm2/Hz 0 nan + DENIS.I 7825.32 CCD 0.04 0 nan 0 nan 0 1.2e-08 W/m2/mum 1 2499 Jy 1 Fouque_etal.2000 + DENIS.J 12374 CCD 0.032 0 nan 0 nan 0 3.17e-09 W/m2/mum 1 1595 Jy 1 Fouque_etal.2000 + DENIS.KS 21415 CCD 0.041 0 nan 0 nan 0 4.34e-10 W/m2/mum 1 665 Jy 1 Fouque_etal.2000 + DIRBE.F100 977021 BOL nan 0 nan 0 nan 0 1.43337e-17 erg/s/cm2/AA 0 4.48346e-24 erg/s/cm2/Hz 0 nan + DIRBE.F12 122948 BOL nan 0 nan 0 nan 0 6.51649e-14 erg/s/cm2/AA 0 3.1533e-22 erg/s/cm2/Hz 0 nan + DIRBE.F140 1.47889e+06 BOL nan 0 nan 0 nan 0 2.72082e-18 erg/s/cm2/AA 0 1.94504e-24 erg/s/cm2/Hz 0 nan + DIRBE.F1_25 12666.1 BOL nan 0 nan 0 nan 0 2.93465e-10 erg/s/cm2/AA 0 1.56131e-20 erg/s/cm2/Hz 0 nan + DIRBE.F240 2.47856e+06 BOL nan 0 nan 0 nan 0 3.86145e-19 erg/s/cm2/AA 0 7.24944e-25 erg/s/cm2/Hz 0 nan + DIRBE.F25 207909 BOL nan 0 nan 0 nan 0 7.10896e-15 erg/s/cm2/AA 0 1.00527e-22 erg/s/cm2/Hz 0 nan + DIRBE.F2_2 22201.2 BOL nan 0 nan 0 nan 0 3.92138e-11 erg/s/cm2/AA 0 6.43052e-21 erg/s/cm2/Hz 0 nan + DIRBE.F3_5 35338 BOL nan 0 nan 0 nan 0 6.9399e-12 erg/s/cm2/AA 0 2.8721e-21 erg/s/cm2/Hz 0 nan + DIRBE.F4_9 48826.3 BOL nan 0 nan 0 nan 0 1.94259e-12 erg/s/cm2/AA 0 1.54205e-21 erg/s/cm2/Hz 0 nan + DIRBE.F60 560906 BOL nan 0 nan 0 nan 0 1.41579e-16 erg/s/cm2/AA 0 1.44511e-23 erg/s/cm2/Hz 0 nan + EEV4280.CCD 6003.93 CCD nan 0 nan 0 nan 0 2.60702e-09 erg/s/cm2/AA 0 3.19862e-20 erg/s/cm2/Hz 0 nan + ESOIR.BRG 21610 CCD 0 1 nan 0 nan 0 4.38e-13 W/m2/nm 1 683 Jy 1 van_Der_Bliek_1996 + ESOIR.CO 22910 CCD 0 1 nan 0 nan 0 3.54e-13 W/m2/nm 1 619 Jy 1 van_Der_Bliek_1996 + ESOIR.H0 15398.2 CCD 0 1 nan 0 nan 0 1.37e-12 W/m2/nm 1 1140 Jy 1 van_Der_Bliek_1996 + ESOIR.K0 22210 CCD 0 1 nan 0 nan 0 3.97e-13 W/m2/nm 1 653 Jy 1 van_Der_Bliek_1996 + ESOIR.L0 37060 CCD 0 1 nan 0 nan 0 5.78e-14 W/m2/nm 1 265 Jy 1 van_Der_Bliek_1996 + ESOIR.N 114659 CCD 0 1 nan 0 nan 0 1.29e-15 W/m2/nm 1 41.7 Jy 1 van_Der_Bliek_1996 + ESOIR.N 114659 CCD 0 1 nan 0 nan 0 1.09e-16 W/m2/nm 1 12.3 Jy 1 van_Der_Bliek_1996 + ESOIR.N1 83610 CCD 0 1 nan 0 nan 0 2.5e-15 W/m2/nm 1 57.8 Jy 1 van_Der_Bliek_1996 + ESOIR.N2 97870 CCD 0 1 nan 0 nan 0 1.4e-15 W/m2/nm 1 43.5 Jy 1 van_Der_Bliek_1996 + ESOIR.N3 128190 CCD 0 1 nan 0 nan 0 4.64e-16 W/m2/nm 1 25.3 Jy 1 van_Der_Bliek_1996 + ESOIR.Q0 183618 CCD nan 0 nan 0 nan 0 1.20428e-14 erg/s/cm2/AA 0 1.35437e-22 erg/s/cm2/Hz 0 nan + GAIA.BP 5437.48 CCD 0.03 1 nan 0 nan 0 3.66471e-09 erg/s/cm2/AA 0 3.59649e-20 erg/s/cm2/Hz 0 nan + GAIA.G 6710.93 CCD 0.03 1 nan 0 nan 0 2.18634e-09 erg/s/cm2/AA 0 3.19934e-20 erg/s/cm2/Hz 0 nan + GAIA.RP 7979.59 CCD 0.03 1 nan 0 nan 0 1.19528e-09 erg/s/cm2/AA 0 2.50633e-20 erg/s/cm2/Hz 0 nan + GAIA.RVS 8597.12 CCD 0.03 1 nan 0 nan 0 8.99474e-10 erg/s/cm2/AA 0 2.21745e-20 erg/s/cm2/Hz 0 nan + GALEX.FUV 1538.62 CCD nan 0 0.029 1 nan 0 4.72496e-08 erg/s/cm2/AA 1 3.71398e-20 erg/s/cm2/Hz 0 http://galexgi.gsfc.nasa.gov/docs/galex/FAQ/counts_background.html + GALEX.NUV 2315.66 CCD nan 0 0.095 1 nan 0 2.21466e-08 erg/s/cm2/AA 1 3.91055e-20 erg/s/cm2/Hz 0 http://galexgi.gsfc.nasa.gov/docs/galex/FAQ/counts_background.html + GENEVA.B 4200.85 CCD -0.898 1 nan 0 nan 0 6.51814e-09 erg/s/cm2/AA 0 3.88163e-20 erg/s/cm2/Hz 0 Rufener1988 + GENEVA.B1 4003.78 CCD 0.002 1 nan 0 nan 0 6.58414e-09 erg/s/cm2/AA 0 3.55999e-20 erg/s/cm2/Hz 0 Rufener1988 + GENEVA.B2 4477.56 CCD 0.612 1 nan 0 nan 0 6.20794e-09 erg/s/cm2/AA 0 4.15097e-20 erg/s/cm2/Hz 0 Rufener1988 + GENEVA.G 5765.89 CCD 1.27 1 nan 0 nan 0 3.11095e-09 erg/s/cm2/AA 0 3.44e-20 erg/s/cm2/Hz 0 Rufener1988 + GENEVA.U 3421.62 CCD 0.607 1 nan 0 nan 0 3.22328e-09 erg/s/cm2/AA 0 1.26454e-20 erg/s/cm2/Hz 0 Rufener1988 + GENEVA.V 5482.6 CCD 0.061 1 nan 0 nan 0 3.61964e-09 erg/s/cm2/AA 0 3.65226e-20 erg/s/cm2/Hz 0 Rufener1988 + GENEVA.V1 5395.63 CCD 0.764 1 nan 0 nan 0 3.78179e-09 erg/s/cm2/AA 0 3.69382e-20 erg/s/cm2/Hz 0 Rufener1988 + HIPPARCOS.HP 5275.11 CCD 0.026 1 nan 0 nan 0 4.06145e-09 erg/s/cm2/AA 0 3.67714e-20 erg/s/cm2/Hz 0 ASIAGO(same_as_JOHNSON.V) + IPHAS.HA 6568.06 CCD 0 1 nan 0 nan 0 1.81344e-09 erg/s/cm2/AA 0 2.60944e-20 erg/s/cm2/Hz 0 Drew_etal.2005 + IPHAS.IP 7671.27 CCD 0 1 nan 0 nan 0 1.30528e-09 erg/s/cm2/AA 0 2.55384e-20 erg/s/cm2/Hz 0 Drew_etal.2005 + IPHAS.RP 6216.15 CCD 0 1 nan 0 nan 0 2.47671e-09 erg/s/cm2/AA 0 3.23745e-20 erg/s/cm2/Hz 0 Drew_etal.2005 + IRAC.36 35375.4 BOL nan 0 nan 0 nan 0 nan nan 0 277.5 Jy 1 http://web.ipac.caltech.edu/staff/gillian/cal.html + IRAC.45 44750.7 BOL nan 0 nan 0 nan 0 nan nan 0 179.5 Jy 1 http://web.ipac.caltech.edu/staff/gillian/cal.html + IRAC.58 57019.3 BOL nan 0 nan 0 nan 0 nan nan 0 116.6 Jy 1 http://web.ipac.caltech.edu/staff/gillian/cal.html + IRAC.80 77843.6 BOL nan 0 nan 0 nan 0 nan nan 0 63.1 Jy 1 http://web.ipac.caltech.edu/staff/gillian/cal.html + IRAS.F100 1.01948e+06 BOL nan 0 nan 0 nan 0 1.27666e-17 erg/s/cm2/AA 0 4.21191e-24 erg/s/cm2/Hz 0 nan + IRAS.F12 115980 BOL nan 0 nan 0 nan 0 8.99122e-14 erg/s/cm2/AA 0 3.64825e-22 erg/s/cm2/Hz 0 nan + IRAS.F25 238775 BOL nan 0 nan 0 nan 0 4.60991e-15 erg/s/cm2/AA 0 8.18366e-23 erg/s/cm2/Hz 0 nan + IRAS.F60 614850 BOL nan 0 nan 0 nan 0 1.23233e-16 erg/s/cm2/AA 0 1.3897e-23 erg/s/cm2/Hz 0 nan + ISOCAM.LW1 44997.9 BOL nan 0 nan 0 nan 0 2.7547e-12 erg/s/cm2/AA 0 1.83505e-21 erg/s/cm2/Hz 0 nan + ISOCAM.LW10 116979 BOL nan 0 nan 0 nan 0 8.2868e-14 erg/s/cm2/AA 0 3.47187e-22 erg/s/cm2/Hz 0 nan + ISOCAM.LW2 68135.1 BOL nan 0 nan 0 nan 0 6.55288e-13 erg/s/cm2/AA 0 9.48141e-22 erg/s/cm2/Hz 0 nan + ISOCAM.LW3 144862 BOL nan 0 nan 0 nan 0 2.99587e-14 erg/s/cm2/AA 0 2.02519e-22 erg/s/cm2/Hz 0 nan + ISOCAM.LW4 60078.5 BOL nan 0 nan 0 nan 0 8.90277e-13 erg/s/cm2/AA 0 1.06024e-21 erg/s/cm2/Hz 0 nan + ISOCAM.LW5 67877.6 BOL nan 0 nan 0 nan 0 5.37998e-13 erg/s/cm2/AA 0 8.24742e-22 erg/s/cm2/Hz 0 nan + ISOCAM.LW6 77478.4 BOL nan 0 nan 0 nan 0 3.28415e-13 erg/s/cm2/AA 0 6.50625e-22 erg/s/cm2/Hz 0 nan + ISOCAM.LW7 96797.9 BOL nan 0 nan 0 nan 0 1.40631e-13 erg/s/cm2/AA 0 4.31139e-22 erg/s/cm2/Hz 0 nan + ISOCAM.LW8 113338 BOL nan 0 nan 0 nan 0 7.16443e-14 erg/s/cm2/AA 0 3.0585e-22 erg/s/cm2/Hz 0 nan + ISOCAM.LW9 149177 BOL nan 0 nan 0 nan 0 2.41792e-14 erg/s/cm2/AA 0 1.78663e-22 erg/s/cm2/Hz 0 nan + ISOCAM.SW1 35896.7 BOL nan 0 nan 0 nan 0 6.7493e-12 erg/s/cm2/AA 0 2.83774e-21 erg/s/cm2/Hz 0 nan + ISOCAM.SW10 46333.2 BOL nan 0 nan 0 nan 0 2.37437e-12 erg/s/cm2/AA 0 1.69638e-21 erg/s/cm2/Hz 0 nan + ISOCAM.SW11 42232.8 BOL nan 0 nan 0 nan 0 3.38673e-12 erg/s/cm2/AA 0 2.01309e-21 erg/s/cm2/Hz 0 nan + ISOCAM.SW2 33050.7 BOL nan 0 nan 0 nan 0 8.70066e-12 erg/s/cm2/AA 0 3.16453e-21 erg/s/cm2/Hz 0 nan + ISOCAM.SW3 44382.2 BOL nan 0 nan 0 nan 0 2.91552e-12 erg/s/cm2/AA 0 1.88722e-21 erg/s/cm2/Hz 0 nan + ISOCAM.SW4 27828.9 BOL nan 0 nan 0 nan 0 1.70274e-11 erg/s/cm2/AA 0 4.35452e-21 erg/s/cm2/Hz 0 nan + ISOCAM.SW5 41251.5 BOL nan 0 nan 0 nan 0 4.57532e-12 erg/s/cm2/AA 0 2.42243e-21 erg/s/cm2/Hz 0 nan + ISOCAM.SW6 37000.4 BOL nan 0 nan 0 nan 0 5.71313e-12 erg/s/cm2/AA 0 2.59513e-21 erg/s/cm2/Hz 0 nan + ISOCAM.SW7 30453.6 BOL nan 0 nan 0 nan 0 1.19394e-11 erg/s/cm2/AA 0 3.68221e-21 erg/s/cm2/Hz 0 nan + ISOCAM.SW8 40528.7 BOL nan 0 nan 0 nan 0 3.93782e-12 erg/s/cm2/AA 0 2.15547e-21 erg/s/cm2/Hz 0 nan + ISOCAM.SW9 38650.8 BOL nan 0 nan 0 nan 0 4.75994e-12 erg/s/cm2/AA 0 2.36961e-21 erg/s/cm2/Hz 0 nan + JOHNSON.110 11078.7 CCD 0.039 1 nan 0 nan 0 4.54025e-10 erg/s/cm2/AA 0 1.85576e-20 erg/s/cm2/Hz 0 nan + JOHNSON.33 3366.72 CCD 0.091 1 nan 0 nan 0 3.2259e-09 erg/s/cm2/AA 0 1.22666e-20 erg/s/cm2/Hz 0 nan + JOHNSON.35 3559.9 CCD 0.074 1 nan 0 nan 0 3.13021e-09 erg/s/cm2/AA 0 1.30965e-20 erg/s/cm2/Hz 0 nan + JOHNSON.37 3750.29 CCD 0.082 1 nan 0 nan 0 4.31727e-09 erg/s/cm2/AA 0 2.0565e-20 erg/s/cm2/Hz 0 nan + JOHNSON.40 4058.45 CCD 0.052 1 nan 0 nan 0 7.05664e-09 erg/s/cm2/AA 0 3.85773e-20 erg/s/cm2/Hz 0 nan + JOHNSON.45 4559.97 CCD 0.04 1 nan 0 nan 0 5.95346e-09 erg/s/cm2/AA 0 4.19593e-20 erg/s/cm2/Hz 0 nan + JOHNSON.52 5186.4 CCD 0.039 1 nan 0 nan 0 4.2249e-09 erg/s/cm2/AA 0 3.79162e-20 erg/s/cm2/Hz 0 nan + JOHNSON.58 5832.3 CCD 0.038 1 nan 0 nan 0 3.02678e-09 erg/s/cm2/AA 0 3.42362e-20 erg/s/cm2/Hz 0 nan + JOHNSON.58P 5854.78 CCD nan 0 nan 0 nan 0 2.96211e-09 erg/s/cm2/AA 0 3.39375e-20 erg/s/cm2/Hz 0 nan + JOHNSON.63 6259.5 CCD 0.05 1 nan 0 nan 0 2.27773e-09 erg/s/cm2/AA 0 3.07584e-20 erg/s/cm2/Hz 0 nan + JOHNSON.72 7239.35 CCD 0.031 1 nan 0 nan 0 1.5524e-09 erg/s/cm2/AA 0 2.71174e-20 erg/s/cm2/Hz 0 nan + JOHNSON.80 8002.1 CCD 0.029 1 nan 0 nan 0 1.13226e-09 erg/s/cm2/AA 0 2.41758e-20 erg/s/cm2/Hz 0 nan + JOHNSON.86 8584.69 CCD 0.043 1 nan 0 nan 0 9.12766e-10 erg/s/cm2/AA 0 2.24296e-20 erg/s/cm2/Hz 0 nan + JOHNSON.99 9831.12 CCD 0.028 1 nan 0 nan 0 6.72283e-10 erg/s/cm2/AA 0 2.16627e-20 erg/s/cm2/Hz 0 nan + JOHNSON.B 4448.06 CCD 0.034 1 nan 0 nan 0 6.1307e-09 erg/s/cm2/AA 0 4.02221e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + JOHNSON.H 16464.4 CCD 0.02 1 nan 0 nan 0 1.15039e-10 erg/s/cm2/AA 0 1.03731e-20 erg/s/cm2/Hz 0 Morel_etal.1978 + JOHNSON.I 8778.22 CCD 0.1 1 nan 0 nan 0 9.09942e-10 erg/s/cm2/AA 0 2.31787e-20 erg/s/cm2/Hz 0 Morel_etal.1978 + JOHNSON.J 12487.8 CCD 0.02 1 nan 0 nan 0 3.11763e-10 erg/s/cm2/AA 0 1.60682e-20 erg/s/cm2/Hz 0 Morel_etal.1978 + JOHNSON.K 21951.2 CCD 0.02 1 nan 0 nan 0 4.16588e-11 erg/s/cm2/AA 0 6.65137e-21 erg/s/cm2/Hz 0 Morel_etal.1978 + JOHNSON.L 35395.5 CCD -0.02 1 nan 0 nan 0 6.91788e-12 erg/s/cm2/AA 0 2.87024e-21 erg/s/cm2/Hz 0 Morel_etal.1978 + JOHNSON.M 50311.2 CCD -0.04 1 nan 0 nan 0 1.78777e-12 erg/s/cm2/AA 0 1.498e-21 erg/s/cm2/Hz 0 Morel_etal.1978 + JOHNSON.N 103566 CCD -0.01 1 nan 0 nan 0 1.20556e-13 erg/s/cm2/AA 0 4.18344e-22 erg/s/cm2/Hz 0 Morel_etal.1978 + JOHNSON.R 6939.52 CCD 0.07 1 nan 0 nan 0 1.81615e-09 erg/s/cm2/AA 0 2.88912e-20 erg/s/cm2/Hz 0 Morel_etal.1978 + JOHNSON.U 3641.75 CCD 0.055 1 nan 0 nan 0 3.9779e-09 erg/s/cm2/AA 0 1.81564e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + JOHNSON.V 5504.67 CCD 0.026 1 nan 0 nan 0 3.58315e-09 erg/s/cm2/AA 0 3.69669e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + KEPLER.CH0102 6283.57 CCD nan 0 nan 0 nan 0 2.47062e-09 erg/s/cm2/AA 0 3.47861e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH0304 6298.82 CCD nan 0 nan 0 nan 0 2.45196e-09 erg/s/cm2/AA 0 3.46705e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH0506 6328.91 CCD nan 0 nan 0 nan 0 2.41888e-09 erg/s/cm2/AA 0 3.34082e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH0708 6316.8 CCD nan 0 nan 0 nan 0 2.43212e-09 erg/s/cm2/AA 0 3.34656e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH0910 6329.32 CCD nan 0 nan 0 nan 0 2.41813e-09 erg/s/cm2/AA 0 3.33968e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH1112 6295.8 CCD nan 0 nan 0 nan 0 2.45648e-09 erg/s/cm2/AA 0 3.36052e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH1314 6291.06 CCD nan 0 nan 0 nan 0 2.46033e-09 erg/s/cm2/AA 0 3.35855e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH1516 6335.45 CCD nan 0 nan 0 nan 0 2.41011e-09 erg/s/cm2/AA 0 3.33411e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH1718 6369.76 CCD nan 0 nan 0 nan 0 2.38098e-09 erg/s/cm2/AA 0 3.44538e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH1920 6384.32 CCD nan 0 nan 0 nan 0 2.36547e-09 erg/s/cm2/AA 0 3.3213e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH2122 6386.65 CCD nan 0 nan 0 nan 0 2.36344e-09 erg/s/cm2/AA 0 3.32119e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH2324 6380.94 CCD nan 0 nan 0 nan 0 2.36885e-09 erg/s/cm2/AA 0 3.32248e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH2526 6388.26 CCD nan 0 nan 0 nan 0 2.36216e-09 erg/s/cm2/AA 0 3.4394e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH2728 6323.43 CCD nan 0 nan 0 nan 0 2.43306e-09 erg/s/cm2/AA 0 3.35835e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH2930 6270.79 CCD nan 0 nan 0 nan 0 2.48346e-09 erg/s/cm2/AA 0 3.37513e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH3132 6280.35 CCD nan 0 nan 0 nan 0 2.47236e-09 erg/s/cm2/AA 0 3.36935e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH3334 6314.47 CCD nan 0 nan 0 nan 0 2.43484e-09 erg/s/cm2/AA 0 3.34841e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH3536 6317.17 CCD nan 0 nan 0 nan 0 2.4316e-09 erg/s/cm2/AA 0 3.34624e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH3738 6391.24 CCD nan 0 nan 0 nan 0 2.35609e-09 erg/s/cm2/AA 0 3.31346e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH3940 6366.72 CCD nan 0 nan 0 nan 0 2.38379e-09 erg/s/cm2/AA 0 3.32857e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH4142 6323.59 CCD nan 0 nan 0 nan 0 2.42526e-09 erg/s/cm2/AA 0 3.34533e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH4344 6326 CCD nan 0 nan 0 nan 0 2.42288e-09 erg/s/cm2/AA 0 3.34475e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH4546 6315.58 CCD nan 0 nan 0 nan 0 2.43328e-09 erg/s/cm2/AA 0 3.34663e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH4748 6321.57 CCD nan 0 nan 0 nan 0 2.42697e-09 erg/s/cm2/AA 0 3.34491e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH4950 6310.74 CCD nan 0 nan 0 nan 0 2.43909e-09 erg/s/cm2/AA 0 3.35002e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH5152 6311.78 CCD nan 0 nan 0 nan 0 2.43819e-09 erg/s/cm2/AA 0 3.35049e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH5354 6306.19 CCD nan 0 nan 0 nan 0 2.44324e-09 erg/s/cm2/AA 0 3.35577e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH5556 6288.29 CCD nan 0 nan 0 nan 0 2.46355e-09 erg/s/cm2/AA 0 3.36734e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH5758 6387.78 CCD nan 0 nan 0 nan 0 2.36212e-09 erg/s/cm2/AA 0 3.4387e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH5960 6366.56 CCD nan 0 nan 0 nan 0 2.38288e-09 erg/s/cm2/AA 0 3.32709e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH6162 6323.8 CCD nan 0 nan 0 nan 0 2.42423e-09 erg/s/cm2/AA 0 3.34263e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH6364 6350.55 CCD nan 0 nan 0 nan 0 2.39401e-09 erg/s/cm2/AA 0 3.32607e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH6566 6375.57 CCD nan 0 nan 0 nan 0 2.37437e-09 erg/s/cm2/AA 0 3.44178e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH6768 6332.07 CCD nan 0 nan 0 nan 0 2.42263e-09 erg/s/cm2/AA 0 3.35024e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH6970 6366.04 CCD nan 0 nan 0 nan 0 2.3847e-09 erg/s/cm2/AA 0 3.32959e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH7172 6392.78 CCD nan 0 nan 0 nan 0 2.35601e-09 erg/s/cm2/AA 0 3.31551e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH7374 6333.33 CCD nan 0 nan 0 nan 0 2.41344e-09 erg/s/cm2/AA 0 3.33676e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH7576 6331.63 CCD nan 0 nan 0 nan 0 2.41506e-09 erg/s/cm2/AA 0 3.33653e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH7778 6289.47 CCD nan 0 nan 0 nan 0 2.46149e-09 erg/s/cm2/AA 0 3.35735e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH7980 6303.79 CCD nan 0 nan 0 nan 0 2.44699e-09 erg/s/cm2/AA 0 3.35525e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH8182 6320.95 CCD nan 0 nan 0 nan 0 2.42526e-09 erg/s/cm2/AA 0 3.34399e-20 erg/s/cm2/Hz 0 nan + KEPLER.CH8384 6262.23 CCD nan 0 nan 0 nan 0 2.4925e-09 erg/s/cm2/AA 0 3.37698e-20 erg/s/cm2/Hz 0 nan + KEPLER.V 6416.84 CCD nan 0 nan 0 nan 0 2.44701e-09 erg/s/cm2/AA 0 3.32701e-20 erg/s/cm2/Hz 0 nan + KRON.I 8400.98 CCD 0.24 1 nan 0 nan 0 1.00976e-09 erg/s/cm2/AA 0 2.36427e-20 erg/s/cm2/Hz 0 GCPD + KRON.R 6880.7 CCD 0.15 1 nan 0 nan 0 1.87881e-09 erg/s/cm2/AA 0 2.97657e-20 erg/s/cm2/Hz 0 GCPD + LANDOLT.B2 4420.33 CCD 0.036 1 nan 0 nan 0 6.20617e-09 erg/s/cm2/AA 0 4.02531e-20 erg/s/cm2/Hz 0 Synphot + LANDOLT.B3 4389 CCD 0.036 1 nan 0 nan 0 6.27617e-09 erg/s/cm2/AA 0 4.01142e-20 erg/s/cm2/Hz 0 Synphot + LANDOLT.I 7884.05 CCD 0.028 1 nan 0 nan 0 1.19379e-09 erg/s/cm2/AA 0 2.46938e-20 erg/s/cm2/Hz 0 Synphot + LANDOLT.R 6499.87 CCD 0.038 1 nan 0 nan 0 2.19313e-09 erg/s/cm2/AA 0 3.06728e-20 erg/s/cm2/Hz 0 Synphot + LANDOLT.U 3641.56 CCD 0.046 1 nan 0 nan 0 4.16396e-09 erg/s/cm2/AA 0 1.86244e-20 erg/s/cm2/Hz 0 Synphot + LANDOLT.V 5482.65 CCD 0.026 1 nan 0 nan 0 3.6234e-09 erg/s/cm2/AA 0 3.65447e-20 erg/s/cm2/Hz 0 Synphot + MAIA.GA 4819.68 CCD nan 0 nan 0 nan 0 5.09823e-09 erg/s/cm2/AA 0 3.96263e-20 erg/s/cm2/Hz 0 nan + MAIA.GEO 4884.58 CCD nan 0 nan 0 nan 0 4.89807e-09 erg/s/cm2/AA 0 3.95043e-20 erg/s/cm2/Hz 0 nan + MAIA.REO 6292.35 CCD nan 0 nan 0 nan 0 2.35005e-09 erg/s/cm2/AA 0 3.10787e-20 erg/s/cm2/Hz 0 nan + MAIA.UA 3627.44 CCD nan 0 nan 0 nan 0 3.70755e-09 erg/s/cm2/AA 0 1.63906e-20 erg/s/cm2/Hz 0 nan + MAIA.UEO 3719.35 CCD nan 0 nan 0 nan 0 4.75121e-09 erg/s/cm2/AA 0 2.21986e-20 erg/s/cm2/Hz 0 nan + MIPS.160 1.61607e+06 BOL nan 0 nan 0 nan 0 2.06608e-18 erg/s/cm2/AA 0 1.71612e-24 erg/s/cm2/Hz 0 nan + MIPS.24 243693 BOL nan 0 nan 0 nan 0 nan nan 0 7.17 Jy 1 Rieke_etal.2008 + MIPS.70 699911 BOL nan 0 nan 0 nan 0 nan nan 0 0.778 Jy 1 Rieke_etal.2008 + MOST.V 5229.97 CCD nan 0 nan 0 nan 0 3.61431e-09 erg/s/cm2/AA 0 3.72204e-20 erg/s/cm2/Hz 0 nan + MSX.A 87973.8 BOL nan 0 nan 0 nan 0 2.45715e-13 erg/s/cm2/AA 0 5.89434e-22 erg/s/cm2/Hz 0 nan + MSX.B1 42941.6 BOL nan 0 nan 0 nan 0 3.18487e-12 erg/s/cm2/AA 0 1.95838e-21 erg/s/cm2/Hz 0 nan + MSX.B2 43530.1 BOL nan 0 nan 0 nan 0 3.00872e-12 erg/s/cm2/AA 0 1.90051e-21 erg/s/cm2/Hz 0 nan + MSX.C 122001 BOL nan 0 nan 0 nan 0 5.42559e-14 erg/s/cm2/AA 0 2.67234e-22 erg/s/cm2/Hz 0 nan + MSX.D 147307 BOL nan 0 nan 0 nan 0 2.56529e-14 erg/s/cm2/AA 0 1.84361e-22 erg/s/cm2/Hz 0 nan + MSX.E 218026 BOL nan 0 nan 0 nan 0 5.75741e-15 erg/s/cm2/AA 0 8.87805e-23 erg/s/cm2/Hz 0 nan + NARROW.HA 6568.1 CCD nan 0 nan 0 nan 0 1.81345e-09 erg/s/cm2/AA 0 2.60954e-20 erg/s/cm2/Hz 0 nan + NICMOS.F110W 11235 CCD nan 0 nan 0 nan 0 4.41217e-10 erg/s/cm2/AA 0 1.85271e-20 erg/s/cm2/Hz 0 nan + NICMOS.F160W 16030.4 CCD nan 0 nan 0 nan 0 1.26805e-10 erg/s/cm2/AA 0 1.07823e-20 erg/s/cm2/Hz 0 nan + NICMOS.F187W 18705.6 CCD nan 0 nan 0 nan 0 7.27227e-11 erg/s/cm2/AA 0 8.46589e-21 erg/s/cm2/Hz 0 nan + NICMOS.F190N 19003.4 CCD nan 0 nan 0 nan 0 6.93676e-11 erg/s/cm2/AA 0 8.3559e-21 erg/s/cm2/Hz 0 nan + NICMOS.F205W 20636.1 CCD nan 0 nan 0 nan 0 5.18117e-11 erg/s/cm2/AA 0 7.2629e-21 erg/s/cm2/Hz 0 nan + NICMOS.F222M 22175.1 CCD nan 0 nan 0 nan 0 3.87211e-11 erg/s/cm2/AA 0 6.34656e-21 erg/s/cm2/Hz 0 nan + OAO2.133 1268.15 CCD -0 1 nan 0 nan 0 3.15903e-09 erg/s/cm2/AA 0 2.20993e-21 erg/s/cm2/Hz 0 nan + OAO2.143 1361.6 CCD -0.37 1 nan 0 nan 0 5.13734e-09 erg/s/cm2/AA 0 3.77232e-21 erg/s/cm2/Hz 0 nan + OAO2.155 1543.32 CCD -0.67 1 nan 0 nan 0 6.487e-09 erg/s/cm2/AA 0 5.3105e-21 erg/s/cm2/Hz 0 nan + OAO2.168 1672.04 CCD nan 0 nan 0 nan 0 6.28383e-09 erg/s/cm2/AA 0 5.85365e-21 erg/s/cm2/Hz 0 nan + OAO2.191 1879.85 CCD -0.43 1 nan 0 nan 0 5.75468e-09 erg/s/cm2/AA 0 6.93408e-21 erg/s/cm2/Hz 0 nan + OAO2.203 2030.46 CCD nan 0 nan 0 nan 0 5.31887e-09 erg/s/cm2/AA 0 7.35629e-21 erg/s/cm2/Hz 0 nan + OAO2.238 2378.09 CCD nan 0 nan 0 nan 0 4.154e-09 erg/s/cm2/AA 0 7.8582e-21 erg/s/cm2/Hz 0 nan + OAO2.246 2442.59 CCD -0.08 1 nan 0 nan 0 4.02305e-09 erg/s/cm2/AA 0 8.1017e-21 erg/s/cm2/Hz 0 nan + OAO2.294 2957.78 CCD nan 0 nan 0 nan 0 3.56132e-09 erg/s/cm2/AA 0 1.00481e-20 erg/s/cm2/Hz 0 nan + OAO2.298 2985.6 CCD nan 0 nan 0 nan 0 3.56115e-09 erg/s/cm2/AA 0 1.02962e-20 erg/s/cm2/Hz 0 nan + OAO2.332 3311 CCD nan 0 nan 0 nan 0 3.34208e-09 erg/s/cm2/AA 0 1.20747e-20 erg/s/cm2/Hz 0 nan + OAO2.425 4244.12 CCD nan 0 nan 0 nan 0 6.48667e-09 erg/s/cm2/AA 0 3.94695e-20 erg/s/cm2/Hz 0 nan + OPEN.BOL 1 BOL nan 0 nan 0 nan 0 3.00272e-05 erg/s/cm2/AA 0 9.02168e-21 erg/s/cm2/Hz 0 Bolometric_open_filters + PACS.B 703898 BOL nan 0 nan 0 nan 0 4.89079e-17 erg/s/cm2/AA 0 8.17069e-24 erg/s/cm2/Hz 0 nan + PACS.G 1.00191e+06 BOL nan 0 nan 0 nan 0 1.18461e-17 erg/s/cm2/AA 0 4.01403e-24 erg/s/cm2/Hz 0 nan + PACS.R 1.60218e+06 BOL nan 0 nan 0 nan 0 1.81605e-18 erg/s/cm2/AA 0 1.58757e-24 erg/s/cm2/Hz 0 nan + PLAVI.NIR 7500 CCD 0 1 nan 0 nan 0 1.40025e-09 erg/s/cm2/AA 0 2.61142e-20 erg/s/cm2/Hz 0 nan + PLAVI.SWIR 13000 CCD 0 1 nan 0 nan 0 2.96782e-10 erg/s/cm2/AA 0 1.61897e-20 erg/s/cm2/Hz 0 nan + PLAVI.VIS 5250 CCD 0 1 nan 0 nan 0 4.17261e-09 erg/s/cm2/AA 0 3.78382e-20 erg/s/cm2/Hz 0 nan + SAAO.35 3529.31 CCD nan 0 nan 0 nan 0 3.13125e-09 erg/s/cm2/AA 0 1.30448e-20 erg/s/cm2/Hz 0 nan + SAAO.38 3815 CCD nan 0 nan 0 nan 0 5.29817e-09 erg/s/cm2/AA 0 2.62114e-20 erg/s/cm2/Hz 0 nan + SAAO.41 4164.2 CCD nan 0 nan 0 nan 0 7.6961e-09 erg/s/cm2/AA 0 4.45415e-20 erg/s/cm2/Hz 0 nan + SAAO.42 4257.77 CCD nan 0 nan 0 nan 0 7.39116e-09 erg/s/cm2/AA 0 4.46831e-20 erg/s/cm2/Hz 0 nan + SAAO.45 4520.67 CCD nan 0 nan 0 nan 0 6.30332e-09 erg/s/cm2/AA 0 4.29668e-20 erg/s/cm2/Hz 0 nan + SAAO.48 4879.71 CCD nan 0 nan 0 nan 0 4.59875e-09 erg/s/cm2/AA 0 3.65406e-20 erg/s/cm2/Hz 0 nan + SAAO.H 16824.6 CCD 0.054 1 nan 0 nan 0 1.15125e-10 erg/s/cm2/AA 0 1.03864e-20 erg/s/cm2/Hz 0 VerhoelstPHD + SAAO.J 12344.1 CCD 0.054 1 nan 0 nan 0 3.17476e-10 erg/s/cm2/AA 0 1.6101e-20 erg/s/cm2/Hz 0 VerhoelstPHD + SAAO.K 21968.5 CCD 0.014 1 nan 0 nan 0 3.9143e-11 erg/s/cm2/AA 0 6.42907e-21 erg/s/cm2/Hz 0 VerhoelstPHD + SAAO.L 34308.7 CCD 0.032 1 nan 0 nan 0 7.45373e-12 erg/s/cm2/AA 0 2.95196e-21 erg/s/cm2/Hz 0 VerhoelstPHD + SAAO.M 50663.9 CCD nan 0 nan 0 nan 0 1.67784e-12 erg/s/cm2/AA 0 1.43656e-21 erg/s/cm2/Hz 0 nan + SAAO.N 116305 CCD nan 0 nan 0 nan 0 7.19901e-14 erg/s/cm2/AA 0 3.24975e-22 erg/s/cm2/Hz 0 nan + SAAO.Q 219546 CCD nan 0 nan 0 nan 0 5.86648e-15 erg/s/cm2/AA 0 9.44081e-23 erg/s/cm2/Hz 0 nan + SCUBA.350NB 3.45424e+06 BOL nan 0 nan 0 nan 0 1.41979e-19 erg/s/cm2/AA 0 4.20985e-25 erg/s/cm2/Hz 0 nan + SCUBA.450NB 4.45476e+06 BOL nan 0 nan 0 nan 0 1.26491e-19 erg/s/cm2/AA 0 8.34657e-25 erg/s/cm2/Hz 0 nan + SCUBA.450WB 4.56406e+06 BOL nan 0 nan 0 nan 0 1.26491e-19 erg/s/cm2/AA 0 8.80122e-25 erg/s/cm2/Hz 0 nan + SCUBA.750NB 7.457e+06 BOL nan 0 nan 0 nan 0 nan erg/s/cm2/AA 0 nan erg/s/cm2/Hz 0 nan + SCUBA.850NB 8.61883e+06 BOL nan 0 nan 0 nan 0 nan erg/s/cm2/AA 0 nan erg/s/cm2/Hz 0 nan + SCUBA.850WB 8.5886e+06 BOL nan 0 nan 0 nan 0 nan erg/s/cm2/AA 0 nan erg/s/cm2/Hz 0 nan + SDSS.G 4694.35 CCD -0.102146 0 0 1 nan 0 5.46588e-09 erg/s/cm2/AA 0 4.01094e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + SDSS.GP 4758.58 CCD -0.1 1 nan 0 nan 0 5.29168e-09 erg/s/cm2/AA 0 3.952e-20 erg/s/cm2/Hz 0 http://www.ucolick.org/~cnaw/sun.html + SDSS.I 7505.34 CCD 0.356723 0 0 1 nan 0 1.39479e-09 erg/s/cm2/AA 0 2.60652e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + SDSS.IP 7720.93 CCD 0.38 1 nan 0 nan 0 1.28012e-09 erg/s/cm2/AA 0 2.53648e-20 erg/s/cm2/Hz 0 http://www.ucolick.org/~cnaw/sun.html + SDSS.R 6177.92 CCD 0.140706 0 0 1 nan 0 2.51312e-09 erg/s/cm2/AA 0 3.17677e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + SDSS.RP 6260.48 CCD 0.15 1 nan 0 nan 0 2.42517e-09 erg/s/cm2/AA 0 3.15748e-20 erg/s/cm2/Hz 0 http://www.ucolick.org/~cnaw/sun.html + SDSS.U 3522.16 CCD 1.017 0 0.04 1 nan 0 3.58004e-09 erg/s/cm2/AA 0 1.49043e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + SDSS.UP 3551.91 CCD 0.98 1 nan 0 nan 0 3.43499e-09 erg/s/cm2/AA 0 1.44086e-20 erg/s/cm2/Hz 0 http://www.ucolick.org/~cnaw/sun.html + SDSS.Z 8936.17 CCD 0.516673 0 0 1 nan 0 8.40621e-10 erg/s/cm2/AA 0 2.2582e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + SDSS.ZP 10689.4 CCD 0.53 1 nan 0 nan 0 5.27087e-10 erg/s/cm2/AA 0 1.97625e-20 erg/s/cm2/Hz 0 http://www.ucolick.org/~cnaw/sun.html + SLOAN.I 7732.95 CCD nan 0 nan 0 nan 0 1.26651e-09 erg/s/cm2/AA 0 2.52626e-20 erg/s/cm2/Hz 0 nan + SLOAN.R 6218.06 CCD nan 0 nan 0 nan 0 2.45948e-09 erg/s/cm2/AA 0 3.23041e-20 erg/s/cm2/Hz 0 nan + SPIRE.250 2.47208e+06 BOL nan 0 nan 0 nan 0 3.08997e-19 erg/s/cm2/AA 0 6.35209e-25 erg/s/cm2/Hz 0 nan + SPIRE.350 3.46967e+06 BOL nan 0 nan 0 nan 0 1.48846e-19 erg/s/cm2/AA 0 4.31535e-25 erg/s/cm2/Hz 0 nan + SPIRE.500 4.96743e+06 BOL nan 0 nan 0 nan 0 5.0712e-19 erg/s/cm2/AA 0 8.90025e-25 erg/s/cm2/Hz 0 nan + STEBBINS.B 4876.97 CCD nan 0 nan 0 nan 0 4.96752e-09 erg/s/cm2/AA 0 3.93815e-20 erg/s/cm2/Hz 0 nan + STEBBINS.G 5680.69 CCD nan 0 nan 0 nan 0 3.25733e-09 erg/s/cm2/AA 0 3.61377e-20 erg/s/cm2/Hz 0 nan + STEBBINS.I 10233.8 CCD nan 0 nan 0 nan 0 5.93039e-10 erg/s/cm2/AA 0 2.07171e-20 erg/s/cm2/Hz 0 nan + STEBBINS.R 7107.47 CCD nan 0 nan 0 nan 0 1.64931e-09 erg/s/cm2/AA 0 2.77906e-20 erg/s/cm2/Hz 0 nan + STEBBINS.U 3516.34 CCD nan 0 nan 0 nan 0 3.6432e-09 erg/s/cm2/AA 0 1.55943e-20 erg/s/cm2/Hz 0 nan + STEBBINS.V 4213.07 CCD nan 0 nan 0 nan 0 6.48456e-09 erg/s/cm2/AA 0 3.89575e-20 erg/s/cm2/Hz 0 nan + STISCCD.50CCD 5751.92 CCD nan 0 nan 0 nan 0 2.63632e-09 erg/s/cm2/AA 0 2.67482e-20 erg/s/cm2/Hz 0 nan + STISCCD.F28X50LP 7218.91 CCD nan 0 nan 0 nan 0 1.63377e-09 erg/s/cm2/AA 0 2.80609e-20 erg/s/cm2/Hz 0 nan + STISFUV.25MAMA 1369.38 CCD nan 0 nan 0 nan 0 4.03606e-09 erg/s/cm2/AA 0 2.6959e-21 erg/s/cm2/Hz 0 nan + STISFUV.F25LYA 1242.79 CCD nan 0 nan 0 nan 0 9.49493e-10 erg/s/cm2/AA 0 5.43434e-22 erg/s/cm2/Hz 0 nan + STISFUV.F25QTZ 1595.28 CCD nan 0 nan 0 nan 0 6.63231e-09 erg/s/cm2/AA 0 5.62896e-21 erg/s/cm2/Hz 0 nan + STISFUV.F25SRF2 1453.12 CCD nan 0 nan 0 nan 0 5.68859e-09 erg/s/cm2/AA 0 4.07304e-21 erg/s/cm2/Hz 0 nan + STISNUV.25MAMA 2265.03 CCD nan 0 nan 0 nan 0 4.35968e-09 erg/s/cm2/AA 0 7.52623e-21 erg/s/cm2/Hz 0 nan + STISNUV.F25CIII 2011.58 CCD nan 0 nan 0 nan 0 5.49871e-09 erg/s/cm2/AA 0 7.39963e-21 erg/s/cm2/Hz 0 nan + STISNUV.F25CN182 2006.84 CCD nan 0 nan 0 nan 0 5.2484e-09 erg/s/cm2/AA 0 7.08957e-21 erg/s/cm2/Hz 0 nan + STISNUV.F25CN270 2723.15 CCD nan 0 nan 0 nan 0 3.74675e-09 erg/s/cm2/AA 0 9.19837e-21 erg/s/cm2/Hz 0 nan + STISNUV.F25MGII 2874.17 CCD nan 0 nan 0 nan 0 3.67148e-09 erg/s/cm2/AA 0 9.66371e-21 erg/s/cm2/Hz 0 nan + STISNUV.F25QTZ 2361.42 CCD nan 0 nan 0 nan 0 4.32611e-09 erg/s/cm2/AA 0 8.02842e-21 erg/s/cm2/Hz 0 nan + STISNUV.F25SRF2 2306.38 CCD nan 0 nan 0 nan 0 4.36202e-09 erg/s/cm2/AA 0 7.75654e-21 erg/s/cm2/Hz 0 nan + STROMGREN.B 4671.2 CCD 0.018 1 nan 0 nan 0 5.73426e-09 erg/s/cm2/AA 0 4.16761e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + STROMGREN.HBN 4876.97 CCD 2.905 1 nan 0 nan 0 4.52476e-09 erg/s/cm2/AA 0 3.59635e-20 erg/s/cm2/Hz 0 GCPDaverage_USE_ONLY_AS_RATIO!!! + STROMGREN.HBW 4857.03 CCD 0 1 nan 0 nan 0 3.60259e-09 erg/s/cm2/AA 0 2.83176e-20 erg/s/cm2/Hz 0 GCPDaverage_USE_ONLY_AS_RATIO!!! + STROMGREN.U 3462.92 CCD 1.432 1 nan 0 nan 0 3.17731e-09 erg/s/cm2/AA 0 1.27398e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + STROMGREN.V 4108.07 CCD 0.179 1 nan 0 nan 0 7.28342e-09 erg/s/cm2/AA 0 4.10546e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + STROMGREN.Y 5477.32 CCD 0.014 1 nan 0 nan 0 3.60969e-09 erg/s/cm2/AA 0 3.62014e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + SUPRIMECAM.B 4360.93 CCD nan 0 nan 0 nan 0 6.16666e-09 erg/s/cm2/AA 0 3.94497e-20 erg/s/cm2/Hz 0 nan + SUPRIMECAM.GP 4687.73 CCD nan 0 nan 0 nan 0 5.44773e-09 erg/s/cm2/AA 0 3.97427e-20 erg/s/cm2/Hz 0 nan + SUPRIMECAM.IC 7961.95 CCD nan 0 nan 0 nan 0 1.15356e-09 erg/s/cm2/AA 0 2.43927e-20 erg/s/cm2/Hz 0 nan + SUPRIMECAM.IP 7662.5 CCD nan 0 nan 0 nan 0 1.30399e-09 erg/s/cm2/AA 0 2.55385e-20 erg/s/cm2/Hz 0 nan + SUPRIMECAM.NB711 7120.08 CCD nan 0 nan 0 nan 0 1.62955e-09 erg/s/cm2/AA 0 2.75561e-20 erg/s/cm2/Hz 0 nan + SUPRIMECAM.NB816 8150.47 CCD nan 0 nan 0 nan 0 1.06791e-09 erg/s/cm2/AA 0 2.36634e-20 erg/s/cm2/Hz 0 nan + SUPRIMECAM.NB921 9183.24 CCD nan 0 nan 0 nan 0 8.09109e-10 erg/s/cm2/AA 0 2.27598e-20 erg/s/cm2/Hz 0 nan + SUPRIMECAM.RC 6499.24 CCD nan 0 nan 0 nan 0 2.1412e-09 erg/s/cm2/AA 0 3.01692e-20 erg/s/cm2/Hz 0 nan + SUPRIMECAM.RP 6239.61 CCD nan 0 nan 0 nan 0 2.43568e-09 erg/s/cm2/AA 0 3.192e-20 erg/s/cm2/Hz 0 nan + SUPRIMECAM.V 5440.31 CCD nan 0 nan 0 nan 0 3.68087e-09 erg/s/cm2/AA 0 3.69748e-20 erg/s/cm2/Hz 0 nan + SUPRIMECAM.ZP 9184.56 CCD nan 0 nan 0 nan 0 8.01828e-10 erg/s/cm2/AA 0 2.2562e-20 erg/s/cm2/Hz 0 nan + TD1.1565 1565 CCD -0.590882 0 nan 0 0.075 1 6.70429e-09 erg/s/cm2/AA 0 5.47936e-21 erg/s/cm2/Hz 0 Thompson1978 + TD1.1965 1965 CCD -0.439466 0 nan 0 0.075 1 5.83158e-09 erg/s/cm2/AA 0 7.51073e-21 erg/s/cm2/Hz 0 Thompson1978 + TD1.2365 2365 CCD -0.0438487 0 nan 0 0.075 1 4.05079e-09 erg/s/cm2/AA 0 7.55709e-21 erg/s/cm2/Hz 0 Thompson1978 + TD1.2740 2740 CCD 0.0711183 0 nan 0 0.075 1 3.64378e-09 erg/s/cm2/AA 0 9.12394e-21 erg/s/cm2/Hz 0 Thompson1978 + TYCHO.BT 4204.4 CCD 0.029 1 nan 0 nan 0 6.55685e-09 erg/s/cm2/AA 0 3.90875e-20 erg/s/cm2/Hz 0 nan + TYCHO.VT 5321.86 CCD 0.03 1 nan 0 nan 0 3.91305e-09 erg/s/cm2/AA 0 3.70697e-20 erg/s/cm2/Hz 0 nan + TYCHO2.BT 4204.4 CCD 0.069 1 nan 0 nan 0 6.55685e-09 erg/s/cm2/AA 0 3.79664e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + TYCHO2.VT 5321.86 CCD 0.032 1 nan 0 nan 0 3.91305e-09 erg/s/cm2/AA 0 3.70691e-20 erg/s/cm2/Hz 0 Maiz-Appellaniz2007 + ULTRACAM.GP 4781.03 CCD nan 0 nan 0 nan 0 5.18851e-09 erg/s/cm2/AA 0 3.94729e-20 erg/s/cm2/Hz 0 nan + ULTRACAM.IP 7653.5 CCD nan 0 nan 0 nan 0 1.30702e-09 erg/s/cm2/AA 0 2.54605e-20 erg/s/cm2/Hz 0 nan + ULTRACAM.RP 6231.71 CCD nan 0 nan 0 nan 0 2.44207e-09 erg/s/cm2/AA 0 3.14866e-20 erg/s/cm2/Hz 0 nan + ULTRACAM.UP 3507.65 CCD nan 0 nan 0 nan 0 3.4292e-09 erg/s/cm2/AA 0 1.40107e-20 erg/s/cm2/Hz 0 nan + ULTRACAM.ZP 10094.7 CCD nan 0 nan 0 nan 0 6.10665e-10 erg/s/cm2/AA 0 2.05429e-20 erg/s/cm2/Hz 0 nan + USNOB1.B1 4448.06 CCD 0.03 1 nan 0 nan 0 6.1307e-09 erg/s/cm2/AA 0 4.02221e-20 erg/s/cm2/Hz 0 Johnson-like + USNOB1.R1 6939.52 CCD 0.07 1 nan 0 nan 0 1.81615e-09 erg/s/cm2/AA 0 2.88912e-20 erg/s/cm2/Hz 0 Johnson-like + UVEX.G 4857.28 CCD 0 1 nan 0 nan 0 5.05341e-09 erg/s/cm2/AA 0 3.93862e-20 erg/s/cm2/Hz 0 Groot_et_al._2009 + UVEX.HA 6568.17 CCD 0 1 nan 0 nan 0 1.81344e-09 erg/s/cm2/AA 0 2.60949e-20 erg/s/cm2/Hz 0 Groot_et_al._2009 + UVEX.I 7746 CCD 0 1 nan 0 nan 0 1.2915e-09 erg/s/cm2/AA 0 2.54412e-20 erg/s/cm2/Hz 0 Groot_et_al._2009 + UVEX.R 6230.09 CCD 0 1 nan 0 nan 0 2.47016e-09 erg/s/cm2/AA 0 3.23487e-20 erg/s/cm2/Hz 0 Groot_et_al._2009 + UVEX.U 3617.28 CCD 0 1 nan 0 nan 0 4.10664e-09 erg/s/cm2/AA 0 1.82805e-20 erg/s/cm2/Hz 0 Groot_et_al._2009 + VILNIUS.P 3737.53 CCD 1.275 1 nan 0 nan 0 4.32219e-09 erg/s/cm2/AA 0 2.0527e-20 erg/s/cm2/Hz 0 GCPD + VILNIUS.S 6527.78 CCD -0.11 1 nan 0 nan 0 2.02595e-09 erg/s/cm2/AA 0 2.87864e-20 erg/s/cm2/Hz 0 GCPD + VILNIUS.U 3444.61 CCD 1.9 1 nan 0 nan 0 3.19379e-09 erg/s/cm2/AA 0 1.26758e-20 erg/s/cm2/Hz 0 GCPD + VILNIUS.V 5440.21 CCD 0.03 1 nan 0 nan 0 3.6821e-09 erg/s/cm2/AA 0 3.65002e-20 erg/s/cm2/Hz 0 GCPD + VILNIUS.X 4050.48 CCD 0.47 1 nan 0 nan 0 7.13368e-09 erg/s/cm2/AA 0 3.917e-20 erg/s/cm2/Hz 0 GCPD + VILNIUS.Y 4661.8 CCD 0.18 1 nan 0 nan 0 5.73013e-09 erg/s/cm2/AA 0 4.14095e-20 erg/s/cm2/Hz 0 GCPD + VILNIUS.Z 5159.82 CCD 0.085 1 nan 0 nan 0 4.29701e-09 erg/s/cm2/AA 0 3.81592e-20 erg/s/cm2/Hz 0 GCPD + VISIR.ARIII 89951 CCD nan 0 nan 0 nan 0 1.7695e-13 erg/s/cm2/AA 0 4.77575e-22 erg/s/cm2/Hz 0 nan + VISIR.NEII 127947 CCD nan 0 nan 0 nan 0 4.38852e-14 erg/s/cm2/AA 0 2.39638e-22 erg/s/cm2/Hz 0 nan + VISIR.NEII1 122706 CCD nan 0 nan 0 nan 0 5.16538e-14 erg/s/cm2/AA 0 2.59425e-22 erg/s/cm2/Hz 0 nan + VISIR.NEII2 130459 CCD nan 0 nan 0 nan 0 4.06316e-14 erg/s/cm2/AA 0 2.30669e-22 erg/s/cm2/Hz 0 nan + VISIR.PAH1 85975.1 CCD nan 0 nan 0 nan 0 2.10709e-13 erg/s/cm2/AA 0 5.19526e-22 erg/s/cm2/Hz 0 nan + VISIR.PAH2 112676 CCD nan 0 nan 0 nan 0 7.2493e-14 erg/s/cm2/AA 0 3.06998e-22 erg/s/cm2/Hz 0 nan + VISIR.PAH22 117387 CCD nan 0 nan 0 nan 0 6.17297e-14 erg/s/cm2/AA 0 2.83735e-22 erg/s/cm2/Hz 0 nan + VISIR.Q1 177308 CCD nan 0 nan 0 nan 0 1.20084e-14 erg/s/cm2/AA 0 1.25927e-22 erg/s/cm2/Hz 0 nan + VISIR.Q2 187780 CCD nan 0 nan 0 nan 0 9.5266e-15 erg/s/cm2/AA 0 1.12011e-22 erg/s/cm2/Hz 0 svo.cab.inta-csic.es/theory/fps + VISIR.Q3 195196 CCD nan 0 nan 0 nan 0 8.18106e-15 erg/s/cm2/AA 0 1.03976e-22 erg/s/cm2/Hz 0 nan + VISIR.SIC 117818 CCD nan 0 nan 0 nan 0 6.19168e-14 erg/s/cm2/AA 0 2.85719e-22 erg/s/cm2/Hz 0 svo.cab.inta-csic.es/theory/fps + VISIR.SIV 104565 CCD nan 0 nan 0 nan 0 9.6862e-14 erg/s/cm2/AA 0 3.53257e-22 erg/s/cm2/Hz 0 svo.cab.inta-csic.es/theory/fps + VISIR.SIV1 98633.3 CCD nan 0 nan 0 nan 0 1.22904e-13 erg/s/cm2/AA 0 3.98835e-22 erg/s/cm2/Hz 0 nan + WALRAVEN.B 4301.33 CCD nan 0 nan 0 nan 0 1.23e-11 erg/s/cm2/AA 1 7.66328e-23 erg/s/cm2/Hz 0 GCPD + WALRAVEN.L 3836.11 CCD nan 0 nan 0 nan 0 1.52e-11 erg/s/cm2/AA 1 7.46436e-23 erg/s/cm2/Hz 0 GCPD + WALRAVEN.U 3633.35 CCD nan 0 nan 0 nan 0 1.61e-11 erg/s/cm2/AA 1 7.08527e-23 erg/s/cm2/Hz 0 GCPD + WALRAVEN.V 5450.85 CCD nan 0 nan 0 nan 0 6.73e-12 erg/s/cm2/AA 1 6.71388e-23 erg/s/cm2/Hz 0 GCPD + WALRAVEN.W 3254.61 CCD nan 0 nan 0 nan 0 2.12e-11 erg/s/cm2/AA 1 7.48843e-23 erg/s/cm2/Hz 0 GCPD + WFCAM.H 16313 CCD 0 1 nan 0 nan 0 1.14e-09 W/m2/mum 1 1019 Jy 1 Hewett2011 + WFCAM.J 12483 CCD 0 1 nan 0 nan 0 2.94e-09 W/m2/mum 1 1530 Jy 1 Hewett2011 + WFCAM.K 22010 CCD 0 1 nan 0 nan 0 3.89e-10 W/m2/mum 1 631 Jy 1 Hewett2011 + WFCAM.Y 10305 CCD 0 1 nan 0 nan 0 5.71e-09 W/m2/mum 1 2026 Jy 1 Hewett2011 + WFCAM.Z 8817 CCD 0 1 nan 0 nan 0 8.59e-09 W/m2/mum 1 2232 Jy 1 Hewett2011 + WFPC2.F170W 1831.19 CCD nan 0 nan 0 nan 0 5.69812e-09 erg/s/cm2/AA 0 6.32238e-21 erg/s/cm2/Hz 0 nan + WFPC2.F255W 2600.08 CCD nan 0 nan 0 nan 0 3.80452e-09 erg/s/cm2/AA 0 8.57101e-21 erg/s/cm2/Hz 0 nan + WFPC2.F300W 2992.8 CCD nan 0 nan 0 nan 0 3.54536e-09 erg/s/cm2/AA 0 1.02039e-20 erg/s/cm2/Hz 0 nan + WFPC2.F336W 3359.5 CCD nan 0 nan 0 nan 0 3.24276e-09 erg/s/cm2/AA 0 1.21943e-20 erg/s/cm2/Hz 0 nan + WFPC2.F380W 3982.84 CCD nan 0 nan 0 nan 0 6.0436e-09 erg/s/cm2/AA 0 3.23925e-20 erg/s/cm2/Hz 0 nan + WFPC2.F439W 4312.1 CCD nan 0 nan 0 nan 0 6.69427e-09 erg/s/cm2/AA 0 4.14592e-20 erg/s/cm2/Hz 0 nan + WFPC2.F450W 4557.37 CCD nan 0 nan 0 nan 0 5.66762e-09 erg/s/cm2/AA 0 3.92657e-20 erg/s/cm2/Hz 0 nan + WFPC2.F467M 4670.26 CCD nan 0 nan 0 nan 0 5.75909e-09 erg/s/cm2/AA 0 4.18888e-20 erg/s/cm2/Hz 0 nan + WFPC2.F502N 5013.16 CCD nan 0 nan 0 nan 0 4.68588e-09 erg/s/cm2/AA 0 3.92794e-20 erg/s/cm2/Hz 0 nan + WFPC2.F547M 5483.89 CCD nan 0 nan 0 nan 0 3.59897e-09 erg/s/cm2/AA 0 3.63599e-20 erg/s/cm2/Hz 0 nan + WFPC2.F555W 5442.93 CCD nan 0 nan 0 nan 0 3.67703e-09 erg/s/cm2/AA 0 3.68718e-20 erg/s/cm2/Hz 0 nan + WFPC2.F569W 5644.4 CCD nan 0 nan 0 nan 0 3.31373e-09 erg/s/cm2/AA 0 3.55815e-20 erg/s/cm2/Hz 0 nan + WFPC2.F606W 6001.29 CCD nan 0 nan 0 nan 0 2.75401e-09 erg/s/cm2/AA 0 3.33546e-20 erg/s/cm2/Hz 0 nan + WFPC2.F656N 6563.65 CCD nan 0 nan 0 nan 0 1.49986e-09 erg/s/cm2/AA 0 2.15521e-20 erg/s/cm2/Hz 0 nan + WFPC2.F673N 6732.25 CCD nan 0 nan 0 nan 0 1.93684e-09 erg/s/cm2/AA 0 2.9281e-20 erg/s/cm2/Hz 0 nan + WFPC2.F675W 6717.73 CCD nan 0 nan 0 nan 0 1.93418e-09 erg/s/cm2/AA 0 2.90289e-20 erg/s/cm2/Hz 0 nan + WFPC2.F702W 6917.13 CCD nan 0 nan 0 nan 0 1.78478e-09 erg/s/cm2/AA 0 2.82719e-20 erg/s/cm2/Hz 0 nan + WFPC2.F785LP 8686.31 CCD nan 0 nan 0 nan 0 9.14666e-10 erg/s/cm2/AA 0 2.30524e-20 erg/s/cm2/Hz 0 nan + WFPC2.F791W 7872.46 CCD nan 0 nan 0 nan 0 1.20429e-09 erg/s/cm2/AA 0 2.47883e-20 erg/s/cm2/Hz 0 nan + WFPC2.F814W 7995.94 CCD nan 0 nan 0 nan 0 1.16139e-09 erg/s/cm2/AA 0 2.47199e-20 erg/s/cm2/Hz 0 nan + WFPC2.F850LP 9114.01 CCD nan 0 nan 0 nan 0 8.13057e-10 erg/s/cm2/AA 0 2.25387e-20 erg/s/cm2/Hz 0 nan WISE.W1 33791.9 BOL 0 1 nan 0 nan 0 8.1787e-15 W/cm2/mum 1 309.54 Jy 1 http://wise2.ipac.caltech.edu/docs/release/prelim/expsup/sec4_3g.html WISE.W2 46293 BOL 0 1 nan 0 nan 0 2.415e-15 W/cm2/mum 1 171.787 Jy 1 http://wise2.ipac.caltech.edu/docs/release/prelim/expsup/sec4_3g.html WISE.W3 123337 BOL 0 1 nan 0 nan 0 6.5151e-17 W/cm2/mum 1 31.674 Jy 1 http://wise2.ipac.caltech.edu/docs/release/prelim/expsup/sec4_3g.html WISE.W4 222532 BOL 0 1 nan 0 nan 0 5.0901e-18 W/cm2/mum 1 8.363 Jy 1 http://wise2.ipac.caltech.edu/docs/release/prelim/expsup/sec4_3g.html - WIYN09HARRIS.B 4292.92 CCD nan 0 nan 0 nan 0 6.1357e-09 erg/s/cm2/AA 0 3.85476e-20 erg/s/cm2/Hz 0 nan - WIYN09HARRIS.I 8314.17 CCD nan 0 nan 0 nan 0 1.03562e-09 erg/s/cm2/AA 0 2.38791e-20 erg/s/cm2/Hz 0 nan - WIYN09HARRIS.R 6575.17 CCD nan 0 nan 0 nan 0 2.09906e-09 erg/s/cm2/AA 0 2.9963e-20 erg/s/cm2/Hz 0 nan - WIYN09HARRIS.U 3659.67 CCD nan 0 nan 0 nan 0 4.03889e-09 erg/s/cm2/AA 0 1.82816e-20 erg/s/cm2/Hz 0 nan - WIYN09HARRIS.V 5438.52 CCD nan 0 nan 0 nan 0 3.68832e-09 erg/s/cm2/AA 0 3.6765e-20 erg/s/cm2/Hz 0 nan - WOOD.A 5897.6 CCD -0.016 1 nan 0 nan 0 2.89427e-09 erg/s/cm2/AA 0 3.35779e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! - WOOD.B 4656.72 CCD -0.168 1 nan 0 nan 0 5.78441e-09 erg/s/cm2/AA 0 4.17871e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! - WOOD.G 5161.44 CCD 0.004 1 nan 0 nan 0 4.27565e-09 erg/s/cm2/AA 0 3.79898e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! - WOOD.H 6574.17 CCD 0.192 1 nan 0 nan 0 1.77394e-09 erg/s/cm2/AA 0 2.55735e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! - WOOD.I 7342.99 CCD 0.202 1 nan 0 nan 0 1.48256e-09 erg/s/cm2/AA 0 2.66639e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! - WOOD.O 6023.32 CCD 0.086 1 nan 0 nan 0 2.72e-09 erg/s/cm2/AA 0 3.29148e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! - WOOD.R 6702.05 CCD 0.167 1 nan 0 nan 0 1.95478e-09 erg/s/cm2/AA 0 2.92865e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! - WOOD.T1 6215.39 CCD -0.048 1 nan 0 nan 0 2.47162e-09 erg/s/cm2/AA 0 3.18478e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! - WOOD.T2 7126 CCD -0.044 1 nan 0 nan 0 1.62584e-09 erg/s/cm2/AA 0 2.75382e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! - WOOD.U 3458.09 CCD 1.104 1 nan 0 nan 0 3.16971e-09 erg/s/cm2/AA 0 1.26677e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! - WOOD.V 4091.29 CCD -0.202 1 nan 0 nan 0 7.29228e-09 erg/s/cm2/AA 0 4.07622e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! - WOOD.Y 5463.97 CCD -0.063 1 nan 0 nan 0 3.63529e-09 erg/s/cm2/AA 0 3.62778e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! + WIYN09HARRIS.B 4292.92 CCD nan 0 nan 0 nan 0 6.1357e-09 erg/s/cm2/AA 0 3.85476e-20 erg/s/cm2/Hz 0 nan + WIYN09HARRIS.I 8314.17 CCD nan 0 nan 0 nan 0 1.03562e-09 erg/s/cm2/AA 0 2.38791e-20 erg/s/cm2/Hz 0 nan + WIYN09HARRIS.R 6575.17 CCD nan 0 nan 0 nan 0 2.09906e-09 erg/s/cm2/AA 0 2.9963e-20 erg/s/cm2/Hz 0 nan + WIYN09HARRIS.U 3659.67 CCD nan 0 nan 0 nan 0 4.03889e-09 erg/s/cm2/AA 0 1.82816e-20 erg/s/cm2/Hz 0 nan + WIYN09HARRIS.V 5438.52 CCD nan 0 nan 0 nan 0 3.68832e-09 erg/s/cm2/AA 0 3.6765e-20 erg/s/cm2/Hz 0 nan + WOOD.A 5897.6 CCD -0.016 1 nan 0 nan 0 2.89427e-09 erg/s/cm2/AA 0 3.35779e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! + WOOD.B 4656.72 CCD -0.168 1 nan 0 nan 0 5.78441e-09 erg/s/cm2/AA 0 4.17871e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! + WOOD.G 5161.44 CCD 0.004 1 nan 0 nan 0 4.27565e-09 erg/s/cm2/AA 0 3.79898e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! + WOOD.H 6574.17 CCD 0.192 1 nan 0 nan 0 1.77394e-09 erg/s/cm2/AA 0 2.55735e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! + WOOD.I 7342.99 CCD 0.202 1 nan 0 nan 0 1.48256e-09 erg/s/cm2/AA 0 2.66639e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! + WOOD.O 6023.32 CCD 0.086 1 nan 0 nan 0 2.72e-09 erg/s/cm2/AA 0 3.29148e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! + WOOD.R 6702.05 CCD 0.167 1 nan 0 nan 0 1.95478e-09 erg/s/cm2/AA 0 2.92865e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! + WOOD.T1 6215.39 CCD -0.048 1 nan 0 nan 0 2.47162e-09 erg/s/cm2/AA 0 3.18478e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! + WOOD.T2 7126 CCD -0.044 1 nan 0 nan 0 1.62584e-09 erg/s/cm2/AA 0 2.75382e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! + WOOD.U 3458.09 CCD 1.104 1 nan 0 nan 0 3.16971e-09 erg/s/cm2/AA 0 1.26677e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! + WOOD.V 4091.29 CCD -0.202 1 nan 0 nan 0 7.29228e-09 erg/s/cm2/AA 0 4.07622e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! + WOOD.Y 5463.97 CCD -0.063 1 nan 0 nan 0 3.63529e-09 erg/s/cm2/AA 0 3.62778e-20 erg/s/cm2/Hz 0 GCPD,use_only_for_colors! diff --git a/sigproc/evaluate.py b/sigproc/evaluate.py index f17e29a23..f999d925f 100644 --- a/sigproc/evaluate.py +++ b/sigproc/evaluate.py @@ -46,7 +46,7 @@ def check(*args,**kwargs): def RSS(data,model): """ Residual sum of squares. - + @param data: data @type data: numpy array @param model: model @@ -60,7 +60,7 @@ def RSS(data,model): def varred(data,model): """ Calculate variance reduction. - + @param data: data @type data: numpy array @param model: model @@ -75,13 +75,13 @@ def varred(data,model): def bic(data,model,k=0,n=None): """ BIC for time regression - + This one is based on the MLE of the variance, - + Sigma_k^2 = RSS/n - + Where our maximum likelihood estimator is then (RSS/n)^n - + @param data: data @type data: numpy array @param model: model @@ -100,17 +100,17 @@ def bic(data,model,k=0,n=None): #-- calculate BIC BIC = n * np.log(RSS_/n) + k * np.log(n) return BIC - + def aic(data,model,k=0,n=None): """ AIC for time regression - + This one is based on the MLE of the variance, - + Sigma_k^2 = RSS/n - + Where our maximum likelihood estimator is then (RSS/n)^n - + @param data: data @type data: numpy array @param model: model @@ -133,24 +133,24 @@ def aic(data,model,k=0,n=None): def fishers_method(pvalues): """ Use Fisher's test to combine probabilities. - + http://en.wikipedia.org/wiki/Fisher's_method - + >>> rvs1 = distributions.norm().rvs(10000) >>> rvs2 = distributions.norm().rvs(10000) >>> pvals1 = scipy.stats.distributions.norm.sf(rvs1) >>> pvals2 = scipy.stats.distributions.norm.sf(rvs2) >>> fpval = fishers_method([pvals1,pvals2]) - + And make a plot (compare with the wiki page). - + >>> p = pl.figure() >>> p = pl.scatter(pvals1,pvals2,c=fpval,edgecolors='none',cmap=pl.cm.spectral) >>> p = pl.colorbar() - + @param pvalues: array of pvalues, the first axis will be combined @type pvalues: ndarray (shape Nprob times n) @return: combined pvalues @@ -160,7 +160,7 @@ def fishers_method(pvalues): X2 = -2*np.sum(np.log(pvalues),axis=0) df = 2*pvalues.shape[0] return distributions.chi2.sf(X2,df) - + #} @@ -170,24 +170,24 @@ def phasediagram(time,signal,nu0,D=0,t0=None,forb=None,asini=None, return_indices=False,chronological=False): """ Construct a phasediagram, using frequency nu0. - + Possibility to include a linear frequency shift D. - + If needed, the indices can be returned. To 'unphase', just do: - + Example usage: - + >>> original = np.random.normal(size=10) >>> indices = np.argsort(original) >>> inv_indices = np.argsort(indices) >>> all(original == np.take(np.take(original,indices),inv_indices)) True - + Optionally, the zero time point can be given, it will be subtracted from all time points - + Joris De Ridder, Pieter Degroote - + @param time: time points [0..Ntime-1] @type time: ndarray @param signal: observed data points [0..Ntime-1] @@ -215,7 +215,7 @@ def phasediagram(time,signal,nu0,D=0,t0=None,forb=None,asini=None, phase = np.fmod(nu0 * (time-t0) + alpha*(sin(2*pi*forb*time) - sin(2*pi*forb*t0)),1.0) #phase = np.fmod(nu0 * (time-t0) - asini/cc/(2*np.pi*forb)*np.cos(2*np.pi*forb*(time-t0)),1.0) phase = np.where(phase<0,phase+1,phase) - + #-- keep track of the phase number, and prepare lists to add the points if chronological: chainnr = np.floor((time-t0)*nu0) @@ -223,7 +223,7 @@ def phasediagram(time,signal,nu0,D=0,t0=None,forb=None,asini=None, mysignl = [[]] currentphase = chainnr[0] #-- divide the signal into separate phase bins - for i in xrange(len(signal)): + for i in range(len(signal)): if chainnr[i]==currentphase: myphase[-1].append(phase[i]) mysignl[-1].append(signal[i]) @@ -235,10 +235,10 @@ def phasediagram(time,signal,nu0,D=0,t0=None,forb=None,asini=None, mysignl.append([signal[i]]) myphase[-1] = np.array(myphase[-1]) mysignl[-1] = np.array(mysignl[-1]) - #-- if we + #-- if we else: indices = np.argsort(phase) - + #-- possibly only return phase and signal if not return_indices and not chronological: return phase[indices], signal[indices] @@ -255,53 +255,53 @@ def phasediagram(time,signal,nu0,D=0,t0=None,forb=None,asini=None, def sine(times, parameters): """ Creates a harmonic function based on given parameters. - + Parameter fields: C{const, ampl, freq, phase}. - + This harmonic function is of the form - + f(p1,...,pm;x1,...,x_n) = S{sum} c_i + a_i sin(2 S{pi} (f_i+ S{phi}_i)) - + where p_i are parameters and x_i are observation times. - + Parameters can be given as a record array containing columns 'ampl', 'freq' and 'phase', and optionally also 'const' (the latter array is summed up so to reduce it to one number). - + C{pars = np.rec.fromarrays([consts,ampls,freqs,phases],names=('const','ampl','freq','phase'))} - + Parameters can also be given as normal 1D numpy array. In that case, it will be transformed into a record array by the C{sine_preppars} function. The array you give must be 1D, optionally contain a constant as the first parameter, followed by 3 three numbers per frequency: - + C{pars = [const, ampl1, freq1, phase1, ampl2, freq2, phase2...]} - + Example usage: We construct a synthetic signal with two frequencies. - + >>> times = np.linspace(0,100,1000) >>> const = 4. >>> ampls = [0.01,0.004] >>> freqs = [0.1,0.234] >>> phases = [0.123,0.234] - + The signal can be made via a record array - + >>> parameters = np.rec.fromarrays([[const,0],ampls,freqs,phases],names = ('const','ampl','freq','phase')) >>> mysine = sine(times,parameters) - + or a normal 1D numpy array - + >>> parameters = np.hstack([const,np.ravel(np.column_stack([ampls,freqs,phases]))]) >>> mysine = sine(times,parameters) - + Of course, the latter is easier if you have your parameters in a list: - + >>> freq1 = [0.01,0.1,0.123] >>> freq2 = [0.004,0.234,0.234] >>> parameters = np.hstack([freq1,freq2]) >>> mysine = sine(times,parameters) - + @param times: observation times @type times: numpy array @param parameters: record array containing amplitudes ('ampl'), frequencies ('freq') @@ -315,7 +315,7 @@ def sine(times, parameters): total_fit = parameters['const'].sum() else: total_fit = 0. - for i in xrange(len(parameters)): + for i in range(len(parameters)): total_fit += parameters['ampl'][i]*sin(2*pi*(parameters['freq'][i]*times+parameters['phase'][i])) return total_fit @@ -327,14 +327,14 @@ def sine(times, parameters): def sine_freqshift(times,parameters,t0=None): """ Creates a sine function with a linear frequency shift. - + Parameter fields: C{const, ampl, freq, phase, D}. - + Similar to C{sine}, but with extra column 'D', which is the linear frequency shift parameter. - + Only accepts record arrays as parameters (for the moment). - + @param times: observation times @type times: numpy array @param parameters: record array containing amplitudes ('ampl'), frequencies ('freq'), @@ -345,33 +345,33 @@ def sine_freqshift(times,parameters,t0=None): """ #-- take epoch into account if t0 is None: t0 = times[0] - + #-- fit the signal signal = np.zeros(len(times)) if 'const' in parameters.dtype.names: signal += np.sum(parameters['const']) - + for par in parameters: frequency = (par['freq'] + par['D']/2.*(times-t0)) * (times-t0) signal += par['ampl'] * sin(2*pi*(frequency + par['phase'])) - + return signal - - + + def sine_orbit(times,parameters,t0=None,nmax=10): """ Creates a sine function with a sinusoidal frequency shift. - + Parameter fields: C{const, ampl, freq, phase, asini, omega}. - + Similar to C{sine}, but with extra column 'asini' and 'forb', which are the orbital parameters. forb in cycles/day or something similar, asini in au. - + For eccentric orbits, add longitude of periastron 'omega' (radians) and 'ecc' (eccentricity). - + Only accepts record arrays as parameters (for the moment). - + @param times: observation times @type times: numpy array @param parameters: record array containing amplitudes ('ampl'), frequencies ('freq'), @@ -382,16 +382,16 @@ def sine_orbit(times,parameters,t0=None,nmax=10): """ #-- take epoch into account if t0 is None: t0 = times[0] - - def ane(n,e): return 2.*np.sqrt(1-e**2)/e/n*jn(n,n*e) + + def ane(n,e): return 2.*np.sqrt(1-e**2)/e/n*jn(n,n*e) def bne(n,e): return 1./n*(jn(n-1,n*e)-jn(n+1,n*e)) - + #-- fit the signal signal = np.zeros(len(times)) names = parameters.dtype.names if 'const' in names: signal += np.sum(parameters['const']) - + cc = 173.144632674 # speed of light in AU/d for par in parameters: alpha = par['freq']*par['asini']/cc @@ -408,7 +408,7 @@ def bne(n,e): return 1./n*(jn(n-1,n*e)-jn(n+1,n*e)) frequency = par['freq']*(times-t0) + \ alpha*(np.sum(np.array([ksins[i]*sin(2*pi*ns[i]*par['forb']*(times-t0)+thns[i]) for i in range(nmax)]),axis=0)+tau) signal += par['ampl'] * sin(2*pi*(frequency + par['phase'])) - + return signal @@ -417,10 +417,10 @@ def bne(n,e): return 1./n*(jn(n-1,n*e)-jn(n+1,n*e)) def periodic_spline(times,parameters,t0=None): """ Evaluate a periodic spline function - + Parameter fields: C{freq, knots, coeffs, degree}, as returned from the corresponding fitting function L{fit.periodic_spline}. - + @param times: observation times @type times: numpy array @param parameters: [frequency,"cj" spline coefficients] @@ -430,7 +430,7 @@ def periodic_spline(times,parameters,t0=None): #-- get time offset if t0 is None: t0 = times[0] - + mytrend = 0. #-- reconstruct entire time series, instead of phased version for i in range(len(parameters)): @@ -439,18 +439,18 @@ def periodic_spline(times,parameters,t0=None): coefficients = (parameters[i]['knots'],parameters[i]['coeffs'],parameters[i]['degree']) phases = np.fmod((times-t0) * frequency,1.0) mytrend += splev(phases,coefficients) - + return mytrend - + @check_input def kepler(times,parameters,itermax=8): """ Construct a radial velocity curve due to Kepler orbit(s). - + Parameter fields: C{gamma, P, T0, e, omega, K} - + @param times: observation times @type times: numpy array @param parameters: record array containing periods ('P' in days), @@ -477,7 +477,7 @@ def kepler(times,parameters,itermax=8): def kepler_diffcorr(times,parameters,itermax=8): """ Compute coefficients for differential correction method. - + See page 97-98 of Hilditch's book "An introduction to close binary stars". """ N = len(times) @@ -514,11 +514,11 @@ def kepler_diffcorr(times,parameters,itermax=8): def binary(times,parameters,n1=None,itermax=8): """ Simultaneous evaluation of two binary components. - + parameter fields must be C{'P','T0','e','omega','K1', 'K2'}. """ if n1 is None: - n1 = len(times)/2 + n1 = len(times)//2 if 'gamma' in parameters.dtype.names: RVfit = parameters['gamma'].sum() else: @@ -529,25 +529,25 @@ def binary(times,parameters,n1=None,itermax=8): RVfit[:n1] += keplerorbit.radial_velocity(p1,times=times[:n1],itermax=itermax) RVfit[n1:] += keplerorbit.radial_velocity(p2,times=times[n1:],itermax=itermax) return RVfit - + @check_input def box(times,parameters,t0=None): """ Evaluate a box transit model. - + Parameter fields: C{cont, freq, ingress, egress, depth} - + Parameters [[frequency,depth, fractional start, fraction end, continuum]] @rtype: array """ if t0 is None: t0 = 0. - + #-- continuum parameters = np.asarray(parameters) model = np.ones(len(times))*sum(parameters['cont']) - + for parameter in parameters: #-- set up the model, define which point is where in a # phasediagram @@ -563,9 +563,9 @@ def spots(times,spots,t0=None,**kwargs): """ Spotted model from Lanza (2003). Extract from Carrington Map. - + Included: - - presence of faculae through Q-factor (set constant to 5 or see + - presence of faculae through Q-factor (set constant to 5 or see Chapman et al. 1997 or Solank & Unruh for a scaling law, since this factor decreases strongly with decreasing rotation period. - differential rotation through B-value (B/period = 0.2 for the sun) @@ -575,11 +575,11 @@ def spots(times,spots,t0=None,**kwargs): (see Hall & Henry (1993) for a relation between stellar radius and sunspot lifetime), through an optional 3rd (time of maximum) and 4th parameter (decay time) - + Not included: - distinction between umbra's and penumbra - sunspot structure - + Parameters of global star: - i_sun: inclination angle, i=pi/2 is edge-on, i=0 is pole on. - l0: 'epoch angle', if L0=0, spot with coordinate (0,0) @@ -588,7 +588,7 @@ def spots(times,spots,t0=None,**kwargs): - period: equatorial period - b: differential rotation b = Omega_pole - Omega_eq where Omega is in radians per time unit (angular velocity) - ap,bp,cp: limb darkening - + Parameters of spots: - lambda: 'greenwich' coordinate (longitude) (pi means push to back of star if l0=0) - theta: latitude (latitude=0 means at equator, latitude=pi/2 means on pole (B{not @@ -596,19 +596,19 @@ def spots(times,spots,t0=None,**kwargs): - A: size - maxt0 (optional): time of maximum size - decay (optional): exponential decay speed - - + + Note: alpha = (Omega_eq - Omega_p) / Omega_eq alpha = -b /2*pi * period - + Use for fitting! """ #-- set zeropoint if t0 is None: t0 = 0. - + #-- simulated light curve template signal = np.zeros(len(times)) - + #-- run through all timepoints this_signal = 0 for spot in spots: @@ -647,22 +647,22 @@ def spots(times,spots,t0=None,**kwargs): def gauss(x, parameters): """ Evaluate a gaussian fit. - + Parameter fields: C{'ampl','mu','sigma'} and optionally C{'const'}. - + Evaluating one Gaussian: - + >>> x = np.linspace(-20,20,10000) >>> pars = [0.5,0.,3.] >>> y = gauss(x,pars) >>> p = pl.plot(x,y,'k-') - + Evaluating multiple Gaussian, with and without constants: - + >>> pars = [0.5,0.,3.,0.1,-10,1.,.1] >>> y = gauss(x,pars) >>> p = pl.plot(x,y,'r-') - + @parameter x: domain @type x: ndarray @parameter parameters: record array. @@ -674,32 +674,32 @@ def gauss(x, parameters): C = parameters['const'].sum() else: C = 0. - + y = C for pars in parameters: y += pars['ampl'] * np.exp( -(x-pars['mu'])**2 / (2.*pars['sigma']**2)) - + return y @check_input def lorentz(x,parameters): """ Evaluate Lorentz profile - + P(f) = A / ( (x-mu)**2 + gamma**2) - + Parameters fields: C{ampl,mu,gamma} and optionally C{const}. - + Evaluating one Lorentzian: - + >>> x = np.linspace(-20,20,10000) >>> pars = [0.5,0.,3.] >>> y = lorentz(x,pars) >>> p = pl.figure() >>> p = pl.plot(x,y,'k-') - + Evaluating multiple Lorentzians, with and without constants: - + >>> pars = [0.5,0.,3.,0.1,-10,1.,.1] >>> y = lorentz(x,pars) >>> p = pl.plot(x,y,'r-') @@ -711,21 +711,21 @@ def lorentz(x,parameters): for par in parameters: y += par['ampl'] / ((x-par['mu'])**2 + par['gamma']**2) return y - + @check_input def voigt(x,parameters): """ Evaluate a Voigt profile. - + Field parameters: C{ampl, mu, gamma, sigma} and optionally C{const} - + Function:: - + z = (x + gamma*i) / (sigma*sqrt(2)) V = A * Real[cerf(z)] / (sigma*sqrt(2*pi)) - + >>> x = np.linspace(-20,20,10000) >>> pars1 = [0.5,0.,2.,2.] >>> pars2 = [0.5,0.,1.,2.] @@ -737,13 +737,13 @@ def voigt(x,parameters): >>> p = pl.plot(x,y1,'b-') >>> p = pl.plot(x,y2,'g-') >>> p = pl.plot(x,y3,'r-') - + Multiple voigt profiles: - + >>> pars4 = [0.5,0.,2.,1.,0.1,-3,0.5,0.5,0.01] >>> y4 = voigt(x,pars4) >>> p = pl.plot(x,y4,'c-') - + @rtype: array @return: Voigt profile """ @@ -761,13 +761,13 @@ def voigt(x,parameters): def power_law(x,parameters): """ Evaluate a power law. - + Parameter fields: C{A, B, C}, optionally C{const} - + Function: - + P(f) = A / (1+ Bf)**C + const - + >>> x = np.linspace(0,10,1000) >>> pars1 = [0.5,1.,1.] >>> pars2 = [0.5,1.,2.] @@ -779,12 +779,12 @@ def power_law(x,parameters): >>> p = pl.plot(x,y1,'b-') >>> p = pl.plot(x,y2,'g-') >>> p = pl.plot(x,y3,'r-') - + Multiple power laws: >>> pars4 = pars1+pars2+pars3 >>> y4 = power_law(x,pars4) >>> p = pl.plot(x,y4,'c-') - + @parameter x: domain @type x: ndarray @parameter parameters: record array. @@ -810,13 +810,13 @@ def power_law(x,parameters): def sine_preppars(pars): """ Prepare sine function parameters in correct form for evaluating/fitting. - + If you input a record array, this function will output a 1D numpy array containing only the independent parameters for use in nonlinear fitting algorithms. - + If you input a 1D numpy array, it will output a record array. - + @param pars: input parameters in record array or normal array form @type pars: record array or normal numpy array @return: input parameters in normal array form or record array @@ -841,7 +841,7 @@ def sine_preppars(pars): else: # with constant if len(pars)%3==1: - converted_pars = np.zeros((4,(len(pars)-1)/3)) + converted_pars = np.zeros((4,(len(pars)-1)//3)) converted_pars[0,0] = pars[0] converted_pars[1] = pars[1::3] converted_pars[2] = pars[2::3] @@ -849,7 +849,7 @@ def sine_preppars(pars): names = ['const','ampl','freq','phase'] # without constant else: - converted_pars = np.zeros((3,len(pars)/3)) + converted_pars = np.zeros((3,len(pars)//3)) converted_pars[0] = pars[0::3] converted_pars[1] = pars[1::3] converted_pars[2] = pars[2::3] @@ -861,13 +861,13 @@ def sine_preppars(pars): def kepler_preppars(pars): """ Prepare Kepler orbit parameters in correct form for evaluating/fitting. - + If you input a record array, this function will output a 1D numpy array containing only the independent parameters for use in nonlinear fitting algorithms. - + If you input a 1D numpy array, it will output a record array. - + @param pars: input parameters in record array or normal array form @type pars: record array or normal numpy array @return: input parameters in normal array form or record array @@ -895,7 +895,7 @@ def kepler_preppars(pars): else: # with constant if len(pars)%5==1: - converted_pars = np.zeros((6,(len(pars)-1)/5)) + converted_pars = np.zeros((6,(len(pars)-1)//5)) converted_pars[0,0] = pars[0] converted_pars[1] = pars[1::5] converted_pars[2] = pars[2::5] @@ -905,7 +905,7 @@ def kepler_preppars(pars): names = ['gamma','P','T0','e','omega','K'] # without constant else: - converted_pars = np.zeros((5,len(pars)/5)) + converted_pars = np.zeros((5,len(pars)//5)) converted_pars[0] = pars[0::5] converted_pars[1] = pars[1::5] converted_pars[2] = pars[2::5] @@ -919,13 +919,13 @@ def kepler_preppars(pars): def binary_preppars(pars): """ Prepare Kepler orbit parameters in correct form for evaluating/fitting. - + If you input a record array, this function will output a 1D numpy array containing only the independent parameters for use in nonlinear fitting algorithms. - + If you input a 1D numpy array, it will output a record array. - + @param pars: input parameters in record array or normal array form @type pars: record array or normal numpy array @return: input parameters in normal array form or record array @@ -955,7 +955,7 @@ def binary_preppars(pars): else: # with constant if len(pars)%6==1: - converted_pars = np.zeros((7,(len(pars)-1)/6)) + converted_pars = np.zeros((7,(len(pars)-1)//6)) converted_pars[0,0] = pars[0] converted_pars[1] = pars[1::6] converted_pars[2] = pars[2::6] @@ -966,7 +966,7 @@ def binary_preppars(pars): names = ['gamma','P','T0','e','omega','K1','K2'] # without constant else: - converted_pars = np.zeros((6,len(pars)/6)) + converted_pars = np.zeros((6,len(pars)//6)) converted_pars[0] = pars[0::6] converted_pars[1] = pars[1::6] converted_pars[2] = pars[2::6] @@ -978,17 +978,17 @@ def binary_preppars(pars): return converted_pars - + def box_preppars(pars): """ Prepare sine function parameters in correct form for evaluating/fitting. - + If you input a record array, this function will output a 1D numpy array containing only the independent parameters for use in nonlinear fitting algorithms. - + If you input a 1D numpy array, it will output a record array. - + @param pars: input parameters in record array or normal array form @type pars: record array or normal numpy array @return: input parameters in normal array form or record array @@ -1004,7 +1004,7 @@ def box_preppars(pars): converted_pars[4::4] = pars['egress'] #-- from flat to record array else: - converted_pars = np.ones((5,(len(pars)-1)/4)) + converted_pars = np.ones((5,(len(pars)-1)//4)) converted_pars[0,0] = pars[0] converted_pars[1] = pars[1::4] converted_pars[2] = pars[2::4] @@ -1012,19 +1012,19 @@ def box_preppars(pars): converted_pars[4] = pars[4::4] names = ['cont','freq','depth','ingress','egress'] converted_pars = np.rec.fromarrays(converted_pars,names=names) - return converted_pars - + return converted_pars + def gauss_preppars(pars): """ Prepare gauss function parameters in correct form for evaluating/fitting. - + If you input a record array, this function will output a 1D numpy array containing only the independent parameters for use in nonlinear fitting algorithms. - + If you input a 1D numpy array, it will output a record array. - + @param pars: input parameters in record array or normal array form @type pars: record array or normal numpy array @return: input parameters in normal array form or record array @@ -1043,10 +1043,10 @@ def gauss_preppars(pars): #-- from flat to record array else: if len(pars)%3==0: - converted_pars = np.zeros((3,len(pars)/3)) + converted_pars = np.zeros((3,len(pars)//3)) names = ['ampl','mu','sigma'] else: - converted_pars = np.zeros((4,(len(pars)-1)/3)) + converted_pars = np.zeros((4,(len(pars)-1)//3)) converted_pars[3,0] = pars[-1] names = ['ampl','mu','sigma','const'] converted_pars[0] = pars[0:-1:3] @@ -1058,13 +1058,13 @@ def gauss_preppars(pars): def lorentz_preppars(pars): """ Prepare Lorentz function parameters in correct form for evaluating/fitting. - + If you input a record array, this function will output a 1D numpy array containing only the independent parameters for use in nonlinear fitting algorithms. - + If you input a 1D numpy array, it will output a record array. - + @param pars: input parameters in record array or normal array form @type pars: record array or normal numpy array @return: input parameters in normal array form or record array @@ -1083,10 +1083,10 @@ def lorentz_preppars(pars): #-- from flat to record array else: if len(pars)%3==0: - converted_pars = np.zeros((3,len(pars)/3)) + converted_pars = np.zeros((3,len(pars)//3)) names = ['ampl','mu','gamma'] else: - converted_pars = np.zeros((4,(len(pars)-1)/3)) + converted_pars = np.zeros((4,(len(pars)-1)//3)) converted_pars[3,0] = pars[-1] names = ['ampl','mu','gamma','const'] converted_pars[0] = pars[0:-1:3] @@ -1098,13 +1098,13 @@ def lorentz_preppars(pars): def voigt_preppars(pars): """ Prepare voigt function parameters in correct form for evaluating/fitting. - + If you input a record array, this function will output a 1D numpy array containing only the independent parameters for use in nonlinear fitting algorithms. - + If you input a 1D numpy array, it will output a record array. - + @param pars: input parameters in record array or normal array form @type pars: record array or normal numpy array @return: input parameters in normal array form or record array @@ -1124,10 +1124,10 @@ def voigt_preppars(pars): #-- from flat to record array else: if len(pars)%4==0: - converted_pars = np.zeros((4,len(pars)/4)) + converted_pars = np.zeros((4,len(pars)//4)) names = ['ampl','mu','sigma','gamma'] else: - converted_pars = np.zeros((5,(len(pars)-1)/4)) + converted_pars = np.zeros((5,(len(pars)-1)//4)) converted_pars[4,0] = pars[-1] names = ['ampl','mu','sigma','gamma','const'] converted_pars[0] = pars[0:-1:4] @@ -1141,13 +1141,13 @@ def voigt_preppars(pars): def power_law_preppars(pars): """ Prepare gauss function parameters in correct form for evaluating/fitting. - + If you input a record array, this function will output a 1D numpy array containing only the independent parameters for use in nonlinear fitting algorithms. - + If you input a 1D numpy array, it will output a record array. - + @param pars: input parameters in record array or normal array form @type pars: record array or normal numpy array @return: input parameters in normal array form or record array @@ -1166,10 +1166,10 @@ def power_law_preppars(pars): #-- from flat to record array else: if len(pars)%3==0: - converted_pars = np.zeros((3,len(pars)/3)) + converted_pars = np.zeros((3,len(pars)//3)) names = ['A','B','C'] else: - converted_pars = np.zeros((4,(len(pars)-1)/3)) + converted_pars = np.zeros((4,(len(pars)-1)//3)) converted_pars[3,0] = pars[-1] names = ['A','B','C','const'] converted_pars[0] = pars[0:-1:3] @@ -1207,4 +1207,3 @@ def _complex_error_function(x): import sys doctest.testmod() pl.show() - diff --git a/sigproc/filtering.py b/sigproc/filtering.py index 4b4954a6b..f029fc1ba 100755 --- a/sigproc/filtering.py +++ b/sigproc/filtering.py @@ -6,7 +6,7 @@ - Sinc - Box - Iterative Nonlinear filter - + Example usage: Generate some data: @@ -31,13 +31,11 @@ >>> p = pl.legend() """ -import copy import logging import numpy as np from numpy import sqrt,exp,pi,cos,sinc,trapz,average -from scipy.integrate import trapz -from multiprocessing import Process, Manager,cpu_count +#from scipy.integrate import trapz from ivs.aux.decorators import make_parallel from ivs.timeseries.decorators import parallel_pergram,defaults_filtering @@ -52,35 +50,35 @@ def filter_signal(x,y,ftype,f0=None,fn=None,step=1,x_template=None,**kwargs): """ Filter a signal. - + Handles equidistant and non-equidistant data. C{x} coordinate array must be monotonically increasing, but does not need to be strict monotonically increasing (e.g. their can be duplicate C{x} coordinates). - + It is possible to define a new template C{x} coordinate array on which to filter to signal C{y}. - - For some filters, it is possible to set an adjustable window: that is, + + For some filters, it is possible to set an adjustable window: that is, you can give an array to the C{window} parameter, but it needs to be the same length as C{x_template}. - + Example usage: - + Generate some data: - + >>> import time >>> x = np.linspace(0,150,10000) >>> y = np.sin(2*np.pi/20.*x)+np.random.normal(size=len(x)) - + Apply some filter: - + >>> c0 = time.time() >>> x1,y1,pnts = filter_signal(x,y,"gauss",sigma=1) >>> print time.time()-c0 >>> c0 = time.time() >>> x1,y1,pnts = filter_signal(x,y,"gauss",sigma=1,threads=2) >>> print time.time()-c0 - + Output parameter C{pnts} shows you how many points of the original signal were used to compute the given point. E.g. for a box filter, it will give the number of points over which was averaged. @@ -95,21 +93,21 @@ def filter_signal(x,y,ftype,f0=None,fn=None,step=1,x_template=None,**kwargs): if f0 is None: f0 = 0 if fn is None: fn = len(x_template) #-- set window and kernel function - ftype = ftype.lower() + ftype = ftype.lower() window = globals()[ftype+'_window'] kernel = globals()[ftype+'_kernel'] logger.info("Applying filter: %s"%(ftype)) - + #-- set window width lower_window,higher_window = window(**kwargs) - + #-- perhaps the window is a number, perhaps an array. Make sure it is an # array if not isinstance(lower_window,np.ndarray): lower_window = lower_window*np.ones(len(x_template))[f0:fn:step] if not isinstance(higher_window,np.ndarray): higher_window = higher_window*np.ones(len(x_template))[f0:fn:step] - + #-- start running through timeseries (make searchsorted local for speedup) logger.debug("FILTER between index %d-%d with step %d"%(f0,fn,step)) x_template = x_template[f0:fn:step] @@ -117,12 +115,12 @@ def filter_signal(x,y,ftype,f0=None,fn=None,step=1,x_template=None,**kwargs): out = [kernel(x,y,t,index=searchsorted([t-1e-30-lower_window[i],t+1e-30+higher_window[i]]), **kwargs) for i,t in enumerate(x_template)] #-- that's it! - return tuple([x_template] + list(np.array(out).T)) - + return tuple([x_template] + list(np.array(out).T)) + #} #{ Basic functions for convolution-based filters - + def _fixed_window(window_width=0): """ Define general window @@ -135,7 +133,7 @@ def _fixed_window(window_width=0): return lower_window,higher_window #} #{ Convolution-based filter kernels and Windows - + def gauss_window(sigma=1.,limit=4.): """ Define Gaussian_window @@ -145,24 +143,24 @@ def gauss_window(sigma=1.,limit=4.): if isinstance(sigma,np.ndarray): sigma = sigma.max() return _fixed_window(window_width=2*sigma*limit) - + def gauss_kernel(times, signal, t,sigma=1.,index=(0,-1),norm_weights=True): """ Define Gaussian kernel. - + #Import necessary modules: #>>> from pylab import plot,figure,title,subplot #>>> from sigproc.timeseries import tfreq - + #Generate some data: #>>> x = linspace(0,150,10000) #>>> y = random.normal(size=len(x)) - + #Apply Gaussian filter twice, subtract and calculate periodogram: #>>> y1,pnts = filter_signal(x,y,"gauss",sigma=1) #>>> y2,pnts = filter_signal(x,y,"gauss",sigma=3) - + #Plot the results: #>>> p=figure(figsize=(8,18)) #>>> p=subplot(311);p=title("GAUSSIAN FILTER") @@ -174,7 +172,7 @@ def gauss_kernel(times, signal, t,sigma=1.,index=(0,-1),norm_weights=True): #>>> p=subplot(313) #>>> p=plot(per1[0],per1[1],'k-') #>>> p=plot(per2[0],per2[1],'r-') - + @rtype: (ndarray,) """ index0,indexn = index @@ -197,9 +195,9 @@ def gauss_kernel(times, signal, t,sigma=1.,index=(0,-1),norm_weights=True): def mad(a, c=0.6745): """ Median Absolute Deviation of a 1D array: - + median(abs(a - median(a))) / c - + @rtype: float """ return np.median(np.abs(a-np.median(a)))/c @@ -207,7 +205,7 @@ def mad(a, c=0.6745): def inl_window(**kwargs): """ Defines the INL window - + @rtype: (float,float) @return: window limits """ @@ -216,23 +214,23 @@ def inl_window(**kwargs): def inl_kernel(times_in,signal_in,t,c=0.6745,sig_level=3.,tolerance=0.01,index=(0,-1),window_width=None): """ Iterative Nonlinear Filter (Aigrain). - + Example usage: - + #Import necessary modules: #>>> from pylab import plot,figure,title,subplot #>>> from sigproc.timeseries import tfreq - + #Generate some data: #>>> x = linspace(0,150,10000) #>>> y = sin(2*pi*x/20.*x)+random.normal(size=len(x)) - + #Apply INL filter twice, subtract and calculate periodogram: #>>> y1,pnts1,mads = filter_signal(x,y,"inl",window_width=1) #>>> y2,pnts2,mads = filter_signal(x,y,"inl",window_width=10) #>>> freq1,per1,stat1 = tfreq.scargle(x,y,norm='amplitude',fn=4) #>>> freq2,per2,stat2 = tfreq.scargle(x,y1-y2,norm='amplitude',fn=4) - + #Plot the results: #>>> p=figure(figsize=(8,18)) #>>> p=subplot(311);p=title("INL FILTER") @@ -244,7 +242,7 @@ def inl_kernel(times_in,signal_in,t,c=0.6745,sig_level=3.,tolerance=0.01,index=( #>>> p=subplot(313) #>>> p=plot(per1[0],per1[1],'k-') #>>> p=plot(per2[0],per2[1],'r-') - + @rtype: (ndarray,ndarray) @return: continuum,sigma """ @@ -264,43 +262,43 @@ def inl_kernel(times_in,signal_in,t,c=0.6745,sig_level=3.,tolerance=0.01,index=( else: continuum = continuum_ outliers = (np.abs(continuum - signal_)>sig_level*sigma) - + return continuum_, sigma,len(times) def pijpers_window(delta=1.): """ Defines the window for the Pijpers filter. - + @rtype: (float,float) @return: window limits """ return _fixed_window(window_width=100*delta) return _fixed_window(**kwargs) - + def pijpers_kernel(times_in, signal_in,t,limit=0.000001,delta=1.,sigma=1.,gamma=0, r=0.0001,index0=0,indexn=-1,index=(0,-1),norm_weights=True): """ Defines the Pijpers (2006) filter kernel. - + Equals box-multiplying in the frequency domain. - + Example usage: - + #Import necessary modules: #>>> from pylab import plot,figure,title,subplot #>>> from sigproc.timeseries import tfreq - + #Generate some data: #>>> x = linspace(0,150,10000) #>>> y = random.normal(size=len(x)) - + #Apply Gaussian filter twice, subtract and calculate periodogram: #>>> y1,pnts1 = filter_signal(x,y,"pijpers",delta=1) #>>> y2,pnts2 = filter_signal(x,y,"pijpers",delta=3) #>>> freq1,per1,stat1 = tfreq.scargle(x,y,norm='amplitude',fn=4) #>>> freq2,per2,stat2 = tfreq.scargle(x,y1-y2,norm='amplitude',fn=4) - + #Plot the results: #>>> p=figure(figsize=(8,18)) #>>> p=subplot(311);p=title("PIJPERS FILTER") @@ -312,16 +310,16 @@ def pijpers_kernel(times_in, signal_in,t,limit=0.000001,delta=1.,sigma=1.,gamma= #>>> p=subplot(313) #>>> p=plot(per1[0],per1[1],'k-') #>>> p=plot(per2[0],per2[1],'r-') - + @rtype: (ndarray,) @return: convolved signal """ index0,indexn = index times = times_in[index0:indexn] signal = signal_in[index0:indexn] - + delta = delta*2*pi - + x = delta * (times-t) #-- compute convolution kernel -- should we normalize? so that sum(weights)=1 weights = delta/pi * sinc(x/pi) * exp(-1/4. * r**2 * x**2) * cos(gamma*x) @@ -334,7 +332,7 @@ def pijpers_kernel(times_in, signal_in,t,limit=0.000001,delta=1.,sigma=1.,gamma= def box_window(**kwargs): """ Defines the window for the box-filter. - + @rtype: (float,float) @return: window limits """ @@ -343,23 +341,23 @@ def box_window(**kwargs): def box_kernel(times_in,signal_in,t,window_width=None,index=(0,-1),norm_weights=True): """ Defines the Box filter kernel (moving average). - + Equals sinc-multiplication of frequency domain. - + Example usage: #Import necessary modules: #>>> from pylab import plot,figure,title,subplot #>>> from sigproc.timeseries import tfreq - + #Generate some data: #>>> x = linspace(0,150,10000) #>>> y = random.normal(size=len(x)) - + #Apply Gaussian filter twice, subtract and calculate periodogram: #>>> y1,pnts = filter_signal(x,y,"box",window_width=1) #>>> y2,pnts = filter_signal(x,y,"box",window_width=3) - + @rtype: (ndarray,) @return: convolved signal """ @@ -369,7 +367,7 @@ def box_kernel(times_in,signal_in,t,window_width=None,index=(0,-1),norm_weights= weights = np.ones(len(times)) if norm_weights: weights /= np.sum(weights) - + convolved_signal = np.sum(weights*signal) return convolved_signal,len(times) @@ -380,34 +378,34 @@ def box_kernel(times_in,signal_in,t,window_width=None,index=(0,-1),norm_weights= #norm_weights=True,is_error_array=False): #""" #Defines the Box filter kernel (moving average). - + #Equals sinc-multiplication in frequency domain. - + #When C{is_error_array} is C{True}, the integration will be done differently. #Instead of the usual - + #F_j = sum_i (F_i dx_{i,j}) / sum_i (dx_{i,j}) - - #it will be - + + #it will be + #s_j = sqrt(sum_i (s_i dx_{i,j})**2) / sum_i (dx_{i,j}) - + #You can only set C{is_error_array} to C{True} when C{norm_weights=True}. - + #Example usage: ##Import necessary modules: ##>>> from pylab import plot,figure,title,subplot ##>>> from sigproc.timeseries import tfreq - + ##Generate some data: ##>>> x = linspace(0,150,10000) ##>>> y = random.normal(size=len(x)) - + ##Apply Gaussian filter twice, subtract and calculate periodogram: ##>>> y1,pnts = filter_signal(x,y,"box",window_width=1) ##>>> y2,pnts = filter_signal(x,y,"box",window_width=3) - + #@rtype: (ndarray,) #@return: convolved signal #""" @@ -427,7 +425,7 @@ def box_kernel(times_in,signal_in,t,window_width=None,index=(0,-1),norm_weights= #assert(is_error_array==False) #convolved = average(signal,weights=weights) #norm_fact = 1 - + #convolved_signal = convolved/norm_fact ##weights /= np.sum(weights) ##convolved_signal = np.sum(weights*signal) @@ -445,10 +443,10 @@ def box_kernel(times_in,signal_in,t,window_width=None,index=(0,-1),norm_weights= def test(): """ - + >>> from pylab import show >>> p=show() - + """ import doctest doctest.testmod() diff --git a/sigproc/fit.py b/sigproc/fit.py index 77f33eacb..09d7fc08b 100644 --- a/sigproc/fit.py +++ b/sigproc/fit.py @@ -6,7 +6,7 @@ 1.1 BD+29.3070 -------------- -Fit the orbit of the main sequence companion of the sdB+MS system BD+29.3070. +Fit the orbit of the main sequence companion of the sdB+MS system BD+29.3070. The radial velocities are obtained from HERMES spectra using the cross correlation tool of the pipeline. @@ -39,24 +39,24 @@ Print the results: >>> print mymodel.param2str() - p = 1012.26 +/- 16.57 - t0 = 2455423.65 +/- 11.27 - e = 0.16 +/- 0.01 - omega = 1.72 +/- 0.08 - k = 6.06 +/- 0.04 + p = 1012.26 +/- 16.57 + t0 = 2455423.65 +/- 11.27 + e = 0.16 +/- 0.01 + omega = 1.72 +/- 0.08 + k = 6.06 +/- 0.04 v0 = 32.23 +/- 0.09 The minimizer already returned errors on the parameters, based on the Levenberg-Marquardt algorithm of scipy. But we can get more robust errors by using the L{Minimizer.estimate_error} method of the minimizer wich uses an F-test to calculate confidence intervals, fx on the period and eccentricity of the orbit: >>> ci = result.estimate_error(p_names=['p', 'e'], sigmas=[0.25,0.65,0.95]) >>> print confidence2string(ci, accuracy=4) -p +p 25.00 % 65.00 % 95.00 % - - 1006.9878 997.1355 980.7742 - + 1017.7479 1029.2554 1053.0851 -e + - 1006.9878 997.1355 980.7742 + + 1017.7479 1029.2554 1053.0851 +e 25.00 % 65.00 % 95.00 % - - 0.1603 0.1542 0.1433 + - 0.1603 0.1542 0.1433 + 0.1667 0.1731 0.1852 Now plot the resulting rv curve over the original curve: @@ -66,7 +66,7 @@ ]include figure]]ivs_sigproc_lmfit_BD+29.3070_1.png] -We can use the L{Minimizer.plot_confidence_interval} method to plot the confidence intervals of two +We can use the L{Minimizer.plot_confidence_interval} method to plot the confidence intervals of two parameters, and show the correlation between them, fx between the period and T0: >>> p = pl.figure(2) @@ -75,7 +75,7 @@ ]include figure]]ivs_sigproc_lmfit_BD+29.3070_2.png] To get a better idea of how the parameter space behaves we can start the fitting process from -different starting values and see how they converge. Fx, we will let the fitting process start +different starting values and see how they converge. Fx, we will let the fitting process start from different values for the period and the eccentricity, and then plot where they converge to Herefore we use the L{grid_minimize} function which has the same input as the normal minimize function @@ -83,7 +83,7 @@ >>> fitters, startpars, models, chi2s = fit.grid_minimize(dates, rv, mymodel, weights=1/err, points=200, parameters = ['p','e'], return_all=True) -We started fitting from 200 points randomly distributed in period and eccentricity space, with the +We started fitting from 200 points randomly distributed in period and eccentricity space, with the boundary values for there parameters as limits. Based on this output we can use the L{plot_convergence} function to plot to which values each starting point converges. @@ -120,7 +120,7 @@ >>> freqs,ampls = pergrams.kepler(times,RV,fn=0.2) >>> freq = freqs[np.argmax(ampls)] >>> pars1 = kepler(times, RV, freq, output_type='new') ->>> print pars1 +>>> print pars1 [11.581314028141733, 2451060.7517886101, 0.19000000000000003, 1.0069244281466982, 11.915330492005735, -59.178393186003241] Now we want to improve this fit using the nonlinear optimizers, deriving errors @@ -135,11 +135,11 @@ [ 1.15815058e+01 2.45106077e+06 1.94720600e-01 1.02204827e+00 1.19264204e+01 -5.91827773e+01] >>> print mymodel.param2str(accuracy=6) - p = 11.581506 +/- 0.004104 - t0 = 2451060.771681 +/- 0.583864 - e = 0.194721 +/- 0.060980 - omega = 1.022048 +/- 0.320605 - k = 11.926420 +/- 0.786787 + p = 11.581506 +/- 0.004104 + t0 = 2451060.771681 +/- 0.583864 + e = 0.194721 +/- 0.060980 + omega = 1.022048 +/- 0.320605 + k = 11.926420 +/- 0.786787 v0 = -59.182777 +/- 0.503345 Evaluate the orbital fits, and make phasediagrams of the fits and the data @@ -191,7 +191,7 @@ >>> mymodel = Model(functions=[gauss1, gauss2]) -Create some data with noise on it +Create some data with noise on it >>> np.random.seed(1111) >>> x = np.linspace(0.5,1.5, num=1000) @@ -210,20 +210,20 @@ Fit the model to the data >>> result = minimize(x,y, mymodel) -Print the resulting values for the parameters. The errors are very small as the data only has some +Print the resulting values for the parameters. The errors are very small as the data only has some small normal distributed noise added to it: >>> print gauss1.param2str(accuracy=6) - a = -0.750354 +/- 0.001802 - mu = 0.999949 +/- 0.000207 - sigma = 0.099597 +/- 0.000267 + a = -0.750354 +/- 0.001802 + mu = 0.999949 +/- 0.000207 + sigma = 0.099597 +/- 0.000267 c = 0.999990 +/- 0.000677 >>> print gauss2.param2str(accuracy=6) - a = 0.216054 +/- 0.004485 - mu = 1.000047 +/- 0.000226 - sigma = 0.009815 +/- 0.000250 + a = 0.216054 +/- 0.004485 + mu = 1.000047 +/- 0.000226 + sigma = 0.009815 +/- 0.000250 c = 0.000000 +/- 0.000000 - + Now plot the results: >>> p = pl.figure(1) @@ -374,7 +374,7 @@ This is the result: >>> print mymodel.param2str() - T = 9678.90 +/- 394.26 + T = 9678.90 +/- 394.26 scale = 1.14 +/- 0.17 A multiple black body is very similar (we make the errors somewhat smaller for @@ -400,11 +400,11 @@ This is the result: >>> print mymodel.param2str() - T_0 = 6155.32 +/- 3338.54 - scale_0 = 9.54 +/- 23.00 - T_1 = 3134.37 +/- 714.01 - scale_1 = 93.40 +/- 17.98 - T_2 = 14696.40 +/- 568.76 + T_0 = 6155.32 +/- 3338.54 + scale_0 = 9.54 +/- 23.00 + T_1 = 3134.37 +/- 714.01 + scale_1 = 93.40 +/- 17.98 + T_2 = 14696.40 +/- 568.76 scale_2 = 1.15 +/- 0.33 And a nice plot of the two cases: @@ -423,8 +423,7 @@ >>> p = pl.gca().set_yscale('log') ]include figure]]ivs_sigproc_lmfit_blackbody.png] -""" -import time +""" import logging import numpy as np @@ -434,7 +433,6 @@ import scipy.optimize from ivs.aux import progressMeter as progress -from ivs.aux import loggers from ivs.sigproc import evaluate from ivs.timeseries import pyKEP @@ -452,16 +450,16 @@ def sine(times, signal, freq, sigma=None,constant=True,error=False,t0=0): """ Fit a harmonic function. - + This function is of the form - + C + \sum_j A_j sin(2pi nu_j (t-t0) + phi_j) - + where the presence of the constant term is an option. The error bars on the fitted parameters can also be requested (by Joris De Ridder). - + (phase in radians!) - + @param times: time points @type times: numpy array @param signal: observations @@ -481,7 +479,7 @@ def sine(times, signal, freq, sigma=None,constant=True,error=False,t0=0): """ #-- Subtract the zero point from the time points. times = times - t0 - + #-- Prepare the input: if a frequency value is given, put it in a list. If # an iterable is given convert it to an array if not hasattr(freq,'__len__'): @@ -492,18 +490,18 @@ def sine(times, signal, freq, sigma=None,constant=True,error=False,t0=0): sigma = np.ones_like(signal) elif not hasattr(sigma,'__len__'): sigma = sigma * np.ones_like(signal) - + #-- Determine the number of fit parameters Ndata = len(times) Nfreq = len(freq) if not constant: Nparam = 2*Nfreq else: Nparam = 2*Nfreq+1 - + #-- The fit function used is of the form # C + \sum_j a_j sin(2pi\nu_j t_i) + b_j cos(2pi\nu_j t_i) # which is linear in its fit parameters. These parameters p can therefore be # solved by minimizing ||b - A p||, where b are the observations, and A is the - # basisfunction matrix, i.e. A[i,j] is the j-th base function evaluated in + # basisfunction matrix, i.e. A[i,j] is the j-th base function evaluated in # the i-th timepoint. The first Nfreq columns are the amplitudes of the sine, # the second Nfreq columns are the amplitudes of the cosine, and if requested, # the last column belongs to the constant @@ -513,12 +511,12 @@ def sine(times, signal, freq, sigma=None,constant=True,error=False,t0=0): A[:,Nfreq+j] = cos(2*pi*freq[j]*times) * sigma if constant: A[:,2*Nfreq] = np.ones(Ndata) * sigma - + b = signal * sigma - + #-- Solve using SVD decomposition fitparam, chisq, rank, s = np.linalg.lstsq(A,b) - + #-- Compute the amplitudes and phases: A_j sin(2pi*\nu_j t_i + phi_j) amplitude = np.zeros(Nfreq) phase = np.zeros(Nfreq) @@ -526,7 +524,7 @@ def sine(times, signal, freq, sigma=None,constant=True,error=False,t0=0): amplitude[j] = np.sqrt(fitparam[j]**2 + fitparam[Nfreq+j]**2) phase[j] = np.arctan2(fitparam[Nfreq+j], fitparam[j]) - #-- If no error bars are needed, we are finished here, we collect all parameters + #-- If no error bars are needed, we are finished here, we collect all parameters if constant: constn = np.zeros(len(amplitude)) constn[0] = fitparam[2*Nfreq] @@ -535,36 +533,36 @@ def sine(times, signal, freq, sigma=None,constant=True,error=False,t0=0): else: names = ['ampl','freq','phase'] fpars = [amplitude,freq,phase/(2*pi)] - + parameters = np.rec.fromarrays(fpars,names=names) - + logger.debug('SINEFIT: Calculated harmonic fit with %d frequencies through %d datapoints'%(Nfreq,Ndata)) - + return parameters def periodic_spline(times, signal, freq, t0=None, order=20, k=3): """ Fit a periodic spline. - + CAUTION: this definition assumes the proposed period is either small compared to the total time range, or there are a lot of points per period. - + This definition basically phases all the data and constructs an empirical periodic function through an averaging process per phasebin, and then performing a splinefit through those points. - + In order to make the first derivative continuous, we repeat the first point(s) at the end and the end point(s) at the beginning. - + The constructed function can than be removed from the original data. - + Output is a record array containing the columns 'freq', 'knots', 'coeffs' and 'degree'. - - Example usage: - + + Example usage: + >>> myfreq = 1/20. >>> times = np.linspace(0,150,10000) >>> signal = sin(2*pi*myfreq*times+0.32*2*pi) + np.random.normal(size=len(times)) @@ -575,7 +573,7 @@ def periodic_spline(times, signal, freq, t0=None, order=20, k=3): >>> p=pl.plot(times,signal,'ko') >>> p=pl.plot(times,trend,'r-') >>> p=pl.title("test:tfit:periodic_spline_fit") - + @param times: time points @type times: numpy 1d array @param signal: observation points @@ -592,13 +590,13 @@ def periodic_spline(times, signal, freq, t0=None, order=20, k=3): #-- get keyword arguments if t0 is None: t0 = times[0] - + #-- Prepare the input: if a frequency value is given, put it in a list. If # an iterable is given convert it to an array if not hasattr(freq,'__len__'): freq = [freq] freq = np.asarray(freq) - + N = order*3+(k-2) parameters = np.zeros(len(freq), dtype=[('freq','float32'), ('knots','%dfloat32'%N), @@ -607,12 +605,12 @@ def periodic_spline(times, signal, freq, t0=None, order=20, k=3): for nf,ifreq in enumerate(freq): #-- get phased signal phases,phased_sig = evaluate.phasediagram(times,signal,ifreq,t0=t0) - + #-- construct phase domain x = np.linspace(0.,1.,order)[:-1] dx = x[1]-x[0] x = x+dx/2 - + #-- make bin-averaged signal. Possible that some bins are empty, just skip # them but warn the user found_phase = [] @@ -624,14 +622,14 @@ def periodic_spline(times, signal, freq, t0=None, order=20, k=3): continue found_phase.append(xi) found_averg.append(np.average(phased_sig[this_bin])) - + found_phase = np.array(found_phase) found_averg = np.array(found_averg) - + #-- make circular found_phase = np.hstack([found_phase-1,found_phase,found_phase+1]) found_averg = np.hstack([found_averg, found_averg,found_averg]) - + #-- compute spline representation knots,coeffs,degree = splrep(found_phase,found_averg,k=k) parameters['freq'][nf] = ifreq @@ -644,31 +642,31 @@ def periodic_spline(times, signal, freq, t0=None, order=20, k=3): def kepler(times, signal, freq, sigma=None, wexp=2., e0=0, en=0.99, de=0.01, output_type='old'): """ Fit a Kepler orbit to a time series. - + Example usage: - + >>> from ivs.timeseries import pergrams - + First set the parameters we want to use: - + >>> pars = tuple([12.456,23456.,0.37,213/180.*pi,98.76,55.]) >>> pars = np.rec.array([pars],dtype=[('P','f8'),('T0','f8'),('e','f8'), ... ('omega','f8'),('K','f8'),('gamma','f8')]) - + Then generate the signal and add some noise - + >>> times = np.linspace(pars[0]['T0'],pars[0]['T0']+5*pars[0]['P'],100) >>> signalo = evaluate.kepler(times,pars) >>> signal = signalo + np.random.normal(scale=20.,size=len(times)) - + Calculate the periodogram: - + >>> freqs,ampls = pergrams.kepler(times,signal) >>> opars = kepler(times,signal,freqs[np.argmax(ampls)]) >>> signalf = evaluate.kepler(times,opars) - + And make some plots - + >>> import pylab as pl >>> p = pl.figure() >>> p = pl.subplot(221) @@ -677,7 +675,7 @@ def kepler(times, signal, freq, sigma=None, wexp=2., e0=0, en=0.99, de=0.01, out >>> p = pl.plot(times,signalf,'b--',lw=2) >>> p = pl.subplot(222) >>> p = pl.plot(freqs,ampls,'k-') - + @param times: time points @type times: numpy 1d array @param signal: observation points @@ -703,23 +701,23 @@ def kepler(times, signal, freq, sigma=None, wexp=2., e0=0, en=0.99, de=0.01, out l1 = np.zeros(maxstep) #-- power LS s2 = np.zeros(maxstep) #-- power Kepler k2 = np.zeros(6) #-- parameters for Kepler orbit - + pyKEP.kepler(times,signal,sigma,f0,fn,df,wexp,e0,en,de,x00,x0n, f1,s1,p1,l1,s2,k2) - + freq,x0,e,w,K,RV0 = k2 pars = [1/freq,x0/(2*pi*freq) + times[0], e, w, K, RV0] if output_type=='old': pars = np.rec.array([tuple(pars)],dtype=[('P','f8'),('T0','f8'),('e','f8'),('omega','f8'),('K','f8'),('gamma','f8')]) - + return pars - + def box(times,signal,freq,b0=0,bn=1,order=50,t0=None): """ Fit box shaped transits. - + @param times: time points @type times: numpy 1d array @param signal: observation points @@ -739,18 +737,18 @@ def box(times,signal,freq,b0=0,bn=1,order=50,t0=None): """ if t0 is None: t0 = 0. - + #-- Prepare the input: if a frequency value is given, put it in a list. If # an iterable is given convert it to an array if not hasattr(freq,'__len__'): freq = [freq] freq = np.asarray(freq) - + parameters = np.rec.fromarrays([np.zeros(len(freq)) for i in range(5)],dtype=[('freq','f8'),('depth','f8'), ('ingress','f8'),('egress','f8'), ('cont','f8')]) bins = np.linspace(b0,bn,order) - + for fnr,frequency in enumerate(freq): parameters[fnr]['freq'] = frequency #-- we need to check two phase diagrams, to compensate for edge effects @@ -792,29 +790,29 @@ def gauss(x,y,threshold=0.1,constant=False,full_output=False, init_guess_method='analytical',window=None): """ Fit a Gaussian profile to data using a polynomial fit. - + y = A * exp( -(x-mu)**2 / (2*sigma**2)) - + ln(y) = ln(A) - (x-mu)**2 / (2*sigma**2) ln(y) = ln(A) - mu**2 / (2*sigma**2) + mu / (sigma**2) * x - x**2 / (2*sigma**2) - + then the parameters are given by - + p0 = - 1 / (2*sigma**2) p1 = mu / ( sigma**2) p2 = ln(A) - mu**2 / (2*sigma**2) - + Note that not all datapoints are used, but only those above a certain values (namely 10% of the maximum value), In this way, we reduce the influence of the continuum and concentrate on the shape of the peak itself. - + Afterwards, we perform a non linear least square fit with above parameters as starting values, but only accept it if the CHI2 has improved. - + If a constant has to be fitted, the nonlinear options has to be True. - + Example: we generate a Lorentzian curve and fit a Gaussian to it: - + >>> x = np.linspace(-10,10,1000) >>> y = evaluate.lorentz(x,[5.,0.,2.]) + np.random.normal(scale=0.1,size=len(x)) >>> pars1,e_pars1 = gauss(x,y) @@ -825,7 +823,7 @@ def gauss(x,y,threshold=0.1,constant=False,full_output=False, >>> p = pl.plot(x,y,'k-') >>> p = pl.plot(x,y1,'r-',lw=2) >>> p = pl.plot(x,y2,'b-',lw=2) - + @param x: x axis data @type x: numpy array @param y: y axis data @@ -844,26 +842,26 @@ def gauss(x,y,threshold=0.1,constant=False,full_output=False, C = np.median(np.sort(y)[:max(int(0.05*N),3)]) else: C = 0. - + if window is not None: win = (window[0]<=x) & (x<=window[1]) x,y = x[win],y[win] - + #-- transform to a polynomial function and perform a fit # first clip where y==0 threshold *= max(y-C) use_in_fit = (y-C)>=threshold xc = x[use_in_fit] yc = y[use_in_fit] - + lnyc = np.log(yc-C) p = np.polyfit(xc, lnyc, 2) - + #-- determine constants sigma = np.sqrt(-1. / (2.*p[0])) mu = sigma**2.*p[1] A = np.exp(p[2] + mu**2. / (2.*sigma**2.)) - + #-- handle NaN Exceptions if A!=A or mu!=mu or sigma!=sigma: logger.error('Initial Gaussian fit failed') @@ -873,7 +871,7 @@ def gauss(x,y,threshold=0.1,constant=False,full_output=False, sigma = xc.ptp()/3. else: init_success = True - + #-- check chi2 if constant: p0 = evaluate.gauss_preppars(np.asarray([A,mu,sigma,C])) @@ -881,7 +879,7 @@ def gauss(x,y,threshold=0.1,constant=False,full_output=False, p0 = evaluate.gauss_preppars(np.asarray([A,mu,sigma])) yf = evaluate.gauss(xc,p0) chi2 = np.sum((yc-yf)**2) - + #-- perform non linear least square fit if constant: pars,e_pars,gain = optimize(x,y,p0,'gauss', minimizer='leastsq') @@ -902,10 +900,10 @@ def gauss(x,y,threshold=0.1,constant=False,full_output=False, def e_sine(times,signal,parameters,correlation_correction=True,limit=10000): """ Compute the errors on the parameters from a sine fit. - + Note: errors on the constant are only calculated when the number of datapoints is below 1000. Otherwise, the matrices involved become to huge. - + @param times: time points @type times: numpy array @param signal: observations @@ -928,39 +926,39 @@ def e_sine(times,signal,parameters,correlation_correction=True,limit=10000): phase = parameters['phase'] amplitude = parameters['ampl'] chisq = np.sum((signal - evaluate.sine(times,parameters))**2) - + #-- for quick reference, we also need the dimensions of the data and # fit parameters Ndata = len(times) Nparam = 2*len(parameters) Nfreq = len(freq) T = times.ptp() - + #-- do we need to include the constant? if 'const' in parameters.dtype.names: constant = True - Nparam += 1 - + Nparam += 1 + #-- these lists will contain the columns and their names errors = [] names = [] - + #-- If error bars on the constant are needed, we do it here. The errors # on the amplitude, frequency and phase are computed below. if constant and Ndatachisq_init or flag!=1: logger.error('Optimization not successful [flag=%d] (%g --> %g)'%(flag,chisq_init,chisq)) chisq = np.inf - + #-- derive the errors from the nonlinear fit if cov is not None: errors = np.sqrt(cov.diagonal()) * np.sqrt(chisq/dof) @@ -1124,19 +1122,19 @@ def optimize(times, signal, parameters, func_name, prep_func=None, errors = np.zeros(len(popt)) if chisq>chisq_init: logger.error('Optimization not successful') - + #-- gain in chi square: if positive, we gained, if negative, we lost... gain = (chisq_init-chisq)/chisq_init*100. - + #-- transform the parameters to record arrays, as well as the errors if prepfunc is not None: - parameters = prepfunc(popt) + parameters = prepfunc(popt) e_parameters = prepfunc(errors) e_parameters.dtype.names = ['e_'+name for name in e_parameters.dtype.names] else: parameters = popt e_parameters = errors - + return parameters,e_parameters, gain #=================================================================================================== @@ -1147,43 +1145,43 @@ class Function(object): Class to define a function with associated parameters. The function can be evaluated using the L{evaluate} method. Parameters can be added/updated, together with boundaries and expressions, and can be hold fixed and released by adjusting the vary keyword in L{setup_parameters}. - - The provided function needs to take two arguments. The first one is a list of parameters, the + + The provided function needs to take two arguments. The first one is a list of parameters, the second is a list of x-values for which the function will be evaluated. For example if you want to define a quadratic function it will look like: - + >>> func = lambda pars, x: pars[0] + pars[1] * x + pars[2] * x**2 - + Functions can be combined using the L{Model} class, or can be fitted directly to data using the L{Minimizer} class. - + To improve the fit, a jacobian can be provided as well. However, some care is nessessary when - defining this function. When using the L{Minimizer} class to fit a Function to data, the + defining this function. When using the L{Minimizer} class to fit a Function to data, the residual function is defined as:: - + residuals = data - Function.evaluate() - - To derive the jacobian you have to take the derivatives of -1 * function. Furthermore the + + To derive the jacobian you have to take the derivatives of -1 * function. Furthermore the derivatives have to be across the rows. For the example quadratic function above, the jacobian would look like: - + >>> jacobian = lambda pars, x: np.array([-np.ones(len(x)), -x, -x**2]).T - + If you get bad results, try flipping all signs. The jacobian does not really improve the speed of the fitprocess, but it does help to reach the minimum in a more consistent way (see examples). - - The internal representation of the parameters uses a parameter object of the U{lmfit + + The internal representation of the parameters uses a parameter object of the U{lmfit } package. No knowledge of this repersentation is required as methods for direct interaction with the parameter values and other settings are provided. If wanted, the parameters object itself can be obtained with the L{parameters} attribute. """ - + def __init__(self, function=None, par_names=None, jacobian=None, resfunc=None): """ Create a new Function by providing the function of which it consists together with the names of each parameter in this function. You can specify the jacobian as well. - + @param function: A function expression @type function: function @param par_names: The names of each parameter of function @@ -1197,25 +1195,25 @@ def __init__(self, function=None, par_names=None, jacobian=None, resfunc=None): self.par_names = par_names self.jacobian = jacobian self.resfunc = resfunc - + #create an empty parameter set based on the parameter names self.parameters = None self.setup_parameters() - + #{ Interaction - + def evaluate(self,x, *args, **kwargs): """ Evaluate the function for the given values and optional the given parameter object. If no parameter object is given then the parameter object belonging to the function is used. - + >>> #func.evaluate(x, parameters) >>> #func.evaluate(x) - + @param x: the independant data for which to evaluate the function @type x: numpy array - + @return: Function(x), same size as x @rtype: numpy array """ @@ -1224,9 +1222,9 @@ def evaluate(self,x, *args, **kwargs): pars = [] for name in self.par_names: pars.append(self.parameters[name].value) - + return self.function(pars,x, **kwargs) - + if len(args) == 1: #-- Use the provided parameters #-- if provided as a ParameterObject @@ -1236,26 +1234,26 @@ def evaluate(self,x, *args, **kwargs): pars.append(args[0][name].value) else: pars = args[0] - + return self.function(pars,x, **kwargs) - + def evaluate_jacobian(self, x, *args): """ Evaluates the jacobian if that function is provided, using the given parameter object. If no parameter object is given then the parameter object belonging to the function is used. """ - if self.jacobian == None: + if self.jacobian is None: return [0.0 for i in self.par_names] - + if len(args) == 0: #-- Use the parameters belonging to this object pars = [] for name in self.par_names: pars.append(self.parameters[name].value) - + return self.jacobian(pars,x) - + if len(args) == 1: #-- Use the provided parameters #-- if provided as a ParameterObject @@ -1265,19 +1263,19 @@ def evaluate_jacobian(self, x, *args): pars.append(args[0][name].value) else: pars = args[0] - + return self.jacobian(pars,x) - + def setup_parameters(self, value=None, bounds=None, vary=None, expr=None, **kwargs): """ Create or adjust a parameter object based on the parameter names and if provided the values, bounds, vary and expressions. Basic checking if the parameter boundaries are consistent is performed. - + Example Use: - + >>> setup_parameters(values=[v1, v2, ...], bounds=[(b1, b2), (b3, b4), ...], vary=[True, False, ...]) - + @param values: The values of the parameters @type values: array @param bounds: min and max boundaries on the parameters [(min,max),...] @@ -1288,34 +1286,34 @@ def setup_parameters(self, value=None, bounds=None, vary=None, expr=None, **kwar @type exprs: array """ nrpars = len(self.par_names) - if value == None: + if value is None: value = kwargs['values'] if 'values' in kwargs else [0 for i in range(nrpars)] - if bounds == None: + if bounds is None: bounds = np.array([[None,None] for i in range(nrpars)]) else: bounds = np.asarray(bounds) - if vary == None: + if vary is None: vary = [True for i in range(nrpars)] - if expr == None: + if expr is None: expr = kwargs['exprs'] if 'exprs' in kwargs else [None for i in range(nrpars)] - + min = kwargs['min'] if 'min' in kwargs else bounds[:,0] max = kwargs['max'] if 'max' in kwargs else bounds[:,1] - + def check_boundaries(min, max, value, name): #-- Check if boundaries are consistent - if max != None and min != None and max < min: + if max is not None and min is not None and max < min: min, max = max, min logging.warning('Parameter %s: max < min, switched boundaries!'%(name)) - if min != None and value < min: + if min is not None and value < min: min = value logging.warning('Parameter %s: value < min, adjusted min!'%(name)) - if max != None and value > max: + if max is not None and value > max: max = value logging.warning('Parameter %s: value > max, adjusted max!'%(name)) return min, max - - if self.parameters == None: + + if self.parameters is None: #-- Create a new parameter object self.parameters = lmfit.Parameters() for i,name in enumerate(self.par_names): @@ -1327,198 +1325,198 @@ def check_boundaries(min, max, value, name): min_, max_ = check_boundaries(min[i], max[i], value[i], name) self.parameters[name].value = float(value[i]) self.parameters[name].user_value = float(value[i]) - self.parameters[name].vary = bool(vary[i]) if vary[i] != None else True + self.parameters[name].vary = bool(vary[i]) if vary[i] is not None else True self.parameters[name].min = min_ self.parameters[name].max = max_ self.parameters[name].expr = expr[i] - + def update_parameter(self, parameter=None, **kwargs): """ - Updates a specified parameter. The parameter can be given by name or by index. This + Updates a specified parameter. The parameter can be given by name or by index. This method also supports the use of min and max keywords to set the lower and upper boundary seperatly. - + Example Use: - + >>> update_parameter(parameter='parname', value=10.0, min=5.0, vary=True) >>> update_parameter(parameter=2, value=0.15, bounds=(0.10, 0.20)) """ - + if type(parameter) == int: parameter = self.parameters[self.par_names[parameter]] elif type(parameter) == str: parameter = self.parameters[parameter] - + #-- if bounds is provided, break them up in min and max. if 'bounds' in kwargs: kwargs['min'] = kwargs['bounds'][0] kwargs['max'] = kwargs['bounds'][1] - + if 'value' in kwargs: kwargs['user_value'] = kwargs['value'] - + for key in ['value', 'min', 'max', 'vary', 'expr']: if key in kwargs: setattr(parameter, key, kwargs[key]) - + def get_parameters(self, parameters=None, error='stderr', full_output=False): """ Returns the parameter values together with the errors if they exist. If No fitting has been done, or the errors could not be calculated, None is returned for the error. - + The parameters of which the settings should be returned can be given in I{parameters}. If None is given, all parameters are returned. - + @param parameters: Parameters to be returned or None for all parameters. @type parameters: array @param error: Which error to return ('stderr', 'mcerr') @type error: string @param full_output: When True, also vary, the boundaries and expression are returned @type full_output: bool - + @return: the parameter values and there errors: value, err, [vary, min, max, expr] @rtype: numpy arrays """ if type(parameters) == str: parameters = [parameters] - - pnames = parameters if parameters != None else self.par_names - + + pnames = parameters if parameters is not None else self.par_names + out = [] for name in pnames: par = self.parameters[name] - out.append([par.value,getattr(par, error), par.vary, par.min, + out.append([par.value,getattr(par, error), par.vary, par.min, par.max, par.expr]) out = np.array(out) - + if full_output: return out.T else: return out[:,0], out[:,1] - + #} - + #{ Print functions - + def param2str(self, **kwargs): """ Converts the parameter object of this model to an easy printable string, including the value, error, boundaries, if the parameter is varied, and if in the fitting process - on of the boundaries was reached. - + on of the boundaries was reached. + The error to be printed can be set with the error keyword. You can chose from the standard error: 'stderr', the monte carlo error: 'mcerr', or any of the confidence intervalls that you have calculated by coding them like: 'ci###'. Fx: 95% (sigma = 0.95) use 'ci95', for 99.7% (sigma = 0.997) use 'ci997'. Don't put decimal signs in the ci! - + The accuracy with which the parameters are printed can be set with the accuracy keyword. - And the amount of information that is printed is determined by full_output. If False, + And the amount of information that is printed is determined by full_output. If False, only parameter value and error are printed, if True also boundaries and vary are shown. - + @param accuracy: Number of decimal places to print @type accuracy: int @param error: Which error type to print ('stderr', 'mcerr' or 'ci###') @type error: string @param full_output: Amount of information to print @type full_output: bool - + @return: parameters in string format @rtype: string """ return parameters2string(self.parameters, **kwargs) - + def correl2str(self, **kwargs): """ Convert the correlations between the different parameters of this function to an easy printable string. The correlations are only printed if they are larger than a certain provided limit. And the accuracy keyword sets the amount of decimals to print - + @param accuracy: number of decimal places to print @param limit: minimum correlation value to print - + @return: correlation in string format @rtype: string """ return correlation2string(self.parameters, **kwargs) - + def ci2str(self, **kwargs): """ Convert the confidence intervals of the parameters of this model to an easy - printable string. - + printable string. + The accuracy with wich the CIs should be printed can be set with the accuracy keyword. - + @param accuracy: Number of decimal places to print @type accuracy: int - + @return: confidence intervals in string format @rtype: string """ return confidence2string(self.parameters, **kwargs) - + #} - + #{ Internal - + def __str__(self): """ String representation of the Function object """ pnames = ", ".join(self.par_names) name = "".format(self.function.__name__, pnames) return name - + #} - + class Model(object): """ Class to create a model using different L{Function}s each with their associated parameters. - This Model can then be used to fit data using the L{Minimizer} class. The Model can be + This Model can then be used to fit data using the L{Minimizer} class. The Model can be evaluated using the L{evaluate} method. Parameters can be added/updated, together with boundaries and expressions, and can be hold fixed or adjusted by changing the vary keyword in L{update_parameter}. - + Parameters for the different Functions are combined. To keep track of which parameter is which, they get a number added to the end indicating the function they belong to. For example: when a Model is created summing a gaussian function with a quadratic function. The gaussian has - parameters [a, mu, sigma, c] and the quadratic function has parameters [s0, s1, s2]. If the + parameters [a, mu, sigma, c] and the quadratic function has parameters [s0, s1, s2]. If the functions are provided in order [Gaussian, Quadratic], the parameters will be renames to: [a_0, mu_0, sigma_0, c_0] and [s0_1, s1_1, s2_1]. Keep in mind that this renaming only happens in the Model object. In the underlying Functions the parameters will keep there original name. - - The functions themselfs can be combined using a mathematical expression in the constructor. + + The functions themselfs can be combined using a mathematical expression in the constructor. If no expression is provided, the output of each function is summed up. Keep in mind that each function needs to have the same output shape:: - + Model(x) = Function1(x) + Function2(x) + ... - + The provided expression needs to be a function taking an array with the results of the Functions in the model as arguments, and having an numpy array as result. This can be done with simple I{lambda} expression or more complicated functions:: - - Model(x) = Expr( [Function1(x),Function2(x),...] ) - - The internal representation of the parameters uses a parameter object of the U{lmfit + + Model(x) = Expr( [Function1(x),Function2(x),...] ) + + The internal representation of the parameters uses a parameter object of the U{lmfit } package. No knowledge of this repersentation is required as methods for direct interaction with the parameter values and other settings are provided. If wanted, the parameters object itself can be obtained with the L{parameters} attribute. - + At the moment the use of a jacobian is not supported at the Model level as it is not possible to derive a symbolic jacobian from the provided functions. If you want to use a jacobian you will have to write a Function yourself in which you can provide a jacobian function. """ - + def __init__(self, functions=None, expr=None, resfunc=None): """ Create a new Model by providing the functions of which it consists. You can provid an expression describing how the Functions have to be combined as well. This expression needs to take the out put of the Fuctions in an array as argument, and provide a new numpy array as result. - + @param functions: A list of L{Function}s @type functions: list @param expr: An expression to combine the given functions. @@ -1532,24 +1530,24 @@ def __init__(self, functions=None, expr=None, resfunc=None): self.jacobian = None self._par_names = None self.parameters = None - + #-- Combine the parameters self.pull_parameters() - + #{ Interaction - + def evaluate(self, x, *args, **kwargs): """ Evaluate the model for the given values and optional a given parameter object. If no parameter object is given then the parameter object belonging to the model is used. - + >>> evaluate(x, parameters) >>> evaluate(x) - + @param x: the independant values for which to evaluate the model. @type x: array - + @return: Model(x) @rtype: numpy array """ @@ -1559,12 +1557,12 @@ def evaluate(self, x, *args, **kwargs): elif len(args) == 1: #-- Use the provided parameters parameters = args[0] - + #-- Update the parameters of the individual functions before calling them self.push_parameters(parameters=parameters) - + #-- For each function, read the arguments and calculate the result - if self.expr == None: + if self.expr is None: result = np.zeros(len(x)) for function in self.functions: result += function.evaluate(x, **kwargs) @@ -1573,179 +1571,179 @@ def evaluate(self, x, *args, **kwargs): for function in self.functions: result.append(function.evaluate(x, **kwargs)) result = self.expr(result) - + return result - + def evaluate_jacobian(self, x, *args): """ Not implemented! """ return [0.0 for p in self.parameters] - + def setup_parameters(self,values=None, bounds=None, vary=None, exprs=None): """ - Not implemented yet, use the L{setup_parameters} method of the Functions - themselfs, or for adjustments of a single parameter use the L{update_parameter} + Not implemented yet, use the L{setup_parameters} method of the Functions + themselfs, or for adjustments of a single parameter use the L{update_parameter} function - + Please provide feedback on redmine on how you would like to use this function!!! """ if values is None: values = [None for i in self.functions] if bounds is None: bounds = [None for i in self.functions] if vary is None: vary = [None for i in self.functions] if exprs is None: exprs = [None for i in self.functions] - + for i,func in enumerate(self.functions): func.setup_parameters(values=values[i],bounds=bounds[i],vary=vary[i],exprs=exprs[i]) - + self.pull_parameters() - + def update_parameter(self, parameter=None, **kwargs): """ Updates a specified parameter. The parameter can be given by name or by index. However, you have to be carefull when using the names. The model class changes - the names of the parameters of the underlying functions based on the order in + the names of the parameters of the underlying functions based on the order in which the functions are provided (See introduction). This method also supports the use of kwargs min and max to set the lower and upper boundary separatly. - + Example use: - + >>> update_parameter(parameter='parname_0', value=10.0, min=5.0, vary=True) >>> update_parameter(parameter=2, value=0.15, bounds=(0.10, 0.20)) """ - + if type(parameter) == int: parameter = self.parameters[self._par_names[parameter]] elif type(parameter) == str: parameter = self.parameters[parameter] - + #-- if bounds is provided, break them up in min and max. if 'bounds' in kwargs: kwargs['min'] = kwargs['bounds'][0] kwargs['max'] = kwargs['bounds'][1] - + for key in ['value', 'min', 'max', 'vary', 'expr']: if key in kwargs: setattr(parameter, key, kwargs[key]) - + self.push_parameters() - + def get_parameters(self, parameters=None, error='stderr', full_output=False): """ Returns the parameter values together with the errors if they exist. If No fitting has been done, or the errors could not be calculated, None is returned for the error. - + The parameters of which the settings should be returned can be given in I{parameters}. If None is given, all parameters are returned. - + @param parameters: Parameters to be returned or None for all parameters. @type parameters: array @param error: Which error to return ('stderr', 'mcerr') @type error: string @param full_output: When True, also vary, the boundaries and expression are returned @type full_output: bool - + @return: the parameter values and there errors: value, err, [vary, min, max, expr] @rtype: numpy arrays """ if type(parameters) == str: parameters = [parameters] - pnames = parameters if parameters != None else self.parameters.keys() - + pnames = parameters if parameters is not None else list(self.parameters.keys()) + out = [] for name in pnames: par = self.parameters[name] - out.append([par.value,getattr(par, error), par.vary, par.min, + out.append([par.value,getattr(par, error), par.vary, par.min, par.max, par.expr]) out = np.array(out) - + if full_output: return out.T else: - return out[:,0], out[:,1] - + return out[:,0], out[:,1] + #} - + #{ Print functions - + def param2str(self, **kwargs): """ Converts the parameter object of this model to an easy printable string, including the value, error, boundaries, if the parameter is varied, and if in the fitting process - on of the boundaries was reached. - + on of the boundaries was reached. + The error to be printed can be set with the error keyword. You can chose from the standard error: 'stderr', the monte carlo error: 'mcerr', or any of the confidence intervalls that you have calculated by coding them like: 'ci###'. Fx: 95% (sigma = 0.95) use 'ci95', for 99.7% (sigma = 0.997) use 'ci997'. Don't put decimal signs in the ci! - + The accuracy with which the parameters are printed can be set with the accuracy keyword. - And the amount of information that is printed is determined by full_output. If False, + And the amount of information that is printed is determined by full_output. If False, only parameter value and error are printed, if True also boundaries and vary are shown. - + @param accuracy: Number of decimal places to print @type accuracy: int @param error: Which error type to print ('stderr', 'mcerr' or 'ci###') @type error: string @param full_output: Amount of information to print @type full_output: bool - + @return: parameters in string format @rtype: string """ return parameters2string(self.parameters, **kwargs) - + def correl2str(self, **kwargs): """ Convert the correlations between the different parameters of this model to an easy printable string. The correlations are only printed if they are larger than a certain provided limit. And the accuracy keyword sets the amount of decimals to print - + @param accuracy: number of decimal places to print @param limit: minimum correlation value to print - + @return: correlation in string format @rtype: string """ return correlation2string(self.parameters, **kwargs) - + def ci2str(self, **kwargs): """ Convert the confidence intervals of the parameters of this model to an easy - printable string. - + printable string. + The accuracy with wich the CIs should be printed can be set with the accuracy keyword. - + @param accuracy: Number of decimal places to print @type accuracy: int - + @return: confidence intervals in string format @rtype: string """ return confidence2string(self.parameters, **kwargs) - + #} - + #{ Advanced attributes - + @property def par_names(self): 'get par_names' return [val for sublist in self._par_names for val in sublist] - + #@par_names.setter #def par_names(self, val): #'set par_names' #self._par_names = val - + #} - + #{ Internal - + def pull_parameters(self): """ Pulls the parameter objects from the underlying functions, and combines it to 1 parameter object. @@ -1754,99 +1752,99 @@ def pull_parameters(self): parameters = [] for func in functions: parameters.append(func.parameters) - + #-- Create new parameter object new_params = lmfit.Parameters() pnames = [] for i, params in enumerate(parameters): pname = [] - for n,par in params.items(): + for n,par in list(params.items()): pname.append(n+'_%i'%(i)) new_params.add(n+'_%i'%(i), value=par.value, vary=par.vary, min=par.min, max=par.max, expr=par.expr) pnames.append(pname) - + self._par_names = pnames self.parameters = new_params - + def push_parameters(self, parameters=None): """ - Pushes the parameters in the combined parameter object to the parameter objects of the underlying + Pushes the parameters in the combined parameter object to the parameter objects of the underlying models or functions. """ - if parameters == None: + if parameters is None: parameters = self.parameters for pnames,function in zip(self._par_names, self.functions): old_parameters = function.parameters for name in pnames: old_name = re.sub('_[0123456789]*$','',name) old_parameters[old_name] = parameters[name] - + def __str__(self): """ String representation of the Model object """ fnames = ", ".join([f.function.__name__ for f in self.functions]) - expr = self.expr if self.expr != None else "Sum()" + expr = self.expr if self.expr is not None else "Sum()" name = "" return name.format(fnames, expr) - + #} - - -class Minimizer(object): + + +class Minimizer(object): """ A minimizer class on the U{lmfit } - Python package, which provides a simple, flexible interface to non-linear - least-squares optimization, or curve fitting. The package is build around the - Levenberg-Marquardt algorithm of scipy, but 2 other minimizers: limited memory - Broyden-Fletcher-Goldfarb-Shanno and simulated annealing are partly supported as - well. For the Levenberg-Marquardt algorithm, the estimated uncertainties and + Python package, which provides a simple, flexible interface to non-linear + least-squares optimization, or curve fitting. The package is build around the + Levenberg-Marquardt algorithm of scipy, but 2 other minimizers: limited memory + Broyden-Fletcher-Goldfarb-Shanno and simulated annealing are partly supported as + well. For the Levenberg-Marquardt algorithm, the estimated uncertainties and correlation between fitted variables are calculated as well. - + This minimizer finds the best parameters to fit a given L{Model} or L{Function} to a - set of data. You only need to provide the Model and data. Weighted fitting is + set of data. You only need to provide the Model and data. Weighted fitting is supported. - + This minimizer uses the basic residual function:: - + residuals = ( data - model(x) ) * weights - - If a more advanced residual functions is required, fx when working with multi + + If a more advanced residual functions is required, fx when working with multi dimentional data, the used can specify its own residual function in the provided Function or Model, or by setting the I{resfunc} keyword. This residual function needs to be of the following call sign:: - + resfunc(synth, data, weights=None, errors=None, **kwargs) - + Functions ========= - - A L{Function} is basicaly a function together with a list of the parameters that is - needs. In the internal representation the parameters are represented as a Parameters - object. However, the user does not need to now how to handle this, and can just + + A L{Function} is basicaly a function together with a list of the parameters that is + needs. In the internal representation the parameters are represented as a Parameters + object. However, the user does not need to now how to handle this, and can just provided or retrieve the parameter values using arrays. Every fitting parameter are extensions of simple numerical variables with the following properties: - Parameters can be fixed or floated in the fit. - Parameters can be bounded with a minimum and/or maximum value. - - Parameters can be written as simple mathematical expressions of other + - Parameters can be written as simple mathematical expressions of other Parameters, using the U{asteval module }. - These values will be re-evaluated at each step in the fit, so that the - expression is satisfied. This gives a simple but flexible approach to - constraining fit variables. - + These values will be re-evaluated at each step in the fit, so that the + expression is satisfied. This gives a simple but flexible approach to + constraining fit variables. + Models ====== - + A L{Model} is a combination of Functions with its own parameter set. The Functions - are provided as a list, and a gamma function can be given to describe how the + are provided as a list, and a gamma function can be given to describe how the functions are combined. Methods to handle the parameters in a model are provided, but - the user is recommended handle the parameters at Function level as the naming of the - parameters changes at Model level. + the user is recommended handle the parameters at Function level as the naming of the + parameters changes at Model level. """ def __init__(self, x, y, model, errors=None, weights=None, resfunc=None, engine='leastsq', args=None, kws=None, grid_points=1, grid_params=None, verbose=False, **kwargs): - + self.x = x self.y = y self.model = model @@ -1857,48 +1855,48 @@ def __init__(self, x, y, model, errors=None, weights=None, resfunc=None, self.resfunc = model.resfunc self.engine = engine self._minimizers = [None] - - if weights == None: + + if weights is None: self.weights = np.ones(len(y)) # if no weigths definded set them all at one. - if resfunc != None: + if resfunc is not None: self.resfunc = resfunc # if residual function is provided, use that one. - + params = model.parameters - + #-- Setup the residual function and the lmfit.minimizer object self._setup_residual_function() self._setup_jacobian_function() fcn_args = (self.x, self.y) fcn_kws = dict(weights=self.weights, errors=self.errors) - if self.model_kws != None: + if self.model_kws is not None: fcn_kws.update(self.model_kws) - + #-- Setup the Minimizer object self._prepare_minimizer(fcn_args, fcn_kws, grid_points, grid_params) - + #-- Actual fitting self._start_minimize(engine, verbose=verbose, Dfun=self.jacobian) - + #{ Error determination - + def calculate_CI(self, parameters=None, sigmas=[0.654, 0.95, 0.997], maxiter=200, **kwargs): """ Returns the confidence intervalls of the given parameters. This function uses the F-test method described below to calculate confidence intervalls. The - I{sigma} parameter describes which confidence level is required in percentage: + I{sigma} parameter describes which confidence level is required in percentage: sigma=0.654 corresponds with the standard 1 sigma level, 0.95 with 2 sigma and 0.997 with 3 sigma. - + The output is a dictionary containing for each parameter the lower and upper boundary of the asked confidence level. If short_output is True, an array of tupples is returned instead. When only one parameter is given, and short_output is True, only a tupple of the lower and upper boundary is returned. - - The confidence intervalls calculated with this function are stored in the + + The confidence intervalls calculated with this function are stored in the Model or Function as well. - + F-test ====== The F-test is used to compare the null model, which is the best fit @@ -1906,39 +1904,39 @@ def calculate_CI(self, parameters=None, sigmas=[0.654, 0.95, 0.997], maxiter=200 parameters is fixed to a specific value. The value is changed util the differnce between chi2_0 and chi2_f can't be explained by the loss of a degree of freedom with a certain confidence. - + M{F = (chi2_f / chi2_0 - 1) * (N-P)/P_fix} - + N is the number of data-points, P the number of parameter of the null model. - P_fix is the number of fixed parameters (or to be more clear, the difference + P_fix is the number of fixed parameters (or to be more clear, the difference of number of parameters betweeen the null model and the alternate model). - + This method relies completely on the I(conf_interval) method of the lmfit - package. - + package. + @param parameters: Names of the parameters to calculate the CIs from (if None, all parameters are used) @type parameters: array of strings @param sigmas: The probability levels used to calculate the CI @type sigmas: array or float - + @return: the confidence intervals. @rtype: dict """ #-- check if a special probability function is provided. prob_func = kwargs.pop('prob_func', None) - - if parameters == None: + + if parameters is None: parameters = self.model.par_names elif type(parameters) == str: parameters = [parameters] - + if not hasattr(sigmas, "__iter__"): sigmas = [sigmas] if 'sigma' in kwargs: sigmas = [kwargs['sigma']] - print 'WARNING: sigma is depricated, use sigmas' - + print('WARNING: sigma is depricated, use sigmas') + #-- Use the adjusted conf_interval() function of the lmfit package. # We need to work on a copy of the minimizer and make a backup of # the parameter object cause lmfit messes up the minimizer when @@ -1946,39 +1944,39 @@ def calculate_CI(self, parameters=None, sigmas=[0.654, 0.95, 0.997], maxiter=200 mini = copy.copy(self.minimizer) backup = copy.deepcopy(self.model.parameters) ci = lmfit.conf_interval(mini, p_names=parameters, sigmas=sigmas, - maxiter=maxiter, prob_func=prob_func, trace=False, + maxiter=maxiter, prob_func=prob_func, trace=False, verbose=False) self.model.parameters = backup - + #-- store the CI values in the parameter object - for key, value in ci.items(): + for key, value in list(ci.items()): self.model.parameters[key].cierr.update(value) - + return ci - + def calculate_CI_2D(self, xpar=None, ypar=None, res=10, limits=None, ctype='prob'): """ Calculates the confidence interval for 2 given parameters. Both the confidence interval - calculated using the F-test method from the I{estimate_error} method, and the normal chi - squares can be obtained using the I{ctype} keyword. - + calculated using the F-test method from the I{estimate_error} method, and the normal chi + squares can be obtained using the I{ctype} keyword. + The confidence intervall is returned as a grid, together with the x and y distribution of the parameters: (x-values, y-values, grid) - + @param xname: The parameter on the x axis @param yname: The parameter on the y axis @param res: The resolution of the grid over which the confidence intervall is calculated @param limits: The upper and lower limit on the parameters for which the confidence intervall is calculated. If None, 5 times the stderr is used. - @param ctype: 'prob' for probabilities plot (using F-test), 'chi2' for chi-squares. - + @param ctype: 'prob' for probabilities plot (using F-test), 'chi2' for chi-squares. + @return: the x values, y values and confidence values @rtype: (array, array, 2d array) """ - + xn = hasattr(res,'__iter__') and res[0] or res yn = hasattr(res,'__iter__') and res[1] or res - + prob_func = None if ctype == 'chi2': def prob_func(Ndata, Nparas, new_chi, best_chi, nfix=1.): @@ -1987,31 +1985,31 @@ def prob_func(Ndata, Nparas, new_chi, best_chi, nfix=1.): x, y, grid = lmfit.conf_interval2d(self.minimizer, xpar, ypar, xn, yn, limits=limits, prob_func=prob_func) np.seterr(divide=old['divide']) - + if ctype=='prob': grid *= 100. - + return x, y, grid - - def calculate_MC_error(self, points=100, errors=None, distribution='gauss', + + def calculate_MC_error(self, points=100, errors=None, distribution='gauss', short_output=True, verbose=True, **kwargs): """ Use Monte-Carlo simulations to estimate the error of each parameter. In this - approach each datapoint is perturbed by its error, and for each new dataset - best fitting parameters are calculated. The MC error of a parameter is its + approach each datapoint is perturbed by its error, and for each new dataset + best fitting parameters are calculated. The MC error of a parameter is its deviation over all iterations (points). - + The errors supplied to this function will overwrite the errors already stored in this Minimizer. If however no errors are supplied, the already stored ones will be used. For now only symetric errors are supported. - + Currently all datapoints are considered to have a symetric gaussian distribution, but in future version more distributions will be supported. - + The MC errors are saved in the Model or Function supplied to this fitter, and can be returned as an array (short_output=True), or as a dictionary (short_output=False). - + @param points: The number of itterations @type points: int @param errors: Possible new errors on the input data. @@ -2020,40 +2018,40 @@ def calculate_MC_error(self, points=100, errors=None, distribution='gauss', @type distribution: str @param short_output: True if you want array, False if you want dictionary @type short_output: bool - + @return: The MC errors of all parameters. @rtype: array or dict """ - if errors != None: + if errors is not None: self.errors = errors - + perturb_args = dict(distribution=distribution) perturb_args.update(kwargs) params = np.empty(shape=(points), dtype=object) - + #-- perturb the data y_perturbed = self._perturb_input_data(points, **perturb_args) - - if verbose: print "MC simulations ({:.0f} points):".format(points) + + if verbose: print("MC simulations ({:.0f} points):".format(points)) if verbose: Pmeter = progress.ProgressMeter(total=points) for i, y_ in enumerate(y_perturbed): if verbose: Pmeter.update(1) - + #-- setup the fit pars = copy.deepcopy(self.model.parameters) fcn_args = (self.x, y_) fcn_kws = dict(weights=self.weights, errors=self.errors) - if self.model_kws != None: + if self.model_kws is not None: fcn_kws.update(self.model_kws) result = lmfit.Minimizer(self.residuals, pars, fcn_args=fcn_args, fcn_kws=fcn_kws, **self.fit_kws) - + #-- run the fit and save the results result.start_minimize(self.engine, Dfun=self.jacobian) params[i] = pars - + pnames, mcerrors = self._mc_error_from_parameters(params) - + if short_output: return mcerrors else: @@ -2061,17 +2059,17 @@ def calculate_MC_error(self, points=100, errors=None, distribution='gauss', for name, err in zip(pnames, mcerrors): out[name] = err return out - + #} - + #{ Plotting Functions - + def plot_fit(self, points=1000, axis=0, legend=False, **kwargs): """ Plot the original data together with the best fit results. This method has some functionality to plot multi-dimensional data, but is limited to 2D data which it - will plot consecutively allong the specified axis. - + will plot consecutively allong the specified axis. + @param points: Number of points to use when plotting the best fit model. @type points: int @param axis: In case of multi-dim input along which axis to split (0 or 1) @@ -2079,77 +2077,77 @@ def plot_fit(self, points=1000, axis=0, legend=False, **kwargs): @param legend: Display legend on plot @type legend: bool """ - + #-- transform to 2D if nessessary res = np.atleast_2d(self.y - self.model.evaluate(self.x)) x, y = np.atleast_2d(self.x), np.atleast_2d(self.y) - err = np.atleast_2d(self.errors) if self.errors != None else np.zeros_like(self.x) - + err = np.atleast_2d(self.errors) if self.errors is not None else np.zeros_like(self.x) + #-- transpose if the axis is 1 if axis == 1: x, y, res, err = x.T, y.T, res.T, err.T - + #-- create synthetic x-data xf = np.empty((len(x), points), dtype=float) for i, x_ in enumerate(x): xf[i] = np.linspace(np.min(x_), np.max(x_), points) - + #-- calculate the synthetic y-data yf = np.atleast_2d(self.model.evaluate(np.squeeze(xf))) - + #-- setup a colorMap - cmap = cm = pl.get_cmap('spectral') + cmap = cm = pl.get_cmap('spectral') norm = mpl.colors.Normalize(vmin=-len(x)-1, vmax=len(x)+1) color = mpl.cm.ScalarMappable(norm=norm, cmap=cmap) - + #-- plot the data and fit for i, (x_, y_, e_) in enumerate(zip(x, y, err)): pl.errorbar(x_, y_, yerr=e_, marker='+', ms=5, ls='', color=color.to_rgba(-i-1), label='data %i'%(i+1)) for i, (xf_, yf_) in enumerate(zip(xf, yf)): pl.plot(xf_, yf_, ls='-', color=color.to_rgba(i+1), label='fit %i'%(i+1)) - + if legend: pl.legend() - + def plot_residuals(self, axis=0, legend=False, **kwargs): """ Plot the residuals of the best fit. This method has some functionality to plot - multi-dimensional data, but is limited to 2D data which it will plot - consecutively allong the specified axis. - + multi-dimensional data, but is limited to 2D data which it will plot + consecutively allong the specified axis. + @param axis: In case of multi-dim input along which axis to split (0 or 1) @type axis: int @param legend: Display legend on plot @type legend: bool """ - + #-- transform to 2D if nessessary res = np.atleast_2d(self.y - self.model.evaluate(self.x)) x = np.atleast_2d(self.x) - err = np.atleast_2d(self.errors) if self.errors != None else np.zeros_like(self.x) + err = np.atleast_2d(self.errors) if self.errors is not None else np.zeros_like(self.x) if axis == 1: x, res, err = x.T, res.T, err.T - + #-- setup a colorMap - cmap = cm = pl.get_cmap('spectral') + cmap = cm = pl.get_cmap('spectral') norm = mpl.colors.Normalize(vmin=-len(x)-1, vmax=len(x)+1) color = mpl.cm.ScalarMappable(norm=norm, cmap=cmap) - + #-- plot residuals for i, (x_, res_, e_) in enumerate(zip(x,res, err)): pl.errorbar(x_, res_, yerr=e_, marker='+', ms=5, ls='', color=color.to_rgba(-i-1), label='data %i'%(i+1)) pl.axhline(y=0, ls=':', color='r') - + if legend: pl.legend() - + def plot_results(self, points=1000, axis=0, legend=False, **kwargs): """ - Creates a basic plot with the fit results and corresponding residuals. This + Creates a basic plot with the fit results and corresponding residuals. This method has some functionality to plot multi-dimensional data, but is up till now limited to 2D data which it will plot consecutively allong the specified axis. Based on the plot_fit and plot_residuals functions - + @param points: Number of points to use when plotting the best fit model. @type points: int @param axis: In case of multi-dim input along which axis to split (0 or 1) @@ -2163,11 +2161,11 @@ def plot_results(self, points=1000, axis=0, legend=False, **kwargs): @param title: title of the plot @type title: str """ - + xlabel = kwargs.pop('xlabel', '$X$') ylabel = kwargs.pop('ylabel', '$Y$') title = kwargs.pop('title', 'Fit Results') - + pl.subplots_adjust(wspace=0.0, hspace=0.0) ax = pl.subplot2grid((3,4), (0,0), rowspan=2, colspan=4) self.plot_fit(points=points, axis=axis, legend=legend, **kwargs) @@ -2179,13 +2177,13 @@ def plot_results(self, points=1000, axis=0, legend=False, **kwargs): for tick in ax.axes.get_xticklabels(): tick.set_visible(False) tick.set_fontsize(0.0) - + ax = pl.subplot2grid((3,4), (2,0), colspan=4) self.plot_residuals(axis=axis, legend=False, **kwargs) pl.ylabel('$O-C$') pl.xlabel(xlabel) - def plot_confidence_interval(self, xpar=None, ypar=None, res=10, limits=None, + def plot_confidence_interval(self, xpar=None, ypar=None, res=10, limits=None, ctype='prob', filled=True, **kwargs): """ Plot the confidence interval for 2 given parameters. Both the confidence @@ -2193,25 +2191,25 @@ def plot_confidence_interval(self, xpar=None, ypar=None, res=10, limits=None, and the normal chi squares can be plotted using the I{type} keyword. In case of chi2, the log10 of the chi squares is plotted to improve the clarity of the plot. - + Extra kwargs are passed to C{confourf} or C{contour}. - + @param xname: The parameter on the x axis @param yname: The parameter on the y axis @param res: The resolution of the grid over which the confidence intervall is calculated - @param limits: The upper and lower limit on the parameters for which the + @param limits: The upper and lower limit on the parameters for which the confidence intervall is calculated. If None, 5 times the stderr is used. @param ctype: 'prob' for probabilities plot (using F-test), 'chi2' for chisquare - plot. + plot. @param filled: True for filled contour plot, False for normal contour plot @param limits: The upper and lower limit on the parameters for which the confidence intervall is calculated. If None, 5 times the stderr is used. """ - - x, y, grid = self.calculate_CI_2D(xpar=xpar, ypar=ypar, res=res, + + x, y, grid = self.calculate_CI_2D(xpar=xpar, ypar=ypar, res=res, limits=limits, ctype=ctype) - + if ctype=='prob': levels = np.linspace(0,100,25) ticks = [0,20,40,60,80,100] @@ -2225,7 +2223,7 @@ def func(x, pos=None): def func(x, pos=None): return "%0.1f"%(10**x) fmtr = mpl.ticker.FuncFormatter(func) - + if filled: pl.contourf(x,y,grid,levels,**kwargs) cbar = pl.colorbar(fraction=0.08, format=fmtr, ticks=ticks) @@ -2237,7 +2235,7 @@ def func(x, pos=None): pl.plot(self.params[xname].value, self.params[yname].value, '+r', ms=10, mew=2) pl.xlabel(xname) pl.ylabel(yname) - + def plot_grid_convergence(self, xpar=None, ypar=None, chi2lim=None, chi2scale='log', show_colorbar='True', **kwargs): """ @@ -2245,24 +2243,24 @@ def plot_grid_convergence(self, xpar=None, ypar=None, chi2lim=None, chi2scale='l minimizer. The start values of the two selected parameters are plotted conected to there final best fit values, while using a color coding for the chisqr value of the fit result. - + @param xpar: The parameter on the x axis @param ypar: The parameter on the y axis @param chi2lim: Optional limit on the chi2 value (in % of the maximum chi2) @param chi2scale: Scale for the chi2 values: 'log' or 'linear' """ - + #-- Get the minimizer grid minis, models, chisqrs = self.grid - - if chi2lim != None: + + if chi2lim is not None: selected = np.where(chisqrs <= chi2lim*max(chisqrs)) models = models[selected] chisqrs = np.abs(chisqrs[selected]) models, chisqrs = models[::-1], chisqrs[::-1] if chi2scale == 'log': chisqrs = np.log10(chisqrs) - + #-- read the requested parameter values x1 = np.empty_like(chisqrs) y1 = np.empty_like(chisqrs) @@ -2275,7 +2273,7 @@ def plot_grid_convergence(self, xpar=None, ypar=None, chi2lim=None, chi2scale='l y2[i] = mod.parameters[ypar].value #-- set the colors - jet = cm = pl.get_cmap('jet') + jet = cm = pl.get_cmap('jet') cNorm = mpl.colors.Normalize(vmin=min(chisqrs), vmax=max(chisqrs)) scalarMap = mpl.cm.ScalarMappable(norm=cNorm, cmap=jet) @@ -2288,7 +2286,7 @@ def plot_grid_convergence(self, xpar=None, ypar=None, chi2lim=None, chi2scale='l scatter = pl.scatter(x2, y2, s=30, c=chisqrs, cmap=mpl.cm.jet, norm=cNorm, edgecolors=None, lw=0) pl.plot(x2[-1],y2[-1], '+r', ms=12, mew=3) - + #-- colorbar if show_colorbar: if chi2scale == 'log': @@ -2298,34 +2296,34 @@ def func(x, pos=None): else: fmtr = None pl.colorbar(scatter, fraction=0.08, format=fmtr) - + pl.xlim(min([min(x1),min(x2)]), max([max(x1),max(x2)])) pl.ylim(min([min(y1),min(y2)]), max([max(y1),max(y2)])) pl.xlabel(xpar) pl.ylabel(ypar) - + #} - + #{ Advanced attributes @property def minimizer(self): 'get minimizer' return self._minimizers[0] - + @minimizer.setter def minimizer(self, val): 'set minimizer' self._minimizers[0] = val - + @property def errors(self): 'get error' return self._error - + @errors.setter def errors(self, val): 'set error' - if val == None: + if val is None: self._error = None elif np.shape(val) == (): self._error = np.ones_like(self.x) * val @@ -2333,7 +2331,7 @@ def errors(self, val): self._error = val else: self._error = np.ones_like(self.x) * val[0] - + @property def grid(self): 'get minimizer grid' @@ -2344,47 +2342,47 @@ def grid(self): models[i].parameters = mini.params chisqrs[i] = mini.chisqr return self._minimizers, models, chisqrs - + #} - + #{ Internal Functions - + def __getattr__(self, name): "allow to reach the attributes of the best fitting minimizer directly" if hasattr(self.minimizer, name): return getattr(self.minimizer, name) else: raise AttributeError - + def _setup_residual_function(self): "Internal function to setup the residual function for the minimizer." - if self.resfunc != None: + if self.resfunc is not None: def residuals(params, x, y, weights=None, errors=None, **kwargs): synth = self.model.evaluate(x, params, **kwargs) return self.resfunc(synth, y, weights=weights, errors=errors, **kwargs) else: def residuals(params, x, y, weights=None, errors=None, **kwargs): return ( y - self.model.evaluate(x, params, **kwargs) ) * weights - + self.residuals = residuals - + def _setup_jacobian_function(self): "Internal function to setup the jacobian function for the minimizer." - if self.model.jacobian != None: + if self.model.jacobian is not None: def jacobian(params, x, y, weights=None, errors=None, **kwargs): return self.model.evaluate_jacobian(x, params, **kwargs) self.jacobian = jacobian else: self.jacobian = None - + def _prepare_minimizer(self, fcn_args, fcn_kws, grid_points=1, grid_params=None, append=False): "Internal function to prepare the minimizer" - + params = self.model.parameters grid_params = params.can_kick(pnames=grid_params) minimizers = np.empty(grid_points, dtype=Minimizer) - + if grid_points == 1 or len(grid_params) == 0: #-- just one fit minimizers[0] = lmfit.Minimizer(self.residuals, params, fcn_args=fcn_args, @@ -2400,66 +2398,66 @@ def _prepare_minimizer(self, fcn_args, fcn_kws, grid_points=1, grid_params=None, self._minimizers.append(minimizers) else: self._minimizers = minimizers - + def _start_minimize(self, engine, verbose=False, **kwargs): "Internal function that starts all minimizers one by one" #-- Possible termial output if len(self._minimizers) <= 1: verbose = False - if verbose: print "Grid Minimizer ({:.0f} points):".format(len(self._minimizers)) + if verbose: print("Grid Minimizer ({:.0f} points):".format(len(self._minimizers))) if verbose: Pmeter = progress.ProgressMeter(total=len(self._minimizers)) - + #-- Start all minimizers chisqrs = np.empty_like(self._minimizers, dtype=float) for i, mini in enumerate(self._minimizers): if verbose: Pmeter.update(1) mini.start_minimize(engine, **kwargs) chisqrs[i] = mini.chisqr - + #-- Sort on chisqr inds = chisqrs.argsort() self._minimizers = self._minimizers[inds] self.model.parameters = self._minimizers[0].params - + def _perturb_input_data(self, points, **kwargs): "Internal function to perturb the input data for MC simulations" - + #-- create iterator for the data points y_ = np.empty( (points,)+self.y.shape, dtype=float) it = np.nditer([self.y, self.errors], ['multi_index'], [['readonly'], ['readonly']]) - + #-- perturb the data while not it.finished: index = (slice(0,points),) + it.multi_index y_[index] = np.random.normal(loc=it[0], scale=it[1], size=points) it.iternext() - + return y_ - + def _mc_error_from_parameters(self, params): " Use standard deviation to get the error on a parameter " #-- calculate the std - pnames = params[0].keys() + pnames = list(params[0].keys()) errors = np.zeros((len(params), len(pnames))) for i, pars in enumerate(params): errors[i] = np.array(pars.value) errors = np.std(errors, axis=0) - + #-- store the error in the original parameter object params = self.model.parameters for name, error in zip(pnames, errors): params[name].mcerr = error - + return pnames, errors - + def __str__(self): " String representation of the Minimizer object " out = [] out.append( ('Model', str(self.model)) ) out.append( ('Data shape', str(np.array(self.x).shape)) ) - out.append( ('Errors', 'Provided' if self._error != None else 'Not Provided') ) - out.append( ('Weights', 'Provided' if self.weights != None else 'Not Provided') ) - out.append( ('Residuals', 'Standard' if self.resfunc == None else 'Custom') ) - out.append( ('Jacobian', 'Provided' if self.jacobian != None else 'Not Provided') ) + out.append( ('Errors', 'Provided' if self._error is not None else 'Not Provided') ) + out.append( ('Weights', 'Provided' if self.weights is not None else 'Not Provided') ) + out.append( ('Residuals', 'Standard' if self.resfunc is None else 'Custom') ) + out.append( ('Jacobian', 'Provided' if self.jacobian is not None else 'Not Provided') ) out.append( ('Engine', str(self.engine)) ) out.append( ("Grid points", "{:.0f}".format(len(self._minimizers))) ) temp = "{:<12s}: {:s}\n" @@ -2467,40 +2465,40 @@ def __str__(self): for s in out: outstr += temp.format(*s) return outstr.rstrip() - + #} -def minimize(x, y, model, errors=None, weights=None, resfunc=None, engine='leastsq', +def minimize(x, y, model, errors=None, weights=None, resfunc=None, engine='leastsq', args=None, kws=None, scale_covar=True, iter_cb=None, verbose=True, **fit_kws): """ Basic minimizer function using the L{Minimizer} class, find values for the parameters - so that the sum-of-squares of M{(y-model(x))} is minimized. When the fitting process - is completed, the parameters of the L{Model} are updated with the results. If the - I{leastsq} engine is used, estimated uncertainties and correlations will be saved to + so that the sum-of-squares of M{(y-model(x))} is minimized. When the fitting process + is completed, the parameters of the L{Model} are updated with the results. If the + I{leastsq} engine is used, estimated uncertainties and correlations will be saved to the L{Model} as well. Returns a I{Minimizer} object. - + Fitting engines =============== - By default, the Levenberg-Marquardt algorithm is used for fitting. While often - criticized, including the fact it finds a local minima, this approach has some + By default, the Levenberg-Marquardt algorithm is used for fitting. While often + criticized, including the fact it finds a local minima, this approach has some distinct advantages. These include being fast, and well-behaved for most curve- - fitting needs, and making it easy to estimate uncertainties for and correlations + fitting needs, and making it easy to estimate uncertainties for and correlations between pairs of fit variables. Alternative fitting algoritms are at least partially implemented, but not all functions will work with them. - - - leastsq: U{Levenberg-Marquardt - }, + + - leastsq: U{Levenberg-Marquardt + }, U{scipy.optimize.leastsq } - anneal: U{Simulated Annealing }, U{scipy.optimize.anneal } - - lbfgsb: U{quasi-Newton optimization - }, + - lbfgsb: U{quasi-Newton optimization + }, U{scipy.optimize.fmin_l_bfgs_b } - - + + @param x: the independent data array (x data) @type x: numpy array @param y: the dependent data array (y data) @@ -2508,66 +2506,66 @@ def minimize(x, y, model, errors=None, weights=None, resfunc=None, engine='least @param model: The I{Model} to fit to the data @param err: The errors on the y data, same dimentions as y @param weights: The weights given to the different y data - @param resfunc: A function to calculate the residuals, if not provided standard + @param resfunc: A function to calculate the residuals, if not provided standard residual function is used. @param engine: Which fitting engine to use: 'leastsq', 'anneal', 'lbfgsb' @param kws: Extra keyword arguments to be passed to the model @param fit_kws: Extra keyword arguments to be passed to the fitter function - + @return: (I{Parameter} object, I{Minimizer} object) - + """ - + fitter = Minimizer(x, y, model, errors=errors, weights=weights, resfunc=resfunc, engine=engine, args=args, kws=kws, scale_covar=scale_covar, iter_cb=iter_cb, verbose=verbose, **fit_kws) if fitter.message and verbose: logger.warning(fitter.message) - return fitter + return fitter def grid_minimize(x, y, model, errors=None, weights=None, resfunc=None, engine='leastsq', - args=None, kws=None, scale_covar=True, iter_cb=None, points=100, + args=None, kws=None, scale_covar=True, iter_cb=None, points=100, parameters=None, return_all=False, verbose=True, **fit_kws): - """ + """ Grid minimizer. Offers the posibility to start minimizing from a grid of starting - parameters defined by the used. The number of starting points can be specified, as - well as the parameters that are varried. For each parameter for which the start + parameters defined by the used. The number of starting points can be specified, as + well as the parameters that are varried. For each parameter for which the start value should be varied, a minimum and maximum value should be provided when setting up that parameter. The starting values are chosen randomly in the range [min,max]. The other arguments are the same as for the normal L{minimize} function. - - If parameters are provided that can not be kicked (starting value can not be varried), - they will be removed from the parameters array automaticaly. If no parameters can be - kicked, only one minimize will be performed independently from the number of points - provided. Pay attention with the vary function of the parameters, even if a parameter + + If parameters are provided that can not be kicked (starting value can not be varried), + they will be removed from the parameters array automaticaly. If no parameters can be + kicked, only one minimize will be performed independently from the number of points + provided. Pay attention with the vary function of the parameters, even if a parameter has vary = False, it will be kicked by the grid minimizer if it appears in parameters. This parameter will then be fixed at its new starting value. - + @param parameters: The parameters that you want to randomly chose in the fitting process @type parameters: array of strings @param points: The number of starting points @type points: int - @param return_all: if True, the results of all fits are returned, if False, only the + @param return_all: if True, the results of all fits are returned, if False, only the best fit is returned. @type return_all: Boolean - + @return: The best minimizer, or all minimizers as [minimizers, newmodels, chisqrs] @rtype: Minimizer object or array of [Minimizer, Model, float] """ - + fitter = Minimizer(x, y, model, errors=errors, weights=weights, resfunc=resfunc, engine=engine, args=args, kws=kws, scale_covar=scale_covar, iter_cb=iter_cb, grid_points=points, grid_params=parameters, verbose=verbose, **fit_kws) if fitter.message and verbose: logger.warning(fitter.message) - + if return_all: return fitter.grid else: return fitter - + #} #{ General purpose @@ -2575,12 +2573,12 @@ def grid_minimize(x, y, model, errors=None, weights=None, resfunc=None, engine=' def get_correlation_factor(residus, full_output=False): """ Calculate the correlation facor rho (Schwarzenberg-Czerny, 2003). - + Under white noise assumption, the residus are expected to change sign every 2 observations (rho=1). Longer distances, 2*rho, are a sign of correlation. - + The errors are then underestimated by a factor 1/sqrt(rho). - + @param residus: residus after the fit @type residus: numpy array @param full_output: if True, the groups of data with same sign will be returned @@ -2589,17 +2587,17 @@ def get_correlation_factor(residus, full_output=False): @rtype: float(,list) """ same_sign_groups = [1] - - for i in xrange(1,len(residus)): + + for i in range(1,len(residus)): if np.sign(residus[i])==np.sign(residus[i-1]): same_sign_groups[-1] += 1 else: same_sign_groups.append(0) - + rho = np.average(same_sign_groups) - + logger.debug("Correlation factor rho = %f, sqrt(rho)=%f"%(rho,np.sqrt(rho))) - + if full_output: return rho, same_sign_groups else: @@ -2624,12 +2622,12 @@ def _calc_length(par, accuracy, field=None): 'mcerrpc':3, 'cierrpc':3, 'bounds':14} - + def calculate_length(par): try: if type(par) == str: out = len(par) - elif par == None or par == np.nan or np.isposinf(par): + elif par is None or par == np.nan or np.isposinf(par): out = 3 elif np.isneginf(par): out = 4 @@ -2640,27 +2638,27 @@ def calculate_length(par): length = length + accuracy + 1 # 1 for the decimal point np.seterr(divide=old['divide']) out = int(length) - except Exception, e: + except Exception as e: logging.warning( 'Could not calculate length of: %s, type = %s, field = %s\nerror: %s'%(par, type(par), field, e)) out = 0 return out - + if type(par) == tuple: out = 0 for p in par: out += calculate_length(p) else: out = calculate_length(par) - - if field != None and field in extralen: + + if field is not None and field in extralen: return out + extralen[field] else: return out def _format_field(par, field, maxlen=10, accuracy=2): fmt = '{{:.{0}f}}'.format(accuracy) - + if field == 'name': temp = '{{:>{0}s}} ='.format(maxlen) return temp.format(par.name) @@ -2680,65 +2678,65 @@ def _format_field(par, field, maxlen=10, accuracy=2): return '(vary)' if getattr(par, field) else '(fixed)' elif field == 'expr': expr = getattr(par, field) - return 'expr = %s'%(expr) if expr != None else '' + return 'expr = %s'%(expr) if expr is not None else '' else: return '' - + def parameters2string(parameters, accuracy=2, error='stderr', output='result', **kwargs): """ Converts a parameter object to string """ out = "Parameters ({:s})\n".format(error) - + if not hasattr(output, '__itter__'): - if output == 'start': + if output == 'start': output = ['name', 'user_value', 'bounds', 'vary', 'expr'] out = "Parameters (initial)\n" - elif output == 'result': + elif output == 'result': output = ['name', 'value', error, error + 'pc'] out = "Parameters ({:s})\n".format(error) - elif output == 'full': + elif output == 'full': output = ['name', 'value', error, error + 'pc', 'bounds', 'vary', 'expr'] out = "Parameters ({:s})\n".format(error) else: output = ['name', 'value'] out = "Parameters \n" - + #-- calculate the nessessary space maxlen = np.zeros(len(output), dtype=int) for i, field in enumerate(output): max_value = 0 - for name, par in parameters.items(): + for name, par in list(parameters.items()): current = _calc_length(getattr(par, field), accuracy, field=field) if current > max_value: max_value = current maxlen[i] = max_value - + #-- create matrix with all values in requered accuracy as string - roundedvals = np.empty(shape=(len(parameters.keys()), len(output)), + roundedvals = np.empty(shape=(len(list(parameters.keys())), len(output)), dtype="|S{:.0f}".format(np.max(maxlen))) for i, (name, par) in enumerate(parameters.items()): for j, field in enumerate(output): roundedvals[i,j] = _format_field(par, field, maxlen[j], accuracy) - + #-- create the template template = ' ' for max_value in maxlen: template += "{{:<{0}s}} ".format(max_value) template += '\n' - + #-- create the output string for line in roundedvals: out += template.format(*line) - + return out.rstrip() - + def correlation2string(parameters, accuracy=3, limit=0.100): """ Converts the correlation of different parameters to string """ out = "Correlations (shown if larger as {{:.{0}f}})\n".format(accuracy).format(limit) - + #-- first select all correlations we want cor_name, cor_value = [],[] - for name1,par in parameters.items(): - for name2, corr in par.correl.items(): + for name1,par in list(parameters.items()): + for name2, corr in list(par.correl.items()): n1 = name1 + ' - ' + name2 n2 = name2 + ' - ' + name1 if corr >=limit and not n1 in cor_name and not n2 in cor_name: @@ -2747,7 +2745,7 @@ def correlation2string(parameters, accuracy=3, limit=0.100): cor_name, cor_value = np.array(cor_name, dtype=str),np.array(cor_value, dtype=float) ind = cor_value.argsort() cor_name, cor_value = cor_name[ind][::-1], cor_value[ind][::-1] - + #-- calculate nessessary space max_name = 0 max_value = 0 @@ -2755,26 +2753,26 @@ def correlation2string(parameters, accuracy=3, limit=0.100): max_name = len(name) if len(name) > max_name else max_name current = _calc_length(value, accuracy) max_value = current if current > max_value else max_value - + #-- print correlations template = ' {{name:<{0}s}} = {{value:.{1}f}}\n'.format(max_name, accuracy) for name, value in zip(cor_name, cor_value): out += template.format(name=name, value=value) - + return out.rstrip() - + def confidence2string(parameters, accuracy=4): """ Converts the confidence intervals to string """ out="Confidence Intervals\n" - - #-- find all calculated cis and the nessessary space + + #-- find all calculated cis and the nessessary space max_name = 0 max_value = 6 sigmas = [] - for name, par in parameters.items(): + for name, par in list(parameters.items()): if len(name) > max_name: max_name = len(name) - for ci in par.cierr.keys(): + for ci in list(par.cierr.keys()): if not ci in sigmas: sigmas.append(ci) current = _calc_length(par.cierr[ci][0], accuracy) if current > max_value: max_value = current @@ -2782,14 +2780,14 @@ def confidence2string(parameters, accuracy=4): if current > max_value: max_value = current sigmas.sort() sigmas.reverse() - + #-- create the output values template = "{{:.{0}f}}".format(accuracy) - cis = np.empty(shape=(len(parameters.keys()), 2*len(sigmas)+1), + cis = np.empty(shape=(len(list(parameters.keys())), 2*len(sigmas)+1), dtype="|S{:.0f}".format(max_value)) for i, (name, par) in enumerate(parameters.items()): #cis for j, sigma in enumerate(sigmas): - if sigma in par.cierr.keys(): + if sigma in list(par.cierr.keys()): cis[i,j] = template.format(par.cierr[sigma][0]) cis[i,-j-1] = template.format(par.cierr[sigma][1]) else: @@ -2797,26 +2795,26 @@ def confidence2string(parameters, accuracy=4): cis[i,-j-1] = '-'*max_value for i, (name, par) in enumerate(parameters.items()): #values cis[i,len(sigmas)] = template.format(par.value) - + template = "{:.2f}" header = np.empty(shape=2*len(sigmas)+1, dtype="|S{:.0f}".format(max_value)) for i, sigma in enumerate(sigmas): header[i] = template.format(sigma*100) header[-i-1] = template.format(sigma*100) header[len(sigmas)] = template.format(0) - + #-- create the output template = "{{:>{0}s}}% ".format(max_value-1) * (2*len(sigmas)+1) template = " {{:>{0}s}} ".format(max_name) + template +"\n" out += template.format('', *header) - + template = "{{:>{0}s}} ".format(max_value) * (2*len(sigmas)+1) template = " {{:>{0}s}}: ".format(max_name) + template +"\n" - for name, ci in zip(parameters.keys(), cis): + for name, ci in zip(list(parameters.keys()), cis): out += template.format(name, *ci) - - return out.rstrip() - + + return out.rstrip() + def plot_convergence(startpars, models, chi2s, xpar=None, ypar=None, clim=None): """ Plot the convergence path of the results from grid_minimize. @@ -2826,13 +2824,13 @@ def plot_convergence(startpars, models, chi2s, xpar=None, ypar=None, clim=None): startpars = startpars[inds] models = models[inds] chi2s = chi2s[inds] - - if clim != None: + + if clim is not None: selected = np.where(chi2s <= clim*max(chi2s)) startpars = startpars[selected] models = models[selected] chi2s = chi2s[selected] - + #-- read the requested parameter values points=len(startpars) x1 = np.zeros(points,dtype=float) @@ -2846,7 +2844,7 @@ def plot_convergence(startpars, models, chi2s, xpar=None, ypar=None, clim=None): y2[i] = models[i].parameters[ypar].value #-- set the colors - jet = cm = pl.get_cmap('jet') + jet = cm = pl.get_cmap('jet') cNorm = mpl.colors.Normalize(vmin=0, vmax=max(chi2s)) scalarMap = mpl.cm.ScalarMappable(norm=cNorm, cmap=jet) @@ -2862,7 +2860,7 @@ def plot_convergence(startpars, models, chi2s, xpar=None, ypar=None, clim=None): pl.ylim(min([min(y1),min(y2)]), max([max(y1),max(y2)])) pl.xlabel(xpar) pl.ylabel(ypar) - + #} @@ -2903,9 +2901,9 @@ def plot_convergence(startpars, models, chi2s, xpar=None, ypar=None, clim=None): frequencies = [] for i in range(9): - print "======== STEP %d ======"%(i) - - + print("======== STEP %d ======"%(i)) + + pergram = pergrams.scargle(times,residus,threads=2) frequency = pergram[0][np.argmax(pergram[1])] frequencies.append(frequency) @@ -2913,28 +2911,28 @@ def plot_convergence(startpars, models, chi2s, xpar=None, ypar=None, clim=None): e_parameters = e_sine(times,signal_,parameters) parameters,e_parameters,gain = optimize(times,signal_,parameters,'sine') frequencies = list(parameters['freq']) - + signalf = evaluate.sine(times,parameters) - - + + if i>> x = np.linspace(-5,5,1000) >>> y = evaluate('gauss',x,[1.,0.,2.,0.]) - + @parameter funcname: name of the function @type funcname: str @parameter domain: domain to evaluate onto @@ -485,7 +485,7 @@ def evaluate(funcname, domain, parameters, **kwargs): function = globals()[funcname](**kwargs) function.setup_parameters(parameters) return function.evaluate(domain) - + if __name__=="__main__": import doctest from matplotlib import pyplot as plt diff --git a/sigproc/interpol.py b/sigproc/interpol.py index 1b44b1598..398dda44e 100644 --- a/sigproc/interpol.py +++ b/sigproc/interpol.py @@ -3,10 +3,7 @@ """ import numpy as np from scipy import ndimage -import astropy.io.fits as pf -import time -import itertools -import pyfinterpol + def __df_dx(oldx,oldy,index,sharp=False): """ @@ -15,7 +12,7 @@ def __df_dx(oldx,oldy,index,sharp=False): xm1,fm1 = oldx[index-1],oldy[index-1] x ,f = oldx[index] ,oldy[index] xp1,fp1 = oldx[index+1],oldy[index+1] - + if not sharp: df_dx = 1./(xp1-xm1) * ( fp1*(x-xm1)/(xp1-x) - fm1*(xp1-x)/(x-xm1)) + \ f*(xp1-2*x+xm1) / ( (x-xm1)*(xp1-x)) @@ -32,11 +29,11 @@ def __P4(x,x0,x1): return (x-x0)**2 * (x-x1) / (x1-x0)**2 def local_interpolation(newx,oldx,oldy,full_output=False): """ A local interpolation method by a polynomial of degree 3. - + After Marc-Antoine Dupret, 2002 (extended version of PhD thesis). - + Cannot extrapolate! - + >>> np.random.seed(1114) >>> oldx = np.sort(np.random.uniform(size=10)) >>> oldy = oldx**2#np.random.uniform(size=10) @@ -44,7 +41,7 @@ def local_interpolation(newx,oldx,oldy,full_output=False): >>> newx = np.linspace(oldx.min(),oldx.max(),1000) >>> newy,disconts = local_interpolation(newx,oldx,oldy,full_output=True) >>> newy_ = local_interpolation(newx,oldx,oldy) - + >>> sharpy = newy.copy() >>> sharpy[disconts] = np.nan >>> smoothy = newy.copy() @@ -54,7 +51,7 @@ def local_interpolation(newx,oldx,oldy,full_output=False): >>> p = pl.plot(newx,smoothy,'go-',lw=2,ms=2,mec='g') >>> p = pl.plot(newx,sharpy,'ro-',lw=2,ms=2,mec='r') >>> p = pl.plot(newx,newy_,'bo--',lw=2,ms=2,mec='b') - + @param newx: new x-array to interpolate on @type newx: ndarray @param oldx: old x-array to interpolate from @@ -79,17 +76,17 @@ def local_interpolation(newx,oldx,oldy,full_output=False): if full_output: disconts = np.zeros(len(newx),bool) #disconts = np.zeros(len(newx)) - + index = -1 for i,x in enumerate(newx): index = oldx.searchsorted(x) - + #if index>=(len(oldx)-1): continue x0,f0 = oldx[index-1],oldy[index-1] x1,f1 = oldx[index],oldy[index] x2,f2 = oldx[index-2],oldy[index-2] - - #-- check sharpness of feature + + #-- check sharpness of feature #sharpness_ = 1./((f1-f0)/(x1-x0)*(x1-x2)/(f1-f2)) numerator = ((x1-x0)*(f1-f2)) denominator = ((f1-f0)*(x1-x2)) @@ -98,20 +95,20 @@ def local_interpolation(newx,oldx,oldy,full_output=False): sharpness = 0. else: sharpness = numerator/denominator - sharp = (0.2<=sharpness<=0.5) + sharp = (0.2<=sharpness<=0.5) if full_output: disconts[i] = sharp#ness#sharp #-- preliminary estimation of df/dx dfdx0 = __df_dx(oldx,oldy,index-1,sharp=sharp) dfdx1 = __df_dx(oldx,oldy,index,sharp=sharp) - - + + #-- interpolation by polynomial of degree 3 P1 = (x-x1)**2 * (2*x-3*x0+x1) / (x1-x0)**3 P2 = -(x-x0)**2 * (2*x-3*x1+x0) / (x1-x0)**3 P3 = (x-x0) * (x-x1)**2 / (x1-x0)**2 P4 = (x-x0)**2 * (x-x1) / (x1-x0)**2 - + #-- interpolating polynomial Px = f0*P1 + f1*P2 + dfdx0*P3 + dfdx1*P4 newy[i] = Px @@ -130,21 +127,21 @@ def local_interpolation_ND(newx,oldx,oldy): x0,f0 = oldx[index-1],oldy[index-1] x1,f1 = oldx[index],oldy[index] x2,f2 = oldx[index-2],oldy[index-2] - + polynomials = [] - + def create_pixeltypegrid(grid_pars,grid_data): """ Creates pixelgrid and arrays of axis values. - + Starting from: * grid_pars: 2D numpy array, 1 column per parameter, unlimited number of cols * grid_data: 2D numpy array, 1 column per variable, data corresponding to the rows in grid_pars - - The grid should be rectangular and complete, i.e. every combination of the unique values in the + + The grid should be rectangular and complete, i.e. every combination of the unique values in the parameter columns should exist. If not, a nan value will be inserted. - + @param grid_pars: Npar x Ngrid array of parameters @type grid_pars: array @param grid_data: Ndata x Ngrid array of data @@ -158,18 +155,18 @@ def create_pixeltypegrid(grid_pars,grid_data): axis_values = [uniques_[0] for uniques_ in uniques] unique_val_indices = [uniques_[1] for uniques_ in uniques] - + data_dim = np.shape(grid_data)[0] par_dims = [len(uv[0]) for uv in uniques] par_dims.append(data_dim) pixelgrid = np.ones(par_dims) - + # We put np.inf as default value. If we get an inf, that means we tried to access # a region of the pixelgrid that is not populated by the data table pixelgrid[pixelgrid==1] = np.inf - + # now populate the multiDgrid indices = [uv[1] for uv in uniques] pixelgrid[indices] = grid_data.T @@ -178,9 +175,9 @@ def create_pixeltypegrid(grid_pars,grid_data): def interpolate(p, axis_values, pixelgrid): """ Interpolates in a grid prepared by create_pixeltypegrid(). - + p is an array of parameter arrays - + @param p: Npar x Ninterpolate array @type p: array @return: Ndata x Ninterpolate array @@ -197,13 +194,13 @@ def interpolate(p, axis_values, pixelgrid): indices = np.searchsorted(av_,val) indices[indices==len(av_)] = len(av_)-1 p_.append(indices) - + #-- The type of p is changes to the same type as in axis_values to catch possible rounding errors # when comparing float64 to float32. for i, ax in enumerate(axis_values): p[i] = np.array(p[i], dtype = ax.dtype) - + #-- Convert requested parameter combination into a coordinate p_ = np.array([np.searchsorted(av_,val) for av_, val in zip(axis_values,p)]) lowervals_stepsize = np.array([[av_[p__-1], av_[p__]-av_[p__-1]] \ @@ -222,7 +219,7 @@ def interpolate(p, axis_values, pixelgrid): #import time #from doctest import testmod #testmod() - + np.random.seed(1114) oldx = np.sort(np.random.uniform(size=10)) oldy = oldx**2#np.random.uniform(size=10) @@ -230,7 +227,7 @@ def interpolate(p, axis_values, pixelgrid): newx = np.linspace(oldx.min(),oldx.max(),1000) newy,disconts = local_interpolation(newx,oldx,oldy,full_output=True) newy_ = local_interpolation(newx,oldx,oldy) - + sharpy = newy.copy() sharpy[disconts] = np.nan smoothy = newy.copy() @@ -240,5 +237,5 @@ def interpolate(p, axis_values, pixelgrid): p = pl.plot(newx,smoothy,'go-',lw=2,ms=2,mec='g') p = pl.plot(newx,sharpy,'ro-',lw=2,ms=2,mec='r') p = pl.plot(newx,newy_,'bo--',lw=2,ms=2,mec='b') - + pl.show() diff --git a/sigproc/lmfit/__init__.py b/sigproc/lmfit/__init__.py index 99da8c080..4982c2c1c 100644 --- a/sigproc/lmfit/__init__.py +++ b/sigproc/lmfit/__init__.py @@ -13,28 +13,28 @@ class with a simple, flexible approach to parameterizing a Author: Matthew Newville Center for Advanced Radiation Sources, The University of Chicago - + Changes applied to lmfit and uncertainties to make it work with sigproc: - + fixed uncertainties import to accomodate its place in the ivs repository uncertainties.umath import uncertainties -> from ivs.sigproc.lmfit import uncertainties - + uncertainties.unumpy.__init__: from uncertainties.unumpy import core -> import core uncertainties.unumpy -> ivs.sigproc.lmfit.uncertainties.unumpy - + uncertainties.unumpy.core: import uncertainties -> from ivs.sigproc.lmfit import uncertainties - + uncertainties.unumpy.ulinalg from uncertainties import __author__ -> from ivs.sigproc.lmfit.uncertainties import __author__ from uncertainties.unumpy import core -> import core - - Delete all tests as they are not nessesary at all. + + Delete all tests as they are not nessesary at all. uncertainties.unumpy.test_unumpy, uncertainties.unumpy.test_ulinalg, uncertainties.test_umath, uncertainties.test_uncertainties - + """ __version__ = '0.7.2' from .minimizer import minimize, Minimizer, MinimizerException, make_paras_and_func @@ -43,8 +43,8 @@ class with a simple, flexible approach to parameterizing a from .printfuncs import (fit_report, ci_report, report_fit, report_ci, report_errors) -from . import uncertainties -from .uncertainties import ufloat, correlated_values +import uncertainties +from uncertainties import ufloat, correlated_values #====================================================== @@ -60,7 +60,7 @@ def can_kick(self,pnames=None): Checks if the given parameters can be kicked and returns the good ones in a list. """ if pnames == None: - pnames = self.keys() + pnames = list(self.keys()) kick_pars = [] for key in pnames: if self[key].can_kick(): @@ -70,7 +70,7 @@ def can_kick(self,pnames=None): @decorators.extend(Parameters) def kick(self,pnames=None): """ - Kicks the given parameters to a new value chosen from the uniform + Kicks the given parameters to a new value chosen from the uniform distribution between max and min value. """ if pnames == None: @@ -86,7 +86,7 @@ def __getattr__(self, name): """ if name in ['name', 'value', 'min', 'max', 'vary', 'expr', 'stderr', 'mcerr', 'cierr', 'correl']: - return [getattr(p, name) for n, p in self.items()] + return [getattr(p, name) for n, p in list(self.items())] else: raise AttributeError @@ -159,14 +159,14 @@ def __getattr__(self, name): def start_minimize(self, engine, **kwargs): "Start the actual fitting" engine = engine.lower() - + # scalar minimize methods: - scal_min = dict(powel='Powell', cg='CG', newton='Newton-CG', + scal_min = dict(powel='Powell', cg='CG', newton='Newton-CG', cobyla='COBYLA', slsqp='SLSQP') if engine in ['powell', 'cg', 'newton', 'cobyla', 'slsqp']: engine = scal_min[engine] self.scalar_minimize(method=engine, **kwargs) - + # other methods elif engine == 'anneal': self.anneal(**kwargs) @@ -176,7 +176,7 @@ def start_minimize(self, engine, **kwargs): self.fmin(**kwargs) else: self.leastsq(**kwargs) - + @decorators.extend(ConfidenceInterval) def calc_all_ci(self): """ @@ -185,15 +185,15 @@ def calc_all_ci(self): """ out = {} for p in self.p_names: - + lower = self.calc_ci(p, -1) upper = self.calc_ci(p, 1) - + o = {} for s, l, u in zip(self.sigmas, lower, upper): o[s] = (l[1], u[1]) out[p] = o - + if self.trace: self.trace_dict = map_trace_to_names(self.trace_dict, self.minimizer.params) diff --git a/sigproc/lmfit/asteval.py b/sigproc/lmfit/asteval.py index 23ade8d23..6342e33eb 100644 --- a/sigproc/lmfit/asteval.py +++ b/sigproc/lmfit/asteval.py @@ -9,7 +9,7 @@ Expressions can be compiled into ast node and then evaluated later, using the current values in the """ -from __future__ import division, print_function + from sys import exc_info, stdout, version_info import ast import math @@ -65,7 +65,7 @@ def op2func(op): # holder for 'returned None' from Larch procedure class Empty: """basic empty containter""" - def __nonzero__(self): + def __bool__(self): return False ReturnedNone = Empty() @@ -134,7 +134,7 @@ def __init__(self, symtable=None, writer=None, use_numpy=True): for sym in FROM_NUMPY: if hasattr(numpy, sym): symtable[sym] = getattr(numpy, sym) - for name, sym in NUMPY_RENAMES.items(): + for name, sym in list(NUMPY_RENAMES.items()): if hasattr(numpy, sym): symtable[name] = getattr(numpy, sym) diff --git a/sigproc/lmfit/astutils.py b/sigproc/lmfit/astutils.py index 6d8b39cee..ca7551690 100644 --- a/sigproc/lmfit/astutils.py +++ b/sigproc/lmfit/astutils.py @@ -4,7 +4,7 @@ Matthew Newville , The University of Chicago """ -from __future__ import division, print_function + import ast from sys import exc_info diff --git a/sigproc/lmfit/confidence.py b/sigproc/lmfit/confidence.py index 662272d94..17c575438 100644 --- a/sigproc/lmfit/confidence.py +++ b/sigproc/lmfit/confidence.py @@ -3,7 +3,7 @@ """ Contains functions to calculate confidence intervals. """ -from __future__ import print_function + import numpy as np from scipy.stats import f from scipy.optimize import brentq @@ -118,10 +118,10 @@ def conf_interval(minimizer, p_names=None, sigmas=(0.674, 0.95, 0.997), def map_trace_to_names(trace, params): "maps trace to param names" out = {} - for name in trace.keys(): + for name in list(trace.keys()): tmp_dict = {} tmp = np.array(trace[name]) - for para_name, values in zip(params.keys() + ['prob'], tmp.T): + for para_name, values in zip(list(params.keys()) + ['prob'], tmp.T): tmp_dict[para_name] = values out[name] = tmp_dict return out @@ -137,7 +137,7 @@ def __init__(self, minimizer, p_names=None, prob_func=None, """ if p_names is None: - self.p_names = minimizer.params.keys() + self.p_names = list(minimizer.params.keys()) else: self.p_names = p_names @@ -198,7 +198,7 @@ def calc_ci(self, para, direction): calc_prob = lambda val, prob: self.calc_prob(para, val, prob) if self.trace: - x = [i.value for i in self.minimizer.params.values()] + x = [i.value for i in list(self.minimizer.params.values())] self.trace_dict[para.name].append(x + [0]) para.vary = False @@ -268,7 +268,7 @@ def calc_prob(self, para, val, offset=0., restore=True): out.chisqr, self.best_chi) if self.trace: - x = [i.value for i in out.params.values()] + x = [i.value for i in list(out.params.values())] self.trace_dict[para.name].append(x + [prob]) return prob - offset diff --git a/sigproc/lmfit/minimizer.py b/sigproc/lmfit/minimizer.py index f8d079624..1b58aa87e 100644 --- a/sigproc/lmfit/minimizer.py +++ b/sigproc/lmfit/minimizer.py @@ -34,7 +34,7 @@ from .parameter import Parameter, Parameters # use locally modified version of uncertainties package -from . import uncertainties +import uncertainties def asteval_with_uncertainties(*vals, **kwargs): """ @@ -73,7 +73,7 @@ def eval_stderr(obj, uvars, _names, _pars, _asteval): uval = wrap_ueval(*uvars, _obj=obj, _names=_names, _pars=_pars, _asteval=_asteval) try: - obj.stderr = uval.std_dev() + obj.stderr = uval.std_dev except: obj.stderr = 0 @@ -232,7 +232,7 @@ def prepare_fit(self, params=None): self.var_map = [] self.vars = [] self.vmin, self.vmax = [], [] - for name, par in self.params.items(): + for name, par in list(self.params.items()): if par.expr is not None: par.ast = self.asteval.parse(par.expr) check_ast_errors(self.asteval.error) @@ -430,7 +430,7 @@ def leastsq(self, **kws): except (LinAlgError, ValueError): cov = None - for par in self.params.values(): + for par in list(self.params.values()): par.stderr, par.correl = 0, None self.covar = cov @@ -458,7 +458,7 @@ def leastsq(self, **kws): par.correl[varn2] = (cov[ivar, jvar]/ (par.stderr * sqrt(cov[jvar, jvar]))) - for pname, par in self.params.items(): + for pname, par in list(self.params.items()): eval_stderr(par, uvars, self.var_map, self.params, self.asteval) @@ -466,7 +466,7 @@ def leastsq(self, **kws): for v, nam in zip(uvars, self.var_map): self.asteval.symtable[nam] = v.nominal_value - for par in self.params.values(): + for par in list(self.params.values()): if hasattr(par, 'ast'): delattr(par, 'ast') return self.success @@ -502,13 +502,13 @@ def minimize(fcn, params, method='leastsq', args=None, kws=None, else: # if scalar_minimize() is supported and method is in list, use it. if HAS_SCALAR_MIN: - for name, method in _scalar_methods.items(): + for name, method in list(_scalar_methods.items()): if meth.startswith(name): fitfunction = fitter.scalar_minimize kwargs = dict(method=method) # look for other built-in methods if fitfunction is None: - for name, method in _fitmethods.items(): + for name, method in list(_fitmethods.items()): if meth.startswith(name): fitfunction = getattr(fitter, method) if fitfunction is not None: @@ -541,7 +541,7 @@ def make_paras_and_func(fcn, x0, used_kwargs=None): p.add(args[0][i], val) if used_kwargs: - for arg, val in used_kwargs.items(): + for arg, val in list(used_kwargs.items()): p.add(arg, val) else: used_kwargs = {} @@ -550,13 +550,10 @@ def func(para): "wrapped func" kwdict = {} - for arg in used_kwargs.keys(): + for arg in list(used_kwargs.keys()): kwdict[arg] = para[arg].value vals = [para[i].value for i in p] return fcn(*vals[:len(x0)], **kwdict) return p, func - - - diff --git a/sigproc/lmfit/ordereddict.py b/sigproc/lmfit/ordereddict.py index 7242b5060..a5b896ddc 100644 --- a/sigproc/lmfit/ordereddict.py +++ b/sigproc/lmfit/ordereddict.py @@ -70,9 +70,9 @@ def popitem(self, last=True): if not self: raise KeyError('dictionary is empty') if last: - key = reversed(self).next() + key = next(reversed(self)) else: - key = iter(self).next() + key = next(iter(self)) value = self.pop(key) return key, value @@ -101,7 +101,7 @@ def keys(self): def __repr__(self): if not self: return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, self.items()) + return '%s(%r)' % (self.__class__.__name__, list(self.items())) def copy(self): return self.__class__(self) @@ -117,7 +117,7 @@ def __eq__(self, other): if isinstance(other, OrderedDict): if len(self) != len(other): return False - for p, q in zip(self.items(), other.items()): + for p, q in zip(list(self.items()), list(other.items())): if p != q: return False return True diff --git a/sigproc/lmfit/parameter.py b/sigproc/lmfit/parameter.py index d0babee8d..54ff6513c 100644 --- a/sigproc/lmfit/parameter.py +++ b/sigproc/lmfit/parameter.py @@ -6,10 +6,10 @@ try: from collections import OrderedDict except ImportError: - from ordereddict import OrderedDict + from .ordereddict import OrderedDict import re -from . import uncertainties +import uncertainties RESERVED_WORDS = ('and', 'as', 'assert', 'break', 'class', 'continue', @@ -184,7 +184,7 @@ def _getval(self): except(TypeError, ValueError): self._val = nan return self._val - + @property def value(self): "get value" @@ -210,7 +210,7 @@ def __pos__(self): "positive" return +self._getval() - def __nonzero__(self): + def __bool__(self): "not zero" return self._getval() != 0 @@ -220,7 +220,7 @@ def __int__(self): def __long__(self): "long" - return long(self._getval()) + return int(self._getval()) def __float__(self): "float" @@ -323,4 +323,3 @@ def isParameter(x): "test for Parameter-ness" return (isinstance(x, Parameter) or x.__class__.__name__ == 'Parameter') - diff --git a/sigproc/lmfit/printfuncs.py b/sigproc/lmfit/printfuncs.py index 034857f29..db688ebae 100644 --- a/sigproc/lmfit/printfuncs.py +++ b/sigproc/lmfit/printfuncs.py @@ -17,7 +17,7 @@ def report_errors(params, modelpars=None, show_correl=True): """ -from __future__ import print_function + def fit_report(params, modelpars=None, show_correl=True, min_correl=0.1): @@ -78,7 +78,7 @@ def fit_report(params, modelpars=None, show_correl=True, min_correl=0.1): if name != name2 and name2 in par.correl: correls["%s, %s" % (name, name2)] = par.correl[name2] - sort_correl = sorted(correls.items(), key=lambda it: abs(it[1])) + sort_correl = sorted(list(correls.items()), key=lambda it: abs(it[1])) sort_correl.reverse() for name, val in sort_correl: if abs(val) < min_correl: @@ -103,7 +103,7 @@ def ci_report(ci): convp = lambda x: ("%.2f" % (x[0]*100))+'%' conv = lambda x: "%.5f" % x[1] title_shown = False - for name, row in ci.items(): + for name, row in list(ci.items()): if not title_shown: add("".join([''.rjust(maxlen)]+[i.rjust(10) for i in map(convp, row)])) title_shown = True diff --git a/sigproc/lmfit/uncertainties/__init__.py b/sigproc/lmfit/uncertainties/__init__.py deleted file mode 100644 index 2bc4c6ebe..000000000 --- a/sigproc/lmfit/uncertainties/__init__.py +++ /dev/null @@ -1,1645 +0,0 @@ -#!! Whenever the documentation below is updated, setup.py should be -# checked for consistency. - -''' -Calculations with full error propagation for quantities with uncertainties. -Derivatives can also be calculated. - -Web user guide: http://packages.python.org/uncertainties/. - -Example of possible calculation: (0.2 +/- 0.01)**2 = 0.04 +/- 0.004. - -Correlations between expressions are correctly taken into account (for -instance, with x = 0.2+/-0.01, 2*x-x-x is exactly zero, as is y-x-x -with y = 2*x). - -Examples: - - import uncertainties - from uncertainties import ufloat - from uncertainties.umath import * # sin(), etc. - - # Mathematical operations: - x = ufloat((0.20, 0.01)) # x = 0.20+/-0.01 - x = ufloat("0.20+/-0.01") # Other representation - x = ufloat("0.20(1)") # Other representation - x = ufloat("0.20") # Implicit uncertainty of +/-1 on the last digit - print x**2 # Square: prints "0.04+/-0.004" - print sin(x**2) # Prints "0.0399...+/-0.00399..." - - print x.std_score(0.17) # Prints "-3.0": deviation of -3 sigmas - - # Access to the nominal value, and to the uncertainty: - square = x**2 # Square - print square # Prints "0.04+/-0.004" - print square.nominal_value # Prints "0.04" - print square.std_dev() # Prints "0.004..." - - print square.derivatives[x] # Partial derivative: 0.4 (= 2*0.20) - - # Correlations: - u = ufloat((1, 0.05), "u variable") # Tag - v = ufloat((10, 0.1), "v variable") - sum_value = u+v - - u.set_std_dev(0.1) # Standard deviations can be updated on the fly - print sum_value - u - v # Prints "0.0" (exact result) - - # List of all sources of error: - print sum_value # Prints "11+/-0.1414..." - for (var, error) in sum_value.error_components().iteritems(): - print "%s: %f" % (var.tag, error) # Individual error components - - # Covariance matrices: - cov_matrix = uncertainties.covariance_matrix([u, v, sum_value]) - print cov_matrix # 3x3 matrix - - # Correlated variables can be constructed from a covariance matrix, if - # NumPy is available: - (u2, v2, sum2) = uncertainties.correlated_values([1, 10, 11], - cov_matrix) - print u2 # Value and uncertainty of u: correctly recovered (1+/-0.1) - print uncertainties.covariance_matrix([u2, v2, sum2]) # == cov_matrix - -- The main function provided by this module is ufloat, which creates -numbers with uncertainties (Variable objects). Variable objects can -be used as if they were regular Python numbers. The main attributes -and methods of Variable objects are defined in the documentation of -the Variable class. - -- Valid operations on numbers with uncertainties include basic -mathematical functions (addition, etc.). - -Most operations from the standard math module (sin, etc.) can be applied -on numbers with uncertainties by using their generalization from the -uncertainties.umath module: - - from uncertainties.umath import sin - print sin(ufloat("1+/-0.01")) # 0.841...+/-0.005... - print sin(1) # umath.sin() also works on floats, exactly like math.sin() - -Logical operations (>, ==, etc.) are also supported. - -Basic operations on NumPy arrays or matrices of numbers with -uncertainties can be performed: - - 2*numpy.array([ufloat((1, 0.01)), ufloat((2, 0.1))]) - -More complex operations on NumPy arrays can be performed through the -dedicated uncertainties.unumpy sub-module (see its documentation). - -Calculations that are performed through non-Python code (Fortran, C, -etc.) can handle numbers with uncertainties instead of floats through -the provided wrap() wrapper: - - import uncertainties - - # wrapped_f is a version of f that can take arguments with - # uncertainties, even if f only takes floats: - wrapped_f = uncertainties.wrap(f) - -If some derivatives of the wrapped function f are known (analytically, -or numerically), they can be given to wrap()--see the documentation -for wrap(). - -- Utility functions are also provided: the covariance matrix between -random variables can be calculated with covariance_matrix(), or used -as input for the definition of correlated quantities (correlated_values() -function--defined only if the NumPy module is available). - -- Mathematical expressions involving numbers with uncertainties -generally return AffineScalarFunc objects, which also print as a value -with uncertainty. Their most useful attributes and methods are -described in the documentation for AffineScalarFunc. Note that -Variable objects are also AffineScalarFunc objects. UFloat is an -alias for AffineScalarFunc, provided as a convenience: testing whether -a value carries an uncertainty handled by this module should be done -with insinstance(my_value, UFloat). - -- Mathematically, numbers with uncertainties are, in this package, -probability distributions. These probabilities are reduced to two -numbers: a nominal value and an uncertainty. Thus, both variables -(Variable objects) and the result of mathematical operations -(AffineScalarFunc objects) contain these two values (respectively in -their nominal_value attribute and through their std_dev() method). - -The uncertainty of a number with uncertainty is simply defined in -this package as the standard deviation of the underlying probability -distribution. - -The numbers with uncertainties manipulated by this package are assumed -to have a probability distribution mostly contained around their -nominal value, in an interval of about the size of their standard -deviation. This should cover most practical cases. A good choice of -nominal value for a number with uncertainty is thus the median of its -probability distribution, the location of highest probability, or the -average value. - -- When manipulating ensembles of numbers, some of which contain -uncertainties, it can be useful to access the nominal value and -uncertainty of all numbers in a uniform manner: - - x = ufloat("3+/-0.1") - print nominal_value(x) # Prints 3 - print std_dev(x) # Prints 0.1 - print nominal_value(3) # Prints 3: nominal_value works on floats - print std_dev(3) # Prints 0: std_dev works on floats - -- Probability distributions (random variables and calculation results) -are printed as: - - nominal value +/- standard deviation - -but this does not imply any property on the nominal value (beyond the -fact that the nominal value is normally inside the region of high -probability density), or that the probability distribution of the -result is symmetrical (this is rarely strictly the case). - -- Linear approximations of functions (around the nominal values) are -used for the calculation of the standard deviation of mathematical -expressions with this package. - -The calculated standard deviations and nominal values are thus -meaningful approximations as long as the functions involved have -precise linear expansions in the region where the probability -distribution of their variables is the largest. It is therefore -important that uncertainties be small. Mathematically, this means -that the linear term of functions around the nominal values of their -variables should be much larger than the remaining higher-order terms -over the region of significant probability. - -For instance, sin(0+/-0.01) yields a meaningful standard deviation -since it is quite linear over 0+/-0.01. However, cos(0+/-0.01) yields -an approximate standard deviation of 0 (because the cosine is not well -approximated by a line around 0), which might not be precise enough -for all applications. - -- Comparison operations (>, ==, etc.) on numbers with uncertainties -have a pragmatic semantics, in this package: numbers with -uncertainties can be used wherever Python numbers are used, most of -the time with a result identical to the one that would be obtained -with their nominal value only. However, since the objects defined in -this module represent probability distributions and not pure numbers, -comparison operator are interpreted in a specific way. - -The result of a comparison operation ("==", ">", etc.) is defined so as -to be essentially consistent with the requirement that uncertainties -be small: the value of a comparison operation is True only if the -operation yields True for all infinitesimal variations of its random -variables, except, possibly, for an infinitely small number of cases. - -Example: - - "x = 3.14; y = 3.14" is such that x == y - -but - - x = ufloat((3.14, 0.01)) - y = ufloat((3.14, 0.01)) - -is not such that x == y, since x and y are independent random -variables that almost never give the same value. However, x == x -still holds. - -The boolean value (bool(x), "if x...") of a number with uncertainty x -is the result of x != 0. - -- The uncertainties package is for Python 2.5 and above. - -- This package contains tests. They can be run either manually or -automatically with the nose unit testing framework (nosetests). - -(c) 2009-2013 by Eric O. LEBIGOT (EOL) . -Please send feature requests, bug reports, or feedback to this address. - -Please support future development by donating $5 or more through PayPal! - -This software is released under a dual license. (1) The BSD license. -(2) Any other license, as long as it is obtained from the original -author.''' - -# The idea behind this module is to replace the result of mathematical -# operations by a local approximation of the defining function. For -# example, sin(0.2+/-0.01) becomes the affine function -# (AffineScalarFunc object) whose nominal value is sin(0.2) and -# whose variations are given by sin(0.2+delta) = 0.98...*delta. -# Uncertainties can then be calculated by using this local linear -# approximation of the original function. - -from __future__ import division # Many analytical derivatives depend on this - -import re -import math -from math import sqrt, log # Optimization: no attribute look-up -import copy -import warnings - -# Numerical version: -__version_info__ = (1, 9) -__version__ = '.'.join(map(str, __version_info__)) - -__author__ = 'Eric O. LEBIGOT (EOL) ' - -# Attributes that are always exported (some other attributes are -# exported only if the NumPy module is available...): -__all__ = [ - - # All sub-modules and packages are not imported by default, - # in particular because NumPy might be unavailable. - 'ufloat', # Main function: returns a number with uncertainty - - # Uniform access to nominal values and standard deviations: - 'nominal_value', - 'std_dev', - - # Utility functions (more are exported if NumPy is present): - 'covariance_matrix', - - # Class for testing whether an object is a number with - # uncertainty. Not usually created by users (except through the - # Variable subclass), but possibly manipulated by external code - # ['derivatives()' method, etc.]. - 'UFloat', - - # Wrapper for allowing non-pure-Python function to handle - # quantities with uncertainties: - 'wrap', - - # The documentation for wrap() indicates that numerical - # derivatives are calculated through partial_derivative(). The - # user might also want to change the size of the numerical - # differentiation step. - 'partial_derivative' - ] - -############################################################################### - -def set_doc(doc_string): - """ - Decorator function that sets the docstring to the given text. - - It is useful for functions whose docstring is calculated - (including string substitutions). - """ - def set_doc_string(func): - func.__doc__ = doc_string - return func - return set_doc_string - -# Some types known to not depend on Variable objects are put in -# CONSTANT_TYPES. The most common types can be put in front, as this -# may slightly improve the execution speed. -CONSTANT_TYPES = (float, int, complex) # , long) - -############################################################################### -# Utility for issuing deprecation warnings - -def deprecation(message): - ''' - Warns the user with the given message, by issuing a - DeprecationWarning. - ''' - warnings.warn(message, DeprecationWarning, stacklevel=2) - - -############################################################################### - -## Definitions that depend on the availability of NumPy: - - -try: - import numpy -except ImportError: - pass -else: - - # NumPy numbers do not depend on Variable objects: - CONSTANT_TYPES += (numpy.number,) - - # Entering variables as a block of correlated values. Only available - # if NumPy is installed. - - #! It would be possible to dispense with NumPy, but a routine should be - # written for obtaining the eigenvectors of a symmetric matrix. See - # for instance Numerical Recipes: (1) reduction to tri-diagonal - # [Givens or Householder]; (2) QR / QL decomposition. - - def correlated_values(nom_values, covariance_mat, tags=None): - """ - Returns numbers with uncertainties (AffineScalarFunc objects) - that correctly reproduce the given covariance matrix, and have - the given (float) values as their nominal value. - - The correlated_values_norm() function returns the same result, - but takes a correlation matrix instead of a covariance matrix. - - The list of values and the covariance matrix must have the - same length, and the matrix must be a square (symmetric) one. - - The numbers with uncertainties returned depend on newly - created, independent variables (Variable objects). - - If 'tags' is not None, it must list the tag of each new - independent variable. - - nom_values -- sequence with the nominal (real) values of the - numbers with uncertainties to be returned. - - covariance_mat -- full covariance matrix of the returned - numbers with uncertainties (not the statistical correlation - matrix, i.e., not the normalized covariance matrix). For - example, the first element of this matrix is the variance of - the first returned number with uncertainty. - """ - - # If no tags were given, we prepare tags for the newly created - # variables: - if tags is None: - tags = (None,) * len(nom_values) - - # The covariance matrix is diagonalized in order to define - # the independent variables that model the given values: - - (variances, transform) = numpy.linalg.eigh(covariance_mat) - - # Numerical errors might make some variances negative: we set - # them to zero: - variances[variances < 0] = 0. - - # Creation of new, independent variables: - - # We use the fact that the eigenvectors in 'transform' are - # special: 'transform' is unitary: its inverse is its transpose: - - variables = tuple( - # The variables represent "pure" uncertainties: - Variable(0, sqrt(variance), tag) - for (variance, tag) in zip(variances, tags)) - - # Representation of the initial correlated values: - values_funcs = tuple( - AffineScalarFunc(value, dict(zip(variables, coords))) - for (coords, value) in zip(transform, nom_values)) - - return values_funcs - - __all__.append('correlated_values') - - def correlated_values_norm(values_with_std_dev, correlation_mat, - tags=None): - ''' - Returns correlated values like correlated_values(), but takes - instead as input: - - - nominal (float) values along with their standard deviation, and - - - a correlation matrix (i.e. a normalized covariance matrix - normalized with individual standard deviations). - - values_with_std_dev -- sequence of (nominal value, standard - deviation) pairs. The returned, correlated values have these - nominal values and standard deviations. - - correlation_mat -- correlation matrix (i.e. the normalized - covariance matrix, a matrix with ones on its diagonal). - ''' - - (nominal_values, std_devs) = numpy.transpose(values_with_std_dev) - - return correlated_values( - nominal_values, - correlation_mat*std_devs*std_devs[numpy.newaxis].T, - tags) - - __all__.append('correlated_values_norm') - -############################################################################### - -# Mathematical operations with local approximations (affine scalar -# functions) - -class NotUpcast(Exception): - 'Raised when an object cannot be converted to a number with uncertainty' - -def to_affine_scalar(x): - """ - Transforms x into a constant affine scalar function - (AffineScalarFunc), unless it is already an AffineScalarFunc (in - which case x is returned unchanged). - - Raises an exception unless 'x' belongs to some specific classes of - objects that are known not to depend on AffineScalarFunc objects - (which then cannot be considered as constants). - """ - - if isinstance(x, AffineScalarFunc): - return x - - #! In Python 2.6+, numbers.Number could be used instead, here: - if isinstance(x, CONSTANT_TYPES): - # No variable => no derivative to define: - return AffineScalarFunc(x, {}) - - # Case of lists, etc. - raise NotUpcast("%s cannot be converted to a number with" - " uncertainty" % type(x)) - -def partial_derivative(f, param_num): - """ - Returns a function that numerically calculates the partial - derivative of function f with respect to its argument number - param_num. - - The step parameter represents the shift of the parameter used in - the numerical approximation. - """ - - def partial_derivative_of_f(*args, **kws): - """ - Partial derivative, calculated with the (-epsilon, +epsilon) - method, which is more precise than the (0, +epsilon) method. - """ - # f_nominal_value = f(*args) - param_kw = None - if '__param__kw__' in kws: - param_kw = kws.pop('__param__kw__') - shifted_args = list(args) # Copy, and conversion to a mutable - shifted_kws = {} - for k, v in kws.items(): - shifted_kws[k] = v - step = 1.e-8 - if param_kw in shifted_kws: - step = step*abs(shifted_kws[param_kw]) - elif param_num < len(shifted_args): - # The step is relative to the parameter being varied, so that - # shsifting it does not suffer from finite precision: - step = step*abs(shifted_args[param_num]) - - if param_kw in shifted_kws: - shifted_kws[param_kw] += step - elif param_num < len(shifted_args): - shifted_args[param_num] += step - - shifted_f_plus = f(*shifted_args, **shifted_kws) - - if param_kw in shifted_kws: - shifted_kws[param_kw] -= 2*step - elif param_num < len(shifted_args): - shifted_args[param_num] -= 2*step - shifted_f_minus = f(*shifted_args, **shifted_kws) - - return (shifted_f_plus - shifted_f_minus)/2/step - - return partial_derivative_of_f - -class NumericalDerivatives(object): - """ - Convenient access to the partial derivatives of a function, - calculated numerically. - """ - # This is not a list because the number of arguments of the - # function is not known in advance, in general. - - def __init__(self, function): - """ - 'function' is the function whose derivatives can be computed. - """ - self._function = function - - def __getitem__(self, n): - """ - Returns the n-th numerical derivative of the function. - """ - return partial_derivative(self._function, n) - -def wrap(f, derivatives_iter=None): - """ - Wraps a function f into a function that also accepts numbers with - uncertainties (UFloat objects) and returns a number with - uncertainties. Doing so may be necessary when function f cannot - be expressed analytically (with uncertainties-compatible operators - and functions like +, *, umath.sin(), etc.). - - f must return a scalar (not a list, etc.). - - In the wrapped function, the standard Python scalar arguments of f - (float, int, etc.) can be replaced by numbers with - uncertainties. The result will contain the appropriate - uncertainty. - - If no argument to the wrapped function has an uncertainty, f - simply returns its usual, scalar result. - - If supplied, derivatives_iter can be an iterable that generally - contains functions; each successive function is the partial - derivative of f with respect to the corresponding variable (one - function for each argument of f, which takes as many arguments as - f). If instead of a function, an element of derivatives_iter - contains None, then it is automatically replaced by the relevant - numerical derivative; this can be used for non-scalar arguments of - f (like string arguments). - - If derivatives_iter is None, or if derivatives_iter contains a - fixed (and finite) number of elements, then any missing derivative - is calculated numerically. - - An infinite number of derivatives can be specified by having - derivatives_iter be an infinite iterator; this can for instance - be used for specifying the derivatives of functions with a - undefined number of argument (like sum(), whose partial - derivatives all return 1). - - Example (for illustration purposes only, as - uncertainties.umath.sin() runs faster than the examples that - follow): wrap(math.sin) is a sine function that can be applied to - numbers with uncertainties. Its derivative will be calculated - numerically. wrap(math.sin, [None]) would have produced the same - result. wrap(math.sin, [math.cos]) is the same function, but with - an analytically defined derivative. - """ - - if derivatives_iter is None: - derivatives_iter = NumericalDerivatives(f) - else: - # Derivatives that are not defined are calculated numerically, - # if there is a finite number of them (the function lambda - # *args: fsum(args) has a non-defined number of arguments, as - # it just performs a sum): - try: # Is the number of derivatives fixed? - len(derivatives_iter) - except TypeError: - pass - else: - derivatives_iter = [ - partial_derivative(f, k) if derivative is None - else derivative - for (k, derivative) in enumerate(derivatives_iter)] - - #! Setting the doc string after "def f_with...()" does not - # seem to work. We define it explicitly: - @set_doc("""\ - Version of %s(...) that returns an affine approximation - (AffineScalarFunc object), if its result depends on variables - (Variable objects). Otherwise, returns a simple constant (when - applied to constant arguments). - - Warning: arguments of the function that are not AffineScalarFunc - objects must not depend on uncertainties.Variable objects in any - way. Otherwise, the dependence of the result in - uncertainties.Variable objects will be incorrect. - - Original documentation: - %s""" % (f.__name__, f.__doc__)) - def f_with_affine_output(*args, **kwargs): - # Can this function perform the calculation of an - # AffineScalarFunc (or maybe float) result? - try: - old_funcs = map(to_affine_scalar, args) - aff_funcs = [to_affine_scalar(a) for a in args] - aff_kws = kwargs - aff_varkws = [] - for key, val in kwargs.items(): - if isinstance(val, Variable): - aff_kws[key] = to_affine_scalar(val) - aff_varkws.append(key) - - except NotUpcast: - - # This function does not know how to itself perform - # calculations with non-float-like arguments (as they - # might for instance be objects whose value really changes - # if some Variable objects had different values): - - # Is it clear that we can't delegate the calculation? - - if any(isinstance(arg, AffineScalarFunc) for arg in args): - # This situation arises for instance when calculating - # AffineScalarFunc(...)*numpy.array(...). In this - # case, we must let NumPy handle the multiplication - # (which is then performed element by element): - return NotImplemented - else: - # If none of the arguments is an AffineScalarFunc, we - # can delegate the calculation to the original - # function. This can be useful when it is called with - # only one argument (as in - # numpy.log10(numpy.ndarray(...)): - return f(*args, **kwargs) - - ######################################## - # Nominal value of the constructed AffineScalarFunc: - args_values = [e.nominal_value for e in aff_funcs] - kw_values = {} - for key, val in aff_kws.items(): - kw_values[key] = val - if key in aff_varkws: - kw_values[key] = val.nominal_value - f_nominal_value = f(*args_values, **kw_values) - - ######################################## - - # List of involved variables (Variable objects): - variables = set() - for expr in aff_funcs: - variables |= set(expr.derivatives) - for vname in aff_varkws: - variables |= set(aff_kws[vname].derivatives) - ## It is sometimes useful to only return a regular constant: - - # (1) Optimization / convenience behavior: when 'f' is called - # on purely constant values (e.g., sin(2)), there is no need - # for returning a more complex AffineScalarFunc object. - - # (2) Functions that do not return a "float-like" value might - # not have a relevant representation as an AffineScalarFunc. - # This includes boolean functions, since their derivatives are - # either 0 or are undefined: they are better represented as - # Python constants than as constant AffineScalarFunc functions. - - if not variables or isinstance(f_nominal_value, bool): - return f_nominal_value - - # The result of 'f' does depend on 'variables'... - - ######################################## - - # Calculation of the derivatives with respect to the arguments - # of f (aff_funcs): - - # The chain rule is applied. This is because, in the case of - # numerical derivatives, it allows for a better-controlled - # numerical stability than numerically calculating the partial - # derivatives through '[f(x + dx, y + dy, ...) - - # f(x,y,...)]/da' where dx, dy,... are calculated by varying - # 'a'. In fact, it is numerically better to control how big - # (dx, dy,...) are: 'f' is a simple mathematical function and - # it is possible to know how precise the df/dx are (which is - # not possible with the numerical df/da calculation above). - - # We use numerical derivatives, if we don't already have a - # list of derivatives: - - #! Note that this test could be avoided by requiring the - # caller to always provide derivatives. When changing the - # functions of the math module, this would force this module - # to know about all the math functions. Another possibility - # would be to force derivatives_iter to contain, say, the - # first 3 derivatives of f. But any of these two ideas has a - # chance to break, one day... (if new functions are added to - # the math module, or if some function has more than 3 - # arguments). - - derivatives_wrt_args = [] - for (arg, derivative) in zip(aff_funcs, derivatives_iter): - derivatives_wrt_args.append(derivative(*args_values, **aff_kws) - if arg.derivatives - else 0) - - - kws_values = [] - for vname in aff_varkws: - kws_values.append( aff_kws[vname].nominal_value) - for (vname, derivative) in zip(aff_varkws, derivatives_iter): - derivatives_wrt_args.append(derivative(__param__kw__=vname, - **kw_values) - if aff_kws[vname].derivatives - else 0) - - ######################################## - # Calculation of the derivative of f with respect to all the - # variables (Variable) involved. - - # Initial value (is updated below): - derivatives_wrt_vars = dict((var, 0.) for var in variables) - - # The chain rule is used (we already have - # derivatives_wrt_args): - - for (func, f_derivative) in zip(aff_funcs, derivatives_wrt_args): - for (var, func_derivative) in func.derivatives.items(): - derivatives_wrt_vars[var] += f_derivative * func_derivative - - for (vname, f_derivative) in zip(aff_varkws, derivatives_wrt_args): - func = aff_kws[vname] - for (var, func_derivative) in func.derivatives.items(): - derivatives_wrt_vars[var] += f_derivative * func_derivative - - # The function now returns an AffineScalarFunc object: - return AffineScalarFunc(f_nominal_value, derivatives_wrt_vars) - - # It is easier to work with f_with_affine_output, which represents - # a wrapped version of 'f', when it bears the same name as 'f': - f_with_affine_output.__name__ = f.__name__ - - return f_with_affine_output - -def _force_aff_func_args(func): - """ - Takes an operator op(x, y) and wraps it. - - The constructed operator returns func(x, to_affine_scalar(y)) if y - can be upcast with to_affine_scalar(); otherwise, it returns - NotImplemented. - - Thus, func() is only called on two AffineScalarFunc objects, if - its first argument is an AffineScalarFunc. - """ - - def op_on_upcast_args(x, y): - """ - Returns %s(self, to_affine_scalar(y)) if y can be upcast - through to_affine_scalar. Otherwise returns NotImplemented. - """ % func.__name__ - - try: - y_with_uncert = to_affine_scalar(y) - except NotUpcast: - # This module does not know how to handle the comparison: - # (example: y is a NumPy array, in which case the NumPy - # array will decide that func() should be applied - # element-wise between x and all the elements of y): - return NotImplemented - else: - return func(x, y_with_uncert) - - return op_on_upcast_args - -######################################## - -# Definition of boolean operators, that assume that self and -# y_with_uncert are AffineScalarFunc. - -# The fact that uncertainties must be smalled is used, here: the -# comparison functions are supposed to be constant for most values of -# the random variables. - -# Even though uncertainties are supposed to be small, comparisons -# between 3+/-0.1 and 3.0 are handled (even though x == 3.0 is not a -# constant function in the 3+/-0.1 interval). The comparison between -# x and x is handled too, when x has an uncertainty. In fact, as -# explained in the main documentation, it is possible to give a useful -# meaning to the comparison operators, in these cases. - -def _eq_on_aff_funcs(self, y_with_uncert): - """ - __eq__ operator, assuming that both self and y_with_uncert are - AffineScalarFunc objects. - """ - difference = self - y_with_uncert - # Only an exact zero difference means that self and y are - # equal numerically: - return not(difference._nominal_value or difference.std_dev()) - -def _ne_on_aff_funcs(self, y_with_uncert): - """ - __ne__ operator, assuming that both self and y_with_uncert are - AffineScalarFunc objects. - """ - - return not _eq_on_aff_funcs(self, y_with_uncert) - -def _gt_on_aff_funcs(self, y_with_uncert): - """ - __gt__ operator, assuming that both self and y_with_uncert are - AffineScalarFunc objects. - """ - return self._nominal_value > y_with_uncert._nominal_value - -def _ge_on_aff_funcs(self, y_with_uncert): - """ - __ge__ operator, assuming that both self and y_with_uncert are - AffineScalarFunc objects. - """ - - return (_gt_on_aff_funcs(self, y_with_uncert) - or _eq_on_aff_funcs(self, y_with_uncert)) - -def _lt_on_aff_funcs(self, y_with_uncert): - """ - __lt__ operator, assuming that both self and y_with_uncert are - AffineScalarFunc objects. - """ - return self._nominal_value < y_with_uncert._nominal_value - -def _le_on_aff_funcs(self, y_with_uncert): - """ - __le__ operator, assuming that both self and y_with_uncert are - AffineScalarFunc objects. - """ - - return (_lt_on_aff_funcs(self, y_with_uncert) - or _eq_on_aff_funcs(self, y_with_uncert)) - -######################################## - -class AffineScalarFunc(object): - """ - Affine functions that support basic mathematical operations - (addition, etc.). Such functions can for instance be used for - representing the local (linear) behavior of any function. - - This class is mostly meant to be used internally. - - This class can also be used to represent constants. - - The variables of affine scalar functions are Variable objects. - - AffineScalarFunc objects include facilities for calculating the - 'error' on the function, from the uncertainties on its variables. - - Main attributes and methods: - - - nominal_value, std_dev(): value at the origin / nominal value, - and standard deviation. - - - error_components(): error_components()[x] is the error due to - Variable x. - - - derivatives: derivatives[x] is the (value of the) derivative - with respect to Variable x. This attribute is a dictionary - whose keys are the Variable objects on which the function - depends. - - All the Variable objects on which the function depends are in - 'derivatives'. - - - std_score(x): position of number x with respect to the - nominal value, in units of the standard deviation. - """ - - # To save memory in large arrays: - __slots__ = ('_nominal_value', 'derivatives') - - #! The code could be modify in order to accommodate for non-float - # nominal values. This could for instance be done through - # the operator module: instead of delegating operations to - # float.__*__ operations, they could be delegated to - # operator.__*__ functions (while taking care of properly handling - # reverse operations: __radd__, etc.). - - def __init__(self, nominal_value, derivatives): - """ - nominal_value -- value of the function at the origin. - nominal_value must not depend in any way of the Variable - objects in 'derivatives' (the value at the origin of the - function being defined is a constant). - - derivatives -- maps each Variable object on which the function - being defined depends to the value of the derivative with - respect to that variable, taken at the nominal value of all - variables. - - Warning: the above constraint is not checked, and the user is - responsible for complying with it. - """ - - # Defines the value at the origin: - - # Only float-like values are handled. One reason is that it - # does not make sense for a scalar function to be affine to - # not yield float values. Another reason is that it would not - # make sense to have a complex nominal value, here (it would - # not be handled correctly at all): converting to float should - # be possible. - self._nominal_value = float(nominal_value) - self.derivatives = derivatives - - # The following prevents the 'nominal_value' attribute from being - # modified by the user: - @property - def nominal_value(self): - "Nominal value of the random number." - return self._nominal_value - - ############################################################ - - - ### Operators: operators applied to AffineScalarFunc and/or - ### float-like objects only are supported. This is why methods - ### from float are used for implementing these operators. - - # Operators with no reflection: - - ######################################## - - # __nonzero__() is supposed to return a boolean value (it is used - # by bool()). It is for instance used for converting the result - # of comparison operators to a boolean, in sorted(). If we want - # to be able to sort AffineScalarFunc objects, __nonzero__ cannot - # return a AffineScalarFunc object. Since boolean results (such - # as the result of bool()) don't have a very meaningful - # uncertainty unless it is zero, this behavior is fine. - - def __nonzero__(self): - """ - Equivalent to self != 0. - """ - #! This might not be relevant for AffineScalarFunc objects - # that contain values in a linear space which does not convert - # the float 0 into the null vector (see the __eq__ function: - # __nonzero__ works fine if subtracting the 0 float from a - # vector of the linear space works as if 0 were the null - # vector of that space): - return self != 0. # Uses the AffineScalarFunc.__ne__ function - - ######################################## - - ## Logical operators: warning: the resulting value cannot always - ## be differentiated. - - # The boolean operations are not differentiable everywhere, but - # almost... - - # (1) I can rely on the assumption that the user only has "small" - # errors on variables, as this is used in the calculation of the - # standard deviation (which performs linear approximations): - - # (2) However, this assumption is not relevant for some - # operations, and does not have to hold, in some cases. This - # comes from the fact that logical operations (e.g. __eq__(x,y)) - # are not differentiable for many usual cases. For instance, it - # is desirable to have x == x for x = n+/-e, whatever the size of e. - # Furthermore, n+/-e != n+/-e', if e != e', whatever the size of e or - # e'. - - # (3) The result of logical operators does not have to be a - # function with derivatives, as these derivatives are either 0 or - # don't exist (i.e., the user should probably not rely on - # derivatives for his code). - - # __eq__ is used in "if data in [None, ()]", for instance. It is - # therefore important to be able to handle this case too, which is - # taken care of when _force_aff_func_args(_eq_on_aff_funcs) - # returns NotImplemented. - __eq__ = _force_aff_func_args(_eq_on_aff_funcs) - - __ne__ = _force_aff_func_args(_ne_on_aff_funcs) - __gt__ = _force_aff_func_args(_gt_on_aff_funcs) - - # __ge__ is not the opposite of __lt__ because these operators do - # not always yield a boolean (for instance, 0 <= numpy.arange(10) - # yields an array). - __ge__ = _force_aff_func_args(_ge_on_aff_funcs) - - __lt__ = _force_aff_func_args(_lt_on_aff_funcs) - __le__ = _force_aff_func_args(_le_on_aff_funcs) - - ######################################## - - # Uncertainties handling: - - def error_components(self): - """ - Individual components of the standard deviation of the affine - function (in absolute value), returned as a dictionary with - Variable objects as keys. - - This method assumes that the derivatives contained in the - object take scalar values (and are not a tuple, like what - math.frexp() returns, for instance). - """ - - # Calculation of the variance: - error_components = {} - for (variable, derivative) in self.derivatives.items(): - # Individual standard error due to variable: - error_components[variable] = abs(derivative*variable._std_dev) - - return error_components - - def std_dev(self): - """ - Standard deviation of the affine function. - - This method assumes that the function returns scalar results. - - This returned standard deviation depends on the current - standard deviations [std_dev()] of the variables (Variable - objects) involved. - """ - #! It would be possible to not allow the user to update the - #std dev of Variable objects, in which case AffineScalarFunc - #objects could have a pre-calculated or, better, cached - #std_dev value (in fact, many intermediate AffineScalarFunc do - #not need to have their std_dev calculated: only the final - #AffineScalarFunc returned to the user does). - return sqrt(sum( - delta**2 for delta in self.error_components().itervalues())) - - def _general_representation(self, to_string): - """ - Uses the to_string() conversion function on both the nominal - value and the standard deviation, and returns a string that - describes them. - - to_string() is typically repr() or str(). - """ - - (nominal_value, std_dev) = (self._nominal_value, self.std_dev()) - - # String representation: - - # Not putting spaces around "+/-" helps with arrays of - # Variable, as each value with an uncertainty is a - # block of signs (otherwise, the standard deviation can be - # mistaken for another element of the array). - - return ("%s+/-%s" % (to_string(nominal_value), to_string(std_dev)) - if std_dev - else to_string(nominal_value)) - - def __repr__(self): - return self._general_representation(repr) - - def __str__(self): - return self._general_representation(str) - - def std_score(self, value): - """ - Returns 'value' - nominal value, in units of the standard - deviation. - - Raises a ValueError exception if the standard deviation is zero. - """ - try: - # The ._nominal_value is a float: there is no integer division, - # here: - return (value - self._nominal_value) / self.std_dev() - except ZeroDivisionError: - raise ValueError("The standard deviation is zero:" - " undefined result.") - - def __deepcopy__(self, memo): - """ - Hook for the standard copy module. - - The returned AffineScalarFunc is a completely fresh copy, - which is fully independent of any variable defined so far. - New variables are specially created for the returned - AffineScalarFunc object. - """ - return AffineScalarFunc( - self._nominal_value, - dict((copy.deepcopy(var), deriv) - for (var, deriv) in self.derivatives.items())) - - def __getstate__(self): - """ - Hook for the pickle module. - """ - obj_slot_values = dict((k, getattr(self, k)) for k in - # self.__slots__ would not work when - # self is an instance of a subclass: - AffineScalarFunc.__slots__) - return obj_slot_values - - def __setstate__(self, data_dict): - """ - Hook for the pickle module. - """ - for (name, value) in data_dict.items(): - setattr(self, name, value) - -# Nicer name, for users: isinstance(ufloat(...), UFloat) is True: -UFloat = AffineScalarFunc - -def get_ops_with_reflection(): - - """ - Returns operators with a reflection, along with their derivatives - (for float operands). - """ - - # Operators with a reflection: - - # We do not include divmod(). This operator could be included, by - # allowing its result (a tuple) to be differentiated, in - # derivative_value(). However, a similar result can be achieved - # by the user by calculating separately the division and the - # result. - - # {operator(x, y): (derivative wrt x, derivative wrt y)}: - - # Note that unknown partial derivatives can be numerically - # calculated by expressing them as something like - # "partial_derivative(float.__...__, 1)(x, y)": - - # String expressions are used, so that reversed operators are easy - # to code, and execute relatively efficiently: - - derivatives_list = { - 'add': ("1.", "1."), - # 'div' is the '/' operator when __future__.division is not in - # effect. Since '/' is applied to - # AffineScalarFunc._nominal_value numbers, it is applied on - # floats, and is therefore the "usual" mathematical division. - 'div': ("1/y", "-x/y**2"), - 'floordiv': ("0.", "0."), # Non exact: there is a discontinuities - # The derivative wrt the 2nd arguments is something like (..., x//y), - # but it is calculated numerically, for convenience: - 'mod': ("1.", "partial_derivative(float.__mod__, 1)(x, y)"), - 'mul': ("y", "x"), - 'pow': ("y*x**(y-1)", "log(x)*x**y"), - 'sub': ("1.", "-1."), - 'truediv': ("1/y", "-x/y**2") - } - - # Conversion to Python functions: - ops_with_reflection = {} - for (op, derivatives) in derivatives_list.items(): - ops_with_reflection[op] = [ - eval("lambda x, y: %s" % expr) for expr in derivatives ] - - ops_with_reflection["r"+op] = [ - eval("lambda y, x: %s" % expr) for expr in reversed(derivatives)] - - return ops_with_reflection - -# Operators that have a reflection, along with their derivatives: -_ops_with_reflection = get_ops_with_reflection() - -# Some effectively modified operators (for the automated tests): -_modified_operators = [] -_modified_ops_with_reflection = [] - -def add_operators_to_AffineScalarFunc(): - """ - Adds many operators (__add__, etc.) to the AffineScalarFunc class. - """ - - ######################################## - - #! Derivatives are set to return floats. For one thing, - # uncertainties generally involve floats, as they are based on - # small variations of the parameters. It is also better to - # protect the user from unexpected integer result that behave - # badly with the division. - - ## Operators that return a numerical value: - - # Single-argument operators that should be adapted from floats to - # AffineScalarFunc objects, associated to their derivative: - simple_numerical_operators_derivatives = { - 'abs': lambda x: 1. if x>=0 else -1., - 'neg': lambda x: -1., - 'pos': lambda x: 1., - 'trunc': lambda x: 0. - } - - for (op, derivative) in ( - simple_numerical_operators_derivatives.items()): - - attribute_name = "__%s__" % op - # float objects don't exactly have the same attributes between - # different versions of Python (for instance, __trunc__ was - # introduced with Python 2.6): - try: - setattr(AffineScalarFunc, attribute_name, - wrap(getattr(float, attribute_name), - [derivative])) - except AttributeError: - pass - else: - _modified_operators.append(op) - - ######################################## - - # Reversed versions (useful for float*AffineScalarFunc, for instance): - for (op, derivatives) in _ops_with_reflection.items(): - attribute_name = '__%s__' % op - # float objects don't exactly have the same attributes between - # different versions of Python (for instance, __div__ and - # __rdiv__ were removed, in Python 3): - try: - setattr(AffineScalarFunc, attribute_name, - wrap(getattr(float, attribute_name), derivatives)) - except AttributeError: - pass - else: - _modified_ops_with_reflection.append(op) - - ######################################## - # Conversions to pure numbers are meaningless. Note that the - # behavior of float(1j) is similar. - for coercion_type in ('complex', 'int', 'long', 'float'): - def raise_error(self): - raise TypeError("can't convert an affine function (%s)" - ' to %s; use x.nominal_value' - # In case AffineScalarFunc is sub-classed: - % (self.__class__, coercion_type)) - - setattr(AffineScalarFunc, '__%s__' % coercion_type, raise_error) - -add_operators_to_AffineScalarFunc() # Actual addition of class attributes - -class Variable(AffineScalarFunc): - """ - Representation of a float-like scalar random variable, along with - its uncertainty. - - Objects are meant to represent variables that are independent from - each other (correlations are handled through the AffineScalarFunc - class). - """ - - # To save memory in large arrays: - __slots__ = ('_std_dev', 'tag') - - def __init__(self, value, std_dev, tag=None): - """ - The nominal value and the standard deviation of the variable - are set. These values must be scalars. - - 'tag' is a tag that the user can associate to the variable. This - is useful for tracing variables. - - The meaning of the nominal value is described in the main - module documentation. - """ - - #! The value, std_dev, and tag are assumed by __copy__() not to - # be copied. Either this should be guaranteed here, or __copy__ - # should be updated. - - # Only float-like values are handled. One reason is that the - # division operator on integers would not produce a - # differentiable functions: for instance, Variable(3, 0.1)/2 - # has a nominal value of 3/2 = 1, but a "shifted" value - # of 3.1/2 = 1.55. - value = float(value) - - # If the variable changes by dx, then the value of the affine - # function that gives its value changes by 1*dx: - - # ! Memory cycles are created. However, they are garbage - # collected, if possible. Using a weakref.WeakKeyDictionary - # takes much more memory. Thus, this implementation chooses - # more cycles and a smaller memory footprint instead of no - # cycles and a larger memory footprint. - - # ! Using AffineScalarFunc instead of super() results only in - # a 3 % speed loss (Python 2.6, Mac OS X): - super(Variable, self).__init__(value, {self: 1.}) - - # We force the error to be float-like. Since it is considered - # as a Gaussian standard deviation, it is semantically - # positive (even though there would be no problem defining it - # as a sigma, where sigma can be negative and still define a - # Gaussian): - - assert std_dev >= 0, "the error must be a positive number" - # Since AffineScalarFunc.std_dev is a property, we cannot do - # "self.std_dev = ...": - self._std_dev = std_dev - - self.tag = tag - - # Standard deviations can be modified (this is a feature). - # AffineScalarFunc objects that depend on the Variable have their - # std_dev() automatically modified (recalculated with the new - # std_dev of their Variables): - def set_std_dev(self, value): - """ - Updates the standard deviation of the variable to a new value. - """ - - # A zero variance is accepted. Thus, it is possible to - # conveniently use infinitely precise variables, for instance - # to study special cases. - - self._std_dev = value - - # The following method is overridden so that we can represent the tag: - def _general_representation(self, to_string): - """ - Uses the to_string() conversion function on both the nominal - value and standard deviation and returns a string that - describes the number. - - to_string() is typically repr() or str(). - """ - num_repr = super(Variable, self)._general_representation(to_string) - - # Optional tag: only full representations (to_string == repr) - # contain the tag, as the tag is required in order to recreate - # the variable. Outputting the tag for regular string ("print - # x") would be too heavy and produce an unusual representation - # of a number with uncertainty. - return (num_repr if ((self.tag is None) or (to_string != repr)) - else "< %s = %s >" % (self.tag, num_repr)) - - def __hash__(self): - # All Variable objects are by definition independent - # variables, so they never compare equal; therefore, their - # id() are therefore allowed to differ - # (http://docs.python.org/reference/datamodel.html#object.__hash__): - return id(self) - - def __copy__(self): - """ - Hook for the standard copy module. - """ - - # This copy implicitly takes care of the reference of the - # variable to itself (in self.derivatives): the new Variable - # object points to itself, not to the original Variable. - - # Reference: http://www.doughellmann.com/PyMOTW/copy/index.html - - #! The following assumes that the arguments to Variable are - # *not* copied upon construction, since __copy__ is not supposed - # to copy "inside" information: - return Variable(self.nominal_value, self.std_dev(), self.tag) - - def __deepcopy__(self, memo): - """ - Hook for the standard copy module. - - A new variable is created. - """ - - # This deep copy implicitly takes care of the reference of the - # variable to itself (in self.derivatives): the new Variable - # object points to itself, not to the original Variable. - - # Reference: http://www.doughellmann.com/PyMOTW/copy/index.html - - return self.__copy__() - - def __getstate__(self): - """ - Hook for the standard pickle module. - """ - obj_slot_values = dict((k, getattr(self, k)) for k in self.__slots__) - obj_slot_values.update(AffineScalarFunc.__getstate__(self)) - # Conversion to a usual dictionary: - return obj_slot_values - - def __setstate__(self, data_dict): - """ - Hook for the standard pickle module. - """ - for (name, value) in data_dict.items(): - setattr(self, name, value) - -############################################################################### - -# Utilities - -def nominal_value(x): - """ - Returns the nominal value of x if it is a quantity with - uncertainty (i.e., an AffineScalarFunc object); otherwise, returns - x unchanged. - - This utility function is useful for transforming a series of - numbers, when only some of them generally carry an uncertainty. - """ - - return x.nominal_value if isinstance(x, AffineScalarFunc) else x - -def std_dev(x): - """ - Returns the standard deviation of x if it is a quantity with - uncertainty (i.e., an AffineScalarFunc object); otherwise, returns - the float 0. - - This utility function is useful for transforming a series of - numbers, when only some of them generally carry an uncertainty. - """ - - return x.std_dev() if isinstance(x, AffineScalarFunc) else 0. - -def covariance_matrix(nums_with_uncert): - """ - Returns a matrix that contains the covariances between the given - sequence of numbers with uncertainties (AffineScalarFunc objects). - The resulting matrix implicitly depends on their ordering in - 'nums_with_uncert'. - - The covariances are floats (never int objects). - - The returned covariance matrix is the exact linear approximation - result, if the nominal values of the numbers with uncertainties - and of their variables are their mean. Otherwise, the returned - covariance matrix should be close to its linear approximation - value. - - The returned matrix is a list of lists. - """ - # See PSI.411 in EOL's notes. - - covariance_matrix = [] - for (i1, expr1) in enumerate(nums_with_uncert): - derivatives1 = expr1.derivatives # Optimization - vars1 = set(derivatives1) - coefs_expr1 = [] - for (i2, expr2) in enumerate(nums_with_uncert[:i1+1]): - derivatives2 = expr2.derivatives # Optimization - coef = 0. - for var in vars1.intersection(derivatives2): - # var is a variable common to both numbers with - # uncertainties: - coef += (derivatives1[var]*derivatives2[var]*var._std_dev**2) - coefs_expr1.append(coef) - covariance_matrix.append(coefs_expr1) - - # We symmetrize the matrix: - for (i, covariance_coefs) in enumerate(covariance_matrix): - covariance_coefs.extend(covariance_matrix[j][i] - for j in range(i+1, len(covariance_matrix))) - - return covariance_matrix - -try: - import numpy -except ImportError: - pass -else: - def correlation_matrix(nums_with_uncert): - ''' - Returns the correlation matrix of the given sequence of - numbers with uncertainties, as a NumPy array of floats. - ''' - - cov_mat = numpy.array(covariance_matrix(nums_with_uncert)) - - std_devs = numpy.sqrt(cov_mat.diagonal()) - - return cov_mat/std_devs/std_devs[numpy.newaxis].T - - __all__.append('correlation_matrix') - -############################################################################### -# Parsing of values with uncertainties: - -POSITIVE_DECIMAL_UNSIGNED = r'(\d+)(\.\d*)?' - -# Regexp for a number with uncertainty (e.g., "-1.234(2)e-6"), where the -# uncertainty is optional (in which case the uncertainty is implicit): -NUMBER_WITH_UNCERT_RE_STR = ''' - ([+-])? # Sign - %s # Main number - (?:\(%s\))? # Optional uncertainty - ([eE][+-]?\d+)? # Optional exponent - ''' % (POSITIVE_DECIMAL_UNSIGNED, POSITIVE_DECIMAL_UNSIGNED) - -NUMBER_WITH_UNCERT_RE = re.compile( - "^%s$" % NUMBER_WITH_UNCERT_RE_STR, re.VERBOSE) - -def parse_error_in_parentheses(representation): - """ - Returns (value, error) from a string representing a number with - uncertainty like 12.34(5), 12.34(142), 12.5(3.4) or 12.3(4.2)e3. - If no parenthesis is given, an uncertainty of one on the last - digit is assumed. - - Raises ValueError if the string cannot be parsed. - """ - - match = NUMBER_WITH_UNCERT_RE.search(representation) - - if match: - # The 'main' part is the nominal value, with 'int'eger part, and - # 'dec'imal part. The 'uncert'ainty is similarly broken into its - # integer and decimal parts. - (sign, main_int, main_dec, uncert_int, uncert_dec, - exponent) = match.groups() - else: - raise ValueError("Unparsable number representation: '%s'." - " Was expecting a string of the form 1.23(4)" - " or 1.234" % representation) - - # The value of the number is its nominal value: - value = float(''.join((sign or '', - main_int, - main_dec or '.0', - exponent or ''))) - - if uncert_int is None: - # No uncertainty was found: an uncertainty of 1 on the last - # digit is assumed: - uncert_int = '1' - - # Do we have a fully explicit uncertainty? - if uncert_dec is not None: - uncert = float("%s%s" % (uncert_int, uncert_dec or '')) - else: - # uncert_int represents an uncertainty on the last digits: - - # The number of digits after the period defines the power of - # 10 than must be applied to the provided uncertainty: - num_digits_after_period = (0 if main_dec is None - else len(main_dec)-1) - uncert = int(uncert_int)/10**num_digits_after_period - - # We apply the exponent to the uncertainty as well: - uncert *= float("1%s" % (exponent or '')) - - return (value, uncert) - - -# The following function is not exposed because it can in effect be -# obtained by doing x = ufloat(representation) and -# x.nominal_value and x.std_dev(): -def str_to_number_with_uncert(representation): - """ - Given a string that represents a number with uncertainty, returns the - nominal value and the uncertainty. - - The string can be of the form: - - 124.5+/-0.15 - - 124.50(15) - - 124.50(123) - - 124.5 - - When no numerical error is given, an uncertainty of 1 on the last - digit is implied. - - Raises ValueError if the string cannot be parsed. - """ - - try: - # Simple form 1234.45+/-1.2: - (value, uncert) = representation.split('+/-') - except ValueError: - # Form with parentheses or no uncertainty: - parsed_value = parse_error_in_parentheses(representation) - else: - try: - parsed_value = (float(value), float(uncert)) - except ValueError: - raise ValueError('Cannot parse %s: was expecting a number' - ' like 1.23+/-0.1' % representation) - - return parsed_value - -def ufloat(representation, tag=None): - """ - Returns a random variable (Variable object). - - Converts the representation of a number into a number with - uncertainty (a random variable, defined by a nominal value and - a standard deviation). - - The representation can be a (value, standard deviation) sequence, - or a string. - - Strings of the form '12.345+/-0.015', '12.345(15)', or '12.3' are - recognized (see full list below). In the last case, an - uncertainty of +/-1 is assigned to the last digit. - - 'tag' is an optional string tag for the variable. Variables - don't have to have distinct tags. Tags are useful for tracing - what values (and errors) enter in a given result (through the - error_components() method). - - Examples of valid string representations: - - -1.23(3.4) - -1.34(5) - 1(6) - 3(4.2) - -9(2) - 1234567(1.2) - 12.345(15) - -12.3456(78)e-6 - 12.3(0.4)e-5 - 0.29 - 31. - -31. - 31 - -3.1e10 - 169.0(7) - 169.1(15) - """ - - # This function is somewhat optimized so as to help with the - # creation of lots of Variable objects (through unumpy.uarray, for - # instance). - - # representations is "normalized" so as to be a valid sequence of - # 2 arguments for Variable(). - - #! Accepting strings and any kind of sequence slows down the code - # by about 5 %. On the other hand, massive initializations of - # numbers with uncertainties are likely to be performed with - # unumpy.uarray, which does not support parsing from strings and - # thus does not have any overhead. - - #! Different, in Python 3: - if isinstance(representation, basestring): - representation = str_to_number_with_uncert(representation) - - #! The tag is forced to be a string, so that the user does not - # create a Variable(2.5, 0.5) in order to represent 2.5 +/- 0.5. - # Forcing 'tag' to be a string prevents numerical uncertainties - # from being considered as tags, here: - if tag is not None: - #! 'unicode' is removed in Python3: - assert isinstance(tag, (str, unicode)), "The tag can only be a string." - - #! The special ** syntax is for Python 2.5 and before (Python 2.6+ - # understands tag=tag): - return Variable(*representation, **{'tag': tag}) - diff --git a/sigproc/lmfit/uncertainties/umath.py b/sigproc/lmfit/uncertainties/umath.py deleted file mode 100644 index efbe34388..000000000 --- a/sigproc/lmfit/uncertainties/umath.py +++ /dev/null @@ -1,352 +0,0 @@ -''' -Mathematical operations that generalize many operations from the -standard math module so that they also work on numbers with -uncertainties. - -Examples: - - from umath import sin - - # Manipulation of numbers with uncertainties: - x = uncertainties.ufloat((3, 0.1)) - print sin(x) # prints 0.141120008...+/-0.098999... - - # The umath functions also work on regular Python floats: - print sin(3) # prints 0.141120008... This is a Python float. - -Importing all the functions from this module into the global namespace -is possible. This is encouraged when using a Python shell as a -calculator. Example: - - import uncertainties - from uncertainties.umath import * # Imports tan(), etc. - - x = uncertainties.ufloat((3, 0.1)) - print tan(x) # tan() is the uncertainties.umath.tan function - -The numbers with uncertainties handled by this module are objects from -the uncertainties module, from either the Variable or the -AffineScalarFunc class. - -(c) 2009-2013 by Eric O. LEBIGOT (EOL) . -Please send feature requests, bug reports, or feedback to this address. - -This software is released under a dual license. (1) The BSD license. -(2) Any other license, as long as it is obtained from the original -author.''' - -from __future__ import division # Many analytical derivatives depend on this - -# Standard modules -import math -import sys -import itertools -import functools -import inspect - -# Local modules -from ivs.sigproc.lmfit import uncertainties - -from ivs.sigproc.lmfit.uncertainties import __author__, to_affine_scalar, AffineScalarFunc - -############################################################################### - -# We wrap the functions from the math module so that they keep track of -# uncertainties by returning a AffineScalarFunc object. - -# Some functions from the math module cannot be adapted in a standard -# way so to work with AffineScalarFunc objects (either as their result -# or as their arguments): - -# (1) Some functions return a result of a type whose value and -# variations (uncertainties) cannot be represented by AffineScalarFunc -# (e.g., math.frexp, which returns a tuple). The exception raised -# when not wrapping them with wrap() is more obvious than the -# one obtained when wrapping them (in fact, the wrapped functions -# attempts operations that are not supported, such as calculation a -# subtraction on a result of type tuple). - -# (2) Some functions don't take continuous scalar arguments (which can -# be varied during differentiation): math.fsum, math.factorial... -# Such functions can either be: - -# - wrapped in a special way. - -# - excluded from standard wrapping by adding their name to -# no_std_wrapping - -# Math functions that have a standard interface: they take -# one or more float arguments, and return a scalar: -many_scalars_to_scalar_funcs = [] - -# Some functions require a specific treatment and must therefore be -# excluded from standard wrapping. Functions -# no_std_wrapping = ['modf', 'frexp', 'ldexp', 'fsum', 'factorial'] - -# Functions with numerical derivatives: -num_deriv_funcs = ['fmod', 'gamma', 'isinf', 'isnan', - 'lgamma', 'trunc'] - -# Functions that do not belong in many_scalars_to_scalar_funcs, but -# that have a version that handles uncertainties: -non_std_wrapped_funcs = [] - -# Function that copies the relevant attributes from generalized -# functions from the math module: -wraps = functools.partial(functools.update_wrapper, - assigned=('__doc__', '__name__')) - -######################################## -# Wrapping of math functions: - -# Fixed formulas for the derivatives of some functions from the math -# module (some functions might not be present in all version of -# Python). Singular points are not taken into account. The user -# should never give "large" uncertainties: problems could only appear -# if this assumption does not hold. - -# Functions not mentioned in _fixed_derivatives have their derivatives -# calculated numerically. - -# Functions that have singularities (possibly at infinity) benefit -# from analytical calculations (instead of the default numerical -# calculation) because their derivatives generally change very fast. -# Even slowly varying functions (e.g., abs()) yield more precise -# results when differentiated analytically, because of the loss of -# precision in numerical calculations. - -#def log_1arg_der(x): -# """ -# Derivative of log(x) (1-argument form). -# """ -# return 1/x - -def log_der0(*args): - """ - Derivative of math.log() with respect to its first argument. - - Works whether 1 or 2 arguments are given. - """ - if len(args) == 1: - return 1/args[0] - else: - return 1/args[0]/math.log(args[1]) # 2-argument form - - # The following version goes about as fast: - - ## A 'try' is used for the most common case because it is fast when no - ## exception is raised: - #try: - # return log_1arg_der(*args) # Argument number check - #except TypeError: - # return 1/args[0]/math.log(args[1]) # 2-argument form - -_erf_coef = 2/math.sqrt(math.pi) # Optimization for erf() - -fixed_derivatives = { - # In alphabetical order, here: - 'acos': [lambda x: -1/math.sqrt(1-x**2)], - 'acosh': [lambda x: 1/math.sqrt(x**2-1)], - 'asin': [lambda x: 1/math.sqrt(1-x**2)], - 'asinh': [lambda x: 1/math.sqrt(1+x**2)], - 'atan': [lambda x: 1/(1+x**2)], - 'atan2': [lambda y, x: x/(x**2+y**2), # Correct for x == 0 - lambda y, x: -y/(x**2+y**2)], # Correct for x == 0 - 'atanh': [lambda x: 1/(1-x**2)], - 'ceil': [lambda x: 0], - 'copysign': [lambda x, y: (1 if x >= 0 else -1) * math.copysign(1, y), - lambda x, y: 0], - 'cos': [lambda x: -math.sin(x)], - 'cosh': [math.sinh], - 'degrees': [lambda x: math.degrees(1)], - 'erf': [lambda x: exp(-x**2)*_erf_coef], - 'erfc': [lambda x: -exp(-x**2)*_erf_coef], - 'exp': [math.exp], - 'expm1': [math.exp], - 'fabs': [lambda x: 1 if x >= 0 else -1], - 'floor': [lambda x: 0], - 'hypot': [lambda x, y: x/math.hypot(x, y), - lambda x, y: y/math.hypot(x, y)], - 'log': [log_der0, - lambda x, y: -math.log(x, y)/y/math.log(y)], - 'log10': [lambda x: 1/x/math.log(10)], - 'log1p': [lambda x: 1/(1+x)], - 'pow': [lambda x, y: y*math.pow(x, y-1), - lambda x, y: math.log(x) * math.pow(x, y)], - 'radians': [lambda x: math.radians(1)], - 'sin': [math.cos], - 'sinh': [math.cosh], - 'sqrt': [lambda x: 0.5/math.sqrt(x)], - 'tan': [lambda x: 1+math.tan(x)**2], - 'tanh': [lambda x: 1-math.tanh(x)**2] - } - -# Many built-in functions in the math module are wrapped with a -# version which is uncertainty aware: - -this_module = sys.modules[__name__] - -# for (name, attr) in vars(math).items(): -for name in dir(math): - - if name in fixed_derivatives: # Priority to functions in fixed_derivatives - derivatives = fixed_derivatives[name] - elif name in num_deriv_funcs: - # Functions whose derivatives are calculated numerically by - # this module fall here (isinf, fmod,...): - derivatives = None # Means: numerical calculation required - else: - continue # 'name' not wrapped by this module (__doc__, e, etc.) - - func = getattr(math, name) - - setattr(this_module, name, - wraps(uncertainties.wrap(func, derivatives), func)) - - many_scalars_to_scalar_funcs.append(name) - -############################################################################### - -######################################## -# Special cases: some of the functions from no_std_wrapping: - -########## -# The math.factorial function is not converted to an uncertainty-aware -# function, because it does not handle non-integer arguments: it does -# not make sense to give it an argument with a numerical error -# (whereas this would be relevant for the gamma function). - -########## - -# fsum takes a single argument, which cannot be differentiated. -# However, each of the arguments inside this single list can -# be a variable. We handle this in a specific way: - -if sys.version_info[:2] >= (2, 6): - - # For drop-in compatibility with the math module: - factorial = math.factorial - non_std_wrapped_funcs.append('factorial') - - - # We wrap math.fsum - - original_func = math.fsum # For optimization purposes - - # The function below exists so that temporary variables do not - # pollute the module namespace: - def wrapped_fsum(): - """ - Returns an uncertainty-aware version of math.fsum, which must - be contained in _original_func. - """ - - # The fsum function is flattened, in order to use the - # wrap() wrapper: - - flat_fsum = lambda *args: original_func(args) - - flat_fsum_wrap = uncertainties.wrap( - flat_fsum, itertools.repeat(lambda *args: 1)) - - return wraps(lambda arg_list: flat_fsum_wrap(*arg_list), - original_func) - - fsum = wrapped_fsum() - non_std_wrapped_funcs.append('fsum') - -########## -@uncertainties.set_doc(math.modf.__doc__) -def modf(x): - """ - Version of modf that works for numbers with uncertainty, and also - for regular numbers. - """ - - # The code below is inspired by uncertainties.wrap(). It is - # simpler because only 1 argument is given, and there is no - # delegation to other functions involved (as for __mul__, etc.). - - aff_func = to_affine_scalar(x) - - (frac_part, int_part) = math.modf(aff_func.nominal_value) - - if aff_func.derivatives: - # The derivative of the fractional part is simply 1: the - # derivatives of modf(x)[0] are the derivatives of x: - return (AffineScalarFunc(frac_part, aff_func.derivatives), int_part) - else: - # This function was not called with an AffineScalarFunc - # argument: there is no need to return numbers with uncertainties: - return (frac_part, int_part) - -many_scalars_to_scalar_funcs.append('modf') - -@uncertainties.set_doc(math.ldexp.__doc__) -def ldexp(x, y): - # The code below is inspired by uncertainties.wrap(). It is - # simpler because only 1 argument is given, and there is no - # delegation to other functions involved (as for __mul__, etc.). - - # Another approach would be to add an additional argument to - # uncertainties.wrap() so that some arguments are automatically - # considered as constants. - - aff_func = to_affine_scalar(x) # y must be an integer, for math.ldexp - - if aff_func.derivatives: - factor = 2**y - return AffineScalarFunc( - math.ldexp(aff_func.nominal_value, y), - # Chain rule: - dict((var, factor*deriv) - for (var, deriv) in aff_func.derivatives.iteritems())) - else: - # This function was not called with an AffineScalarFunc - # argument: there is no need to return numbers with uncertainties: - - # aff_func.nominal_value is not passed instead of x, because - # we do not have to care about the type of the return value of - # math.ldexp, this way (aff_func.nominal_value might be the - # value of x coerced to a difference type [int->float, for - # instance]): - return math.ldexp(x, y) -many_scalars_to_scalar_funcs.append('ldexp') - -@uncertainties.set_doc(math.frexp.__doc__) -def frexp(x): - """ - Version of frexp that works for numbers with uncertainty, and also - for regular numbers. - """ - - # The code below is inspired by uncertainties.wrap(). It is - # simpler because only 1 argument is given, and there is no - # delegation to other functions involved (as for __mul__, etc.). - - aff_func = to_affine_scalar(x) - - if aff_func.derivatives: - result = math.frexp(aff_func.nominal_value) - # With frexp(x) = (m, e), dm/dx = 1/(2**e): - factor = 1/(2**result[1]) - return ( - AffineScalarFunc( - result[0], - # Chain rule: - dict((var, factor*deriv) - for (var, deriv) in aff_func.derivatives.iteritems())), - # The exponent is an integer and is supposed to be - # continuous (small errors): - result[1]) - else: - # This function was not called with an AffineScalarFunc - # argument: there is no need to return numbers with uncertainties: - return math.frexp(x) -non_std_wrapped_funcs.append('frexp') - -############################################################################### -# Exported functions: - -__all__ = many_scalars_to_scalar_funcs + non_std_wrapped_funcs - diff --git a/sigproc/lmfit/uncertainties/unumpy/__init__.py b/sigproc/lmfit/uncertainties/unumpy/__init__.py deleted file mode 100644 index 1028797a2..000000000 --- a/sigproc/lmfit/uncertainties/unumpy/__init__.py +++ /dev/null @@ -1,82 +0,0 @@ -""" -Utilities for NumPy arrays and matrices that contain numbers with -uncertainties. - -This package contains: - -1) utilities that help with the creation and manipulation of NumPy -arrays and matrices of numbers with uncertainties; - -2) generalizations of multiple NumPy functions so that they also work -with arrays that contain numbers with uncertainties. - -- Arrays of numbers with uncertainties can be built as follows: - - arr = unumpy.uarray(([1, 2], [0.01, 0.002])) # (values, uncertainties) - -NumPy arrays of numbers with uncertainties can also be built directly -through NumPy, thanks to NumPy's support of arrays of arbitrary objects: - - arr = numpy.array([uncertainties.ufloat((1, 0.1)),...]) - -- Matrices of numbers with uncertainties are best created in one of -two ways: - - mat = unumpy.umatrix(([1, 2], [0.01, 0.002])) # (values, uncertainties) - -Matrices can also be built by converting arrays of numbers with -uncertainties, through the unumpy.matrix class: - - mat = unumpy.matrix(arr) - -unumpy.matrix objects behave like numpy.matrix objects of numbers with -uncertainties, but with better support for some operations (such as -matrix inversion): - - # The inverse or pseudo-inverse of a unumpy.matrix can be calculated: - print mat.I # Would not work with numpy.matrix([[ufloat(...),...]]).I - -- Nominal values and uncertainties of arrays can be directly accessed: - - print unumpy.nominal_values(arr) # [ 1. 2.] - print unumpy.std_devs(mat) # [ 0.01 0.002] - -- This module defines uncertainty-aware mathematical functions that -generalize those from uncertainties.umath so that they work on NumPy -arrays of numbers with uncertainties instead of just scalars: - - print unumpy.cos(arr) # Array with the cosine of each element - -NumPy's function names are used, and not those of the math module (for -instance, unumpy.arccos is defined, like in NumPy, and is not named -acos like in the standard math module). - -The definitions of the mathematical quantities calculated by these -functions are available in the documentation of uncertainties.umath. - -- The unumpy.ulinalg module contains more uncertainty-aware functions -for arrays that contain numbers with uncertainties (see the -documentation for this module). - -This module requires the NumPy package. - -(c) 2009-2013 by Eric O. LEBIGOT (EOL) . -Please send feature requests, bug reports, or feedback to this address. - -This software is released under a dual license. (1) The BSD license. -(2) Any other license, as long as it is obtained from the original -author.""" - -# Local modules: -from core import * -import core -from ivs.sigproc.lmfit.uncertainties.unumpy import ulinalg # Local sub-module - -from ivs.sigproc.lmfit.uncertainties import __author__ - -# __all__ is set so that pydoc shows all important functions: -__all__ = core.__all__ -# "import numpy" makes numpy.linalg available. This behavior is -# copied here, for maximum compatibility: -__all__.append('ulinalg') - diff --git a/sigproc/lmfit/uncertainties/unumpy/core.py b/sigproc/lmfit/uncertainties/unumpy/core.py deleted file mode 100644 index ef54137cd..000000000 --- a/sigproc/lmfit/uncertainties/unumpy/core.py +++ /dev/null @@ -1,612 +0,0 @@ -""" -Core functions used by unumpy and some of its submodules. - -(c) 2010-2013 by Eric O. LEBIGOT (EOL). -""" - -# The functions found in this module cannot be defined in unumpy or -# its submodule: this creates import loops, when unumpy explicitly -# imports one of the submodules in order to make it available to the -# user. - -from __future__ import division - -# Standard modules: -import sys - -# 3rd-party modules: -import numpy -from numpy.core import numeric - -# Local modules: -from ivs.sigproc.lmfit import uncertainties -from ivs.sigproc.lmfit.uncertainties import umath - -from ivs.sigproc.lmfit.uncertainties import __author__ - -__all__ = [ - # Factory functions: - 'uarray', 'umatrix', - - # Utilities: - 'nominal_values', 'std_devs', - - # Classes: - 'matrix' - ] - -############################################################################### -# Utilities: - -# nominal_values() and std_devs() are defined as functions (instead of -# as additional methods of the unumpy.matrix class) because the user -# might well directly build arrays of numbers with uncertainties -# without going through the factory functions found in this module -# (uarray() and umatrix()). Thus, -# numpy.array([uncertainties.ufloat((1, 0.1))]) would not -# have a nominal_values() method. Adding such a method to, say, -# unumpy.matrix, would break the symmetry between NumPy arrays and -# matrices (no nominal_values() method), and objects defined in this -# module. - -# ! Warning: the __doc__ is set, but help(nominal_values) does not -# display it, but instead displays the documentation for the type of -# nominal_values (i.e. the documentation of its class): - -to_nominal_values = numpy.vectorize( - uncertainties.nominal_value, - otypes=[float], # Because vectorize() has side effects (dtype setting) - doc=("Applies uncertainties.nominal_value to the elements of" - " a NumPy (or unumpy) array (this includes matrices).")) - -to_std_devs = numpy.vectorize( - uncertainties.std_dev, - otypes=[float], # Because vectorize() has side effects (dtype setting) - doc=("Returns the standard deviation of the numbers with uncertainties" - " contained in a NumPy array, or zero for other objects.")) - -def unumpy_to_numpy_matrix(arr): - """ - If arr in a unumpy.matrix, it is converted to a numpy.matrix. - Otherwise, it is returned unchanged. - """ - - return arr.view(numpy.matrix) if isinstance(arr, matrix) else arr - -def nominal_values(arr): - """ - Returns the nominal values of the numbers in NumPy array arr. - - Elements that are not uncertainties.AffineScalarFunc are passed - through untouched (because a numpy.array can contain numbers with - uncertainties and pure floats simultaneously). - - If arr is of type unumpy.matrix, the returned array is a - numpy.matrix, because the resulting matrix does not contain - numbers with uncertainties. - """ - - return unumpy_to_numpy_matrix(to_nominal_values(arr)) - -def std_devs(arr): - """ - Returns the standard deviations of the numbers in NumPy array arr. - - Elements that are not uncertainties.AffineScalarFunc are given a - zero uncertainty ((because a numpy.array can contain numbers with - uncertainties and pure floats simultaneously).. - - If arr is of type unumpy.matrix, the returned array is a - numpy.matrix, because the resulting matrix does not contain - numbers with uncertainties. - """ - - return unumpy_to_numpy_matrix(to_std_devs(arr)) - -############################################################################### - -def derivative(u, var): - """ - Returns the derivative of u along var, if u is an - uncertainties.AffineScalarFunc instance, and if var is one of the - variables on which it depends. Otherwise, return 0. - """ - if isinstance(u, uncertainties.AffineScalarFunc): - try: - return u.derivatives[var] - except KeyError: - return 0. - else: - return 0. - -def wrap_array_func(func): - """ - Returns a version of the function func() that works even when - func() is given a NumPy array that contains numbers with - uncertainties. - - func() is supposed to return a NumPy array. - - This wrapper is similar to uncertainties.wrap(), except that it - handles an array argument instead of float arguments. - - func -- version that takes and returns a single NumPy array. - """ - - @uncertainties.set_doc("""\ - Version of %s(...) that works even when its first argument is a NumPy - array that contains numbers with uncertainties. - - Warning: elements of the first argument array that are not - AffineScalarFunc objects must not depend on uncertainties.Variable - objects in any way. Otherwise, the dependence of the result in - uncertainties.Variable objects will be incorrect. - - Original documentation: - %s""" % (func.__name__, func.__doc__)) - def wrapped_func(arr, *args): - # Nominal value: - arr_nominal_value = nominal_values(arr) - func_nominal_value = func(arr_nominal_value, *args) - - # The algorithm consists in numerically calculating the derivatives - # of func: - - # Variables on which the array depends are collected: - variables = set() - for element in arr.flat: - # floats, etc. might be present - if isinstance(element, uncertainties.AffineScalarFunc): - variables |= set(element.derivatives.iterkeys()) - - # If the matrix has no variables, then the function value can be - # directly returned: - if not variables: - return func_nominal_value - - # Calculation of the derivatives of each element with respect - # to the variables. Each element must be independent of the - # others. The derivatives have the same shape as the output - # array (which might differ from the shape of the input array, - # in the case of the pseudo-inverse). - derivatives = numpy.vectorize(lambda _: {})(func_nominal_value) - for var in variables: - - # A basic assumption of this package is that the user - # guarantees that uncertainties cover a zone where - # evaluated functions are linear enough. Thus, numerical - # estimates of the derivative should be good over the - # standard deviation interval. This is true for the - # common case of a non-zero standard deviation of var. If - # the standard deviation of var is zero, then var has no - # impact on the uncertainty of the function func being - # calculated: an incorrect derivative has no impact. One - # scenario can give incorrect results, however, but it - # should be extremely uncommon: the user defines a - # variable x with 0 standard deviation, sets y = func(x) - # through this routine, changes the standard deviation of - # x, and prints y; in this case, the uncertainty on y - # might be incorrect, because this program had no idea of - # the scale on which func() is linear, when it calculated - # the numerical derivative. - - # The standard deviation might be numerically too small - # for the evaluation of the derivative, though: we set the - # minimum variable shift. - - shift_var = max(var._std_dev/1e5, 1e-8*abs(var._nominal_value)) - # An exceptional case is that of var being exactly zero. - # In this case, an arbitrary shift is used for the - # numerical calculation of the derivative. The resulting - # derivative value might be quite incorrect, but this does - # not matter as long as the uncertainty of var remains 0, - # since it is, in this case, a constant. - if not shift_var: - shift_var = 1e-8 - - # Shift of all the elements of arr when var changes by shift_var: - shift_arr = array_derivative(arr, var)*shift_var - - # Origin value of array arr when var is shifted by shift_var: - shifted_arr_values = arr_nominal_value + shift_arr - func_shifted = func(shifted_arr_values, *args) - numerical_deriv = (func_shifted-func_nominal_value)/shift_var - - # Update of the list of variables and associated - # derivatives, for each element: - for (derivative_dict, derivative_value) in ( - zip(derivatives.flat, numerical_deriv.flat)): - - if derivative_value: - derivative_dict[var] = derivative_value - - # numbers with uncertainties are build from the result: - return numpy.vectorize(uncertainties.AffineScalarFunc)( - func_nominal_value, derivatives) - - # It is easier to work with wrapped_func, which represents a - # wrapped version of 'func', when it bears the same name as - # 'func' (the name is used by repr(wrapped_func)). - wrapped_func.__name__ = func.__name__ - - return wrapped_func - -############################################################################### -# Arrays - -# Vectorized creation of an array of variables: - -# ! Looking up uncertainties.Variable beforehand through '_Variable = -# uncertainties.Variable' does not result in a significant speed up: - -_uarray = numpy.vectorize(lambda v, s: uncertainties.Variable(v, s), - otypes=[object]) - -def uarray((values, std_devs)): - """ - Returns a NumPy array of numbers with uncertainties - initialized with the given nominal values and standard - deviations. - - values, std_devs -- valid arguments for numpy.array, with - identical shapes (list of numbers, list of lists, numpy.ndarray, - etc.). - """ - - return _uarray(values, std_devs) - -############################################################################### - -def array_derivative(array_like, var): - """ - Returns the derivative of the given array with respect to the - given variable. - - The returned derivative is a Numpy ndarray of the same shape as - array_like, that contains floats. - - array_like -- array-like object (list, etc.) that contains - scalars or numbers with uncertainties. - - var -- Variable object. - """ - return numpy.vectorize(lambda u: derivative(u, var), - # The type is set because an - # integer derivative should not - # set the output type of the - # array: - otypes=[float])(array_like) - -def func_with_deriv_to_uncert_func(func_with_derivatives): - """ - Returns a function that can be applied to array-like objects that - contain numbers with uncertainties (lists, lists of lists, Numpy - arrays, etc.). - - func_with_derivatives -- defines a function that takes array-like - objects containing scalars and returns an array. Both the value - and the derivatives of this function with respect to multiple - scalar parameters are calculated by func_with_derivatives(). - - func_with_derivatives(arr, input_type, derivatives, *args) returns - an iterator. The first element is the value of the function at - point 'arr' (with the correct type). The following elements are - arrays that represent the derivative of the function for each - derivative array from the iterator 'derivatives'. - - func_with_derivatives takes the following arguments: - - arr -- Numpy ndarray of scalars where the function must be - evaluated. - - input_type -- type of the input array-like object. This type is - used for determining the type that the function should return. - - derivatives -- iterator that returns the derivatives of the - argument of the function with respect to multiple scalar - variables. func_with_derivatives() returns the derivatives of - the defined function with respect to these variables. - - args -- additional arguments that define the result (example: - for the pseudo-inverse numpy.linalg.pinv: numerical cutoff). - - Examples of func_with_derivatives: inv_with_derivatives(). - """ - - def wrapped_func(array_like, *args): - """ - array_like -- array-like object that contains numbers with - uncertainties (list, Numpy ndarray or matrix, etc.). - - args -- additional arguments that are passed directly to - func_with_derivatives. - """ - - # So that .flat works even if array_like is a list. Later - # useful for faster code: - array_version = numpy.asarray(array_like) - - # Variables on which the array depends are collected: - variables = set() - for element in array_version.flat: - # floats, etc. might be present - if isinstance(element, uncertainties.AffineScalarFunc): - variables |= set(element.derivatives.iterkeys()) - - array_nominal = nominal_values(array_version) - # Function value, and derivatives at array_nominal (the - # derivatives are with respect to the variables contained in - # array_like): - func_and_derivs = func_with_derivatives( - array_nominal, - type(array_like), - (array_derivative(array_version, var) for var in variables), - *args) - - func_nominal_value = func_and_derivs.next() - - if not variables: - return func_nominal_value - - # The result is built progressively, with the contribution of - # each variable added in turn: - - # Calculation of the derivatives of the result with respect to - # the variables. - derivatives = numpy.array( - [{} for _ in xrange(func_nominal_value.size)], dtype=object) - derivatives.resize(func_nominal_value.shape) - - # Memory-efficient approach. A memory-hungry approach would - # be to calculate the matrix derivatives will respect to all - # variables and then combine them into a matrix of - # AffineScalarFunc objects. The approach followed here is to - # progressively build the matrix of derivatives, by - # progressively adding the derivatives with respect to - # successive variables. - for (var, deriv_wrt_var) in zip(variables, func_and_derivs): - - # Update of the list of variables and associated - # derivatives, for each element: - for (derivative_dict, derivative_value) in zip( - derivatives.flat, deriv_wrt_var.flat): - if derivative_value: - derivative_dict[var] = derivative_value - - # An array of numbers with uncertainties are built from the - # result: - result = numpy.vectorize(uncertainties.AffineScalarFunc)( - func_nominal_value, derivatives) - - # Numpy matrices that contain numbers with uncertainties are - # better as unumpy matrices: - if isinstance(result, numpy.matrix): - result = result.view(matrix) - - return result - - return wrapped_func - -########## Matrix inverse - -def inv_with_derivatives(arr, input_type, derivatives): - """ - Defines the matrix inverse and its derivatives. - - See the definition of func_with_deriv_to_uncert_func() for its - detailed semantics. - """ - - inverse = numpy.linalg.inv(arr) - # The inverse of a numpy.matrix is a numpy.matrix. It is assumed - # that numpy.linalg.inv is such that other types yield - # numpy.ndarrays: - if issubclass(input_type, numpy.matrix): - inverse = inverse.view(numpy.matrix) - yield inverse - - # It is mathematically convenient to work with matrices: - inverse_mat = numpy.asmatrix(inverse) - - # Successive derivatives of the inverse: - for derivative in derivatives: - derivative_mat = numpy.asmatrix(derivative) - yield -inverse_mat * derivative_mat * inverse_mat - -_inv = func_with_deriv_to_uncert_func(inv_with_derivatives) -_inv.__doc__ = """\ - Version of numpy.linalg.inv that works with array-like objects - that contain numbers with uncertainties. - - The result is a unumpy.matrix if numpy.linalg.pinv would return a - matrix for the array of nominal values. - - Analytical formulas are used. - - Original documentation: - %s - """ % numpy.linalg.inv.__doc__ - -########## Matrix pseudo-inverse - -def pinv_with_derivatives(arr, input_type, derivatives, rcond): - """ - Defines the matrix pseudo-inverse and its derivatives. - - Works with real or complex matrices. - - See the definition of func_with_deriv_to_uncert_func() for its - detailed semantics. - """ - - inverse = numpy.linalg.pinv(arr, rcond) - # The pseudo-inverse of a numpy.matrix is a numpy.matrix. It is - # assumed that numpy.linalg.pinv is such that other types yield - # numpy.ndarrays: - if issubclass(input_type, numpy.matrix): - inverse = inverse.view(numpy.matrix) - yield inverse - - # It is mathematically convenient to work with matrices: - inverse_mat = numpy.asmatrix(inverse) - - # Formula (4.12) from The Differentiation of Pseudo-Inverses and - # Nonlinear Least Squares Problems Whose Variables - # Separate. Author(s): G. H. Golub and V. Pereyra. Source: SIAM - # Journal on Numerical Analysis, Vol. 10, No. 2 (Apr., 1973), - # pp. 413-432 - - # See also - # http://mathoverflow.net/questions/25778/analytical-formula-for-numerical-derivative-of-the-matrix-pseudo-inverse - - # Shortcuts. All the following factors should be numpy.matrix objects: - PA = arr*inverse_mat - AP = inverse_mat*arr - factor21 = inverse_mat*inverse_mat.H - factor22 = numpy.eye(arr.shape[0])-PA - factor31 = numpy.eye(arr.shape[1])-AP - factor32 = inverse_mat.H*inverse_mat - - # Successive derivatives of the inverse: - for derivative in derivatives: - derivative_mat = numpy.asmatrix(derivative) - term1 = -inverse_mat*derivative_mat*inverse_mat - derivative_mat_H = derivative_mat.H - term2 = factor21*derivative_mat_H*factor22 - term3 = factor31*derivative_mat_H*factor32 - yield term1+term2+term3 - -# Default rcond argument for the generalization of numpy.linalg.pinv: -try: - # Python 2.6+: - _pinv_default = numpy.linalg.pinv.__defaults__[0] -except AttributeError: - _pinv_default = 1e-15 - -_pinv_with_uncert = func_with_deriv_to_uncert_func(pinv_with_derivatives) - -@uncertainties.set_doc(""" - Version of numpy.linalg.pinv that works with array-like objects - that contain numbers with uncertainties. - - The result is a unumpy.matrix if numpy.linalg.pinv would return a - matrix for the array of nominal values. - - Analytical formulas are used. - - Original documentation: - %s - """ % numpy.linalg.pinv.__doc__) -def _pinv(array_like, rcond=_pinv_default): - return _pinv_with_uncert(array_like, rcond) - -########## Matrix class - -class matrix(numpy.matrix): - # The name of this class is the same as NumPy's, which is why it - # does not follow PEP 8. - """ - Class equivalent to numpy.matrix, but that behaves better when the - matrix contains numbers with uncertainties. - """ - - def __rmul__(self, other): - # ! NumPy's matrix __rmul__ uses an apparently a restrictive - # dot() function that cannot handle the multiplication of a - # scalar and of a matrix containing objects (when the - # arguments are given in this order). We go around this - # limitation: - if numeric.isscalar(other): - return numeric.dot(self, other) - else: - return numeric.dot(other, self) # The order is important - - # The NumPy doc for getI is empty: - # @uncertainties.set_doc(numpy.matrix.getI.__doc__) - def getI(self): - "Matrix inverse of pseudo-inverse" - - # numpy.matrix.getI is OK too, but the rest of the code assumes that - # numpy.matrix.I is a property object anyway: - - M, N = self.shape - if M == N: - func = _inv - else: - func = _pinv - return func(self) - - - # ! In Python >= 2.6, this could be simplified as: - # I = numpy.matrix.I.getter(__matrix_inverse) - I = property(getI, numpy.matrix.I.fset, numpy.matrix.I.fdel, - numpy.matrix.I.__doc__) - - @property - def nominal_values(self): - """ - Nominal value of all the elements of the matrix. - """ - return nominal_values(self) - - std_devs = std_devs - -def umatrix(*args): - """ - Constructs a matrix that contains numbers with uncertainties. - - The input data is the same as for uarray(...): a tuple with the - nominal values, and the standard deviations. - - The returned matrix can be inverted, thanks to the fact that it is - a unumpy.matrix object instead of a numpy.matrix one. - """ - - return uarray(*args).view(matrix) - -############################################################################### - -def define_vectorized_funcs(): - """ - Defines vectorized versions of functions from uncertainties.umath. - - Some functions have their name translated, so as to follow NumPy's - convention (example: math.acos -> numpy.arccos). - """ - - this_module = sys.modules[__name__] - # NumPy does not always use the same function names as the math - # module: - func_name_translations = dict( - (f_name, 'arc'+f_name[1:]) - for f_name in ['acos', 'acosh', 'asin', 'atan', 'atan2', 'atanh']) - - new_func_names = [func_name_translations.get(function_name, function_name) - for function_name in umath.many_scalars_to_scalar_funcs] - - for (function_name, unumpy_name) in zip( - umath.many_scalars_to_scalar_funcs, new_func_names): - - # ! The newly defined functions (uncertainties.unumpy.cos, etc.) - # do not behave exactly like their NumPy equivalent (numpy.cos, - # etc.): cos(0) gives an array() and not a - # numpy.float... (equality tests succeed, though). - func = getattr(umath, function_name) - setattr( - this_module, unumpy_name, - numpy.vectorize(func, - # If by any chance a function returns, - # in a particular case, an integer, - # side-effects in vectorize() would - # fix the resulting dtype to integer, - # which is not what is wanted: - otypes=[object], - doc="""\ -Vectorized version of umath.%s. - -Original documentation: -%s""" % (function_name, func.__doc__))) - - __all__.append(unumpy_name) - -define_vectorized_funcs() diff --git a/sigproc/lmfit/uncertainties/unumpy/ulinalg.py b/sigproc/lmfit/uncertainties/unumpy/ulinalg.py deleted file mode 100644 index 1eef043af..000000000 --- a/sigproc/lmfit/uncertainties/unumpy/ulinalg.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -This module provides uncertainty-aware functions that generalize some -of the functions from numpy.linalg. - -(c) 2010-2013 by Eric O. LEBIGOT (EOL) . -""" - -from ivs.sigproc.lmfit.uncertainties import __author__ -import core - -# This module cannot import unumpy because unumpy imports this module. - -__all__ = ['inv', 'pinv'] - -inv = core._inv -pinv = core._pinv - diff --git a/sigproc/testFit.py b/sigproc/testFit.py index eb2c411e2..1c24bab9e 100644 --- a/sigproc/testFit.py +++ b/sigproc/testFit.py @@ -5,62 +5,60 @@ """ import copy import numpy as np -from ivs.sigproc.lmfit import Minimizer from ivs.sigproc import fit, lmfit, funclib import unittest try: - import mock from mock import patch noMock = False except Exception: noMock = True class FitTestCase(unittest.TestCase): - + def assertArrayEqual(self, l1, l2, msg=None): for i, (f1, f2) in enumerate(zip(l1,l2)): msg_ = "Array not equal on: %i, %s != %s"%(i, str(f1), str(f2)) - if msg != None: msg_ = msg_ + ", " + msg + if msg != None: msg_ = msg_ + ", " + msg self.assertEqual(f1,f2, msg=msg_) - + def assertArrayAlmostEqual(self, l1,l2,places=None,delta=None, msg=None): for i, (f1, f2) in enumerate(zip(l1, l2)): msg_ = "Array not equal on: %i, %s != %s"%(i, str(f1), str(f2)) - if msg != None: msg_ = msg_ + ", " + msg + if msg != None: msg_ = msg_ + ", " + msg self.assertAlmostEqual(f1,f2,places=places, delta=delta, msg=msg_) - + class TestCase1Function(FitTestCase): - + def setUp(self): self.pnames = ['ampl', 'freq', 'phase'] self.function = lambda p, x: p[0] * np.sin(2*np.pi*(p[1]*x + p[2])) self.Dfunction = lambda p, x: p[0] * np.cos(2*np.pi*(p[1]*x + p[2])) self.model = fit.Function(function=self.function, par_names=self.pnames,\ jacobian=self.Dfunction) - + self.value = [1.5, 0.12, 3.14] self.bounds = [[1.0, 2.0], [-0.20,0.20], [0.0, 6.24]] self.vary = [True, True, True] self.expression = ['<10', None, None] self.model.setup_parameters(values=self.value, bounds=self.bounds, vary=self.vary,\ exprs=self.expression) - + def testConstructor(self): """ sigproc.fit.Function constructor """ self.assertEqual(self.model.function, self.function) self.assertEqual(self.model.jacobian, self.Dfunction) self.assertEqual(self.model.par_names, self.pnames) - self.assertListEqual(self.pnames, self.model.parameters.keys()) - + self.assertListEqual(self.pnames, list(self.model.parameters.keys())) + def testEvaluate(self): """ sigproc.fit.Function evaluate """ pars = [1.5, 0.12, 3.14] x = np.linspace(0,10,1000) y1 = self.function(pars, x) - y2 = self.model.evaluate(x, pars) + y2 = self.model.evaluate(x, pars) self.assertTrue(np.all(y1 == y2)) - + def testEvaluateJacobian(self): """ sigproc.fit.Function evaluate_jacobian """ pars = [1.5, 0.12, 3.14] @@ -68,7 +66,7 @@ def testEvaluateJacobian(self): y1 = self.Dfunction(pars, x) y2 = self.model.evaluate_jacobian(x, pars) self.assertTrue(np.all(y1 == y2)) - + def testSetupParameters(self): """ sigproc.fit.Function setup_parameters """ val,err,var,min,max,expr = self.model.get_parameters(full_output=True) @@ -77,7 +75,7 @@ def testSetupParameters(self): self.assertArrayEqual(min, np.array(self.bounds)[:,0].tolist()) self.assertArrayEqual(max, np.array(self.bounds)[:,1].tolist()) self.assertArrayEqual(expr, self.expression) - + def testUpdateParameters(self): """ sigproc.fit.Function update_parameter """ self.model.update_parameter('ampl', value=1.75, min=0.0, max=3.5, vary=False) @@ -86,42 +84,42 @@ def testUpdateParameters(self): self.assertEqual(0.0, min[0]) self.assertEqual(3.5, max[0]) self.assertEqual(False, vary[0]) - + def testBoundaryAdapting(self): """ sigproc.fit.Function setup_parameters boundary adapting """ pars = [2.0, 0.10, 3.14] - bounds = [(1.0, 1.5), (0.50, 1.0), (0.0, 6.28)] + bounds = [(1.0, 1.5), (0.50, 1.0), (0.0, 6.28)] self.model.setup_parameters(values=pars, bounds=bounds) val, err, var, min, max, expr = self.model.get_parameters(full_output=True) self.assertArrayEqual(min, [1.0, 0.10, 0.0]) self.assertArrayEqual(max, [2.0, 1.0, 6.28]) - + class TestCase2Model(unittest.TestCase): - + def setUp(self): self.pnames1 = ['p1', 'p2'] self.function1 = lambda p, x: p[0] + p[1] * x self.func1 = fit.Function(function=self.function1, par_names=self.pnames1) - + self.values1 = [2.5, 1.0] self.func1.setup_parameters(values=self.values1) - + self.pnames2 = ['p3', 'p4'] self.function2 = lambda p, x: p[0] * x**2 + p[1] * x**3 self.func2 = fit.Function(function=self.function2, par_names=self.pnames2) - + self.values2 = [-1.0, 0.5] self.func2.setup_parameters(values=self.values2) - + self.mexpr = lambda x: x[0] + x[1] self.model = fit.Model(functions=[self.func1, self.func2], expr=self.mexpr) - + def testConstructor(self): """ sigproc.fit.Model constructor """ self.assertListEqual(self.model.functions, [self.func1, self.func2]) self.assertEqual(self.model.expr, self.mexpr) self.assertNotEqual(self.model.par_names, None) - + def testPullParameters(self): """ sigproc.fit.Model pull parameters """ names = self.model.par_names @@ -131,14 +129,14 @@ def testPullParameters(self): self.assertTrue(self.pnames2[1], names[1][1]) values = self.model.get_parameters()[0].tolist() self.assertListEqual(values, np.ravel([self.values1, self.values2]).tolist()) - + def testPushParameters(self): """ sigproc.fit.Model push parameters """ self.model.parameters['p1_0'].value = 2.4 self.assertNotEqual(self.func1.parameters['p1'].value, 2.4) self.model.push_parameters() self.assertEqual(self.func1.parameters['p1'].value, 2.4) - + def testEvaluate(self): """ sigproc.fit.Model evaluate """ x = np.linspace(0,10,1000) @@ -147,7 +145,7 @@ def testEvaluate(self): y3 = self.mexpr([self.func1.evaluate(x), self.func2.evaluate(x)]) self.assertTrue(np.all(y1==y2)) self.assertTrue(np.all(y1==y3)) - + def testUpdateParameters(self): """ sigproc.fit.Model update_parameter """ self.model.update_parameter(parameter='p1_0', value=1.5, min=0.0, max=2.5, expr='<=2.5') @@ -155,191 +153,191 @@ def testUpdateParameters(self): self.assertEqual(self.model.parameters['p1_0'].min, 0.0) self.assertEqual(self.model.parameters['p1_0'].max, 2.5) self.assertEqual(self.model.parameters['p1_0'].expr, '<=2.5') - + with patch.object(self.model, 'push_parameters') as mock: self.model.update_parameter(parameter='p1_0', value=2.5) mock.assert_called_once() class TestCase3Minimizer(FitTestCase): - @classmethod + @classmethod def setUpClass(cls): - + parameters = lmfit.Parameters() parameters.add('ampl', value=10) parameters.add('freq', value=1.) parameters.add('phase', value=3.14) parameters.add('const', value=-1.) - + model = funclib.sine() - + x = np.linspace(0,10, num=100) y = model.evaluate(x, parameters) - + cls.parameters = parameters cls.model = model cls.x = x cls.y = y cls.weights = np.ones_like(y) cls.errors = np.ones_like(y) - + @unittest.skipIf(noMock, "Mock not installed") @patch('ivs.sigproc.lmfit.Minimizer') def test1setupResiduals(self, mocked_class): """ sigproc.fit.Minimizer _setup_residual_function """ - + result = fit.minimize(self.x, self.y, self.model) - + msg = 'attr residuals is not set' self.assertTrue(hasattr(result, 'residuals'), msg=msg) - + msg = 'residuals are not correctly calculated' - residuals = result.residuals(self.parameters, self.x, self.y, + residuals = result.residuals(self.parameters, self.x, self.y, weights=self.weights) self.assertTrue(np.all(np.abs(residuals) < 1.0), msg=msg) - + @unittest.skipIf(noMock, "Mock not installed") @patch('ivs.sigproc.lmfit.Minimizer') def test2setupJacobian(self, mocked_class): """ sigproc.fit.Minimizer _setup_jacobian_function """ result = fit.minimize(self.x, self.y, self.model, err=self.errors) - + msg = 'attr jacobian is not set' self.assertTrue(hasattr(result, 'jacobian'), msg=msg) - + msg = 'jacobian should be None in this case' self.assertTrue(result.jacobian == None, msg=msg) - + @unittest.skipIf(noMock, "Mock not installed") @patch('ivs.sigproc.lmfit.Minimizer') def test3perturbdata(self, mocked_class): """ sigproc.fit.Minimizer _perturb_input_data """ result = fit.minimize(self.x, self.y, self.model, errors=self.errors) - + result.x = self.x.reshape(2,50) result.y = self.y.reshape(2,50) result.errors = result.errors.reshape(2,50) - + np.random.seed(11) npoints = 100 y_ = result._perturb_input_data(100) dy = np.abs(np.ravel(y_[0]) - np.ravel(result.y)) - - print dy - + + print(dy) + msg = 'Perturbed data has wrong shape' self.assertEqual(y_.shape, (100,) + result.y.shape, msg=msg) - + msg = 'Perturbed data is NOT perturbed' self.assertTrue(np.all(dy > 0.), msg=msg) - + msg = 'Perturbed data is to strongly perturbed' self.assertTrue(np.all(dy < 3.), msg=msg) - + @unittest.skipIf(noMock, "Mock not installed") @patch('ivs.sigproc.lmfit.Minimizer') def test4mcErrorFromParameters(self, mocked_class): """ sigproc.fit.Minimizer _mc_error_from_parameters """ pass - + class TestCase4FittingFunction(FitTestCase): """ Integration test testing the fitting of a simple Function """ - - @classmethod + + @classmethod def setUpClass(cls): #-- setup the function pnames = ['ampl', 'freq', 'phase'] function = lambda p, x: p[0] * np.sin(2*np.pi*(p[1]*x + p[2])) Dfunction = lambda p, x: p[0] * np.cos(2*np.pi*(p[1]*x + p[2])) - fitFunc = fit.Function(function=function, par_names=pnames) + fitFunc = fit.Function(function=function, par_names=pnames) value = [1.5, 0.12, 3.14] bounds = [[1.0, 2.0], [0.0,0.25], [0.0, 6.28]] vary = [True, True, True] fitFunc.setup_parameters(values=value, bounds=bounds, vary=vary) - + #-- create fake data x = np.linspace(0,10,1000) np.random.seed(1111) y = fitFunc.evaluate(x) + np.random.normal(loc=0.0, scale=0.1, size=1000) - + #-- Change starting parameters fitFunc.update_parameter(parameter='ampl', values=1.2) fitFunc.update_parameter(parameter='freq', values=0.10) fitFunc.update_parameter(parameter='phase', values=3.0) - + cls.x = x cls.y = y cls.pnames = pnames cls.value = value cls.fitFunc = fitFunc cls.dFunc = Dfunction - + def setUp(self): self.model = copy.copy(self.fitFunc) - + def test1minimize(self): """ I sigproc.fit.Minimizer Function minimize """ model = self.model - + result = fit.minimize(self.x, self.y, model) values = model.get_parameters()[0] - + msg = 'Fit did not converge to the correct values' self.assertAlmostEqual(values[0], self.value[0], places=2, msg=msg) self.assertAlmostEqual(values[1], self.value[1], places=2, msg=msg) self.assertAlmostEqual(values[2], self.value[2], places=2, msg=msg) - + def test2grid_minimize(self): """ I sigproc.fit.Minimizer Function grid_minimize """ model = self.model points = 100 fitters, newmodels, chisqrs = fit.grid_minimize(self.x, self.y, model, - parameters=self.pnames, points=points, + parameters=self.pnames, points=points, return_all=True, verbose=False) values = newmodels[0].get_parameters()[0] pars1 = [mod_.parameters['ampl'] for mod_ in newmodels] - + msg = 'Not correct amount of fitting points' self.assertEqual(len(fitters), points, msg=msg) self.assertEqual(len(newmodels), points, msg=msg) self.assertEqual(len(chisqrs), points, msg=msg) - + msg = 'Parameters not well distributed' for i, j in zip(np.random.randint(0,points, size=50), np.random.randint(0,points, size=50)): if i != j: self.assertNotEqual(pars1[i], pars1[j], msg=msg) - + msg = 'Chi2 is not ordened correctly' self.assertTrue(chisqrs[0] == np.min(chisqrs), msg=msg) self.assertTrue(np.all(np.diff(chisqrs) >= 0), msg=msg) - + msg = 'Fit did not converge to the correct values' self.assertArrayAlmostEqual(values[0:2], self.value[0:2], places=2, msg=msg) - + def test3ci_interval(self): """ I sigproc.fit.Minimizer Function calculate_CI """ model = self.model - + result = fit.minimize(self.x, self.y, model) ci = result.calculate_CI(parameters=None, sigmas=[0.674, 0.997]) - + expected_ci = "\n\t{'ampl': {0.674: (1.4955, 1.5036), 0.997: (1.4873, 1.5117)}"\ +"\n\t'phase': {0.674: (3.1372, 3.1394), 0.997: (3.1349, 3.1417)},"\ +"\n\t'freq': {0.674: (0.1202, 0.1206), 0.997: (0.1197, 0.1210)}}" - - + + msg = 'dict output format is not correct:\nexpected: '+expected_ci+\ '\nreceived: \n\t'+str(ci) self.assertTrue('ampl' in ci, msg=msg) self.assertTrue('phase' in ci, msg=msg) self.assertTrue('freq' in ci, msg=msg) - + self.assertTrue(0.674 in ci['ampl'] and 0.997 in ci['ampl'], msg=msg) self.assertTrue(0.674 in ci['phase'] and 0.997 in ci['phase'], msg=msg) self.assertTrue(0.674 in ci['freq'] and 0.997 in ci['freq'], msg=msg) - + msg = 'ci results are not correct, expected: \n' + expected_ci self.assertArrayAlmostEqual(ci['ampl'][0.674], [1.4955, 1.5036], places=2, msg=msg) self.assertArrayAlmostEqual(ci['ampl'][0.997], [1.4873, 1.5117], places=2, msg=msg) @@ -347,28 +345,28 @@ def test3ci_interval(self): self.assertArrayAlmostEqual(ci['phase'][0.997], [3.1349, 3.1417], places=2, msg=msg) self.assertArrayAlmostEqual(ci['freq'][0.674], [0.1202, 0.1206], places=2, msg=msg) self.assertArrayAlmostEqual(ci['freq'][0.997], [0.1197, 0.1210], places=2, msg=msg) - + msg = 'ci intervals are not stored in the parameter object' - for name, param in model.parameters.items(): + for name, param in list(model.parameters.items()): self.assertTrue(0.674 in param.cierr, msg=msg) self.assertTrue(0.997 in param.cierr, msg=msg) - + msg = 'Reruning calculate_CI fails and raises an exception!' try: result.calculate_CI(parameters=None, sigma=0.85, maxiter=200, short_output=True) result.calculate_CI(parameters=None, sigma=0.95, maxiter=200, short_output=True) except Exception: self.fail(msg) - + def test4ci2d_interval(self): """ I sigproc.fit.Minimizer Function calculate_CI_2D """ model = self.model - + result = fit.minimize(self.x, self.y, model) x, y, ci = result.calculate_CI_2D(xpar='ampl', ypar='freq', res=10, limits=None, ctype='prob') values, errors = model.get_parameters() - + msg = 'Auto limits are not value +- 5 * stderr' vmin = values - 5*errors vmax = values + 5*errors @@ -376,7 +374,7 @@ def test4ci2d_interval(self): self.assertAlmostEqual(max(x), vmax[0], places=4, msg=msg) self.assertAlmostEqual(min(y), vmin[1], places=4, msg=msg) self.assertAlmostEqual(max(y), vmax[1], places=4, msg=msg) - + msg = 'Grid values are not correctly distributed' exp1 = [1.4790, 1.4835, 1.4881, 1.4927, 1.4972, 1.5018, 1.5064, 1.5109, 1.5155, 1.5200] @@ -388,41 +386,41 @@ def test4ci2d_interval(self): msg = 'resolution of the grid is not correct' self.assertEqual(len(ci), 10, msg=msg) self.assertEqual(len(ci[0]), 10, msg=msg) - + msg = 'CI values are not correct' - exp1 = [99.9996, 99.9572, 98.2854, 79.4333, 27.5112, + exp1 = [99.9996, 99.9572, 98.2854, 79.4333, 27.5112, 25.6265, 77.7912, 98.0525, 99.9489, 99.9995] exp2 = [99.9996, 99.9565, 98.2751, 79.4030, 27.5112, 25.6436, 77.8194, 98.0628, 99.9496, 99.9996] self.assertArrayAlmostEqual(ci[4], exp1, places=2, msg=msg) self.assertArrayAlmostEqual(ci[:,4], exp2, places=2, msg=msg) - + def test5mc_error(self): """ I sigproc.fit.Minimizer Function calculate_MC_error """ result = fit.minimize(self.x, self.y, self.model) - + np.random.seed(11) mcerrors = result.calculate_MC_error(errors=0.5, points=100, short_output=True, verbose=False) - + msg = 'Short output returned wrong type (array expected)' self.assertEqual(np.shape(mcerrors), (3,), msg=msg) - + msg = 'Calculated MC errors are wrong' self.assertArrayAlmostEqual(mcerrors, [0.02156, 0.00108, 0.00607], places=3, msg=msg) - + np.random.seed(11) - mcerrors = result.calculate_MC_error(errors=0.5, points=50, short_output=False, + mcerrors = result.calculate_MC_error(errors=0.5, points=50, short_output=False, verbose=False) - + msg = 'Long output returned wrong type (dict expected)' self.assertEqual(type(mcerrors), dict, msg=msg) - + msg = 'Not all parameters are included in output' self.assertTrue('ampl' in mcerrors, msg=msg) self.assertTrue('freq' in mcerrors, msg=msg) self.assertTrue('phase' in mcerrors, msg=msg) - + msg = 'MC error is not stored in the parameters object' params = self.model.parameters self.assertEqual(params['ampl'].mcerr, mcerrors['ampl'], msg=msg) @@ -433,46 +431,46 @@ class TestCase5IntegrationJacobian(FitTestCase): """ Integration test testing the fitting of a Function with Jacobian """ - @classmethod + @classmethod def setUpClass(cls): "===Exponential_decay_Jacobian===" - - pnames = ['p1', 'p2', 'p3'] - def func(var, x): + pnames = ['p1', 'p2', 'p3'] + + def func(var, x): return var[0]*np.exp(-var[1]*x)+var[2] def dfunc(var,x): v = np.exp(-var[1]*x) return np.array([-v,+var[0]*x*v,-np.ones(len(x))]).T - + cls.function = func cls.jacobian = dfunc - + model = fit.Function(function=func, par_names=pnames, jacobian=dfunc) pars = [10, 10, 10] bounds = [(0, 20), (0, 20), (0, 20)] vary = [True, True, True] model.setup_parameters(values=pars, bounds=bounds, vary=vary) cls._model = model - + #-- Creating data to fit xs = np.linspace(0,4,50) ys = func([2.5,1.3,0.5],xs) np.random.seed(3333) err = 0.1*np.random.normal(size=len(xs)) yn = ys + err - + cls.x = xs cls.y = yn cls.err = np.abs(err) - + def setUp(self): self.model = copy.deepcopy(self._model) - + def test1minimize(self): """ I sigproc.fit.Minimizer Jacobian minimize """ - + self.model.setup_parameters(value=[3.0, 1.0, 1.0]) result = fit.minimize(self.x, self.y, self.model) names = self.model.parameters.name @@ -480,102 +478,102 @@ def test1minimize(self): err = self.model.parameters.stderr valr = [2.4272, 1.2912, 0.4928] errr = [0.0665, 0.0772, 0.0297] - - print self.model.param2str(accuracy=4, output='result') - + + print(self.model.param2str(accuracy=4, output='result')) + msg = 'Resulting value for {:s} is not correct within {:.0f}\%' pc = 0.05 # 5% difference is allowed for i in range(3): - self.assertAlmostEqual(val[i], valr[i], delta = pc*valr[i], + self.assertAlmostEqual(val[i], valr[i], delta = pc*valr[i], msg=msg.format(names[i], pc*100)) - + msg = 'Resulting std-error for {:s} is not correct within {:.0f}\%' pc = 0.1 # 10% difference is allowed for i in range(3): self.assertAlmostEqual(err[i], errr[i], delta = pc*errr[i], msg=msg.format(names[i], pc*100)) - + def test2grid_minimize(self): """ I sigproc.fit.Minimizer Jacobian grid_minimize """ - + result = fit.grid_minimize(self.x, self.y, self.model, points=100) names = self.model.parameters.name val = self.model.parameters.value err = self.model.parameters.stderr valr = [2.4272, 1.2912, 0.4928] errr = [0.4345, 0.3796, 0.0921] - - print self.model.param2str(accuracy=4, output='result') - + + print(self.model.param2str(accuracy=4, output='result')) + msg = 'Resulting value for {:s} is not correct within {:.0f}\%' pc = 0.05 # 5% difference is allowed for i in range(3): - self.assertAlmostEqual(val[i], valr[i], delta = pc*valr[i], + self.assertAlmostEqual(val[i], valr[i], delta = pc*valr[i], msg=msg.format(names[i], pc*100)) - + msg = 'Resulting std-error for {:s} is not correct within {:.0f}\%' pc = 0.1 # 10% difference is allowed for i in range(3): self.assertAlmostEqual(err[i], errr[i], delta = pc*errr[i], msg=msg.format(names[i], pc*100)) - + def test3ci_interval(self): """ I sigproc.fit.Minimizer Jacobian calculate_CI """ self.model.setup_parameters(value=[3.0, 1.0, 1.0]) - + result = fit.minimize(self.x, self.y, self.model) ci = result.calculate_CI(parameters=None, sigmas=[0.994]) cir = [[2.2378,2.6210],[1.0807,1.5276],[0.4015,0.5740]] val = self.model.parameters.value names = self.model.parameters.name - + msg = 'The 99.4\% CI interval for {:s} is not correct within {:.0f}\%' pc = 0.05 for i,p in enumerate(names): self.assertArrayAlmostEqual(ci[p][0.994], cir[i], delta = pc*val[i], msg=msg.format(p, pc*100)) - + def test4ci2d_interval(self): """ I sigproc.fit.Minimizer Jacobian calculate_CI_2D """ self.model.setup_parameters(value=[3.0, 1.0, 1.0]) - + result = fit.minimize(self.x, self.y, self.model) x, y, ci = result.calculate_CI_2D(xpar='p1', ypar='p2', res=10, limits=None, ctype='prob') - + cir = [99.9977, 99.9019, 97.5451, 74.5012, 20.5787, 38.3632, 87.5572, 99.1614, 99.9734, 99.9994] - + msg = 'The 2D CI values are not correct within 2\%' pc = 0.05 for i in range(3): self.assertArrayAlmostEqual(ci[4], cir, delta = 2, msg = msg) - + def test5mc_error(self): """ I sigproc.fit.Minimizer Jacobian calculate_MC_error """ self.model.setup_parameters(value=[3.0, 1.0, 1.0]) result = fit.minimize(self.x, self.y, self.model) result.calculate_MC_error(errors=self.err, points=100, short_output=True, verbose=False) - + names = self.model.parameters.name err = self.model.parameters.mcerr errr = [0.0795, 0.0688, 0.0281] - + msg = 'Resulting MC-error for {:s} is not correct within {:.0f}\%' pc = 0.05 # 10% difference is allowed for i in range(3): self.assertAlmostEqual(err[i], errr[i], delta = pc*errr[i], msg=msg.format(names[i], pc*100)) - - + + class TestCase6FittingModel(FitTestCase): """ Integration test testing the fitting of a simple Model """ - - @classmethod + + @classmethod def setUpClass(cls): gauss1 = funclib.gauss() pars = [-0.75,1.0,0.1,1] @@ -584,97 +582,97 @@ def setUpClass(cls): pars = [0.22,1.0,0.01,0.0] vary = [True, True, True, False] gauss2.setup_parameters(values=pars, vary=vary) - + mymodel = fit.Model(functions=[gauss1, gauss2]) - + np.random.seed(1111) x = np.linspace(0.5,1.5, num=1000) y = mymodel.evaluate(x) noise = np.random.normal(0.0, 0.015, size=len(x)) y = y+noise - + pars = [-0.70,1.0,0.11,0.95] gauss1.setup_parameters(values=pars) pars = [0.27,1.0,0.005,0.0] vary = [True, True, True, False] gauss2.setup_parameters(values=pars, vary=vary) - + cls.x = x cls.y = y cls.value1 = [-0.75,1.0,0.1,1] cls.value2 = [0.22,1.0,0.01,0.0] cls.fitModel = mymodel - + def testMinimize(self): """ I sigproc.fit.Minimizer Model minimize """ model = self.fitModel - + result = fit.minimize(self.x, self.y, model) functions = model.functions values1 = functions[0].get_parameters()[0] values2 = functions[1].get_parameters()[0] - + self.assertArrayAlmostEqual(values1, self.value1, places=2) self.assertArrayAlmostEqual(values2, self.value2, places=2) class TestCase7Funclib(unittest.TestCase): - + def testFunclibFunctions(self): """ sigproc.funclib: all functions """ def check_function(fname, param, x, expected, dec=5, **kwargs): y = funclib.evaluate(fname,np.array(x),param, **kwargs) y = np.round(y, dec) self.assertTrue(np.all(y == expected), msg=fname+" Failed") - + #-- blackbody check_function('blackbody', [5798, 1e-3], [3000, 4120, 10734], [3141.80541, 6106.61968, 2304.17307]) - + #-- rayleigh_jeans check_function('rayleigh_jeans', [5798, 1e-5], [3000, 4120, 10734], [14853.11541, 4175.55021, 90.62671]) - + #-- wien check_function('wien', [5798, 1e-3], [3000, 4120, 10734], [3141.00219, 6091.82801, 2075.87277]) - + #-- box_transit check_function('box_transit', [2.,0.4,0.1,0.3,0.5], [0.74, 0.75, 2.74, 2.75], [1.5, 2.0, 2.0, 1.5]) - + #-- polynomial check_function('polynomial', [1,2.5,1.2], [0.27, 1.34, 2.01], [1.9479, 6.3456, 10.2651], d=2) - + #-- soft_parabola check_function('soft_parabola', [1.,0,1.,0.25], [-0.87, 0, 1.0], [0.83796, 1.0, 0.0]) - + #-- gauss check_function('gauss', [5,1.,2.,0.5], [-5, 1.0, 4.23], [0.55554, 5.5, 1.85707]) - + #-- voigt check_function('voigt', [20.,1.,1.5,3.,0.5], [-5, 1.0, 4.23], [0.97541, 2.28835, 1.5696]) - + #-- lorentz check_function('lorentz', [5,1.,2.,0.5], [-5, 1.0, 4.23], [0.625, 1.75, 0.84643]) - + #-- sine check_function('sine', [1.,2.,0,0], [0.24, 2.0, 7.32], [0.12533, 0.0, -0.77051]) - + #-- sine_linfreqshift check_function('sine_linfreqshift', [1.,0.5,0,0,.5], [0.24, 2.0, 7.32], [0.74761, 0.0, 0.34228]) - + #-- sine_expfreqshift check_function('sine_expfreqshift', [1.,0.5,0,0,1.2], [0.24, 2.0, 7.32], [0.69665, 0.96315, -0.88957]) - + #-- sine_orbit check_function('sine_orbit', [1.,2.,0,0,0.1,10.,0.1], [0.24, 2.0, 7.32], [0.01663, 0.63673, -0.15785]) - + #-- kepler_orbit check_function('kepler_orbit', [2.5,0.,0.5,0,3,1.], [0.24, 2.0, 7.32], [2.55123, 0.63411, 3.34141]) - + #-- power_law check_function('power_law', [2.,3.,1.5,0.0,0.5], [0.24, 2.0, 7.32], [1.74151, 0.62741, 0.51925]) - + #if __name__ == '__main__': - #unittest.main() - - + #unittest.main() + + #suite = [unittest.TestLoader().loadTestsFromTestCase(LMfitTestCase)] #suite += unittest.TestLoader().loadTestsFromTestCase(FunctionTestCase) #allTests = unittest.TestSuite(suite) diff --git a/sigproc/testLMFit.py b/sigproc/testLMFit.py index 9edb66424..ea0f7708e 100644 --- a/sigproc/testLMFit.py +++ b/sigproc/testLMFit.py @@ -6,45 +6,44 @@ import copy import numpy as np from ivs.sigproc.lmfit import Minimizer, ConfidenceInterval -from ivs.sigproc.lmfit import Parameter, Parameters +from ivs.sigproc.lmfit import Parameter from ivs.sigproc import lmfit import unittest try: import mock from mock import MagicMock - from mock import patch noMock = False except Exception: noMock = True class FitTestCase(unittest.TestCase): - + def assertArrayEqual(self, l1, l2, msg=None): for i, (f1, f2) in enumerate(zip(l1,l2)): msg_ = "Array not equal on: %i, %s != %s"%(i, str(f1), str(f2)) - if msg != None: msg_ = msg_ + ", " + msg + if msg != None: msg_ = msg_ + ", " + msg self.assertEqual(f1,f2, msg=msg_) - + def assertArrayAlmostEqual(self, l1,l2,places=None,delta=None, msg=None): for i, (f1, f2) in enumerate(zip(l1, l2)): msg_ = "Array not equal on: %i, %s != %s"%(i, str(f1), str(f2)) - if msg != None: msg_ = msg_ + ", " + msg + if msg != None: msg_ = msg_ + ", " + msg self.assertAlmostEqual(f1,f2,places=places, delta=delta, msg=msg_) class TestCase1Parameter(FitTestCase): - + def setUp(self): self.attrs1 = dict(name='p1', value=15.21, vary=True, min=10., max=20., expr=None) self.attrs2 = dict(name='p2', value=-0.22, vary=True, min=None, max=None, expr=None) self.param1 = Parameter(**self.attrs1) self.param2 = Parameter(**self.attrs2) - + self.param1.stderr = 0.1521 self.param1.mcerr = 0.7605 self.ci = {'0.95':(14.10, 16.32), '0.997':(13.52, 17.67)} self.param1.cierr = copy.copy(self.ci) - + def test1NewAttrs(self): """ sigproc.lmfit.Parameter: new error attributes """ par = self.param1 @@ -52,83 +51,83 @@ def test1NewAttrs(self): self.assertTrue(hasattr(par, 'correl'), msg='Parameter does not have var correl') self.assertTrue(hasattr(par, 'cierr'), msg='Parameter does not have var cierr') self.assertTrue(hasattr(par, 'mcerr'), msg='Parameter does not have var mcerr') - + def test2CanKick(self): """ sigproc.lmfit.Parameter: can_kick() """ par1 = self.param1 par2 = self.param2 self.assertTrue(par1.can_kick(), msg='Parameter should be able to kick') self.assertFalse(par2.can_kick(), msg='Parameter should NOT be able to kick') - + def test3Kick(self): """ sigproc.lmfit.Parameter: kick() """ par = self.param1 par.kick() - + msg = 'Parameter value needs to change after Kick' self.assertNotEqual(par.value, self.attrs1['value'], msg=msg) self.assertEqual(par.value, par.user_value, msg=msg) - + msg = 'Kicked Parameter value should be between min and max' self.assertTrue(par.value >= self.attrs1['min'] and par.value <= self.attrs1['max'], msg=msg) - + def test4GetAttr(self): """ sigproc.lmfit.Parameter: __getattr__() """ par = self.param1 - + msg = 'CI values should be reachable directly by fx. ci95' self.assertTrue(hasattr(par, 'ci95'), msg=msg) self.assertTrue(hasattr(par, 'ci997'), msg=msg) self.assertTrue(hasattr(par, 'ci665'), msg=msg) - + msg = 'Returned CI values are not correct' self.assertEqual(par.ci95, self.ci['0.95'], msg=msg) self.assertEqual(par.ci997, self.ci['0.997'], msg=msg) self.assertEqual(par.ci665, (np.nan, np.nan), msg=msg) - + msg = 'Percentual error should be reachable directly by fx. stderrpc' self.assertTrue(hasattr(par, 'stderrpc'), msg=msg) self.assertTrue(hasattr(par, 'mcerrpc'), msg=msg) - + msg = 'Returned percentual errors are not correct' self.assertEqual(par.stderrpc, 1., msg=msg) self.assertEqual(par.mcerrpc, 5., msg=msg) - + class TestCase2Parameters(FitTestCase): - + def setUp(self): self.attrs1 = dict(value=15.21, vary=True, min=10., max=20., expr=None) self.attrs2 = dict(value=-0.22, vary=True, min=None, max=None, expr=None) self.attrs3 = dict(value=None, vary=True, min=None, max=None, expr='p2/2.0') self.attrs4 = dict(value=1000., vary=True, min=100, max=1500, expr=None) - + self.params = lmfit.Parameters() self.params.add('p1', **self.attrs1) self.params.add('p2', **self.attrs2) self.params.add('p3', **self.attrs3) self.params.add('p4', **self.attrs4) - + def test1CanKick(self): """ sigproc.lmfit.Parameters: can_kick() """ - + msg='can_kick() does not return the correct parameters (p1, p4)' kick_pars = self.params.can_kick() self.assertEqual(kick_pars, ['p1', 'p4'], msg=msg) - + msg='can_kick(pnames) does not return the correct parameters (p4)' kick_pars = self.params.can_kick(pnames=['p2', 'p3', 'p4']) self.assertEqual(kick_pars, ['p4'], msg=msg) - + @unittest.skipIf(noMock, "Mock not installed") def test2Kick(self): """ sigproc.lmfit.Parameters: kick() """ - + self.params['p1'].kick = MagicMock() self.params['p2'].kick = MagicMock() self.params['p3'].kick = MagicMock() self.params['p4'].kick = MagicMock() - + self.params.kick(pnames=['p1']) msg='kick(pnames) did not kick the requested parameter (p1)' self.assertTrue(self.params['p1'].kick.called, msg=msg) @@ -136,12 +135,12 @@ def test2Kick(self): self.assertFalse(self.params['p2'].kick.called, msg=msg) self.assertFalse(self.params['p3'].kick.called, msg=msg) self.assertFalse(self.params['p4'].kick.called, msg=msg) - + self.params['p1'].kick = MagicMock() self.params['p2'].kick = MagicMock() self.params['p3'].kick = MagicMock() self.params['p4'].kick = MagicMock() - + self.params.kick() msg='kick() did not kick all possible parameters (p1, p4)' self.assertTrue(self.params['p1'].kick.called, msg=msg) @@ -149,48 +148,48 @@ def test2Kick(self): msg='kick() should NOT kick other parameters (p2, p3)' self.assertFalse(self.params['p2'].kick.called, msg=msg) self.assertFalse(self.params['p3'].kick.called, msg=msg) - + def test3test4GetAttr(self): """ sigproc.lmfit.Parameters: __getattr__() """ - + pars = self.params - + msg = 'Parameters does not have attribute {:s}' for name in ['name', 'value', 'min', 'max', 'vary', 'expr', 'stderr', 'mcerr', 'cierr']: self.assertTrue(hasattr(pars, name), msg=msg.format(name)) - + msg = 'value attr in parameters does NOT return the correct values' self.assertArrayEqual(pars.value, [15.21, -0.22, None, 1000], msg=msg) - + msg = 'value min in parameters does NOT return the correct values' self.assertArrayEqual(pars.min, [10., -np.inf, -np.inf, 100], msg=msg) - + msg = 'value expr in parameters does NOT return the correct values' self.assertArrayEqual(pars.expr, [None, None, 'p2/2.0', None], msg=msg) - + class TestCase3ConfidenceIterval(FitTestCase): - + @unittest.skipIf(noMock, "Mock not installed") @mock.patch.object(ConfidenceInterval, '__init__') def test1Format(self, mocked_init): """ sigproc.lmfit.confidence.conf_interval(): output format """ mocked_init.return_value = None - + ci = ConfidenceInterval() ci.p_names = ['p1', 'p2'] ci.sigmas = [0.674, 0.95, 0.997] ci.trace = False ci.calc_ci = MagicMock(return_value=[('0.674', 10.0), ('0.95',-0.022), ('0.997', 56.)]) ciOut = ci.calc_all_ci() - + expected_output = "{'p1': {0.95: (.., ..), 0.674: (.., ..), 0.997: (.., ..)}" + \ ", 'p2': {0.95: (.., ..), 0.674: (.., ..), 0.997: (.., ..)}}" - + msg = 'Both parameters (p1, p2) should be in the output dict.' + expected_output self.assertTrue('p1' in ciOut, msg=msg) self.assertTrue('p2' in ciOut, msg=msg) - + msg = 'All sigma values should be in the output dictionary.' + expected_output self.assertTrue(0.674 in ciOut['p1'], msg=msg) self.assertTrue(0.95 in ciOut['p1'], msg=msg) @@ -198,21 +197,21 @@ def test1Format(self, mocked_init): self.assertTrue(0.674 in ciOut['p2'], msg=msg) self.assertTrue(0.95 in ciOut['p2'], msg=msg) self.assertTrue(0.997 in ciOut['p2'], msg=msg) - + class TestCase4Minimizer(FitTestCase): - + @unittest.skipIf(noMock, "Mock not installed") @mock.patch.object(Minimizer, '__init__') def test1start_minimizer(self, mocked_init): """ sigproc.lmfit.minimize.Minimizer(): start_minimize() """ mocked_init.return_value = None - + mini = Minimizer() mini.anneal = MagicMock(return_value=None) mini.lbfgsb = MagicMock(return_value=None) mini.fmin = MagicMock(return_value=None) mini.leastsq = MagicMock(return_value=None) - + msg = "start_minimize('leastsq') should only start minimizer.leastsq()" mini.start_minimize('leastsq') self.assertTrue(mini.leastsq.called, msg=msg) @@ -220,7 +219,7 @@ def test1start_minimizer(self, mocked_init): self.assertFalse(mini.lbfgsb.called, msg=msg) self.assertFalse(mini.fmin.called, msg=msg) mini.leastsq = MagicMock(return_value=None) - + msg = "start_minimize('leastsq') should only start minimizer.leastsq()" mini.start_minimize('nelder') self.assertFalse(mini.leastsq.called, msg=msg) @@ -229,8 +228,8 @@ def test1start_minimizer(self, mocked_init): self.assertTrue(mini.fmin.called, msg=msg) class TestCase5Integration(FitTestCase): - - @classmethod + + @classmethod def setUpClass(cls): # the fitting model (residuals) def residual(pars, x, data=None): @@ -245,7 +244,7 @@ def residual(pars, x, data=None): if data is None: return model return (model - data) - + # create sample data p_true = lmfit.Parameters() p_true.add('amp', value=14.0) @@ -253,7 +252,7 @@ def residual(pars, x, data=None): p_true.add('shift', value=0.123) p_true.add('decay', value=0.010) cls.p_true = p_true - + n = 2500 xmin = 0. xmax = 250.0 @@ -261,44 +260,44 @@ def residual(pars, x, data=None): noise = np.random.normal(scale=0.7215, size=n) x = np.linspace(xmin, xmax, n) data = residual(p_true, x) + noise - + # setup reasonable starting values for the fit p_fit = lmfit.Parameters() p_fit.add('amp', value=13.0) p_fit.add('period', value=2) p_fit.add('shift', value=0.0) p_fit.add('decay', value=0.02) - + result = lmfit.minimize(residual, p_fit, args=(x,), kws={'data':data}) cls.result = result cls.p_fit = copy.deepcopy(p_fit) ci = lmfit.conf_interval(result) cls.ci = ci - + def test1minimizer_value(self): """ I sigproc.lmfit minimize: value""" - + p_fit = self.p_fit names = p_fit.name values = p_fit.value - + p_true = self.p_true real = p_true.value - + msg = 'Resulting value for {:s} is not correct within {:.0f}\%' pc = 0.05 # 5% difference is allowed for i in range(4): - self.assertAlmostEqual(values[i], real[i], delta = pc*real[i], + self.assertAlmostEqual(values[i], real[i], delta = pc*real[i], msg=msg.format(names[i], pc*100)) - - + + def test2minimizer_stderr(self): """ I sigproc.lmfit minimize: stderror""" - + p_fit = self.p_fit names = p_fit.name err = p_fit.stderr - + msg = 'Resulting std-error for {:s} is not correct within {:.0f}\%' pc = 0.1 # 10% difference is allowed real = [0.0501, 0.0027, 0.0049, 4.1408e-05] @@ -308,25 +307,25 @@ def test2minimizer_stderr(self): def test3minimizer_correl(self): """ I sigproc.lmfit minimize: correlation """ - + p_fit = self.p_fit corr = p_fit.correl - + msg = 'Correlations for {:s} is not correct within 10\%' - self.assertAlmostEqual(corr[1]['shift'], 0.800155, delta = 0.080, + self.assertAlmostEqual(corr[1]['shift'], 0.800155, delta = 0.080, msg = msg.format('period-shift')) - self.assertAlmostEqual(corr[3]['amp'], 0.57584, delta = 0.057, + self.assertAlmostEqual(corr[3]['amp'], 0.57584, delta = 0.057, msg = msg.format('amp-decay')) - + msg = 'Correlation for {:s} is to high (> 0.10)' self.assertTrue(corr[0]['period'] < 0.10, msg = msg.format('amp-period')) self.assertTrue(corr[1]['decay'] < 0.10, msg = msg.format('shift-decay')) self.assertTrue(corr[2]['decay'] < 0.10, msg = msg.format('period-decay')) - + def test4confidenceIntervals(self): """ I sigproc.lmfit conf_interval: values """ ci = self.ci - + msg = "{:.0f}\% CI values for '{:s}' not correct" self.assertArrayAlmostEqual(ci['period'][0.674], (5.3240, 5.3294), places=2, msg = msg.format(67.4, 'period')) @@ -335,5 +334,4 @@ def test4confidenceIntervals(self): self.assertArrayAlmostEqual(ci['shift'][0.997], (0.1059, 0.1351), places=2, msg = msg.format(99.7, 'shift')) - - \ No newline at end of file + diff --git a/spectra/linelists.py b/spectra/linelists.py index c21ec1632..7b969a1dd 100644 --- a/spectra/linelists.py +++ b/spectra/linelists.py @@ -62,26 +62,32 @@ def VALD(elem=None,xmin=3200.,xmax=4800.,outputdir=None): """ Request linelists from VALD for each ion seperately within a specific wavelength range. - + elem = an array of ions e.g. ['CI','OII'], xmin and xmax: wavelength range in which the spectral lines are searched, outputdir = output directory chosen by the user. - + If no elements are given, this function returns all of them. - + @param elem: list of ions @type elem: list of str + + Example usage: + + >>> x = VALD(elem=['CI','OII'],xmin=3000., xmax=4000.) + CI + OII """ if elem is None: files = sorted(config.glob('VALD_individual','VALD_*.lijnen')) elem = [os.path.splitext(os.path.basename(ff))[0].split('_')[1] for ff in files] - + all_lines = [] for i in range(len(elem)): - print elem[i] + print(elem[i]) filename = config.get_datafile('VALD_individual','VALD_' + elem[i] + '.lijnen') if not os.path.isfile(filename): logger.info('No data for element ' + str(elem[i])) return None - + newwav,newexc,newep,newgf = np.loadtxt(filename).T lines = np.rec.fromarrays([newwav,newexc,newep,newgf],names=['wavelength','ion','ep','gf']) keep = (xmin<=lines['wavelength']) & (lines['wavelength']<=xmax) @@ -100,36 +106,36 @@ def get_lines(teff,logg,z=0,atoms=None,ions=None,wrange=(-inf,inf),\ blend=0.0): """ Retrieve line transitions and strengths for a specific stellar type - + Selection wavelength range in angstrom. - + Ions should be a list of ions to include. This can either be a string or a number - + A lines is considerd a blend if the closest line is closer than C{blend} angstrom. - + Returns record array with fields C{wavelength}, C{ion} and C{depth}. - + Example usage: - + Retrieve all Silicon lines between 4500 and 4600 for a B1V star. - - >>> data = get_lines(20000,4.0,atoms=['Si'],wrange=(4500,4600)) + + >>> data = get_lines(20000,4.0,atoms=['Si'],ions=['CI','CII'],wrange=(4200,6800)) >>> p = pl.figure() >>> p = pl.vlines(data['wavelength'],1,1-data['depth']) - + See how the depth of the Halpha line varies wrt temperature: - - >>> teffs = range(5000,21000,1000) + range(22000,32000,2000) + range(30000,50000,50000) + + >>> teffs = list(range(5000,21000,1000)) + list(range(22000,32000,2000)) + list(range(30000,50000,50000)) >>> depths = np.zeros((len(teffs),7)) >>> for i,teff in enumerate(teffs): ... data = get_lines(teff,5.0,ions=['HI'],wrange=(3800,7000)) ... depths[i] = data['depth'] - + >>> p = pl.figure();p = pl.title('Depth of Balmer lines (Halpha-Heta)') >>> p = pl.plot(teffs,1-depths,'o-') >>> p = pl.xlabel('Effective temperature');p = pl.grid() - + """ #-- get filepath filename = 'mask.%d.%02d.p%02d'%(int(teff),int(logg*10),int(z)) @@ -144,7 +150,7 @@ def get_lines(teff,logg,z=0,atoms=None,ions=None,wrange=(-inf,inf),\ blends_right= np.hstack([np.diff(data['wavelength']),1e10]) keep = (blends_left>blend) & (blends_right>blend) data = data[keep] - + #-- only keep those transitions within a certain wavelength range keep = (wrange[0]<=data['wavelength']) & (data['wavelength']<=wrange[1]) data = data[keep] @@ -163,7 +169,7 @@ def get_lines(teff,logg,z=0,atoms=None,ions=None,wrange=(-inf,inf),\ ions = [(isinstance(ion,str) and name2ioncode(ion) or ion) for ion in ions] for ion in ions: keep = keep | (np.abs(data['ion']-ion)<0.005) - + return data[keep] @@ -174,6 +180,9 @@ def get_lines(teff,logg,z=0,atoms=None,ions=None,wrange=(-inf,inf),\ def ioncode2name(ioncode): """ Convert 14.01 to SiII + + >>> print(ioncode2name(14.01)) + SiII """ atom = int(np.floor(ioncode)) ion = int(np.round((ioncode-atom)*100)) @@ -182,6 +191,9 @@ def ioncode2name(ioncode): def name2ioncode(name): """ Convert SiII to 14.01 + + >>> print(name2ioncode("SiII")) + 14.01 """ atomname,ionstage = splitname(name) return atomcode.index(atomname) + roman.index(ionstage)/100. @@ -189,6 +201,9 @@ def name2ioncode(name): def splitname(name): """ Split SiII into Si and II + + >>> print(splitname("BaIV")) + ('Ba', 'IV') """ atomname = '' ionstage = '' @@ -207,4 +222,3 @@ def splitname(name): import doctest doctest.testmod() pl.show() - \ No newline at end of file diff --git a/spectra/lsd.py b/spectra/lsd.py index 529479c28..575983dab 100644 --- a/spectra/lsd.py +++ b/spectra/lsd.py @@ -109,16 +109,16 @@ def lsd(velos,V,S,rvs,masks,Lambda=0.): """ Compute LSD profiles and cross correlation functions. - + Possibility to include Tikhonov regularization to clean up the profiles, when setting C{Lambda>0}. - + Possibility to include multiprofile LSD. Parameter C{masks} should be a list of C{(centers,weights)} (if you give only one mask, give C{masks=[(centers,weights)]}. - + See Donati, 1997 for the original paper and Kochukhov, 2010 for extensions. - + @parameter velos: velocity vector of observations @type velos: array of length N_spec @parameter V: observation array @@ -139,7 +139,7 @@ def lsd(velos,V,S,rvs,masks,Lambda=0.): Nspec = V.shape[1] Nmask = len(masks) V = np.matrix(V)-1 - + #-- weights of the individual pixels S = np.matrix(np.diag(S)) #-- line masks (yes, this can be vectorized but I'm too lazy for the moment) @@ -164,7 +164,7 @@ def lsd(velos,V,S,rvs,masks,Lambda=0.): R[1,0] = -1 R[-1,-1] = 1 R[-2,-1] = -1 - #-- compute the LSD + #-- compute the LSD X = M.T*(S**2) XM = X*M if Lambda: @@ -202,7 +202,7 @@ def __generate_test_spectra(Nspec,binary=False,noise=0.01): weights1 = [0.5,0.1,0.3] line_centers2 = [-5,2,10] weights2 = [0.3,0.4,0.1] - + #-- weights S = np.ones(spec_length) #-- profiles @@ -216,10 +216,10 @@ def __generate_test_spectra(Nspec,binary=False,noise=0.01): masks.append((line_centers2,weights2)) for i,prof_velo in enumerate(obs_velo2): for line_center,weight in zip(line_centers2,weights2): - V[i] += evaluate.gauss(velos,[weight,line_center-prof_velo,1.]) + V[i] += evaluate.gauss(velos,[weight,line_center-prof_velo,1.]) V = 1-np.array(V) V = np.matrix(V).T - + return velos,V,S,masks if __name__=="__main__": diff --git a/spectra/model.py b/spectra/model.py index 801e7d2b2..bda59ac10 100644 --- a/spectra/model.py +++ b/spectra/model.py @@ -43,14 +43,12 @@ import astropy.io.fits as pf import inspect from ivs import config -from ivs.units import constants -from ivs.units import conversions -from ivs.aux.decorators import memoized from ivs.spectra import tools from ivs.aux import loggers import numpy as np -from Scientific.Functions.Interpolation import InterpolatingFunction +# from Scientific.Functions.Interpolation import InterpolatingFunction +from scipy.interpolate import RegularGridInterpolator as InterpolatingFunction logger = logging.getLogger("SPEC.MODEL") logger.addHandler(loggers.NullHandler) @@ -160,7 +158,7 @@ def get_file(**kwargs): elif grid=='tmapsdb': basename = 'TMAP2011_sdB.fits' else: - raise ValueError, "grid %s does not exist"%(grid) + raise ValueError("grid %s does not exist"%(grid)) #-- retrieve the absolute path of the file and check if it exists: grid = config.get_datafile(basedir,basename) @@ -229,7 +227,7 @@ def get_table(teff=None,logg=None,vrad=0,vrot=0,**kwargs): # wavelength grid. First we need to check which arguments we can pass # through argspec = inspect.getargspec(tools.rotational_broadening) - mykwargs = dict(zip(argspec.args[-len(argspec.defaults):],argspec.defaults)) + mykwargs = dict(list(zip(argspec.args[-len(argspec.defaults):],argspec.defaults))) for key in kwargs: if key in mykwargs: mykwargs[key] = kwargs[key] @@ -288,7 +286,7 @@ def get_grid_mesh(wave=None,teffrange=None,loggrange=None,**kwargs): array found in the grid will be used as a template. It might take a long a time and cost a lot of memory if you load the entire - grid. Therefor, you can also set range of temperature and gravity. + grid. Therefore, you can also set range of temperature and gravity. @param wave: wavelength to define the grid on @type wave: ndarray @@ -347,8 +345,10 @@ def get_grid_mesh(wave=None,teffrange=None,loggrange=None,**kwargs): except: flux[i,j,:] = np.interp(wave,wave_,flux_) cont[i,j,:] = np.interp(wave,wave_,cont_) - flux_grid = InterpolatingFunction([np.log10(teffs),loggs],flux) - cont_grid = InterpolatingFunction([np.log10(teffs),loggs],cont) + # flux_grid = InterpolatingFunction([np.log10(teffs),loggs],flux) + # cont_grid = InterpolatingFunction([np.log10(teffs),loggs],cont) + flux_grid = InterpolatingFunction((np.log10(teffs),loggs),flux) + cont_grid = InterpolatingFunction((np.log10(teffs),loggs),cont) #logger.info('Constructed spectrum interpolation grid') return wave,teffs,loggs,flux,flux_grid,cont_grid @@ -363,13 +363,14 @@ def get_grid_mesh(wave=None,teffrange=None,loggrange=None,**kwargs): #import doctest import pylab as pl import numpy as np + from cycler import cycler #doctest.testmod() grids = get_gridnames() p = pl.figure() - color_cycle = [pl.cm.spectral(j) for j in np.linspace(0, 1.0, len(grids))] - p = pl.gca().set_color_cycle(color_cycle) + color_cycle = cycler(color=[pl.cm.Spectral(j) for j in np.linspace(0, 1.0, len(grids))]) + p = pl.gca().set_prop_cycle(color_cycle) for grid in grids: vturb = 'ostar' in grid and 10 or 2 diff --git a/spectra/moments.py b/spectra/moments.py index 773bb1385..c2e1a1b4a 100644 --- a/spectra/moments.py +++ b/spectra/moments.py @@ -4,47 +4,48 @@ import numpy as np import scipy.integrate import pylab as pl +from ivs import config def moments(velo,flux,SNR=200.,max_mom=3): """ Compute the moments from a line profile. """ mymoms,e_mymoms = np.zeros(max_mom+1),np.zeros(max_mom+1) - + #-- compute zeroth moment (equivalent width) m0 = scipy.integrate.simps( (1-flux),x=velo) - + #-- to calculate the uncertainties, we need the error in each velocity # bin, and the error on the zeroth moment sigma_i = 1./SNR / np.sqrt(flux) Delta_v0 = np.sqrt(scipy.integrate.simps( sigma_i,x=velo)**2) e_m0 = np.sqrt(2)*(Delta_v0/m0) - + mymoms[0] = m0 e_mymoms[0] = e_m0 - + #-- calculate other moments for n in range(1,max_mom+1): mymoms[n] = scipy.integrate.simps((1-flux)*velo**n,x=velo)/ m0 #-- and the uncertainty on it Delta_vn = np.sqrt(scipy.integrate.simps(sigma_i*velo**n)**2) e_mymoms[n] = np.sqrt( (Delta_vn/m0)**2 + (Delta_v0/m0 * mymoms[n])**2 ) - + return mymoms,e_mymoms def moments_fromfiles(filelist,read_func,max_mom=3,**kwargs): """ - Compute the moments from a list of files containg line profiles. - + Compute the moments from a list of files containing line profiles. + The C{read_func} should return 2 arrays and a float, representing velocities, normalised fluxes and the SNR of the line profile. - + m0: equivalent width m1: radial velocity m2: variance m3: skewness - + @param filelist: list of filenames @type filelist: list of strings @param read_func: function which reads in a file and returns velocities (array), @@ -52,10 +53,16 @@ def moments_fromfiles(filelist,read_func,max_mom=3,**kwargs): @type read_func: Python function @param max_mom: maximum moment to compute @type max_mom: integer - @return: a list containg the moments and a list containing the errors + @return: a list containing the moments and a list containing the errors @rtype: [max_mom x array],[max_mom x array] + + Example usage: + + >>> filelist = np.loadtxt( config.get_datafile('Spectra_testfiles','profiles.list') ,str) + + >>> output,errors = moments_fromfiles(filelist,get_profile_from_file) """ - + output = [np.zeros(len(filelist)) for i in range(max_mom+1)] errors = [np.zeros(len(filelist)) for i in range(max_mom+1)] for i,f in enumerate(filelist): @@ -71,15 +78,15 @@ def moments_fromfiles(filelist,read_func,max_mom=3,**kwargs): def profiles_fromfiles(filelist,read_func,max_mom=3,**kwargs): """ Compute an average profile from a file list of profiles. - + The C{read_func} should return 2 arrays and a float, representing velocities, normalised fluxes and the SNR of the line profile. - + m0: equivalent width m1: radial velocity m2: variance m3: skewness - + @param filelist: list of filenames @type filelist: list of strings @param read_func: function which reads in a file and returns velocities (array), @@ -87,10 +94,16 @@ def profiles_fromfiles(filelist,read_func,max_mom=3,**kwargs): @type read_func: Python function @param max_mom: maximum moment to compute @type max_mom: integer - @return: a list containg the moments and a list containing the errors + @return: a list containing the moments and a list containing the errors @rtype: velo,av_prof,fluxes + + Example usage: + + >>> filelist = np.loadtxt( config.get_datafile('Spectra_testfiles','profiles.list') ,str) + + >>> templatevelo,av_prof,fluxes = profiles_fromfiles(filelist,get_profile_from_file) """ - + output = [np.zeros(len(filelist)) for i in range(max_mom+1)] errors = [np.zeros(len(filelist)) for i in range(max_mom+1)] for i,f in enumerate(filelist): @@ -102,9 +115,29 @@ def profiles_fromfiles(filelist,read_func,max_mom=3,**kwargs): fluxes[0] = flux else: fluxes[i] = np.interp(template_velo,velo,flux) - + av_prof = fluxes.mean(axis=0) fluxes -= av_prof - + return template_velo,av_prof,fluxes - \ No newline at end of file + + + +if __name__=="__main__": + import doctest + """ + The doctest examples are un-normalised profiles in wavelength space. + Correct science usage should be normalised profiles in velocity space. + """ + def get_profile_from_file(filename): + """ + Used for doctest purposes: import line profile from a given test file + """ + filedata = np.loadtxt(config.get_datafile('Spectra_testfiles',filename),delimiter = '\t').T + #Calculate SNR of profiles + SNR = np.nanmean(filedata[1])/np.nanstd(filedata[1]) + #returns the profile velocity, flux, and SNR + return filedata[0],filedata[1],SNR + + doctest.testmod() + pl.show() diff --git a/spectra/testTools.py b/spectra/testTools.py index c2b6c32aa..f7afd4135 100644 --- a/spectra/testTools.py +++ b/spectra/testTools.py @@ -7,76 +7,76 @@ class SpectrumTestCase(unittest.TestCase): """Add some extra usefull assertion methods to the testcase class""" - + def assertNoCosmic(self, wave, flux, wrange, sigma=3, msg=None): flux = flux[(wave>=wrange[0]-0.5) & (wave<=wrange[1]+0.5)] wave = wave[(wave>=wrange[0]-0.5) & (wave<=wrange[1]+0.5)] avg = np.median(flux) std = np.std(flux) - + msg_ = "Cosmic detected in %s <-> %s"%(wrange[0], wrange[1]) if msg != None: msg_ = msg_ + ", " + str(msg) self.assertTrue(np.all(flux < avg + sigma * std), msg=msg_) - + def assertAbsorptionLine(self, wave, flux, line, cont, depth=0.75, msg=None): lf = np.min(flux[(wave>=line[0]) & (wave<=line[1])]) cf = np.average(flux[(wave>=cont[0]) & (wave<=cont[1])]) - + msg_ = "Absorption line missing in %s <-> %s"%(line[0], line[1]) if msg != None: msg_ = msg_ + ", " + str(msg) self.assertTrue( lf <= cf * depth, msg=msg_ ) - + def assertArrayEqual(self, l1, l2, msg=None): for i, (f1, f2) in enumerate(zip(l1,l2)): msg_ = "Array not equal on: %i, %s != %s"%(i, str(f1), str(f2)) - if msg != None: msg_ = msg_ + ", " + msg + if msg != None: msg_ = msg_ + ", " + msg self.assertEqual(f1,f2, msg=msg_) - + def assertArrayAlmostEqual(self, l1,l2,places=None,delta=None, msg=None): for i, (f1, f2) in enumerate(zip(l1, l2)): msg_ = "Array not equal on: %i, %s != %s"%(i, str(f1), str(f2)) - if msg != None: msg_ = msg_ + ", " + msg + if msg != None: msg_ = msg_ + ", " + msg self.assertAlmostEqual(f1,f2,places=places, delta=delta, msg=msg_) class MergeCosmicClippingTestCase1(SpectrumTestCase): """ Testcase using a synthetic spectrum with 1 emission line and 4 cosmics """ - - @classmethod + + @classmethod def setUpClass(cls): np.random.seed(1111) num = 1000 level = np.array([134, 53, 261, 111]) noise = 0.01 * level - + # create standard noise wave = np.linspace(100,200, num=num) flux1 = np.random.normal(level[0], noise[0], num) flux2 = np.random.normal(level[1], noise[1], num) flux3 = np.random.normal(level[2], noise[2], num) flux4 = np.random.normal(level[3], noise[3], num) - + # create cosmics flux1[467] *= 2.5 flux2[197] *= 2.6 flux3[816] *= 2.4 flux4[23] *= 2.8 - + # create spectral feature flux1[365] *= 1.40 flux2[365] *= 1.43 flux3[365] *= 1.38 flux4[365] *= 1.39 - + waves = np.array([wave, wave, wave, wave]) fluxes = np.array([flux1, flux2, flux3, flux4]) vrads = [555., 565., 545., 560.] - + wave1, flux1, accepted, rejected = tools.merge_cosmic_clipping(waves, fluxes, sigma=2.0, base='median', offset='std', window=51, runs=2, full_output=True) - wave2, flux2 = tools.merge_cosmic_clipping(waves, fluxes, vrads=vrads, + wave2, flux2 = tools.merge_cosmic_clipping(waves, fluxes, vrads=vrads, sigma=2.0, base='median', offset='std', window=51, runs=2) - + cls.wave1 = wave1 cls.flux1 = flux1 cls.wave2 = wave2 @@ -88,43 +88,43 @@ def setUpClass(cls): cls.vrads = vrads cls.num = num cls.avg = np.sum(level) * 1.05 - + def testRemoveCosmics(self): """ spectra.tools.merge_cosmic_clipping() synth remove cosmics """ self.assertTrue(self.flux1[467] < self.avg, 'Cosmic NOT removed at 467') self.assertTrue(self.flux1[197] < self.avg, 'Cosmic NOT removed at 197') self.assertTrue(self.flux1[816] < self.avg, 'Cosmic NOT removed at 816') self.assertTrue(self.flux1[23] < self.avg, 'Cosmic NOT removed at 23') - + def testKeepSpectralFeatures(self): """ spectra.tools.merge_cosmic_clipping() synth keep features """ self.assertTrue(self.flux1[365] > 1.3 * self.avg and self.flux1[365] < 1.5 * self.avg, 'Feature wrongly removed') - + def testRemoveCosmicsVrad(self): """ spectra.tools.merge_cosmic_clipping() synth Vrad remove cosmics """ self.assertTrue(self.flux2[467] < self.avg, 'Cosmic NOT removed at 467') self.assertTrue(self.flux2[197] < self.avg, 'Cosmic NOT removed at 197') self.assertTrue(self.flux2[816] < self.avg, 'Cosmic NOT removed at 816') self.assertTrue(self.flux2[23] < self.avg, 'Cosmic NOT removed at 23') - + def testKeepSpectralFeaturesVrad(self): """ spectra.tools.merge_cosmic_clipping() synth Vrad keep features """ - self.assertTrue(self.flux2[365] > 1.3 * self.avg and self.flux2[365] < 1.5 * self.avg, + self.assertTrue(self.flux2[365] > 1.3 * self.avg and self.flux2[365] < 1.5 * self.avg, 'Feature wrongly removed') - + def testUseWaveFirstSpectrum(self): """ spectra.tools.merge_cosmic_clipping() synth return wavescale first spectrum """ - self.assertArrayAlmostEqual(self.wave1, self.waves[0], places=3, + self.assertArrayAlmostEqual(self.wave1, self.waves[0], places=3, msg='Did not return wavelength of first spectrum') - + def testShiftSpectraVrad(self): """ spectra.tools.merge_cosmic_clipping() synth shift spectra with Vrad """ self.assertAlmostEqual(self.wave2[0], 99.8148719272, places=3, msg='Spectrum not shifted') self.assertAlmostEqual(self.wave2[-1], 199.629743854, places=3, msg='Spectrum not shifted') f = self.flux2[(self.wave2 > 136.2) & (self.wave2 < 136.36)] self.assertTrue(f[0] > 1.3 * self.avg, 'Spectrum not shifted') - + def testFullOutput(self): """ spectra.tools.merge_cosmic_clipping() synth Full Output """ rejected = self.rejected @@ -133,7 +133,7 @@ def testFullOutput(self): rej = np.array([467, 197, 816, 23]) self.assertArrayEqual(rejected[0], np.arange(len(self.waves)), msg='Rejected not oke') self.assertArrayEqual(rejected[1], rej, msg='Rejected not oke') - + #-- Test accepted self.assertEqual(len(accepted[1][accepted[0] == 0]), 999, msg='Accepted 0 not oke') self.assertEqual(len(accepted[1][accepted[0] == 1]), 999, msg='Accepted 1 not oke') @@ -154,8 +154,8 @@ def testFullOutput(self): class MergeCosmicClippingTestCase2(SpectrumTestCase): """ Testcase using real spectra of Feige 66 (5000 - 7000 AA) """ - - @classmethod + + @classmethod def setUpClass(cls): temp = '/STER/mercator/hermes/%s/reduced/%s_HRF_OBJ_ext_CosmicsRemoved_log_merged_c.fits' objlist = [('20090619','237033'), ('20090701','240226'), @@ -175,7 +175,7 @@ def setUpClass(cls): ('20130106','00445346'), ('20130215','00452556'), ('20130406','00457718'), ('20130530','00474128')] mergeList = [temp%o for o in objlist] - + waves, fluxes = [], [] for ifile in mergeList: w, f = fits.read_spectrum(ifile) @@ -183,23 +183,23 @@ def setUpClass(cls): w = w[(w>5000) & (w<7000)] waves.append(w) fluxes.append(f) - + wave, flux, accepted, rejected = tools.merge_cosmic_clipping(waves, fluxes, sigma=5.0, base='average', offset='std', window=51, runs=2, full_output=True) - + cls.nspek = len(objlist) cls.wave = wave cls.flux = flux cls.accepted = accepted cls.rejected = rejected - + def testRemoveCosmics(self): """ spectra.tools.merge_cosmic_clipping() Feige 66 remove cosmics """ self.assertNoCosmic(self.wave, self.flux, (6161.7, 6161.9)) self.assertNoCosmic(self.wave, self.flux, (6166.5, 6166.8), sigma=4) self.assertNoCosmic(self.wave, self.flux, (6200.2, 6200.5)) self.assertNoCosmic(self.wave, self.flux, (6546.9, 6547.1)) - + def testKeepSpectralFeatures(self): """ spectra.tools.merge_cosmic_clipping() Feige 66 keep features """ self.assertAbsorptionLine(self.wave, self.flux, (5159.8, 5160.3), (5157.8, 5159.4), @@ -212,8 +212,8 @@ def testFullOutput(self): """ spectra.tools.merge_cosmic_clipping() Feige 66 Full Output """ rejected = self.rejected accepted = self.accepted - - self.assertTrue(len(accepted[0]) > 2070043-100 and len(accepted[0]) < 2070043+100, + + self.assertTrue(len(accepted[0]) > 2070043-100 and len(accepted[0]) < 2070043+100, msg=' Exceptional deviation from expected acceptions! ') - self.assertTrue(len(rejected[0]) > 250 and len(rejected[0]) < 400, + self.assertTrue(len(rejected[0]) > 250 and len(rejected[0]) < 400, msg=' Exceptional deviation from expected rejections! ') diff --git a/spectra/tools.py b/spectra/tools.py index ec8dbed60..452c2dd7e 100644 --- a/spectra/tools.py +++ b/spectra/tools.py @@ -81,7 +81,7 @@ from ivs.spectra import pyrotin4 import numpy as np import logging -from numpy import pi,sin,cos,sqrt +from numpy import pi,sqrt import scipy.stats from scipy.signal import fftconvolve, medfilt from ivs.timeseries import pergrams @@ -97,27 +97,27 @@ def doppler_shift(wave,vrad,vrad_units='km/s',flux=None): """ Shift a spectrum with towards the red or blue side with some radial velocity. - + You can give units with the extra keywords C{vrad_units} (units of wavelengths are not important). The shifted wavelengths will be in the same units as the input wave array. - + If units are not supplied, the radial velocity is assumed to be in km/s. - + If you want to apply a barycentric (or orbital) correction, you'd probabl want to reverse the sign of the radial velocity! - + When the keyword C{flux} is set, the spectrum will be interpolated onto the original wavelength grid (so the original wavelength grid will not change). When the keyword C{flux} is not set, the wavelength array will be changed (but the fluxes not, obviously). - + When C{flux} is set, fluxes will be returned. - + When C{flux} is not set, wavelengths will be returned. - + Example usage: shift a spectrum to the blue ('left') with 20 km/s. - + >>> wave = np.linspace(3000,8000,1000) >>> wave_shift1 = doppler_shift(wave,20.) >>> wave_shift2 = doppler_shift(wave,20000.,vrad_units='m/s') @@ -125,7 +125,7 @@ def doppler_shift(wave,vrad,vrad_units='km/s',flux=None): (3000.200138457119, 8000.5337025523177) >>> print(wave_shift2[0],wave_shift2[-1]) (3000.200138457119, 8000.5337025523177) - + @param wave: wavelength array @type wave: ndarray @param vrad: radial velocity (negative shifts towards blue, positive towards red) @@ -134,7 +134,7 @@ def doppler_shift(wave,vrad,vrad_units='km/s',flux=None): @type vrad_units: str (interpretable for C{units.conversions.convert}) @return: shifted wavelength array/shifted flux @rtype: ndarray - """ + """ cc = constants.cc cc = conversions.convert('m/s',vrad_units,cc) wave_out = wave * (1+vrad/cc) @@ -147,50 +147,50 @@ def doppler_shift(wave,vrad,vrad_units='km/s',flux=None): def vsini(wave,flux,epsilon=0.6,clam=None,window=None,**kwargs): """ Deterimine vsini of an observed spectrum via the Fourier transform method. - + According to Simon-Diaz (2006) and Carroll (1933): - + vsini = 0.660 * c/ (lambda * f1) - + But more general (see Reiners 2001, Dravins 1990) - + vsini = q1 * c/ (lambda*f1) - + where f1 is the first minimum of the Fourier transform. - + The error is estimated as the Rayleigh limit of the Fourier Transform - + Example usage and tests: Generate some data. We need a central wavelength (A), the speed of light in angstrom/s, limb darkening coefficients and test vsinis: - + >>> clam = 4480. >>> c = conversions.convert('m/s','A/s',constants.cc) >>> epsilons = np.linspace(0.,1.0,10) >>> vsinis = np.linspace(50,300,10) - + We analytically compute the shape of the Fourier transform in the following domain (and need the C{scipy.special.j1} for this) - + >>> x = np.linspace(0,30,1000)[1:] >>> from scipy.special import j1 - + Keep track of the calculated and predicted q1 values: - + >>> q1s = np.zeros((len(epsilons),len(vsinis))) >>> q1s_pred = np.zeros((len(epsilons),len(vsinis))) - + Start a figure and set the color cycle - + >>> p= pl.figure() >>> p=pl.subplot(131) >>> color_cycle = [pl.cm.spectral(j) for j in np.linspace(0, 1.0, len(epsilons))] >>> p = pl.gca().set_color_cycle(color_cycle) >>> p=pl.subplot(133);p=pl.title('Broadening kernel') >>> p = pl.gca().set_color_cycle(color_cycle) - + Now run over all epsilons and vsinis and determine the q1 constant: - + >>> for j,epsilon in enumerate(epsilons): ... for i,vsini in enumerate(vsinis): ... vsini = conversions.convert('km/s','A/s',vsini) @@ -219,25 +219,25 @@ def vsini(wave,flux,epsilon=0.6,clam=None,window=None,**kwargs): ... p= pl.plot(vsinis,q1s_pred[j],'-') ... p= pl.subplot(133) ... p= pl.plot(lambdas,G,'-') - + And plot the results: - + >>> p= pl.subplot(131) >>> p= pl.xlabel('v sini [km/s]');p = pl.ylabel('q1') >>> p= pl.legend(prop=dict(size='small')) - - + + >>> p= pl.subplot(132);p=pl.title('Fourier transform') >>> p= pl.plot(x,g**2,'k-',label='Analytical FT') >>> p= pl.plot(myx,g_/max(g_),'r-',label='Computed FT') >>> p= pl.plot(minima*2*pi*delta,minvals/max(g_),'bo',label='Minima') >>> p= pl.legend(prop=dict(size='small')) >>> p= pl.gca().set_yscale('log') - + ]include figure]]ivs_spectra_tools_vsini_kernel.png] - + Extra keyword arguments are passed to L{pergrams.deeming} - + @param wave: wavelength array in Angstrom @type wave: ndarray @param flux: normalised flux of the profile @@ -284,22 +284,22 @@ def rotational_broadening(wave_spec,flux_spec,vrot,fwhm=0.25,epsilon=0.6, """ Apply rotational broadening to a spectrum assuming a linear limb darkening law. - + This function is based on the ROTIN program. See Fortran file for explanations of parameters. - + Limb darkening law is linear, default value is epsilon=0.6 - + Possibility to normalize as well by giving continuum in 'cont' parameter. - + B{Warning}: C{method='python'} is still experimental, but should work. - - Section 1. Parameters for rotational convolution + + Section 1. Parameters for rotational convolution ================================================ C{VROT}: v sin i (in km/s): - - - if VROT=0 - rotational convolution is + + - if VROT=0 - rotational convolution is a) either not calculated, b) or, if simultaneously FWHM is rather large (vrot/c*lambda < FWHM/20.), @@ -308,18 +308,18 @@ def rotational_broadening(wave_spec,flux_spec,vrot,fwhm=0.25,epsilon=0.6, value of VROT is changed as in the previous case - if VROT<0 - the value of abs(VROT) is used regardless of how small compared to FWHM it is - + C{CHARD}: characteristic scale of the variations of unconvolved stellar spectrum (basically, characteristic distance between two neighbouring wavelength points) - in A: - + - if =0 - program sets up default (0.01 A) - + C{STEPR}: wavelength step for evaluation rotational convolution; - + - if =0, the program sets up default (the wavelength interval corresponding to the rotational velocity - devided by 3.) + devided by 3.) - if <0, convolved spectrum calculated on the original (detailed) SYNSPEC wavelength mesh @@ -329,7 +329,7 @@ def rotational_broadening(wave_spec,flux_spec,vrot,fwhm=0.25,epsilon=0.6, C{FWHM}: WARNING: this is not the full width at half maximum for Gaussian instrumental profile, but the sigma (FWHM = 2.3548 sigma). - + C{STEPI}: wavelength step for evaluating instrumental convolution - if =0, the program sets up default (FWHM/10.) - if <0, convolved spectrum calculated with the previous @@ -344,7 +344,7 @@ def rotational_broadening(wave_spec,flux_spec,vrot,fwhm=0.25,epsilon=0.6, C{ALAM0}: initial wavelength C{ALAM1}: final wavelength C{IREL}: for =1 relative spectrum, =0 absolute spectrum - + @return: wavelength,flux @rtype: array, array """ @@ -356,12 +356,12 @@ def rotational_broadening(wave_spec,flux_spec,vrot,fwhm=0.25,epsilon=0.6, contw,contf = cont if chard is None: chard = np.diff(wave_spec).mean() - + #-- apply broadening logger.info('ROTIN rot.broad. with vrot=%.3f (epsilon=%.2f)'%(vrot,epsilon)) w3,f3,ind = pyrotin4.pyrotin(wave_spec,flux_spec,contw,contf, vrot,chard,stepr,fwhm,stepi,alam0,alam1,irel,epsilon) - + return w3[:ind],f3[:ind] elif method=='python': logger.info("PYTHON rot.broad with vrot=%.3f (epsilon=%.2f)"%(vrot,epsilon)) @@ -379,7 +379,7 @@ def rotational_broadening(wave_spec,flux_spec,vrot,fwhm=0.25,epsilon=0.6, kernel /= sum(kernel) flux_conv = fftconvolve(1-flux_,kernel,mode='same') flux_spec = np.interp(wave_spec+dwave/2,wave_,1-flux_conv,left=1,right=1) - if vrot>0: + if vrot>0: #-- convert wavelength array into velocity space, this is easier # we also need to make it equidistant! wave_ = np.log(wave_spec) @@ -406,15 +406,15 @@ def rotational_broadening(wave_spec,flux_spec,vrot,fwhm=0.25,epsilon=0.6, def combine(list_of_spectra,R=200.,lambda0=(950.,'AA'),lambdan=(3350.,'AA')): """ Combine and weight-average spectra on a common wavelength grid. - + C{list_of_spectra} should be a list of lists/arrays. Each element in the main list should be (wavelength,flux,error). - + If you have FUSE fits files, use L{ivs.fits.read_fuse}. If you have IUE FITS files, use L{ivs.fits.read_iue}. - + After Peter Woitke. - + @param R: resolution @type R: float @param lambda0: start wavelength, unit @@ -444,7 +444,7 @@ def combine(list_of_spectra,R=200.,lambda0=(950.,'AA'),lambdan=(3350.,'AA')): lam_i0_dc = 0.5*(wave0+wave) lam_i1_dc = 0.5*(wave1+wave) dlam_i = lam_i1_dc-lam_i0_dc - + for j in range(Nw): A = np.min(np.vstack([lamc_j[j+1]*np.ones(len(wave)),lam_i1_dc]),axis=0) B = np.max(np.vstack([lamc_j[j]*np.ones(len(wave)),lam_i0_dc]),axis=0) @@ -452,53 +452,53 @@ def combine(list_of_spectra,R=200.,lambda0=(950.,'AA'),lambdan=(3350.,'AA')): norm = np.sum(overlaps) binned_fluxes[snr,j] = np.sum(flux*overlaps)/norm binned_errors[snr,j] = np.sqrt(np.sum((err*overlaps)**2))/norm - + #-- STEP 3: all available spectra sets are co-added, using the inverse # square of the bin uncertainty as weight binned_fluxes[np.isnan(binned_fluxes)] = 0 binned_errors[np.isnan(binned_errors)] = 1e300 weights = 1./binned_errors**2 - + totalflux = np.sum(weights*binned_fluxes,axis=0)/np.sum(weights,axis=0) totalerr = np.sqrt(np.sum((weights*binned_errors)**2,axis=0))/np.sum(weights,axis=0) totalspec = np.sum(binned_fluxes>0,axis=0) - + #-- that's it! return x[:-1],totalflux,totalerr,totalspec -def merge_cosmic_clipping(waves, fluxes, vrads=None, vrad_units='km/s', sigma=3.0, +def merge_cosmic_clipping(waves, fluxes, vrads=None, vrad_units='km/s', sigma=3.0, base='average', offset='std', window=51, runs=2, full_output=False, **kwargs): """ Method to combine a set of spectra while removing cosmic rays by comparing the spectra with each other and removing the outliers. - + Algorithm: - + For each spectrum a very rough continuum is determined by using a median filter. - Then there are multiple passes through the spectra. In one pass, outliers are + Then there are multiple passes through the spectra. In one pass, outliers are identified by compairing the flux point with the median of all normalized spectra plus sigma times the standard deviation of all points. The standard deviation is the median of the std for all flux points in 1 spectrum (spec_std) plus the std of all fluxes of a given wavelength point over all spectra. - + C{spec_std = np.median( np.std(fluxes) )} C{fn > np.median(fn) + sigma * (np.std(fn) + spec_std)} - + Where fn are the roughly normalized spectra, NOT the original spectra. In the next passes those outliers are not used to calculat the median and std of the spectrum. After the last pass, the flux points that were rejected are - replaced by the rough continuum value, and they are summed to get the final + replaced by the rough continuum value, and they are summed to get the final flux. - + The value for sigma strongly depends on the number of spectra and the quality of the spectra. General, the more spectra, the higher sigma should be. Using a too low value for sigma will throw away to much information. The effect of the window size and the number of runs is limited. - + Returns the wavelengths and fluxes of the merged spectra, and if full_output is True, also a list of accepted and rejected points, produced by np.where() - + @param waves: list of wavelengths @param fluxes: list of fluxes @param vrads: list of radial velocities (optional) @@ -507,48 +507,48 @@ def merge_cosmic_clipping(waves, fluxes, vrads=None, vrad_units='km/s', sigma=3. @param window: window size used in median filter @param runs: number of iterations through the spectra @param full_output: True is need to return accepted and rejected - + @return: wavelenght and flux of merged spectrum (, accepted and rejected points) @rtype: array, array (, tuple, tuple) """ - + # Get the correct function for base from numpy masked arrays base = getattr(np.ma, base) - + # If vrads are given, shift the spectra to zero velocity if vrads != None: for i, (wave, rv) in enumerate(zip(waves, vrads)): waves[i] = doppler_shift(wave, -rv, vrad_units=vrad_units) - + # setup output arrays wave = waves[0] flux = np.zeros_like(waves[0]) - + # define a rough continuum for each spectrum by using median smoothing fc = np.array([np.interp(wave, w_, medfilt(f_, window)) for w_, f_ in zip(waves, fluxes)]) fc = np.ma.masked_array( fc, mask = fc == 0. ) - + # convert all spectra to same wavelength scale and calc normalized spectra fo = np.array([np.interp(wave, w_, f_) for w_, f_ in zip(waves, fluxes)]) fo = np.ma.masked_array( fo, mask=np.isfinite(fo) == False ) - + # calculate normalized flux from fc and fo fn = np.array([f_/c_ for f_, c_ in zip(fo, fc)]) fn = np.ma.masked_array( fn, mask=np.isfinite(fn) == False ) - + for i in range(runs): # calculate average and standard deviation for each wavelength bin a = base(fn, axis=0) if offset == 'std': a += np.median(np.ma.std(fn, axis=1)) s = np.ma.std(fn, axis=0) - + # perform sigma clipping fn.mask = np.ma.mask_or( fn.mask, np.ma.make_mask(fn > a+sigma*s) ) - + # sum the original flux over all spectra flux = np.sum( np.where(fn.mask, fc, fo), axis=0) logger.debug('Merged %i spectra with sigma = %f and base = %s'%(len(waves), sigma, base)) - + if full_output: rejected = np.where(fn.mask) accepted = np.where(fn.mask == False) @@ -561,92 +561,92 @@ def cross_correlate(obj_wave, obj_flux, temp_wave, temp_flux, step=0.3, nsteps=5 """ Cross correlate a spectrum with a template, working in velocity space. The velocity range is controlled by using step, nsteps and start_dev as: - + velocity = np.arange(start_dev - nsteps * step , start_dev + nsteps * step , step) - - If two_step is set to True, then it will run twice, and in the second run focus on + + If two_step is set to True, then it will run twice, and in the second run focus on the velocity where the correlation is at its maximum. - + Returns the velocity and the normalized correlation function """ - + def correlate(dvel): rebin_flux = interp1d(temp_wave * ( 1 + 1000. * dvel / constants.cc ), temp_flux)(obj_wave) s2 = np.sqrt(np.sum(rebin_flux**2)/len(rebin_flux)) #RMS uncertainty return 1. / ( len(obj_flux) * s1 * s2 ) * np.sum( obj_flux * rebin_flux ) - + #-- First correlation s1 = np.sqrt(sum(obj_flux**2)/len(obj_flux)) #RMS uncertainty velocity = np.arange(start_dev - nsteps * step , start_dev + nsteps * step , step) correlation = np.array([correlate(dvel) for dvel in velocity]) - + #-- Possible second correlation if two_step: start_dev = velocity[correlation == np.max(correlation)] velocity = np.arange(start_dev - nsteps * step , start_dev + nsteps * step , step) correlation = np.array([correlate(dvel) for dvel in velocity]) - + #-- 'normalize' the correlation function correlation = correlation / correlation[0] - + return velocity, correlation def get_response(instrument='hermes'): """ - Returns the response curve of the given instrument. Up till now only a HERMES - response cruve is available. This response curve is based on 25 spectra of the + Returns the response curve of the given instrument. Up till now only a HERMES + response cruve is available. This response curve is based on 25 spectra of the single sdB star Feige 66, and has a wavelenght range of 3800 to 8000 A. - + @param instrument: the instrument of which you want the response curve @type instrument: string """ - + basedir = 'spectables/responses/' - + if instrument == 'hermes': basename = 'response_Feige66.fits' - + response = config.get_datafile(basedir,basename) - + wave, flux = fits.read_spectrum(response) - + return wave, flux def remove_response(wave, flux, instrument='hermes'): """ Divides the provided spectrum by the response curve of the given instrument. Up till now only a HERMES response curve is available. - + @param wave: wavelenght array @type wave: numpy array @param flux: flux array @type flux: numpy array @param instrument: the instrument of which you want the response curve @type instrument: string - + @return: the new spectrum (wave, flux) @rtype: (array, array) """ - + #-- get the response curve wr, fr = get_response(instrument=instrument) fr_ = interp1d(wr, fr, kind='linear') - + #-- select usefull part of the spectrum flux = flux[(wave >= wr[0]) & (wave <= wr[-1])] wave = wave[(wave >= wr[0]) & (wave <= wr[-1])] - + flux = flux / fr_(wave) - + return wave, flux - + if __name__=="__main__": - + import pylab as pl from ivs.inout import fits import time - + temp = '/STER/mercator/hermes/%s/reduced/%s_HRF_OBJ_ext_CosmicsRemoved_log_merged_c.fits' objlist = [('20090619','237033'), ('20090701','240226'), ('20090712','241334'), ('20090712','241335'), @@ -665,11 +665,11 @@ def remove_response(wave, flux, instrument='hermes'): ('20130106','00445346'), ('20130215','00452556'), ('20130406','00457718'), ('20130530','00474128')] mergeList = [temp%o for o in objlist] - + t1 = time.time() waves, fluxes = [], [] wtotal, ftotal = fits.read_spectrum(mergeList[0]) - wtotal = wtotal[(wtotal>5000) & (wtotal<7000)] + wtotal = wtotal[(wtotal>5000) & (wtotal<7000)] ftotal = np.zeros_like(wtotal) for ifile in mergeList: w, f = fits.read_spectrum(ifile) @@ -679,32 +679,32 @@ def remove_response(wave, flux, instrument='hermes'): fluxes.append(f) ftotal += np.interp(wtotal,w,f) t1 = time.time() - t1 - + t2 = time.time() wave, flux, accepted, rejected = merge_cosmic_clipping(waves, fluxes, sigma=5.0, window=21, offset='std', base='average', full_output=True) t2 = time.time() - t2 - - print 'reading ', t1 - print 'processing ', t2 - + + print('reading ', t1) + print('processing ', t2) + wrej = wtotal[rejected[1]] frej = ftotal[rejected[1]] - print len(wrej) - - + print(len(wrej)) + + ftotal = ftotal[(wtotal>5000) & (wtotal<7000)] - wtotal = wtotal[(wtotal>5000) & (wtotal<7000)] + wtotal = wtotal[(wtotal>5000) & (wtotal<7000)] flux = flux[(wave>5000) & (wave<7000)] wave = wave[(wave>5000) & (wave<7000)] - + pl.plot(wtotal, ftotal) pl.plot(wave, flux) pl.plot(wrej, frej, '.r') pl.show() - - - + + + #import doctest #import pylab as pl #doctest.testmod() diff --git a/statistics/covellipse.py b/statistics/covellipse.py index 4c53021c1..34841632b 100644 --- a/statistics/covellipse.py +++ b/statistics/covellipse.py @@ -29,7 +29,7 @@ Make a plot of the data as well as the ellipse: >>> p.scatter(m[:,0], m[:,1], s=5) ->>> p.plot(x,y,linewidth=2) +>>> p.plot(x,y,linewidth=2) """ @@ -41,7 +41,7 @@ def ellipse(xc, yc, a, b, phi): """ Returns x and y values of a complete ellipse - + @param xc: x-coordinate of the center of the ellipse @type xc: float @param yc: y-coordinate of the center of the ellipse @@ -51,17 +51,17 @@ def ellipse(xc, yc, a, b, phi): @param b: half the small axis of the ellipse @type b: float @param phi: angle between long axis and the x-axis (radians) - @type phi: float - @return: x,y: coordinate arrays of ellipse + @type phi: float + @return: x,y: coordinate arrays of ellipse @rtype: tuple of 2 arrays (dtype: float) """ - + t = np.linspace(0,2*pi,100) x = xc + a * np.cos(t) * np.cos(phi) - b * np.sin(t) * np.sin(phi) y = yc + a * np.cos(t) * np.sin(phi) + b * np.sin(t) * np.cos(phi) - + return x,y - + @@ -70,16 +70,16 @@ def sigmaEllipse(xc, yc, covMatrix, nSigma): """ Return x and y value of the |nSigma|-sigma ellipse Given a covariance matrix, and the center of the ellipse (xc,yc) - + @param xc: x-coordinate of the center of the ellipse @type xc: float @param yc: y-coordinate of the center of the ellipse @type yc: float @param covMatrix: 2D covariance matrix @type covMatrix: 2x2 float nd-array - @param nSigma: the nSigma-contour of the ellipse will be returned + @param nSigma: the nSigma-contour of the ellipse will be returned @type nSigma: float - @return: (x,y): the x and y values of the ellipse + @return: (x,y): the x and y values of the ellipse @rtype: tuple of two float ndarrays """ @@ -87,13 +87,13 @@ def sigmaEllipse(xc, yc, covMatrix, nSigma): b = covMatrix [0,1] c = b d = covMatrix[1,1] - + eigenvalue1 = 0.5*(a+d+np.sqrt((a-d)**2+4*b*c)) eigenvalue2 = 0.5*(a+d-np.sqrt((a-d)**2+4*b*c)) - + semimajor = sqrt(eigenvalue1) * nSigma semiminor = sqrt(eigenvalue2) * nSigma - + if b == 0.0: eigenvector1 = np.array([1, 0]) eigenvector2 = np.array([0, 1]) @@ -102,10 +102,10 @@ def sigmaEllipse(xc, yc, covMatrix, nSigma): eigenvector1 = np.array([b, eigenvalue1-a]) eigenvector2 = np.array([b, eigenvalue2-a]) phi = atan((eigenvalue1-a)/b) - + x,y = ellipse(xc, yc, semimajor, semiminor, phi) - + return x,y - - + + diff --git a/statistics/linearregression.py b/statistics/linearregression.py index a849b6798..333e68533 100644 --- a/statistics/linearregression.py +++ b/statistics/linearregression.py @@ -67,7 +67,7 @@ >>> myFit.confidenceIntervals(0.05) (array([ 0.19383541, 2.98301422]), array([ 2.84545333, 3.0292886 ])) -The 95% confidence interval of coefficient a_0 is thus [0.19, 2.85]. See the list +The 95% confidence interval of coefficient a_0 is thus [0.19, 2.85]. See the list of methods for more functionality. Note that also weights can be taken into account. To evaluate your linear model in a new set of covariates 'xnew', use the method evaluate(). @@ -81,12 +81,12 @@ 1.80110565 1.99606954 2.32608192 2.8846888 3.83023405 5.43074394 8.13990238 12.72565316 20.48788288 33.62688959 55.86708392 93.51271836 157.23490405 265.09646647 447.67207215] - + Assessing the models ==================== -Test if we can reject the null hypothesis that all coefficients are zero +Test if we can reject the null hypothesis that all coefficients are zero with a 5% significance level: >>> myFit.FstatisticTest(0.05) @@ -140,7 +140,7 @@ without the observations. Fitting the same model to different sets of observations can therefore be done a lot faster this way. - + """ @@ -149,7 +149,6 @@ from math import sqrt,log,pi from itertools import combinations import numpy as np -import scipy as sp import scipy.stats as stats @@ -159,35 +158,35 @@ class LinearModel(object): """ - A linear model class - + A linear model class + The class implements a linear model of the form - + C{y(x) = a_0 * f_0(x) + a_1 * f_1(x) + ... + a_{n-1} f_{n-1}(x)} - + where - y are the responses (observables) - x are the covariates - a_i are the regression coefficients (to be determined) - f_i(x) are the regressors evaluated in the covariates. - + Note: LinearModel instances can be added and printed. """ def __init__(self, regressors, nameList, covMatrix = None, regressorsAreWeighted = False): - + """ - Initialiser of the LinearModel class. + Initialiser of the LinearModel class. Example: - + >>> x = linspace(0,10,100) >>> lm = LinearModel([sin(x),x*x], ["sin(x)", "x^2"]) - - @param regressors: either a list of equally-sized numpy arrays with the regressors + + @param regressors: either a list of equally-sized numpy arrays with the regressors evaluated in the covariates: [f_0(x),f_1(x),f_2(x),...], - or an N x M design matrix (numpy array) where these regressor arrays + or an N x M design matrix (numpy array) where these regressor arrays are column-stacked, with N the number of regressors, and M the number of data points. @type regressors: list or ndarray @@ -201,11 +200,11 @@ def __init__(self, regressors, nameList, covMatrix = None, regressorsAreWeighted @type regressorsAreWeighted: boolean @return: a LinearModel instance @rtype: LinearModel - + """ # Sanity check of the 'regressors' - + if isinstance(regressors, list): self._designMatrix = np.column_stack(np.double(regressors)) self._nParameters = len(regressors) @@ -219,15 +218,15 @@ def __init__(self, regressors, nameList, covMatrix = None, regressorsAreWeighted # Sanity check of the 'nameList' - + if len(nameList) != self._nParameters: raise ValueError("Number of names not equal to number of regressors") else: self._regressorNames = copy.copy(nameList) - + # Sanity check of the 'covMatrix' - + if covMatrix == None: self._covMatrixObserv = None elif not isinstance(covMatrix, np.ndarray): @@ -242,26 +241,26 @@ def __init__(self, regressors, nameList, covMatrix = None, regressorsAreWeighted else: self._covMatrixObserv = covMatrix - - # If a covariance matrix is specified, and if not already done, compute the weighted - # design matrix. The weights are determined by the cholesky decomposition of the - # inverse of the covariance matrix. A simple way would be to first invert the covariance - # matrix, cholesky-decompose the result, and then do a matrix multiplication. A faster + + # If a covariance matrix is specified, and if not already done, compute the weighted + # design matrix. The weights are determined by the cholesky decomposition of the + # inverse of the covariance matrix. A simple way would be to first invert the covariance + # matrix, cholesky-decompose the result, and then do a matrix multiplication. A faster # method is to cholesky-decompose the covariance matrix, and then determine the # weighted design matrix by a standard solve. # 'choleskyLower' is the lower triangular matrix of the cholesky decomposition. - + if (self._covMatrixObserv != None) and (regressorsAreWeighted == False): self._choleskyLower = np.linalg.cholesky(self._covMatrixObserv) for n in range(self._designMatrix.shape[1]): self._designMatrix[:,n] = np.linalg.solve(self._choleskyLower, self._designMatrix[:,n]) else: self._choleskyLower = None - - - + + + # Initialisation of several internal variables - + self._pseudoInverse = None self._standardCovarianceMatrix = None self._conditionNumber = None @@ -274,33 +273,33 @@ def __init__(self, regressors, nameList, covMatrix = None, regressorsAreWeighted self._w = None self._B = None self._svdTOL = np.finfo(np.double).eps * self._nObservations # tolerance to ignore singular values - - + + def __add__(self, linearModel): - + """ Defines operator '+' of two LinearModel instances. - + Example: - + >>> x = np.linspace(0,10,100) >>> lm1 = LinearModel([x], ["x"]) >>> lm2 = LinearModel([x**2], ["x^2"]) >>> lm3 = lm1 + lm2 # the same as LinearModel([x, x**2], ["x", "x^2"]) - - + + @param linearModel: a LinearModel instance with the same number of observations as the linear model in 'self' @type linearModel: LinearModel class - @return: a newly created LinearModel object representing a linear model that is the sum + @return: a newly created LinearModel object representing a linear model that is the sum of 'self' and 'linearModel'. 'self' and 'linearModel' are unaltered. @rtype: LinearModel - + """ - + if not isinstance(linearModel, LinearModel): raise TypeError("Only a LinearModel can be added to a LinearModel") @@ -309,32 +308,32 @@ def __add__(self, linearModel): if not np.alltrue(linearModel._covMatrixObserv == self._covMatrixObserv): raise ValueError("Linear model has a different covariance matrix") - + designMatrix = np.hstack([self._designMatrix, linearModel.designMatrix()]) regressorNames = self._regressorNames + linearModel.regressorNames() - + if self._covMatrixObserv == None: return LinearModel(designMatrix, regressorNames) else: return LinearModel(designMatrix, regressorNames, self._covMatrixObserv, regressorsAreWeighted = True) - - - - - - - - + + + + + + + + def designMatrix(self): - + """ Returns the design matrix of the linear model - + Example: - + >>> x = np.linspace(0,10,5) >>> lm = LinearModel([x], ["x"]) >>> lm.designMatrix() @@ -344,14 +343,14 @@ def designMatrix(self): [ 7.5 , 56.25], [ 10. , 100. ]]) - @return: a N x M design matrix. If the model is written as + @return: a N x M design matrix. If the model is written as y(x) = a_0 * f_0(x) + a_1 * f_1(x) + ... + a_{n-1} f_{n-1}(x) then the design matrix A is defined by A_{i,j} = f_i(x_j). N is the number of regressors, M is the number of data points. @rtype: ndarray - + """ - + return self._designMatrix @@ -366,42 +365,42 @@ def designMatrix(self): def _singularValueDecompose(self): - + """ Performs and stores the singular value decomposition of the design matrix - + Private class method, not to be used by the user. - - The singular value decomposition of the design matrix is defined as - + + The singular value decomposition of the design matrix is defined as + C{X = U * W * V^t } - - where + + where - X: M x N design matrix. - - U: M x M unitary matrix + - U: M x M unitary matrix - W: M x N diagonal matrix - V: N x N unitary matrix - with M the number of observations, and N the number of regressors. Note that for a + with M the number of observations, and N the number of regressors. Note that for a unitary matrix A: A^{-1} = A^t - + @return: nothing - + """ - + # Compute the singular value decomposition of the design matrix. # As the number of singular values is between 1 and min(M,N), only compute # the first min(M,N) columns of U, as only these columns are ever used # (i.e. multiplied with non-zero values). In the following decomposition: # - w is a vector containing the non-zero diagonal elements of W # - V^t is returned rather than V - - + + self._U,self._w, Vt = np.linalg.svd(self._designMatrix, full_matrices=0) self._V = Vt.T # svd() actually returns V^t rather than V # Compute the inverse of the singular values. But set the inverse value - # to zero if the - + # to zero if the + wmax = self._w.max() self._invSingularValues = np.zeros_like(self._w) for n in range(len(self._w)): @@ -409,7 +408,7 @@ def _singularValueDecompose(self): self._invSingularValues[n] = 1.0 / self._w[n] - + @@ -418,17 +417,17 @@ def _singularValueDecompose(self): def pseudoInverse(self): - + """ Computes the pseudo-inverse in a robust way - + Remarks: - - The "robustness" is obtained by using a singular value decomposition - of X, and treating the singular values in order to avoid numerical + - The "robustness" is obtained by using a singular value decomposition + of X, and treating the singular values in order to avoid numerical problems. - + Example: - + >>> x = linspace(0,10,5) >>> lm = LinearModel([x, x**2], ["x", "x^2"]) >>> lm.pseudoInverse() @@ -437,15 +436,15 @@ def pseudoInverse(self): [ -1.07455262e-17, -1.80645161e-02, -2.06451613e-02, -7.74193548e-03, 2.06451613e-02]]) - @return: A numerical safe version of the N x M matrix - X^t X)^{-1} X^t where X is the M x N design matrix, - ^t the transpose, ^{-1} the inverse matrix. + @return: A numerical safe version of the N x M matrix + X^t X)^{-1} X^t where X is the M x N design matrix, + ^t the transpose, ^{-1} the inverse matrix. The multiplications are matrix multiplications. @rtype: ndarray """ - + if self._pseudoInverse is not None: return self._pseudoInverse else: @@ -456,7 +455,7 @@ def pseudoInverse(self): for n in range(N): temp[n] = self._invSingularValues[n] * self._U[:,n] self._pseudoInverse = np.dot(self._V, temp) - + return self._pseudoInverse @@ -472,22 +471,22 @@ def pseudoInverse(self): def standardCovarianceMatrix(self): - + """ Computes the standard covariance matrix. - + Remarks: - - The "safeness" is obtained by using a singular value - decomposition of X, and treating the singular values to avoid + - The "safeness" is obtained by using a singular value + decomposition of X, and treating the singular values to avoid numerical problems. - - The matrix C is the covariance matrix of the regression + - The matrix C is the covariance matrix of the regression coefficients if the signal noise would be i.i.d. with sigma = 1. - In the case of sigma != 1, the actual covariance matrix can be obtained by simply rescaling the standard covariance matrix. See L{LinearFit.covarianceMatrix}. - - Example: - + + Example: + >>> x = linspace(0,10,5) >>> lm = LinearModel([x, x**2], ["x", "x^2"]) >>> lm.standardCovarianceMatrix() @@ -495,21 +494,21 @@ def standardCovarianceMatrix(self): [-0.01032258, 0.00123871]]) @return: A numerical safe version of C = (X^t X)^{-1} where - X is the design matrix, ^t the transpose, ^{-1} the - inverse matrix. The multiplications are matrix - multiplications. + X is the design matrix, ^t the transpose, ^{-1} the + inverse matrix. The multiplications are matrix + multiplications. @rtype: ndarray """ - + if self._standardCovarianceMatrix != None: return self._standardCovarianceMatrix else: if self._V == None: self._singularValueDecompose() self._standardCovarianceMatrix = np.dot(self._V, np.dot(np.diag(self._invSingularValues**2), self._V.T)) return self._standardCovarianceMatrix - + @@ -521,33 +520,33 @@ def standardCovarianceMatrix(self): def conditionNumber(self): - + """ Computes the condition number. - + Example: - + >>> x = linspace(0,10,5) >>> lm = LinearModel([x, x**2], ["x", "x^2"]) >>> lm..conditionNumber() 35.996606504814814 @return: The ratio of the highest to the lowest singular value, - where the singular values are obtained by singular value - decomposition of the design matrix. A large condition number + where the singular values are obtained by singular value + decomposition of the design matrix. A large condition number implies an ill-conditioned design matrix. @rtype: double - + """ - + if self._conditionNumber is not None: return self._conditionNumber else: if self._w == None: self._singularValueDecompose() self._conditionNumber = self._w.max() / self._w.min() return self._conditionNumber - + @@ -559,17 +558,17 @@ def conditionNumber(self): def singularValues(self): - + """ - Return the non-zero singular values + Return the non-zero singular values as obtained with the singular value decomposition - - Remarks: - - the singular values can be used a diagnostic to see how + + Remarks: + - the singular values can be used a diagnostic to see how ill-conditioned the matrix inversion is. - + Example: - + >>> x = linspace(0,10,5) >>> lm = LinearModel([x, x**2], ["x", "x^2"]) >>> lm.singularValues() @@ -579,7 +578,7 @@ def singularValues(self): @rtype: ndarray """ - + if self._w != None: return self._w else: @@ -598,30 +597,30 @@ def singularValues(self): def hatMatrix(self): - + """ Computes the hat matrix. - + The hat matrix is defined by H = X (X^t X)^{-1} X^t with - X the (weighted) design matrix - - ^t the transpose, - - ^{-1} the inverse matrix - and where all multiplications are matrix multiplications. + - ^t the transpose, + - ^{-1} the inverse matrix + and where all multiplications are matrix multiplications. It has the property that \hat{y} = H * y where - \hat{y} are the (weighted) predictions - - y the (weighted) observations. + - y the (weighted) observations. So, it "puts a hat" on the (weighted) observation vector. - + Remarks: - as the number of data points may be large, this matrix may become so huge that it no longer fits into your computer's memory - - for weighted (and/or correlated) observations, the resulting - weighted hat matrix does not put a hat on the unweighted + - for weighted (and/or correlated) observations, the resulting + weighted hat matrix does not put a hat on the unweighted observations vector, only on the weighted one. - + Example: - + >>> x = linspace(0,10,5) >>> lm = LinearModel([x, x**2], ["x", "x^2"]) >>> lm.hatMatrix() @@ -638,16 +637,16 @@ def hatMatrix(self): @return: The hat matrix. @rtype: ndarray - + """ - + if self._hatMatrix is not None: return self._hatMatrix else: if self._U is None: self._singularValueDecompose() self._hatMatrix = np.dot(self._U, self._U.T) return self._hatMatrix - + @@ -660,23 +659,23 @@ def hatMatrix(self): def traceHatMatrix(self): - + """ Returns the trace of the (possibly weighted) hat matrix. - - The computation of the entire MxM hat matrix (M the number of - observations) is avoided to calculate the trace. This is useful - when the number of observations is so high that the hat matrix + + The computation of the entire MxM hat matrix (M the number of + observations) is avoided to calculate the trace. This is useful + when the number of observations is so high that the hat matrix does no longer fit into the memory. - + Remarks: - If the hat matrix was computed anyway it will be used. - The trace of the hat matrix that puts a hat on the original - observations equals the trace of the hat matrix that puts a + observations equals the trace of the hat matrix that puts a hat on the weighted observations. - + Example: - + >>> x = linspace(0,10,5) >>> lm = LinearModel([x, x**2], ["x", "x^2"]) >>> lm.hatMatrix() @@ -684,9 +683,9 @@ def traceHatMatrix(self): @return: The trace of the hat matrix. @rtype: ndarray - + """ - + if self._traceHatMatrix is not None: return self._traceHatMatrix else: @@ -698,8 +697,8 @@ def traceHatMatrix(self): for k in range(self._U.shape[1]): self._traceHatMatrix += np.sum(np.square(self._U[:,k])) return self._traceHatMatrix - - + + @@ -712,15 +711,15 @@ def traceHatMatrix(self): def degreesOfFreedom(self): - + """ Returns the effective number of degrees of freedom. - + The d.o.f. is defined as the trace of the hat matrix. See also: U{Wikipedia} - + Example: - + >>> x = linspace(0,10,5) >>> lm = LinearModel([x, x**2], ["x", "x^2"]) >>> lm.degreesOfFreedom() @@ -728,15 +727,15 @@ def degreesOfFreedom(self): @return: The effective number of degrees of freedom. @rtype: integer - + """ - + if self._degreesOfFreedom is not None: return self._degreesOfFreedom else: self._degreesOfFreedom = self._nObservations - int(round(self.traceHatMatrix())) return self._degreesOfFreedom - + @@ -748,25 +747,25 @@ def degreesOfFreedom(self): def regressorNames(self): - + """ Returns the regressor names. - + Example: - + >>> x = linspace(0,10,100) - >>> lm = LinearModel([sin(x),x*x], ["sin(x)", "x^2"]) + >>> lm = LinearModel([sin(x),x*x], ["sin(x)", "x^2"]) >>> lm.regressorNames() ["sin(x)", "x^2"] @return: list with strings containing the names of the regressors. @rtype: list - + """ - + return self._regressorNames - - + + @@ -780,12 +779,12 @@ def regressorNames(self): def containsRegressorName(self, regressorName): - + """ Checks if the linear model contains the given regressor. - - Example: - + + Example: + >>> x = linspace(0,10,100) >>> lm = LinearModel([sin(x), x*x], ["sin(x)", "x^2"]) >>> lm.containsRegressorName("x^2") @@ -795,15 +794,15 @@ def containsRegressorName(self, regressorName): @param regressorName: name of a regressor @type regressorName: string - @return: True if one of the regressor names is identical + @return: True if one of the regressor names is identical to the input 'regressorName', else False. @rtype: boolean - + """ - + return regressorName in self._regressorNames - - + + @@ -812,12 +811,12 @@ def containsRegressorName(self, regressorName): def someRegressorNameContains(self, someString): - + """ Checks if a regressor name contains a given substring. - + Example: - + >>> x = linspace(0,10,100) >>> lm = LinearModel([sin(x), x*x], ["sin(x)", "x^2"]) >>> lm.someRegressorNameContains("sin") @@ -825,16 +824,16 @@ def someRegressorNameContains(self, someString): @param someString: substring to be checked @type someString: string - + @return: True if at least one of the regressor names contains the substring someString, else return false. @rtype: boolean - + """ for name in self._regressorNames: if someString in name: return True - + return False @@ -847,12 +846,12 @@ def someRegressorNameContains(self, someString): def withIntercept(self): - + """ Checks if there is a constant regressor - + Example: - + >>> x = linspace(0,10,100) >>> lm = LinearModel([1, x*x], ["1", "x^2"]) >>> lm.withIntercept() @@ -860,10 +859,10 @@ def withIntercept(self): @return: True if there is a constant regressor (the intercept). False otherwise. - @rtype: boolean - + @rtype: boolean + """ - + if self._withIntercept is not None: return self._withIntercept else: @@ -874,7 +873,7 @@ def withIntercept(self): break return self._withIntercept - + @@ -884,12 +883,12 @@ def withIntercept(self): def nObservations(self): - + """ Returns the number of observations - + Example: - + >>> x = linspace(0,10,23) >>> lm = LinearModel([x], ["x"]) >>> lm.nObservations() @@ -897,15 +896,15 @@ def nObservations(self): @return: the number of observations that the LinearModel expects @rtype: integer - + """ - + return self._nObservations - - - - - + + + + + @@ -914,12 +913,12 @@ def nObservations(self): def nParameters(self): - + """ Returns the number of fit coefficients - + Example: - + >>> x = linspace(0,10,100) >>> lm = LinearModel([sin(x),x*x], ["sin(x)", "x^2"]) >>> lm.nParameters() @@ -927,12 +926,12 @@ def nParameters(self): @return: the numbero fit coefficients of the linear model @rtype: integer - + """ - + return self._nParameters - - + + @@ -944,36 +943,35 @@ def nParameters(self): def fitData(self, observations): - + """ Create and return a LinearFit object. - - From this object the user can extract all kinds of quantities + + From this object the user can extract all kinds of quantities related to the linear fit. See L{LinearFit}. - + Example: - + >>> x = linspace(0,10,100) >>> lm = LinearModel([x], ["x"]) >>> obs = x + normal(0.0, 1.0, 100) # some simulated observations >>> linearfit = lm.fitData(obs) - @param observations: array with the observations y_i. The size + @param observations: array with the observations y_i. The size of the array should be compatible with the number of expected observations in the LinearModel. @type observations: ndarray @return: a LinearFit instance, containing the fit of the observations with the linear model. @rtype: LinearFit - + """ - + if len(observations) != self._nObservations: + raise ValueError("Number of observations should be {0} != {1}".format(self._nObservations, len(observations))) - + return LinearFit(self, observations) - - @@ -983,24 +981,26 @@ def fitData(self, observations): - - + + + + def submodels(self, Nmin = 1, Nmax = None, nested = True, ranks = None, simpleFirst=True): - + """ Returns a generator capable of generating submodels - + The generator sequentially returns the submodels which are instances of LinearModel with only a subset of the regressors of the parent model. - The submodels with fewer regressors will always appear first in the list. - The order in which the regressors appear in the submodels will be from + The submodels with fewer regressors will always appear first in the list. + The order in which the regressors appear in the submodels will be from low rank to high rank. - - Examples: - + + Examples: + If the regressors are [1, x, x**2] then - Nmin=1, nested=False, ranks=None gives the submodels - [1], [x], [x**2], [1,x], [1,x**2], [x,x**2], [1,x,x**2] + [1], [x], [x**2], [1,x], [1,x**2], [x,x**2], [1,x,x**2] - Nmin=1, Nmax=2, nested=False, ranks=None gives the submodels [1], [x], [x**2], [1,x], [1,x**2], [x,x**2] - Nmin=2, nested=False, ranks=None gives the submodels @@ -1008,7 +1008,7 @@ def submodels(self, Nmin = 1, Nmax = None, nested = True, ranks = None, simpleFi - Nmin=3, nested=True, ranks=None gives the submodel [1,x,x**2] - Nmin=3, nested=True, ranks=[1,0,3] gives the submodel - [x,1,x**2] + [x,1,x**2] - Nmin=2, nested=False, ranks=[1,0,3] gives the submodels [x,1], [x,x**2], [1,x**2], [x,1,x**2] - Nmin=2, nested=True, ranks=[1,0,3] gives the submodels @@ -1023,29 +1023,29 @@ def submodels(self, Nmin = 1, Nmax = None, nested = True, ranks = None, simpleFi - Nmin=1, Nmax=2, nested=False, ranks=[0,1,2,2] gives the submodels [1], [x], [sin(2x),cos(2x)], [1,x], [1,sin(2x),cos(2x)], [x,sin(2x),cos(2x)] - @param Nmin: Minimum number of regressors (or groups of equally ranking - regressors). Should be minimal 1. + @param Nmin: Minimum number of regressors (or groups of equally ranking + regressors). Should be minimal 1. @type Nmin: integer - @param Nmax: Maximum number of regressors (or groups of equally ranking - regressors). If == None, it will take the maximum number - possible. Note: if the ranks = [0,1,2,2] then there are - maximum 3 "groups" of regressors, as the latter 2 regressors + @param Nmax: Maximum number of regressors (or groups of equally ranking + regressors). If == None, it will take the maximum number + possible. Note: if the ranks = [0,1,2,2] then there are + maximum 3 "groups" of regressors, as the latter 2 regressors will always be taken together. @type Nmax: integer - @param nested: if False: return all possible submodels, i.e. all possible + @param nested: if False: return all possible submodels, i.e. all possible combinations of regressors. If true: return only nested - submodels with combinations of regressors (or groups of - equally ranked regressors) so that a higher ranked regressors + submodels with combinations of regressors (or groups of + equally ranked regressors) so that a higher ranked regressors will never appear without the lower ranked regressors @type nested: boolean - @param ranks: list with (integer) rankings of the regressors, only the - relative ranking of the numbers will be used, so the exact - numbers are not important. For example, [1,2,3] has the - same result as [10,24,35]. Regressors having the same rank - should always be included together in a submodel, regardless - how the input parameter 'nested' has been set. In case of - nested submodels, a regressor with a higher rank should - never occur without the regressors with lower rank in the + @param ranks: list with (integer) rankings of the regressors, only the + relative ranking of the numbers will be used, so the exact + numbers are not important. For example, [1,2,3] has the + same result as [10,24,35]. Regressors having the same rank + should always be included together in a submodel, regardless + how the input parameter 'nested' has been set. In case of + nested submodels, a regressor with a higher rank should + never occur without the regressors with lower rank in the same submodel. @type ranks: list with integers @param simpleFirst: if True: generate the more simple submodels first. @@ -1053,38 +1053,38 @@ def submodels(self, Nmin = 1, Nmax = None, nested = True, ranks = None, simpleFi @type simpleFirst: boolean @return: a python generator implementing the yield method @rtype: generator - + """ - + # If no ranking of the regressors are given, consider the first regressor - # having the most important, and going down, the last regressor the least + # having the most important, and going down, the last regressor the least # important - + if ranks == None: ranks = np.arange(self._nParameters) - - # Sort the (unique) ranks from low (most important) to high (least important) - + + # Sort the (unique) ranks from low (most important) to high (least important) + uniqueRanks = np.unique(ranks) if Nmax == None: Nmax = len(uniqueRanks) - + # nRegressor is a list to be looped over, containing for each submodel the number # of regressors. Be aware that a group of regressors with equal rank are considered to - # be only 1 regressor in this accounting. - + # be only 1 regressor in this accounting. + if simpleFirst: - nRegressorRange = range(Nmin,Nmax+1) + nRegressorRange = list(range(Nmin,Nmax+1)) else: - nRegressorRange = range(Nmax,Nmin-1,-1) - + nRegressorRange = list(range(Nmax,Nmin-1,-1)) + # Make a generator yield the submodels - + if nested == True: for n in nRegressorRange: nameListSub = [] regressorListSub = [] - indices = [k for m in uniqueRanks[:n] for k in np.where(ranks == m)[0]] + indices = [k for m in uniqueRanks[:n] for k in np.where(ranks == m)[0]] nameListSub += [self._regressorNames[k] for k in indices] regressorListSub += [self._designMatrix[:,k] for k in indices] if self._covMatrixObserv != None: @@ -1103,8 +1103,6 @@ def submodels(self, Nmin = 1, Nmax = None, nested = True, ranks = None, simpleFi yield LinearModel(regressorListSub, nameListSub, covMatrixObservSub, regressorsAreWeighted=True) else: yield LinearModel(regressorListSub, nameListSub) - - @@ -1112,65 +1110,67 @@ def submodels(self, Nmin = 1, Nmax = None, nested = True, ranks = None, simpleFi - + + + def copy(self): - + """ Returns a duplicate of the current LinearModel - + Example: - + >>> x = linspace(0,10,100) >>> lm = LinearModel([x], ["x"]) >>> lm2 = lm.copy() @return: of duplicate of self. Not a reference, a real copy. @rtype: LinearModel - + """ - + if self._covMatrixObserv != None: return LinearModel(self._designMatrix.copy(), copy.copy(self._regressorNames), self._covMatrixObserv.copy()) else: return LinearModel(self._designMatrix.copy(), copy.copy(self._regressorNames)) - - - - - - + + + + + + def __str__(self): - + """ Generates what is written to the console if the LinearModel is printed @return: nothing - + """ - + if self._regressorNames[0] == "1": str = "Model: y = a_0" else: str = "Model: y = a_0 * %s" % self._regressorNames[0] - + for n in range(1, self._nParameters): if self._regressorNames[n] == "1": str += " + a_%d" % n else: str += " + a_%d * %s" % (n, self._regressorNames[n]) - + str += "\nExpected number of observations: %d" % self._nObservations - + return str - - + + @@ -1187,79 +1187,79 @@ class PolynomialModel(LinearModel): """ Class to implement a polynomial model - + The class implements a polynomial model function of the form - + C{y(x) = a_0 * a_1 * x + a_2 * x^2 + ...} - - where y are the responses (observables), x are the covariates, + + where y are the responses (observables), x are the covariates, the a_i are the regression coefficients (to be determined). Such a class provides convenient way of creating a polynomial model. - + Remarks: - PolynomialModel instances can be added to other PolynomialModel or LinearModel instances to give a new LinearModel instance. """ - - + + def __init__(self, covariate, covariateName, orderList, covMatrix = None): """ - Initialiser of the PolynomialModel class. + Initialiser of the PolynomialModel class. Example: - + >>> time = linspace(0,10,100) >>> pm = PolynomialModel(time, "t", [2,1]) - - @param covariate: an array of size N with the covariate values, + + @param covariate: an array of size N with the covariate values, where N is the number of observations @type covariate: ndarray - @param covariateName: the (preferably short) name of the covariate. + @param covariateName: the (preferably short) name of the covariate. E.g. "t", or "x". @type covariateName: string @param orderList: list of the orders that should be included in the model. E.g. [3,0,1] implies the model - a_0 * x^3 + a_1 + a_2 * x + a_0 * x^3 + a_1 + a_2 * x @type orderList: list @param covMatrix: square array containing the covariance matrix of the - observations. This way, weights and correlations can + observations. This way, weights and correlations can be taken into account. @type covMatrix: ndarray @return: an instance of PolynomialModel, subclass of LinearModel @rtype: PolynomialModel - + """ - + # Create and fill the design matrix - + designMatrix = np.empty((len(covariate), len(orderList))) for n in range(len(orderList)): designMatrix[:,n] = covariate**orderList[n] - - + + # List the regressor names - + regressorNames = [] for n in range(len(orderList)): if orderList[n] == 0: regressorNames += ["1"] else: regressorNames += ["%s^%d" % (covariateName, orderList[n])] - - + + # Let the base class do the initialising - + super(PolynomialModel, self).__init__(designMatrix, regressorNames, covMatrix, regressorsAreWeighted=False) - - + + # Testing for an intercept can be done more efficiently for polynomials - + if 0 in orderList: self._withIntercept = True else: self._withIntercept = False - + @@ -1268,44 +1268,44 @@ class HarmonicModel(LinearModel): """ Class to implement a harmonic model function. - + The class implements a harmonic model of the form - + C{y(x) = a_0 * sin(2*pi*f1*t) + a_1 * cos(2*pi*f1*t) + a_2 * sin(2*pi*f2*t) + a_3 * cos(2*pi*f2*t) + ...} - - where y are the responses (observables), x are the covariates, + + where y are the responses (observables), x are the covariates, the a_i are the regression coefficients (to be determined). Such a class provides a more convenient short way of creating a harmonic model. - - Remarks: + + Remarks: - HarmonicModel instances can be added to other HarmonicModel or LinearModel instances to give a new LinearModel instance. """ - - - def __init__(self, covariate, covariateName, freqList, freqNameList, + + + def __init__(self, covariate, covariateName, freqList, freqNameList, maxNharmonics=1, covMatrix = None): """ - Initialiser of HarmonicModel class, with *known* frequencies. + Initialiser of HarmonicModel class, with *known* frequencies. + + Example: - Example: - >>> time = linspace(0,10,100) >>> hm = HarmonicModel(time, "t", [2.34, 8.9], ["f_1", "f_2"], 1) - - @param covariate: an array of size N with the covariate values, + + @param covariate: an array of size N with the covariate values, where N is the number of observations. @type covariate: ndarray - @param covariateName: a (preferably short) name of the covariate. + @param covariateName: a (preferably short) name of the covariate. E.g. "t", or "x". @type covariateName: string @param freqList: the frequencies (unit: inverse unit of covariate) @type freqList: list - @param freqNameList: the (preferably short) names of the frequencies. + @param freqNameList: the (preferably short) names of the frequencies. E.g. "f_1" or "nu_1". @type freqNameList: list @param maxNharmonics: integer values > 1. The number of harmonics for @@ -1319,11 +1319,11 @@ def __init__(self, covariate, covariateName, freqList, freqNameList, @type covMatrix: ndarray @return: an instance of HarmonicModel, subclass of LinearModel @rtype: HarmonicModel - + """ - + # Create and fill the design matrix - + nParam = 2*len(freqList)*maxNharmonics designMatrix = np.empty((len(covariate), nParam)) runningIndex = 0 @@ -1332,23 +1332,23 @@ def __init__(self, covariate, covariateName, freqList, freqNameList, designMatrix[:,runningIndex] = np.sin(2.*pi*k*freqList[n]*covariate) designMatrix[:,runningIndex+1] = np.cos(2.*pi*k*freqList[n]*covariate) runningIndex += 2 - + # List the regressor names - + regressorNames = [] for n in range(len(freqList)): for k in range(1,maxNharmonics+1): - regressorNames += ["sin(2*pi*%d*%s*%s)" % (k, freqNameList[n], covariateName), + regressorNames += ["sin(2*pi*%d*%s*%s)" % (k, freqNameList[n], covariateName), "cos(2*pi*%d*%s*%s)" % (k, freqNameList[n], covariateName)] - + # Let the base class do the initialising - + super(HarmonicModel, self).__init__(designMatrix, regressorNames, covMatrix, regressorsAreWeighted=False) - - + + # There is no intercept for a harmonic model - + self._withIntercept = False @@ -1369,38 +1369,38 @@ def __init__(self, covariate, covariateName, freqList, freqNameList, class LinearFit(object): """ - A class that allows to easily extract the fit coefficients, + A class that allows to easily extract the fit coefficients, covariance matrix, predictions, residuals, etc. - + Remarks: LinearFit instances can be printed """ - - + + def __init__(self, linearModel, observations): - + """ Initialises the LinearFit instance - + @param linearModel: a LinearModel instance @type linearModel: LinearModel - @param observations: the observations. The size of the array must be - compatible with the number of observations that + @param observations: the observations. The size of the array must be + compatible with the number of observations that the linearModel expects. @type observations: ndarray @return: a LinearFit instance @rtype: LinearFit - + """ - + # Store some ndarrays internally for later - - self._linearModel = linearModel + + self._linearModel = linearModel self._nObservations = len(observations) self._nParameters = linearModel.designMatrix().shape[1] - + # Init some quantities as None, signaling that they have not yet been computed - + self._regressionCoefficients = None self._residuals = None self._weightedResiduals = None @@ -1420,28 +1420,28 @@ def __init__(self, linearModel, observations): self._AICvalue = None self._Fstatistic = None self._weightedFstatistic = None - - + + # If a covariance of the observations was specified, weight the observations. - + self._originalObservations = observations if linearModel._covMatrixObserv != None: self._weightedObservations = np.linalg.solve(linearModel._choleskyLower, observations) else: self._weightedObservations = observations - + def observations(self, weighted=False): - + """ Returns the observations - Remarks: + Remarks: - if no covariance matrix of the observations was specified for the model, the "decorrelated" observations are identical to the original observations. - + @param weighted: If false return the original observations, if True, return de decorrelated observations. @type weighted: boolean @@ -1449,48 +1449,48 @@ def observations(self, weighted=False): @rtype: ndarray """ - + if weighted: return self._weightedObservations else: return self._originalObservations - - - + + + def regressionCoefficients(self): - + """ - Returns the regression coefficients. - + Returns the regression coefficients. + @return: the fit coefficients @rtype: ndarray - + """ - + if self._regressionCoefficients is not None: return self._regressionCoefficients else: - self._regressionCoefficients = np.dot(self._linearModel.pseudoInverse(), + self._regressionCoefficients = np.dot(self._linearModel.pseudoInverse(), self._weightedObservations) return self._regressionCoefficients - - + + def sumSqResiduals(self, weighted=False): - + """ Returns the sum of the squared residuals - + @param weighted: If false the unweighted residuals are used, if True, the weighted ones are used. @type weighted: boolean @return: the sum of the squared residuals @rtype: double - + """ - + if weighted: if self._sumSqWeightedResiduals is not None: return self._sumSqWeightedResiduals @@ -1504,26 +1504,26 @@ def sumSqResiduals(self, weighted=False): self._sumSqResiduals = np.sum(np.square(self.residuals(weighted=False))) return self._sumSqResiduals - - + + def residualVariance(self, weighted=False): - + """ Estimates the variance of the residuals of the time series. - + As normalization the degrees of freedom is used - + @param weighted: If false the unweighted residuals are used, if True, the weighted ones are used. @type weighted: boolean @return: the variance of the residuals @rtype: double - + """ - + if weighted: if self._weightedResidualVariance is not None: return self._weightedResidualVariance @@ -1538,42 +1538,42 @@ def residualVariance(self, weighted=False): self._residualVariance = self.sumSqResiduals(weighted=False) \ / self._linearModel.degreesOfFreedom() return self._residualVariance - + def covarianceMatrix(self): - + """ Returns the covariance matrix of the fit coefficients - + @return: the MxM covariance matrix of the fit coefficients with M the number of fit coefficients @rtype: ndarray - + """ - + if self._covarianceMatrix is not None: return self._covarianceMatrix else: self._covarianceMatrix = self._linearModel.standardCovarianceMatrix() \ * self.residualVariance(weighted=True) return self._covarianceMatrix - - - + + + def correlationMatrix(self): - + """ Returns the correlation matrix of the fit coefficients - + @return: the MxM correlation matrix of the fit coefficients with M the number of fit coefficients - @rtype: ndarray - + @rtype: ndarray + """ - + if self._correlationMatrix is not None: return self._correlationMatrix else: @@ -1586,122 +1586,122 @@ def correlationMatrix(self): cor[m,n] = cor[n,m] cor[n,n] = 1.0 return self._correlationMatrix - + def errorBars(self): - + """ Returns the formal error bars of the fit coefficients - + @return: The square root of the diagonal of the covariance matrix @rtype: ndarray - + """ - + if self._errorBars is not None: return self._errorBars else: self._errorBars = np.sqrt(self.covarianceMatrix().diagonal()) return self._errorBars - + def confidenceIntervals(self, alpha): - + """ Returns the symmetric (1-alpha) confidence interval around the fit coefficients E.g. if alpha = 0.05, the 95% symmetric confidence interval is returned. Remarks: - - The formula used assumes that the noise on the observations is + - The formula used assumes that the noise on the observations is independently, identically and gaussian distributed. - + @param alpha: confidence level in ]0,1[ @type alpha: double @return: the arrays lowerBounds[0..K-1] and upperBounds[0..K-1] which contain respectively the lower and the upper bounds of the symmetric interval around the fit coefficients, where K is the number of fit coeffs. @rtype: tuple - + """ t = stats.t.ppf(1.0-alpha/2., self._linearModel.degreesOfFreedom()) lowerBounds = self.regressionCoefficients() - t * self.errorBars() upperBounds = self.regressionCoefficients() + t * self.errorBars() - + return lowerBounds, upperBounds - - - + + + def t_values(self): - + """ Returns the formal t-values of the fit coefficients - + @return: t-values = fit coefficients divided by their formal error bars @rtype: ndarray - + """ - + if self._t_values is not None: return self._t_values else: self._t_values = self.regressionCoefficients() / self.errorBars() return self._t_values - - - + + + def regressorTtest(self, alpha): - + """ - Performs a hypothesis T-test on each of the regressors + Performs a hypothesis T-test on each of the regressors Null hypothesis: H0 : fit coefficient == 0 Alternative hypothesis : H1 : fit coefficient != 0 - - Remarks: + + Remarks: - This test assumes that the noise is independently, - identically, and gaussian distributed. It is not robust + identically, and gaussian distributed. It is not robust against this assumption. - + @param alpha: significance level of the hypothesis test. In ]0,1[. E.g.: alpha = 0.05 @type alpha: double - @return: a boolean array of length K with K the number of regressors. - "True" if the null hypothesis was rejected for the regressor, + @return: a boolean array of length K with K the number of regressors. + "True" if the null hypothesis was rejected for the regressor, "False" otherwise. @rtype: ndarray - + """ - + t = stats.t.ppf(1.0-alpha/2., self._linearModel.degreesOfFreedom()) - + return np.fabs(self.t_values()) > t - + def predictions(self, weighted=False): - + """ Returns the predicted (fitted) values - - It concerns the predictions for the original observations, + + It concerns the predictions for the original observations, not the decorrelated ones. Remarks: - - If no covariance matrix of the observations was specified - for the model, the weighted predictions are identical to + - If no covariance matrix of the observations was specified + for the model, the weighted predictions are identical to the unweighted ones. - - @param weighted: If True/False, the predictions for the + + @param weighted: If True/False, the predictions for the decorrelated/original observations will be used. @type weighted: boolean - + """ - + if weighted: if self._weightedPredictions is not None: return self._weightedPredictions @@ -1721,25 +1721,25 @@ def predictions(self, weighted=False): self._predictions = np.dot(self._linearModel.designMatrix(), self.regressionCoefficients()) return self._predictions - + def residuals(self, weighted=False): - + """ - Returns an array with the residuals. + Returns an array with the residuals. Residuals = observations minus the predictions Remarks: - If no covariance matrix of the observations was specified for the model, the weighted residuals are identical to the unweighted ones. - - @param weighted: If True/False, the residuals for the + + @param weighted: If True/False, the residuals for the decorrelated/original observations will be used. @type weighted: boolean - + """ - + if weighted: if self._weightedResiduals is not None: return self._weightedResiduals @@ -1753,15 +1753,15 @@ def residuals(self, weighted=False): self._residuals = self._originalObservations - self.predictions(weighted=False) return self._residuals - - + + def coefficientOfDetermination(self, weighted=False): - + """ Returns the coefficient of determination - + The coeff of determination is defined by 1 - S1 / S2 with - S1 the sum of squared residuals: S1 = \frac{\sum_{i=1}^N (y_i - \hat{y}_i)^2} @@ -1771,18 +1771,18 @@ def coefficientOfDetermination(self, weighted=False): S2 = \frac{\sum_{i=1}^N (y_i)^2} Remarks: - - If no covariance matrix of the observations was specified - for the model, the weighted coeff of determination is + - If no covariance matrix of the observations was specified + for the model, the weighted coeff of determination is identical to the unweighted one. - - @param weighted: If True/False, the residuals for the + + @param weighted: If True/False, the residuals for the decorrelated/original observations will be used. @type weighted: boolean @return: coefficient of determination @rtype: double - + """ - + if weighted: if self._weightedCoeffOfDetermination is not None: return self._weightedCoeffOfDetermination @@ -1793,7 +1793,7 @@ def coefficientOfDetermination(self, weighted=False): else: sampleVariance = np.sum(np.square(self._weightedObservations)) self._weightedCoeffOfDetermination = 1.0 - self.sumSqResiduals(weighted=True) / sampleVariance - + return self._coefficientOfDetermination else: if self._coefficientOfDetermination is not None: @@ -1805,54 +1805,54 @@ def coefficientOfDetermination(self, weighted=False): else: sampleVariance = np.sum(np.square(self._originalObservations)) self._coefficientOfDetermination = 1.0 - self.sumSqResiduals(weighted=False) / sampleVariance - + return self._coefficientOfDetermination - + def BICvalue(self): - + """ - Returns the Bayesian Information Criterion value. - + Returns the Bayesian Information Criterion value. + Remarks: - Also called the Schwartz Information Criterion (SIC) - Gaussian noise is assumed, with unknown variance sigma^2 - Constant terms in the log-likelihood are omitted - + TODO: . make a weighted version - + @return: BIC value @rtype: double - + """ if self._BICvalue is not None: return self._BICvalue else: # Include the sigma of the noise in the number of unknown fit parameters - nParam = self._nParameters + 1 + nParam = self._nParameters + 1 self._BICvalue = self._nObservations * log(self.sumSqResiduals(weighted=False)/self._nObservations) \ + nParam * log(self._nObservations) return self._BICvalue - - - - + + + + def AICvalue(self): - + """ - Returns the 2nd order Akaike Information Criterion value - + Returns the 2nd order Akaike Information Criterion value + Remarks: - Gaussian noise is assumed, with unknown variance sigma^2 - Constant terms in the log-likelihood were omitted - If the number of observations equals the number of parameters + 1 then the 2nd order AIC is not defined. In this case the method gives a nan. - + TODO: . make a weighted version @return: AIC value @@ -1872,68 +1872,68 @@ def AICvalue(self): self._AICvalue = self._nObservations * log(self.sumSqResiduals(weighted=False)/self._nObservations) \ + 2*nParam + 2*nParam*(nParam+1)/(self._nObservations - nParam - 1) return self._AICvalue - + def Fstatistic(self, weighted=False): - + """ Returns the F-statistic, commonly used to assess the fit. - + Remarks: - if no covariance matrix of the observations was specified for the model, the weighted F-statistic is identical to the unweighted one - @param weighted: If True/False, the residuals for the + @param weighted: If True/False, the residuals for the decorrelated/original observations will be used. @type weighted: boolean @return: the F-statistic @rtype: double - + """ - + if weighted: if self._weightedFstatistic is not None: return self._weightedFstatistic else: Rsq = self.coefficientOfDetermination(weighted=True) df = self._linearModel.degreesOfFreedom() - + if self._nParameters == 1: self._Fstatistic = Rsq / (1-Rsq) * df / self._nParameters else: self._Fstatistic = Rsq / (1-Rsq) * df / (self._nParameters-1) - - return self._weightedFstatistic + + return self._weightedFstatistic else: if self._Fstatistic is not None: return self._Fstatistic else: Rsq = self.coefficientOfDetermination(weighted=False) df = self._linearModel.degreesOfFreedom() - + if self._nParameters == 1: self._Fstatistic = Rsq / (1-Rsq) * df / self._nParameters else: self._Fstatistic = Rsq / (1-Rsq) * df / (self._nParameters-1) - - return self._Fstatistic - - + + return self._Fstatistic + + def FstatisticTest(self, alpha, weighted=False): - + """ Performs a hypothesis F-test on the fit - + Null hypothesis: H0 : all fit coefficients == 0 Alternative hypothesis : H1 : at least one fit coefficient != 0 - + Stated otherwise (with R^2 the coefficient of determination): Null hypothesis: H0 : R^2 == 0 Alternative hypothesis: H1: R^2 != 0 - + Remarks: - if no covariance matrix of the observations was specified for the model, the weighted F-test is identical to the unweighted one @@ -1941,16 +1941,16 @@ def FstatisticTest(self, alpha, weighted=False): @param alpha: significance level of the hypothesis test. In ]0,1[. @type alpha: double - @param weighted: If True/False, the weighted/not-weighted + @param weighted: If True/False, the weighted/not-weighted F-statistic will be used. @type weighted: boolean @return: "True" if null hypothesis was rejected, "False" otherwise @rtype: boolean - + """ - + df = self._linearModel.degreesOfFreedom() - + if self._nParameters == 1: Fcritical = stats.f.ppf(1.0-alpha, self._nParameters, df) else: @@ -1961,17 +1961,17 @@ def FstatisticTest(self, alpha, weighted=False): def summary(self, outputStream = sys.stdout): - + """ Writes some basic results of fitting the model to the observations. - + @param outputStream: defaulted to the standard output console, but can be replaced by another open stream, like a file stream. @type outputStream: stream class. The .write() method is used. @return: nothing - + """ - + regressorNames = self._linearModel.regressorNames() if regressorNames[0] == "1": outputStream.write("Model: y = a_0") @@ -1983,13 +1983,13 @@ def summary(self, outputStream = sys.stdout): else: outputStream.write(" + a_%d * %s" % (n, regressorNames[n])) outputStream.write("\n") - + coeff = self.regressionCoefficients() errors = self.errorBars() outputStream.write("Regression coefficients:\n") for n in range(self._nParameters): - outputStream.write(" a_%d = %f +/- %f\n" % (n, coeff[n], errors[n])) - + outputStream.write(" a_%d = %f +/- %f\n" % (n, coeff[n], errors[n])) + outputStream.write("Sum of squared residuals: %f\n" % self.sumSqResiduals()) outputStream.write("Estimated variance of the residuals: %f\n" % self.residualVariance()) outputStream.write("Coefficient of determination R^2: %f\n" % self.coefficientOfDetermination()) @@ -2000,12 +2000,12 @@ def summary(self, outputStream = sys.stdout): def __str__(self): - + """ Returns the string written by printing the LinearFit object - + """ - + return("Linear fit of a dataset of {0} observations, using a linear model with {1} regressors".format(self._nObservations, self._nParameters)) @@ -2015,17 +2015,17 @@ def __str__(self): def evaluate(self, regressors): - + """ Evaluates your current best fit in regressors evaluated in new covariates. - + Remark: - The new regressor functions should be exactly the same ones as you used to define the linear model. They should only be evaluated in new covariates. This is not checked for! - + Example: - + >>> noise = array([0.44, -0.48, 0.26, -2.00, -0.93, 2.21, -0.57, -2.04, -1.09, 1.53]) >>> x = linspace(0, 5, 10) >>> obs = 2.0 + 3.0 * exp(x) + noise @@ -2042,42 +2042,42 @@ def evaluate(self, regressors): 1.80110565 1.99606954 2.32608192 2.8846888 3.83023405 5.43074394 8.13990238 12.72565316 20.48788288 33.62688959 55.86708392 93.51271836 157.23490405 265.09646647 447.67207215] - - @param regressors: either a list of equally-sized numpy arrays with the regressors + + @param regressors: either a list of equally-sized numpy arrays with the regressors evaluated in the new covariates: [f_0(xnew),f_1(xnew),f_2(xnew),...], - or an N x M design matrix (numpy array) where these regressor arrays + or an N x M design matrix (numpy array) where these regressor arrays are column-stacked, with N the number of regressors, and M the number of data points. @type regressors: either a list or an ndarray @return: the linear model evaluated in the new regressors @rtype: ndarray """ - + # Sanity check of the 'regressors' - + if not isinstance(regressors, list) and not isinstance(regressors, np.ndarray): raise TypeError("LinearModel only accepts a list of regressors, or a design matrix") - + # Construct the design matrix, if required - + if isinstance(regressors, list): designMatrix = np.column_stack(np.double(regressors)) else: designMatrix = regressors - + # Check whether the design matrix has the proper shape - + if designMatrix.ndim != 2: raise TypeError("Design matrix is not 2-dimensional") - + if designMatrix.shape[1] != self._nParameters: raise TypeError("Number of regressors not equal to the one of the original model") # Evaluate the model in the new regressors - + return np.dot(designMatrix, self.regressionCoefficients()) - - - + + + __all__ = [LinearModel, PolynomialModel, HarmonicModel, LinearFit] diff --git a/statistics/pca.py b/statistics/pca.py index 6faa5d06b..666ec9820 100644 --- a/statistics/pca.py +++ b/statistics/pca.py @@ -3,65 +3,65 @@ """ Principal component analysis """ -from numpy import abs, array, average, corrcoef, mat, shape, std, sum, transpose, zeros +from numpy import array, average, corrcoef, mat, shape, std, sum, transpose, zeros from numpy.linalg import svd - + __author__ = "Henning Risvik" __date__ = "February 2008" __version__ = "1.1.02" - -#{ Preprocessing Methods + +#{ Preprocessing Methods def mean_center(X): """ - - @param X: 2-dimensional matrix of number data + + @param X: 2-dimensional matrix of number data @type X: numpy array - - + + @return: Mean centered X (always has same dimensions as X) - + """ (rows, cols) = shape(X) new_X = zeros((rows, cols), float) _averages = average(X, 0) - + for row in range(rows): new_X[row, 0:cols] = X[row, 0:cols] - _averages[0:cols] return new_X - - -def standardization(X): + + +def standardization(X): """ - - @param X: 2-dimensional matrix of number data + + @param X: 2-dimensional matrix of number data @type X: numpy array - - + + @return: Standardized X (always has same dimensions as X) - + """ (rows, cols) = shape(X) new_X = zeros((rows, cols)) _STDs = std(X, 0) - + for value in _STDs: - if value == 0: raise ZeroDivisionError, 'division by zero, cannot proceed' - + if value == 0: raise ZeroDivisionError('division by zero, cannot proceed') + for row in range(rows): new_X[row, 0:cols] = X[row, 0:cols] / _STDs[0:cols] - return new_X + return new_X #} - -#{ NIPALS array help functions + +#{ NIPALS array help functions def get_column(E): """ Get an acceptable column-vector of E. - - @param E: 2-dimensional matrix of number data - @type E: numpy array - + + @param E: 2-dimensional matrix of number data + @type E: numpy array + @return: a non-zero vector """ (rows, cols) = shape(E) @@ -69,17 +69,17 @@ def get_column(E): t = E[:,col_ind] # extract a column if (vec_inner(t) > 0): return t - raise ValueError, 'all column vectors in E are zero vectors' # error: sum of matrix is 0 + raise ValueError('all column vectors in E are zero vectors') # error: sum of matrix is 0 def get_column_mat(E): """ NIPALS matrix help function. Get an acceptable column-vector of E. - - @param E: 2-dimensional matrix of number data - @type E: numpy matrix - + + @param E: 2-dimensional matrix of number data + @type E: numpy matrix + @return: a non-zero vector """ (rows, cols) = shape(E) @@ -88,68 +88,68 @@ def get_column_mat(E): eig = transpose(t)*t if (eig > 0): return t - raise ValueError, 'all column vectors in E are zero vectors' # error: sum of matrix is 0 + raise ValueError('all column vectors in E are zero vectors') # error: sum of matrix is 0 def vec_inner(v): """ @param v: Vector of number data. - @type v: numpy array - + @type v: numpy array + @return: transpose(v) * v (float or int) """ return sum(v * v); def mat_prod(A, x): - """ + """ @param A: 2-dimensional matrix of number data. - @type A: numpy array - + @type A: numpy array + @param x: Vector of number data. - @type x: numpy array + @type x: numpy array @return: b of (Ax = b). Product of: matrix A (m,n) * vector x (n) = vector b (m) """ #m = A.shape[0] #b = zeros((m), float) - + # calc: Ax = b #for i in range(m): # b[i] = sum(A[i,:]*x) - return array(map(lambda a: sum(a[:]*x), A)) + return array([sum(a[:]*x) for a in A]) def remove_tp_prod(E, t, p): """ - - sets: E = E - (t*transpose(p)) - E: (m, n)-matrix, (t*transpose(p)): (m, n)-matrix - - + + sets: E = E - (t*transpose(p)) + E: (m, n)-matrix, (t*transpose(p)): (m, n)-matrix + + @param E: 2-dimensional matrix of number data. - @type E: numpy array - + @type E: numpy array + @param t: Vector of number data. Current Scores (of PC_i). - @type t: numpy array + @type t: numpy array @param p: Vector of number data. Current Loading (of PC_i). - @type p: numpy array + @type p: numpy array - @return: None + @return: None """ - + m = E.shape[0] for i in range(m): E[i, :] = E[i, :] - (t[i] * p) - + #} #{ NIPALS Algorithm """ - Estimation of PC components with the iterative NIPALS method: + Estimation of PC components with the iterative NIPALS method: E[0] = mean_center(X) (the E-matrix for the zero-th PC) @@ -157,37 +157,37 @@ def remove_tp_prod(E, t, p): t = E(:, 0) (a column in X (mean centered) is set as starting t vector) for i=1 to (PCs): - + 1 p=(E[i-1]'t) / (t't) Project X onto t to find the corresponding (improve estimated) loading p - + 2 p = p * (p'p)^-0.5 Normalise loading vector p to length 1 - + 3 t = (E[i-1]p) / (p'p) Project X onto p to find corresponding (improve estimated) score vector t - + 4 Check for convergence, if difference between eigenval_new and eigenval_old is larger than threshold*eigenval_new return to step 1 - - 5 E[i] = E[i-1] - tp' Remove the estimated PC component from E[i-1] - -""" + + 5 E[i] = E[i-1] - tp' Remove the estimated PC component from E[i-1] + +""" def nipals_mat(X, PCs, threshold, E_matrices): """ - - + + PCA by NIPALS using numpy matrix - - - @param X: 2-dimensional matrix of number data. + + + @param X: 2-dimensional matrix of number data. @type X: numpy array - + @param PCs: Number of Principal Components. @type PCs: int - - @param threshold: Convergence check value. For checking on convergence to zero difference (e.g. 0.000001). + + @param threshold: Convergence check value. For checking on convergence to zero difference (e.g. 0.000001). @type threshold: float - + @param E_matrices: If E-matrices should be retrieved or not. E-matrices (for each PC) or explained_var (explained variance for each PC). @type E_matrices: bool - + @return: (Scores, Loadings, E) """ @@ -195,10 +195,10 @@ def nipals_mat(X, PCs, threshold, E_matrices): maxPCs = min(rows, cols) # max number of PCs is min(objects, variables) if maxPCs < PCs: PCs = maxPCs # change to maxPCs if PCs > maxPCs - + Scores = zeros((rows, PCs), float) # all Scores (T) Loadings = zeros((PCs, cols), float) # all Loadings (P) - + E = mat(X.copy()) #E[0] (should already be mean centered) @@ -207,61 +207,61 @@ def nipals_mat(X, PCs, threshold, E_matrices): else: explained_var = zeros((PCs)) tot_explained_var = 0 - + # total object residual variance for PC[0] (calculating from E[0]) e_tot0 = 0 # for E[0] the total object residual variance is 100% for k in range(rows): e_k = array(E[k, :])**2 e_tot0 += sum(e_k) - - + + t = get_column_mat(E) # extract a column - - - + + + # do iterations (0, PCs) for i in range(PCs): convergence = False ready_for_compare = False E_t = transpose(E) - + while not convergence: eigenval = float(transpose(t)*t) p = (E_t*t) / eigenval # ........................................... step 1 - - _p = float(transpose(p)*p) + + _p = float(transpose(p)*p) p = p * _p**(-0.5) # ............................................... step 2 t = (E*p) / (transpose(p)*p) # ..................................... step 3 - - + + eigenval_new = float(transpose(t)*t) if not ready_for_compare: ready_for_compare = True else: # ready for convergence check if (eigenval_new - eigenval_old) < threshold*eigenval_new: # ... step 4 - convergence = True + convergence = True eigenval_old = eigenval_new; - - + + p = transpose(p) E = E - (t*p) # ........................................................ step 5 - + # add Scores and Loadings for PC[i] to the collection of all PCs _t = array(t) # NOT optimal Scores[:, i] = _t[:,0]; Loadings[i, :] = p[0,:] # co - + if E_matrices: # complete error matrix # can calculate object residual variance (row-wise) or variable resiudal variance (column-wise) # total residual variance can also be calculated - + Error_matrices[i] = E.copy() - + else: # total object residual variance for E[i] e_tot = 0 - + for k in range(rows): e_ = zeros((cols), float) for k_col in range(cols): @@ -270,103 +270,103 @@ def nipals_mat(X, PCs, threshold, E_matrices): tot_obj_residual_var = (e_tot / e_tot0) explained_var[i] = 1 - tot_obj_residual_var - tot_explained_var tot_explained_var += explained_var[i] - - - + + + if E_matrices: return Scores, Loadings, Error_matrices - return Scores, Loadings, explained_var + return Scores, Loadings, explained_var def nipals_arr(X, PCs, threshold, E_matrices): """ - + PCA by NIPALS using numpy array - - - @param X: 2-dimensional matrix of number data. + + + @param X: 2-dimensional matrix of number data. @type X: numpy array - + @param PCs: Number of Principal Components. @type PCs: int - - @param threshold: Convergence check value. For checking on convergence to zero (e.g. 0.000001). + + @param threshold: Convergence check value. For checking on convergence to zero (e.g. 0.000001). @type threshold: float - + @param E_matrices: If E-matrices should be retrieved or not. E-matrices (for each PC) or explained_var (explained variance for each PC). @type E_matrices: bool - + @return: (Scores, Loadings, E) """ - + (rows, cols) = shape(X) maxPCs = min(rows, cols) # max number of PCs is min(objects, variables) if maxPCs < PCs: PCs = maxPCs # change to maxPCs if PCs > maxPCs Scores = zeros((rows, PCs), float) # all Scores (T) Loadings = zeros((PCs, cols), float) # all Loadings (P) - + E = X.copy() #E[0] (should already be mean centered) - + if E_matrices: Error_matrices = zeros((PCs, rows, cols), float) # all Error matrices (E) else: explained_var = zeros((PCs)) tot_explained_var = 0 - + # total object residual variance for PC[0] (calculating from E[0]) e_tot0 = 0 # for E[0] the total object residual variance is 100% for k in range(rows): e_k = E[k, :]**2 - e_tot0 += sum(e_k) - - + e_tot0 += sum(e_k) + + t = get_column(E) # extract a column p = zeros((cols), float) - + # do iterations (0, PCs) for i in range(PCs): convergence = False ready_for_compare = False E_t = transpose(E) - + while not convergence: _temp = vec_inner(t) p = mat_prod(E_t, t) / _temp # ..................................... step 1 - + _temp = vec_inner(p)**(-0.5) p = p * _temp # .................................................... step 2 - + _temp = vec_inner(p) t = mat_prod(E, p) / _temp # ....................................... step 3 - - + + eigenval_new = vec_inner(t) if not ready_for_compare: ready_for_compare = True else: # ready for convergence check if (eigenval_new - eigenval_old) < threshold*eigenval_new: # ... step 4 - convergence = True + convergence = True eigenval_old = eigenval_new; remove_tp_prod(E, t, p) # .............................................. step 5 - + # add Scores and Loadings for PC[i] to the collection of all PCs Scores[:, i] = t; Loadings[i, :] = p - - + + if E_matrices: # complete error matrix # can calculate object residual variance (row-wise) or variable resiudal variance (column-wise) # total residual variance can also be calculated - + Error_matrices[i] = E.copy() - + else: # total object residual variance for E[i] e_tot = 0 @@ -380,7 +380,7 @@ def nipals_arr(X, PCs, threshold, E_matrices): if E_matrices: return Scores, Loadings, Error_matrices else: - return Scores, Loadings, explained_var + return Scores, Loadings, explained_var #} @@ -388,178 +388,178 @@ def nipals_arr(X, PCs, threshold, E_matrices): #{ Principal Component Analysis (using NIPALS) def PCA_nipals(X, standardize=True, PCs=10, threshold=0.0001, E_matrices=False): """ - + PCA by NIPALS and get Scores, Loadings, E - - @param X: 2-dimensional matrix of number data. + + @param X: 2-dimensional matrix of number data. @type X: numpy array - + @param standardize: Wheter X should be standardized or not. @type standardize: bool - + @param PCs: Number of Principal Components. @type PCs: int - - @param threshold: Convergence check value. For checking on convergence to zero (e.g. 0.000001). + + @param threshold: Convergence check value. For checking on convergence to zero (e.g. 0.000001). @type threshold: float - + @param E_matrices: If E-matrices should be retrieved or not. E-matrices (for each PC) or explained_var (explained variance for each PC). @type E_matrices: bool - + @return: nipals_mat(X, PCs, threshold, E_matrices) """ X = mean_center(X) - + if standardize: X = standardization(X) - - return nipals_mat(X, PCs, threshold, E_matrices) - + + return nipals_mat(X, PCs, threshold, E_matrices) + def PCA_nipals2(X, standardize=True, PCs=10, threshold=0.0001, E_matrices=False): """ - + PCA by NIPALS and get Scores, Loadings, E - - @param X: 2-dimensional matrix of number data. + + @param X: 2-dimensional matrix of number data. @type X: numpy array - + @param standardize: Wheter X should be standardized or not. @type standardize: bool - + @param PCs: Number of Principal Components. @type PCs: int - - @param threshold: Convergence check value. For checking on convergence to zero (e.g. 0.000001). + + @param threshold: Convergence check value. For checking on convergence to zero (e.g. 0.000001). @type threshold: float - + @param E_matrices: If E-matrices should be retrieved or not. E-matrices (for each PC) or explained_var (explained variance for each PC). @type E_matrices: bool - + @return: nipals_arr(X, PCs, threshold, E_matrices) """ X = mean_center(X) - + if standardize: X = standardization(X) - - return nipals_arr(X, PCs, threshold, E_matrices) + + return nipals_arr(X, PCs, threshold, E_matrices) def PCA_nipals_c(X, standardize=True, PCs=10, threshold=0.0001, E_matrices=False): """ - + PCA by NIPALS and get Scores, Loadings, E - - @param X: 2-dimensional matrix of number data. + + @param X: 2-dimensional matrix of number data. @type X: numpy array - + @param standardize: Wheter X should be standardized or not. @type standardize: bool - + @param PCs: Number of Principal Components. @type PCs: int - - @param threshold: Convergence check value. For checking on convergence to zero (e.g. 0.000001). + + @param threshold: Convergence check value. For checking on convergence to zero (e.g. 0.000001). @type threshold: float - + @param E_matrices: If E-matrices should be retrieved or not. E-matrices (for each PC) or explained_var (explained variance for each PC). @type E_matrices: bool - + @return: nipals_c(X, PCs, threshold, E_matrices) """ """ USING C PYTHON EXTENSION """ X = mean_center(X) - + if standardize: X = standardization(X) - - return nipals_c(X, PCs, threshold, E_matrices) - -#} - -#{ Principal Component Analysis (using SVD) + + return nipals_c(X, PCs, threshold, E_matrices) + +#} + +#{ Principal Component Analysis (using SVD) def PCA_svd(X, standardize=True): - """ + """ PCA by SVD and get Scores, Loadings, E Remake of method made by Oliver Tomic Ph.D. - - @param X: 2-dimensional matrix of number data. + + @param X: 2-dimensional matrix of number data. @type X: numpy array - + @param standardize: Wheter X should be standardized or not. @type standardize: bool - + @return: (Scores, Loadings, explained_var) """ X = mean_center(X) #print X - + if standardize: - X = standardization(X) - + X = standardization(X) + (rows, cols) = shape(X) - + # Singular Value Decomposition [U, S, V] = svd(X) - + # adjust if matrix shape does not match: if shape(S)[0] < shape(U)[0]: U = U[:, 0:shape(S)[0]] - + Scores = U * S # all Scores (T) Loadings = V # all Loadings (P) - + variances = S**2 / cols variances_sum = sum(variances) explained_var = variances / variances_sum - + return Scores, Loadings, explained_var #} -#{ Correlation Loadings +#{ Correlation Loadings def CorrelationLoadings(X, Scores): """ Get correlation loadings matrix based on Scores (T of PCA) and X (original variables, not mean centered). Remake of method made by Oliver Tomic Ph.D. - - @param X: 2-dimensional matrix of number data. + + @param X: 2-dimensional matrix of number data. @type X: numpy array - + @param Scores: Scores of PCA (T). @type Scores: numpy array @return: Returns the correlation loadings matrix """ - + # Creates empty matrix for correlation loadings PCs = shape(Scores)[1] # number of PCs rows = shape(X)[1] # number of objects (rows) in X CorrLoadings = zeros((PCs, rows), float) - + # Calculates correlation loadings with formula: # correlation = cov(x,y)/(std(x)*std(y)) - + # For each PC in score matrix for i in range(PCs): Scores_PC_i = Scores[:, i] # Scores for PC[i] - - # For each variable/attribute in X + + # For each variable/attribute in X for row in range(rows): orig_vars = X[:, row] # column of variables in X corrs = corrcoef(Scores_PC_i, orig_vars) CorrLoadings[i, row] = corrs[0,1] - - return CorrLoadings + + return CorrLoadings #} \ No newline at end of file diff --git a/statistics/testlinearregression.py b/statistics/testlinearregression.py index 67ea11bb7..511676e49 100644 --- a/statistics/testlinearregression.py +++ b/statistics/testlinearregression.py @@ -8,7 +8,7 @@ import unittest from math import pi, sqrt import numpy as np -from linearregression import LinearModel, PolynomialModel, HarmonicModel, LinearFit +from .linearregression import LinearModel, PolynomialModel, HarmonicModel, LinearFit @@ -17,11 +17,11 @@ class LinearModelTestCase(unittest.TestCase): """ Test the LinearModel class and its methods. """ - + def setUp(self): - + # Generate a simple noiseless dataset - + self.nObservations = 10 self.nParameters = 2 time = np.arange(self.nObservations, dtype=np.double) @@ -40,9 +40,9 @@ def tearDown(self): def testInitLinearModel(self): - + self.assertRaises(ValueError, lambda : LinearModel(self.regressorList, ["only 1 name"])) - + linearModel = LinearModel(self.regressorList, self.regressorNames) self.assertTrue(linearModel.nParameters() == self.nParameters) self.assertTrue(linearModel.regressorNames() == self.regressorNames) @@ -50,36 +50,36 @@ def testInitLinearModel(self): def testContainsRegressorName(self): - + linearModel = LinearModel(self.regressorList, self.regressorNames) self.assertTrue(linearModel.containsRegressorName("t^2")) self.assertFalse(linearModel.containsRegressorName("x")) - - + + def testSomeRegressorNameContains(self): - + linearModel = LinearModel(self.regressorList, self.regressorNames) self.assertTrue(linearModel.someRegressorNameContains("^2")) self.assertFalse(linearModel.someRegressorNameContains("**2")) def testWithIntercept(self): - + # Test a model with an intercept - + linearModel = LinearModel(self.regressorList, self.regressorNames) self.assertTrue(linearModel.withIntercept()) - + # Test a model without intercept - + linearModel = LinearModel(self.RegressorList2, self.RegressorNames2) self.assertFalse(linearModel.withIntercept()) def testModelMatrix(self): - + # With a list of regressor arrays as input - + linearModel = LinearModel(self.regressorList, self.regressorNames) self.assertTrue(isinstance(linearModel.designMatrix(), np.ndarray)) self.assertTrue(linearModel.designMatrix().dtype == np.double) @@ -92,46 +92,46 @@ def testModelMatrix(self): self.assertTrue(isinstance(linearModel.designMatrix(), np.ndarray)) self.assertTrue(linearModel.designMatrix().dtype == np.double) self.assertTrue(linearModel.designMatrix().shape == (self.nObservations, self.nParameters)) - self.assertTrue(np.alltrue(linearModel.designMatrix() == self.designMatrix)) + self.assertTrue(np.alltrue(linearModel.designMatrix() == self.designMatrix)) def testPseudoInverse(self): - - expectedPseudoInverse = np.array([[ 0.21264822, 0.20869565, 0.19683794, 0.1770751, 0.14940711, + + expectedPseudoInverse = np.array([[ 0.21264822, 0.20869565, 0.19683794, 0.1770751, 0.14940711, 0.11383399, 0.07035573, 0.01897233, -0.04031621, -0.10750988], [-0.00395257, -0.00381388, -0.00339782, -0.00270439, -0.00173358, -0.0004854, 0.00104015, 0.00284308, 0.00492338, 0.00728105]]) linearModel = LinearModel(self.regressorList, self.regressorNames) - self.assertTrue(np.allclose(linearModel.pseudoInverse(), expectedPseudoInverse, + self.assertTrue(np.allclose(linearModel.pseudoInverse(), expectedPseudoInverse, rtol=1.0e-6, atol=1.e-08)) def testStandardCovarianceMatrix(self): - + expectedStandardCovarianceMatrix = np.array([[ 2.12648221e-01, -3.95256917e-03], [-3.95256917e-03, 1.38686638e-04]]) - linearModel = LinearModel(self.regressorList, self.regressorNames) - self.assertTrue(np.allclose(linearModel.standardCovarianceMatrix(), + linearModel = LinearModel(self.regressorList, self.regressorNames) + self.assertTrue(np.allclose(linearModel.standardCovarianceMatrix(), expectedStandardCovarianceMatrix, rtol=1.0e-6, atol=1.e-08)) def testConditionNumber(self): - + expectedConditionNumber = 57.120830024 linearModel = LinearModel(self.regressorList, self.regressorNames) self.assertAlmostEqual(linearModel.conditionNumber(), expectedConditionNumber, places = 5) def testSingularValues(self): - + expectedSingularValues = np.array([123.84788663, 2.16817379]) linearModel = LinearModel(self.regressorList, self.regressorNames) - self.assertTrue(np.allclose(linearModel.singularValues(), expectedSingularValues, + self.assertTrue(np.allclose(linearModel.singularValues(), expectedSingularValues, rtol=1.0e-6, atol=1.e-08)) def testHatMatrix(self): - + expectedHatMatrix = np.array([[ 0.21264822, 0.20869565, 0.19683794, 0.1770751 , 0.14940711, 0.11383399, 0.07035573, 0.01897233, -0.04031621, -0.10750988], [ 0.20869565, 0.20488177, 0.19344012, 0.17437071, 0.14767353, @@ -158,21 +158,21 @@ def testHatMatrix(self): def testTraceHatMatrix(self): - + linearModel = LinearModel(self.regressorList, self.regressorNames) expectedTrace = 2.0 self.assertAlmostEqual(linearModel.traceHatMatrix(), expectedTrace, places=7) def testDegreesOfFreedom(self): - + expectedDegreesOfFreedom = 8 linearModel = LinearModel(self.regressorList, self.regressorNames) self.assertEqual(linearModel.degreesOfFreedom(), expectedDegreesOfFreedom) def testAddLinearModel(self): - + linearModel = LinearModel(self.regressorList, self.regressorNames) linearModel2 = LinearModel(self.RegressorList2, self.RegressorNames2) linearModel = linearModel + linearModel2 @@ -180,14 +180,14 @@ def testAddLinearModel(self): self.assertTrue(np.alltrue(linearModel.designMatrix() == self.newModelMatrix)) self.assertTrue(linearModel.regressorNames() == self.regressorNames + self.RegressorNames2) self.assertTrue(linearModel._covMatrixObserv == None) - + self.assertRaises(TypeError, lambda lm : lm + 1.0, linearModel) self.assertRaises(ValueError, lambda x,y : LinearModel(x,y) + LinearModel(x[:-1,:],y), self.designMatrix, self.regressorNames) - - + + def testCopyLinearModel(self): - + linearModel = LinearModel(self.regressorList, self.regressorNames) linearModel2 = linearModel.copy() self.assertTrue(linearModel2.nParameters() == self.nParameters) @@ -196,9 +196,9 @@ def testCopyLinearModel(self): self.assertTrue(id(linearModel2.designMatrix()) != id(linearModel.designMatrix())) self.assertTrue(id(linearModel2.regressorNames()) != id(linearModel.regressorNames())) - + def testFitData(self): - + linearModel = LinearModel(self.regressorList, self.regressorNames) self.assertTrue(isinstance(linearModel.fitData(self.observations), LinearFit)) @@ -212,71 +212,71 @@ class WeightedLinearModelTestCase(unittest.TestCase): Test the LinearModel class with a specified covariance matrix for the observations. Test only those method affected by weights. """ - + def setUp(self): - + # Generate simple noiseless dataset - + self.nObservations = 10 self.nParameters = 2 self.time = np.arange(self.nObservations, dtype=np.double) self.regressorList = [np.ones_like(self.time), self.time] self.unweightedDesignMatrix = np.column_stack(self.regressorList) self.regressorNames = ["1", "t"] - + # First covariance matrix: different weights, no correlations - + self.covMatrixObserv1 = np.diag(0.1 + 0.1 * np.arange(self.nObservations)) - + # Second covariance matrix: different weights and correlations # Correlation coefficient: 0.6 - + self.covMatrixObserv2 = np.diag(0.1 + 0.1 * np.arange(self.nObservations)) for i in range(self.nObservations): for j in range(self.nObservations): if i>=j: continue self.covMatrixObserv2[i,j] = 0.6 * sqrt(self.covMatrixObserv2[i,i] * self.covMatrixObserv2[j,j]) self.covMatrixObserv2[j,i] = self.covMatrixObserv2[i,j] - - + + def tearDown(self): pass def testInitWeightedLinearModel(self): - + # The covariance matrix of the observations should be 1) a numpy array # 2) with the proper dimensions, and 3) with the correct size. - + self.assertRaises(TypeError, lambda x,y,z : LinearModel(x,y,z), \ self.regressorList, self.regressorNames, [1,2,3]) self.assertRaises(TypeError, lambda x,y,z : LinearModel(x,y,z), \ - self.regressorList, self.regressorNames, np.arange(10)) + self.regressorList, self.regressorNames, np.arange(10)) self.assertRaises(TypeError, lambda x,y,z : LinearModel(x,y,z), \ self.regressorList, self.regressorNames, \ np.arange(self.nObservations*5).reshape(self.nObservations,5)) - + linearModel = LinearModel(self.regressorList, self.regressorNames, self.covMatrixObserv1, regressorsAreWeighted=True) self.assertTrue(isinstance(linearModel._covMatrixObserv, np.ndarray)) self.assertTrue(linearModel._covMatrixObserv.dtype == np.double) self.assertTrue(linearModel._covMatrixObserv.shape == (self.nObservations, self.nObservations)) self.assertTrue(np.alltrue(linearModel._covMatrixObserv == self.covMatrixObserv1)) - + linearModel = LinearModel(self.regressorList, self.regressorNames, self.covMatrixObserv1, regressorsAreWeighted=False) self.assertTrue(isinstance(linearModel._covMatrixObserv, np.ndarray)) self.assertTrue(linearModel._covMatrixObserv.dtype == np.double) self.assertTrue(linearModel._covMatrixObserv.shape == (self.nObservations, self.nObservations)) self.assertTrue(np.alltrue(linearModel._covMatrixObserv == self.covMatrixObserv1)) - - + + def testWeightedModelMatrix(self): - + linearModel = LinearModel(self.regressorList, self.regressorNames, self.covMatrixObserv2, regressorsAreWeighted=True) self.assertTrue(np.alltrue(linearModel.designMatrix() == self.unweightedDesignMatrix)) - + linearModel = LinearModel(self.regressorList, self.regressorNames, self.covMatrixObserv2, regressorsAreWeighted=False) self.assertFalse(np.alltrue(linearModel.designMatrix() == self.unweightedDesignMatrix)) - + expectedWeightedDesignMatrix = np.array([[ 3.16227766e+00, 4.73643073e-15], [ 4.23376727e-01, 2.79508497e+00], [-2.67843095e-01, 3.79299210e+00], @@ -290,9 +290,9 @@ def testWeightedModelMatrix(self): self.assertTrue(np.allclose(linearModel.designMatrix(), expectedWeightedDesignMatrix, rtol=1.0e-6, atol=1.e-08)) - + def testWeightedHatMatrix(self): - + expectedWeightedHatMatrix = np.array([[ 0.93746979, 0.22625449, 0.05730702, -0.00315076, -0.02629974, -0.03331944, -0.03243355, -0.02737749, -0.02003319, -0.01142021], [ 0.22625449, 0.08788299, 0.05898899, 0.05159613, 0.05137251, @@ -313,44 +313,44 @@ def testWeightedHatMatrix(self): 0.13645396, 0.14486348, 0.15235719, 0.15920448, 0.16556802], [-0.01142021, 0.07293014, 0.10200995, 0.11911848, 0.13159879, 0.14174555, 0.15051078, 0.15836442, 0.16556802, 0.17228071]]) - - linearModel = LinearModel(self.regressorList, self.regressorNames, self.covMatrixObserv2) + + linearModel = LinearModel(self.regressorList, self.regressorNames, self.covMatrixObserv2) self.assertTrue(np.allclose(linearModel.hatMatrix(), expectedWeightedHatMatrix, rtol=1.0e-6, atol=1.e-08)) def testWeightedDegreesOfFreedom(self): - + expectedDegreesOfFreedom = 8 linearModel = LinearModel(self.regressorList, self.regressorNames, self.covMatrixObserv2) self.assertAlmostEqual(linearModel.degreesOfFreedom(), expectedDegreesOfFreedom, places=6) def testAddWeigthedLinearModel(self): - + regressorList2 = [self.time**2] regressorNames2 = ["t^2"] self.assertRaises(ValueError, lambda v,w,x,y,z : LinearModel(v,w) + LinearModel(x,y,z), - self.regressorList, self.regressorNames, + self.regressorList, self.regressorNames, regressorList2, regressorNames2, self.covMatrixObserv1) - + self.assertRaises(ValueError, lambda u,v,w,x,y,z : LinearModel(u,v,w) + LinearModel(x,y,z), self.regressorList, self.regressorNames, self.covMatrixObserv1, regressorList2, regressorNames2, self.covMatrixObserv2) - + linearModel1 = LinearModel(self.regressorList, self.regressorNames, self.covMatrixObserv2) linearModel2 = LinearModel(regressorList2, regressorNames2, self.covMatrixObserv2) linearModel = linearModel1 + linearModel2 self.assertTrue(np.alltrue(linearModel._covMatrixObserv == self.covMatrixObserv2)) - - + + def testCopyWeightedLinearModel(self): - + linearModel = LinearModel(self.regressorList, self.regressorNames, self.covMatrixObserv2, regressorsAreWeighted=False) linearModel2 = linearModel.copy() self.assertTrue(np.alltrue(linearModel2._covMatrixObserv == self.covMatrixObserv2)) self.assertTrue(id(linearModel2._covMatrixObserv) != id(linearModel._covMatrixObserv)) - + @@ -360,41 +360,41 @@ class PolynomialModelTestCase(unittest.TestCase): """ Test the PolynomialModel class and its methods. """ - - + + def setUp(self): - + # Generate a simple noiseless dataset - + self.nObservations = 10 self.time = np.arange(self.nObservations, dtype=np.double) - + def tearDown(self): pass def testInitPolynomialModel(self): - - # Define the input, and derive what the output should be - + + # Define the input, and derive what the output should be + orderList = [0,2] nParameters = len(orderList) covariateName = "t" - regressorNames = ["1", "t^2"] + regressorNames = ["1", "t^2"] designMatrix = np.empty((self.nObservations, nParameters)) for n in range(len(orderList)): designMatrix[:,n] = self.time**orderList[n] - - # Assert if the output is what it should be - + + # Assert if the output is what it should be + polyModel = PolynomialModel(self.time, covariateName, orderList) self.assertTrue(polyModel.nParameters() == nParameters) self.assertTrue(polyModel.regressorNames() == regressorNames) self.assertTrue(polyModel.nObservations() == self.nObservations) self.assertTrue(np.alltrue(polyModel.designMatrix() == designMatrix)) - + @@ -403,33 +403,33 @@ class WeightedPolynomialModelTestCase(unittest.TestCase): """ Test if a covariance matrix can be specified for a polynomial model """ - - + + def setUp(self): - + # Generate a simple noiseless dataset - + self.nObservations = 10 self.time = np.arange(self.nObservations, dtype=np.double) - + def tearDown(self): pass def testInitPolynomialModel(self): - - # Defining a polynomial model with no covariance matrix should be the + + # Defining a polynomial model with no covariance matrix should be the # same as one with a diagonal covariance matrix with ones on the diagonal. - + orderList = [0,2] nParameters = len(orderList) covariateName = "t" - regressorNames = ["1", "t^2"] + regressorNames = ["1", "t^2"] polyModel1 = PolynomialModel(self.time, covariateName, orderList) polyModel2 = PolynomialModel(self.time, covariateName, orderList, np.diag(np.ones(self.nObservations))) - + self.assertTrue(np.alltrue(polyModel1.designMatrix() == polyModel2.designMatrix())) @@ -441,24 +441,24 @@ class HarmonicModelTestCase(unittest.TestCase): """ Test the HarmonicModel class and its methods. """ - - + + def setUp(self): - + # Generate a simple noiseless dataset - + self.nObservations = 10 self.time = np.arange(self.nObservations, dtype=np.double) - + def tearDown(self): pass def testInitHarmonicModel(self): - - # Define the input, and derive what the output should be - + + # Define the input, and derive what the output should be + freqList = [2.1, 3.4] freqNameList = ["f_1", "f_2"] maxNharmonics = 2 @@ -467,7 +467,7 @@ def testInitHarmonicModel(self): regressorNames = ["sin(2*pi*1*f_1*t)", "cos(2*pi*1*f_1*t)", \ "sin(2*pi*2*f_1*t)", "cos(2*pi*2*f_1*t)", \ "sin(2*pi*1*f_2*t)", "cos(2*pi*1*f_2*t)", \ - "sin(2*pi*2*f_2*t)", "cos(2*pi*2*f_2*t)"] + "sin(2*pi*2*f_2*t)", "cos(2*pi*2*f_2*t)"] designMatrix = np.empty((self.nObservations, nParameters)) runningIndex = 0 for n in range(len(freqList)): @@ -475,9 +475,9 @@ def testInitHarmonicModel(self): designMatrix[:,runningIndex] = np.sin(2*pi*k*freqList[n]*self.time) designMatrix[:,runningIndex+1] = np.cos(2*pi*k*freqList[n]*self.time) runningIndex += 2 - - # Assert if the output is what it should be - + + # Assert if the output is what it should be + harmonicModel = HarmonicModel(self.time, covariateName, freqList, freqNameList,maxNharmonics) self.assertTrue(harmonicModel.nParameters() == nParameters) self.assertTrue(harmonicModel.regressorNames() == regressorNames) @@ -491,27 +491,27 @@ def testInitHarmonicModel(self): class WeightedHarmonicModelTestCase(unittest.TestCase): """ - Test if a covariance matrix can be specified for a Harmonic model + Test if a covariance matrix can be specified for a Harmonic model """ - - + + def setUp(self): - + # Generate a simple noiseless dataset - + self.nObservations = 10 self.time = np.arange(self.nObservations, dtype=np.double) - + def tearDown(self): pass def testInitHarmonicModel(self): - - # Defining a harmonic model with no covariance matrix should be the + + # Defining a harmonic model with no covariance matrix should be the # same as one with a diagonal covariance matrix with ones on the diagonal. - + freqList = [2.1] freqNameList = ["f_1"] maxNharmonics = 1 @@ -521,7 +521,7 @@ def testInitHarmonicModel(self): covMatrixObserv = np.diag(np.ones(self.nObservations)) harmonicModel1 = HarmonicModel(self.time, covariateName, freqList, freqNameList, maxNharmonics) harmonicModel2 = HarmonicModel(self.time, covariateName, freqList, freqNameList, maxNharmonics, covMatrixObserv) - + self.assertTrue(np.alltrue(harmonicModel1.designMatrix() == harmonicModel2.designMatrix())) @@ -535,47 +535,47 @@ class LinearSubmodelGeneratorTestCase(unittest.TestCase): """ def setUp(self): - + self.nObservations = 10 self.x = np.arange(self.nObservations) x = self.x # local shorter alias ones= np.ones_like(x) self.linearModel1 = LinearModel([ones,x,x**2], ["1", "x", "x^2"]) self.linearModel2 = LinearModel([ones,x,np.sin(x),np.cos(x)], ["1","x","sin(x)","cos(x)"]) - + def tearDown(self): pass def testSubmodel(self): - + generator = self.linearModel1.submodels(Nmin=3, nested=True, ranks=[1,0,3]) self.assertTrue(generator.__class__.__name__ == "generator") - submodel = generator.next() + submodel = next(generator) self.assertTrue(isinstance(submodel, LinearModel)) designMatrix = np.column_stack([self.x,np.ones(len(self.x)),self.x**2]) - self.assertTrue(np.alltrue(submodel.designMatrix() == designMatrix)) + self.assertTrue(np.alltrue(submodel.designMatrix() == designMatrix)) self.assertTrue(submodel.regressorNames() == ["x","1","x^2"]) - + def testSubmodelGenerator(self): - + names = [] for model in self.linearModel1.submodels(Nmin=1, nested=False, ranks=None): names += [model.regressorNames()] self.assertTrue(names == [["1"], ["x"], ["x^2"], ["1","x"], ["1","x^2"], ["x","x^2"], ["1","x","x^2"]]) - + names = [] for model in self.linearModel1.submodels(Nmin=1, Nmax=2, nested=False, ranks=None): names += [model.regressorNames()] self.assertTrue(names == [["1"], ["x"], ["x^2"], ["1","x"], ["1","x^2"], ["x","x^2"]]) - + names = [] for model in self.linearModel1.submodels(Nmin=2, nested=False, ranks=None): names += [model.regressorNames()] self.assertTrue(names == [["1","x"], ["1","x^2"], ["x","x^2"], ["1","x","x^2"]]) - + names = [] for model in self.linearModel1.submodels(Nmin=3, nested=True, ranks=None): names += [model.regressorNames()] @@ -590,23 +590,23 @@ def testSubmodelGenerator(self): for model in self.linearModel1.submodels(Nmin=2, nested=False, ranks=[1,0,3]): names += [model.regressorNames()] self.assertTrue(names == [["x","1"], ["x","x^2"], ["1","x^2"], ["x","1","x^2"]]) - + names = [] for model in self.linearModel1.submodels(Nmin=2, nested=True, ranks=[1,0,3]): names += [model.regressorNames()] self.assertTrue(names == [["x","1"], ["x","1","x^2"]]) - + names = [] for model in self.linearModel2.submodels(Nmin=3, nested=True, ranks=[0,1,2,2]): names += [model.regressorNames()] self.assertTrue(names == [["1","x","sin(x)","cos(x)"]]) - + names = [] for model in self.linearModel2.submodels(Nmin=2, nested=True, ranks=[0,1,2,2]): names += [model.regressorNames()] self.assertTrue(names == [["1","x"], ["1","x","sin(x)","cos(x)"]]) - + names = [] for model in self.linearModel2.submodels(Nmin=2, nested=False, ranks=[0,1,2,2]): names += [model.regressorNames()] @@ -616,7 +616,7 @@ def testSubmodelGenerator(self): for model in self.linearModel2.submodels(Nmin=1, Nmax=2, nested=False, ranks=[0,1,2,2]): names += [model.regressorNames()] self.assertTrue(names == [["1"], ["x"], ["sin(x)","cos(x)"], ["1","x"], ["1","sin(x)","cos(x)"], ["x","sin(x)","cos(x)"]]) - + @@ -625,14 +625,14 @@ class LinearFitTestCase(unittest.TestCase): """ Test the LinearFit class and its methods - + """ def setUp(self): - - # Generate a simple dataset. + + # Generate a simple dataset. # Noise-values are drawn from a N(0,1), but are truncated. - + self.nObservations = 10 self.nParameters = 2 self.time = np.arange(self.nObservations, dtype=np.double) @@ -642,7 +642,7 @@ def setUp(self): regressorNames = ["1", "t^2"] self.linearModel = LinearModel(regressorList, regressorNames) self.linearFit = self.linearModel.fitData(self.observations) - + def tearDown(self): pass @@ -654,30 +654,30 @@ def testLinearFit(self): def testObservations(self): - + # Since the linear model did not have a covariance matrix specified, the # decorrelated observations, should be simply the original observations - + self.assertTrue(isinstance(self.linearFit.observations(weighted=True), np.ndarray)) self.assertEqual(self.linearFit.observations(weighted=True).shape, (self.nObservations,)) self.assertTrue(np.alltrue(self.observations == self.linearFit.observations(weighted=True))) self.assertTrue(np.alltrue(self.observations == self.linearFit.observations(weighted=False))) - - + + def testRegressionCoefficients(self): - + expectedCoeff = np.array([2.47811858, 2.01543444]) self.assertTrue(isinstance(self.linearFit.regressionCoefficients(), np.ndarray)) self.assertEqual(self.linearFit.regressionCoefficients().shape, (self.nParameters,)) self.assertTrue(np.allclose(self.linearFit.regressionCoefficients(), expectedCoeff, rtol=1.0e-6, atol=1.e-08)) - + def testResiduals(self): - - expectedResiduals = np.array([-0.19811858, -0.51355301, 0.98014368, 0.6229715, - 0.10493045, -1.64397947, 0.52624173, 0.09559406, - -0.27592247, 0.30169212]) + + expectedResiduals = np.array([-0.19811858, -0.51355301, 0.98014368, 0.6229715, + 0.10493045, -1.64397947, 0.52624173, 0.09559406, + -0.27592247, 0.30169212]) self.assertTrue(isinstance(self.linearFit.residuals(weighted=False), np.ndarray)) self.assertEqual(self.linearFit.residuals(weighted=False).shape, (self.nObservations,)) self.assertTrue(np.allclose(self.linearFit.residuals(weighted=False), expectedResiduals, rtol=1.0e-6, atol=1.e-08)) @@ -686,24 +686,24 @@ def testResiduals(self): def testPredictions(self): expectedPredictions = np.array([2.47811858, 4.49355301, 10.53985632, 20.6170285, - 34.72506955, 52.86397947, 75.03375827, + 34.72506955, 52.86397947, 75.03375827, 101.23440594, 131.46592247, 165.72830788]) self.assertTrue(isinstance(self.linearFit.predictions(weighted=False), np.ndarray)) self.assertEqual(self.linearFit.predictions(weighted=False).shape, (self.nObservations,)) self.assertTrue(np.allclose(self.linearFit.predictions(weighted=False), expectedPredictions, rtol=1.0e-6, atol=1.e-08)) - def testCovarianceMatrix(self): - + def testCovarianceMatrix(self): + expectedCovMatrix = np.array([[1.28084978e-01, -2.38076167e-03], - [-2.38076167e-03, 8.35354974e-05]]) + [-2.38076167e-03, 8.35354974e-05]]) self.assertTrue(isinstance(self.linearFit.covarianceMatrix(), np.ndarray)) self.assertEqual(self.linearFit.covarianceMatrix().shape, (self.nParameters,self.nParameters)) self.assertTrue(np.allclose(self.linearFit.covarianceMatrix(), expectedCovMatrix, rtol=1.0e-6, atol=1.e-08)) def testErrorBars(self): - + expectedErrors = np.array([0.35788962, 0.00913978]) self.assertTrue(isinstance(self.linearFit.errorBars(), np.ndarray)) self.assertEqual(self.linearFit.errorBars().shape, (self.nParameters,)) @@ -711,7 +711,7 @@ def testErrorBars(self): def testConfidenceIntervals(self): - + alpha = 0.05 expectedLower = np.array([1.65282364, 1.99435808]) expectedUpper = np.array([3.30341351, 2.0365108 ]) @@ -726,38 +726,38 @@ def testConfidenceIntervals(self): def testT_values(self): - + expectedTvalues = np.array([6.92425385, 220.51235807]) self.assertTrue(isinstance(self.linearFit.t_values(), np.ndarray)) self.assertEqual(self.linearFit.t_values().shape, (self.nParameters,)) self.assertTrue(np.allclose(self.linearFit.t_values(), expectedTvalues, rtol=1.0e-6, atol=1.e-08)) - - + + def testSumOfSquaredResiduals(self): - - expectedSumSqResiduals = 4.81866162957 + + expectedSumSqResiduals = 4.81866162957 self.assertAlmostEqual(self.linearFit.sumSqResiduals(weighted=False), expectedSumSqResiduals, places=7) def testResidualVariance(self): - + expectedResidualVariance = 0.60233270369599989 self.assertAlmostEqual(self.linearFit.residualVariance(weighted=False), expectedResidualVariance, places=7) - - + + def testCorrelationMatrix(self): - + expectedCorrelationMatrix = np.array([[1., -0.72783225], [-0.72783225, 1.]]) self.assertTrue(np.allclose(self.linearFit.correlationMatrix(), expectedCorrelationMatrix, rtol=1.0e-6, atol=1.e-08)) - - + + def testCoefficientOfDetermination(self): - + # Test using a model with an intercept expectedCoefficientOfDetermination = 0.99983550516908659 self.assertAlmostEqual(self.linearFit.coefficientOfDetermination(weighted=False), expectedCoefficientOfDetermination, places=7) - + # Test using a model without an intercept linearModel = LinearModel([self.time**2], ["t^2"]) @@ -765,21 +765,21 @@ def testCoefficientOfDetermination(self): expectedCoefficientOfDetermination = 0.99948312767720937 self.assertAlmostEqual(linearFit.coefficientOfDetermination(weighted=False), expectedCoefficientOfDetermination, places=7) - + def testBICvalue(self): - + expectedBICvalue = -0.39313345804942657 self.assertAlmostEqual(self.linearFit.BICvalue(), expectedBICvalue, places=7) - + def testAICvalue(self): - + expectedAICvalue = 2.6991112629684357 self.assertAlmostEqual(self.linearFit.AICvalue(), expectedAICvalue, places=7) - - + + def testThypothesisTest(self): - + regressorList = [np.ones_like(self.time), self.time**2, self.time**3] regressorNames = ["1", "t^2", "t^3"] linearModel = LinearModel(regressorList, regressorNames) @@ -788,22 +788,22 @@ def testThypothesisTest(self): alpha = 0.05 # significance level expectedOutput = np.array([True, True, False]) nullRejected = linearFit.regressorTtest(alpha) - + self.assertTrue(isinstance(nullRejected, np.ndarray)) self.assertTrue(nullRejected.dtype == np.bool) self.assertEqual(len(nullRejected), len(regressorList)) self.assertTrue(np.all(nullRejected == expectedOutput)) - + def testFstatistic(self): - + # Test on a model with more than one regressor - + expectedFstatistic = 48625.747064132738 self.assertAlmostEqual(self.linearFit.Fstatistic(weighted=False), expectedFstatistic, places=7) # Test on a model with only one regressor - + expectedFstatistic = 17403.423925909559 linearModel = LinearModel([np.square(self.time)], ["t^2"]) linearFit = linearModel.fitData(self.observations) @@ -811,16 +811,16 @@ def testFstatistic(self): def testFstatisticTest(self): - + # with tested model the same as the true model - + alpha = 0.05 expectedOutput = True # null hypothesis rejected self.assertTrue(self.linearFit.FstatisticTest(alpha) == expectedOutput) - - + + # with a fit on pure noise noise - + alpha = 0.05 expectedOutput = False # null hypothesis could not be rejected linearFit = self.linearModel.fitData(self.noise) @@ -829,18 +829,18 @@ def testFstatisticTest(self): def testEvaluate(self): - + expectedOutput = np.array([ 52.86397947, 42.8147219 , 33.8820485 , 26.06595927, 19.36645422, 13.78353335, 9.31719665, 5.96744412, 3.73427577, 2.6176916 , 2.6176916 , 3.73427577, 5.96744412, 9.31719665, 13.78353335, 19.36645422, 26.06595927, 33.8820485 , 42.8147219 , 52.86397947]) - + xnew = np.linspace(-5.0, +5.0, 20) newRegressorList = [np.ones_like(xnew), xnew**2] output = self.linearFit.evaluate(newRegressorList) self.assertTrue(np.allclose(output, expectedOutput, rtol=1.0e-6, atol=1.e-08)) - + @@ -850,13 +850,13 @@ class WeightedLinearFitTestCase(unittest.TestCase): """ Test the LinearFit class with a specified covariance matrix for the observations. Test only those method affected by weights. - + """ def setUp(self): - + # Generate simple noiseless dataset - + self.nObservations = 10 self.nParameters = 2 time = np.arange(self.nObservations, dtype=np.double) @@ -865,75 +865,75 @@ def setUp(self): self.regressorList = [np.ones_like(time), time] self.regressorNames = ["1", "t"] self.designMatrix = np.column_stack(self.regressorList) - + # First covariance matrix: different weights, no correlations - + covMatrixObserv1 = np.diag(0.1 + 0.1 * np.arange(self.nObservations)) - + # Second covariance matrix: different weights and correlations # Correlation coefficient: 0.6 - + covMatrixObserv2 = np.diag(0.1 + 0.1 * np.arange(self.nObservations)) for i in range(self.nObservations): for j in range(self.nObservations): if i>=j: continue covMatrixObserv2[i,j] = 0.6 * sqrt(covMatrixObserv2[i,i] * covMatrixObserv2[j,j]) covMatrixObserv2[j,i] = covMatrixObserv2[i,j] - + # Add noise according to the different covariance matrices - + noise1 = np.array([-0.26944792, -0.56542802, 0.62106263, -0.03113657, 0.98090236, \ - 0.02678669, 1.2237701 , -0.50112787, -0.47742454, 1.16351356]) + 0.02678669, 1.2237701 , -0.50112787, -0.47742454, 1.16351356]) noise2 = np.array([ 0.24484502, -0.22979797, 0.40639882, 0.12137103, 0.20694025, \ 0.68952746, -0.30300402, -0.11136982, 0.3549814 , 0.20528704]) self.observations1 += noise1 self.observations2 += noise2 - + # Instantiate the (weighted) linear models and their linear fits - + self.linearModel1 = LinearModel(self.regressorList, self.regressorNames, covMatrixObserv1) self.linearModel2 = LinearModel(self.regressorList, self.regressorNames, covMatrixObserv2) self.linearFit1 = self.linearModel1.fitData(self.observations1) self.linearFit2 = self.linearModel2.fitData(self.observations2) - - + + def tearDown(self): pass def testDecorrelatedObservations(self): - - expectedObservations = np.array([3.9365456, 0.03889641, 1.73885587, 0.65979961, 0.82899163, + + expectedObservations = np.array([3.9365456, 0.03889641, 1.73885587, 0.65979961, 0.82899163, 1.73402234, -0.1799988, 0.37289876, 1.25537791, 1.05209144]) - + self.assertTrue(isinstance(self.linearFit2.observations(weighted=True), np.ndarray)) self.assertEqual(self.linearFit2.observations(weighted=True).shape, (self.nObservations,)) self.assertTrue(np.allclose(expectedObservations, self.linearFit2.observations(weighted=True), rtol=1.0e-6, atol=1.e-08)) def testWeightedRegressionCoefficients(self): - + # A test with a diagonal covariance matrix - + expectedWeightedCoeff = np.array([0.76576446, 0.40030724]) self.assertTrue(np.allclose(self.linearFit1.regressionCoefficients(), expectedWeightedCoeff, rtol=1.0e-6, atol=1.e-08)) # A test with a non-diagonal covariance matrix - + expectedWeightedCoeff = np.array([1.1623421, 0.3040605]) self.assertTrue(np.allclose(self.linearFit2.regressionCoefficients(), expectedWeightedCoeff, rtol=1.0e-6, atol=1.e-08)) def testResiduals(self): - + # A test with a diagonal covariance matrix - + expectedResiduals = np.array([-0.03521238, -0.43149972, 0.65468369, -0.09782275, 0.81390894, -0.24051397, 0.85616220, -0.96904301, -1.04564692, 0.49498394]) self.assertTrue(np.allclose(self.linearFit1.residuals(weighted=False), expectedResiduals, rtol=1.0e-6, atol=1.e-08)) # A test with a non-diagonal covariance matrix - + expectedResiduals = np.array([0.082502896, -0.396200554, 0.235935777, -0.053152473, 0.028356288, 0.506883039, -0.489708901, -0.302135160, 0.160155600, 0.006400781]) self.assertTrue(np.allclose(self.linearFit2.residuals(weighted=False), expectedResiduals, rtol=1.0e-6, atol=1.e-08)) diff --git a/timeseries/decorators.py b/timeseries/decorators.py index 15b593867..9a616bb53 100644 --- a/timeseries/decorators.py +++ b/timeseries/decorators.py @@ -9,7 +9,6 @@ from multiprocessing import Manager,Process,cpu_count import numpy as np from ivs.aux import loggers -#from ivs.timeseries import windowfunctions logger = logging.getLogger("TS.DEC") logger.addHandler(loggers.NullHandler) @@ -17,16 +16,16 @@ def parallel_pergram(fctn): """ Run periodogram calculations in parallel. - + This splits up the frequency range between f0 and fn in 'threads' parts. - + This must decorate a 'make_parallel' decorator. """ @functools.wraps(fctn) def globpar(*args,**kwargs): - #-- construct a manager to collect all calculations - manager = Manager() - arr = manager.list([]) + #-- construct a manager to collect all calculations + manager = Manager() + arr = manager.list([]) all_processes = [] #-- get information on frequency range f0 = kwargs['f0'] @@ -38,35 +37,35 @@ def globpar(*args,**kwargs): threads = cpu_count()-1 else: threads = float(threads) - + #-- extend the arguments to include the parallel array myargs = tuple(list(args) + [arr] ) #-- however, some functions cannot be parallelized if fctn.__name__ in ['fasper']: threads = 1 - + #-- distribute the periodogram calcs over different threads, and wait for i in range(int(threads)): #-- define new start and end frequencies kwargs['f0'] = f0 + i*(fn-f0) / threads kwargs['fn'] = f0 +(i+1)*(fn-f0) / threads logger.debug("parallel: starting process %s: f=%.4f-%.4f"%(i,kwargs['f0'],kwargs['fn'])) - p = Process(target=fctn, args=myargs, kwargs=kwargs) + p = Process(target=fctn, args=myargs, kwargs=kwargs) p.start() - all_processes.append(p) - - for p in all_processes: p.join() - - logger.debug("parallel: all processes ended") - + all_processes.append(p) + + for p in all_processes: p.join() + + logger.debug("parallel: all processes ended") + #-- join all periodogram pieces freq = np.hstack([output[0] for output in arr]) ampl = np.hstack([output[1] for output in arr]) sort_arr = np.argsort(freq) - ampl = ampl[sort_arr] + ampl = ampl[sort_arr] freq = freq[sort_arr] ampl[np.isnan(ampl)] = 0. - + if len(arr[0])>2: rest = [] for i in range(2,len(arr[0])): @@ -76,7 +75,7 @@ def globpar(*args,**kwargs): return tuple([freq,ampl]+list(rest)) else: return freq,ampl - + return globpar @@ -91,7 +90,7 @@ def globpar(*args,**kwargs): times = args[0] signal = args[1] T = times.ptp() - + #-- get information on frequency range. If it is not given, compute the # start (0.1/T) and stop (Nyquist) frequency. # Also compute the frequency step as 0.1/T @@ -107,14 +106,14 @@ def globpar(*args,**kwargs): kwargs['f0'] = f0 kwargs['df'] = df kwargs['fn'] = fn - + #-- maybe the data needs to be windowed window = kwargs.pop('window',None) if window is not None: signal = signal*windowfunctions.getWindowFunction(window)(times) signal -= signal.mean() logger.debug('Signal is windowed with %s'%(window)) - + #-- normalise weights if they are given weights = kwargs.get('weights',None) if weights is not None: @@ -123,7 +122,7 @@ def globpar(*args,**kwargs): logger.debug("Weights were initially not normalized: normalization performed.") kwargs['weights'] = weights return fctn(times,signal,*args[2:],**kwargs) - + return globpar @@ -136,7 +135,8 @@ def globpar(*args,**kwargs): #-- this is the information we need the compute everything # it is possible a template x-axis has been given. If so, the parallelization # depends on the length of the template, not of the original thing. - fn = ('x_template' in kwargs) and len(kwargs['x_template']) or len(args[0]) + fn = (('x_template' in kwargs) and len(kwargs['x_template']) + or len(args[0])) f0 = kwargs.get('f0',0) fn = kwargs.get('fn',fn) kwargs['f0'] = f0 @@ -151,13 +151,13 @@ def globpar(*args,**kwargs): def getNyquist(times,nyq_stat=np.inf): """ Calculate Nyquist frequency. - + Typical use is minimum or median of time points differences. - + If C{nyq_stat} is not callable, it is assumed to be a number and that number will just be returned: this you can do to search for frequencies above the nyquist frequency - + @param times: sorted array containing time points @type times: numpy array @param nyq_stat: statistic to use or absolute value of the Nyquist frequency @@ -169,4 +169,4 @@ def getNyquist(times,nyq_stat=np.inf): nyquist = nyq_stat else: nyquist = 1/(2.*nyq_stat(np.diff(times))) - return nyquist \ No newline at end of file + return nyquist diff --git a/timeseries/eacf.py b/timeseries/eacf.py index af39da860..431ff03ac 100644 --- a/timeseries/eacf.py +++ b/timeseries/eacf.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- """ -Computes the envelope auto-correlation function. This function is estimated by computing the power spectrum of +Computes the envelope auto-correlation function. This function is estimated by computing the power spectrum of a smoothed power spectrum of a time series. It is ideally suited to discover periodicities in the power spectrum, -as is often the case for solar-like oscillators (cf the asymptotic relation of Tassoul), and can thus be used to +as is often the case for solar-like oscillators (cf the asymptotic relation of Tassoul), and can thus be used to estimate the mean large separation (mean frequency spacing between two consecutive radial modes). As an example we take the Kepler red giant KIC3744043, which shows a beautiful spectrum of solar-like oscillations: @@ -33,8 +33,8 @@ ]include figure]]eacf_smooth15.png] -The highest peak is caused by the main periodicity in the power spectrum. This does not, however, correspond directly -to the mean large separation, but to half the large separation. The reason is that almost in the middle between each +The highest peak is caused by the main periodicity in the power spectrum. This does not, however, correspond directly +to the mean large separation, but to half the large separation. The reason is that almost in the middle between each two radial modes there is a dipole mode. To compute the large separation we therefore have to enter: >>> meanLargeSeparation = 2.0 * spacings[np.argmax(autoCorrelation)] @@ -43,10 +43,10 @@ How did we choose the value 2.0 (microHz) for the FWHM of the smoothing kernel? The smoothing is done by convolving the power spectrum with a Hann kernel with a certain width. If you take the width too large, the spectrum will be too -heavily smoothed so that no periodicities can be detected. If you take the width too small, the EACF will contain too +heavily smoothed so that no periodicities can be detected. If you take the width too small, the EACF will contain too many features, which makes it difficult to recognize the right peak. As a rule-of-thumb you can take the kernel width to be 1/8 of your initial estimate of the mean large separation. If you don't have such an initial estimate, you can -estimate nu_max, which is the frequency of maximum power (= the location of the gaussian envelope of the +estimate nu_max, which is the frequency of maximum power (= the location of the gaussian envelope of the power excess), and derive from that value a first estimate for the large separation: >>> nuMax = 120.0 # for KIC3744043 @@ -68,14 +68,14 @@ def eacf(freqs, spectrum, spacings, kernelWidth, minFreq=None, maxFreq=None, doS """ Compute the Envelope Auto-Correlation Function (EACF) of a signal. - The EACF is derived by computing the power spectrum of a smoothed version of the - power spectrum of the time series. It allows to identify equispaced patterns in the + The EACF is derived by computing the power spectrum of a smoothed version of the + power spectrum of the time series. It allows to identify equispaced patterns in the power spectrum - Source: + Source: - Mosser & Appourchaux, 2009, A&A 508, p. 877 - Mosser, 2010, Astron. Nachr. 331, p. 944 - + @param freqs: frequencies in which the power specturm is given. Assumed to be equidistant. @type freqs: ndarray @param spectrum: power spectrum corresponding to the given frequency points. @@ -96,12 +96,12 @@ def eacf(freqs, spectrum, spacings, kernelWidth, minFreq=None, maxFreq=None, doS @return: autoCorrelation, croppedFreqs, smoothedSpectrum - autoCorrelation: the EACF evaluated in the values of 'spacings' - croppedFreqs: the frequencies of the selected part [minFreq, maxFreq] - - smoothedSpectrum: the smoothed power spectrum used to compute the EACF. Same length as croppedFreqs. + - smoothedSpectrum: the smoothed power spectrum used to compute the EACF. Same length as croppedFreqs. @rtype: (ndarray, ndarray, ndarray) """ - + # If requested, perform sanity checks - # The 1000. in the check on equidistancy was needed to let pass the arrays created by + # The 1000. in the check on equidistancy was needed to let pass the arrays created by # linspace() and arange(). if doSanityCheck: @@ -122,35 +122,35 @@ def eacf(freqs, spectrum, spacings, kernelWidth, minFreq=None, maxFreq=None, doS # Set the default values - + if minFreq == None: minFreq = freqs[0] if maxFreq == None: maxFreq = freqs[-1] freqStep = freqs[1]-freqs[0] - + # Crop the spectrum to the specified range - + croppedSpectrum = spectrum[(freqs >= minFreq) & (freqs <= maxFreq)] croppedFreqs = freqs[(freqs >= minFreq) & (freqs <= maxFreq)] - + # Set up a normalized Hann smoothing kernel. - # Note: a Hann window of size N has FWHM = (N-1)/2. The FWHM is given by + # Note: a Hann window of size N has FWHM = (N-1)/2. The FWHM is given by # the user in frequency units, so the corresponding N can be derived. - + kernelSize = 1 + 2 * int(round(kernelWidth/freqStep)) kernel = np.hanning(kernelSize) kernel = kernel/kernel.sum() - + # Smooth the spectrum using a convolution - + smoothedSpectrum = np.convolve(kernel, croppedSpectrum, mode='same') smoothedSpectrum -= smoothedSpectrum.mean() - + # Compute the power spectrum of the power spectrum - + autoCorrelation = DFTpower2(croppedFreqs, smoothedSpectrum, 1.0/spacings) - + # That's it. - - return autoCorrelation, croppedFreqs, smoothedSpectrum - + + return autoCorrelation, croppedFreqs, smoothedSpectrum + diff --git a/timeseries/freqanalyse.py b/timeseries/freqanalyse.py index e96648326..d1a396f45 100644 --- a/timeseries/freqanalyse.py +++ b/timeseries/freqanalyse.py @@ -1,789 +1,787 @@ -# -*- coding: utf-8 -*- -""" -Frequency Analysis Routines. - -Here are some examples: - -Section 1. Pulsation frequency analysis -======================================= - -Do a frequency analysis of the star HD129929, after Aerts 2004. We reproduce -their Fig. 8 here. - -Import necessary modules: - ->>> from ivs.catalogs import vizier - -Read in the data and do an iterative prewhitening frequency analysis: - ->>> data,units,comms = vizier.search('J/A+A/415/241/table1') ->>> params = iterative_prewhitening(data.HJD,data.Umag-data.Umag.mean(),f0=6.2,fn=7.2,maxiter=6,\ - stopcrit=(stopcrit_scargle_snr,4.,6,)) ->>> print pl.mlab.rec2txt(params,precision=6) - const ampl freq phase e_const e_ampl e_freq e_phase stopcrit - 0.000310 0.014722 6.461700 0.443450 0.000401 0.001323 0.000006 0.089865 4.950496 - 0.000000 0.014866 6.978306 -0.050189 0.000000 0.001224 0.000006 0.082305 5.677022 - 0.000000 0.011687 6.449591 0.016647 0.000000 0.001090 0.000007 0.093280 5.747565 - 0.000000 0.011546 6.990431 -0.482814 0.000000 0.001001 0.000006 0.086678 6.454955 - 0.000000 0.009380 6.590938 -0.382048 0.000000 0.000938 0.000007 0.100016 6.510729 - 0.000000 0.007645 6.966174 0.323627 0.000000 0.000863 0.000008 0.112876 5.703801 - -Plot the results: - ->>> p = pl.figure() ->>> p = pl.vlines(params['freq'],0,params['ampl'],color='k',linestyle='-') ->>> p = pl.xlabel('Frequency [c d$^{-1}$]') ->>> p = pl.ylabel('Amplitude [magnitude]') ->>> p = pl.annotate('$\ell=2$',(6.46,0.015),xycoords='data',va='bottom',ha='center',fontsize='large') ->>> p = pl.annotate('$\ell=0$',(6.59,0.010),xycoords='data',va='bottom',ha='center',fontsize='large') ->>> p = pl.annotate('$\ell=1$',(6.99,0.015),xycoords='data',va='bottom',ha='center',fontsize='large') ->>> p = pl.xlim(6.2,7.2) ->>> p = pl.ylim(0,0.018) - - -Section 2: Line profile variability -=================================== - -We generate some line profiles as Gaussian profiles, with varying average. This -as an analogue for purely radial variability. - ->>> wave = np.linspace(4950.,5000.,200.) ->>> times = np.linspace(0,1.,100) ->>> A,sigma,mu = 0.5, 5., 4975. ->>> frequency = 1/0.2 ->>> wshifts = 10.*np.sin(2*np.pi*frequency*times) ->>> spectra = np.zeros((len(times),len(wave))) ->>> for i,wshift in enumerate(wshifts): -... spectra[i] = 1.-evaluate.gauss(wave,[A,mu+wshift,sigma])+np.random.normal(size=len(wave),scale=0.01) - -Once the line profiles are constructed, we can compute the Fourier transform -of these lines: - ->>> output = spectrum_2D(times,wave,spectra,f0=frequency/2.,fn=3*frequency,df=0.001,threads=2,method='scargle',subs_av=True,full_output=True) - -With this output, we can find retrieve the frequency. We plot the periodograms -for each wavelength bin on the left, and on the right the average over all -wavelength bins: - ->>> p = pl.figure() ->>> p = pl.subplot(121) ->>> p = pl.imshow(output['pergram'][1][::-1],extent=[wave[0],wave[-1],frequency/2,3*frequency],aspect='auto') ->>> p = pl.xlabel('Wavelength (A)') ->>> p = pl.ylabel('Frequency (c/d)') ->>> cbar = pl.colorbar() ->>> cbar.set_label('Amplitude') ->>> p = pl.subplot(122) ->>> p = pl.plot(output['pergram'][1].mean(axis=1),output['pergram'][0],'k-') ->>> p = pl.ylim(frequency/2,3*frequency) ->>> p = pl.xlabel('Amplitude') ->>> p = pl.ylabel('Frequency (c/d)') - -]]include figure]]timeseries_freqanalyse_02.png] - -We can then fix the frequency and compute all the parameters. However, we now -choose not to subtract the average profile, but instead use the GLS periodogram -to include the constant - ->>> output = spectrum_2D(times,wave,spectra,f0=frequency-0.05,fn=frequency+0.05,df=0.001,threads=2,method='gls',subs_av=False,full_output=True) - ->>> p = pl.figure() ->>> p = pl.subplot(221) ->>> p = pl.errorbar(wave,output['pars']['const'],yerr=output['pars']['e_const'],fmt='ko-') ->>> p,q = pl.xlabel('Wavelength (A)'),pl.ylabel('Constant') ->>> p = pl.subplot(222) ->>> p = pl.errorbar(wave,output['pars']['ampl'],yerr=output['pars']['e_ampl'],fmt='ro-') ->>> p,q = pl.xlabel('Wavelength (A)'),pl.ylabel('Amplitude') ->>> p = pl.subplot(223) ->>> p = pl.errorbar(wave,output['pars']['freq'],yerr=output['pars']['e_freq'],fmt='ko-') ->>> p,q = pl.xlabel('Wavelength (A)'),pl.ylabel('Frequency (c/d)') ->>> p = pl.subplot(224) ->>> p = pl.errorbar(wave,output['pars']['phase'],yerr=output['pars']['e_phase'],fmt='ro-') ->>> p,q = pl.xlabel('Wavelength (A)'),pl.ylabel('Phase') - -]]include figure]]timeseries_freqanalyse_03.png] - -Section 3. Time-frequency analysis -================================== - -Example usage: we generate some data where the frequency jumps to double -its value in the middle of the time series. The first half is thus given by - ->>> params = np.rec.fromarrays([[10.],[1.],[0.],[0.]],names=['freq','ampl','const','phase']) ->>> times = np.linspace(0,15,1000) ->>> signal = evaluate.sine(times,params) - -And the second half by - ->>> params['freq'] = 20. ->>> signal[:len(times)/2] = evaluate.sine(times[:len(times)/2],params) - -The time-frequency analysis is calculate via the command - ->>> output = time_frequency(times,signal,fn=30.) - -And the outcome can be plotted via - ->>> p = pl.figure() ->>> p = pl.imshow(output['pergram'][1].T[::-1],aspect='auto',extent=[times[0],times[-1],output['pergram'][0][0],output['pergram'][0][-1]]) ->>> p,q = pl.xlabel('Time (d)'),pl.ylabel('Frequency (c/d)') - -]]include figure]]timeseries_freqanalyse_04.png] - -or - ->>> p = pl.figure() ->>> p = pl.subplot(221) ->>> p = pl.errorbar(output['times'],output['pars']['const'],yerr=output['pars']['e_const'],fmt='ko-') ->>> p,q = pl.xlabel('Time (d)'),pl.ylabel('Constant') ->>> p = pl.subplot(222) ->>> p = pl.errorbar(output['times'],output['pars']['ampl'],yerr=output['pars']['e_ampl'],fmt='ro-') ->>> p,q = pl.xlabel('Time (d)'),pl.ylabel('Amplitude') ->>> p = pl.subplot(223) ->>> p = pl.errorbar(output['times'],output['pars']['freq'],yerr=output['pars']['e_freq'],fmt='ko-') ->>> p,q = pl.xlabel('Time (d)'),pl.ylabel('Frequency (c/d)') ->>> p = pl.subplot(224) ->>> p = pl.errorbar(output['times'],output['pars']['phase'],yerr=output['pars']['e_phase'],fmt='ro-') ->>> p,q = pl.xlabel('Time (d)'),pl.ylabel('Phase') - -]]include figure]]timeseries_freqanalyse_05.png] - -Author: Pieter Degroote -""" -import logging -import numpy as np -import pylab as pl -from ivs.sigproc import fit -from ivs.sigproc import evaluate -from ivs.timeseries import pergrams -from ivs.timeseries.decorators import defaults_pergram -from ivs.aux import numpy_ext -import os - -logger = logging.getLogger("TS.FREQANAL") - -#{ Convenience functions - -def find_frequency(times,signal,method='scargle',model='sine',full_output=False, - optimize=0,max_loops=20, scale_region=0.1, scale_df=0.20, model_kwargs=None, - correlation_correction=True,prewhiteningorder_snr=False, - prewhiteningorder_snr_window=1.,**kwargs): - """ - Find one frequency, automatically going to maximum precision and return - parameters & error estimates. - - This routine makes the frequency grid finer until it is finer than the - estimated error on the frequency. After that, it will compute (harmonic) - parameters and estimate errors. - - There is a possibility to escape this optimization by setting scale_df=0 or - freqregscale=0. - - You can include a nonlinear least square update of the parameters, by - setting the keyword C{optimize=1} (optimization outside loop) or - C{optimize=2} (optimization after each iteration). - - The method with which to find the frequency can be set with the keyword - C{method}, the model used to fit and optimize should be set with C{model}. - Extra keywords for the model functions should go in C{model_kwargs}. If - C{method} is a tuple, the first method will be used for the first frequency - search only. This could be useful to take advantage of such methods as - fasper which do not allow for iterative zoom-ins. By default, the function looks - for the highest (or deepest in the case of the pdm method) peak, but instead it is - possible to go for the peak having the highest SNR before prewhitening by setting - C{prewhiteningorder_snr} to True. In this case, the noise spectrum is calculated - using a convolution with a C{prewhiteningorder_snr_window} wide box. - - Possible extra keywords: see definition of the used periodogram function. - - B{Warning}: the timeseries must be B{sorted in time} and B{cannot contain - the same timepoint twice}. Otherwise, a 'ValueError, concatenation problem' - can occur. - - Example keywords: - - 'correlation_correction', default=True - - 'freqregscale', default=0.5: factor for zooming in on frequency - - 'dfscale', default = 0.25: factor for optimizing frequency resolution - - Example usage: We generate a sine signal - - >>> times = np.linspace(0,100,1000) - >>> signal = np.sin(2*np.pi*2.5*times) + np.random.normal(size=len(times)) - - Compute the frequency - - >>> parameters, pgram, model = find_frequency(times,signal,full_output=True) - - Make a plot: - - >>> p = pl.figure() - >>> p = pl.axes([0.1,0.3,0.85,0.65]) - >>> p = pl.plot(pgram[0],pgram[1],'k-') - >>> p = pl.xlim(2.2,2.8) - >>> p = pl.ylabel('Amplitude') - >>> p = pl.axes([0.1,0.1,0.85,0.2]) - >>> p = pl.plot(pgram[0][:-1],np.diff(pgram[0]),'k-') - >>> p = pl.xlim(2.2,2.8) - >>> p,q = pl.xlabel('Frequency (c/d)'),pl.ylabel('Frequency resolution $\Delta F$') - - ]]include figure]]timeseries_freqanalyse_06.png] - - @rtype: record array(, 2x1Darray, 1Darray) - @return: parameters and errors(, periodogram, model function) - """ - if model_kwargs is None: - model_kwargs = dict() - #-- initial values - e_f = 0 - freq_diff = np.inf - prev_freq = -np.inf - counter = 0 - - f_max = np.inf - f_min = 0.#-np.inf - - #-- calculate periodogram until frequency precision is - # under 1/10th of correlation corrected version of frequency error - method_kwargs = kwargs.copy() # don't modify the dictionary the user gave - - while freq_diff>e_f/10.: - #-- possibly, we might want to use different periodograms for the first - # calculation than for the zoom in, since some periodograms are faster - # than others but do not have the ability to 'zoom in' (e.g. the FFT) - if freq_diff==np.inf and not isinstance(method,str): - method_ = method[1] - method = method[0] # override method to be a string the next time - #-- calculate periodogram - freqs,ampls = getattr(pergrams,method)(times,signal,**method_kwargs) - f0,fn,df = freqs[0],freqs[-1],freqs[1]-freqs[0] - #-- now use the second method for the zoom-ins from now on - if freq_diff==np.inf and not isinstance(method,str): - method = method_ - #-- extract the frequency: this part should be generalized, but for now, - # it will do: - if method in ['pdm']: - frequency = freqs[np.argmin(ampls)] - #-- instead of going for the highest peak, let's get the most significant one - if prewhiteningorder_snr: - if counter == 0: #we calculate a noise spectrum with a convolution in a 1 d-1 window - windowlength = float(prewhiteningorder_snr_window)/(freqs[1]-freqs[0]) - window = np.ones(int(windowlength))/float(windowlength) - ampls_ = np.concatenate((ampls[::-1],ampls,ampls[::-1])) #we mirror the amplitude spectrum on both ends so the convolution will be better near the edges - noises_ = np.convolve(ampls_, window, 'same') - noises = np.split(noises_,3)[1] #and we recut the resulted convolution to match the original frequency range - freqs_old = np.copy(freqs) - noises_old = np.copy(noises) - else: - noises = np.interp(freqs,freqs_old,noises_old) #we use the original noise spectrum in this narrower windows too, which should save some time, and avoid the problem of having a wider window for the SNR calculation than the width of the zoom-in window - frequency = freqs[np.argmax(ampls/noises)] - else: - frequency = freqs[np.argmax(ampls)] - if full_output and counter==0: - freqs_,ampls_ = freqs,ampls - #-- estimate parameters and calculate a fit, errors and residuals - params = getattr(fit,model)(times,signal,frequency,**model_kwargs) - if hasattr(fit,'e_'+model): - errors = getattr(fit,'e_'+model)(times,signal,params,correlation_correction=correlation_correction) - e_f = errors['e_freq'][-1] - #-- possibly there are not errors defined for this fitting functions - else: - errors = None - #-- optimize inside loop if necessary and if we gained prediction - # value: - if optimize==2: - params_,errors_,gain = fit.optimize(times,signal,params,model) - if gain>0: - params = params_ - logger.info('Accepted optimization (gained %g%%)'%gain) - - #-- improve precision - freq_diff = abs(frequency-prev_freq) - prev_freq = frequency - freq_region = fn-f0 - f0 = max(f_min,frequency-freq_region*scale_region/2.) - fn = min(f_max,frequency+freq_region*scale_region/2.) - df *= scale_df - method_kwargs['f0'] = f0 - method_kwargs['fn'] = fn - method_kwargs['df'] = df - #-- possibilities to escape iterative zoom in - #print '---> {counter}/{max_loops}: freq={frequency} ({f0}-->{fn}/{df}), e_f={e_f}, freq_diff={freq_diff}'.format(**locals()),max(ampls) - if scale_region==0 or scale_df==0: - break - if counter >= max_loops: - logger.error("Frequency precision not reached in %d steps, breaking loop"%(max_loops)) - break - if (fn-f0)/df<5: - logger.error("Frequency precision not reached with stepsize %e , breaking loop"%(df/scale_df)) - break - counter += 1 - #-- optimize parameters outside of loop if necessary: - if optimize==1: - params_,errors_,gain = fit.optimize(times,signal,params,model) - if gain>0: - params = params_ - logger.info('Accepted optimization (gained %g%%)'%gain) - #-- add the errors to the parameter array if possible - if errors is not None: - params = numpy_ext.recarr_join(params,errors) - logger.info("%s model parameters via %s periodogram:\n"%(model,method)+pl.mlab.rec2txt(params,precision=8)) - #-- when full output is required, return parameters, periodogram and fitting - # function - if full_output: - mymodel = getattr(evaluate,model)(times,params) - return params,(freqs_,ampls_),mymodel - else: - return params - - - -def iterative_prewhitening(times,signal,maxiter=1000,optimize=0,method='scargle', - model='sine',full_output=False,stopcrit=None,correlation_correction=True, - prewhiteningorder_snr=False,prewhiteningorder_snr_window=1.,**kwargs): - """ - Fit one or more functions to a timeseries via iterative prewhitening. - - This function will use C{find_frequency} to fit the function parameters to - the original signal (including any previously found parameters), - optimize the parameters if needed, and remove it from the data to search - for a new frequency in the residuals. - - It is always the original signal that is used to fit all parameters again; - B{only the (optimized) frequency is remembered from step to step} (Vanicek's - method). - - You best set C{maxiter} to some sensable value, to hard-limit the number of - frequencies that will be searched for. You can additionally use a C{stopcrit} - and stop looking for frequencies once it is reached. C{stopcrit} should be - a tuple; the first argument is the function to call, the other arguments - are passed to the function, after the mandatory arguments C{times,signal, - modelfunc,allparams,pergram}. See L{stopcrit_scargle_snr} for an example of - such a function. - - By default, the function looks for the highest (or deepest in the case of the pdm - method) peak, but instead it is possible to go for the peak having the highest - SNR before prewhitening by setting C{prewhiteningorder_snr} to True. In this case, - the noise spectrum is calculated using a convolution with a - C{prewhiteningorder_snr_window} wide box. Usage of this is strongly encouraged, - especially combined with L{stopcrit_scargle_snr} as C{stopcrit}. - - @return: parameters, model(, model function) - @rtype: rec array(, ndarray) - """ - residuals = signal.copy() - frequencies = [] - stop_criteria = [] - while maxiter: - #-- compute the next frequency from the residuals - params,pergram,this_fit = find_frequency(times,residuals,method=method, - full_output=True,correlation_correction=correlation_correction, - prewhiteningorder_snr=prewhiteningorder_snr, - prewhiteningorder_snr_window=prewhiteningorder_snr_window,**kwargs) - - #-- do the fit including all frequencies - frequencies.append(params['freq'][-1]) - allparams = getattr(fit,model)(times,signal,frequencies) - - #-- if there's a need to optimize, optimize the last n parameters - if optimize>0: - residuals_for_optimization = residuals - if optimize<=len(params): - model_fixed_params = getattr(evaluate,model)(times,allparams[:-optimize]) - residuals_for_optimization -= model_fixed_params - uparams,e_uparams, gain = fit.optimize(times,residuals_for_optimization,allparams[-optimize:],model) - #-- only accept the optimization if we gained prediction power - if gain>0: - allparams[-optimize:] = uparams - logger.info('Accepted optimization (gained %g%%)'%gain) - - #-- compute the residuals to use in the next prewhitening step - modelfunc = getattr(evaluate,model)(times,allparams) - residuals = signal - modelfunc - - #-- exhaust the counter - maxiter -= 1 - - #-- check stop criterion - if stopcrit is not None: - func = stopcrit[0] - args = stopcrit[1:] - condition,value = func(times,signal,modelfunc,allparams,pergram,*args) - logger.info('Stop criterion (%s): %.3g'%(func.__name__,value)) - stop_criteria.append(value) - if condition: - logger.info('Stop criterion reached') - break - - #-- calculate the errors - e_allparams = getattr(fit,'e_'+model)(times,signal,allparams,correlation_correction=correlation_correction) - - allparams = numpy_ext.recarr_join(allparams,e_allparams) - if stopcrit is not None: - allparams = numpy_ext.recarr_join(allparams,np.rec.fromarrays([stop_criteria],names=['stopcrit'])) - - if full_output: - return allparams,modelfunc - else: - return allparams - - - - -def spectrum_2D(x,y,matrix,weights_2d=None,show_progress=False, - subs_av=True,full_output=False,**kwargs): - """ - Compute a 2D periodogram. - - Rows (first axis) should be spectra chronologically - - x are time points (length N) - y are second axis units (e.g. wavelengths) (length NxM) - - If the periodogram/wavelength combination has a large range, this script - can produce a B{ValueError} or B{MemoryError}. To solve this, you could - iterate this function over a subset of wavelength bins yourself, and write - the results to a file. - - This function also outputs a model, which you can use to subtract from the - data (probably together with the average profile, which is also returned) - and look further for any frequencies:: - - output = spectrum_2D(x,y,matrix,subs_av=True) - new_matrix = matrix - output['avprof'] - output['model' - output2 = spectrum_2D(x,y,new_matrix,subs_av=False) - - If you want to prewhiten a model, you'd probably want to use the same - frequency across the whole line profile. You can hack this by setting - C{f0=frequency} and C{fn=frequency+df} with C{df} the size of the frequency - step. - - B{Example usage}: first we generate some variable line profiles. In this case, - this is just a radial velocity variation - - >>> times = np.linspace(0,150,100) - >>> wavel = np.r_[4500:4600:1.0] - >>> matrix = np.zeros((len(times),len(wavel))) - >>> for i in xrange(len(times)): - ... central_wave = 5*np.sin(2*np.pi/10*times[i]) - ... matrix[i,:] = 1 - 0.5*np.exp( -(wavel-4550-central_wave)**2/10**2) - - Once the line profiles are constructed, we can compute the Fourier transform - of these lines: - - >>> output = spectrum_2D(times,wavel,matrix,method='scargle',model='sine',f0=0.05,fn=0.3,subs_av=True,full_output=True) - - With this output, we can find retrieve the frequency. We plot the periodograms - for each wavelength bin on the left, and on the right the average over all - wavelength bins: - - >>> p = pl.figure() - >>> p = pl.subplot(121) - >>> p = pl.imshow(output['pergram'][1][::-1],extent=[wavel[0],wavel[-1],0.05,0.3],aspect='auto') - >>> p = pl.subplot(122) - >>> p = pl.plot(output['pergram'][1].mean(axis=1),output['pergram'][0],'k-') - >>> p = pl.ylim(0.05,0.3) - - We can then fix the frequency and compute all the parameters. However, we now - choose not to subtract the average profile, but instead use the GLS periodogram - to include the constant - - >>> output = spectrum_2D(times,wavel,matrix,method='gls',model='sine',f0=0.095,fn=0.105,full_output=True) - - >>> p = pl.figure() - >>> p = pl.subplot(221) - >>> p = pl.errorbar(wavel,output['pars']['const'],yerr=output['pars']['e_const'],fmt='ko-') - >>> p = pl.subplot(222) - >>> p = pl.errorbar(wavel,output['pars']['ampl'],yerr=output['pars']['e_ampl'],fmt='ro-') - >>> p = pl.subplot(223) - >>> p = pl.errorbar(wavel,output['pars']['freq'],yerr=output['pars']['e_freq'],fmt='ko-') - >>> p = pl.subplot(224) - >>> p = pl.errorbar(wavel,output['pars']['phase'],yerr=output['pars']['e_phase'],fmt='ro-') - - @return: dict with keys C{avprof} (2D array), C{pars} (rec array), C{model} (1D array), C{pergram} (freqs,2Darray) - @rtype: dict - """ - #-- compute average profile - if subs_av: - matrix_av = np.outer(np.ones(len(matrix)),matrix.mean(axis=0)) - matrix = matrix - matrix_av - else: - matrix_av = 0. - - #-- prepare output of sine-parameters - params = [] - freq_spectrum = [] - mymodel = [] - #-- do frequency analysis - for iwave in xrange(len(matrix[0])): - signal = matrix[:,iwave] - if weights_2d is not None: - weights = weights_2d[:,iwave] - kwargs['weights'] = weights - - #-- make sure output is always a tuple, in case full output was asked - # we don't want iterative zoom in so set scale_df=0 - out = find_frequency(x,signal,full_output=full_output,scale_df=0,**kwargs) - - #-- add the parameters of this wavelength bin to the list - if full_output: - params.append(out[0]) - freq_spectrum.append(out[1][1]) - mymodel.append(out[2]) - else: - params.append(out) - - #-- prepare output - output = {} - output['avprof'] = matrix_av - output['pars'] = np.hstack(params) - if full_output: - output['pergram'] = out[1][0],np.vstack(freq_spectrum).T - output['model'] = np.vstack(mymodel).T - - return output - -@defaults_pergram -def time_frequency(times,signal,window_width=None,n_windows=100, - window='rectangular',detrend=None,**kwargs): - """ - Short Time (Fourier) Transform. - - Slide a window through the timeseries, multiply the timeseries with the - window, and perform a Fourier Transform. - - It is best to fix explicitly C{f0}, C{fn} and C{df}, to limit the computation - time! - - Extra kwargs go to L{find_frequency} - - @param n_windows: number of slices - @type n_windows: integer - @param window_width: width of each slice (defaults to T/20) - @type window_width: float - @param detrend: detrending function, accepting times and signal as args - @type detrend: callable - @param window: window function to apply - @type window: string - @return: spectrogram, times used, parameters, errors, points used per slice - @rtype: dict - """ - if window_width is None: - window_width = kwargs.get('window_width',times.ptp()/20.) - #-- cut light curve until window fits exactly - stft_times = np.linspace(times[0]+window_width/2.,times[-1]-window_width/2.,n_windows) - - #-- get f0,fn and df to fix in all computations. If they are not given, they - # are set to default values by the decorator - f0 = kwargs.pop('f0') - fn = kwargs.pop('fn') - df = kwargs.pop('df') - nyq_stat = kwargs.pop('nyq_stat',fn) - - #-- prepare arrays for parameters, points and spectrum - pars = [] - pnts = np.zeros(n_windows) - spec = None - #-- compute the periodogram for each slice - for i,t in enumerate(stft_times): - region = (abs(times-t) <= (window_width/2.)) - times_ = times[region] - signal_ = signal[region] - if detrend: - times_,signal_ = detrend(times_,signal_) - pnts[i] = len(times_) - if len(times_)>1: - output = find_frequency(times_,signal_,full_output=True,f0=f0,fn=fn,df=df,nyq_stat=nyq_stat,scale_df=0,**kwargs) - if spec is None: - spec = np.ones((n_windows,len(output[1][1]))) - pars.append(output[0]) - spec[i,:len(output[1][1])] = output[1][1] - else: - nanpars = np.rec.array(np.nan*np.ones(len(pars[-1].dtype.names)),dtype=pars[-1].dtype) - pars.append(nanpars) - #try: - # pars.append(np.nan*pars[-1]) - #except: - # print pars - # print pars[-1] - # print pars[-1].dtype - # print 0*pars[-1] - # print np.nan*pars[-1] - # raise - spec[i] = np.nan - out = {} - out['times'] = stft_times - out['pars'] = np.hstack(pars) - out['pergram'] = (output[1][0],spec) - return out - -#{ Convenience stop-criteria - -def stopcrit_scargle_prob(times,signal,modelfunc,allparams,pergram,crit_value): - """ - Stop criterium based on probability. - """ - value = pergrams.scargle_probability(pergram[1].max(),times,pergram[0]) - print value - return value>crit_value,value - -def stopcrit_scargle_snr(times,signal,modelfunc,allparams,pergram,crit_value,width=6.): - """ - Stop criterium based on signal-to-noise ratio. - """ - width = width/2. - argmax = np.argmax(pergram[1]) - ampls = pergram[1] - start = max(0,pergram[0][argmax]-width) - stop = min(pergram[0][argmax]+width,pergram[0][-1]) - if start==0: - stop += width-pergram[0][argmax] - if stop==pergram[0][-1]: - start = pergram[0][-1]-pergram[0][argmax]+width - ampls = ampls[(start<=pergram[0]) & (pergram[0]<=stop)] - value = pergram[1][argmax]/ampls.mean() - return value>> periods = linspace(1./p[0][-1],1./p[0][0],2*len(p[0])) - >>> ampl = interpol.linear_interpolation(1./p[0][::-1],p[1][::-1],periods) - >>> ampl = where(isnan(ampl),hstack([ampl[1:],ampl[:1]]),ampl) - - @param frequencies: frequency array - @type frequencies: numpy 1d array - @param power: power spectrum - @type power: numpy 1d array - @keyword max_step: maximum time shift - @type max_step: float - @keyword interval: tuple of frequencies (start, end) - @type interval: tuple of floats - @keyword threshold: high cut off value (in units of sample mean) - @type threshold: float - @return: domain of autocorrelation and autocorrelation - @rtype: (ndarray,ndarray) - """ - #-- cut out the interesting part of the spectrum - if interval is not (): - cut_out = frequencies[(interval[0]<=frequencies) & (frequencies<=interval[1])] - start_freq = cut_out[0] - stop_freq = cut_out[-1] - start = np.argmin(abs(frequencies-interval[0])) - stop = np.argmin(abs(frequencies-interval[1])) - else: - start = 1 - stop = len(frequencies)-1 - - #-- compute the frequency step - Dfreq = (frequencies[start+1] - frequencies[start+0]) - max_step = int(max_step/Dfreq) - autocorr = [] - variance = [] - mean = np.average(power) - - #-- cut of high peaks at a signal to noise level of 6 - # cut of low values at a signal to noise level of 1 - if threshold is not None: - power[power>=(threshold*mean)] = threshold*mean - #power = where(less(power, mean), mean, power) - - #-- normalize power as to arrive at the AUTO-correlation function. - mean = np.average(power) - power = power-mean - - #-- compute autocorrelation. If nessecary, take border effects into - # account. - for i in xrange(2,max_step): - end_s = min(len(power), stop+i) - end_o = start + end_s - (start+i) - original = power[start :end_o] - shifted = power[start+i:end_s] - if len(original) < 10: - logger.error("AUTOCORR: too few points left in interval, breaking up.") - break - if method==1: - autocorr.append(np.average(original*shifted)) - else: - autocorr.append(np.correlate(original,shifted)) - variance.append(np.average(original*original)) - - domain = np.arange(2,max_step) * Dfreq - domain = domain[0:len(autocorr)] - - #-- normalize - autocorr = np.array(autocorr)/np.array(variance) - logger.info("Computed autocorrelation in interval %s with maxstep %s"%(interval,max_step)) - return domain, autocorr - -if __name__=="__main__": - import doctest - import pylab as pl - import sys - from ivs.aux import argkwargparser - from ivs.inout import ascii - - #-- if no arguments are given, we just do a test run - if not sys.argv[1:]: - doctest.testmod() - pl.show() - sys.exit() - - #-- if arguments are given, we assume the user wants to run one of the - # functions with arguments given in the command line - # EXAMPLES: - # $:> python freqanalyse.py find_frequency infile=test.dat full_output=True - # $:> python freqanalyse.py time_frequency infile=test.dat full_output=True - else: - method,args,kwargs = argkwargparser.parse() - print "Running method %s with arguments %s and keyword arguments %s"%(method,args,kwargs) - if '--help' in args or 'help' in args or 'help' in kwargs: - sys.exit() - full_output = kwargs.get('full_output',False) - times,signal = ascii.read2array(kwargs.pop('infile')).T[:2] - out = globals()[method](times,signal, **kwargs) - - #-- when find_frequency is called - if method=='find_frequency' and full_output: - print pl.mlab.rec2txt(out[0],precision=8) - pl.figure() - pl.subplot(211) - pl.plot(out[1][0],out[1][1],'k-') - pl.subplot(212) - pl.plot(times,signal,'ko',ms=2) - pl.plot(times,out[2],'r-',lw=2) - pl.show() - elif method=='find_frequency': - print pl.mlab.rec2txt(out) - - #-- when time_frequency is called - elif method=='time_frequency': - print pl.mlab.rec2txt(out['pars'],precision=8) - pl.figure() - pl.imshow(out['pergram'][1].T[::-1],aspect='auto',extent=[out['times'][0],out['times'][-1],out['pergram'][0][0],out['pergram'][0][-1]]) - - - pl.show() - - \ No newline at end of file +# -*- coding: utf-8 -*- +""" +Frequency Analysis Routines. + +Here are some examples: + +Section 1. Pulsation frequency analysis +======================================= + +Do a frequency analysis of the star HD129929, after Aerts 2004. We reproduce +their Fig. 8 here. + +Import necessary modules: + +>>> from ivs.catalogs import vizier + +Read in the data and do an iterative prewhitening frequency analysis: + +>>> data,units,comms = vizier.search('J/A+A/415/241/table1') +>>> params = iterative_prewhitening(data.HJD,data.Umag-data.Umag.mean(),f0=6.2,fn=7.2,maxiter=6,\ + stopcrit=(stopcrit_scargle_snr,4.,6,)) +>>> print pl.mlab.rec2txt(params,precision=6) + const ampl freq phase e_const e_ampl e_freq e_phase stopcrit + 0.000310 0.014722 6.461700 0.443450 0.000401 0.001323 0.000006 0.089865 4.950496 + 0.000000 0.014866 6.978306 -0.050189 0.000000 0.001224 0.000006 0.082305 5.677022 + 0.000000 0.011687 6.449591 0.016647 0.000000 0.001090 0.000007 0.093280 5.747565 + 0.000000 0.011546 6.990431 -0.482814 0.000000 0.001001 0.000006 0.086678 6.454955 + 0.000000 0.009380 6.590938 -0.382048 0.000000 0.000938 0.000007 0.100016 6.510729 + 0.000000 0.007645 6.966174 0.323627 0.000000 0.000863 0.000008 0.112876 5.703801 + +Plot the results: + +>>> p = pl.figure() +>>> p = pl.vlines(params['freq'],0,params['ampl'],color='k',linestyle='-') +>>> p = pl.xlabel('Frequency [c d$^{-1}$]') +>>> p = pl.ylabel('Amplitude [magnitude]') +>>> p = pl.annotate('$\ell=2$',(6.46,0.015),xycoords='data',va='bottom',ha='center',fontsize='large') +>>> p = pl.annotate('$\ell=0$',(6.59,0.010),xycoords='data',va='bottom',ha='center',fontsize='large') +>>> p = pl.annotate('$\ell=1$',(6.99,0.015),xycoords='data',va='bottom',ha='center',fontsize='large') +>>> p = pl.xlim(6.2,7.2) +>>> p = pl.ylim(0,0.018) + + +Section 2: Line profile variability +=================================== + +We generate some line profiles as Gaussian profiles, with varying average. This +as an analogue for purely radial variability. + +>>> wave = np.linspace(4950.,5000.,200.) +>>> times = np.linspace(0,1.,100) +>>> A,sigma,mu = 0.5, 5., 4975. +>>> frequency = 1/0.2 +>>> wshifts = 10.*np.sin(2*np.pi*frequency*times) +>>> spectra = np.zeros((len(times),len(wave))) +>>> for i,wshift in enumerate(wshifts): +... spectra[i] = 1.-evaluate.gauss(wave,[A,mu+wshift,sigma])+np.random.normal(size=len(wave),scale=0.01) + +Once the line profiles are constructed, we can compute the Fourier transform +of these lines: + +>>> output = spectrum_2D(times,wave,spectra,f0=frequency/2.,fn=3*frequency,df=0.001,threads=2,method='scargle',subs_av=True,full_output=True) + +With this output, we can find retrieve the frequency. We plot the periodograms +for each wavelength bin on the left, and on the right the average over all +wavelength bins: + +>>> p = pl.figure() +>>> p = pl.subplot(121) +>>> p = pl.imshow(output['pergram'][1][::-1],extent=[wave[0],wave[-1],frequency/2,3*frequency],aspect='auto') +>>> p = pl.xlabel('Wavelength (A)') +>>> p = pl.ylabel('Frequency (c/d)') +>>> cbar = pl.colorbar() +>>> cbar.set_label('Amplitude') +>>> p = pl.subplot(122) +>>> p = pl.plot(output['pergram'][1].mean(axis=1),output['pergram'][0],'k-') +>>> p = pl.ylim(frequency/2,3*frequency) +>>> p = pl.xlabel('Amplitude') +>>> p = pl.ylabel('Frequency (c/d)') + +]]include figure]]timeseries_freqanalyse_02.png] + +We can then fix the frequency and compute all the parameters. However, we now +choose not to subtract the average profile, but instead use the GLS periodogram +to include the constant + +>>> output = spectrum_2D(times,wave,spectra,f0=frequency-0.05,fn=frequency+0.05,df=0.001,threads=2,method='gls',subs_av=False,full_output=True) + +>>> p = pl.figure() +>>> p = pl.subplot(221) +>>> p = pl.errorbar(wave,output['pars']['const'],yerr=output['pars']['e_const'],fmt='ko-') +>>> p,q = pl.xlabel('Wavelength (A)'),pl.ylabel('Constant') +>>> p = pl.subplot(222) +>>> p = pl.errorbar(wave,output['pars']['ampl'],yerr=output['pars']['e_ampl'],fmt='ro-') +>>> p,q = pl.xlabel('Wavelength (A)'),pl.ylabel('Amplitude') +>>> p = pl.subplot(223) +>>> p = pl.errorbar(wave,output['pars']['freq'],yerr=output['pars']['e_freq'],fmt='ko-') +>>> p,q = pl.xlabel('Wavelength (A)'),pl.ylabel('Frequency (c/d)') +>>> p = pl.subplot(224) +>>> p = pl.errorbar(wave,output['pars']['phase'],yerr=output['pars']['e_phase'],fmt='ro-') +>>> p,q = pl.xlabel('Wavelength (A)'),pl.ylabel('Phase') + +]]include figure]]timeseries_freqanalyse_03.png] + +Section 3. Time-frequency analysis +================================== + +Example usage: we generate some data where the frequency jumps to double +its value in the middle of the time series. The first half is thus given by + +>>> params = np.rec.fromarrays([[10.],[1.],[0.],[0.]],names=['freq','ampl','const','phase']) +>>> times = np.linspace(0,15,1000) +>>> signal = evaluate.sine(times,params) + +And the second half by + +>>> params['freq'] = 20. +>>> signal[:len(times)/2] = evaluate.sine(times[:len(times)/2],params) + +The time-frequency analysis is calculate via the command + +>>> output = time_frequency(times,signal,fn=30.) + +And the outcome can be plotted via + +>>> p = pl.figure() +>>> p = pl.imshow(output['pergram'][1].T[::-1],aspect='auto',extent=[times[0],times[-1],output['pergram'][0][0],output['pergram'][0][-1]]) +>>> p,q = pl.xlabel('Time (d)'),pl.ylabel('Frequency (c/d)') + +]]include figure]]timeseries_freqanalyse_04.png] + +or + +>>> p = pl.figure() +>>> p = pl.subplot(221) +>>> p = pl.errorbar(output['times'],output['pars']['const'],yerr=output['pars']['e_const'],fmt='ko-') +>>> p,q = pl.xlabel('Time (d)'),pl.ylabel('Constant') +>>> p = pl.subplot(222) +>>> p = pl.errorbar(output['times'],output['pars']['ampl'],yerr=output['pars']['e_ampl'],fmt='ro-') +>>> p,q = pl.xlabel('Time (d)'),pl.ylabel('Amplitude') +>>> p = pl.subplot(223) +>>> p = pl.errorbar(output['times'],output['pars']['freq'],yerr=output['pars']['e_freq'],fmt='ko-') +>>> p,q = pl.xlabel('Time (d)'),pl.ylabel('Frequency (c/d)') +>>> p = pl.subplot(224) +>>> p = pl.errorbar(output['times'],output['pars']['phase'],yerr=output['pars']['e_phase'],fmt='ro-') +>>> p,q = pl.xlabel('Time (d)'),pl.ylabel('Phase') + +]]include figure]]timeseries_freqanalyse_05.png] + +Author: Pieter Degroote +""" +import logging +import numpy as np +import pylab as pl +from ivs.sigproc import fit +from ivs.sigproc import evaluate +from ivs.timeseries import pergrams +from ivs.timeseries.decorators import defaults_pergram +from ivs.aux import numpy_ext + +logger = logging.getLogger("TS.FREQANAL") + +#{ Convenience functions + +def find_frequency(times,signal,method='scargle',model='sine',full_output=False, + optimize=0,max_loops=20, scale_region=0.1, scale_df=0.20, model_kwargs=None, + correlation_correction=True,prewhiteningorder_snr=False, + prewhiteningorder_snr_window=1.,**kwargs): + """ + Find one frequency, automatically going to maximum precision and return + parameters & error estimates. + + This routine makes the frequency grid finer until it is finer than the + estimated error on the frequency. After that, it will compute (harmonic) + parameters and estimate errors. + + There is a possibility to escape this optimization by setting scale_df=0 or + freqregscale=0. + + You can include a nonlinear least square update of the parameters, by + setting the keyword C{optimize=1} (optimization outside loop) or + C{optimize=2} (optimization after each iteration). + + The method with which to find the frequency can be set with the keyword + C{method}, the model used to fit and optimize should be set with C{model}. + Extra keywords for the model functions should go in C{model_kwargs}. If + C{method} is a tuple, the first method will be used for the first frequency + search only. This could be useful to take advantage of such methods as + fasper which do not allow for iterative zoom-ins. By default, the function looks + for the highest (or deepest in the case of the pdm method) peak, but instead it is + possible to go for the peak having the highest SNR before prewhitening by setting + C{prewhiteningorder_snr} to True. In this case, the noise spectrum is calculated + using a convolution with a C{prewhiteningorder_snr_window} wide box. + + Possible extra keywords: see definition of the used periodogram function. + + B{Warning}: the timeseries must be B{sorted in time} and B{cannot contain + the same timepoint twice}. Otherwise, a 'ValueError, concatenation problem' + can occur. + + Example keywords: + - 'correlation_correction', default=True + - 'freqregscale', default=0.5: factor for zooming in on frequency + - 'dfscale', default = 0.25: factor for optimizing frequency resolution + + Example usage: We generate a sine signal + + >>> times = np.linspace(0,100,1000) + >>> signal = np.sin(2*np.pi*2.5*times) + np.random.normal(size=len(times)) + + Compute the frequency + + >>> parameters, pgram, model = find_frequency(times,signal,full_output=True) + + Make a plot: + + >>> p = pl.figure() + >>> p = pl.axes([0.1,0.3,0.85,0.65]) + >>> p = pl.plot(pgram[0],pgram[1],'k-') + >>> p = pl.xlim(2.2,2.8) + >>> p = pl.ylabel('Amplitude') + >>> p = pl.axes([0.1,0.1,0.85,0.2]) + >>> p = pl.plot(pgram[0][:-1],np.diff(pgram[0]),'k-') + >>> p = pl.xlim(2.2,2.8) + >>> p,q = pl.xlabel('Frequency (c/d)'),pl.ylabel('Frequency resolution $\Delta F$') + + ]]include figure]]timeseries_freqanalyse_06.png] + + @rtype: record array(, 2x1Darray, 1Darray) + @return: parameters and errors(, periodogram, model function) + """ + if model_kwargs is None: + model_kwargs = dict() + #-- initial values + e_f = 0 + freq_diff = np.inf + prev_freq = -np.inf + counter = 0 + + f_max = np.inf + f_min = 0.#-np.inf + + #-- calculate periodogram until frequency precision is + # under 1/10th of correlation corrected version of frequency error + method_kwargs = kwargs.copy() # don't modify the dictionary the user gave + + while freq_diff>e_f/10.: + #-- possibly, we might want to use different periodograms for the first + # calculation than for the zoom in, since some periodograms are faster + # than others but do not have the ability to 'zoom in' (e.g. the FFT) + if freq_diff==np.inf and not isinstance(method,str): + method_ = method[1] + method = method[0] # override method to be a string the next time + #-- calculate periodogram + freqs,ampls = getattr(pergrams,method)(times,signal,**method_kwargs) + f0,fn,df = freqs[0],freqs[-1],freqs[1]-freqs[0] + #-- now use the second method for the zoom-ins from now on + if freq_diff==np.inf and not isinstance(method,str): + method = method_ + #-- extract the frequency: this part should be generalized, but for now, + # it will do: + if method in ['pdm']: + frequency = freqs[np.argmin(ampls)] + #-- instead of going for the highest peak, let's get the most significant one + if prewhiteningorder_snr: + if counter == 0: #we calculate a noise spectrum with a convolution in a 1 d-1 window + windowlength = float(prewhiteningorder_snr_window)/(freqs[1]-freqs[0]) + window = np.ones(int(windowlength))/float(windowlength) + ampls_ = np.concatenate((ampls[::-1],ampls,ampls[::-1])) #we mirror the amplitude spectrum on both ends so the convolution will be better near the edges + noises_ = np.convolve(ampls_, window, 'same') + noises = np.split(noises_,3)[1] #and we recut the resulted convolution to match the original frequency range + freqs_old = np.copy(freqs) + noises_old = np.copy(noises) + else: + noises = np.interp(freqs,freqs_old,noises_old) #we use the original noise spectrum in this narrower windows too, which should save some time, and avoid the problem of having a wider window for the SNR calculation than the width of the zoom-in window + frequency = freqs[np.argmax(ampls/noises)] + else: + frequency = freqs[np.argmax(ampls)] + if full_output and counter==0: + freqs_,ampls_ = freqs,ampls + #-- estimate parameters and calculate a fit, errors and residuals + params = getattr(fit,model)(times,signal,frequency,**model_kwargs) + if hasattr(fit,'e_'+model): + errors = getattr(fit,'e_'+model)(times,signal,params,correlation_correction=correlation_correction) + e_f = errors['e_freq'][-1] + #-- possibly there are not errors defined for this fitting functions + else: + errors = None + #-- optimize inside loop if necessary and if we gained prediction + # value: + if optimize==2: + params_,errors_,gain = fit.optimize(times,signal,params,model) + if gain>0: + params = params_ + logger.info('Accepted optimization (gained %g%%)'%gain) + + #-- improve precision + freq_diff = abs(frequency-prev_freq) + prev_freq = frequency + freq_region = fn-f0 + f0 = max(f_min,frequency-freq_region*scale_region/2.) + fn = min(f_max,frequency+freq_region*scale_region/2.) + df *= scale_df + method_kwargs['f0'] = f0 + method_kwargs['fn'] = fn + method_kwargs['df'] = df + #-- possibilities to escape iterative zoom in + #print '---> {counter}/{max_loops}: freq={frequency} ({f0}-->{fn}/{df}), e_f={e_f}, freq_diff={freq_diff}'.format(**locals()),max(ampls) + if scale_region==0 or scale_df==0: + break + if counter >= max_loops: + logger.error("Frequency precision not reached in %d steps, breaking loop"%(max_loops)) + break + if (fn-f0)/df<5: + logger.error("Frequency precision not reached with stepsize %e , breaking loop"%(df/scale_df)) + break + counter += 1 + #-- optimize parameters outside of loop if necessary: + if optimize==1: + params_,errors_,gain = fit.optimize(times,signal,params,model) + if gain>0: + params = params_ + logger.info('Accepted optimization (gained %g%%)'%gain) + #-- add the errors to the parameter array if possible + if errors is not None: + params = numpy_ext.recarr_join(params,errors) + logger.info("%s model parameters via %s periodogram:\n"%(model,method)+pl.mlab.rec2txt(params,precision=8)) + #-- when full output is required, return parameters, periodogram and fitting + # function + if full_output: + mymodel = getattr(evaluate,model)(times,params) + return params,(freqs_,ampls_),mymodel + else: + return params + + + +def iterative_prewhitening(times,signal,maxiter=1000,optimize=0,method='scargle', + model='sine',full_output=False,stopcrit=None,correlation_correction=True, + prewhiteningorder_snr=False,prewhiteningorder_snr_window=1.,**kwargs): + """ + Fit one or more functions to a timeseries via iterative prewhitening. + + This function will use C{find_frequency} to fit the function parameters to + the original signal (including any previously found parameters), + optimize the parameters if needed, and remove it from the data to search + for a new frequency in the residuals. + + It is always the original signal that is used to fit all parameters again; + B{only the (optimized) frequency is remembered from step to step} (Vanicek's + method). + + You best set C{maxiter} to some sensable value, to hard-limit the number of + frequencies that will be searched for. You can additionally use a C{stopcrit} + and stop looking for frequencies once it is reached. C{stopcrit} should be + a tuple; the first argument is the function to call, the other arguments + are passed to the function, after the mandatory arguments C{times,signal, + modelfunc,allparams,pergram}. See L{stopcrit_scargle_snr} for an example of + such a function. + + By default, the function looks for the highest (or deepest in the case of the pdm + method) peak, but instead it is possible to go for the peak having the highest + SNR before prewhitening by setting C{prewhiteningorder_snr} to True. In this case, + the noise spectrum is calculated using a convolution with a + C{prewhiteningorder_snr_window} wide box. Usage of this is strongly encouraged, + especially combined with L{stopcrit_scargle_snr} as C{stopcrit}. + + @return: parameters, model(, model function) + @rtype: rec array(, ndarray) + """ + residuals = signal.copy() + frequencies = [] + stop_criteria = [] + while maxiter: + #-- compute the next frequency from the residuals + params,pergram,this_fit = find_frequency(times,residuals,method=method, + full_output=True,correlation_correction=correlation_correction, + prewhiteningorder_snr=prewhiteningorder_snr, + prewhiteningorder_snr_window=prewhiteningorder_snr_window,**kwargs) + + #-- do the fit including all frequencies + frequencies.append(params['freq'][-1]) + allparams = getattr(fit,model)(times,signal,frequencies) + + #-- if there's a need to optimize, optimize the last n parameters + if optimize>0: + residuals_for_optimization = residuals + if optimize<=len(params): + model_fixed_params = getattr(evaluate,model)(times,allparams[:-optimize]) + residuals_for_optimization -= model_fixed_params + uparams,e_uparams, gain = fit.optimize(times,residuals_for_optimization,allparams[-optimize:],model) + #-- only accept the optimization if we gained prediction power + if gain>0: + allparams[-optimize:] = uparams + logger.info('Accepted optimization (gained %g%%)'%gain) + + #-- compute the residuals to use in the next prewhitening step + modelfunc = getattr(evaluate,model)(times,allparams) + residuals = signal - modelfunc + + #-- exhaust the counter + maxiter -= 1 + + #-- check stop criterion + if stopcrit is not None: + func = stopcrit[0] + args = stopcrit[1:] + condition,value = func(times,signal,modelfunc,allparams,pergram,*args) + logger.info('Stop criterion (%s): %.3g'%(func.__name__,value)) + stop_criteria.append(value) + if condition: + logger.info('Stop criterion reached') + break + + #-- calculate the errors + e_allparams = getattr(fit,'e_'+model)(times,signal,allparams,correlation_correction=correlation_correction) + + allparams = numpy_ext.recarr_join(allparams,e_allparams) + if stopcrit is not None: + allparams = numpy_ext.recarr_join(allparams,np.rec.fromarrays([stop_criteria],names=['stopcrit'])) + + if full_output: + return allparams,modelfunc + else: + return allparams + + + + +def spectrum_2D(x,y,matrix,weights_2d=None,show_progress=False, + subs_av=True,full_output=False,**kwargs): + """ + Compute a 2D periodogram. + + Rows (first axis) should be spectra chronologically + + x are time points (length N) + y are second axis units (e.g. wavelengths) (length NxM) + + If the periodogram/wavelength combination has a large range, this script + can produce a B{ValueError} or B{MemoryError}. To solve this, you could + iterate this function over a subset of wavelength bins yourself, and write + the results to a file. + + This function also outputs a model, which you can use to subtract from the + data (probably together with the average profile, which is also returned) + and look further for any frequencies:: + + output = spectrum_2D(x,y,matrix,subs_av=True) + new_matrix = matrix - output['avprof'] - output['model' + output2 = spectrum_2D(x,y,new_matrix,subs_av=False) + + If you want to prewhiten a model, you'd probably want to use the same + frequency across the whole line profile. You can hack this by setting + C{f0=frequency} and C{fn=frequency+df} with C{df} the size of the frequency + step. + + B{Example usage}: first we generate some variable line profiles. In this case, + this is just a radial velocity variation + + >>> times = np.linspace(0,150,100) + >>> wavel = np.r_[4500:4600:1.0] + >>> matrix = np.zeros((len(times),len(wavel))) + >>> for i in xrange(len(times)): + ... central_wave = 5*np.sin(2*np.pi/10*times[i]) + ... matrix[i,:] = 1 - 0.5*np.exp( -(wavel-4550-central_wave)**2/10**2) + + Once the line profiles are constructed, we can compute the Fourier transform + of these lines: + + >>> output = spectrum_2D(times,wavel,matrix,method='scargle',model='sine',f0=0.05,fn=0.3,subs_av=True,full_output=True) + + With this output, we can find retrieve the frequency. We plot the periodograms + for each wavelength bin on the left, and on the right the average over all + wavelength bins: + + >>> p = pl.figure() + >>> p = pl.subplot(121) + >>> p = pl.imshow(output['pergram'][1][::-1],extent=[wavel[0],wavel[-1],0.05,0.3],aspect='auto') + >>> p = pl.subplot(122) + >>> p = pl.plot(output['pergram'][1].mean(axis=1),output['pergram'][0],'k-') + >>> p = pl.ylim(0.05,0.3) + + We can then fix the frequency and compute all the parameters. However, we now + choose not to subtract the average profile, but instead use the GLS periodogram + to include the constant + + >>> output = spectrum_2D(times,wavel,matrix,method='gls',model='sine',f0=0.095,fn=0.105,full_output=True) + + >>> p = pl.figure() + >>> p = pl.subplot(221) + >>> p = pl.errorbar(wavel,output['pars']['const'],yerr=output['pars']['e_const'],fmt='ko-') + >>> p = pl.subplot(222) + >>> p = pl.errorbar(wavel,output['pars']['ampl'],yerr=output['pars']['e_ampl'],fmt='ro-') + >>> p = pl.subplot(223) + >>> p = pl.errorbar(wavel,output['pars']['freq'],yerr=output['pars']['e_freq'],fmt='ko-') + >>> p = pl.subplot(224) + >>> p = pl.errorbar(wavel,output['pars']['phase'],yerr=output['pars']['e_phase'],fmt='ro-') + + @return: dict with keys C{avprof} (2D array), C{pars} (rec array), C{model} (1D array), C{pergram} (freqs,2Darray) + @rtype: dict + """ + #-- compute average profile + if subs_av: + matrix_av = np.outer(np.ones(len(matrix)),matrix.mean(axis=0)) + matrix = matrix - matrix_av + else: + matrix_av = 0. + + #-- prepare output of sine-parameters + params = [] + freq_spectrum = [] + mymodel = [] + #-- do frequency analysis + for iwave in range(len(matrix[0])): + signal = matrix[:,iwave] + if weights_2d is not None: + weights = weights_2d[:,iwave] + kwargs['weights'] = weights + + #-- make sure output is always a tuple, in case full output was asked + # we don't want iterative zoom in so set scale_df=0 + out = find_frequency(x,signal,full_output=full_output,scale_df=0,**kwargs) + + #-- add the parameters of this wavelength bin to the list + if full_output: + params.append(out[0]) + freq_spectrum.append(out[1][1]) + mymodel.append(out[2]) + else: + params.append(out) + + #-- prepare output + output = {} + output['avprof'] = matrix_av + output['pars'] = np.hstack(params) + if full_output: + output['pergram'] = out[1][0],np.vstack(freq_spectrum).T + output['model'] = np.vstack(mymodel).T + + return output + +@defaults_pergram +def time_frequency(times,signal,window_width=None,n_windows=100, + window='rectangular',detrend=None,**kwargs): + """ + Short Time (Fourier) Transform. + + Slide a window through the timeseries, multiply the timeseries with the + window, and perform a Fourier Transform. + + It is best to fix explicitly C{f0}, C{fn} and C{df}, to limit the computation + time! + + Extra kwargs go to L{find_frequency} + + @param n_windows: number of slices + @type n_windows: integer + @param window_width: width of each slice (defaults to T/20) + @type window_width: float + @param detrend: detrending function, accepting times and signal as args + @type detrend: callable + @param window: window function to apply + @type window: string + @return: spectrogram, times used, parameters, errors, points used per slice + @rtype: dict + """ + if window_width is None: + window_width = kwargs.get('window_width',times.ptp()/20.) + #-- cut light curve until window fits exactly + stft_times = np.linspace(times[0]+window_width/2.,times[-1]-window_width/2.,n_windows) + + #-- get f0,fn and df to fix in all computations. If they are not given, they + # are set to default values by the decorator + f0 = kwargs.pop('f0') + fn = kwargs.pop('fn') + df = kwargs.pop('df') + nyq_stat = kwargs.pop('nyq_stat',fn) + + #-- prepare arrays for parameters, points and spectrum + pars = [] + pnts = np.zeros(n_windows) + spec = None + #-- compute the periodogram for each slice + for i,t in enumerate(stft_times): + region = (abs(times-t) <= (window_width/2.)) + times_ = times[region] + signal_ = signal[region] + if detrend: + times_,signal_ = detrend(times_,signal_) + pnts[i] = len(times_) + if len(times_)>1: + output = find_frequency(times_,signal_,full_output=True,f0=f0,fn=fn,df=df,nyq_stat=nyq_stat,scale_df=0,**kwargs) + if spec is None: + spec = np.ones((n_windows,len(output[1][1]))) + pars.append(output[0]) + spec[i,:len(output[1][1])] = output[1][1] + else: + nanpars = np.rec.array(np.nan*np.ones(len(pars[-1].dtype.names)),dtype=pars[-1].dtype) + pars.append(nanpars) + #try: + # pars.append(np.nan*pars[-1]) + #except: + # print pars + # print pars[-1] + # print pars[-1].dtype + # print 0*pars[-1] + # print np.nan*pars[-1] + # raise + spec[i] = np.nan + out = {} + out['times'] = stft_times + out['pars'] = np.hstack(pars) + out['pergram'] = (output[1][0],spec) + return out + +#{ Convenience stop-criteria + +def stopcrit_scargle_prob(times,signal,modelfunc,allparams,pergram,crit_value): + """ + Stop criterium based on probability. + """ + value = pergrams.scargle_probability(pergram[1].max(),times,pergram[0]) + print(value) + return value>crit_value,value + +def stopcrit_scargle_snr(times,signal,modelfunc,allparams,pergram,crit_value,width=6.): + """ + Stop criterium based on signal-to-noise ratio. + """ + width = width/2. + argmax = np.argmax(pergram[1]) + ampls = pergram[1] + start = max(0,pergram[0][argmax]-width) + stop = min(pergram[0][argmax]+width,pergram[0][-1]) + if start==0: + stop += width-pergram[0][argmax] + if stop==pergram[0][-1]: + start = pergram[0][-1]-pergram[0][argmax]+width + ampls = ampls[(start<=pergram[0]) & (pergram[0]<=stop)] + value = pergram[1][argmax]/ampls.mean() + return value>> periods = linspace(1./p[0][-1],1./p[0][0],2*len(p[0])) + >>> ampl = interpol.linear_interpolation(1./p[0][::-1],p[1][::-1],periods) + >>> ampl = where(isnan(ampl),hstack([ampl[1:],ampl[:1]]),ampl) + + @param frequencies: frequency array + @type frequencies: numpy 1d array + @param power: power spectrum + @type power: numpy 1d array + @keyword max_step: maximum time shift + @type max_step: float + @keyword interval: tuple of frequencies (start, end) + @type interval: tuple of floats + @keyword threshold: high cut off value (in units of sample mean) + @type threshold: float + @return: domain of autocorrelation and autocorrelation + @rtype: (ndarray,ndarray) + """ + #-- cut out the interesting part of the spectrum + if interval is not (): + cut_out = frequencies[(interval[0]<=frequencies) & (frequencies<=interval[1])] + start_freq = cut_out[0] + stop_freq = cut_out[-1] + start = np.argmin(abs(frequencies-interval[0])) + stop = np.argmin(abs(frequencies-interval[1])) + else: + start = 1 + stop = len(frequencies)-1 + + #-- compute the frequency step + Dfreq = (frequencies[start+1] - frequencies[start+0]) + max_step = int(max_step/Dfreq) + autocorr = [] + variance = [] + mean = np.average(power) + + #-- cut of high peaks at a signal to noise level of 6 + # cut of low values at a signal to noise level of 1 + if threshold is not None: + power[power>=(threshold*mean)] = threshold*mean + #power = where(less(power, mean), mean, power) + + #-- normalize power as to arrive at the AUTO-correlation function. + mean = np.average(power) + power = power-mean + + #-- compute autocorrelation. If nessecary, take border effects into + # account. + for i in range(2,max_step): + end_s = min(len(power), stop+i) + end_o = start + end_s - (start+i) + original = power[start :end_o] + shifted = power[start+i:end_s] + if len(original) < 10: + logger.error("AUTOCORR: too few points left in interval, breaking up.") + break + if method==1: + autocorr.append(np.average(original*shifted)) + else: + autocorr.append(np.correlate(original,shifted)) + variance.append(np.average(original*original)) + + domain = np.arange(2,max_step) * Dfreq + domain = domain[0:len(autocorr)] + + #-- normalize + autocorr = np.array(autocorr)/np.array(variance) + logger.info("Computed autocorrelation in interval %s with maxstep %s"%(interval,max_step)) + return domain, autocorr + +if __name__=="__main__": + import doctest + import pylab as pl + import sys + from ivs.aux import argkwargparser + from ivs.inout import ascii + + #-- if no arguments are given, we just do a test run + if not sys.argv[1:]: + doctest.testmod() + pl.show() + sys.exit() + + #-- if arguments are given, we assume the user wants to run one of the + # functions with arguments given in the command line + # EXAMPLES: + # $:> python freqanalyse.py find_frequency infile=test.dat full_output=True + # $:> python freqanalyse.py time_frequency infile=test.dat full_output=True + else: + method,args,kwargs = argkwargparser.parse() + print("Running method %s with arguments %s and keyword arguments %s"%(method,args,kwargs)) + if '--help' in args or 'help' in args or 'help' in kwargs: + sys.exit() + full_output = kwargs.get('full_output',False) + times,signal = ascii.read2array(kwargs.pop('infile')).T[:2] + out = globals()[method](times,signal, **kwargs) + + #-- when find_frequency is called + if method=='find_frequency' and full_output: + print(pl.mlab.rec2txt(out[0],precision=8)) + pl.figure() + pl.subplot(211) + pl.plot(out[1][0],out[1][1],'k-') + pl.subplot(212) + pl.plot(times,signal,'ko',ms=2) + pl.plot(times,out[2],'r-',lw=2) + pl.show() + elif method=='find_frequency': + print(pl.mlab.rec2txt(out)) + + #-- when time_frequency is called + elif method=='time_frequency': + print(pl.mlab.rec2txt(out['pars'],precision=8)) + pl.figure() + pl.imshow(out['pergram'][1].T[::-1],aspect='auto',extent=[out['times'][0],out['times'][-1],out['pergram'][0][0],out['pergram'][0][-1]]) + + + pl.show() + diff --git a/timeseries/keplerorbit.py b/timeseries/keplerorbit.py index 9dab27d25..7ae93a49d 100644 --- a/timeseries/keplerorbit.py +++ b/timeseries/keplerorbit.py @@ -135,7 +135,7 @@ def radial_velocity(parameters,times=None,theta=None,itermax=8): """ Evaluate Radial velocities due to kepler orbit. - + These parameters define the Keplerian orbit if you give times points (C{times}, days): 1. Period of the system (days) 2. time of periastron passage T0 (not x0!) (HJD) @@ -144,7 +144,7 @@ def radial_velocity(parameters,times=None,theta=None,itermax=8): of the orbit within its own plane (radians) 5. the semiamplitude of the velocity curve (km/s) 6. systemic velocity RV0 (RV of centre of mass of system) (km/s) - + These parameters define the Keplerian orbit if you give angles (C{theta}, radians): 1. Period of the system (days) 2. eccentricity @@ -153,13 +153,13 @@ def radial_velocity(parameters,times=None,theta=None,itermax=8): orbit within in its own plane (radians) 5. inclination of the orbit (radians) 6. systemic velocity RV0 (RV of centre of mass of system) (km/s) - + The periastron passage T0 can be derived via x0 by calculating - + T0 = x0/(2pi*Freq) + times[0] - + See e.g. p 41,42 of Hilditch, 'An Introduction To Close Binary Stars' - + @parameter parameters: parameters of Keplerian orbit (dependent on input) @type parameters: iterable @parameter times: observation times (days) @@ -171,7 +171,7 @@ def radial_velocity(parameters,times=None,theta=None,itermax=8): @return: fitted radial velocities (km/s) @rtype: ndarray """ - + #-- in the case time points are given: if times is not None: #-- expand parameters and calculate x0 @@ -182,30 +182,30 @@ def radial_velocity(parameters,times=None,theta=None,itermax=8): E,true_an = true_anomaly(times*2*np.pi*freq-x0,e,itermax=itermax) #-- evaluate Keplerian radial velocity orbit RVfit = RV0 + K*(e*np.cos(omega) + np.cos(true_an+omega)) - + elif theta is not None: P,e,a,omega,i,RV0 = parameters P = conversions.convert('d','s',P) a = conversions.convert('au','m',a) K = 2*np.pi*a*np.sin(i)/ (P*np.sqrt(1.-e**2)) RVfit = RV0 + K*(e*np.cos(omega) + np.cos(theta+omega))/1000. - + return RVfit def orbit_in_plane(times,parameters,component='primary',coordinate_frame='polar'): """ Construct an orbit in the orbital plane. - + Give times in days - + Parameters contains: 1. period (days) 2. eccentricity 3. semi-major axis (au) 4. time of periastron passage T0 (not x0!) (HJD) - + Return r (m) and theta (radians) - + @param times: times of observations (days) @type times: array @param parameters: list of parameters (P,e,a,T0) @@ -223,7 +223,7 @@ def orbit_in_plane(times,parameters,component='primary',coordinate_frame='polar' a = conversions.convert('au','m',a) T0 = conversions.convert('d','s',T0) times = conversions.convert('d','s',times) - + n = 2*np.pi/P ma = n*(times-T0) E,theta = true_anomaly(ma,e) @@ -231,11 +231,11 @@ def orbit_in_plane(times,parameters,component='primary',coordinate_frame='polar' PR = r*np.sin(theta) #PR[E>0] *= -1 #theta[E>0] *= -1 - + #-- correct angles if secondary component is calculated if 'sec' in component.lower(): theta += np.pi - + if coordinate_frame=='polar': return r,theta elif coordinate_frame=='cartesian': @@ -244,7 +244,7 @@ def orbit_in_plane(times,parameters,component='primary',coordinate_frame='polar' def velocity_in_plane(times,parameters,component='primary',coordinate_frame='polar'): """ Calculate the velocity in the orbital plane. - + @param times: times of observations (days) @type times: array @param parameters: list of parameters (P,e,a,T0) @@ -262,13 +262,13 @@ def velocity_in_plane(times,parameters,component='primary',coordinate_frame='pol P,e,a,T0 = parameters P = conversions.convert('d','s',P) a = conversions.convert('au','m',a) - + #-- compute rdot and thetadot l = r*(1+e*np.cos(theta)) L = 2*np.pi*a**2/P*np.sqrt(1-e**2) rdot = L/l*e*np.sin(theta) thetadot = L/r**2 - + #-- convert to the right coordinate frame if coordinate_frame=='polar': return rdot,thetadot @@ -280,15 +280,15 @@ def velocity_in_plane(times,parameters,component='primary',coordinate_frame='pol def project_orbit(r,theta,parameters): """ Project an orbit onto the plane of the sky. - + Parameters contains the Euler angles: 1. omega: the longitude of periastron gives the orientation of the orbit within in its own plane (radians) 2. Omega: PA of ascending node (radians) 3. i: inclination (radians), i=pi/2 is edge on - + Returns x,y (orbit in plane of the sky) and z - + See Hilditch p41 for a sketch of the coordinates. The difference with this is that all angles are inverted. In this approach, North is in the positive X direction, East is in the negative Y direction. @@ -298,13 +298,13 @@ def project_orbit(r,theta,parameters): y = r*(np.sin(Omega)*np.cos(theta+omega) + np.cos(+Omega)*np.sin(theta+omega)*np.cos(i)) z = r*(np.sin(theta+omega)*np.sin(i)) return x,y,z - - + + def orbit_on_sky(times,parameters,distance=None,component='primary'): """ Construct an orbit projected on the sky. - + Parameters contains: 1. period (days) 2. eccentricity @@ -314,12 +314,12 @@ def orbit_on_sky(times,parameters,distance=None,component='primary'): orbit within in its own plane (radians) 6. Omega: PA of ascending node (radians) 7. i: inclination (radians), i=pi/2 is edge on - + You can give an extra parameter 'distance' as a tuple (value,'unit'). This will be used to convert the distances to angular scale (arcsec). - + Else, this function returns the distances in AU. - + See Hilditch p41 for a sketch of the coordinates. The difference with this is that all angles are inverted. In this approach, North is in the positive X direction, East is in the negative Y direction. @@ -330,7 +330,7 @@ def orbit_on_sky(times,parameters,distance=None,component='primary'): r,theta = orbit_in_plane(times,pars_in_plane,component=component) #-- and project in onto the sky according to the euler angles x,y,z = project_orbit(r,theta,euler_angles) - + #-- if necessary, convert the true distance to angular scale if distance is not None: d = conversions.convert(distance[1],'m',distance[0]) @@ -340,18 +340,18 @@ def orbit_on_sky(times,parameters,distance=None,component='primary'): return x,y,z else: return x/au,y/au,z/au - - + + def true_anomaly(M,e,itermax=8): """ Calculation of true and eccentric anomaly in Kepler orbits. - + M is the phase of the star, e is the eccentricity - + See p.39 of Hilditch, 'An Introduction To Close Binary Stars' - + @parameter M: phase @type M: float @parameter e: eccentricity @@ -385,7 +385,7 @@ def true_anomaly(M,e,itermax=8): def calculate_phase(T,e,omega,pshift=0): """ Compute orbital phase from true anomaly T - + @parameter T: true anomaly @type T: float @parameter omega: argument of periastron (radians) @@ -401,18 +401,18 @@ def calculate_phase(T,e,omega,pshift=0): M = E - e*np.sin(E) return (M+omega)/(2.0*np.pi) - 0.25 + pshift - - + + def calculate_critical_phases(omega,e,pshift=0): """ Compute phase of superior conjunction and periastron passage. - + Example usage: >>> omega = np.pi/4.0 >>> e = 0.3 >>> print calculate_critical_phases(omega,e) (-0.125, -0.057644612788576133, -0.42054512757020118, -0.19235538721142384, 0.17054512757020118) - + @parameter omega: argument of periastron (radians) @type omega: float @parameter e: eccentricity @@ -435,10 +435,10 @@ def calculate_critical_phases(omega,e,pshift=0): def eclipse_separation(e,omega): """ Calculate the eclipse separation between primary and secondary in a light curve. - + Minimum separation at omega=pi Maximum spearation at omega=0 - + @parameter e: eccentricity @type e: float @parameter omega: argument of periastron (radians) @@ -453,9 +453,9 @@ def eclipse_separation(e,omega): def omega_from_eclipse_separation(separation,e): """ Caculate the argument of periastron from the eclipse separation and eccentricity. - + separation in phase units. - + @parameter separation: separation in phase units (0.5 is half) @type separation: float @parameter e: eccentricity @@ -473,7 +473,7 @@ def omega_from_eclipse_separation(separation,e): else: omega = optimize.bisect(lambda x:separation-eclipse_separation(e,x),maxsep_omega,minsep_omega) return omega - + #} #{ Kepler's laws @@ -481,15 +481,15 @@ def omega_from_eclipse_separation(separation,e): def third_law(M=None,a=None,P=None): """ Kepler's third law. - + Give two quantities, derived the third. - + M = total mass system (solar units) a = semi-major axis (au) P = period (d) - + >>> print third_law(M=1.,a=1.) - 365.256891359 + 365.256891359 >>> print third_law(a=1.,P=365.25) 1.00003773538 >>> print third_law(M=1.,P=365.25) @@ -501,18 +501,17 @@ def third_law(M=None,a=None,P=None): P *= (24*3600.) if M is not None: M *= Msol - + if M is None: return 4*np.pi**2*a**3/P**2/GG/Msol if a is None: return (GG*M*P**2/(4*np.pi**2))**(1./3.)/au if P is None: return np.sqrt(4*np.pi**2*a**3/(GG*M))/(24*3600.) - + if __name__=="__main__": import doctest import pylab as pl doctest.testmod() pl.show() - \ No newline at end of file diff --git a/timeseries/pergrams.py b/timeseries/pergrams.py index 4fbb8f3b9..ce05944b7 100644 --- a/timeseries/pergrams.py +++ b/timeseries/pergrams.py @@ -66,7 +66,7 @@ ... clock.append(time.time()-c0) ... freqs.append(len(freq)) ... p = pl.plot(freqs,clock,'o-',label=tech) -... print tech,np.polyfit(np.log10(np.array(freqs)),np.log10(np.array(clock)),1) +... print(tech,np.polyfit(np.log10(np.array(freqs)),np.log10(np.array(clock)),1)) >>> #p = pl.legend(loc='best',fancybox=True) >>> #q = p.get_frame().set_alpha(0.5) >>> #p,q = pl.xlabel('Number of frequencies'),pl.ylabel('Seconds') @@ -140,24 +140,17 @@ """ import logging import numpy as np -from numpy import cos,sin,pi +from numpy import pi from scipy.special import jn from ivs.aux.decorators import make_parallel from ivs.aux import loggers from ivs.aux import termtools -from ivs.timeseries.decorators import parallel_pergram,defaults_pergram,getNyquist - -import pyscargle -import pyscargle_single -import pyfasper -import pyfasper_single -import pyclean -import pyGLS -import pyKEP -import pydft -import multih -import deeming as fdeeming -import eebls +from ivs.timeseries.decorators import (parallel_pergram, defaults_pergram, + getNyquist) + +from ivs.timeseries import (pyscargle, pyscargle_single, pyfasper, + pyfasper_single, pyclean, pyGLS, pyKEP, pydft, + multih, deeming as fdeeming, eebls) logger = logging.getLogger("TS.PERGRAMS") @@ -172,34 +165,34 @@ def scargle(times, signal, f0=None, fn=None, df=None, norm='amplitude', weights=None, single=False): """ Scargle periodogram of Scargle (1982). - + Several options are available (possibly combined): 1. weighted Scargle 2. Amplitude spectrum 3. Distribution power spectrum 4. Traditional power spectrum 5. Power density spectrum (see Kjeldsen, 2005 or Carrier, 2010) - + This definition makes use of a Fortran-routine written by Jan Cuypers, Conny Aerts and Peter De Cat. A slightly adapted version is used for the weighted version (adapted by Pieter Degroote). - + Through the option "norm", it's possible to norm the periodogram as to get a periodogram that has a known statistical distribution. Usually, this norm is the variance of the data (NOT of the noise or residuals, see Schwarzenberg- Czerny 1998!). - + Also, it is possible to retrieve the power density spectrum in units of [ampl**2/frequency]. In this routine, the normalisation constant is taken to be the total time span T. Kjeldsen (2005) chooses to multiply the power by the 'effective length of the observing run', which is calculated as the reciprocal of the area under spectral window (in power, and take 2*Nyquist as upper frequency value). - + REMARK: this routine does B{not} automatically remove the average. It is the user's responsibility to do this adequately: e.g. subtract a B{weighted} average if one computes the weighted periodogram!! - + @param times: time points @type times: numpy array @param signal: observations @@ -216,7 +209,7 @@ def scargle(times, signal, f0=None, fn=None, df=None, norm='amplitude', @type df: float @return: frequencies, amplitude spectrum @rtype: array,array - """ + """ if single: pyscargle_ = pyscargle_single else: pyscargle_ = pyscargle @@ -226,7 +219,7 @@ def scargle(times, signal, f0=None, fn=None, df=None, norm='amplitude', nf=int((fn-f0)/df+0.001)+1 f1=np.zeros(nf,'d');s1=np.zeros(nf,'d') ss=np.zeros(nf,'d');sc=np.zeros(nf,'d');ss2=np.zeros(nf,'d');sc2=np.zeros(nf,'d') - + #-- run the Fortran routine if weights is None: f1,s1=pyscargle_.scar2(signal,times,f0,df,f1,s1,ss,sc,ss2,sc2) @@ -234,8 +227,8 @@ def scargle(times, signal, f0=None, fn=None, df=None, norm='amplitude', w=np.array(weights,'float') logger.debug('Weighed scargle') f1,s1=pyscargle_.scar3(signal,times,f0,df,f1,s1,ss,sc,ss2,sc2,w) - - #-- search for peaks/frequencies/amplitudes + + #-- search for peaks/frequencies/amplitudes if not s1[0]: s1[0]=0. # it is possible that the first amplitude is a none-variable fact = np.sqrt(4./n) if norm =='distribution': # statistical distribution @@ -243,7 +236,7 @@ def scargle(times, signal, f0=None, fn=None, df=None, norm='amplitude', elif norm == "amplitude": # amplitude spectrum s1 = fact * np.sqrt(s1) elif norm == "density": # power density - s1 = fact**2 * s1 * T + s1 = fact**2 * s1 * T return f1, s1 @@ -254,9 +247,9 @@ def scargle(times, signal, f0=None, fn=None, df=None, norm='amplitude', def fasper(times,signal, f0=None, fn=None, df=None, single=True, norm='amplitude'): """ Fasper periodogram from Numerical Recipes. - + Normalisation here is not correct!! - + @param times: time points @type times: numpy array @param signal: observations @@ -295,7 +288,7 @@ def fasper(times,signal, f0=None, fn=None, df=None, single=True, norm='amplitude elif norm == "amplitude": # amplitude spectrum wk2 = fact * np.sqrt(wk2) elif norm == "density": # power density - wk2 = fact**2 * wk2 * T + wk2 = fact**2 * wk2 * T if f0 is not None: keep = f0>> times_ = np.linspace(0,150,1000) - >>> times = np.array([times_[i] for i in xrange(len(times_)) if (i%10)>7]) + >>> times = np.array([times_[i] for i in range(len(times_)) if (i%10)>7]) >>> signal = np.sin(2*pi/10*times) + np.random.normal(size=len(times)) - + Compute the scargle periodogram as a reference, and compare with the the CLEAN periodogram with different gains. - + >>> niter,freqbins = 10,[0,1.2] >>> p1 = scargle(times,signal,fn=1.2,norm='amplitude',threads=2) >>> p2 = clean(times,signal,fn=1.2,gain=1.0,niter=niter,freqbins=freqbins) >>> p3 = clean(times,signal,fn=1.2,gain=0.1,niter=niter,freqbins=freqbins) - + Make a figure of the result: - + >>> p=pl.figure() >>> p=pl.plot(p1[0],p1[1],'k-',label="Scargle") >>> p=pl.plot(p2[0],p2[1],'r-',label="Clean (g=1.0)") >>> p=pl.plot(p3[0],p3[1],'b-',label="Clean (g=0.1)") >>> p=pl.legend() - + ]]include figure]]ivs_timeseries_pergrams_clean.png] - + @keyword freqbins: frequency bins for clean computation @type freqbins: list or array @keyword niter: number of iterations @@ -443,17 +436,17 @@ def clean(times,signal, f0=None, fn=None, df=None, freqbins=None, niter=10., n = len(times) if freqbins is None: freqbins = [f0,fn] - + startfreqs = np.array(freqbins[0::2]) endfreqs = np.array(freqbins[1::2]) nbins = len(freqbins)-1 - + nf = int(fn/df) - + #-- do clean computation, seems not so straightforward to thread cleaning f,wpow,wpha = pyclean.main_clean(times,signal,fn,nf,gain,niter,nbins,\ startfreqs,endfreqs) - + return f,wpow @@ -470,18 +463,18 @@ def clean(times,signal, f0=None, fn=None, df=None, freqbins=None, niter=10., def schwarzenberg_czerny(times, signal, f0=None, fn=None, df=None, nh=2, mode=1): """ Multi harmonic periodogram of Schwarzenberg-Czerny (1996). - + This periodogram follows an F-distribution, so it is possible to perform hypothesis testing. - + If the number of the number of harmonics is 1, then this peridogram reduces to the Lomb-Scargle periodogram except for its better statistic behaviour. This script uses a Fortran procedure written by Schwarzenberg-Czerny. - + Modes: - mode=1: AoV Fisher-Snedecor F(df1,df2) statistic - mode=2: total power fitted in all harmonics for mode=2 - + @param times: list of observations times @type times: numpy 1d array @param signal: list of observations @@ -504,21 +497,21 @@ def schwarzenberg_czerny(times, signal, f0=None, fn=None, df=None, nh=2, mode=1) th = np.zeros(len(frequencies)) #-- use Fortran subroutine th = multih.sfou(n,times,signal,ll,f0,df,nh,mode,th) - + # th *= 0.5 seemed necessary to fit the F-distribution - + return frequencies,th - + def DFTpower(time, signal, f0=None, fn=None, df=None, full_output=False): """ - Computes the modulus square of the fourier transform. - + Computes the modulus square of the fourier transform. + Unit: square of the unit of signal. Time points need not be equidistant. The normalisation is such that a signal A*sin(2*pi*nu_0*t) gives power A^2 at nu=nu_0 - - @param time: time points [0..Ntime-1] + + @param time: time points [0..Ntime-1] @type time: ndarray @param signal: signal [0..Ntime-1] @type signal: ndarray @@ -529,25 +522,25 @@ def DFTpower(time, signal, f0=None, fn=None, df=None, full_output=False): @param df: see f0 @type df: float @return: power spectrum of the signal - @rtype: array + @rtype: array """ freqs = np.arange(f0,fn,df) Ntime = len(time) Nfreq = int(np.ceil((fn-f0)/df)) - + A = np.exp(1j*2.*pi*f0*time) * signal B = np.exp(1j*2.*pi*df*time) - ft = np.zeros(Nfreq, complex) + ft = np.zeros(Nfreq, complex) ft[0] = A.sum() for k in range(1,Nfreq): A *= B ft[k] = np.sum(A) - + if full_output: return freqs,ft**2*4.0/Ntime**2 else: - return freqs,(ft.real**2 + ft.imag**2) * 4.0 / Ntime**2 + return freqs,(ft.real**2 + ft.imag**2) * 4.0 / Ntime**2 def DFTpower2(time, signal, freqs): @@ -567,7 +560,7 @@ def DFTpower2(time, signal, freqs): @return: power spectrum. Unit: square of unit of 'signal' @rtype: ndarray """ - + powerSpectrum = np.zeros(len(freqs)) for i, freq in enumerate(freqs): @@ -578,19 +571,19 @@ def DFTpower2(time, signal, freqs): return(powerSpectrum) - + def DFTscargle(times, signal,f0,fn,df): - + """ Compute Discrete Fourier Transform for unevenly spaced data ( Scargle, 1989). - + Doesn't work yet! - + It is recommended to start f0 at 0. It is recommended to stop fn at the nyquist frequency - + This makes use of a FORTRAN algorithm written by Scargle (1989). - + @param times: observations times @type times: numpy array @param signal: observations @@ -608,7 +601,7 @@ def DFTscargle(times, signal,f0,fn,df): #df *= 2*np.pi #-- initialize nfreq = int((fn-f0)/(df)) - print "Nfreq=",nfreq + print("Nfreq=",nfreq) tzero = times[0] si = 1. lfreq = 2*nfreq+1 @@ -619,89 +612,89 @@ def DFTscargle(times, signal,f0,fn,df): w = np.zeros(mm) wz = df #pi/(nn+dt) nn = len(times) - + #-- calculate DFT ftrx,ftix,om,w = pydft.ft(signal,times,wz,nfreq,si,lfreq,tzero,df,ftrx,ftix,om,w,nn,mm) - + if f0==0: ftrx[1:] *= np.sqrt(2) ftix[1:] *= np.sqrt(2) w[1:] *= np.sqrt(2) - + cut_off = len(w) for i in range(0,len(w))[::-1]: if w[i] != 0: cut_off = i+1;break - + om = om[:cut_off] ftrx = ftrx[:cut_off] ftix = ftix[:cut_off] w = w[:cut_off] - + # norm amplitudes for easy inversion T = times[-1]-times[0] N = len(times) - + w *= T/(2.*N) ftrx *= T/(2.*N) ftix *= T/(2.*N) - + return om*2*np.pi,w,ftrx,ftix - - + + def FFTpower(signal, timestep): """ Computes power spectrum of an equidistant time series 'signal' using the FFT algorithm. The length of the time series need not - be a power of 2 (zero padding is done automatically). + be a power of 2 (zero padding is done automatically). Normalisation is such that a signal A*sin(2*pi*nu_0*t) gives power A^2 at nu=nu_0 (IF nu_0 is in the 'freq' array) - + @param signal: the time series [0..Ntime-1] @type signal: ndarray @param timestep: time step fo the equidistant time series @type timestep: float @return: frequencies and the power spectrum @rtype: array,array - + """ - - # Compute the FFT of a real-valued signal. If N is the number + + # Compute the FFT of a real-valued signal. If N is the number # of points of the original signal, 'Nfreq' is (N/2+1). - + fourier = np.fft.rfft(signal) Ntime = len(signal) Nfreq = len(fourier) - + # Compute the power - + power = np.abs(fourier)**2 * 4.0 / Ntime**2 - + # Compute the frequency array. # First compute an equidistant array that goes from 0 to 1 (included), # with in total as many points as in the 'fourier' array. # Then rescale the array that it goes from 0 to the Nyquist frequency # which is 0.5/timestep - + freq = np.arange(float(Nfreq)) / (Nfreq-1) * 0.5 / timestep - + # That's it! - + return (freq, power) - - - + + + def FFTpowerdensity(signal, timestep): - + """ Computes the power density of an equidistant time series 'signal', using the FFT algorithm. The length of the time series need not - be a power of 2 (zero padding is done automatically). + be a power of 2 (zero padding is done automatically). @param signal: the time series [0..Ntime-1] @type signal: ndarray @@ -711,35 +704,35 @@ def FFTpowerdensity(signal, timestep): @rtype: array,array """ - - # Compute the FFT of a real-valued signal. If N is the number + + # Compute the FFT of a real-valued signal. If N is the number # of points of the original signal, 'Nfreq' is (N/2+1). - + fourier = np.fft.rfft(signal) Ntime = len(signal) Nfreq = len(fourier) - + # Compute the power density - + powerdensity = np.abs(fourier)**2 / Ntime * timestep - + # Compute the frequency array. # First compute an equidistant array that goes from 0 to 1 (included), # with in total as many points as in the 'fourier' array. # Then rescale the array that it goes from 0 to the Nyquist frequency # which is 0.5/timestep - + freq = np.arange(float(Nfreq)) / (Nfreq-1) * 0.5 / timestep - + # That's it! - + return (freq, powerdensity) - - - + + + @@ -749,19 +742,19 @@ def weightedpower(time, signal, weight, freq): Compute the weighted power spectrum of a time signal. For each given frequency a weighted sine fit is done using chi-square minimization. - - @param time: time points [0..Ntime-1] + + @param time: time points [0..Ntime-1] @type time: ndarray @param signal: observations [0..Ntime-1] @type signal: ndarray @param weight: 1/sigma_i^2 of observation @type weight: ndarray - @param freq: frequencies [0..Nfreq-1] for which the power + @param freq: frequencies [0..Nfreq-1] for which the power needs to be computed @type freq: ndarray @return: weighted power [0..Nfreq-1] @rtype: array - + """ result = np.zeros(len(freq)) @@ -783,11 +776,11 @@ def weightedpower(time, signal, weight, freq): else: result[i] = np.sum(signal)/len(signal) - return(result) - - - - + return(result) + + + + @@ -801,18 +794,18 @@ def pdm(times, signal,f0=None,fn=None,df=None,Nbin=5,Ncover=2, D=0,forbit=None,asini=None,e=None,omega=None,nmax=10): """ Phase Dispersion Minimization of Jurkevich-Stellingwerf (1978) - + This definition makes use of a Fortran routine written by Jan Cuypers and Conny Aerts. - + Inclusion of linear frequency shift by Pieter Degroote (see Cuypers 1986) - + Inclusion of binary orbital motion by Pieter Degroote (see Shibahashi & Kurtz 2012). When orbits are added, times must be in days, then asini is in AU. - + For circular orbits, give only forbit and asini. - + @param times: time points @type times: numpy array @param signal: observations @@ -834,14 +827,14 @@ def pdm(times, signal,f0=None,fn=None,df=None,Nbin=5,Ncover=2, """ T = times.ptp() n = len(times) - + #-- initialize variables xvar = signal.std()**2. xx = (n-1) * xvar nf = int((fn-f0) / df + 0.001) + 1 f1 = np.zeros(nf,'d') s1 = np.zeros(nf,'d') - + #-- use Fortran subroutine #-- Normal PDM if D is None and asini is None: @@ -862,11 +855,11 @@ def pdm(times, signal,f0=None,fn=None,df=None,Nbin=5,Ncover=2, tau = -np.sum(bns*np.sin(omega)) f1, s1 = pyscargle.justel4(signal,times,f0,df,Nbin,Ncover,xvar,xx,asini, forbit,e,omega,ksins,thns,tau,f1,s1,n,nf,nmax) - - + + #-- it is possible that the first computed value is a none-variable - if not s1[0]: s1[0] = 1. - + if not s1[0]: s1[0] = 1. + return f1, s1 @@ -891,23 +884,23 @@ def box(times, signal, f0=None, fn=None, df=None, Nbin=10, qmi=0.005, qma=0.75 ) [ see Kovacs, Zucker & Mazeh 2002, A&A, Vol. 391, 369 ] - This is the slightly modified version of the original BLS routine - by considering Edge Effect (EE) as suggested by + This is the slightly modified version of the original BLS routine + by considering Edge Effect (EE) as suggested by Peter R. McCullough [ pmcc@stsci.edu ]. - This modification was motivated by considering the cases when - the low state (the transit event) happened to be devided between - the first and last bins. In these rare cases the original BLS - yields lower detection efficiency because of the lower number of + This modification was motivated by considering the cases when + the low state (the transit event) happened to be devided between + the first and last bins. In these rare cases the original BLS + yields lower detection efficiency because of the lower number of data points in the bin(s) covering the low state. For further comments/tests see www.konkoly.hu/staff/kovacs.html - + Transit fraction and precision are given by nb,qmi and qma - + Remark: output parameter parameter contains: [frequency,depth,transit fraction width,fractional start, fraction end] - + @param times: observation times @type times: numpy 1D array @param signal: observations @@ -932,15 +925,15 @@ def box(times, signal, f0=None, fn=None, df=None, Nbin=10, qmi=0.005, qma=0.75 ) T = times.ptp() u = np.zeros(n) v = np.zeros(n) - + #-- frequency vector and variables nf = (fn-f0)/df if f0<2./T: f0=2./T - + #-- calculate EEBLS spectrum and model parameters power,depth,qtran,in1,in2 = eebls.eebls(times,signal,u,v,nf,f0,df,Nbin,qmi,qma,n) frequencies = np.linspace(f0,fn,nf) - + #-- to return parameters of fit, do this: # pars = [max_freq,depth,qtran+(1./float(nb)),(in1-1)/float(nb),in2/float(nb)] return frequencies,power @@ -956,7 +949,7 @@ def kepler(times,signal, f0=None, fn=None, df=None, e0=0., en=0.91, de=0.1, errors=None, wexp=2, x00=0.,x0n=359.9): """ Keplerian periodogram of Zucker et al (2010). - + @param times: observation times @type times: numpy 1D array @param signal: observations @@ -985,7 +978,7 @@ def kepler(times,signal, f0=None, fn=None, df=None, e0=0., en=0.91, de=0.1, if errors is None: errors = np.ones(n) maxstep = int((fn-f0)/df+1) - + #-- initialize parameters f1 = np.zeros(maxstep) #-- frequency s1 = np.zeros(maxstep) #-- power @@ -993,7 +986,7 @@ def kepler(times,signal, f0=None, fn=None, df=None, e0=0., en=0.91, de=0.1, l1 = np.zeros(maxstep) #-- power LS s2 = np.zeros(maxstep) #-- power Kepler k2 = np.zeros(6) #-- parameters for Kepler orbit - + #-- calculate Kepler periodogram pyKEP.kepler(times+0,signal+0,errors,f0,fn,df,wexp,e0,en,de,\ x00,x0n,f1,s1,p1,l1,s2,k2) @@ -1007,17 +1000,17 @@ def Zwavelet(time, signal, freq, position, sigma=10.0): """ Weighted Wavelet Z-transform of Foster (1996) - + Computes "Weighted Wavelet Z-Transform" which is a type of time-frequency diagram more suitable for non-equidistant time series. - See: G. Foster, 1996, Astronomical Journal, 112, 1709 - + See: G. Foster, 1996, Astronomical Journal, 112, 1709 + The result can be plotted as a colorimage (imshow in pylab). - + Mind the max, min order for position. It's often useful to try log(Z) and/or different sigmas. - + @param time: time points [0..Ntime-1] @type time: ndarray @param signal: observed data points [0..Ntime-1] @@ -1031,72 +1024,72 @@ def Zwavelet(time, signal, freq, position, sigma=10.0): @type sigma: float @return: Z[0..Npos-1, 0..Nfreq-1]: the Z-transform: time-freq diagram @rtype: array - + """ - + y = np.zeros(3) S = np.zeros([3,3]) Z = np.zeros([len(position),len(freq)]) - + for i in range(len(position)): - + tau = position[i] arg = 2.0*pi*(time-tau) - + for j in range(len(freq)): - + nu = freq[j] - + # Compute statistical weights akin the Morlet wavelet - + weight = np.exp(-(time-tau)**2 * (nu / 2./sigma)**2) W = np.sum(weight) - + # Compute the base functions. A 3rd base function is the constant 1. - + cosine = np.cos(arg*nu) sine = np.sin(arg*nu) - - print arg, nu - print sine, cosine - + + print(arg, nu) + print(sine, cosine) + # Compute the innerproduct of the base functions # phi_0 = 1 (constant), phi_1 = cosine, phi_2 = sine - + S[0,0] = 1.0 S[0,1] = S[1,0] = np.sum(weight * 1.0 * cosine) / W S[0,2] = S[2,0] = np.sum(weight * 1.0 * sine) / W S[1,1] = np.sum(weight * cosine * cosine) / W S[1,2] = S[2,1] = np.sum(weight * cosine * sine) / W S[2,2] = np.sum(weight * sine * sine) / W - print S + print(S) invS = np.linalg.inv(S) - + # Determine the best-fit coefficients y_k of the base functions - + for k in range(3): y[k] = np.sum(weight * 1.0 * signal) / W * invS[k,0] \ + np.sum(weight * cosine * signal) / W * invS[k,1] \ - + np.sum(weight * sine * signal) / W * invS[k,2] - + + np.sum(weight * sine * signal) / W * invS[k,2] + # Compute the best-fit model - + model = y[0] + y[1] * cosine + y[2] * sine - + # Compute the weighted variation of the signal and the model functions - + Vsignal = np.sum(weight * signal**2) / W - (np.sum(weight * signal) / W)**2 Vmodel = np.sum(weight * model**2) / W - (np.sum(weight * model) / W)**2 - + # Calculate the weighted Wavelet Z-Transform - + Neff = W**2 / np.sum(weight**2) Z[i,j] = (Neff - 3) * Vmodel / 2. / (Vsignal - Vmodel) - + # That's it! - + return Z - + #} #{ Pure Python versions @@ -1109,13 +1102,13 @@ def pdm_py(time, signal, f0=None, fn=None, df=None, Nbin=10, Ncover=5, D=0.): """ Computes the theta-statistics to do a Phase Dispersion Minimisation. See Stellingwerf R.F., 1978, ApJ, 224, 953) - + Joris De Ridder - + Inclusion of linear frequency shift by Pieter Degroote (see Cuypers 1986) - + @param time: time points [0..Ntime-1] - @type time: ndarray + @type time: ndarray @param signal: observed data points [0..Ntime-1] @type signal: ndarray @param f0: start frequency @@ -1134,60 +1127,60 @@ def pdm_py(time, signal, f0=None, fn=None, df=None, Nbin=10, Ncover=5, D=0.): @rtype: array """ freq = np.arange(f0,fn+df,df) - + Ntime = len(time) Nfreq = len(freq) - + binsize = 1.0 / Nbin covershift = 1.0 / (Nbin * Ncover) - + theta = np.zeros(Nfreq) - + for i in range(Nfreq): - + # Compute the phases in [0,1[ for all time points phase = np.fmod((time - time[0]) * freq[i] + D/2.*time**2, 1.0) - + # Reset the number of (shifted) bins without datapoints - + Nempty = 0 - + # Loop over all Nbin * Ncover (shifted) bins - + for k in range(Nbin): for n in range(Ncover): - + # Determine the left and right boundary of one such bin # Note that due to the modulo, right may be < left. So instead # of 0-----+++++------1, the bin might be 0++-----------+++1 . - - left = np.fmod(k * binsize + n * covershift, 1.0) - right = np.fmod((k+1) * binsize + n * covershift, 1.0) + + left = np.fmod(k * binsize + n * covershift, 1.0) + right = np.fmod((k+1) * binsize + n * covershift, 1.0) # Select all data points in that bin - + if (left < right): bindata = np.compress((left <= phase) & (phase < right), signal) else: bindata = np.compress(~((right <= phase) & (phase < left)), signal) - # Compute the contribution of that bin to the theta-statistics - + # Compute the contribution of that bin to the theta-statistics + if (len(bindata) != 0): theta[i] += (len(bindata) - 1) * bindata.var() else: Nempty += 1 - - # Normalize the theta-statistics - theta[i] /= Ncover * Ntime - (Ncover * Nbin - Nempty) - + # Normalize the theta-statistics + + theta[i] /= Ncover * Ntime - (Ncover * Nbin - Nempty) + # Normalize the theta-statistics again - - theta /= signal.var() - + + theta /= signal.var() + # That's it! - + return freq,theta @@ -1206,8 +1199,8 @@ def fasper_py(x,y,ofac,hifac, MACC=4): element in wk2, and prob, an estimate of the significance of that maximum against the hypothesis of random noise. A small value of prob indicates that a significant periodic signal is present. - - Reference: + + Reference: Press, W. H. & Rybicki, G. B. 1989 ApJ vol. 338, p. 277-280. Fast algorithm for spectral analysis of unevenly sampled data @@ -1220,7 +1213,7 @@ def fasper_py(x,y,ofac,hifac, MACC=4): Hifac : Hifac * "average" Nyquist frequency = highest frequency for which values of the Lomb normalized periodogram will be calculated. - + Returns: Wk1 : An array of Lomb periodogram frequencies. Wk2 : An array of corresponding values of the Lomb periodogram. @@ -1235,24 +1228,24 @@ def fasper_py(x,y,ofac,hifac, MACC=4): Translation of IDL code (orig. Numerical recipies) """ #Check dimensions of input arrays - n = long(len(x)) + n = int(len(x)) if n != len(y): - print 'Incompatible arrays.' + print('Incompatible arrays.') return nout = 0.5*ofac*hifac*n - nfreqt = long(ofac*hifac*n*MACC) #Size the FFT as next power - nfreq = 64L # of 2 above nfreqt. + nfreqt = int(ofac*hifac*n*MACC) #Size the FFT as next power + nfreq = 64 # of 2 above nfreqt. - while nfreq < nfreqt: + while nfreq < nfreqt: nfreq = 2*nfreq - ndim = long(2*nfreq) - + ndim = int(2*nfreq) + #Compute the mean, variance ave = y.mean() ##sample variance because the divisor is N-1 - var = ((y-y.mean())**2).sum()/(len(y)-1) + var = ((y-y.mean())**2).sum()/(len(y)-1) # and range of the data. xmin = x.min() xmax = x.max() @@ -1267,7 +1260,7 @@ def fasper_py(x,y,ofac,hifac, MACC=4): ck = ((x-xmin)*fac) % fndim ckk = (2.0*ck) % fndim - for j in range(0L, n): + for j in range(0, n): __spread__(y[j]-ave,wk1,ndim,ck[j],MACC) __spread__(1.0,wk2,ndim,ckk[j],MACC) @@ -1281,9 +1274,9 @@ def fasper_py(x,y,ofac,hifac, MACC=4): iwk1 = wk1.imag rwk2 = wk2.real iwk2 = wk2.imag - + df = 1.0/(xdif*ofac) - + #Compute the Lomb value for each frequency hypo2 = 2.0 * abs( wk2 ) hc2wt = rwk2/hypo2 @@ -1302,18 +1295,18 @@ def fasper_py(x,y,ofac,hifac, MACC=4): #Significance estimation - #expy = exp(-wk2) - #effm = 2.0*(nout)/ofac + #expy = exp(-wk2) + #effm = 2.0*(nout)/ofac #sig = effm*expy #ind = (sig > 0.01).nonzero() #sig[ind] = 1.0-(1.0-expy[ind])**effm #Estimate significance of largest peak value - expy = np.exp(-pmax) - effm = 2.0*(nout)/ofac + expy = np.exp(-pmax) + effm = 2.0*(nout)/ofac prob = effm*expy - if prob > 0.01: + if prob > 0.01: prob = 1.0-(1.0-expy)**effm return wk1,wk2,nout,jmax,prob @@ -1324,20 +1317,20 @@ def fasper_py(x,y,ofac,hifac, MACC=4): def windowfunction(time, freq): """ - Computes the modulus square of the window function of a set of - time points at the given frequencies. The time point need not be - equidistant. The normalisation is such that 1.0 is returned at + Computes the modulus square of the window function of a set of + time points at the given frequencies. The time point need not be + equidistant. The normalisation is such that 1.0 is returned at frequency 0. - + @param time: time points [0..Ntime-1] - @type time: ndarray + @type time: ndarray @param freq: frequency points. Units: inverse unit of 'time' [0..Nfreq-1] - @type freq: ndarray + @type freq: ndarray @return: |W(freq)|^2 [0..Nfreq-1] @rtype: array - + """ - + Ntime = len(time) Nfreq = len(freq) winkernel = np.empty_like(freq) @@ -1346,7 +1339,7 @@ def windowfunction(time, freq): winkernel[i] = np.sum(np.cos(2.0*pi*freq[i]*time))**2 \ + np.sum(np.sin(2.0*pi*freq[i]*time))**2 - # Normalise such that winkernel(nu = 0.0) = 1.0 + # Normalise such that winkernel(nu = 0.0) = 1.0 return winkernel/Ntime**2 @@ -1354,7 +1347,7 @@ def windowfunction(time, freq): def check_input(times,signal,**kwargs): """ Check the input arguments for periodogram calculations for mistakes. - + If you get an error when trying to compute a periodogram, and you don't understand it, just feed the input you gave to this function, and it will perform some basic checks. @@ -1362,53 +1355,53 @@ def check_input(times,signal,**kwargs): #-- check if the input are arrays and have the same 1D shape is_array0 = isinstance(times,np.ndarray) is_array1 = isinstance(signal,np.ndarray) - if not is_array0: print(termtools.red('ERROR: time input is not an array')) - if not is_array1: print(termtools.red('ERROR: signal input is not an array')) + if not is_array0: print((termtools.red('ERROR: time input is not an array'))) + if not is_array1: print((termtools.red('ERROR: signal input is not an array'))) if not is_array0 or not is_array1: times = np.asarray(times) signal = np.asarray(signal) - print(termtools.green("---> FIXED: inputs are arrays")) - print(termtools.green("OK: inputs are arrays")) + print((termtools.green("---> FIXED: inputs are arrays"))) + print((termtools.green("OK: inputs are arrays"))) onedim = (len(times.shape)==1) & (len(signal.shape)==1) same_shape = times.shape==signal.shape if not onedim or not same_shape: - print(termtools.red('ERROR: input is not 1D or not of same length')) + print((termtools.red('ERROR: input is not 1D or not of same length'))) return False - print(termtools.green("OK: inputs are 1D and have same length")) + print((termtools.green("OK: inputs are 1D and have same length"))) #-- check if the signal constains nans or infs: isnan0 = np.sum(np.isnan(times)) isnan1 = np.sum(np.isnan(signal)) isinf0 = np.sum(np.isinf(times)) isinf1 = np.sum(np.isinf(signal)) - if isnan0: print(termtools.red('ERROR: time array contains nans')) - if isnan1: print(termtools.red('ERROR: signal array contains nans')) - if isinf0: print(termtools.red('ERROR: time array contains infs')) - if isinf1: print(termtools.red('ERROR: signal array contains infs')) + if isnan0: print((termtools.red('ERROR: time array contains nans'))) + if isnan1: print((termtools.red('ERROR: signal array contains nans'))) + if isinf0: print((termtools.red('ERROR: time array contains infs'))) + if isinf1: print((termtools.red('ERROR: signal array contains infs'))) if not isnan0 and not isnan1 and not isinf0 and not isinf1: - print(termtools.green('OK: no infs or nans')) + print((termtools.green('OK: no infs or nans'))) else: keep = -np.isnan(times) & -np.isnan(signal) & -np.isinf(times) & -np.isinf(signal) times,signal = times[keep],signal[keep] - print(termtools.green('---> FIXED: infs and nans removed')) + print((termtools.green('---> FIXED: infs and nans removed'))) #-- check if the timeseries is sorted is_sorted = np.all(np.diff(times)>0) if not is_sorted: - print(termtools.red('ERROR: time array is not sorted')) + print((termtools.red('ERROR: time array is not sorted'))) sa = np.argsort(times) times,signal = times[sa],signal[sa] - print(termtools.green('---> FIXED: time array is sorted')) + print((termtools.green('---> FIXED: time array is sorted'))) else: - print(termtools.green("OK: time array is sorted")) - print(termtools.green("No inconsistencies found or inconsistencies are fixed")) - + print((termtools.green("OK: time array is sorted"))) + print((termtools.green("No inconsistencies found or inconsistencies are fixed"))) + #-- check keyword arguments: fnyq = getNyquist(times,nyq_stat=np.min) - print("Default Nyquist frequency: {}".format(fnyq)) + print(("Default Nyquist frequency: {}".format(fnyq))) if 'nyq_stat' in kwargs: fnyq = getNyquist(times,nyq_stat=kwargs['nyq_stat']) - print("Nyquist value manually set to {}".format(fnyq)) + print(("Nyquist value manually set to {}".format(fnyq))) if 'fn' in kwargs and kwargs['fn']>fnyq: - print(termtools.red("Final frequency 'fn' is larger than the Nyquist frequency")) + print((termtools.red("Final frequency 'fn' is larger than the Nyquist frequency"))) return times,signal @@ -1419,25 +1412,25 @@ def __spread__(y, yy, n, x, m): (i.e., possible noninteger) array element number x. The weights used are coefficients of the Lagrange interpolating polynomial Arguments: - y : - yy : - n : - x : - m : + y : + yy : + n : + x : + m : Returns: - + """ nfac=[0,1,1,2,6,24,120,720,5040,40320,362880] if m > 10. : - print 'factorial table too small in spread' + print('factorial table too small in spread') return - ix=long(x) - if x == float(ix): + ix=int(x) + if x == float(ix): yy[ix]=yy[ix]+y else: - ilo = long(x-0.5*float(m)+1.0) - ilo = min( max( ilo , 1 ), n-m+1 ) + ilo = int(x-0.5*float(m)+1.0) + ilo = min( max( ilo , 1 ), n-m+1 ) ihi = ilo+m-1 nden = nfac[m] fac=x-ilo @@ -1445,39 +1438,39 @@ def __spread__(y, yy, n, x, m): yy[ihi] = yy[ihi] + y*fac/(nden*(x-ihi)) for j in range(ihi-1,ilo-1,-1): nden=(nden/(j+1-ilo))*(j-ihi) - yy[j] = yy[j] + y*fac/(nden*(x-j)) + yy[j] = yy[j] + y*fac/(nden*(x-j)) def __ane__(n,e): return 2.*np.sqrt(1-e**2)/e/n*jn(n,n*e) - + def __bne__(n,e): return 1./n*(jn(n-1,n*e)-jn(n+1,n*e)) - + def getSignificance(wk1, wk2, nout, ofac): """ Returns the peak false alarm probabilities - + Hence the lower is the probability and the more significant is the peak """ - expy = np.exp(-wk2) - effm = 2.0*(nout)/ofac + expy = np.exp(-wk2) + effm = 2.0*(nout)/ofac sig = effm*expy ind = (np.sig > 0.01).nonzero() sig[ind] = 1.0-(1.0-expy[ind])**effm return sig - + def scargle_probability(peak_value,times,freqs,correct_for_frange=False,**kwargs): """ Compute the probability to observe a peak in the Scargle periodogram. - + If C{correct_for_frange=True}, the Bonferroni correction will be applied to a smaller number of frequencies (i.e. the independent number of frequencies in C{freqs}). To be conservative, set C{correct_for_frange=False}. - + Example simulation: - + >>> times = np.linspace(0,1,5000) >>> N = 500 >>> probs = np.zeros(N) @@ -1487,9 +1480,9 @@ def scargle_probability(peak_value,times,freqs,correct_for_frange=False,**kwargs ... f,s = scargle(times,signal,threads='max',norm='distribution') ... peaks[i] = s.max() ... probs[i] = scargle_probability(s.max(),times,f) - + Now make a plot: - + >>> p = pl.figure() >>> p = pl.subplot(131) >>> p = pl.plot(probs,'ko') @@ -1504,9 +1497,9 @@ def scargle_probability(peak_value,times,freqs,correct_for_frange=False,**kwargs >>> p = pl.plot([1e-6,100],[1e-6,100],'r-',lw=2) >>> p = pl.xlabel('Should observe this many points below threshold') >>> p = pl.ylabel('Observed this many points below threshold') - + ]]include figure]]ivs_timeseries_pergrams_prob.png] - + """ #-- independent frequencies nr_obs = len(times) @@ -1527,17 +1520,17 @@ def scargle_probability(peak_value,times,freqs,correct_for_frange=False,**kwargs import pylab as pl from ivs.aux import loggers logger = loggers.get_basic_logger() - + #-- run tests if '--test' in sys.argv[1] or '-t' in sys.argv[1]: import doctest doctest.testmod() pl.show() - #-- command line interface + #-- command line interface else: - + method,args,kwargs = argkwargparser.parse() - print "Running method %s with arguments %s and keyword arguments %s"%(method,args,kwargs) + print("Running method %s with arguments %s and keyword arguments %s"%(method,args,kwargs)) if '--help' in args or 'help' in args or 'help' in kwargs: sys.exit() times,signal = ascii.read2array(kwargs.pop('infile')).T[:2] diff --git a/timeseries/pyGLS.f b/timeseries/pyGLS.f index 6b3b3a3f4..bbd29d6b2 100644 --- a/timeseries/pyGLS.f +++ b/timeseries/pyGLS.f @@ -87,7 +87,7 @@ SUBROUTINE Phases(JD,twopiFreq,N,phase) subroutine gls(JD_,RV_,RVerr_,N_,Fbeg,Fend,step,wexp, $f1,s1,p1,l1,maxstep) implicit none - INTEGER i,j,Nmax,N,wexp,N_, index + INTEGER i,j,N,wexp,N_, index_bn INTEGER maxstep c ! wexp = weightening exponent (default Chi2) c !!! weightening of errors: wexp=0 (variance), wexp=2 (Chi2) !!! @@ -95,7 +95,7 @@ subroutine gls(JD_,RV_,RVerr_,N_,Fbeg,Fend,step,wexp, CHARACTER TV*40, fittype*8/"unknown"/ DOUBLE PRECISION twopi PARAMETER(twopi=2.*3.141592654) - PARAMETER(Nmax=500000) ! max. data points + INTEGER, PARAMETER :: Nmax=500000 ! max_ data points DOUBLE PRECISION JD_(N_),RV_(N_),RVerr_(N_) DOUBLE PRECISION f1(maxstep),s1(maxstep),p1(maxstep),l1(maxstep) DOUBLE PRECISION JD(Nmax),RV(Nmax),RVerr(Nmax),phase(Nmax) @@ -107,7 +107,7 @@ subroutine gls(JD_,RV_,RVerr_,N_,Fbeg,Fend,step,wexp, DOUBLE PRECISION dummy,FAP,M,prob COMMON /Messwerte/ v,wy,ww,YY,RVmean,N c DATA RVmean,m33,YY,powSin /4* 0./ - + c SET default values to some stuff do 90 i=1,Nmax v(i) = 0. diff --git a/timeseries/pyKEP.f b/timeseries/pyKEP.f index 24afbca97..709e24f4a 100644 --- a/timeseries/pyKEP.f +++ b/timeseries/pyKEP.f @@ -87,7 +87,7 @@ SUBROUTINE Phases(JD,twopiFreq,N,phase) subroutine kepler(JD_,RV_,RVerr_,N_,Fbeg,Fend,step,wexp,emin,emax, $estep,x0min,x0max,f1,s1,p1,l1,s2,maxstep,k2) implicit none - INTEGER i,j,Nmax,N,wexp,N_, index + INTEGER i,j,N,wexp,N_, index_bn INTEGER maxstep c ! wexp = weightening exponent (default Chi2) c !!! weightening of errors: wexp=0 (variance), wexp=2 (Chi2) !!! @@ -96,7 +96,7 @@ subroutine kepler(JD_,RV_,RVerr_,N_,Fbeg,Fend,step,wexp,emin,emax, DOUBLE PRECISION twopi,G,AU,Msun,Mjup, fa PARAMETER(twopi=2.*3.141592654,G=6.6726E-11,AU=1.496E11) PARAMETER(Msun=1.989E30, Mjup = Msun/1047.39) - PARAMETER(Nmax=10000) ! max. data points + INTEGER, PARAMETER :: Nmax=10000 ! max_. data points DOUBLE PRECISION JD_(N_),RV_(N_),RVerr_(N_) DOUBLE PRECISION f1(maxstep),s1(maxstep),p1(maxstep),l1(maxstep) DOUBLE PRECISION s2(maxstep) @@ -109,8 +109,8 @@ subroutine kepler(JD_,RV_,RVerr_,N_,Fbeg,Fend,step,wexp,emin,emax, DOUBLE PRECISION A,B,C,CBest, Amp,ph, K,RV0,x0,e,w,xx0,ee DOUBLE PRECISION A1sinK,A1sinS,AsinK,AsinS,powKe,zwischen DOUBLE PRECISION mass,mass2S,mass2K, dummy,FAP,M,prob - DOUBLE PRECISION emin,emax,estep ! default values - DOUBLE PRECISION x0min,x0max,x0step ! default values + DOUBLE PRECISION emin,emax,estep ! default_ values + DOUBLE PRECISION x0min,x0max,x0step ! default_ values COMMON /Messwerte/ v,wy,ww,YY,RVmean,N c DATA RVmean,m33,YY, mass2S,mass2K, powSin,powKep /7* 0./ c set all defaults to zero @@ -127,7 +127,7 @@ subroutine kepler(JD_,RV_,RVerr_,N_,Fbeg,Fend,step,wexp,emin,emax, mass2K = 0 powSin = 0 powKep = 0 - + fa=(86400./twopi/G/Msun)**(1./3.) ! fa= 4.69679026E-06 diff --git a/timeseries/windowfunctions.py b/timeseries/windowfunctions.py index 69eec43c9..5d90c7448 100644 --- a/timeseries/windowfunctions.py +++ b/timeseries/windowfunctions.py @@ -31,7 +31,7 @@ #{ Main wrapper def getWindowFunction(name,times): return globals()[name.lower()](times) - + #} #{ Low dynamic range @@ -63,7 +63,7 @@ def hann(times): window = 0.5* (1-np.cos(2*np.pi*n)/N_1) logger.debug("Selected Hann window") return window - + def cosine(times): """ Cosine window diff --git a/units/conversions.py b/units/conversions.py index bdf01ab04..88e6d6e50 100644 --- a/units/conversions.py +++ b/units/conversions.py @@ -55,7 +55,7 @@ even more confusing, there is another unit which is equal to the reciprocal second, namely the Becquerel. This is used for stochastic or non-recurrent phenomena. Basically, the problem is:: - + rad/s == 1/s 1/s == Hz @@ -97,7 +97,7 @@ Section 1. The Python module ============================ - + The main function L{convert} (see link for a full list of examples) does all the work and is called via @@ -133,161 +133,161 @@ For help and a list of all defined units and abbreviations, do:: $:> python conversions.py --help - ===================================== | ===================================== - = Units of absorbed dose = | = Units of acceleration = - ===================================== | ===================================== - Gy = gray | Gal = Gal - ===================================== | ===================================== - = Units of angle = | = Units of area = - ===================================== | ===================================== - am = arcminute | a = are - as = arcsecond | ac = acre (international) - cy = cycle | b = barn - deg = degree | ===================================== - rad = radian | = Units of coordinate = - rpm = revolutions per minute | ===================================== - sr = sterradian | complex_coord = - ===================================== | deg_coord = degrees - = Units of catalytic activity = | ecliptic = ecliptic - ===================================== | equatorial = equatorial - kat = katal | galactic = galactic - ===================================== | rad_coord = radians - = Units of currency = | ===================================== - ===================================== | = Units of dose equivalent = - EUR = EURO | ===================================== - ===================================== | Sv = sievert - = Units of dynamic viscosity = | rem = rem - ===================================== | ===================================== - P = poise | = Units of electric capacitance = - ===================================== | ===================================== - = Units of electric charge = | F = Farad - ===================================== | ===================================== - C = Coulomb | = Units of electric conductance = - ===================================== | ===================================== - = Units of electric current = | S = Siemens - ===================================== | ===================================== - A = Ampere | = Units of electric potential difference = - Bi = biot | ===================================== - Gi = Ampere | V = Volt - ===================================== | ===================================== - = Units of electric resistance = | = Units of energy = - ===================================== | ===================================== + ===================================== | ===================================== + = Units of absorbed dose = | = Units of acceleration = + ===================================== | ===================================== + Gy = gray | Gal = Gal + ===================================== | ===================================== + = Units of angle = | = Units of area = + ===================================== | ===================================== + am = arcminute | a = are + as = arcsecond | ac = acre (international) + cy = cycle | b = barn + deg = degree | ===================================== + rad = radian | = Units of coordinate = + rpm = revolutions per minute | ===================================== + sr = sterradian | complex_coord = + ===================================== | deg_coord = degrees + = Units of catalytic activity = | ecliptic = ecliptic + ===================================== | equatorial = equatorial + kat = katal | galactic = galactic + ===================================== | rad_coord = radians + = Units of currency = | ===================================== + ===================================== | = Units of dose equivalent = + EUR = EURO | ===================================== + ===================================== | Sv = sievert + = Units of dynamic viscosity = | rem = rem + ===================================== | ===================================== + P = poise | = Units of electric capacitance = + ===================================== | ===================================== + = Units of electric charge = | F = Farad + ===================================== | ===================================== + C = Coulomb | = Units of electric conductance = + ===================================== | ===================================== + = Units of electric current = | S = Siemens + ===================================== | ===================================== + A = Ampere | = Units of electric potential difference = + Bi = biot | ===================================== + Gi = Ampere | V = Volt + ===================================== | ===================================== + = Units of electric resistance = | = Units of energy = + ===================================== | ===================================== O = Ohm | Cal = large calorie (international table) - ===================================== | J = Joule + ===================================== | J = Joule = Units of energy/power = | cal = small calorie (international table) - ===================================== | eV = electron volt - Lsol = Solar luminosity | erg = ergon - ===================================== | foe = (ten to the) fifty one ergs - = Units of flux density = | ===================================== - ===================================== | = Units of flux = - Jy = Jansky | ===================================== - ===================================== | ABmag = AB magnitude - = Units of frequency = | Amag = amplitude in magnitude - ===================================== | STmag = ST magnitude - hz = Hertz | ampl = fractional amplitude - ===================================== | flux_ratio = flux ratio - = Units of inductance = | mag = magnitude - ===================================== | mag_color = color + ===================================== | eV = electron volt + Lsol = Solar luminosity | erg = ergon + ===================================== | foe = (ten to the) fifty one ergs + = Units of flux density = | ===================================== + ===================================== | = Units of flux = + Jy = Jansky | ===================================== + ===================================== | ABmag = AB magnitude + = Units of frequency = | Amag = amplitude in magnitude + ===================================== | STmag = ST magnitude + hz = Hertz | ampl = fractional amplitude + ===================================== | flux_ratio = flux ratio + = Units of inductance = | mag = magnitude + ===================================== | mag_color = color H = Henry | pph = amplitude in parts per hundred ===================================== | ppm = amplitude in parts per million = Units of length = | ppt = amplitude in parts per thousand - ===================================== | vegamag = Vega magnitude - AA = angstrom | ===================================== - AU = astronomical unit | = Units of force = - Rearth = Earth radius | ===================================== - Rjup = Jupiter radius | N = Newton - Rsol = Solar radius | dyn = dyne - USft = foot (US) | ===================================== - USmi = mile (US) | = Units of illuminance = - a0 = Bohr radius | ===================================== - bs = beard second | lx = lux - ch = chain | ph = phot - ell = ell | sb = stilb - fathom = fathom | ===================================== - ft = foot (international) | = Units of kynamatic viscosity = - fur = furlong | ===================================== - in = inch (international) | St = stokes - ly = light year | ===================================== - m = meter | = Units of luminous flux = - mi = mile (international) | ===================================== - nami = nautical mile | lm = lumen - pc = parsec | ===================================== - perch = pole | = Units of magnetic field strength = - pole = perch | ===================================== - potrzebie = potrzebie | G = Gauss - rd = rod | T = Tesla - smoot = smooth | ===================================== - yd = yard (international) | = Units of magnetizing field = - ===================================== | ===================================== - = Units of m/s = | Oe = Oersted - ===================================== | ===================================== - cc = Speed of light | = Units of power = - ===================================== | ===================================== - = Units of magnetic flux = | W = Watt - ===================================== | dp = Donkeypower - Mx = Maxwell | hp = Horsepower - Wb = Weber | ===================================== - ===================================== | = Units of roentgen = - = Units of mass = | ===================================== - ===================================== | R = Roentgen - Mearth = Earth mass | ===================================== - Mjup = Jupiter mass | = Units of temperature = - Mlun = Lunar mass | ===================================== - Msol = Solar mass | Cel = Celcius - carat = carat | Far = Fahrenheit - firkin = firkin | K = Kelvin - g = gram | Tsol = Solar temperature - gr = gram | ===================================== - lb = pound | = Units of velocity = - mol = molar mass | ===================================== - ounce = ounce | knot = nautical mile per hour - st = stone | mph = miles per hour - ton = gram | - u = atomic mass | - ===================================== | - = Units of pressure = | - ===================================== | - Pa = Pascal | - at = atmosphere (technical) | - atm = atmosphere (standard) | - ba = barye | - bar = baros | - mmHg = millimeter of mercury | - psi = pound per square inch | - torr = Torricelli | - ===================================== | - = Units of second = | - ===================================== | - fortnight = fortnight | - ===================================== | - = Units of time = | - ===================================== | - Bq = Becquerel | - CD = calender day | - Ci = Curie | - JD = Julian day | - MJD = modified Julian day | - cr = century | - d = day | - h = hour | - j = jiffy | - min = minute | - mo = month | - s = second | - sidereal = sidereal day | - wk = week | - yr = year | - ===================================== | - = Units of volume = | - ===================================== | - USgal = gallon (US) | - USgi = gill (Canadian and UK imperial)| - bbl = barrel | - bu = bushel | - gal = gallon (Canadian and UK imperial)| - gi = gill (Canadian and UK imperial)| - l = liter | - ngogn = 1000 cubic potrzebies | + ===================================== | vegamag = Vega magnitude + AA = angstrom | ===================================== + AU = astronomical unit | = Units of force = + Rearth = Earth radius | ===================================== + Rjup = Jupiter radius | N = Newton + Rsol = Solar radius | dyn = dyne + USft = foot (US) | ===================================== + USmi = mile (US) | = Units of illuminance = + a0 = Bohr radius | ===================================== + bs = beard second | lx = lux + ch = chain | ph = phot + ell = ell | sb = stilb + fathom = fathom | ===================================== + ft = foot (international) | = Units of kynamatic viscosity = + fur = furlong | ===================================== + in = inch (international) | St = stokes + ly = light year | ===================================== + m = meter | = Units of luminous flux = + mi = mile (international) | ===================================== + nami = nautical mile | lm = lumen + pc = parsec | ===================================== + perch = pole | = Units of magnetic field strength = + pole = perch | ===================================== + potrzebie = potrzebie | G = Gauss + rd = rod | T = Tesla + smoot = smooth | ===================================== + yd = yard (international) | = Units of magnetizing field = + ===================================== | ===================================== + = Units of m/s = | Oe = Oersted + ===================================== | ===================================== + cc = Speed of light | = Units of power = + ===================================== | ===================================== + = Units of magnetic flux = | W = Watt + ===================================== | dp = Donkeypower + Mx = Maxwell | hp = Horsepower + Wb = Weber | ===================================== + ===================================== | = Units of roentgen = + = Units of mass = | ===================================== + ===================================== | R = Roentgen + Mearth = Earth mass | ===================================== + Mjup = Jupiter mass | = Units of temperature = + Mlun = Lunar mass | ===================================== + Msol = Solar mass | Cel = Celcius + carat = carat | Far = Fahrenheit + firkin = firkin | K = Kelvin + g = gram | Tsol = Solar temperature + gr = gram | ===================================== + lb = pound | = Units of velocity = + mol = molar mass | ===================================== + ounce = ounce | knot = nautical mile per hour + st = stone | mph = miles per hour + ton = gram | + u = atomic mass | + ===================================== | + = Units of pressure = | + ===================================== | + Pa = Pascal | + at = atmosphere (technical) | + atm = atmosphere (standard) | + ba = barye | + bar = baros | + mmHg = millimeter of mercury | + psi = pound per square inch | + torr = Torricelli | + ===================================== | + = Units of second = | + ===================================== | + fortnight = fortnight | + ===================================== | + = Units of time = | + ===================================== | + Bq = Becquerel | + CD = calender day | + Ci = Curie | + JD = Julian day | + MJD = modified Julian day | + cr = century | + d = day | + h = hour | + j = jiffy | + min = minute | + mo = month | + s = second | + sidereal = sidereal day | + wk = week | + yr = year | + ===================================== | + = Units of volume = | + ===================================== | + USgal = gallon (US) | + USgi = gill (Canadian and UK imperial)| + bbl = barrel | + bu = bushel | + gal = gallon (Canadian and UK imperial)| + gi = gill (Canadian and UK imperial)| + l = liter | + ngogn = 1000 cubic potrzebies | Usage: conversions.py --from= --to= [options] value [error] @@ -314,7 +314,7 @@ In fact, the C{to} parameter is optional (despite it not being a positional argument, C{from} is not optional). The script will assume you want to convert to SI:: - + $:> python conversions.py --from=nRsol/h 1.2345 1.2345 nRsol/h = 0.000238501 m1 s-1 @@ -323,10 +323,10 @@ $:> python conversions.py --from=mag --to=erg/s/cm2/AA --photband=GENEVA.U 7.84 0.02 7.84 +/- 0.02 mag = 4.12191e-12 +/- 7.59283e-14 erg/s/cm2/AA - -If you want to do coordinate transformations, e.g. from fractional radians to + +If you want to do coordinate transformations, e.g. from fractional radians to degrees/arcminutes/arcseconds, you can do:: - + $:> python conversions.py --from=rad_coord --to=equatorial 5.412303,0.123 (5.412303, 0.123) rad_coord = 20:40:24.51,7:02:50.6 equ @@ -338,7 +338,7 @@ 1. You want to work in a different base unit system (e.g. cgs instead of SI) 2. You disagree with some of the literature values and want to use your own. - + Section 3.1. Changing base unit system -------------------------------------- @@ -376,7 +376,7 @@ >>> set_convention(units='SI') ('imperial', 'standard', 'rad') -The function L{set_convention} returns the current settings, so you can +The function L{set_convention} returns the current settings, so you can remember the old settings and make a temporary switch: >>> old_settings = set_convention(units='cgs') @@ -452,7 +452,7 @@ Now we can calculate the C{sini}: ->>> sini = vsini * P / (2*np.pi*R) +>>> sini = vsini * P / (2*np.pi*R) And take the arcsine to recover the inclination angle in radians. Because we are working with the L{Unit} class, we can also immediately retrieve it in @@ -513,10 +513,10 @@ import os import sys import logging -import urllib +# import urllib.request, urllib.parse, urllib.error import numpy as np -import pytz import datetime +import imp #-- optional libraries: WARNING: when these modules are not installed, the # module's use is restricted @@ -525,12 +525,12 @@ #-- from IVS repository from ivs.units import constants -from ivs.units.uncertainties import unumpy,AffineScalarFunc,ufloat -from ivs.units.uncertainties.unumpy import log10,log,exp,sqrt -from ivs.units.uncertainties.unumpy import sin,cos,tan -from ivs.units.uncertainties.unumpy import arcsin,arccos,arctan +from uncertainties import unumpy,ufloat +from uncertainties.core import AffineScalarFunc +from numpy import log10,log,exp,sqrt +from numpy import sin,cos,tan +from numpy import arcsin,arccos,arctan from ivs.sed import filters -from ivs.inout import ascii from ivs.aux import loggers from ivs.aux.decorators import memoized @@ -542,83 +542,83 @@ def convert(_from,_to,*args,**kwargs): """ Convert one unit to another. - + Basic explanation ================= - + The unit strings C{_from} and C{_to} should by default be given in the form - + C{erg s-1 cm-2 AA-1} - + Common alternatives are also accepted (see below). - + Square brackets '[]' denote a logarithmic value. - + If one positional argument is given, it can be either a scalar, numpy array or C{uncertainties} object. The function will also return one argument of the same type. - + If two positional arguments are given, the second argument is assumed to be the uncertainty on the first (scalar or numpy array). The function will also return two arguments. Basic examples: - + >>> convert('km','cm',1.) 100000.0 >>> convert('m/s','km/h',1,0.1) (3.5999999999999996, 0.36) - + Keyword arguments can give extra information, for example when converting from Flambda to Fnu, and should be tuples (float(,error),'unit'): - + >>> convert('AA','km/s',4553,0.1,wave=(4552.,0.1,'AA')) (65.85950307557613, 9.314963362464114) - + Extra ===== - + The unit strings C{_from} and C{_to} should by default be given in the form - + C{erg s-1 cm-2 AA-1} - + Common alternatives are also accepted, but don't drive this too far: - + C{erg/s/cm2/AA} - + The crasiest you're allowed to go is something like - + >>> print(convert('10mW m-2/nm','erg s-1 cm-2 AA-1',1.)) 1.0 - + But there is a limit on the interpretation of this prefactor also. Floats will probably not work, and exponentials require exactly two digits. - + Parentheses are in no circumstances accepted. Some common aliases are also resolved (for a full list, see dictionary C{_aliases}): - + C{erg/s/cm2/angstrom} - + You don't really have to spell both units if either the 'from' or 'to' units is consistently within one convention (SI, cgs, solar...). But of course you have to give at least one!: - + >>> convert('kg','cgs',1.) 1000.0 >>> convert('g','SI',1.) 0.001 >>> convert('SI','g',1.) 1000.0 - + B{WARNINGS}: 1. the conversion involving sr and pixels is B{not tested}. 2. the conversion involving magnitudes is calibrated but not fully tested 3. non-integer powers are not functioning yet - + Examples: - + B{Spectra}: - + >>> convert('AA','km/s',4553.,wave=(4552.,'AA')) 65.85950307557613 >>> convert('AA','km/s',4553.,wave=(4552.,0.1,'AA')) @@ -633,9 +633,9 @@ def convert(_from,_to,*args,**kwargs): 0.3993883287866966 >>> print(convert('erg s-1 cm-2 AA-1','SI',1.)) 10000000.0 - + B{Fluxes}: - + >>> print(convert('erg/s/cm2/AA','Jy',1e-10,wave=(10000.,'angstrom'))) 333.564095198 >>> print(convert('erg/s/cm2/AA','Jy',1e-10,freq=(constants.cc/1e-6,'hz'))) @@ -660,27 +660,27 @@ def convert(_from,_to,*args,**kwargs): 1.49896229e-09 >>> print(convert('erg/s/cm2','Jy',1.,wave=(2.,'micron'))) 667128190.396 - + #>>> print(convert('Jy','erg/s/cm2/micron/sr',1.,wave=(2.,'micron'),ang_diam=(3.,'mas'))) #4511059.82981 #>>> print(convert('Jy','erg/s/cm2/micron/sr',1.,wave=(2.,'micron'),pix=(3.,'mas'))) #3542978.10531 #>>> print(convert('erg/s/cm2/micron/sr','Jy',1.,wave=(2.,'micron'),ang_diam=(3.,'mas'))) #2.21677396826e-07 - + >>> print(convert('Jy','erg/s/cm2/micron',1.,wave=(2,'micron'))) 7.49481145e-10 >>> print(convert('10mW m-2 nm-1','erg s-1 cm-2 AA-1',1.)) 1.0 - + #>>> print convert('Jy','erg s-1 cm-2 micron-1 sr-1',1.,ang_diam=(2.,'mas'),wave=(1.,'micron')) #40599538.4683 - + B{Angles}: - + >>> convert('sr','deg2',1.) 3282.806350011744 - + >>> ang_diam = 3.21 # mas >>> scale = convert('mas','sr',ang_diam/2.) >>> print(ang_diam,scale) @@ -689,9 +689,9 @@ def convert(_from,_to,*args,**kwargs): >>> print(ang_diam,scale) (3.2100000000000004, 6.054800067947964e-17) - + B{Magnitudes and amplitudes}: - + >>> print(convert('ABmag','Jy',0.,photband='SDSS.U')) 3767.03798984 >>> print(convert('Jy','erg cm-2 s-1 AA-1',3630.7805477,wave=(1.,'micron'))) @@ -706,16 +706,16 @@ def convert(_from,_to,*args,**kwargs): (0.9214583192957981, 0.09218827316735488) >>> print(convert('mag_color','flux_ratio',0.599,0.004,photband='GENEVA.U-B')) (1.1391327795013377, 0.004196720251233046) - + B{Frequency analysis}: - + >>> convert('cy/d','muHz',1.) 11.574074074074074 >>> convert('muhz','cy/d',11.574074074074074) 1.0 - + B{Interferometry}: - + >>> convert('m/rad','cy/arcsec',85.,wave=(2.2,'micron')) 187.3143767923207 >>> convert('cm/rad','cy/arcmin',8500.,wave=(2200,'nm'))/60. @@ -728,9 +728,9 @@ def convert(_from,_to,*args,**kwargs): 38.54483473437971 >>> convert('cycles/mas','m',0.187,freq=(300000.,'Ghz')) 38.5448347343797 - + B{Temperature}: - + >>> print(convert('Far','K',123.)) 323.705555556 >>> print(convert('kFar','kK',0.123)) @@ -743,9 +743,9 @@ def convert(_from,_to,*args,**kwargs): 50.0 >>> print(convert('dCel','kFar',100.)) 0.05 - + B{Time and Dates}: - + >>> convert('sidereal d','d',1.) 1.0027379093 >>> convert('JD','CD',2446257.81458) @@ -760,11 +760,11 @@ def convert(_from,_to,*args,**kwargs): 0.0 >>> convert('MJD','CD',0,jtype='mjd') (1858.0, 11.0, 17.0) - + B{Coordinates}: When converting coordinates with pyephem, you get pyephem coordinates back. They have built-in string represenations and float conversions: - + >>> x,y = convert('equatorial','galactic',('17:45:40.4','-29:00:28.1'),epoch='2000') >>> print (x,y) (6.282224277178722, -0.000825178833899176) @@ -775,24 +775,24 @@ def convert(_from,_to,*args,**kwargs): (4.64964430303663, -0.5050315085342665) >>> print x,y 17:45:37.20 -28:56:10.2 - + It is also possible to immediately convert to radians or degrees in floats (this can be useful for plotting): - + >>> convert('equatorial','deg_coord',('17:45:40.4','-29:00:28.1')) (266.41833333333335, -29.00780555555556) >>> convert('equatorial','rad_coord',('17:45:40.4','-29:00:28.1')) (4.649877104342426, -0.5062817157227474) - + B{Magnetism and Electricity}: The stored energy in a magnet, called magnet performance or maximum energy product (often abbreviated BHmax), is typically measured in units of megagauss-oersteds (MGOe). One MGOe is approximately equal to 7957.74715 J/m3 (wikipedia): - + >>> convert('MG Oe','J/m3',1.) 7957.747154594768 - - + + @param _from: units to convert from @type _from: str @param _to: units to convert to @@ -807,7 +807,7 @@ def convert(_from,_to,*args,**kwargs): #-- remember if user wants to unpack the results to have no trace of # uncertainties, or wants to get uncertainty objects back unpack = kwargs.pop('unpack',True) - + #-- get the input arguments: if only one is given, it is either an # C{uncertainty} from the C{uncertainties} package, or it is just a float if len(args)==1: @@ -815,20 +815,20 @@ def convert(_from,_to,*args,**kwargs): # if two arguments are given, we assume the first is the actual value and # the second is the error on the value elif len(args)==2: - start_value = unumpy.uarray([args[0],args[1]]) + start_value = unumpy.uarray(*[args[0],args[1]]) else: raise ValueError('illegal input') - + #-- (un)logarithmicize (denoted by '[]') m_in = re.search(r'\[(.*)\]',_from) m_out = re.search(r'\[(.*)\]',_to) - + if m_in is not None: _from = m_in.group(1) start_value = 10**start_value if m_out is not None: _to = m_out.group(1) - + #-- It is possible the user gave a convention for either the from or to # units (but not both!) #-- break down the from and to units to their basic elements @@ -838,7 +838,7 @@ def convert(_from,_to,*args,**kwargs): _to = change_convention(_to,_from) fac_from,uni_from = breakdown(_from) fac_to,uni_to = breakdown(_to) - + #-- convert the kwargs to SI units if they are tuples (make a distinction # when uncertainties are given) if uni_from!=uni_to and is_basic_unit(uni_from,'length') and not ('wave' in kwargs):# or 'freq' in kwargs_SI or 'photband' in kwargs_SI): @@ -855,10 +855,10 @@ def convert(_from,_to,*args,**kwargs): kwargs_SI[key] = kwargs[key] #-- add some default values if necessary logger.debug('Convert %s to %s, fac_from-start_value %s/%s'%(uni_from,uni_to,fac_from,start_value)) - + #-- conversion is easy if same units ret_value = 1. - + if uni_from==uni_to: #-- if nonlinear conversions from or to: if isinstance(fac_from,NonLinearConverter): @@ -868,7 +868,7 @@ def convert(_from,_to,*args,**kwargs): ret_value *= fac_from*start_value except TypeError: raise TypeError('Cannot multiply value with a float; probably argument is a tuple (value,error), please expand with *(value,error)') - + #-- otherwise a little bit more complicated else: #-- first check where the unit differences are @@ -887,7 +887,7 @@ def convert(_from,_to,*args,**kwargs): only_to = '' #-- first we remove any differences concerning (ster)radians - # we recently added fac_from* to all these things, maybe this needs to + # we recently added fac_from* to all these things, maybe this needs to # change? #if 'rad2' in only_from: # start_value = fac_from*_switch['rad2_to_'](start_value,**kwargs_SI) @@ -909,11 +909,11 @@ def convert(_from,_to,*args,**kwargs): # only_from = only_from.replace('rad-1','') # logger.debug('Switching from /rad') # fac_from = 1. - + #-- then we do what is left over (if anything is left over) if only_from or only_to: logger.debug("Convert %s to %s"%(only_from,only_to)) - + #-- nonlinear conversions need a little tweak try: key = '%s_to_%s'%(only_from,only_to) @@ -940,12 +940,12 @@ def convert(_from,_to,*args,**kwargs): ret_value = fac_to(ret_value,inv=True,**kwargs_SI) else: ret_value /= fac_to - + #-- logarithmicize if m_out is not None: ret_value = log10(ret_value) - - #-- unpack the uncertainties if: + + #-- unpack the uncertainties if: # 1. the input was not given as an uncertainty # 2. the input was without uncertainties, but extra keywords had uncertainties # 3. the input was with uncertainties (float or array) and unpack==True @@ -956,17 +956,17 @@ def convert(_from,_to,*args,**kwargs): ret_value = unumpy.nominal_values(ret_value),unumpy.std_devs(ret_value) #-- convert to real floats if real floats were given if not ret_value[0].shape: - ret_value = np.asscalar(ret_value[0]),np.asscalar(ret_value[1]) - + ret_value = np.asscalar(ret_value[0]),np.asscalar(ret_value[1]) + return ret_value def nconvert(_froms,_tos,*args,**kwargs): """ Convert a list/array/tuple of values with different units to other units. - + This silently catches some exceptions and replaces the value with nan! - + @rtype: array """ if len(args)==1: @@ -977,7 +977,7 @@ def nconvert(_froms,_tos,*args,**kwargs): _tos = [_tos for i in _froms] elif isinstance(_froms,str): _froms = [_froms for i in _tos] - + for i,(_from,_to) in enumerate(list(zip(_froms,_tos))): myargs = [iarg[i] for iarg in args] mykwargs = {} @@ -990,7 +990,7 @@ def nconvert(_froms,_tos,*args,**kwargs): ret_value[i] = convert(_from,_to,*myargs,**mykwargs) except ValueError: #no calibration ret_value[i] = np.nan - + if len(args)==2: ret_value = ret_value.T return ret_value @@ -999,15 +999,15 @@ def nconvert(_froms,_tos,*args,**kwargs): def change_convention(to_,units,origin=None): """ Change units from one convention to another. - + Example usage: - + >>> units1 = 'kg m2 s-2 K-1 mol-1' >>> print change_convention('cgs',units1) K-1 g1 cm2 mol-1 s-2 >>> print change_convention('sol','g cm2 s-2 K-1 mol-1') Tsol-1 Msol1 Rsol2 mol-1 s-2 - + @param to_: convention name to change to @type to_: str @param units: units to change @@ -1034,18 +1034,18 @@ def change_convention(to_,units,origin=None): #-- weave them back in new_units = "".join(["".join([i,j]) for i,j in zip(new_units,powers)]) return new_units - + def set_convention(units='SI',values='standard',frequency='rad'): """ Consistently change the values of the fundamental constants to the paradigm of other system units or programs. - + This can be important in pulsation codes, where e.g. the value of the gravitational constant and the value of the solar radius can have siginficant influences on the frequencies. - + Setting C{frequency} to C{rad} gives you the following behaviour: - + >>> old_settings = set_convention(frequency='rad') >>> convert('s-1','rad/s',1.0) 1.0 @@ -1055,9 +1055,9 @@ def set_convention(units='SI',values='standard',frequency='rad'): 6.283185307179586 >>> convert('','mas',constants.au/(1000*constants.pc)) 0.99999999999623 - + Setting C{frequency} to C{cy} gives you the following behaviour: - + >>> old_settings = set_convention(frequency='cy') >>> convert('s-1','rad/s',1.0) 6.283185307179586 @@ -1068,7 +1068,7 @@ def set_convention(units='SI',values='standard',frequency='rad'): >>> convert('','mas',constants.au/(1000*constants.pc)) 6.2831853071558985 >>> old_settings = set_convention(*old_settings) - + @param units: name of the new units base convention @type units: str @param values: name of the value set for fundamental constants @@ -1093,7 +1093,7 @@ def set_convention(units='SI',values='standard',frequency='rad'): _switch['rad-1_to_'] = do_nothing constants._current_frequency = frequency.lower() logger.debug('Changed frequency convention to {0}'.format(frequency)) - + if to_return[:2]==(units,values): return to_return #-- first reload the constants to their original values (i.e. SI based) @@ -1167,12 +1167,12 @@ def set_convention(units='SI',values='standard',frequency='rad'): _names[new_name] = _names.pop(name) #-- convert the switches in this module to the new convention #for switch in _switch: - + constants._current_convention = units constants._current_values = values #-- when we set everything back to SI, make sure we have no rounding errors: if units=='SI' and values=='standard' and frequency=='rad': - reload(constants) + imp.reload(constants) logger.warning('Reloading of constants') logger.info('Changed convention to {0} with values from {1} set'.format(units,values)) return to_return @@ -1186,24 +1186,24 @@ def reset(): def get_convention(): """ Returns convention and name of set of values. - + >>> get_convention() ('SI', 'standard', 'rad') - + @rtype: str,str @return: convention, values """ return constants._current_convention,constants._current_values,constants._current_frequency - + def get_constant(constant_name,units='SI',value='standard'): """ Convenience function to retrieve the value of a constant in a particular system. - + >>> Msol = get_constant('Msol','SI') >>> Msol = get_constant('Msol','kg') - + @param constant_name: name of the constant @type constant_name: str @param units: name of the unit base system @@ -1229,13 +1229,13 @@ def get_constants(units='SI',values='standard'): """ Convenience function to retrieve the value of all constants in a particular system. - + This can be helpful when you want to attach a copy of the fundamental constants to some module or class instances, and be B{sure} these values never change. - + Yes, I know, there's a lot that can go wrong with values that are supposed to be CONSTANT! - + @rtype: dict """ cvars = dir(constants) @@ -1244,10 +1244,10 @@ def get_constants(units='SI',values='standard'): for cvar in cvars: myconstants[cvar] = get_constant(cvar,units,values) return myconstants - - - - + + + + #} #{ Conversions basics and helper functions @@ -1256,9 +1256,9 @@ def get_constants(units='SI',values='standard'): def solve_aliases(unit): """ Resolve simple aliases in a unit's name. - + Resolves aliases and replaces division signs with negative power. - + @param unit: unit (e.g. erg s-1 angstrom-1) @type unit: str @return: aliases-resolved unit (e.g. erg s-1 AA-1) @@ -1267,7 +1267,7 @@ def solve_aliases(unit): #-- resolve aliases for alias in _aliases: unit = unit.replace(alias[0],alias[1]) - + #-- replace slash-forward with negative powers if '/' in unit: unit_ = [uni.split('/') for uni in unit.split()] @@ -1291,16 +1291,16 @@ def solve_aliases(unit): ravelled += uni unit = " ".join(ravelled) return unit - + def components(unit): """ Decompose a unit into: factor, SI base units, power. - + You probably want to call L{solve_aliases} first. - + Examples: - + >>> factor1,units1,power1 = components('m') >>> factor2,units2,power2 = components('g2') >>> factor3,units3,power3 = components('hg3') @@ -1308,7 +1308,7 @@ def components(unit): >>> factor5,units5,power5 = components('mm') >>> factor6,units6,power6 = components('W3') >>> factor7,units7,power7 = components('s-2') - + >>> print "{0}, {1}, {2}".format(factor1,units1,power1) 1.0, m, 1 >>> print "{0}, {1}, {2}".format(factor2,units2,power2) @@ -1323,7 +1323,7 @@ def components(unit): 1.0, m2 kg1 s-3, 3 >>> print "{0}, {1}, {2}".format(factor7,units7,power7) 1.0, s, -2 - + @param unit: unit name @type unit: str @return: 3-tuple with factor, SI base unit and power @@ -1340,7 +1340,7 @@ def components(unit): if m is not None: factor *= float(m.group(0)[:2])**(float(m.group(0)[3]+'1')*float(m.group(0)[4:6])) unit = unit[6:] - + if not unit[-1].isdigit(): unit += '1' #-- decompose unit in base name and power #m = re.search(r'(\d*)(.+?)(-{0,1}\d+)',unit) @@ -1358,7 +1358,7 @@ def components(unit): # (e.g., 'mu') and a unit name (e.g. 'm')) into prefix and unit name #-- check if basis is part of _factors dictionary. If not, find the # combination of _scalings and basis which is inside the dictionary! - + for scale in _scalings: scale_unit,base_unit = basis[:len(scale)],basis[len(scale):] if scale_unit==scale and base_unit in _factors and not basis in _factors: @@ -1372,14 +1372,14 @@ def components(unit): else: if not basis in _factors: raise ValueError('Unknown unit %s'%(basis)) - + #-- switch from base units to SI units if hasattr(_factors[basis][0],'__call__'): factor = factor*_factors[basis][0]() else: factor *= _factors[basis][0] basis = _factors[basis][1] - + return factor,basis,power @@ -1387,21 +1387,21 @@ def components(unit): def breakdown(unit): """ Decompose a unit into SI base units containing powers. - + Examples: - + >>> factor1,units1 = breakdown('erg s-1 W2 kg2 cm-2') >>> factor2,units2 = breakdown('erg s-1 cm-2 AA-1') >>> factor3,units3 = breakdown('W m-3') - - + + >>> print "{0}, {1}".format(factor1,units1) 0.001, kg5 m4 s-9 >>> print "{0}, {1}".format(factor2,units2) 10000000.0, kg1 m-1 s-3 >>> print "{0}, {1}".format(factor3,units3) 1.0, kg1 m-1 s-3 - + @param unit: unit's name @type unit: str @return: 2-tuple factor, unit's base name @@ -1430,7 +1430,7 @@ def breakdown(unit): else: total_units.append(basis_) total_power.append(power_*power) - + #-- make sure to return a sorted version total_units = sorted(['%s%s'%(i,j) for i,j in zip(total_units,total_power) if j!=0]) return total_factor," ".join(total_units) @@ -1438,33 +1438,33 @@ def breakdown(unit): def compress(unit,ignore_factor=False): """ Compress basic units to more human-readable type. - + If you are only interested in a shorter form of the units, regardless of the values, you need to set C{ignore_factor=True}. - + For example: erg/s/cm2/AA can be written shorter as W1 m-3, but 1 erg/s/cm2/AA is not equal to 1 W1 m-3. Thus: - + >>> print(compress('erg/s/cm2/AA',ignore_factor=True)) W1 m-3 >>> print(compress('erg/s/cm2/AA',ignore_factor=False)) erg/s/cm2/AA - + You cannot write the latter shorter without converting the units! - + For the same reasoning, compressing non-basic units will not work out: - + >>> print(compress('Lsol')) Lsol >>> print(compress('Lsol',ignore_factor=True)) - W1 - + W1 + So actually, this function is really designed to compress a combination of basic units: - + >>> print(compress('kg1 m-1 s-3')) W1 m-3 - + @param unit: combination of units to compress @type unit: str @param ignore_factor: if True, then a `sloppy compress' will be performed, @@ -1473,7 +1473,7 @@ def compress(unit,ignore_factor=False): @type ignore_factor: bool @rtype: str @return: shorter version of original units - + """ #-- if input is not a basic unit (or is not trivially expandable as # a basic unit, do nothing) @@ -1493,13 +1493,13 @@ def compress(unit,ignore_factor=False): new_unit = ' '.join([y[1],left_over]) if len(new_unit)>> y = 'AA A kg-2 F-11 Wb-1' >>> split_units_powers(y) (['AA', 'A', 'kg', 'F', 'Wb'], ['1', '1', '-2', '-11', '-1']) @@ -1515,7 +1515,7 @@ def split_units_powers(unit): >>> y = '10angstrom A0.5/kg2 5F-11/100Wb2.5' >>> split_units_powers(y) (['10AA', 'A', 'kg', '5F', '100Wb'], ['1', '0.5', '-2', '-11', '-2.5']) - + @param unit: unit @type unit: str @rtype: list,list @@ -1530,14 +1530,14 @@ def split_units_powers(unit): unit = ' '.join([comp[-1].isdigit() and comp or comp+'1' for comp in unit.split()]) #-- now split and remove spaces units = [i.strip() for i in units.findall(unit)] - powers = [i.strip() for i in powers.findall(unit)] + powers = [i.strip() for i in powers.findall(unit)] return units,powers - + def is_basic_unit(unit,type): """ Check if a unit is represents one of the basic quantities (length, mass...) - + >>> is_basic_unit('K','temperature') True >>> is_basic_unit('m','length') @@ -1546,7 +1546,7 @@ def is_basic_unit(unit,type): True >>> is_basic_unit('km','length') # not a basic unit in any type of convention False - + @parameter unit: unit to check @type unit: str @parameter type: type to check @@ -1562,7 +1562,7 @@ def is_basic_unit(unit,type): def is_type(unit,type): """ Check if a unit is of a certain type (mass, length, force...) - + >>> is_type('K','temperature') True >>> is_type('m','length') @@ -1581,7 +1581,7 @@ def is_type(unit,type): True >>> is_type('W','force') False - + @parameter unit: unit to check @type unit: str @parameter type: type to check @@ -1598,13 +1598,13 @@ def is_type(unit,type): #-- via names if comps in _names and _names[comps]==type: return True - + return False def get_type(unit): """ Return human readable name of the unit - + @rtype: str """ comps = breakdown(unit)[1] @@ -1619,15 +1619,15 @@ def get_type(unit): def round_arbitrary(x, base=5): """ Round to an arbitrary base. - + Example usage: - + >>> round_arbitrary(1.24,0.25) 1.25 >>> round_arbitrary(1.37,0.75) 1.5 - - + + @param x: number to round @type x: float @param base: base to round to @@ -1640,9 +1640,9 @@ def round_arbitrary(x, base=5): def unit2texlabel(unit,full=False): """ Convert a unit string to a nicely formatted TeX label. - + For fluxes, also the Flambda, lambda Flambda, or Fnu, or nuFnu is returned. - + @param unit: unit name @type unit: str @param full: return "name [unit]" or only "unit" @@ -1655,7 +1655,7 @@ def unit2texlabel(unit,full=False): powers = [(power!='1' and power or ' ') for power in powers] powers = [(power[0]!=' ' and '$^{{{0}}}$'.format(power) or power) for power in powers] unit_ = ' '.join([(name+power) for name,power in zip(names,powers)]) - + translate = {'kg1 m-1 s-3' :r'$F_\lambda$ [{0}]'.format(unit_), 'cy-1 kg1 s-2':r'$F_\nu$ [{0}]'.format(unit_), 'kg1 s-3':r'$\lambda F_\lambda$ [{0}]'.format(unit_), @@ -1670,12 +1670,12 @@ def unit2texlabel(unit,full=False): label = label.replace('AA','$\AA$') label = label.replace('mu','$\mu$') label = label.replace('sol','$_\odot$') - + if full: label = '{0} [{1}]'.format(get_type(unit).title(),label.strip()) - - return label - + + return label + @@ -1683,7 +1683,7 @@ def unit2texlabel(unit,full=False): def get_help(): """ Return a string with a list and explanation of all defined units. - + @rtype: str """ try: @@ -1702,9 +1702,9 @@ def get_help(): text[i%2] += help_text[key] out = '' #for i,j in itertools.zip_longest(*text,fillvalue=''): # for Python 3 - for i,j in itertools.izip_longest(*text,fillvalue=''): + for i,j in itertools.zip_longest(*text,fillvalue=''): out += '%s| %s\n'%(i,j) - + return out def units2sphinx(): @@ -1718,22 +1718,22 @@ def units2sphinx(): text.append("| {0:20s} | {1:50} | {2:20s} | {3:30s} | {4:50s} |".format(key,*_factors[key])) text.append(divider) return "\n".join(text) - + def derive_wrapper(fctn): @functools.wraps(fctn) def func(*args,**kwargs): argspec = inspect.getargspec(fctn) - print argspec + print(argspec) result = fctn(*args,**kwargs) return result return func #} #{ Linear change-of-base conversions - + def distance2velocity(arg,**kwargs): """ Switch from distance to velocity via a reference wavelength. - + @param arg: distance (SI, m) @type arg: float @keyword wave: reference wavelength (SI, m) @@ -1751,7 +1751,7 @@ def distance2velocity(arg,**kwargs): def velocity2distance(arg,**kwargs): """ Switch from velocity to distance via a reference wavelength. - + @param arg: velocity (SI, m/s) @type arg: float @keyword wave: reference wavelength (SI, m) @@ -1769,10 +1769,10 @@ def velocity2distance(arg,**kwargs): def fnu2flambda(arg,**kwargs): """ Switch from Fnu to Flambda via a reference wavelength. - + Flambda and Fnu are spectral irradiance in wavelength and frequency, respectively - + @param arg: spectral irradiance (SI,W/m2/Hz) @type arg: float @keyword photband: photometric passband @@ -1804,10 +1804,10 @@ def fnu2flambda(arg,**kwargs): def flambda2fnu(arg,**kwargs): """ Switch from Flambda to Fnu via a reference wavelength. - + Flambda and Fnu are spectral irradiance in wavelength and frequency, respectively - + @param arg: spectral irradiance (SI, W/m2/m) @type arg: float @keyword photband: photometric passband @@ -1839,10 +1839,10 @@ def flambda2fnu(arg,**kwargs): def fnu2nufnu(arg,**kwargs): """ Switch from Fnu to nuFnu via a reference wavelength. - + Flambda and Fnu are spectral irradiance in wavelength and frequency, respectively - + @param arg: spectral irradiance (SI,W/m2/Hz) @type arg: float @keyword photband: photometric passband @@ -1871,10 +1871,10 @@ def fnu2nufnu(arg,**kwargs): def nufnu2fnu(arg,**kwargs): """ Switch from nuFnu to Fnu via a reference wavelength. - + Flambda and Fnu are spectral irradiance in wavelength and frequency, respectively - + @param arg: spectral irradiance (SI,W/m2/Hz) @type arg: float @keyword photband: photometric passband @@ -1903,10 +1903,10 @@ def nufnu2fnu(arg,**kwargs): def flam2lamflam(arg,**kwargs): """ Switch from lamFlam to Flam via a reference wavelength. - + Flambda and Fnu are spectral irradiance in wavelength and frequency, respectively - + @param arg: spectral irradiance (SI,W/m2/Hz) @type arg: float @keyword photband: photometric passband @@ -1935,10 +1935,10 @@ def flam2lamflam(arg,**kwargs): def lamflam2flam(arg,**kwargs): """ Switch from lamFlam to Flam via a reference wavelength. - + Flambda and Fnu are spectral irradiance in wavelength and frequency, respectively - + @param arg: spectral irradiance (SI,W/m2/Hz) @type arg: float @keyword photband: photometric passband @@ -1967,7 +1967,7 @@ def lamflam2flam(arg,**kwargs): def distance2spatialfreq(arg,**kwargs): """ Switch from distance to spatial frequency via a reference wavelength. - + @param arg: distance (SI, m) @type arg: float @keyword photband: photometric passband @@ -1994,7 +1994,7 @@ def distance2spatialfreq(arg,**kwargs): def spatialfreq2distance(arg,**kwargs): """ Switch from spatial frequency to distance via a reference wavelength. - + @param arg: spatial frequency (SI, cy/as) @type arg: float @keyword photband: photometric passband @@ -2021,7 +2021,7 @@ def spatialfreq2distance(arg,**kwargs): def per_sr(arg,**kwargs): """ Switch from [Q] to [Q]/sr - + @param arg: some SI unit @type arg: float @return: some SI unit per steradian @@ -2044,7 +2044,7 @@ def per_sr(arg,**kwargs): def times_sr(arg,**kwargs): """ Switch from [Q]/sr to [Q] - + @param arg: some SI unit per steradian @type arg: float @return: some SI unit @@ -2067,7 +2067,7 @@ def times_sr(arg,**kwargs): def per_cy(arg,**kwargs): """ Switch from radians/s to cycle/s - + @rtype: float """ return arg / (2*np.pi) @@ -2075,7 +2075,7 @@ def per_cy(arg,**kwargs): def times_cy(arg,**kwargs): """ Switch from cycle/s to radians/s - + @rtype: float """ return 2*np.pi*arg @@ -2083,12 +2083,12 @@ def times_cy(arg,**kwargs): def period2freq(arg,**kwargs): """ Convert period to frequency and back. - + @rtype: float """ return 1./arg - + def do_nothing(arg,**kwargs): logger.warning('Experimental: probably just dropped the "cy" unit, please check results') @@ -2104,7 +2104,7 @@ def do_quad(arg,**kwargs): def Hz2_to_ss(change,frequency): """ Convert Frequency shift in Hz2 to period change in seconds per second. - + Frequency in Hz! """ #-- to second per second @@ -2119,50 +2119,50 @@ def Hz2_to_ss(change,frequency): def derive_luminosity(radius,temperature,units=None): """ Convert radius and effective temperature to stellar luminosity. - + Units given to radius and temperature must be understandable by C{Unit}. - + Stellar luminosity is returned, by default in Solar units. - + I recommend to give the input as follows, since this is most clear also from a programmatical point of view: - + >>> print(derive_luminosity((1.,'Rsol'),(5777,'K'),units='erg/s')) 3.83916979644e+33 erg/s - + The function is pretty smart, however: it knows what the output units should be, so you could ask to put them in the 'standard' units of some convention: - + >>> print(derive_luminosity((1.,'Rsol'),(5777,'K'),units='cgs')) 3.83916979644e+33 g1 cm2 s-3 - + If you give nothing, everything will be interpreted in the current convention's units: - + >>> print(derive_luminosity(7e8,5777.)) - 3.88892117726e+26 W1 - + 3.88892117726e+26 W1 + You are also free to give units (you can do this in a number of ways), and if you index the return value, you can get the value ([0], float or uncertainty) or unit ([1],string): - + >>> derive_luminosity((1.,'Rsol'),(1.,0.1,'Tsol'),units='Lsol')[0] 1.0000048246799305+/-0.4000019298719723 - + >>> derive_luminosity((1.,'Rsol'),((1.,0.1),'Tsol'),units='Lsol')[0] 1.0000048246799305+/-0.4000019298719723 - + Finally, you can simply work with Units: - + >>> radius = Unit(1.,'Rsol') >>> temperature = Unit(5777.,'K') >>> print(derive_luminosity(radius,temperature,units='foe/s')) 3.83916979644e-18 foe/s - + Everything should be independent of the current convention, unless it needs to be: - + >>> old = set_convention('cgs') >>> derive_luminosity((1.,'Rsol'),(1.,0.1,'Tsol'),units='Lsol')[0] 1.0000048246799305+/-0.4000019298719722 @@ -2174,7 +2174,7 @@ def derive_luminosity(radius,temperature,units=None): >>> print(derive_luminosity(1.,1.,units='Lsol')) 1.00000482468 Lsol >>> old = set_convention(*old) - + @param radius: (radius(, error), units) @type radius: 2 or 3 tuple, Unit or float (current convention) @param temperature: (effective temperature(, error), units) @@ -2187,27 +2187,27 @@ def derive_luminosity(radius,temperature,units=None): radius = Unit(radius,unit='length') temperature = Unit(temperature,unit='temperature') sigma = Unit('sigma') - + lumi = 4*np.pi*sigma*radius**2*temperature**4 - + return lumi.convert(units) - + def derive_radius(luminosity,temperature, units='m'): """ Convert luminosity and effective temperature to stellar radius. - + Units given to luminosity and temperature must be understandable by C{convert}. - + Stellar radius is returned in SI units. - + Example usage: - + >>> print(derive_radius((3.9,'[Lsol]'),(3.72,'[K]'),units='Rsol')) 108.091293736 Rsol >>> print(derive_radius(3.8e26,5777.,units='Rjup')) 9.89759670363 Rjup - + @param luminosity: (Luminosity(, error), units) @type luminosity: 2 or 3 tuple @param temperature: (effective temperature(, error), units) @@ -2220,20 +2220,20 @@ def derive_radius(luminosity,temperature, units='m'): luminosity = Unit(luminosity,unit='power') temperature = Unit(temperature,unit='temperature') sigma = Unit('sigma') - + R = np.sqrt(luminosity / (temperature**4)/(4*np.pi*sigma)) - - return R.convert(units) + + return R.convert(units) def derive_radius_slo(numax,Deltanu0,teff,unit='Rsol'): """ Derive stellar radius from solar-like oscillations diagnostics. - + Large separation is frequency spacing between modes of different radial order. - + Small separation is spacing between modes of even and odd angular degree but same radial order. - + @param numax: (numax(, error), units) @type numax: 2 or 3 tuple @param Deltanu0: (large separation(, error), units) @@ -2247,19 +2247,19 @@ def derive_radius_slo(numax,Deltanu0,teff,unit='Rsol'): Deltanu0_sol = convert(constants.Deltanu0_sol[-1],'muHz',*constants.Deltanu0_sol[:-1],unpack=False) #-- take care of temperature if len(teff)==3: - teff = (unumpy.uarray([teff[0],teff[1]]),teff[2]) + teff = (unumpy.uarray(*[teff[0],teff[1]]),teff[2]) teff = convert(teff[-1],'5777K',*teff[:-1],unpack=False) #-- take care of numax if len(numax)==3: - numax = (unumpy.uarray([numax[0],numax[1]]),numax[2]) + numax = (unumpy.uarray(*[numax[0],numax[1]]),numax[2]) numax = convert(numax[-1],'mHz',*numax[:-1],unpack=False) #-- take care of large separation if len(Deltanu0)==3: - Deltanu0 = (unumpy.uarray([Deltanu0[0],Deltanu0[1]]),Deltanu0[2]) + Deltanu0 = (unumpy.uarray(*[Deltanu0[0],Deltanu0[1]]),Deltanu0[2]) Deltanu0 = convert(Deltanu0[-1],'muHz',*Deltanu0[:-1],unpack=False) R = sqrt(teff)/numax_sol * numax/Deltanu0**2 * Deltanu0_sol**2 - return convert('Rsol',unit,R) - + return convert('Rsol',unit,R) + #@derive_wrapper def derive_radius_rot(veq_sini,rot_period,incl=(90.,'deg'),unit='Rsol'): """ @@ -2268,18 +2268,18 @@ def derive_radius_rot(veq_sini,rot_period,incl=(90.,'deg'),unit='Rsol'): incl = Unit(incl) radius = veq_sini*rot_period/(2*np.pi)/np.sin(incl) return radius - - - + + + def derive_logg(mass,radius, unit='[cm/s2]'): """ Convert mass and radius to stellar surface gravity. - + Units given to mass and radius must be understandable by C{convert}. - + Logarithm of surface gravity is returned in CGS units. - + @param mass: (mass(, error), units) @type mass: 2 or 3 tuple @param radius: (radius(, error), units) @@ -2289,30 +2289,30 @@ def derive_logg(mass,radius, unit='[cm/s2]'): """ #-- take care of mass if len(mass)==3: - mass = (unumpy.uarray([mass[0],mass[1]]),mass[2]) + mass = (unumpy.uarray(*[mass[0],mass[1]]),mass[2]) M = convert(mass[-1],'g',*mass[:-1],unpack=False) #-- take care of radius if len(radius)==3: - radius = (unumpy.uarray([radius[0],radius[1]]),radius[2]) + radius = (unumpy.uarray(*[radius[0],radius[1]]),radius[2]) R = convert(radius[-1],'cm',*radius[:-1],unpack=False) #-- calculate surface gravity in logarithmic CGS units logg = log10(constants.GG_cgs*M / (R**2)) logg = convert('[cm/s2]',unit,logg) return logg - - + + def derive_logg_zg(mass, zg, unit='cm s-2', **kwargs): """ Convert mass and gravitational redshift to stellar surface gravity. Provide the gravitational redshift as a velocity. - + Units given to mass and zg must be understandable by C{convert}. - + Logarithm of surface gravity is returned in CGS units unless otherwhise stated in unit. - + >>> print derive_logg_zg((0.47, 'Msol'), (2.57, 'km/s')) 5.97849814386 - + @param mass: (mass(, error), units) @type mass: 2 or 3 tuple @param zg: (zg(, error), units) @@ -2322,27 +2322,27 @@ def derive_logg_zg(mass, zg, unit='cm s-2', **kwargs): @return: log g (and error) @rtype: 1- or 2-tuple """ - + #-- take care of mass if len(mass)==3: - mass = (unumpy.uarray([mass[0],mass[1]]),mass[2]) + mass = (unumpy.uarray(*[mass[0],mass[1]]),mass[2]) M = convert(mass[-1],'g',*mass[:-1],unpack=False) #-- take care of zg if len(zg)==3: - zg = (unumpy.uarray([zg[0],zg[1]]),zg[2]) + zg = (unumpy.uarray(*[zg[0],zg[1]]),zg[2]) gr = convert(zg[-1],'cm s-1',*zg[:-1],unpack=False, **kwargs) - + logg = np.log10( gr**2 * constants.cc_cgs**2 / (constants.GG_cgs * M) ) return convert('cm s-2', unit, logg) def derive_logg_slo(teff,numax, unit='[cm/s2]'): """ Derive stellar surface gravity from solar-like oscillations diagnostics. - + Units given to teff and numax must be understandable by C{convert}. - + Logarithm of surface gravity is returned in CGS units. - + @param teff: (effective temperature(, error), units) @type teff: 2 or 3 tuple @param numax: (numax(, error), units) @@ -2353,17 +2353,17 @@ def derive_logg_slo(teff,numax, unit='[cm/s2]'): numax_sol = convert(constants.numax_sol[-1],'mHz',*constants.numax_sol[:-1],unpack=False) #-- take care of temperature if len(teff)==3: - teff = (unumpy.uarray([teff[0],teff[1]]),teff[2]) + teff = (unumpy.uarray(*[teff[0],teff[1]]),teff[2]) teff = convert(teff[-1],'5777K',*teff[:-1],unpack=False) #-- take care of numax if len(numax)==3: - numax = (unumpy.uarray([numax[0],numax[1]]),numax[2]) + numax = (unumpy.uarray(*[numax[0],numax[1]]),numax[2]) numax = convert(numax[-1],'mHz',*numax[:-1],unpack=False) #-- calculate surface gravity in logarithmic CGS units GG = convert(constants.GG_units,'Rsol3 Msol-1 s-2',constants.GG) surf_grav = GG*sqrt(teff)*numax / numax_sol logg = convert('Rsol s-2',unit,surf_grav) - return logg + return logg def derive_mass(surface_gravity,radius,unit='kg'): """ @@ -2371,27 +2371,27 @@ def derive_mass(surface_gravity,radius,unit='kg'): """ #-- take care of logg if len(surface_gravity)==3: - surface_gravity = (unumpy.uarray([surface_gravity[0],surface_gravity[1]]),surface_gravity[2]) + surface_gravity = (unumpy.uarray(*[surface_gravity[0],surface_gravity[1]]),surface_gravity[2]) grav = convert(surface_gravity[-1],'m/s2',*surface_gravity[:-1],unpack=False) #-- take care of radius if len(radius)==3: - radius = (unumpy.uarray([radius[0],radius[1]]),radius[2]) + radius = (unumpy.uarray(*[radius[0],radius[1]]),radius[2]) R = convert(radius[-1],'m',*radius[:-1],unpack=False) #-- calculate mass in SI M = grav*R**2/constants.GG return convert('kg',unit,M) - + def derive_zg(mass, logg, unit='cm s-1', **kwargs): """ - Convert mass and stellar surface gravity to gravitational redshift. - + Convert mass and stellar surface gravity to gravitational redshift. + Units given to mass and logg must be understandable by C{convert}. - + Gravitational redshift is returned in CGS units unless otherwhise stated in unit. - + >>> print derive_zg((0.47, 'Msol'), (5.98, 'cm s-2'), unit='km s-1') 2.57444756874 - + @param mass: (mass(, error), units) @type mass: 2 or 3 tuple @param logg: (logg(, error), units) @@ -2403,35 +2403,35 @@ def derive_zg(mass, logg, unit='cm s-1', **kwargs): """ #-- take care of mass if len(mass)==3: - mass = (unumpy.uarray([mass[0],mass[1]]),mass[2]) + mass = (unumpy.uarray(*[mass[0],mass[1]]),mass[2]) M = convert(mass[-1],'g',*mass[:-1],unpack=False) #-- take care of logg if len(logg)==3: - logg = (unumpy.uarray([logg[0],logg[1]]),logg[2]) + logg = (unumpy.uarray(*[logg[0],logg[1]]),logg[2]) g = convert(logg[-1],'cm s-2',*logg[:-1],unpack=False, **kwargs) - + zg = 10**g / constants.cc_cgs * np.sqrt(constants.GG_cgs * M / 10**g) return convert('cm s-1', unit, zg) def derive_numax(mass,radius,temperature,unit='mHz'): """ Derive the predicted nu_max according to Kjeldsen and Bedding (1995). - + Example: compute the predicted numax for the Sun in mHz >>> print derive_numax((1.,'Msol'),(1.,'Rsol'),(5777.,'K')) 3.05 """ #-- take care of mass if len(mass)==3: - mass = (unumpy.uarray([mass[0],mass[1]]),mass[2]) + mass = (unumpy.uarray(*[mass[0],mass[1]]),mass[2]) M = convert(mass[-1],'Msol',*mass[:-1],unpack=False) #-- take care of radius if len(radius)==3: - radius = (unumpy.uarray([radius[0],radius[1]]),radius[2]) + radius = (unumpy.uarray(*[radius[0],radius[1]]),radius[2]) R = convert(radius[-1],'Rsol',*radius[:-1],unpack=False) #-- take care of effective temperature if len(temperature)==3: - temperature = (unumpy.uarray([temperature[0],temperature[1]]),temperature[2]) + temperature = (unumpy.uarray(*[temperature[0],temperature[1]]),temperature[2]) teff = convert(temperature[-1],'5777K',*temperature[:-1],unpack=False) #-- predict nu_max nu_max = M/R**2/sqrt(teff)*3.05 @@ -2440,42 +2440,42 @@ def derive_numax(mass,radius,temperature,unit='mHz'): def derive_nmax(mass,radius,temperature): """ Derive the predicted n_max according to Kjeldsen and Bedding (1995). - + Example: compute the predicted numax for the Sun in mHz >>> print derive_nmax((1.,'Msol'),(1.,'Rsol'),(5777.,'K')) 21.0 """ #-- take care of mass if len(mass)==3: - mass = (unumpy.uarray([mass[0],mass[1]]),mass[2]) + mass = (unumpy.uarray(*[mass[0],mass[1]]),mass[2]) M = convert(mass[-1],'Msol',*mass[:-1]) #-- take care of radius if len(radius)==3: - radius = (unumpy.uarray([radius[0],radius[1]]),radius[2]) + radius = (unumpy.uarray(*[radius[0],radius[1]]),radius[2]) R = convert(radius[-1],'Rsol',*radius[:-1]) #-- take care of effective temperature if len(temperature)==3: - temperature = unumpy.uarray([temperature[0],temperature[1]]) + temperature = unumpy.uarray(*[temperature[0],temperature[1]]) teff = convert(temperature[-1],'5777K',*temperature[:-1]) #-- predict n_max n_max = sqrt(M/teff/R)*22.6 - 1.6 return n_max - + def derive_Deltanu0(mass,radius,unit='mHz'): """ Derive the predicted large spacing according to Kjeldsen and Bedding (1995). - + Example: compute the predicted large spacing for the Sun in mHz >>> print derive_Deltanu0((1.,'Msol'),(1.,'Rsol'),unit='muHz') 134.9 """ #-- take care of mass if len(mass)==3: - mass = (unumpy.uarray([mass[0],mass[1]]),mass[2]) + mass = (unumpy.uarray(*[mass[0],mass[1]]),mass[2]) M = convert(mass[-1],'Msol',*mass[:-1]) #-- take care of radius if len(radius)==3: - radius = (unumpy.uarray([radius[0],radius[1]]),radius[2]) + radius = (unumpy.uarray(*[radius[0],radius[1]]),radius[2]) R = convert(radius[-1],'Rsol',*radius[:-1]) #-- predict large spacing Deltanu0 = sqrt(M)*R**(-1.5)*134.9 @@ -2484,73 +2484,73 @@ def derive_Deltanu0(mass,radius,unit='mHz'): def derive_ampllum(luminosity,mass,temperature,wavelength,unit='ppm'): """ Derive the luminosity amplitude around nu_max of solar-like oscillations. - + See Kjeldsen and Bedding (1995). - + >>> print derive_ampllum((1.,'Lsol'),(1,'Msol'),(5777.,'K'),(550,'nm')) (4.7, 0.3) """ #-- take care of mass if len(mass)==3: - mass = (unumpy.uarray([mass[0],mass[1]]),mass[2]) + mass = (unumpy.uarray(*[mass[0],mass[1]]),mass[2]) M = convert(mass[-1],'Msol',*mass[:-1]) #-- take care of effective temperature if len(temperature)==3: - temperature = unumpy.uarray([temperature[0],temperature[1]]) + temperature = unumpy.uarray(*[temperature[0],temperature[1]]) teff = convert(temperature[-1],'5777K',*temperature[:-1]) #-- take care of luminosity if len(luminosity)==3: - luminosity = unumpy.uarray([luminosity[0],luminosity[1]]) + luminosity = unumpy.uarray(*[luminosity[0],luminosity[1]]) lumi = convert(luminosity[-1],'Lsol',*luminosity[:-1]) #-- take care of wavelength if len(wavelength)==3: - wavelength = unumpy.uarray([wavelength[0],wavelength[1]]) + wavelength = unumpy.uarray(*[wavelength[0],wavelength[1]]) wave = convert(wavelength[-1],'550nm',*wavelength[:-1]) - ampllum = lumi / wave / teff**2 / M * ufloat((4.7,0.3)) + ampllum = lumi / wave / teff**2 / M * ufloat(4.7,0.3) return convert('ppm',unit,ampllum) def derive_ampllum_from_velo(velo,temperature,wavelength,unit='ppm'): """ Derive the luminosity amplitude around nu_max of solar-like oscillations. - + See Kjeldsen and Bedding (1995). - + >>> print derive_ampllum_from_velo((1.,'m/s'),(5777.,'K'),(550,'nm')) (20.1, 0.05) """ #-- take care of velocity if len(velo)==3: - velo = (unumpy.uarray([velo[0],velo[1]]),velo[2]) + velo = (unumpy.uarray(*[velo[0],velo[1]]),velo[2]) velo = convert(velo[-1],'m/s',*velo[:-1]) #-- take care of effective temperature if len(temperature)==3: - temperature = unumpy.uarray([temperature[0],temperature[1]]) + temperature = unumpy.uarray(*[temperature[0],temperature[1]]) teff = convert(temperature[-1],'5777K',*temperature[:-1]) #-- take care of wavelength if len(wavelength)==3: - wavelength = unumpy.uarray([wavelength[0],wavelength[1]]) + wavelength = unumpy.uarray(*[wavelength[0],wavelength[1]]) wave = convert(wavelength[-1],'550nm',*wavelength[:-1]) - ampllum = velo / wave / teff**2 * ufloat((20.1,0.05)) #not sure about error + ampllum = velo / wave / teff**2 * ufloat(20.1,0.05) #not sure about error return convert('ppm',unit,ampllum) def derive_amplvel(luminosity,mass,unit='cm/s'): """ Derive the luminosity amplitude around nu_max of solar-like oscillations. - + See Kjeldsen and Bedding (1995). - + >>> print derive_amplvel((1.,'Lsol'),(1,'Msol'),unit='cm/s') (23.4, 1.4) """ #-- take care of mass if len(mass)==3: - mass = (unumpy.uarray([mass[0],mass[1]]),mass[2]) + mass = (unumpy.uarray(*[mass[0],mass[1]]),mass[2]) M = convert(mass[-1],'Msol',*mass[:-1]) #-- take care of luminosity if len(luminosity)==3: - luminosity = unumpy.uarray([luminosity[0],luminosity[1]]) + luminosity = unumpy.uarray(*[luminosity[0],luminosity[1]]) lumi = convert(luminosity[-1],'Lsol',*luminosity[:-1]) - amplvel = lumi / M * ufloat((23.4,1.4)) + amplvel = lumi / M * ufloat(23.4,1.4) return convert('cm/s',unit,amplvel) def derive_galactic_uvw(ra, dec, pmra, pmdec, d, vrad, lsr=False, unit='km s-1'): @@ -2558,20 +2558,20 @@ def derive_galactic_uvw(ra, dec, pmra, pmdec, d, vrad, lsr=False, unit='km s-1') Calculate the Galactic space velocity (U,V,W) of a star based on the propper motion, location, radial velocity and distance. (U,V,W) are returned in km/s unless stated otherwhise in unit. - + Follows the general outline of Johnson & Soderblom (1987, AJ, 93,864) - except that U is positive outward toward the Galactic *anti*center, and - the J2000 transformation matrix to Galactic coordinates is taken from + except that U is positive outward toward the Galactic *anti*center, and + the J2000 transformation matrix to Galactic coordinates is taken from the introduction to the Hipparcos catalog. - + Uses solar motion from Coskunoglu et al. 2011 MNRAS: (U,V,W)_Sun = (-8.5, 13.38, 6.49) - + Example for HD 6755: - + >>> derive_galactic_uvw((17.42943586, 'deg'), (61.54727506, 'deg'), (628.42, 'mas yr-1'), (76.65, 'mas yr-1'), (139, 'pc'), (-321.4, 'km s-1'), lsr=True) (142.66328352779027, -483.55149105148121, 93.216106970932813) - + @param ra: Right assension of the star @param dec: Declination of the star @param pmra: propper motion in RA @@ -2580,34 +2580,34 @@ def derive_galactic_uvw(ra, dec, pmra, pmdec, d, vrad, lsr=False, unit='km s-1') @param vrad: radial velocity @param lsr: if True, correct for solar motion @param unit: units for U,V and W - + @return: (U,V,W) """ #-- take care of ra if len(ra)==3: - ra = (unumpy.uarray([ra[0],ra[1]]),ra[2]) + ra = (unumpy.uarray(*[ra[0],ra[1]]),ra[2]) ra = convert(ra[-1],'deg',*ra[:-1]) #-- take care of dec if len(dec)==3: - dec = (unumpy.uarray([dec[0],dec[1]]),dec[2]) + dec = (unumpy.uarray(*[dec[0],dec[1]]),dec[2]) dec = convert(dec[-1],'deg',*dec[:-1]) #-- take care of pmra if len(pmra)==3: - pmra = (unumpy.uarray([pmra[0],pmra[1]]),pmra[2]) + pmra = (unumpy.uarray(*[pmra[0],pmra[1]]),pmra[2]) pmra = convert(pmra[-1],'mas yr-1',*pmra[:-1]) #-- take care of pmdec if len(pmdec)==3: - pmdec = (unumpy.uarray([pmdec[0],pmdec[1]]),pmdec[2]) + pmdec = (unumpy.uarray(*[pmdec[0],pmdec[1]]),pmdec[2]) pmdec = convert(pmdec[-1],'mas yr-1',*pmdec[:-1]) #-- take care of d if len(d)==3: - d = (unumpy.uarray([d[0],d[1]]),d[2]) + d = (unumpy.uarray(*[d[0],d[1]]),d[2]) d = convert(d[-1],'pc',*d[:-1]) #-- take care of pmdec if len(vrad)==3: - vrad = (unumpy.uarray([vrad[0],vrad[1]]),vrad[2]) + vrad = (unumpy.uarray(*[vrad[0],vrad[1]]),vrad[2]) vrad = convert(vrad[-1],'km s-1',*vrad[:-1]) - + plx = 1e3 / d # parallax in mas cosd = np.cos(np.radians(dec)) @@ -2615,9 +2615,9 @@ def derive_galactic_uvw(ra, dec, pmra, pmdec, d, vrad, lsr=False, unit='km s-1') cosa = np.cos(np.radians(ra)) sina = np.sin(np.radians(ra)) - k = 4.74047 #Equivalent of 1 A.U/yr in km/s - A_G = np.array( [ [ 0.0548755604, +0.4941094279, -0.8676661490], - [ 0.8734370902, -0.4448296300, -0.1980763734], + k = 4.74047 #Equivalent of 1 A.U/yr in km/s + A_G = np.array( [ [ 0.0548755604, +0.4941094279, -0.8676661490], + [ 0.8734370902, -0.4448296300, -0.1980763734], [ 0.4838350155, 0.7469822445, +0.4559837762] ]).T vec1 = vrad @@ -2633,7 +2633,7 @@ def derive_galactic_uvw(ra, dec, pmra, pmdec, d, vrad, lsr=False, unit='km s-1') w = ( A_G[2,0]*cosa*cosd+A_G[2,1]*sina*cosd+A_G[2,2]*sind)*vec1+ \ (-A_G[2,0]*sina +A_G[2,1]*cosa )*vec2+ \ (-A_G[2,0]*cosa*sind-A_G[2,1]*sina*sind+A_G[2,2]*cosd)*vec3 - + if lsr: #lsr_vel=[-10.0,5.2,7.2] # Dehnen & Binney (1998) #lsr_vel=[-11.1, 12.24, 7.25] # Schonrich et al. (2010) @@ -2641,22 +2641,22 @@ def derive_galactic_uvw(ra, dec, pmra, pmdec, d, vrad, lsr=False, unit='km s-1') u = u+lsr_vel[0] v = v+lsr_vel[1] w = w+lsr_vel[2] - + return convert('km s-1',unit,u), convert('km s-1',unit,v), convert('km s-1',unit,w) def convert_Z_FeH(Z=None, FeH=None, Zsun=0.0122): """ Convert between Z and [Fe/H] using the conversion of Bertelli et al. (1994). Provide one of the 2 arguments, and the other will be returned. - + log10(Z/Zsun) = 0.977 [Fe/H] """ - + if Z != None: return np.log10(Z/Zsun) / 0.977 else: return 10**(0.977*FeH + np.log10(Zsun)) - + #} @@ -2667,9 +2667,9 @@ def convert_Z_FeH(Z=None, FeH=None, Zsun=0.0122): class NonLinearConverter(): """ Base class for nonlinear conversions - + This class keeps track of prefix-factors and powers. - + To have a real nonlinear converter, you need to define the C{__call__} attribute. """ @@ -2719,12 +2719,16 @@ class VegaMag(NonLinearConverter): def __call__(self,meas,photband=None,inv=False,**kwargs): #-- this part should include something where the zero-flux is retrieved zp = filters.get_info() - match = zp['photband']==photband.upper() - if sum(match)==0: raise ValueError("No calibrations for %s"%(photband)) + match = zp['photband'] == photband.upper() + if not any(match): + raise ValueError("No calibrations for %s"%(photband)) + return F0 = convert(zp['Flam0_units'][match][0],'W/m3',zp['Flam0'][match][0]) mag0 = float(zp['vegamag'][match][0]) - if not inv: return 10**(-(meas-mag0)/2.5)*F0 - else: return -2.5*log10(meas/F0)+mag0 + if not inv: + return 10**(-(meas-mag0)/2.5)*F0 + else: + return -2.5*log10(meas/F0)+mag0 class ABMag(NonLinearConverter): """ @@ -2734,54 +2738,63 @@ def __call__(self,meas,photband=None,inv=False,**kwargs): zp = filters.get_info() F0 = convert('W/m2/Hz',constants._current_convention,3.6307805477010024e-23) match = zp['photband']==photband.upper() - if sum(match)==0: raise ValueError("No calibrations for %s"%(photband)) + if not any(match): + raise ValueError("No calibrations for %s"%(photband)) + return mag0 = float(zp['ABmag'][match][0]) - if np.isnan(mag0): mag0 = 0. + if np.isnan(mag0): + mag0 = 0. if not inv: try: return 10**(-(meas-mag0)/2.5)*F0 except OverflowError: return np.nan - else: return -2.5*log10(meas/F0) + else: + return -2.5*log10(meas/F0) class STMag(NonLinearConverter): """ Convert an ST magnitude to W/m2/m (Flambda) and back - + mag = -2.5*log10(F) - 21.10 - + F0 = 3.6307805477010028e-09 erg/s/cm2/AA """ def __call__(self,meas,photband=None,inv=False,**kwargs): zp = filters.get_info() F0 = convert('erg/s/cm2/AA',constants._current_convention,3.6307805477010028e-09)#0.036307805477010027 match = zp['photband']==photband.upper() - if sum(match)==0: raise ValueError("No calibrations for %s"%(photband)) + if not any(match): + raise ValueError("No calibrations for %s"%(photband)) + return mag0 = float(zp['STmag'][match][0]) - if np.isnan(mag0): mag0 = 0. - if not inv: return 10**(-(meas-mag0)/-2.5)*F0 - else: return -2.5*log10(meas/F0) + if np.isnan(mag0): + mag0 = 0. + if not inv: + return 10**(-(meas-mag0)/-2.5)*F0 + else: + return -2.5*log10(meas/F0) class Color(NonLinearConverter): """ Convert a color to a flux ratio and back - + B-V = -2.5log10(FB) + CB - (-2.5log10(FV) + CV) B-V = -2.5log10(FB) + CB + 2.5log10(FV) - CV B-V = -2.5log10(FB/FV) + (CB-CV) - + and thus - + FB/FV = 10 ** [((B-V) - (CB-CV)) / (-2.5)] - + where - + CB = 2.5log10[FB(m=0)] CV = 2.5log10[FV(m=0)] - + Stromgren colour indices: - + m1 = v - 2b + y c1 = u - 2v + b Hbeta = HBN - HBW @@ -2830,7 +2843,7 @@ def __call__(self,meas,photband=None,inv=False,**kwargs): class DecibelSPL(NonLinearConverter): """ Convert a Decibel to intensity W/m2 and back. - + This is only valid for Decibels as a sound Pressure level """ def __call__(self,meas,inv=False): @@ -2854,7 +2867,7 @@ def __call__(self,meas,inv=False,**kwargs): #L= J//11 #month = J+2-12*L #year = 100*(N-49)+I+L - + j = meas + 0.5 + 32044 g = np.floor(j/146097) dg = np.fmod(j,146097) @@ -2875,7 +2888,7 @@ def __call__(self,meas,inv=False,**kwargs): year,month,day = meas[:3] hour = len(meas)>3 and meas[3] or 0. mint = len(meas)>4 and meas[4] or 0. - secn = len(meas)>5 and meas[5] or 0. + secn = len(meas)>5 and meas[5] or 0. a = (14 - month)//12 y = year + 4800 - a m = month + 12*a - 3 @@ -2889,7 +2902,7 @@ def __call__(self,meas,inv=False,**kwargs): class ModJulianDay(NonLinearConverter): """ Convert a Modified Julian Day to Julian Day and back - + The CoRoT conversion has been checked with the CoRoT data archive: it is correct at least to the second (the archive tool's precision). """ @@ -2974,124 +2987,124 @@ def __call__(self,mycoord,inv=False): return x + 1j*y #} -#{ Currencies -@memoized -def set_exchange_rates(): - """ - Download currency exchange rates from the European Central Bank. - """ - myurl = 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml' - url = urllib.URLopener() - #url = urllib.request.URLopener() # for Python 3 - logger.info('Downloading current exchanges rates from ecb.europa.eu') - filen,msg = url.retrieve(myurl) - ff = open(filen,'r') - for line in ff.readlines(): - if '') - ff.close() - #-- now also retrieve the name of the currencies: - myurl = 'http://www.ecb.europa.eu/stats/exchange/eurofxref/html/index.en.html' - url = urllib.URLopener() - #url = urllib.request.URLopener() # for Python 3 - logger.info('Downloading information on currency names from ecb.europa.eu') - filen,msg = url.retrieve(myurl) - ff = open(filen,'r') - gotcurr = False - for line in ff.readlines(): - if gotcurr: - name = line.split('>')[1].split('<')[0] - if curr in _factors: - _factors[curr] = (_factors[curr][0],_factors[curr][1],_factors[curr][2],name) - gotcurr = False - if '') +# ff.close() +# #-- now also retrieve the name of the currencies: +# myurl = 'http://www.ecb.europa.eu/stats/exchange/eurofxref/html/index.en.html' +# url = urllib.request.URLopener() +# #url = urllib.request.URLopener() # for Python 3 +# logger.info('Downloading information on currency names from ecb.europa.eu') +# filen,msg = url.retrieve(myurl) +# ff = open(filen,'r') +# gotcurr = False +# for line in ff.readlines(): +# if gotcurr: +# name = line.split('>')[1].split('<')[0] +# if curr in _factors: +# _factors[curr] = (_factors[curr][0],_factors[curr][1],_factors[curr][2],name) +# gotcurr = False +# if '>> a = Unit(2.,'m') >>> b = Unit(4.,'km') >>> c = Unit(3.,'cm2') - + And you can calculate via: - + >>> print a*b 8000.0 m2 >>> print a/c 6666.66666667 m-1 >>> print a+b 4002.0 m - + B{Example 1:} You want to calculate the equatorial velocity of the Sun: - + >>> distance = Unit(2*np.pi,'Rsol') >>> time = Unit(22.,'d') >>> print (distance/time) 2299.03495719 m1 s-1 - + or directly to km/s: - + >>> print (distance/time).convert('km/s') 2.29903495719 km/s - + and with uncertainties: - + >>> distance = Unit((2*np.pi,0.1),'Rsol') >>> print (distance/time).convert('km/s') 2.29903495719+/-0.0365902777778 km/s - + B{Example 2}: The surface gravity of the Sun: - + >>> G = Unit('GG') >>> M = Unit('Msol') >>> R = Unit('Rsol') >>> logg = np.log10((G*M/R**2).convert('cgs')) >>> print logg - 4.43830739117 - - or - + 4.43830739117 + + or + >>> G = Unit('GG') >>> M = Unit(1.,'Msol') >>> R = Unit(1.,'Rsol') >>> logg = np.log10((G*M/R**2).convert('cgs')) >>> print logg - 4.43830739117 - - or - + 4.43830739117 + + or + >>> old = set_convention('cgs') - + >>> G = Unit('GG') >>> M = Unit((1.,0.01),'Msol') >>> R = Unit((1.,0.01),'Rsol') >>> logg = np.log10(G*M/R**2) >>> print logg - 4.43830739117+/-0.00971111983789 - + 4.43830739117+/-0.00971111983789 + >>> old = set_convention('SI') - + B{Example 3}: The speed of light in vacuum: - + >>> eps0 = Unit('eps0') >>> mu0 = Unit('mu0') >>> cc = Unit('cc') @@ -3105,13 +3118,13 @@ class Unit(object): >>> print cc_ 299792458.004 m1 s-1 >>> print cc_/cc - 1.00000000001 - + 1.00000000001 + """ def __new__(cls,*args,**kwargs): """ Create a new Unit instance. - + Overriding the default __new__ method makes sure that it is possible to initialize a Unit with an existing Unit instance. In that case, the original instance will simply be returned, and the @@ -3122,33 +3135,33 @@ def __new__(cls,*args,**kwargs): return super(Unit, cls).__new__(cls) else: return args[0] - + def __init__(self,value,unit=None,**kwargs): """ Different ways to initialize a Unit. - + Without uncertainties: - + >>> x1 = Unit(5.,'m') >>> x4 = Unit((5.,'m')) >>> x5 = Unit(5.,'length') - + The latter only works with the basic names ('length', 'time', 'temperature', etc..., and assures that the value is interpreted correctly in the current convention (SI,cgs...) - + With uncertainties: - + >>> x2 = Unit((5.,1.),'m') >>> x3 = Unit((5.,1.,'m')) - + Initiating with an existing instance will not do anything (not even making a copy! Only a new reference to the original object is made): - + >>> x6 = Unit(x3) - + This is the output from printing the examples above: - + >>> print x1 5.0 m >>> print x2 @@ -3172,11 +3185,11 @@ def __init__(self,value,unit=None,**kwargs): value = value[:-1] if len(value)==1: value = value[0] - #-- set the input values + #-- set the input values self.value = value self.unit = unit self.kwargs = kwargs - #-- make sure we can calculate with defined constants + #-- make sure we can calculate with defined constants if isinstance(self.value,str) and hasattr(constants,self.value): self.unit = getattr(constants,self.value+'_units') self.value = getattr(constants,self.value) @@ -3184,8 +3197,8 @@ def __init__(self,value,unit=None,**kwargs): try: self.value = ufloat(self.value) except: - self.value = unumpy.uarray(self.value) - + self.value = unumpy.uarray(*self.value) + #-- values and units to work with if self.unit is not None: #-- perhaps someone says the unit is of "type" length. If so, @@ -3203,11 +3216,11 @@ def __init__(self,value,unit=None,**kwargs): if self.unit and len(self.unit) and self.unit[0]=='[': self.value = 10**self.value self.unit = self.unit[1:-1] - + def convert(self,unit,compress=True): """ Convert this Unit to different units. - + By default, the converted unit will be compressed (i.e. it will probably not consist of only basic units, but be more readable). """ @@ -3220,17 +3233,17 @@ def convert(self,unit,compress=True): if compress: new_unit.compress() return new_unit - + def compress(self): """ Compress current unit to a more readable version. """ self.unit = compress(self.unit) return self - + def as_tuple(self): return self[0],self[1] - + def get_value(self): """ Returns (value,error) in case of uncertainties. @@ -3240,7 +3253,7 @@ def get_value(self): if not val.shape: val = float(val) if not err.shape: err = float(err) return val,err - + def __getitem__(self,key): """ Implements indexing, [0] returns value, [1] return unit. @@ -3249,20 +3262,20 @@ def __getitem__(self,key): return self.value elif key==1: return self.unit - + def __lt__(self,other): """ Compare SI-values of Units with eacht other. """ return self.convert('SI')[0]>> x = Unit(5.,'m15') >>> y = Unit(2.5,'m6.5') >>> print(x/y) @@ -3347,7 +3360,7 @@ def __div__(self,other): outcome = self.value/other new_unit = self.unit return Unit(outcome,new_unit) - + def __rdiv__(self,other): if hasattr(other,'unit'): unit1 = change_convention(constants._current_convention,self.unit) @@ -3384,11 +3397,11 @@ def __rdiv__(self,other): fac,new_unit = breakdown(new_unit) outcome = other/value1 return Unit(outcome,new_unit) - + def __pow__(self,power): """ Raise a unit to a power: - + >>> x = Unit(5.,'m') >>> print(x**0.5) 2.2360679775 m0.5 @@ -3396,7 +3409,7 @@ def __pow__(self,power): 0.2 m-1 >>> print(x**3) 125.0 m3 - + >>> x = Unit(9.,'m11') >>> print(x**0.5) 3.0 m5.5 @@ -3412,7 +3425,7 @@ def __pow__(self,power): #new_unit = ' '.join(power*[self._basic_unit]) #fac,new_unit = breakdown(new_unit) #return Unit(self._SI_value**power,new_unit) - + def sin(self): return Unit(sin(self.convert('rad').value),'') def cos(self): return Unit(cos(self.convert('rad').value),'') def tan(self): return Unit(tan(self.convert('rad').value),'') @@ -3423,19 +3436,19 @@ def log10(self): return Unit(log10(self.value),'') def log(self): return Unit(log(self.value),'') def exp(self): return Unit(exp(self.value),'') def sqrt(self): return self**0.5 - + def __str__(self): return '{0} {1}'.format(self.value,self.unit) - + def __repr__(self): return "Unit('{value}','{unit}')".format(value=repr(self.value),unit=self.unit) - - + + #} - + _fluxcalib = os.path.join(os.path.abspath(os.path.dirname(__file__)),'fluxcalib.dat') #-- basic units which the converter should know about _factors = collections.OrderedDict([ @@ -3459,7 +3472,7 @@ def __repr__(self): ('mi', (1609.344, 'm','length','mile (international)')), # mile (international) ('USmi', (1609.344, 'm','length','mile (US)')), # US mile or survey mile or statute mile ('nami', (1852., 'm','length','nautical mile')), # nautical mile - ('a0', (constants.a0, constants.a0_units,'length','Bohr radius')), # Bohr radius + ('U0', (constants.a0, constants.a0_units,'length','Bohr radius')), # Bohr radius ('ell', (1.143, 'm','length','ell')), # ell ('yd', (0.9144, 'm','length','yard (international)')), # yard (international) ('potrzebie',(0.002263348517438173216473,'m','length','potrzebie')), @@ -3487,7 +3500,7 @@ def __repr__(self): # TIME ('s', ( 1e+00, 's','time','second')), # second ('min', ( 60., 's','time','minute')), # minute - ('h', (3600., 's','time','hour')), # hour + ('h', (3600., 's','time','hour')), # hour ('d', (86400., 's','time','day')), # day ('wk', (7*24*3600., 's','time','week')), # week ('mo', (30*24*3600., 's','time','month')), # month @@ -3618,7 +3631,7 @@ def __repr__(self): electric_current='A',lum_intens='cd',amount='mol',currency='EUR'), # solar 'imperial':dict(mass='lb',length='yd',time='s',temperature='K', electric_current='A',lum_intens='cd',amount='mol',currency='GBP'), # Imperial (UK/US) system - } + } #-- some names of combinations of units _names = {'m1':'length', @@ -3629,7 +3642,7 @@ def __repr__(self): 'K':'temperature', 'm1 s-2':'acceleration', 'kg1 s-2':'surface tension', - 'cy-1 kg1 s-2':'flux density', # W/m2/Hz + 'cy-1 kg1 s-2':'flux density', # W/m2/Hz 'kg1 m-1 s-3':'flux density', # W/m3 'kg1 s-3':'flux', # W/m2 'cy1 s-1':'frequency', @@ -3657,7 +3670,7 @@ def __repr__(self): 'm-2 cd':'illuminance', } -#-- scaling factors for prefixes +#-- scaling factors for prefixes _scalings ={'y': 1e-24, # yocto 'z': 1e-21, # zepto 'a': 1e-18, # atto @@ -3680,7 +3693,7 @@ def __repr__(self): 'Z': 1e+21, # zetta 'Y': 1e+24 # yotta } - + #-- some common aliases _aliases = [('micron','mum'),('au','AU'),('lbs','lb'), ('micro','mu'),('milli','m'),('kilo','k'),('mega','M'),('giga','G'), @@ -3701,7 +3714,7 @@ def __repr__(self): ('solMass','Msol'),('solLum','Lsol'), ('pk','hp'),('mph','mi/h'),('f.u.','fu') ] - + #-- Change-of-base function definitions _switch = {'s1_to_': distance2velocity, # switch from wavelength to velocity 's-1_to_': velocity2distance, # switch from wavelength to velocity @@ -3732,20 +3745,19 @@ def __repr__(self): 'cy2_to_': do_nothing, 'cy-2_to_': do_nothing, } - - + + if __name__=="__main__": - + if not sys.argv[1:]: import doctest doctest.testmod() quit() from optparse import OptionParser, Option, OptionGroup - import datetime import copy - + logger = loggers.get_basic_logger() - + #-- make sure we can parse strings as Python code def check_pythoncode(option, opt, value): try: @@ -3758,16 +3770,16 @@ def check_pythoncode(option, opt, value): class MyOption (Option): TYPES = Option.TYPES + ("pythoncode",) TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER) - TYPE_CHECKER["pythoncode"] = check_pythoncode + TYPE_CHECKER["pythoncode"] = check_pythoncode usage = "List of available units:\n" + get_help() + "\nUsage: %prog --from= --to= [options] value [error]" - + #-- define all the input parameters parser = OptionParser(option_class=MyOption,usage=usage) parser.add_option('--from',dest='_from',type='str', help="units to convert from",default='SI') parser.add_option('--to',dest='_to',type='str', help="units to convert to",default='SI') - + group = OptionGroup(parser,'Extra quantities when changing base units (e.g. Flambda to Fnu)') group.add_option('--wave','-w',dest='wave',type='str', help="wavelength with units (e.g. used to convert Flambda to Fnu)",default=None) @@ -3776,13 +3788,13 @@ class MyOption (Option): group.add_option('--photband','-p',dest='photband',type='str', help="photometric passband",default=None) parser.add_option_group(group) - + #-- prepare inputs for functions (options, args) = parser.parse_args() options = vars(options) _from = options.pop('_from') _to = options.pop('_to') - + #-- in case of normal floats or floats with errors if not any([',' in i for i in args]): args = tuple([float(i) for i in args]) @@ -3798,7 +3810,7 @@ class MyOption (Option): if isinstance(options[option],str) and ',' in options[option]: entry = options[option].split(',') options[option] = (float(entry[0]),entry[1]) - + #-- check if currencies are asked. If so, download the latest exchange rates # we cannot exactly know if something is a currency before we download all # the names, and we don't want to download the names if no currencies are @@ -3807,10 +3819,10 @@ class MyOption (Option): to_is_probably_currency = (hasattr(_to,'isupper') and _to.isupper() and len(_to)==3) if from_is_probably_currency or to_is_probably_currency: set_exchange_rates() - + #-- do the conversion output = convert(_from,_to,*args,**options) - + #-- and nicely print to the screen if _to=='SI': fac,_to = breakdown(_from) diff --git a/units/uncertainties/__init__.py b/units/uncertainties/__init__.py deleted file mode 100644 index 0eaaea83b..000000000 --- a/units/uncertainties/__init__.py +++ /dev/null @@ -1,1548 +0,0 @@ -#!/usr/bin/env python - -#!! When the documentation below is updated, setup.py should be -# checked for consistency. - -''' -Calculations with full error propagation for quantities with uncertainties. -Derivatives can also be calculated. - -Web user guide: http://packages.python.org/uncertainties/. - -Example of possible calculation: (0.2 +/- 0.01)**2 = 0.04 +/- 0.004. - -Correlations between expressions are correctly taken into account (for -instance, with x = 0.2+/-0.01, 2*x-x-x is exactly zero, as is y-x-x -with y = 2*x). - -Examples: - - import uncertainties - from uncertainties import ufloat - from uncertainties.umath import * # sin(), etc. - - # Mathematical operations: - x = ufloat((0.20, 0.01)) # x = 0.20+/-0.01 - x = ufloat("0.20+/-0.01") # Other representation - x = ufloat("0.20(1)") # Other representation - x = ufloat("0.20") # Implicit uncertainty of +/-1 on the last digit - print x**2 # Square: prints "0.04+/-0.004" - print sin(x**2) # Prints "0.0399...+/-0.00399..." - - print x.position_in_sigmas(0.17) # Prints "-3.0": deviation of -3 sigmas - - # Access to the nominal value, and to the uncertainty: - square = x**2 # Square - print square # Prints "0.04+/-0.004" - print square.nominal_value # Prints "0.04" - print square.std_dev() # Prints "0.004..." - - print square.derivatives[x] # Partial derivative: 0.4 (= 2*0.20) - - # Correlations: - u = ufloat((1, 0.05), "u variable") # Tag - v = ufloat((10, 0.1), "v variable") - sum_value = u+v - - u.set_std_dev(0.1) # Standard deviations can be updated on the fly - print sum_value - u - v # Prints "0.0" (exact result) - - # List of all sources of error: - print sum_value # Prints "11+/-0.1414..." - for (var, error) in sum_value.error_components().iteritems(): - print "%s: %f" % (var.tag, error) # Individual error components - - # Covariance matrices: - cov_matrix = uncertainties.covariance_matrix([u, v, sum_value]) - print cov_matrix # 3x3 matrix - - # Correlated variables can be constructed from a covariance matrix, if - # NumPy is available: - (u2, v2, sum2) = uncertainties.correlated_values([1, 10, 11], - cov_matrix) - print u2 # Value and uncertainty of u: correctly recovered (1+/-0.1) - print uncertainties.covariance_matrix([u2, v2, sum2]) # == cov_matrix - -- The main function provided by this module is ufloat, which creates -numbers with uncertainties (Variable objects). Variable objects can -be used as if they were regular Python numbers. The main attributes -and methods of Variable objects are defined in the documentation of -the Variable class. - -- Valid operations on numbers with uncertainties include basic -mathematical functions (addition, etc.). - -Most operations from the standard math module (sin, etc.) can be applied -on numbers with uncertainties by using their generalization from the -uncertainties.umath module: - - from uncertainties.umath import sin - print sin(ufloat("1+/-0.01")) # 0.841...+/-0.005... - print sin(1) # umath.sin() also works on floats, exactly like math.sin() - -Logical operations (>, ==, etc.) are also supported. - -Basic operations on NumPy arrays or matrices of numbers with -uncertainties can be performed: - - 2*numpy.array([ufloat((1, 0.01)), ufloat((2, 0.1))]) - -More complex operations on NumPy arrays can be performed through the -dedicated uncertainties.unumpy sub-module (see its documentation). - -Calculations that are performed through non-Python code (Fortran, C, -etc.) can handle numbers with uncertainties instead of floats through -the provided wrap() wrapper: - - import uncertainties - - # wrapped_f is a version of f that can take arguments with - # uncertainties, even if f only takes floats: - wrapped_f = uncertainties.wrap(f) - -If some derivatives of the wrapped function f are known (analytically, -or numerically), they can be given to wrap()--see the documentation -for wrap(). - -- Utility functions are also provided: the covariance matrix between -random variables can be calculated with covariance_matrix(), or used -as input for the definition of correlated quantities (correlated_values() -function--defined only if the NumPy module is available). - -- Mathematical expressions involving numbers with uncertainties -generally return AffineScalarFunc objects, which also print as a value -with uncertainty. Their most useful attributes and methods are -described in the documentation for AffineScalarFunc. Note that -Variable objects are also AffineScalarFunc objects. UFloat is an -alias for AffineScalarFunc, provided as a convenience: testing whether -a value carries an uncertainty handled by this module should be done -with insinstance(my_value, UFloat). - -- Mathematically, numbers with uncertainties are, in this package, -probability distributions. These probabilities are reduced to two -numbers: a nominal value and an uncertainty. Thus, both variables -(Variable objects) and the result of mathematical operations -(AffineScalarFunc objects) contain these two values (respectively in -their nominal_value attribute and through their std_dev() method). - -The uncertainty of a number with uncertainty is simply defined in -this package as the standard deviation of the underlying probability -distribution. - -The numbers with uncertainties manipulated by this package are assumed -to have a probability distribution mostly contained around their -nominal value, in an interval of about the size of their standard -deviation. This should cover most practical cases. A good choice of -nominal value for a number with uncertainty is thus the median of its -probability distribution, the location of highest probability, or the -average value. - -- When manipulating ensembles of numbers, some of which contain -uncertainties, it can be useful to access the nominal value and -uncertainty of all numbers in a uniform manner: - - x = ufloat("3+/-0.1") - print nominal_value(x) # Prints 3 - print std_dev(x) # Prints 0.1 - print nominal_value(3) # Prints 3: nominal_value works on floats - print std_dev(3) # Prints 0: std_dev works on floats - -- Probability distributions (random variables and calculation results) -are printed as: - - nominal value +/- standard deviation - -but this does not imply any property on the nominal value (beyond the -fact that the nominal value is normally inside the region of high -probability density), or that the probability distribution of the -result is symmetrical (this is rarely strictly the case). - -- Linear approximations of functions (around the nominal values) are -used for the calculation of the standard deviation of mathematical -expressions with this package. - -The calculated standard deviations and nominal values are thus -meaningful approximations as long as the functions involved have -precise linear expansions in the region where the probability -distribution of their variables is the largest. It is therefore -important that uncertainties be small. Mathematically, this means -that the linear term of functions around the nominal values of their -variables should be much larger than the remaining higher-order terms -over the region of significant probability. - -For instance, sin(0+/-0.01) yields a meaningful standard deviation -since it is quite linear over 0+/-0.01. However, cos(0+/-0.01) yields -an approximate standard deviation of 0 (because the cosine is not well -approximated by a line around 0), which might not be precise enough -for all applications. - -- Comparison operations (>, ==, etc.) on numbers with uncertainties -have a pragmatic semantics, in this package: numbers with -uncertainties can be used wherever Python numbers are used, most of -the time with a result identical to the one that would be obtained -with their nominal value only. However, since the objects defined in -this module represent probability distributions and not pure numbers, -comparison operator are interpreted in a specific way. - -The result of a comparison operation ("==", ">", etc.) is defined so as -to be essentially consistent with the requirement that uncertainties -be small: the value of a comparison operation is True only if the -operation yields True for all infinitesimal variations of its random -variables, except, possibly, for an infinitely small number of cases. - -Example: - - "x = 3.14; y = 3.14" is such that x == y - -but - - x = ufloat((3.14, 0.01)) - y = ufloat((3.14, 0.01)) - -is not such that x == y, since x and y are independent random -variables that almost never give the same value. However, x == x -still holds. - -The boolean value (bool(x), "if x...") of a number with uncertainty x -is the result of x != 0. - -- The uncertainties package is for Python 2.5 and above. - -- This package contains tests. They can be run either manually or -automatically with the nose unit testing framework (nosetests). - -(c) 2009-2010 by Eric O. LEBIGOT (EOL) . -Please send feature requests, bug reports, or feedback to this address. - -Please support future development by donating $5 or more through PayPal! - -This software is released under a dual license. (1) The BSD license. -(2) Any other license, as long as it is obtained from the original -author.''' - -# The idea behind this module is to replace the result of mathematical -# operations by a local approximation of the defining function. For -# example, sin(0.2+/-0.01) becomes the affine function -# (AffineScalarFunc object) whose nominal value is sin(0.2) and -# whose variations are given by sin(0.2+delta) = 0.98...*delta. -# Uncertainties can then be calculated by using this local linear -# approximation of the original function. - -from __future__ import division # Many analytical derivatives depend on this - -import re -import math -from math import sqrt, log # Optimization: no attribute look-up -import copy - -# Numerical version: -__version_info__ = (1, 7, 1) -__version__ = '.'.join(str(num) for num in __version_info__) - -__author__ = 'Eric O. LEBIGOT (EOL)' - -# Attributes that are always exported (some other attributes are -# exported only if the NumPy module is available...): -__all__ = [ - - # All sub-modules and packages are not imported by default, - # because NumPy could not be installed: - - 'ufloat', # Main function: returns a number with uncertainty - - # Uniform access to nominal values and standard deviations: - 'nominal_value', - 'std_dev', - - # Utility functions (more are exported if NumPy is present): - 'covariance_matrix', - - # Class for testing whether an object is a number with - # uncertainty. Not usually created by users (except through the - # Variable subclass), but possibly manipulated by external code - # ['derivatives()' method, etc.]. - 'UFloat', - - # Wrapper for allowing non-pure-Python function to handle - # quantitites with uncertainties: - 'wrap', - - # The documentation for wrap() indicates that numerical - # derivatives are calculated through partial_derivative(). The - # user might also want to change the size of the numerical - # differentiation step. - 'partial_derivative' - - ] - -############################################################################### - -def set_doc(doc_string): - """ - Decorator function that sets the docstring to the given text. - - It is useful for functions whose docstring is calculated - (including string substitutions). - """ - def set_doc_string(func): - func.__doc__ = doc_string - return func - return set_doc_string - -############################################################################### - -# Mathematical operations with local approximations (affine scalar -# functions) - -class NotUpcast(Exception): - pass - -def to_affine_scalar(x): - """ - Transforms x into a constant affine scalar function - (AffineScalarFunc), unless it is already an AffineScalarFunc (in - which case x is returned unchanged). - - Raises an exception unless 'x' belongs to some specific classes of - objects that are known not to depend on AffineScalarFunc objects - (which then cannot be considered as constants). - """ - - if isinstance(x, AffineScalarFunc): - return x - - #! In Python 2.6+, numbers.Number could be used instead, here: - if isinstance(x, (float, int, complex, long)): - # No variable => no derivative to define: - return AffineScalarFunc(x, {}) - - # Case of lists, etc. - raise NotUpcast("%s cannot be converted to a number with" - " uncertainty" % type(x)) - -def partial_derivative(f, param_num): - """ - Returns a function that numerically calculates the partial - derivative of function f with respect to its argument number - param_num. - - The step parameter represents the shift of the parameter used in - the numerical approximation. - """ - - def partial_derivative_of_f(*args): - """ - Partial derivative, calculated with the (-epsilon, +epsilon) - method, which is more precise than the (0, +epsilon) method. - """ - # f_nominal_value = f(*args) - - shifted_args = list(args) # Copy, and conversion to a mutable - - # The step is relative to the parameter being varied, so that - # shifting it does not suffer from finite precision: - step = 1e-8*abs(shifted_args[param_num]) - if not step: - step = 1e-8 # Arbitrary, but "small" with respect to 1 - - shifted_args[param_num] += step - shifted_f_plus = f(*shifted_args) - - shifted_args[param_num] -= 2*step # Optimization: only 1 list copy - shifted_f_minus = f(*shifted_args) - - return (shifted_f_plus - shifted_f_minus)/2/step - - return partial_derivative_of_f - -class NumericalDerivatives(object): - """ - Sequence with the successive numerical derivatives of a function. - """ - # This is a sequence and not a list because the number of - # arguments of the function is not known in advance, in general. - - def __init__(self, function): - """ - 'function' is the function whose derivatives can be computed. - """ - self._function = function - - def __getitem__(self, n): - """ - Returns the n-th numerical derivative of the function. - """ - return partial_derivative(self._function, n) - -def wrap(f, derivatives_funcs=None): - """ - Wraps function f so that, when applied to numbers with - uncertainties (AffineScalarFunc objects) or float-like arguments, - f returns a local approximation of its values (in the form of an - object of class AffineScalarFunc). In this case, if none of the - arguments of f involves variables [i.e. Variable objects], f - simply returns its usual result. - - When f is not called on AffineScalarFunc or float-like - arguments, the original result of f is returned. - - If supplied, 'derivatives_funcs' is a sequence or iterator that - generally contains functions; each successive function is the - partial derivatives of f with respect to the corresponding - variable (one function for each argument of f, which takes as many - arguments as f). If derivatives_funcs is None, or if - derivatives_funcs contains a finite number of elements, then - missing derivatives are calculated numerically through - partial_derivative(). - - Example: wrap(math.sin) is a sine function that can be applied to - numbers with uncertainties. Its derivative will be calculated - numerically. wrap(math.sin, [None]) would have produced the same - result. wrap(math.sin, [math.cos]) is the same function, but with - an analytically defined derivative. - """ - - if derivatives_funcs is None: - derivatives_funcs = NumericalDerivatives(f) - else: - # Derivatives that are not defined are calculated numerically, - # if there is a finite number of them (the function lambda - # *args: fsum(args) has a non-defined number of arguments, as - # it just performs a sum... - try: # Is the number of derivatives fixed? - len(derivatives_funcs) - except TypeError: - pass - else: - derivatives_funcs = [ - partial_derivative(f, k) if derivative is None - else derivative - for (k, derivative) in enumerate(derivatives_funcs)] - - #! Setting the doc string after "def f_with...()" does not - # seem to work. We define it explicitly: - @set_doc("""\ - Version of %s(...) that returns an affine approximation - (AffineScalarFunc object), if its result depends on variables - (Variable objects). Otherwise, returns a simple constant (when - applied to constant arguments). - - Warning: arguments of the function that are not AffineScalarFunc - objects must not depend on uncertainties.Variable objects in any - way. Otherwise, the dependence of the result in - uncertainties.Variable objects will be incorrect. - - Original documentation: - %s""" % (f.__name__, f.__doc__)) - def f_with_affine_output(*args): - # Can this function perform the calculation of an - # AffineScalarFunc (or maybe float) result? - try: - aff_funcs = map(to_affine_scalar, args) - - except NotUpcast: - - # This function does not now how to itself perform - # calculations with non-float-like arguments (as they - # might for instance be objects whose value really changes - # if some Variable objects had different values): - - # Is it clear that we can't delegate the calculation? - - if any(isinstance(arg, AffineScalarFunc) for arg in args): - # This situation arises for instance when calculating - # AffineScalarFunc(...)*numpy.array(...). In this - # case, we must let NumPy handle the multiplication - # (which is then performed element by element): - return NotImplemented - else: - # If none of the arguments is an AffineScalarFunc, we - # can delegate the calculation to the original - # function. This can be useful when it is called with - # only one argument (as in - # numpy.log10(numpy.ndarray(...)): - return f(*args) - - ######################################## - # Nominal value of the constructed AffineScalarFunc: - args_values = [e.nominal_value for e in aff_funcs] - f_nominal_value = f(*args_values) - - ######################################## - - # List of involved variables (Variable objects): - variables = set() - for expr in aff_funcs: - variables |= set(expr.derivatives) - - ## It is sometimes useful to only return a regular constant: - - # (1) Optimization / convenience behavior: when 'f' is called - # on purely constant values (e.g., sin(2)), there is no need - # for returning a more complex AffineScalarFunc object. - - # (2) Functions that do not return a "float-like" value might - # not have a relevant representation as an AffineScalarFunc. - # This includes boolean functions, since their derivatives are - # either 0 or are undefined: they are better represented as - # Python constants than as constant AffineScalarFunc functions. - - if not variables or isinstance(f_nominal_value, bool): - return f_nominal_value - - # The result of 'f' does depend on 'variables'... - - ######################################## - - # Calculation of the derivatives with respect to the arguments - # of f (aff_funcs): - - # The chain rule is applied. This is because, in the case of - # numerical derivatives, it allows for a better-controlled - # numerical stability than numerically calculating the partial - # derivatives through '[f(x + dx, y + dy, ...) - - # f(x,y,...)]/da' where dx, dy,... are calculated by varying - # 'a'. In fact, it is numerically better to control how big - # (dx, dy,...) are: 'f' is a simple mathematical function and - # it is possible to know how precise the df/dx are (which is - # not possible with the numerical df/da calculation above). - - # We use numerical derivatives, if we don't already have a - # list of derivatives: - - #! Note that this test could be avoided by requiring the - # caller to always provide derivatives. When changing the - # functions of the math module, this would force this module - # to know about all the math functions. Another possibility - # would be to force derivatives_funcs to contain, say, the - # first 3 derivatives of f. But any of these two ideas has a - # chance to break, one day... (if new functions are added to - # the math module, or if some function has more than 3 - # arguments). - - derivatives_wrt_args = [] - for (arg, derivative) in zip(aff_funcs, derivatives_funcs): - derivatives_wrt_args.append(derivative(*args_values) - if arg.derivatives - else 0) - - ######################################## - # Calculation of the derivative of f with respect to all the - # variables (Variable) involved. - - # Initial value (is updated below): - derivatives_wrt_vars = dict((var, 0.) for var in variables) - - # The chain rule is used (we already have - # derivatives_wrt_args): - - for (func, f_derivative) in zip(aff_funcs, derivatives_wrt_args): - for (var, func_derivative) in func.derivatives.iteritems(): - derivatives_wrt_vars[var] += f_derivative * func_derivative - - # The function now returns an AffineScalarFunc object: - return AffineScalarFunc(f_nominal_value, derivatives_wrt_vars) - - # It is easier to work with f_with_affine_output, which represents - # a wrapped version of 'f', when it bears the same name as 'f': - f_with_affine_output.__name__ = f.__name__ - - return f_with_affine_output - -def _force_aff_func_args(func): - """ - Takes an operator op(x, y) and wraps it. - - The constructed operator returns func(x, to_affine_scalar(y)) if y - can be upcast with to_affine_scalar(); otherwise, it returns - NotImplemented. - - Thus, func() is only called on two AffineScalarFunc objects, if - its first argument is an AffineScalarFunc. - """ - - def op_on_upcast_args(x, y): - """ - Returns %s(self, to_affine_scalar(y)) if y can be upcast - through to_affine_scalar. Otherwise returns NotImplemented. - """ % func.__name__ - - try: - y_with_uncert = to_affine_scalar(y) - except NotUpcast: - # This module does not know how to handle the comparison: - # (example: y is a NumPy array, in which case the NumPy - # array will decide that func() should be applied - # element-wise between x and all the elements of y): - return NotImplemented - else: - return func(x, y_with_uncert) - - return op_on_upcast_args - -######################################## - -# Definition of boolean operators, that assume that self and -# y_with_uncert are AffineScalarFunc. - -# The fact that uncertainties must be smalled is used, here: the -# comparison functions are supposed to be constant for most values of -# the random variables. - -# Even though uncertainties are supposed to be small, comparisons -# between 3+/-0.1 and 3.0 are handled (even though x == 3.0 is not a -# constant function in the 3+/-0.1 interval). The comparison between -# x and x is handled too, when x has an uncertainty. In fact, as -# explained in the main documentation, it is possible to give a useful -# meaning to the comparison operators, in these cases. - -def _eq_on_aff_funcs(self, y_with_uncert): - """ - __eq__ operator, assuming that both self and y_with_uncert are - AffineScalarFunc objects. - """ - difference = self - y_with_uncert - # Only an exact zero difference means that self and y are - # equal numerically: - return not(difference._nominal_value or difference.std_dev()) - -def _ne_on_aff_funcs(self, y_with_uncert): - """ - __ne__ operator, assuming that both self and y_with_uncert are - AffineScalarFunc objects. - """ - - return not _eq_on_aff_funcs(self, y_with_uncert) - -def _gt_on_aff_funcs(self, y_with_uncert): - """ - __gt__ operator, assuming that both self and y_with_uncert are - AffineScalarFunc objects. - """ - return self._nominal_value > y_with_uncert._nominal_value - -def _ge_on_aff_funcs(self, y_with_uncert): - """ - __ge__ operator, assuming that both self and y_with_uncert are - AffineScalarFunc objects. - """ - - return (_gt_on_aff_funcs(self, y_with_uncert) - or _eq_on_aff_funcs(self, y_with_uncert)) - -def _lt_on_aff_funcs(self, y_with_uncert): - """ - __lt__ operator, assuming that both self and y_with_uncert are - AffineScalarFunc objects. - """ - return self._nominal_value < y_with_uncert._nominal_value - -def _le_on_aff_funcs(self, y_with_uncert): - """ - __le__ operator, assuming that both self and y_with_uncert are - AffineScalarFunc objects. - """ - - return (_lt_on_aff_funcs(self, y_with_uncert) - or _eq_on_aff_funcs(self, y_with_uncert)) - -######################################## - -class AffineScalarFunc(object): - """ - Affine functions that support basic mathematical operations - (addition, etc.). Such functions can for instance be used for - representing the local (linear) behavior of any function. - - This class is mostly meant to be used internally. - - This class can also be used to represent constants. - - The variables of affine scalar functions are Variable objects. - - AffineScalarFunc objects include facilities for calculating the - 'error' on the function, from the uncertainties on its variables. - - Main attributes and methods: - - - nominal_value, std_dev(): value at the origin / nominal value, - and standard deviation. - - - error_components(): error_components()[x] is the error due to - Variable x. - - - derivatives: derivatives[x] is the (value of the) derivative - with respect to Variable x. This attribute is a dictionary - whose keys are the Variable objects on which the function - depends. - - All the Variable objects on which the function depends are in - 'derivatives'. - - - position_in_sigmas(x): position of number x with respect to the - nominal value, in units of the standard deviation. - """ - - # To save memory in large arrays: - __slots__ = ('_nominal_value', 'derivatives') - - #! The code could be modify in order to accommodate for non-float - # nominal values. This could for instance be done through - # the operator module: instead of delegating operations to - # float.__*__ operations, they could be delegated to - # operator.__*__ functions (while taking care of properly handling - # reverse operations: __radd__, etc.). - - def __init__(self, nominal_value, derivatives): - """ - nominal_value -- value of the function at the origin. - nominal_value must not depend in any way of the Variable - objects in 'derivatives' (the value at the origin of the - function being defined is a constant). - - derivatives -- maps each Variable object on which the function - being defined depends to the value of the derivative with - respect to that variable, taken at the nominal value of all - variables. - - Warning: the above constraint is not checked, and the user is - responsible for complying with it. - """ - - # Defines the value at the origin: - - # Only float-like values are handled. One reason is that it - # does not make sense for a scalar function to be affine to - # not yield float values. Another reason is that it would not - # make sense to have a complex nominal value, here (it would - # not be handled correctly at all): converting to float should - # be possible. - self._nominal_value = float(nominal_value) - self.derivatives = derivatives - - # The following prevents the 'nominal_value' attribute from being - # modified by the user: - @property - def nominal_value(self): - "Nominal value of the random number." - return self._nominal_value - - ############################################################ - - - ### Operators: operators applied to AffineScalarFunc and/or - ### float-like objects only are supported. This is why methods - ### from float are used for implementing these operators. - - # Operators with no reflection: - - ######################################## - - # __nonzero__() is supposed to return a boolean value (it is used - # by bool()). It is for instance used for converting the result - # of comparison operators to a boolean, in sorted(). If we want - # to be able to sort AffineScalarFunc objects, __nonzero__ cannot - # return a AffineScalarFunc object. Since boolean results (such - # as the result of bool()) don't have a very meaningful - # uncertainty unless it is zero, this behavior is fine. - - def __nonzero__(self): - """ - Equivalent to self != 0. - """ - #! This might not be relevant for AffineScalarFunc objects - # that contain values in a linear space which does not convert - # the float 0 into the null vector (see the __eq__ function: - # __nonzero__ works fine if subtracting the 0 float from a - # vector of the linear space works as if 0 were the null - # vector of that space): - return self != 0. # Uses the AffineScalarFunc.__ne__ function - - # Compatibility with Python 3: - __bool__ = __nonzero__ - - ######################################## - - ## Logical operators: warning: the resulting value cannot always - ## be differentiated. - - # The boolean operations are not differentiable everywhere, but - # almost... - - # (1) I can rely on the assumption that the user only has "small" - # errors on variables, as this is used in the calculation of the - # standard deviation (which performs linear approximations): - - # (2) However, this assumption is not relevant for some - # operations, and does not have to hold, in some cases. This - # comes from the fact that logical operations (e.g. __eq__(x,y)) - # are not differentiable for many usual cases. For instance, it - # is desirable to have x == x for x = n+/-e, whatever the size of e. - # Furthermore, n+/-e != n+/-e', if e != e', whatever the size of e or - # e'. - - # (3) The result of logical operators does not have to be a - # function with derivatives, as these derivatives are either 0 or - # don't exist (i.e., the user should probably not rely on - # derivatives for his code). - - # __eq__ is used in "if data in [None, ()]", for instance. It is - # therefore important to be able to handle this case too, which is - # taken care of when _force_aff_func_args(_eq_on_aff_funcs) - # returns NotImplemented. - __eq__ = _force_aff_func_args(_eq_on_aff_funcs) - - __ne__ = _force_aff_func_args(_ne_on_aff_funcs) - __gt__ = _force_aff_func_args(_gt_on_aff_funcs) - - # __ge__ is not the opposite of __lt__ because these operators do - # not always yield a boolean (for instance, 0 <= numpy.arange(10) - # yields an array). - __ge__ = _force_aff_func_args(_ge_on_aff_funcs) - - __lt__ = _force_aff_func_args(_lt_on_aff_funcs) - __le__ = _force_aff_func_args(_le_on_aff_funcs) - - ######################################## - - # Uncertainties handling: - - def error_components(self): - """ - Individual components of the standard deviation of the affine - function (in absolute value), returned as a dictionary with - Variable objects as keys. - - This method assumes that the derivatives contained in the - object take scalar values (and are not a tuple, like what - math.frexp() returns, for instance). - """ - - # Calculation of the variance: - error_components = {} - for (variable, derivative) in self.derivatives.iteritems(): - # Individual standard error due to variable: - error_components[variable] = abs(derivative*variable._std_dev) - - return error_components - - def std_dev(self): - """ - Standard deviation of the affine function. - - This method assumes that the function returns scalar results. - - This returned standard deviation depends on the current - standard deviations [std_dev()] of the variables (Variable - objects) involved. - """ - #! It would be possible to not allow the user to update the - #std dev of Variable objects, in which case AffineScalarFunc - #objects could have a pre-calculated or, better, cached - #std_dev value (in fact, many intermediate AffineScalarFunc do - #not need to have their std_dev calculated: only the final - #AffineScalarFunc returned to the user does). - return sqrt(sum( - delta**2 for delta in self.error_components().itervalues())) - - def _general_representation(self, to_string): - """ - Uses the to_string() conversion function on both the nominal - value and the standard deviation, and returns a string that - describes them. - - to_string() is typically repr() or str(). - """ - - (nominal_value, std_dev) = (self._nominal_value, self.std_dev()) - - # String representation: - - # Not putting spaces around "+/-" helps with arrays of - # Variable, as each value with an uncertainty is a - # block of signs (otherwise, the standard deviation can be - # mistaken for another element of the array). - - return ("%s+/-%s" % (to_string(nominal_value), to_string(std_dev)) - if std_dev - else to_string(nominal_value)) - - def __repr__(self): - return self._general_representation(repr) - - def __str__(self): - return self._general_representation(str) - - def position_in_sigmas(self, value): - """ - Returns 'value' - nominal value, in units of the standard - deviation. - - Raises a ValueError exception if the standard deviation is zero. - """ - try: - # The ._nominal_value is a float: there is no integer division, - # here: - return (value - self._nominal_value) / self.std_dev() - except ZeroDivisionError: - raise ValueError("The standard deviation is zero:" - " undefined result.") - - def __deepcopy__(self, memo): - """ - Hook for the standard copy module. - - The returned AffineScalarFunc is a completely fresh copy, - which is fully independent of any variable defined so far. - New variables are specially created for the returned - AffineScalarFunc object. - """ - return AffineScalarFunc( - self._nominal_value, - dict((copy.deepcopy(var), deriv) - for (var, deriv) in self.derivatives.iteritems())) - - def __getstate__(self): - """ - Hook for the pickle module. - """ - obj_slot_values = dict((k, getattr(self, k)) for k in - # self.__slots__ would not work when - # self is an instance of a subclass: - AffineScalarFunc.__slots__) - return obj_slot_values - - def __setstate__(self, data_dict): - """ - Hook for the pickle module. - """ - for (name, value) in data_dict.iteritems(): - setattr(self, name, value) - -# Nicer name, for users: isinstance(ufloat(...), UFloat) is True: -UFloat = AffineScalarFunc - -def get_ops_with_reflection(): - - """ - Returns operators with a reflection, along with their derivatives - (for float operands). - """ - - # Operators with a reflection: - - # We do not include divmod(). This operator could be included, by - # allowing its result (a tuple) to be differentiated, in - # derivative_value(). However, a similar result can be achieved - # by the user by calculating separately the division and the - # result. - - # {operator(x, y): (derivative wrt x, derivative wrt y)}: - - # Note that unknown partial derivatives can be numerically - # calculated by expressing them as something like - # "partial_derivative(float.__...__, 1)(x, y)": - - # String expressions are used, so that reversed operators are easy - # to code, and execute relatively efficiently: - - derivatives_list = { - 'add': ("1.", "1."), - # 'div' is the '/' operator when __future__.division is not in - # effect. Since '/' is applied to - # AffineScalarFunc._nominal_value numbers, it is applied on - # floats, and is therefore the "usual" mathematical division. - 'div': ("1/y", "-x/y**2"), - 'floordiv': ("0.", "0."), # Non exact: there is a discontinuities - # The derivative wrt the 2nd arguments is something like (..., x//y), - # but it is calculated numerically, for convenience: - 'mod': ("1.", "partial_derivative(float.__mod__, 1)(x, y)"), - 'mul': ("y", "x"), - 'pow': ("y*x**(y-1)", "log(x)*x**y"), - 'sub': ("1.", "-1."), - 'truediv': ("1/y", "-x/y**2") - } - - # Conversion to Python functions: - ops_with_reflection = {} - for (op, derivatives) in derivatives_list.iteritems(): - ops_with_reflection[op] = [ - eval("lambda x, y: %s" % expr) for expr in derivatives ] - - ops_with_reflection["r"+op] = [ - eval("lambda y, x: %s" % expr) for expr in reversed(derivatives)] - - return ops_with_reflection - -# Operators that have a reflexion, along with their derivatives: -_ops_with_reflection = get_ops_with_reflection() - -# Some effectively modified operators (for the automated tests): -_modified_operators = [] - -def add_operators_to_AffineScalarFunc(): - """ - Adds many operators (__add__, etc.) to the AffineScalarFunc class. - """ - - ######################################## - - #! Derivatives are set to return floats. For one thing, - # uncertainties generally involve floats, as they are based on - # small variations of the parameters. It is also better to - # protect the user from unexpected integer result that behave - # badly with the division. - - ## Operators that return a numerical value: - - # Single-argument operators that should be adapted from floats to - # AffineScalarFunc objects: - simple_numerical_operators_derivatives = { - 'abs': lambda x: 1. if x>=0 else -1., - 'neg': lambda x: -1., - 'pos': lambda x: 1., - 'trunc': lambda x: 0. - } - - for (op, derivative) in \ - simple_numerical_operators_derivatives.iteritems(): - - attribute_name = "__%s__" % op - # float objects don't exactly have the same attributes between - # different versions of Python (for instance, __trunc__ was - # introduced with Python 2.6): - if attribute_name in dir(float): - setattr(AffineScalarFunc, attribute_name, - wrap(getattr(float, attribute_name), - [derivative])) - _modified_operators.append(op) - - ######################################## - - # Reversed versions (useful for float*AffineScalarFunc, for instance): - for (op, derivatives) in _ops_with_reflection.iteritems(): - attribute_name = '__%s__' % op - setattr(AffineScalarFunc, attribute_name, - wrap(getattr(float, attribute_name), derivatives)) - - ######################################## - # Conversions to pure numbers are meaningless. Note that the - # behavior of float(1j) is similar. - for coercion_type in ('complex', 'int', 'long', 'float'): - def raise_error(self): - raise TypeError("can't convert an affine function (%s)" - ' to %s; use x.nominal_value' - # In case AffineScalarFunc is sub-classed: - % (self.__class__, coercion_type)) - - setattr(AffineScalarFunc, '__%s__' % coercion_type, raise_error) - -add_operators_to_AffineScalarFunc() # Actual addition of class attributes - -class Variable(AffineScalarFunc): - """ - Representation of a float-like scalar random variable, along with - its uncertainty. - """ - - # To save memory in large arrays: - __slots__ = ('_std_dev', 'tag') - - def __init__(self, value, std_dev, tag=None): - """ - The nominal value and the standard deviation of the variable - are set. These values must be scalars. - - 'tag' is a tag that the user can associate to the variable. This - is useful for tracing variables. - - The meaning of the nominal value is described in the main - module documentation. - """ - - #! The value, std_dev, and tag are assumed by __copy__() not to - # be copied. Either this should be guaranteed here, or __copy__ - # should be updated. - - # Only float-like values are handled. One reason is that the - # division operator on integers would not produce a - # differentiable functions: for instance, Variable(3, 0.1)/2 - # has a nominal value of 3/2 = 1, but a "shifted" value - # of 3.1/2 = 1.55. - value = float(value) - - # If the variable changes by dx, then the value of the affine - # function that gives its value changes by 1*dx: - - # ! Memory cycles are created. However, they are garbage - # collected, if possible. Using a weakref.WeakKeyDictionary - # takes much more memory. Thus, this implementation chooses - # more cycles and a smaller memory footprint instead of no - # cycles and a larger memory footprint. - - # ! Using AffineScalarFunc instead of super() results only in - # a 3 % speed loss (Python 2.6, Mac OS X): - super(Variable, self).__init__(value, {self: 1.}) - - # We force the error to be float-like. Since it is considered - # as a Gaussian standard deviation, it is semantically - # positive (even though there would be no problem defining it - # as a sigma, where sigma can be negative and still define a - # Gaussian): - - assert std_dev >= 0, "the error must be a positive number" - # Since AffineScalarFunc.std_dev is a property, we cannot do - # "self.std_dev = ...": - self._std_dev = std_dev - - self.tag = tag - - # Standard deviations can be modified (this is a feature). - # AffineScalarFunc objects that depend on the Variable have their - # std_dev() automatically modified (recalculated with the new - # std_dev of their Variables): - def set_std_dev(self, value): - """ - Updates the standard deviation of the variable to a new value. - """ - - # A zero variance is accepted. Thus, it is possible to - # conveniently use infinitely precise variables, for instance - # to study special cases. - - self._std_dev = value - - # The following method is overridden so that we can represent the tag: - def _general_representation(self, to_string): - """ - Uses the to_string() conversion function on both the nominal - value and standard deviation and returns a string that - describes the number. - - to_string() is typically repr() or str(). - """ - num_repr = super(Variable, self)._general_representation(to_string) - - # Optional tag: only full representations (to_string == repr) - # contain the tag, as the tag is required in order to recreate - # the variable. Outputting the tag for regular string ("print - # x") would be too heavy and produce an unusual representation - # of a number with uncertainty. - return (num_repr if ((self.tag is None) or (to_string != repr)) - else "< %s = %s >" % (self.tag, num_repr)) - - def __copy__(self): - """ - Hook for the standard copy module. - """ - - # This copy implicitly takes care of the reference of the - # variable to itself (in self.derivatives): the new Variable - # object points to itself, not to the original Variable. - - # Reference: http://www.doughellmann.com/PyMOTW/copy/index.html - - #! The following assumes that the arguments to Variable are - # *not* copied upon construction, since __copy__ is not supposed - # to copy "inside" information: - return Variable(self.nominal_value, self.std_dev(), self.tag) - - def __deepcopy__(self, memo): - """ - Hook for the standard copy module. - - A new variable is created. - """ - - # This deep copy implicitly takes care of the reference of the - # variable to itself (in self.derivatives): the new Variable - # object points to itself, not to the original Variable. - - # Reference: http://www.doughellmann.com/PyMOTW/copy/index.html - - return self.__copy__() - - def __getstate__(self): - """ - Hook for the standard pickle module. - """ - obj_slot_values = dict((k, getattr(self, k)) for k in self.__slots__) - obj_slot_values.update(AffineScalarFunc.__getstate__(self)) - # Conversion to a usual dictionary: - return obj_slot_values - - def __setstate__(self, data_dict): - """ - Hook for the standard pickle module. - """ - for (name, value) in data_dict.iteritems(): - setattr(self, name, value) - -############################################################################### - -# Utilities - -def nominal_value(x): - """ - Returns the nominal value of x if it is a quantity with - uncertainty (i.e., an AffineScalarFunc object); otherwise, returns - x unchanged. - - This utility function is useful for transforming a series of - numbers, when only some of them generally carry an uncertainty. - """ - - return x.nominal_value if isinstance(x, AffineScalarFunc) else x - -def std_dev(x): - """ - Returns the standard deviation of x if it is a quantity with - uncertainty (i.e., an AffineScalarFunc object); otherwise, returns - the float 0. - - This utility function is useful for transforming a series of - numbers, when only some of them generally carry an uncertainty. - """ - - return x.std_dev() if isinstance(x, AffineScalarFunc) else 0. - -def covariance_matrix(functions): - """ - Returns a matrix that contains the covariances between the given - sequence of numbers with uncertainties (AffineScalarFunc objects). - The resulting matrix implicitly depends on their ordering in - 'functions'. - - The covariances are floats (never int objects). - - The returned covariance matrix is the exact linear approximation - result, if the nominal values of the functions and of their - variables are their mean. Otherwise, the returned covariance - matrix should be close to it linear approximation value. - """ - # See PSI.411. - - covariance_matrix = [] - for (i1, expr1) in enumerate(functions): - derivatives1 = expr1.derivatives # Optimization - vars1 = set(derivatives1) - coefs_expr1 = [] - for (i2, expr2) in enumerate(functions[:i1+1]): - derivatives2 = expr2.derivatives # Optimization - coef = 0. - for var in vars1.intersection(derivatives2): - # var is a variable common to both functions: - coef += (derivatives1[var]*derivatives2[var]*var._std_dev**2) - coefs_expr1.append(coef) - covariance_matrix.append(coefs_expr1) - - # We symmetrize the matrix: - for (i, covariance_coefs) in enumerate(covariance_matrix): - covariance_coefs.extend(covariance_matrix[j][i] - for j in range(i+1, len(covariance_matrix))) - - return covariance_matrix - -# Entering variables as a block of correlated values. Only available -# if NumPy is installed. - -#! It would be possible to dispense with NumPy, but a routine should be -# written for obtaining the eigenvectors of a symmetric matrix. See -# for instance Numerical Recipes: (1) reduction to tri-diagonal -# [Givens or Householder]; (2) QR / QL decomposition. - -try: - import numpy -except ImportError: - pass -else: - - def correlated_values(values, covariance_mat, tags=None): - """ - Returns numbers with uncertainties (AffineScalarFunc objects) - that correctly reproduce the given covariance matrix, and have - the given values as their nominal value. - - The list of values and the covariance matrix must have the - same length, and the matrix must be a square (symmetric) one. - - The affine functions returned depend on newly created, - independent variables (Variable objects). - - If 'tags' is not None, it must list the tag of each new - independent variable. - """ - - # If no tags were given, we prepare tags for the newly created - # variables: - if tags is None: - tags = (None,) * len(values) - - # The covariance matrix is diagonalized in order to define - # the independent variables that model the given values: - - (variances, transform) = numpy.linalg.eigh(covariance_mat) - - # Numerical errors might make some variances negative: we set - # them to zero: - variances[variances < 0] = 0. - - # Creation of new, independent variables: - - # We use the fact that the eigenvectors in 'transform' are - # special: 'transform' is unitary: its inverse is its transpose: - - variables = tuple( - # The variables represent uncertainties only: - Variable(0, sqrt(variance), tag) - for (variance, tag) in zip(variances, tags)) - - # Representation of the initial correlated values: - values_funcs = tuple( - AffineScalarFunc(value, dict(zip(variables, coords))) - for (coords, value) in zip(transform, values)) - - return values_funcs - - __all__.append('correlated_values') - -############################################################################### -# Parsing of values with uncertainties: - -POSITIVE_DECIMAL_UNSIGNED = r'(\d+)(\.\d*)?' - -# Regexp for a number with uncertainty (e.g., "-1.234(2)e-6"), where the -# uncertainty is optional (in which case the uncertainty is implicit): -NUMBER_WITH_UNCERT_RE_STR = ''' - ([+-])? # Sign - %s # Main number - (?:\(%s\))? # Optional uncertainty - ([eE][+-]?\d+)? # Optional exponent - ''' % (POSITIVE_DECIMAL_UNSIGNED, POSITIVE_DECIMAL_UNSIGNED) - -NUMBER_WITH_UNCERT_RE = re.compile( - "^%s$" % NUMBER_WITH_UNCERT_RE_STR, re.VERBOSE) - -def parse_error_in_parentheses(representation): - """ - Returns (value, error) from a string representing a number with - uncertainty like 12.34(5), 12.34(142), 12.5(3.4) or 12.3(4.2)e3. - If no parenthesis is given, an uncertainty of one on the last - digit is assumed. - - Raises ValueError if the string cannot be parsed. - """ - - match = NUMBER_WITH_UNCERT_RE.search(representation) - - if match: - # The 'main' part is the nominal value, with 'int'eger part, and - # 'dec'imal part. The 'uncert'ainty is similarly broken into its - # integer and decimal parts. - (sign, main_int, main_dec, uncert_int, uncert_dec, - exponent) = match.groups() - else: - raise ValueError("Unparsable number representation: '%s'." - " Was expecting a string of the form 1.23(4)" - " or 1.234" % representation) - - # The value of the number is its nominal value: - value = float(''.join((sign or '', - main_int, - main_dec or '.0', - exponent or ''))) - - if uncert_int is None: - # No uncertainty was found: an uncertainty of 1 on the last - # digit is assumed: - uncert_int = '1' - # No uncertainty was found: an uncertainty of 0 on the last - # digit is assumed: - uncert_int = '0' - - # Do we have a fully explicit uncertainty? - if uncert_dec is not None: - uncert = float("%s%s" % (uncert_int, uncert_dec or '')) - else: - # uncert_int represents an uncertainty on the last digits: - - # The number of digits after the period defines the power of - # 10 than must be applied to the provided uncertainty: - num_digits_after_period = (0 if main_dec is None - else len(main_dec)-1) - uncert = int(uncert_int)/10**num_digits_after_period - - # We apply the exponent to the uncertainty as well: - uncert *= float("1%s" % (exponent or '')) - - return (value, uncert) - - -# The following function is not exposed because it can in effect be -# obtained by doing x = ufloat(representation) and -# x.nominal_value and x.std_dev(): -def str_to_number_with_uncert(representation): - """ - Given a string that represents a number with uncertainty, returns the - nominal value and the uncertainty. - - The string can be of the form: - - 124.5+/-0.15 - - 124.50(15) - - 124.50(123) - - 124.5 - - When no numerical error is given, an uncertainty of 1 on the last - digit is implied. - - Raises ValueError if the string cannot be parsed. - """ - - try: - # Simple form 1234.45+/-1.2: - (value, uncert) = representation.split('+/-') - except ValueError: - # Form with parentheses or no uncertainty: - parsed_value = parse_error_in_parentheses(representation) - else: - try: - parsed_value = (float(value), float(uncert)) - except ValueError: - raise ValueError('Cannot parse %s: was expecting a number' - ' like 1.23+/-0.1' % representation) - - return parsed_value - -def ufloat(representation, tag=None): - """ - Returns a random variable (Variable object). - - Converts the representation of a number into a number with - uncertainty (a random variable, defined by a nominal value and - a standard deviation). - - The representation can be a (value, standard deviation) sequence, - or a string. - - Strings of the form '12.345+/-0.015', '12.345(15)', or '12.3' are - recognized (see full list below). In the last case, an - uncertainty of +/-1 is assigned to the last digit. - - 'tag' is an optional string tag for the variable. Variables - don't have to have distinct tags. Tags are useful for tracing - what values (and errors) enter in a given result (through the - error_components() method). - - Examples of valid string representations: - - -1.23(3.4) - -1.34(5) - 1(6) - 3(4.2) - -9(2) - 1234567(1.2) - 12.345(15) - -12.3456(78)e-6 - 0.29 - 31. - -31. - 31 - -3.1e10 - 169.0(7) - 169.1(15) - """ - - # This function is somewhat optimized so as to help with the - # creation of lots of Variable objects (through unumpy.uarray, for - # instance). - - # representations is "normalized" so as to be a valid sequence of - # 2 arguments for Variable(). - - #! Accepting strings and any kind of sequence slows down the code - # by about 5 %. On the other hand, massive initializations of - # numbers with uncertainties are likely to be performed with - # unumpy.uarray, which does not support parsing from strings and - # thus does not have any overhead. - - #! Different, in Python 3: - if isinstance(representation, basestring): - representation = str_to_number_with_uncert(representation) - - #! The tag is forced to be a string, so that the user does not - # create a Variable(2.5, 0.5) in order to represent 2.5 - # +/- 0.5. Forcing 'tag' to be a string prevents errors from being - # considered as tags, here: - - #! 'unicode' is removed in Python3: - if tag is not None: - assert ((type(tag) is str) or (type(tag) is unicode)), \ - "The tag can only be a string." - - #! init_args must contain all arguments, here: - return Variable(*representation, **{'tag': tag}) - -############################################################################### -# Support for legacy code (will be removed in the future): - -def NumberWithUncert(*args): - """ - Wrapper for legacy code. Obsolete: do not use. Use ufloat - instead. - """ - import warnings - warnings.warn("NumberWithUncert is obsolete." - " Use ufloat instead.", DeprecationWarning, - stacklevel=2) - return ufloat(*args) - -def num_with_uncert(*args): - """ - Wrapper for legacy code. Obsolete: do not use. Use ufloat - instead. - """ - import warnings - warnings.warn("num_with_uncert is obsolete." - " Use ufloat instead.", DeprecationWarning, - stacklevel=2) - return ufloat(*args) - -def array_u(*args): - """ - Wrapper for legacy code. Obsolete: do not use. Use - unumpy.uarray instead. - """ - import warnings - warnings.warn("uncertainties.array_u is obsolete." - " Use uncertainties.unumpy.uarray instead.", - DeprecationWarning, - stacklevel=2) - import uncertainties.unumpy - return uncertainties.unumpy.uarray(*args) - -def nominal_values(*args): - """ - Wrapper for legacy code. Obsolete: do not use. Use - unumpy.nominal_values instead. - """ - import warnings - warnings.warn("uncertainties.nominal_values is obsolete." - " Use uncertainties.unumpy.nominal_values instead.", - DeprecationWarning, - stacklevel=2) - import uncertainties.unumpy - return uncertainties.unumpy.nominal_values(*args) - -def std_devs(*args): - """ - Wrapper for legacy code. Obsolete: do not use. Use ufloat - instead. - """ - import warnings - warnings.warn("uncertainties.std_devs is obsolete." - " Use uncertainties.unumpy.std_devs instead.", - DeprecationWarning, - stacklevel=2) - import uncertainties.unumpy - return uncertainties.unumpy.std_devs(*args) - diff --git a/units/uncertainties/umath.py b/units/uncertainties/umath.py deleted file mode 100644 index 5f9c45b4e..000000000 --- a/units/uncertainties/umath.py +++ /dev/null @@ -1,339 +0,0 @@ -''' -Mathematical operations that generalize many operations from the -standard math module so that they also work on numbers with -uncertainties. - -Examples: - - from umath import sin - - # Manipulation of numbers with uncertainties: - x = uncertainties.ufloat((3, 0.1)) - print sin(x) # prints 0.141120008...+/-0.098999... - - # The umath functions also work on regular Python floats: - print sin(3) # prints 0.141120008... This is a Python float. - -Importing all the functions from this module into the global namespace -is possible. This is encouraged when using a Python shell as a -calculator. Example: - - import uncertainties - from uncertainties.umath import * # Imports tan(), etc. - - x = uncertainties.ufloat((3, 0.1)) - print tan(x) # tan() is the uncertainties.umath.tan function - -The numbers with uncertainties handled by this module are objects from -the uncertainties module, from either the Variable or the -AffineScalarFunc class. - -(c) 2009-2010 by Eric O. LEBIGOT (EOL) . -Please send feature requests, bug reports, or feedback to this address. - -This software is released under a dual license. (1) The BSD license. -(2) Any other license, as long as it is obtained from the original -author.''' - -from __future__ import division # Many analytical derivatives depend on this - -# Standard modules -import math -import sys -import itertools -import functools -import inspect - -# Local modules -from ivs.units import uncertainties - -from ivs.units.uncertainties import __author__, to_affine_scalar, AffineScalarFunc - -############################################################################### - -# We wrap the functions from the math module so that they keep track of -# uncertainties by returning a AffineScalarFunc object. - -# Some functions from the math module cannot be adapted in a standard -# way so to work with AffineScalarFunc objects (either as their result -# or as their arguments): - -# (1) Some functions return a result of a type whose value and -# variations (uncertainties) cannot be represented by AffineScalarFunc -# (e.g., math.frexp, which returns a tuple). The exception raised -# when not wrapping them with wrap() is more obvious than the -# one obtained when wrapping them (in fact, the wrapped functions -# attempts operations that are not supported, such as calculation a -# subtraction on a result of type tuple). - -# (2) Some functions don't take continuous scalar arguments (which can -# be varied during differentiation): math.fsum, math.factorial... -# Such functions can either be: - -# - wrapped in a special way. - -# - excluded from standard wrapping by adding their name to -# no_std_wrapping - -# Math functions that have a standard interface: they take -# one or more float arguments, and return a scalar: -many_scalar_to_scalar_funcs = [] - -# Some functions require a specific treatment and must therefore be -# excluded from standard wrapping. Functions -no_std_wrapping = ['modf', 'frexp', 'ldexp', 'fsum', 'factorial'] - -# Functions that do not belong in many_scalar_to_scalar_funcs, but -# that have a version that handles uncertainties: -non_std_wrapped_funcs = [] - -# Function that copies the relevant attributes from generalized -# functions from the math module: -wraps = functools.partial(functools.update_wrapper, - assigned=('__doc__', '__name__')) - -######################################## -# Wrapping of built-in math functions not in no_std_wrapping: - -# Fixed formulas for the derivatives of some functions from the math -# module (some functions might not be present in all version of -# Python). Singular points are not taken into account. The user -# should never give "large" uncertainties: problems could only appear -# if this assumption does not hold. - -# Functions not mentioned in _fixed_derivatives have their derivatives -# calculated numerically. - -# Functions that have singularities (possibly at infinity) benefit -# from analytical calculations (instead of the default numerical -# calculation). Even slowly varying functions (e.g., abs()) yield -# more precise results when differentiated analytically, because of -# the loss of precision in numerical calculations. - -#def log_1arg_der(x): -# """ -# Derivative of log(x) (1-argument form). -# """ -# return 1/x - -def log_der0(*args): - """ - Derivative of math.log() with respect to its first argument. - - Works whether 1 or 2 arguments are given. - """ - if len(args) == 1: - return 1/args[0] - else: - return 1/args[0]/math.log(args[1]) # 2-argument form - - # The following version goes about as fast: - - ## A 'try' is used for the most common case because it is fast when no - ## exception is raised: - #try: - # return log_1arg_der(*args) # Argument number check - #except TypeError: - # return 1/args[0]/math.log(args[1]) # 2-argument form - -fixed_derivatives = { - # In alphabetical order, here: - 'acos': [lambda x: -1/math.sqrt(1-x**2)], - 'acosh': [lambda x: 1/math.sqrt(x**2-1)], - 'asin': [lambda x: 1/math.sqrt(1-x**2)], - 'asinh': [lambda x: 1/math.sqrt(1+x**2)], - 'atan': [lambda x: 1/(1+x**2)], - 'atan2': [lambda y, x: x/(x**2+y**2), # Correct for x == 0 - lambda y, x: -y/(x**2+y**2)], # Correct for x == 0 - 'atanh': [lambda x: 1/(1-x**2)], - 'ceil': [lambda x: 0], - 'copysign': [lambda x, y: (1 if x >= 0 else -1) * math.copysign(1, y), - lambda x, y: 0], - 'cos': [lambda x: -math.sin(x)], - 'cosh': [math.sinh], - 'degrees': [lambda x: math.degrees(1)], - 'exp': [math.exp], - 'fabs': [lambda x: 1 if x >= 0 else -1], - 'floor': [lambda x: 0], - 'hypot': [lambda x, y: x/math.hypot(x, y), - lambda x, y: y/math.hypot(x, y)], - 'log': [log_der0, - lambda x, y: -math.log(x, y)/y/math.log(y)], - 'log10': [lambda x: 1/x/math.log(10)], - 'log1p': [lambda x: 1/(1+x)], - 'pow': [lambda x, y: y*math.pow(x, y-1), - lambda x, y: math.log(x) * math.pow(x, y)], - 'radians': [lambda x: math.radians(1)], - 'sin': [math.cos], - 'sinh': [math.cosh], - 'sqrt': [lambda x: 0.5/math.sqrt(x)], - 'tan': [lambda x: 1+math.tan(x)**2], - 'tanh': [lambda x: 1-math.tanh(x)**2] - } - -# Many built-in functions in the math module are wrapped with a -# version which is uncertainty aware: - -this_module = sys.modules[__name__] - -# We do not want to wrap module attributes such as __doc__, etc.: -for (name, func) in inspect.getmembers(math, inspect.isbuiltin): - - if name in no_std_wrapping: - continue - - if name in fixed_derivatives: - derivatives = fixed_derivatives[name] - else: - # Functions whose derivatives are calculated numerically by - # this module fall here (isinf, fmod,...): - derivatives = None # Means: numerical calculation required - setattr(this_module, name, - wraps(uncertainties.wrap(func, derivatives), func)) - many_scalar_to_scalar_funcs.append(name) - -############################################################################### - -######################################## -# Special cases: some of the functions from no_std_wrapping: - -########## -# The math.factorial function is not converted to an uncertainty-aware -# function, because it does not handle non-integer arguments: it does -# not make sense to give it an argument with a numerical error -# (whereas this would be relevant for the gamma function). - -########## - -# fsum takes a single argument, which cannot be differentiated. -# However, each of the arguments inside this single list can -# be a variable. We handle this in a specific way: - -if sys.version_info[:2] >= (2, 6): - - # For drop-in compatibility with the math module: - factorial = math.factorial - non_std_wrapped_funcs.append('factorial') - - - # We wrap math.fsum - - original_func = math.fsum # For optimization purposes - - # The function below exists so that temporary variables do not - # pollute the module namespace: - def wrapped_fsum(): - """ - Returns an uncertainty-aware version of math.fsum, which must - be contained in _original_func. - """ - - # The fsum function is flattened, in order to use the - # wrap() wrapper: - - flat_fsum = lambda *args: original_func(args) - - flat_fsum_wrap = uncertainties.wrap( - flat_fsum, itertools.repeat(lambda *args: 1)) - - return wraps(lambda arg_list: flat_fsum_wrap(*arg_list), - original_func) - - fsum = wrapped_fsum() - non_std_wrapped_funcs.append('fsum') - -########## -@uncertainties.set_doc(math.modf.__doc__) -def modf(x): - """ - Version of modf that works for numbers with uncertainty, and also - for regular numbers. - """ - - # The code below is inspired by uncertainties.wrap(). It is - # simpler because only 1 argument is given, and there is no - # delegation to other functions involved (as for __mul__, etc.). - - aff_func = to_affine_scalar(x) - - (frac_part, int_part) = math.modf(aff_func.nominal_value) - - if aff_func.derivatives: - # The derivative of the fractional part is simply 1: the - # derivatives of modf(x)[0] are the derivatives of x: - return (AffineScalarFunc(frac_part, aff_func.derivatives), int_part) - else: - # This function was not called with an AffineScalarFunc - # argument: there is no need to return numbers with uncertainties: - return (frac_part, int_part) - -many_scalar_to_scalar_funcs.append('modf') - -@uncertainties.set_doc(math.ldexp.__doc__) -def ldexp(x, y): - # The code below is inspired by uncertainties.wrap(). It is - # simpler because only 1 argument is given, and there is no - # delegation to other functions involved (as for __mul__, etc.). - - # Another approach would be to add an additional argument to - # uncertainties.wrap() so that some arguments are automatically - # considered as constants. - - aff_func = to_affine_scalar(x) # y must be an integer, for math.ldexp - - if aff_func.derivatives: - factor = 2**y - return AffineScalarFunc( - math.ldexp(aff_func.nominal_value, y), - # Chain rule: - dict((var, factor*deriv) - for (var, deriv) in aff_func.derivatives.iteritems())) - else: - # This function was not called with an AffineScalarFunc - # argument: there is no need to return numbers with uncertainties: - - # aff_func.nominal_value is not passed instead of x, because - # we do not have to care about the type of the return value of - # math.ldexp, this way (aff_func.nominal_value might be the - # value of x coerced to a difference type [int->float, for - # instance]): - return math.ldexp(x, y) -many_scalar_to_scalar_funcs.append('ldexp') - -@uncertainties.set_doc(math.frexp.__doc__) -def frexp(x): - """ - Version of frexp that works for numbers with uncertainty, and also - for regular numbers. - """ - - # The code below is inspired by uncertainties.wrap(). It is - # simpler because only 1 argument is given, and there is no - # delegation to other functions involved (as for __mul__, etc.). - - aff_func = to_affine_scalar(x) - - if aff_func.derivatives: - result = math.frexp(aff_func.nominal_value) - # With frexp(x) = (m, e), dm/dx = 1/(2**e): - factor = 1/(2**result[1]) - return ( - AffineScalarFunc( - result[0], - # Chain rule: - dict((var, factor*deriv) - for (var, deriv) in aff_func.derivatives.iteritems())), - # The exponent is an integer and is supposed to be - # continuous (small errors): - result[1]) - else: - # This function was not called with an AffineScalarFunc - # argument: there is no need to return numbers with uncertainties: - return math.frexp(x) -non_std_wrapped_funcs.append('frexp') - -############################################################################### -# Exported functions: - -__all__ = many_scalar_to_scalar_funcs + non_std_wrapped_funcs - diff --git a/units/uncertainties/unumpy/__init__.py b/units/uncertainties/unumpy/__init__.py deleted file mode 100644 index bb68d3b68..000000000 --- a/units/uncertainties/unumpy/__init__.py +++ /dev/null @@ -1,82 +0,0 @@ -""" -Utilities for NumPy arrays and matrices that contain numbers with -uncertainties. - -This package contains: - -1) utilities that help with the creation and manipulation of NumPy -arrays and matrices of numbers with uncertainties; - -2) generalizations of multiple NumPy functions so that they also work -with arrays that contain numbers with uncertainties. - -- Arrays of numbers with uncertainties can be built as follows: - - arr = unumpy.uarray(([1, 2], [0.01, 0.002])) # (values, uncertainties) - -NumPy arrays of numbers with uncertainties can also be built directly -through NumPy, thanks to NumPy's support of arrays of arbitrary objects: - - arr = numpy.array([uncertainties.ufloat((1, 0.1)),...]) - -- Matrices of numbers with uncertainties are best created in one of -two ways: - - mat = unumpy.umatrix(([1, 2], [0.01, 0.002])) # (values, uncertainties) - -Matrices can also be built by converting arrays of numbers with -uncertainties, through the unumpy.matrix class: - - mat = unumpy.matrix(arr) - -unumpy.matrix objects behave like numpy.matrix objects of numbers with -uncertainties, but with better support for some operations (such as -matrix inversion): - - # The inverse or pseudo-inverse of a unumpy.matrix can be calculated: - print mat.I # Would not work with numpy.matrix([[ufloat(...),...]]).I - -- Nominal values and uncertainties of arrays can be directly accessed: - - print unumpy.nominal_values(arr) # [ 1. 2.] - print unumpy.std_devs(mat) # [ 0.01 0.002] - -- This module defines uncertainty-aware mathematical functions that -generalize those from uncertainties.umath so that they work on NumPy -arrays of numbers with uncertainties instead of just scalars: - - print unumpy.cos(arr) # Array with the cosine of each element - -NumPy's function names are used, and not those of the math module (for -instance, unumpy.arccos is defined, like in NumPy, and is not named -acos like in the standard math module). - -The definitions of the mathematical quantities calculated by these -functions are available in the documentation of uncertainties.umath. - -- The unumpy.ulinalg module contains more uncertainty-aware functions -for arrays that contain numbers with uncertainties (see the -documentation for this module). - -This module requires the NumPy package. - -(c) 2009-2010 by Eric O. LEBIGOT (EOL) . -Please send feature requests, bug reports, or feedback to this address. - -This software is released under a dual license. (1) The BSD license. -(2) Any other license, as long as it is obtained from the original -author.""" - -# Local modules: -from core import * -import core -import ulinalg # Local sub-module - -#from ivs.units.uncertainties import __author__ - -# __all__ is set so that pydoc shows all important functions: -__all__ = core.__all__ -# "import numpy" makes numpy.linalg available. This behavior is -# copied here, for maximum compatibility: -__all__.append('ulinalg') - diff --git a/units/uncertainties/unumpy/core.py b/units/uncertainties/unumpy/core.py deleted file mode 100644 index bcde71dd9..000000000 --- a/units/uncertainties/unumpy/core.py +++ /dev/null @@ -1,602 +0,0 @@ -""" -Core functions used by unumpy and some of its submodules. - -(c) 2010 by Eric O. LEBIGOT (EOL). -""" - -# The functions found in this module cannot be defined in unumpy or -# its submodule: this creates import loops, when unumpy explicitly -# imports one of the submodules in order to make it available to the -# user. - -from __future__ import division - -# Standard modules: -import sys - -# 3rd-party modules: -import numpy - -# Local modules: -from ivs.units import uncertainties -from ivs.units.uncertainties import umath - -from ivs.units.uncertainties import __author__ - -__all__ = [ - # Factory functions: - 'uarray', 'umatrix', - - # Utilties: - 'nominal_values', 'std_devs', - - # Classes: - 'matrix' - ] - -############################################################################### -# Utilities: - -# nominal_values() and std_devs() are defined as functions (instead of -# as additional methods of the unumpy.matrix class) because the user -# might well directly build arrays of numbers with uncertainties -# without going through the factory functions found in this module -# (uarray() and umatrix()). Thus, -# numpy.array([uncertainties.ufloat((1, 0.1))]) would not -# have a nominal_values() method. Adding such a method to, say, -# unumpy.matrix, would break the symmetry between NumPy arrays and -# matrices (no nominal_values() method), and objects defined in this -# module. - -# ! Warning: the __doc__ is set, but help(nominal_values) does not -# display it, but instead displays the documentation for the type of -# nominal_values (i.e. the documentation of its class): - -to_nominal_values = numpy.vectorize( - uncertainties.nominal_value, - otypes=[float], # Because vectorize() has side effects (dtype setting) - doc=("Applies uncertainties.nominal_value to the elements of" - " a NumPy (or unumpy) array (this includes matrices).")) - -to_std_devs = numpy.vectorize( - uncertainties.std_dev, - otypes=[float], # Because vectorize() has side effects (dtype setting) - doc=("Returns the standard deviation of the numbers with uncertainties" - " contained in a NumPy array, or zero for other objects.")) - -def unumpy_to_numpy_matrix(arr): - """ - If arr in a unumpy.matrix, it is converted to a numpy.matrix. - Otherwise, it is returned unchanged. - """ - - return arr.view(numpy.matrix) if isinstance(arr, matrix) else arr - -def nominal_values(arr): - """ - Returns the nominal values of the numbers in NumPy array arr. - - Elements that are not uncertainties.AffineScalarFunc are passed - through untouched (because a numpy.array can contain numbers with - uncertainties and pure floats simultaneously). - - If arr is of type unumpy.matrix, the returned array is a - numpy.matrix, because the resulting matrix does not contain - numbers with uncertainties. - """ - - return unumpy_to_numpy_matrix(to_nominal_values(arr)) - -def std_devs(arr): - """ - Returns the standard deviations of the numbers in NumPy array arr. - - Elements that are not uncertainties.AffineScalarFunc are given a - zero uncertainty ((because a numpy.array can contain numbers with - uncertainties and pure floats simultaneously).. - - If arr is of type unumpy.matrix, the returned array is a - numpy.matrix, because the resulting matrix does not contain - numbers with uncertainties. - """ - - return unumpy_to_numpy_matrix(to_std_devs(arr)) - -############################################################################### - -def derivative(u, var): - """ - Returns the derivative of u along var, if u is an - uncertainties.AffineScalarFunc instance, and if var is one of the - variables on which it depends. Otherwise, return 0. - """ - if isinstance(u, uncertainties.AffineScalarFunc): - try: - return u.derivatives[var] - except KeyError: - return 0. - else: - return 0. - -def wrap_array_func(func): - """ - Returns a version of the function func() that works even when - func() is given a NumPy array that contains numbers with - uncertainties. - - func() is supposed to return a NumPy array. - - This wrapper is similar to uncertainties.wrap(), except that it - handles an array argument instead of float arguments. - - func -- version that takes and returns a single NumPy array. - """ - - @uncertainties.set_doc("""\ - Version of %s(...) that works even when its first argument is a NumPy - array that contains numbers with uncertainties. - - Warning: elements of the first argument array that are not - AffineScalarFunc objects must not depend on uncertainties.Variable - objects in any way. Otherwise, the dependence of the result in - uncertainties.Variable objects will be incorrect. - - Original documentation: - %s""" % (func.__name__, func.__doc__)) - def wrapped_func(arr, *args): - # Nominal value: - arr_nominal_value = nominal_values(arr) - func_nominal_value = func(arr_nominal_value, *args) - - # The algorithm consists in numerically calculating the derivatives - # of func: - - # Variables on which the array depends are collected: - variables = set() - for element in arr.flat: - # floats, etc. might be present - if isinstance(element, uncertainties.AffineScalarFunc): - variables |= set(element.derivatives.iterkeys()) - - # If the matrix has no variables, then the function value can be - # directly returned: - if not variables: - return func_nominal_value - - # Calculation of the derivatives of each element with respect - # to the variables. Each element must be independent of the - # others. The derivatives have the same shape as the output - # array (which might differ from the shape of the input array, - # in the case of the pseudo-inverse). - derivatives = numpy.vectorize(lambda _: {})(func_nominal_value) - for var in variables: - - # A basic assumption of this package is that the user - # guarantees that uncertainties cover a zone where - # evaluated functions are linear enough. Thus, numerical - # estimates of the derivative should be good over the - # standard deviation interval. This is true for the - # common case of a non-zero standard deviation of var. If - # the standard deviation of var is zero, then var has no - # impact on the uncertainty of the function func being - # calculated: an incorrect derivative has no impact. One - # scenario can give incorrect results, however, but it - # should be extremely uncommon: the user defines a - # variable x with 0 standard deviation, sets y = func(x) - # through this routine, changes the standard deviation of - # x, and prints y; in this case, the uncertainty on y - # might be incorrect, because this program had no idea of - # the scale on which func() is linear, when it calculated - # the numerical derivative. - - # The standard deviation might be numerically too small - # for the evaluation of the derivative, though: we set the - # minimum variable shift. - - shift_var = max(var._std_dev/1e5, 1e-8*abs(var._nominal_value)) - # An exceptional case is that of var being exactly zero. - # In this case, an arbitrary shift is used for the - # numerical calculation of the derivative. The resulting - # derivative value might be quite incorrect, but this does - # not matter as long as the uncertainty of var remains 0, - # since it is, in this case, a constant. - if not shift_var: - shift_var = 1e-8 - - # Shift of all the elements of arr when var changes by shift_var: - shift_arr = array_derivative(arr, var)*shift_var - - # Origin value of array arr when var is shifted by shift_var: - shifted_arr_values = arr_nominal_value + shift_arr - func_shifted = func(shifted_arr_values, *args) - numerical_deriv = (func_shifted-func_nominal_value)/shift_var - - # Update of the list of variables and associated - # derivatives, for each element: - for (derivative_dict, derivative_value) \ - in zip(derivatives.flat, numerical_deriv.flat): - if derivative_value: - derivative_dict[var] = derivative_value - - # numbers with uncertainties are build from the result: - return numpy.vectorize(uncertainties.AffineScalarFunc)( - func_nominal_value, derivatives) - - # It is easier to work with wrapped_func, which represents a - # wrapped version of 'func', when it bears the same name as - # 'func' (the name is used by repr(wrapped_func)). - wrapped_func.__name__ = func.__name__ - - return wrapped_func - -############################################################################### -# Arrays - -# Vectorized creation of an array of variables: - -# ! Looking up uncertainties.Variable beforehand through '_Variable = -# uncertainties.Variable' does not result in a significant speed up: - -_uarray = numpy.vectorize(lambda v, s: uncertainties.Variable(v, s), - otypes=[object]) - -def uarray((values, std_devs)): - """ - Returns a NumPy array of numbers with uncertainties - initialized with the given nominal values and standard - deviations. - - values, std_devs -- valid arguments for numpy.array, with - identical shapes (list of numbers, list of lists, numpy.ndarray, - etc.). - """ - - return _uarray(values, std_devs) - -############################################################################### - -def array_derivative(array_like, var): - """ - Returns the derivative of the given array with respect to the - given variable. - - The returned derivative is a Numpy ndarray of the same shape as - array_like, that contains floats. - - array_like -- array-like object (list, etc.) that contains - scalars or numbers with uncertainties. - - var -- Variable object. - """ - return numpy.vectorize(lambda u: derivative(u, var), - # The type is set because an - # integer derivative should not - # set the output type of the - # array: - otypes=[float])(array_like) - -def func_with_deriv_to_uncert_func(func_with_derivatives): - """ - Returns a function that can be applied to array-like objects that - contain numbers with uncertainties (lists, lists of lists, Numpy - arrays, etc.). - - func_with_derivatives -- defines a function that takes array-like - objects containing scalars and returns an array. Both the value - and the derivatives of this function with respect to multiple - scalar parameters are calculated by func_with_derivatives(). - - func_with_derivatives(arr, input_type, derivatives, *args) returns - an iterator. The first element is the value of the function at - point 'arr' (with the correct type). The following elements are - arrays that represent the derivative of the function for each - derivative array from the iterator 'derivatives'. - - func_with_derivatives takes the following arguments: - - arr -- Numpy ndarray of scalars where the function must be - evaluated. - - input_type -- type of the input array-like object. This type is - used for determining the type that the function should return. - - derivatives -- iterator that returns the derivatives of the - argument of the function with respect to multiple scalar - variables. func_with_derivatives() returns the derivatives of - the defined function with respect to these variables. - - args -- additional arguments that define the result (example: - for the pseudo-inverse numpy.linalg.pinv: numerical cutoff). - - Examples of func_with_derivatives: inv_with_derivatives(). - """ - - def wrapped_func(array_like, *args): - """ - array_like -- array-like object that contains numbers with - uncertainties (list, Numpy ndarray or matrix, etc.). - - args -- additional arguments that are passed directly to - func_with_derivatives. - """ - - # So that .flat works even if array_like is a list. Later - # useful for faster code: - array_version = numpy.asarray(array_like) - - # Variables on which the array depends are collected: - variables = set() - for element in array_version.flat: - # floats, etc. might be present - if isinstance(element, uncertainties.AffineScalarFunc): - variables |= set(element.derivatives.iterkeys()) - - array_nominal = nominal_values(array_version) - # Function value, and derivatives at array_nominal (the - # derivatives are with respect to the variables contained in - # array_like): - func_and_derivs = func_with_derivatives( - array_nominal, - type(array_like), - (array_derivative(array_version, var) for var in variables), - *args) - - func_nominal_value = func_and_derivs.next() - - if not variables: - return func_nominal_value - - # The result is built progressively, with the contribution of - # each variable added in turn: - - # Calculation of the derivatives of the result with respect to - # the variables. - derivatives = numpy.array( - [{} for _ in xrange(func_nominal_value.size)], dtype=object) - derivatives.resize(func_nominal_value.shape) - - # Memory-efficient approach. A memory-hungry approach would - # be to calculate the matrix derivatives will respect to all - # variables and then combine them into a matrix of - # AffineScalarFunc objects. The approach followed here is to - # progressively build the matrix of derivatives, by - # progressively adding the derivatives with respect to - # successive variables. - for (var, deriv_wrt_var) in zip(variables, func_and_derivs): - - # Update of the list of variables and associated - # derivatives, for each element: - for (derivative_dict, derivative_value) in zip( - derivatives.flat, deriv_wrt_var.flat): - if derivative_value: - derivative_dict[var] = derivative_value - - # An array of numbers with uncertainties are built from the - # result: - result = numpy.vectorize(uncertainties.AffineScalarFunc)( - func_nominal_value, derivatives) - - # Numpy matrices that contain numbers with uncertainties are - # better as unumpy matrices: - if isinstance(result, numpy.matrix): - result = result.view(matrix) - - return result - - return wrapped_func - -########## Matrix inverse - -def inv_with_derivatives(arr, input_type, derivatives): - """ - Defines the matrix inverse and its derivatives. - - See the definition of func_with_deriv_to_uncert_func() for its - detailed semantics. - """ - - inverse = numpy.linalg.inv(arr) - # The inverse of a numpy.matrix is a numpy.matrix. It is assumed - # that numpy.linalg.inv is such that other types yield - # numpy.ndarrays: - if issubclass(input_type, numpy.matrix): - inverse = inverse.view(numpy.matrix) - yield inverse - - # It is mathematically convenient to work with matrices: - inverse_mat = numpy.asmatrix(inverse) - - # Successive derivatives of the inverse: - for derivative in derivatives: - derivative_mat = numpy.asmatrix(derivative) - yield -inverse_mat * derivative_mat * inverse_mat - -_inv = func_with_deriv_to_uncert_func(inv_with_derivatives) -_inv.__doc__ = """\ - Version of numpy.linalg.inv that works with array-like objects - that contain numbers with uncertainties. - - The result is a unumpy.matrix if numpy.linalg.pinv would return a - matrix for the array of nominal values. - - Analytical formulas are used. - - Original documentation: - %s - """ % numpy.linalg.inv.__doc__ - -########## Matrix pseudo-inverse - -def pinv_with_derivatives(arr, input_type, derivatives, rcond): - """ - Defines the matrix pseudo-inverse and its derivatives. - - Works with real or complex matrices. - - See the definition of func_with_deriv_to_uncert_func() for its - detailed semantics. - """ - - inverse = numpy.linalg.pinv(arr, rcond) - # The pseudo-inverse of a numpy.matrix is a numpy.matrix. It is - # assumed that numpy.linalg.pinv is such that other types yield - # numpy.ndarrays: - if issubclass(input_type, numpy.matrix): - inverse = inverse.view(numpy.matrix) - yield inverse - - # It is mathematically convenient to work with matrices: - inverse_mat = numpy.asmatrix(inverse) - - # Formula (4.12) from The Differentiation of Pseudo-Inverses and - # Nonlinear Least Squares Problems Whose Variables - # Separate. Author(s): G. H. Golub and V. Pereyra. Source: SIAM - # Journal on Numerical Analysis, Vol. 10, No. 2 (Apr., 1973), - # pp. 413-432 - - # See also - # http://mathoverflow.net/questions/25778/analytical-formula-for-numerical-derivative-of-the-matrix-pseudo-inverse - - # Shortcuts. All the following factors should be numpy.matrix objects: - PA = arr*inverse_mat - AP = inverse_mat*arr - factor21 = inverse_mat*inverse_mat.H - factor22 = numpy.eye(arr.shape[0])-PA - factor31 = numpy.eye(arr.shape[1])-AP - factor32 = inverse_mat.H*inverse_mat - - # Successive derivatives of the inverse: - for derivative in derivatives: - derivative_mat = numpy.asmatrix(derivative) - term1 = -inverse_mat*derivative_mat*inverse_mat - derivative_mat_H = derivative_mat.H - term2 = factor21*derivative_mat_H*factor22 - term3 = factor31*derivative_mat_H*factor32 - yield term1+term2+term3 - -# Default rcond argument for the generalization of numpy.linalg.pinv: -try: - # Python 2.6+: - _pinv_default = numpy.linalg.pinv.__defaults__[0] -except AttributeError: - _pinv_default = 1e-15 - -_pinv_with_uncert = func_with_deriv_to_uncert_func(pinv_with_derivatives) - -@uncertainties.set_doc(""" - Version of numpy.linalg.pinv that works with array-like objects - that contain numbers with uncertainties. - - The result is a unumpy.matrix if numpy.linalg.pinv would return a - matrix for the array of nominal values. - - Analytical formulas are used. - - Original documentation: - %s - """ % numpy.linalg.pinv.__doc__) -def _pinv(array_like, rcond=_pinv_default): - return _pinv_with_uncert(array_like, rcond) - -########## Matrix class - -class matrix(numpy.matrix): - # The name of this class is the same as NumPy's, which is why it - # does not follow PEP 8. - """ - Class equivalent to numpy.matrix, but that behaves better when the - matrix contains numbers with uncertainties. - """ - - # The NumPy doc for getI is empty: - # @uncertainties.set_doc(numpy.matrix.getI.__doc__) - def getI(self): - "Matrix inverse of pseudo-inverse" - - # numpy.matrix.getI is OK too, but the rest of the code assumes that - # numpy.matrix.I is a property object anyway: - - M, N = self.shape - if M == N: - func = _inv - else: - func = _pinv - return func(self) - - - # ! In Python >= 2.6, this could be simplified as: - # I = numpy.matrix.I.getter(__matrix_inverse) - I = property(getI, numpy.matrix.I.fset, numpy.matrix.I.fdel, - numpy.matrix.I.__doc__) - - @property - def nominal_values(self): - """ - Nominal value of all the elements of the matrix. - """ - return nominal_values(self) - - std_devs = std_devs - -def umatrix(*args): - """ - Constructs a matrix that contains numbers with uncertainties. - - The input data is the same as for uarray(...): a tuple with the - nominal values, and the standard deviations. - - The returned matrix can be inverted, thanks to the fact that it is - a unumpy.matrix object instead of a numpy.matrix one. - """ - - return uarray(*args).view(matrix) - -############################################################################### - -def define_vectorized_funcs(): - """ - Defines vectorized versions of functions from uncertainties.umath. - - Some functions have their name translated, so as to follow NumPy's - convention (example: math.acos -> numpy.arccos). - """ - - this_module = sys.modules[__name__] - # NumPy does not always use the same function names as the math - # module: - func_name_translations = dict( - (f_name, 'arc'+f_name[1:]) - for f_name in ['acos', 'acosh', 'asin', 'atan', 'atan2', 'atanh']) - - new_func_names = [ - func_name_translations[function_name] - if function_name in func_name_translations - else function_name - for function_name in umath.many_scalar_to_scalar_funcs] - - for (function_name, unumpy_name) in \ - zip(umath.many_scalar_to_scalar_funcs, new_func_names): - - # ! The newly defined functions (uncertainties.unumpy.cos, etc.) - # do not behave exactly like their NumPy equivalent (numpy.cos, - # etc.): cos(0) gives an array() and not a - # numpy.float... (equality tests succeed, though). - func = getattr(umath, function_name) - setattr( - this_module, unumpy_name, - numpy.vectorize(func, - # If by any chance a function returns, - # in a particular case, an integer, - # side-effects in vectorize() would - # fix the resulting dtype to integer, - # which is not what is wanted: - otypes=[object], - doc="""\ -Vectorized version of umath.%s. - -Original documentation: -%s""" % (function_name, func.__doc__))) - - __all__.append(unumpy_name) - -define_vectorized_funcs() diff --git a/units/uncertainties/unumpy/ulinalg.py b/units/uncertainties/unumpy/ulinalg.py deleted file mode 100644 index 22081769f..000000000 --- a/units/uncertainties/unumpy/ulinalg.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -This module provides uncertainty-aware functions that generalize some -of the functions from numpy.linalg. - -(c) 2010 by Eric O. LEBIGOT (EOL) . -""" - -from ivs.units.uncertainties import __author__ -from . import core - -# This module cannot import unumpy because unumpy imports this module. - -__all__ = ['inv', 'pinv'] - -inv = core._inv -pinv = core._pinv -