def __init__(self, orbit="circular", ld="quad", collCheck=True): if not orbit in ["circular", "keplerian"]: raise (PE.PyAValError( "Invalid option for orbit: " + str(orbit), soltuion="Use either 'circular' or 'keplerian'.")) if not ld in ["quad", "nl"]: raise (PE.PyAValError("Invalid option for orbit: " + str(ld), soltuion="Use either 'quad' or 'nl'.")) _ZList.__init__(self, orbit, collCheck) if (not _importOccultquad) and (ld == "quad"): raise (PE.PyARequiredImport( "Could not import required shared object library 'occultquad.so'", solution=[ "Use 'pip install PyAstronomy_ext' to get it.", "Invoke PyA's install script (setup.py) with the --with-ext option.", "Go to 'forTrans' directory of PyAstronomy and invoke\n f2py -c occultquad.pyf occultquad.f" ])) if (not _importOccultnl) and (ld == "nl"): raise (PE.PyARequiredImport( "Could not import required shared object library 'occultquad.so'", solution=[ "Use 'pip install PyAstronomy_ext' to get it.", "Invoke PyA's install script (setup.py) with the --with-ext option.", "Go to 'forTrans' directory of PyAstronomy and invoke\n f2py -c occultnl.pyf occultnl.f" ])) if orbit == "circular": plist = ["p", "a", "i", "T0", "per", "b"] else: # It is an elliptical orbit plist = ["p", "a", "i", "per", "b", "e", "Omega", "tau", "w"] if ld == "quad": plist.extend(["linLimb", "quadLimb"]) else: # Non-linear LD plist.extend(["a1", "a2", "a3", "a4"]) fuf.OneDFit.__init__(self, plist) self.freeze(plist) self.setRootName("Occultquad") if orbit == "keplerian": self["w"] = -90.0 self._orbit = orbit self._ld = ld
def restoreFromFits(filename, ext=1, verbose=1): if not ic.check["pyfits"]: raise (PE.PyARequiredImport("pyfits required to use fits file.", where="Params::updateFitsHeader")) return import pyfits if not os.path.isfile(filename): raise (PE.PyAValError("No such file: " + str(filename), where="restoreFromFits", solution="Give correct filename.")) return ff = pyfits.open(filename)[ext] try: modelname = ff.header['PA_model'] except: raise (PE.pyaOtherErrors("File (" + str(filename) + ") contains no model.", where="restoreFromFits", solution="Use another file.")) return if modelname == 'convLine': import convLine as cl nLines = ff.header['model_p0'] model = cl.ConvLine(nLines) elif modelname == 'multiGauss1d': from PyAstronomy.funcFit import MultiGauss1d nLines = ff.header['model_p0'] model = MultiGauss1d(nLines) for p in model.parameters(): model[p] = ff.header[p] return model
def plotDeviance(self, parsList=None): """ Plots value of deviance over parameter values encountered during sampling. Parameters ---------- parsList : string or list of strings, optional, Refers to a parameter name or a list of parameter names. If None, all available parameters are plotted. """ if not ic.check["matplotlib"]: PE.warn(PE.PyARequiredImport("To use 'plotHists', matplotlib has to be installed.", \ solution="Install matplotlib.")) return if isinstance(parsList, six.string_types): parsList = [parsList] tracesDic = {} if parsList is not None: for parm in parsList: self._parmCheck(parm) tracesDic[parm] = self[parm] else: # Use all available traces for parm in self.availableParameters(): tracesDic[parm] = self[parm] ps = self.__plotsizeHelper(len(tracesDic)) for i, [pars, trace] in enumerate(six.iteritems(tracesDic), 1): plt.subplot(ps[0], ps[1], i) plt.xlabel(pars) plt.ylabel("Deviance") plt.plot(self[pars], self["deviance"], '.')
def plotHist(self, parsList=None): """ Plots distributions for a number of traces. Parameters ---------- parsList : string or list of strings, optional, Refers to a parameter name or a list of parameter names. If None, all available parameters are plotted. """ if not ic.check["matplotlib"]: PE.warn(PE.PyARequiredImport("To use 'plotHists', matplotlib has to be installed.", \ solution="Install matplotlib.")) return if isinstance(parsList, six.string_types): parsList = [parsList] tracesDic = {} if parsList is not None: for parm in parsList: self._parmCheck(parm) tracesDic[parm] = self[parm] else: # Use all available traces for parm in self.availableParameters(): tracesDic[parm] = self[parm] cols, rows = self.__plotsizeHelper(len(tracesDic)) for i, [pars, trace] in enumerate(tracesDic.items()): if len(tracesDic) > 1: plt.subplot(rows, cols, i + 1) plt.hist(trace, label=pars + " hist") plt.legend()
def bayesFactors(model1, model2): """ Computes the Bayes factor for two competing models. The computation is based on Newton & Raftery 1994 (Eq. 13). Parameters ---------- model1 : PyAstronomy.funcFit.TraceAnalysis instance TraceAnalysis for the first model. model2 : PyAstronomy.funcFit.TraceAnalysis instance TraceAnalysis for the second model. Returns ------- BF : float The Bayes factor BF (neither log10(BF) nor ln(BF)). Note that a small value means that the first model is favored, i.e., BF=p(M2)/p(M1). """ if not ic.check["pymc"]: raise(PE.PyARequiredImport("pymc is required to evaluate the bayesFactor", \ solution="Install pymc")) logp1 = model1["deviance"] / (-2.) logp2 = model2["deviance"] / (-2.) # Given a number of numbers x1, x2, ... whose logarithms are given by l_i=ln(x_i) etc., # logsum calculates: ln(sum_i exp(l_i)) = ln(sum_i x_i) bf = numpy.exp( pymc.flib.logsum(-logp1) - numpy.log(len(logp1)) - (pymc.flib.logsum(-logp2) - numpy.log(len(logp2)))) return bf
def planck(T, lam=None, nu=None): """ Evaluate Planck's radiation law. Depending on whether wavelength or frequency is specified as input, the function evaluates: .. math:: B_{\\nu} = \\frac{2\\pi h \\nu^3}{c^2} \\frac{1}{e^{\\frac{h\\nu}{kT}} - 1} or .. math:: B_{\\lambda} = \\frac{2\\pi h c^2}{\\lambda^5} \\frac{1}{e^{\\frac{h c}{\\lambda kT}} - 1} \\; . If lambda is given (in meters), the output units are W/(m^2 m). To convert into erg/(cm^2 A s), the output has to be multiplied by a factor of 1e-7. Parameters ---------- T : float Temperature in Kelvin. lam : float or array, optional Wavelength in meters. nu : float or array, optional Frequency in Hz. Returns ------- Spectral radiance : float or array Depending on whether `lam` or `nu` were specified, returns the spectral radiance per area and wavelength or frequency. The unit (SI) will be W/(m^2 m) if `lam` was given and W/(m^2 Hz) if `nu` was specified. """ if _ic.check["quantities"]: from PyAstronomy import constants c = constants.PyAConstants(unitSystem="SI") else: raise (PE.PyARequiredImport( "You need to install 'quantities' to use this function.\n 'quantities' can be obtained from 'http://pypi.python.org/pypi/quantities'." )) return None if (lam is not None) and (nu is not None): raise (PE.PyAParameterConflict( "Specify either 'lam' OR 'nu', but not both.")) if (lam is None) and (nu is None): raise (PE.PyAParameterConflict("Specify either 'lam' OR 'nu'.")) # Parameters have been specified properly if lam is not None: result = 2. * np.pi * c.h * (c.c**2) / (lam**5) result /= (np.exp(c.h * c.c / (lam * c.k * T)) - 1.0) return result elif nu is not None: result = 2. * np.pi * c.h * (nu**3) / (c.c**2) result /= (np.exp(c.h * nu / (c.k * T)) - 1.0) return result
def plotCorrEnh(self, parsList=None, **plotArgs): """ Produces enhanced correlation plots. Parameters ---------- parsList : list of string, optional If not given, all available traces are used. Otherwise a list of at least two parameters has to be specified. plotArgs : dict, optional Keyword arguments handed to plot procedures of pylab. The following keywords are available: contour,bins,cmap,origin,interpolation,colors """ if not ic.check["matplotlib"]: PE.warn(PE.PyARequiredImport("To use 'plotCorr', matplotlib has to be installed.", \ solution="Install matplotlib.")) return tracesDic = {} if parsList is not None: for parm in parsList: self._parmCheck(parm) tracesDic[parm] = self[parm] if len(tracesDic) < 2: raise(PE.PyAValError("For plotting correlations, at least two valid parameters are needed.", \ where="TraceAnalysis::plotCorr")) else: # Use all available traces for parm in self.availableParameters(): tracesDic[parm] = self[parm] pars = list(tracesDic.keys()) traces = list(tracesDic.values()) fontmap = {1: 10, 2: 9, 3: 8, 4: 8, 5: 8} if not len(tracesDic) - 1 in fontmap: fontmap[len(tracesDic) - 1] = 8 k = 1 for j in range(len(tracesDic)): for i in range(len(tracesDic)): if i > j: plt.subplot(len(tracesDic) - 1, len(tracesDic) - 1, k) # plt.title("Pearson's R: %1.5f" % self.pearsonr(pars[j],pars[i])[0], fontsize='x-small') plt.xlabel(pars[j], fontsize=fontmap[len(tracesDic) - 1]) plt.ylabel(pars[i], fontsize=fontmap[len(tracesDic) - 1]) tlabels = plt.gca().get_xticklabels() plt.setp(tlabels, 'fontsize', fontmap[len(tracesDic) - 1]) tlabels = plt.gca().get_yticklabels() plt.setp(tlabels, 'fontsize', fontmap[len(tracesDic) - 1]) # plt.plot(traces[j],traces[i],'.',**plotArgs) self.__hist2d(traces[j], traces[i], **plotArgs) if i != j: k += 1
def _read_sweetcat(self): """ Read SWEETCat into a pandas DataFrame """ if not _ic.check["pandas"]: raise(PE.PyARequiredImport("Could not import pandas module.", solution="Please install pandas (http://pandas.pydata.org). Please also check the dependencies.")) else: import pandas as pd ffn = self._fs.requestFile(self.dataFileName, 'r', gzip.open) self.data = pd.read_csv( ffn, sep='\t', names=self.names, na_values=['~'])
def updateFitsHeader(model, hdr, clobber=False, conf=0.9): """ Update the delivered fits-header with the parameters of the model. Parameters: - `hdr` - Pyfits header which should be updated with the model parameters. - `conf' - Confidence level for MCMC errors. - `clobber` - Allows to overwrite parameters which might be already present in the header. """ if not ic.check["pyfits"]: raise (PE.PyARequiredImport("pyfits required to use fits file.", where="Params::updateFitsHeader")) return try: # @FIXME The next line can NEVER work # x=hdr[p] raise (PE.pyaOtherErrors( " Keyword PA_model already present in fits-header, aborting...", where="Params::updateFitsHeader")) return except: pass hdr.update('PA_model', model.naming.getRoot(), 'PyAstronomy model type') for p in model.parameters(): if clobber == False: try: x = hdr[p] raise (PE.pyaOtherErrors(" Parameter " + str(p) + " already present in fits-header", where="Params::updateFitsHeader")) return except: pass hdr.update(p, model[p]) if ic.check["pymc"]: from pymc.utils import hpd hdr.update('Conf', conf, "Error Confidence Level") for p in model.parameters(): try: v_err = hpd(model.MCMC.trace(p)[:], 1.0 - conf) p0, p1 = str(p + '_e0'), str(p + '_e1') if len(p0) > 8: raise (PE.pyaOtherErrors( " Cannot save Error for parameter " + str(p) + " because len(" + p0 + ")>8", where="Params::updateFitsHeader")) return hdr.update(p0, v_err[0], "Lower confidence boundary") hdr.update(p1, v_err[1], "Upper confidence boundary") except: pass return hdr
def _checkPackage(self, p): """ Check whether package is availabel and raise exception otherwise. Parameters ---------- p : string Name of package """ if not ic.check[p]: raise(PE.PyARequiredImport("The package '" + str(p) + "' is not currently installed.", \ solution="Please install " + str(p)))
def equiTempPlanet(Ts, R, a, albedo=0.0, e=0.0, solarUnits=False): """ Calculate the planetary equilibrium temperature. The equilibrium temperature is defined, e.g., in Laughlin et al. 2011 (ApJ, 729), Eq. 1. Albedo was added "by hand". Note that units have to be consistent for stellar radius and large semi-major axis. The `solarUnits` flag can be used to use solar units for these quantities. Parameters ---------- Ts : float Stellar effective temperature [K]. R : float Radius of the star a : float Large semi-major axis albedo : float Albedo of the planetary atmosphere. Default is 0.0. e : float, optional Eccentricity of orbit (default is 0.0). solarUnits : boolean, optional If this flag is set True, stellar radius and semi-major axis are taken to be in solar radii and AU. Returns ------- Equilibrium temperature : float The equilibrium temperature of the planet. """ if _ic.check["quantities"]: from PyAstronomy import constants as c else: raise (PE.PyARequiredImport( "You need to install 'quantities' to use this function.")) return None if solarUnits: a = a * c.AU R = R * c.RSun if a < R: raise (PE.PyAValError( "The semi-major axis cannot be smaller than the radius of the star." )) teq = np.power(1. - albedo, 1./4.) * np.sqrt(R / (2.0 * a)) * Ts \ / np.power(1.0 - e**2, 1./8.) return teq
def getAllDataPandas(self): """ Get all data as pandas DataFrame. Returns ------- table : DataFrame All available data in pandas format. """ if not _ic.check["pandas"]: raise(PE.PyARequiredImport("You need to install 'pandas' to use pandas DataFrames.", \ solution="Install 'pandas' package.")) return self.vot.to_pandas()
def __init__(self): _ZList.__init__(self, "circular") if not _importOccultnl: raise(PE.PyARequiredImport("Could not import required shared object library 'occultquad.so'",\ solution=["Invoke PyA's install script (setup.py) with the --with-ext option.", "Go to 'forTrans' directory of PyAstronomy and invoke\n f2py -c occultnl.pyf occultnl.f"] )) fuf.OneDFit.__init__( self, ["p", "a", "i", "a1", "a2", "a3", "a4", "T0", "per", "b"]) self.freeze(["p", "a", "i", "a1", "a2", "a3", "a4", "T0", "per", "b"]) self.setRootName("MandelAgolNL") self.__zlist = None
def __init__(self): _ZList.__init__(self, "circular") fuf.OneDFit.__init__(self, ["p", "a", "i", "linLimb", "quadLimb", "T0", "per", "b"]) self.freeze(["p", "a", "i", "linLimb", "quadLimb", "T0", "per", "b"]) self.setRootName("PalCirc08") self._zlist=None if (not mpmath_imported) and (not boostEll_imported): raise(PE.PyARequiredImport("Neither mpmath nor the elliptical integrals from the boost library could be found!", \ where="PalLC::__init__", solution="Install mpmath or make Boost library available (more complicated - see documentation)")) self.useBoost = False if boostEll_imported: self.useBoost = True self.ell = ell.ell()
def __init__(self, collisionCheck=False): _ZList.__init__(self, "keplerian", collisionCheck) fuf.OneDFit.__init__(self, [ "p", "a", "i", "linLimb", "quadLimb", "tau", "per", "b", "w", "Omega", "e" ]) self.freeze([ "p", "a", "i", "linLimb", "quadLimb", "tau", "per", "b", "w", "Omega", "e" ]) self.setRootName("Pal08") self["per"] = 1.0 self["w"] = -90 self._zlist = None if (not mpmath_imported) and (not boostEll_imported): raise (PE.PyARequiredImport( "Neither mpmath nor the elliptical integrals from the boost library could be found!", where="PalLC::__init__", solution= "Install mpmath or make Boost library available (more complicated - see documentation)" )) self.useBoost = False if boostEll_imported: self.useBoost = True self.ell = ell.ell() self.collisionCheck = collisionCheck # Wrap get/setitem to inform about change in parameter name T0paE = PE.PyADeprecationError( "The parameter 'T0pa' in PalLCKep had to be renamed 'tau'.", solution="Use 'tau' instead of 'T0pa'.") def getitem(specifier, **kwargs): if specifier == "T0pa": PE.warn(T0paE) return PalLC.__getitem__(self, specifier, **kwargs) self.__getitem__ = getitem def setitem(specifier, value): if specifier == "T0pa": PE.warn(T0paE) PalLC.__setitem__(self, specifier, value) self.__setitem__ = setitem
def plot(self, *args, **kwargs): """ Creates a matplotlib figure and axes class instance to visualize the result. Parameters: - `FAPlevels` - optional, List of false-alarm probability (FAP) levels - `*args` - optional, Arguments passed to plot method of axes class. - `**kwargs` - optional, Keyword arguments passed plot method to axes class. This method provides a quick and simple way to visualize the results of the a periodogram calculation. Returns: The created *Figure* and *Axes* class instances. """ if not _mplImport: raise(PE.PyARequiredImport("To use this function matplotlib must be installed.", \ where="PeriodBase::plot", solution=["Install matplotlib.", "Avoid call and use custom plotting interface."])) fig = mpl.figure() ax = fig.add_subplot(1, 1, 1) if "FAPlevels" in kwargs: fapLvs = numpy.array(kwargs["FAPlevels"]) del kwargs["FAPlevels"] powLvs = self.powerLevel(fapLvs) for i, powLv in enumerate(powLvs): ax.plot([self.freq.min(), self.freq.max()], [powLv, powLv], 'k--') ax.annotate("FAP " + ("%2.3g" % fapLvs[i]), xy=(self.freq.max(), powLv), xytext=None, xycoords='data', textcoords='data', arrowprops=None, horizontalalignment="right") if self.label is None: ax.set_title("Periodogram") ax.set_xlabel("Frequency") ax.set_ylabel("Power") else: ax.set_title(self.label["title"]) ax.set_xlabel(self.label["xlabel"]) ax.set_ylabel(self.label["ylabel"]) ax.plot(self.freq, self.power, *args, **kwargs) return fig, ax
def __init__(self, skipUpdate=False, forceUpdate=False): if not _ic.check["astropy"]: raise(PE.PyARequiredImport("The 'astropy' package is not installed. astropy is required to read VO tables.", solution="Please install 'astropy'.")) configFilename = os.path.join("pyasl", "resBased", "epeuvo.cfg") pp.PyAUpdateCycle.__init__(self, configFilename, "ExoUpdate") self.dataFileName = os.path.join("pyasl", "resBased", "epeu.vo.gz") self._fs = pp.PyAFS() if forceUpdate: self._update(self._download) elif (self.needsUpdate() or (not self._fs.fileExists(self.dataFileName))) and (not skipUpdate): # Download data if data file does not exist or # regular update is indicated self._update(self._download) self._readData()
def plotTraceHist(self, parm): """ Plots trace and histogram (distribution). Parameters ---------- parm : string The variable name. """ if not ic.check["matplotlib"]: PE.warn(PE.PyARequiredImport("To use 'plotTraceHist', matplotlib has to be installed.", \ solution="Install matplotlib.")) return self._parmCheck(parm) plt.subplot(1, 2, 1) self.plotTrace(parm) plt.subplot(1, 2, 2) self.plotHist(parm)
def plotTrace(self, parm, fmt='b-'): """ Plots the trace. Parameters ---------- parm : string The variable name. fmt : string, optional The matplotlib format string used to plot the trace. Default is 'b-'. """ if not ic.check["matplotlib"]: PE.warn(PE.PyARequiredImport("To use 'plotTrace', matplotlib has to be installed.", \ solution="Install matplotlib.")) return self._parmCheck(parm) plt.plot(self[parm], fmt, label=parm + " trace") plt.legend()
def plotFIP(): """ Show a plot of first ionization energy vs. atomic number. """ if not _ic.check["matplotlib"]: raise(PE.PyARequiredImport("Could not import matplotlib.")) fip = FirstIonizationPot() z = range(1,31,1) fips = [] for an in z: fips.append(fip.getFIP(an)[0]) import matplotlib.pylab as plt plt.xlabel("Atomic number") plt.ylabel("First ionization energy [eV]") plt.plot(z, fips, 'bp-') plt.show()
def _plot(self): """ Create a plot. """ try: import matplotlib.pylab as plt except ImportError: raise (PE.PyARequiredImport("Could not import matplotlib.pylab.")) self.fig = plt.figure() self.fig.subplots_adjust(hspace=0.35) self.ax = self.fig.add_subplot(2, 1, 1) self.ax.set_title("Normalized periodogram") self.ax.set_xlabel("Frequency") self.ax.set_ylabel("Power") self.ax.plot(self.freq, self.power, 'b.--') self.ax = self.fig.add_subplot(2, 1, 2) self.ax.set_title("Data") self.ax.set_xlabel("Time") self.ax.set_ylabel("Data") self.ax.plot(self.t, self.y, 'r.--') plt.show()
def __init__(self, lineList, uniSig=None, modelBinsize=0.005, useFastRB=True, verbose=False, onlyAbs=True): if not ic.check["scipy"]: raise(PE.PyARequiredImport("Could not import scipy.", \ solution="Install scipy.")) # Check whether depths are given self._depthsGiven = (len(lineList[0,::]) == 3) if (not self._depthsGiven) and (uniSig is None): raise(PE.PyAValError("No width and no depth given.", \ solution="Specify line depth in `lineList` or use `uniSig`.")) # Check whether unified sigma shall be used # Note: Line depth will be ignored then self._uniSig = uniSig # Only absorption lines self._onlyAbs = onlyAbs # Verbosity self._verbose = verbose # Use fast rotational broadening? self._usefastRB = useFastRB # Save binsize for the model self._modelBinsize = modelBinsize # Copy line list self._lineList = lineList.copy() # Number of lines self._nl = len(lineList[::,0]) # A MultiGaussFit object self._mg = fuf.MultiGauss1d(self._nl) # Store all parameter names from GaussFit pars = list(self._mg.parameters().keys()) # Remove lin and off from multiGauss keys pars.remove("lin") pars.remove("off") pars.extend(["lineScale", "scale", "eps", "vrad", "vsini"]) fuf.OneDFit.__init__(self, pars) # Name the model self.setRootName("LineListGauss") # Assign start values for i in range(self._nl): p = str(i+1) # Assign position self._mg["mu"+p] = lineList[i,0] self["mu"+p] = lineList[i,0] # Assign amplitude/EW # Note: Positive EWs correspond to absorption lines self._mg["A"+p] = -lineList[i,1] self["A"+p] = lineList[i,1] # Assign width (sigma) if self._depthsGiven and (self._uniSig is None): # Depth IS given and no unified sigma specified self._mg["sig"+p] = abs(lineList[i,1])/((1.-lineList[i,2]) * np.sqrt(2.*np.pi)) self["sig"+p] = self._mg["sig"+p] elif not (self._uniSig is None): # uniSig IS given self._mg["sig"+p] = self._uniSig self["sig"+p] = self._mg["sig"+p] self["scale"] = 1.0 self["lineScale"] = 1.0 if self._onlyAbs: # Apply restrictions to prevent emission lines for i in range(self._nl): p = str(i+1) self.setRestriction({"A"+p:[0.0, None]})
def ibtrapz(x, y, x0, x1, iaout=False): """ Use the trapezoid rule to integrate and interpolate boundary values. Can be used for integration on tabled data, where the integration boundaries are not located on the grid. The values to use at the boundaries are determined using linear interpolation. Parameters ---------- x,y : arrays The data. x0, x1 : float The integration boundaries. iaout : boolean, optional If True, the arrays used internally for integration are also returned. The default is False. Returns ------- Integral : float The value of the resulting integral. xi, yi : arrays, optional Internally used arrays for integration including the values derived at the boundaries. Only returned if `iaout` is set to True. """ if not _ic.check["scipy"]: raise(PE.PyARequiredImport("scipy could not be imported.", \ where="ibtrapz", \ solution="Please install scipy to use ibtrapz.")) import scipy.interpolate as sci import scipy.integrate as scinteg if (x1 < x0): raise(PE.PyAValError("x1 must be larger than x0.", \ where="ibtrapz", \ solution="Exchange boundaries.")) if np.any(x[1:] - x[0:-1] < 0.0): raise(PE.PyAValError("x axis must be in ascending order.", \ where="ibtrapz", \ solution="Sort values.")) if (x0 < np.min(x)) or (x1 > np.max(x)): raise(PE.PyAValError("One or both integration boundaries are beyond the range of data given.", \ where="ibtrapz", \ solution="Give more data or adjust integration boundaries.")) # Interpolate at integration boundaries fi = sci.interp1d(x, y) y0, y1 = fi(x0), fi(x1) # Search data within boundaries indi = np.where((x > x0) & (x < x1))[0] # Construct arrays to be used in integration xi = np.zeros(len(indi) + 2) yi = np.zeros(len(indi) + 2) xi[1:-1] = x[indi] yi[1:-1] = y[indi] xi[0] = x0 xi[-1] = x1 yi[0] = y0 yi[-1] = y1 if iaout: return scinteg.trapz(yi, xi), xi, yi return scinteg.trapz(yi, xi)
def crosscorrRV(w, f, tw, tf, rvmin, rvmax, drv, mode="doppler", skipedge=0, edgeTapering=None): """ Cross-correlate a spectrum with a template. The algorithm implemented here works as follows: For each RV shift to be considered, the wavelength axis of the template is shifted, either linearly or using a proper Doppler shift depending on the `mode`. The shifted template is then linearly interpolated at the wavelength points of the observation (spectrum) to calculate the cross-correlation function. Parameters ---------- w : array The wavelength axis of the observation. f : array The flux axis of the observation. tw : array The wavelength axis of the template. tf : array The flux axis of the template. rvmin : float Minimum radial velocity for which to calculate the cross-correlation function [km/s]. rvmax : float Maximum radial velocity for which to calculate the cross-correlation function [km/s]. drv : float The width of the radial-velocity steps to be applied in the calculation of the cross-correlation function [km/s]. mode : string, {lin, doppler}, optional The mode determines how the wavelength axis will be modified to represent a RV shift. If "lin" is specified, a mean wavelength shift will be calculated based on the mean wavelength of the observation. The wavelength axis will then be shifted by that amount. If "doppler" is specified (the default), the wavelength axis will properly be Doppler-shifted. skipedge : int, optional If larger zero, the specified number of bins will be skipped from the begin and end of the observation. This may be useful if the template does not provide sufficient coverage of the observation. edgeTapering : float or tuple of two floats If not None, the method will "taper off" the edges of the observed spectrum by multiplying with a sine function. If a float number is specified, this will define the width (in wavelength units) to be used for tapering on both sides. If different tapering widths shall be used, a tuple with two (positive) numbers must be given, specifying the width to be used on the low- and high wavelength end. If a nonzero 'skipedge' is given, it will be applied first. Edge tapering can help to avoid edge effects (see, e.g., Gullberg and Lindegren 2002, A&A 390). Returns ------- dRV : array The RV axis of the cross-correlation function. The radial velocity refer to a shift of the template, i.e., positive values indicate that the template has been red-shifted and negative numbers indicate a blue-shift of the template. The numbers are given in km/s. CC : array The cross-correlation function. """ if not _ic.check["scipy"]: raise(PE.PyARequiredImport("This routine needs scipy (.interpolate.interp1d).", \ where="crosscorrRV", \ solution="Install scipy")) import scipy.interpolate as sci # Copy and cut wavelength and flux arrays w, f = w.copy(), f.copy() if skipedge > 0: w, f = w[skipedge:-skipedge], f[skipedge:-skipedge] if edgeTapering is not None: # Smooth the edges using a sine if isinstance(edgeTapering, float): edgeTapering = [edgeTapering, edgeTapering] if len(edgeTapering) != 2: raise(PE.PyAValError("'edgeTapering' must be a float or a list of two floats.", \ where="crosscorrRV")) if edgeTapering[0] < 0.0 or edgeTapering[1] < 0.0: raise(PE.PyAValError("'edgeTapering' must be (a) number(s) >= 0.0.", \ where="crosscorrRV")) # Carry out edge tapering (left edge) indi = np.where(w < w[0] + edgeTapering[0])[0] f[indi] *= np.sin((w[indi] - w[0]) / edgeTapering[0] * np.pi / 2.0) # Carry out edge tapering (right edge) indi = np.where(w > (w[-1] - edgeTapering[1]))[0] f[indi] *= np.sin((w[indi] - w[indi[0]]) / edgeTapering[1] * np.pi / 2.0 + np.pi / 2.0) # Speed of light in km/s c = 299792.458 # Check order of rvmin and rvmax if rvmax <= rvmin: raise(PE.PyAValError("rvmin needs to be smaller than rvmax.", where="crosscorrRV", \ solution="Change the order of the parameters.")) # Check whether template is large enough if mode == "lin": meanWl = np.mean(w) dwlmax = meanWl * (rvmax / c) dwlmin = meanWl * (rvmin / c) if (tw[0] + dwlmax) > w[0]: raise(PE.PyAValError("The minimum wavelength is not covered by the template for all indicated RV shifts.", \ where="crosscorrRV", \ solution=["Provide a larger template", "Try to use skipedge"])) if (tw[-1] + dwlmin) < w[-1]: raise(PE.PyAValError("he maximum wavelength is not covered by the template for all indicated RV shifts.", \ where="crosscorrRV", \ solution=["Provide a larger template", "Try to use skipedge"])) elif mode == "doppler": # Ensure that the template covers the entire observaion for all shifts maxwl = tw[-1] * (1.0 + rvmin / c) minwl = tw[0] * (1.0 + rvmax / c) if minwl > w[0]: raise(PE.PyAValError("he minimum wavelength is not covered by the template for all indicated RV shifts.", \ where="crosscorrRV", \ solution=["Provide a larger template", "Try to use skipedge"])) if maxwl < w[-1]: raise(PE.PyAValError("The maximum wavelength is not covered by the template for all indicated RV shifts.", \ where="crosscorrRV", \ solution=["Provide a larger template", "Try to use skipedge"])) else: raise(PE.PyAValError("Unknown mode: " + str(mode), \ where="crosscorrRV", \ solution="See documentation for available modes.")) # Calculate the cross correlation drvs = np.arange(rvmin, rvmax, drv) cc = np.zeros(len(drvs)) for i, rv in enumerate(drvs): if mode == "lin": # Shift the template linearly fi = sci.interp1d(tw + meanWl * (rv / c), tf) elif mode == "doppler": # Apply the Doppler shift fi = sci.interp1d(tw * (1.0 + rv / c), tf) # Shifted template evaluated at location of spectrum cc[i] = np.sum(f * fi(w)) return drvs, cc
def read1dFitsSpec(fn, hdu=0, fullout=False, CRPIX1=None, keymap={}): """ Read a simple 1d spectrum from fits file. Reads a 1d-spectrum from a file and constructs the associated wavelength axis. To this end, the expression: wvl = ((np.arange(N) + 1.0) - CRPIX1) * CDELT1 + CRVAL1 will be evaluated, where N is the number of bins and CRPIX1, CDELT1, and CRVAL1 are header keywords. Parameters ---------- fn : string Filename hdu : int, optional The number of the HDU to be used. The default is 0, i.e., the primary HDU. fullout : boolean, optional If True, the header keywords used to construct the wavelength axis will be returned. The default is False. CRPIX1 : int, optional Can be used to circumvent missing CRPIX1 entry. keymap : dict, optional Can be used to map header keywords Returns ------- wvl : array The wavelength array. flx : array The flux array. Examples -------- :: from PyAstronomy import pyasl wvl, flx = pyasl.read1dFitsSpec("mySpectrum.fits") """ if (not _ic.check["pyfits"]) and (not _ic.check["astropy.io.fits"]): raise(PE.PyARequiredImport("Could neither import module 'pyfits' and 'astropy.io.fits'.", where="read1dFitsSpec", solution="Install pyfits: http://www.stsci.edu/institute/software_hardware/pyfits")) if not os.path.isfile(fn): raise(PE.PyAFileError(fn, "ne", where="read1dFitsSpec", solution="Check file name.")) if _ic.check["pyfits"]: import pyfits else: import astropy.io.fits as pyfits hl = pyfits.open(fn) naxis = hl[hdu].header["NAXIS"] if hl[hdu].header["NAXIS"] != 1: if hl[hdu].header["NAXIS"] == 2 and hl[hdu].header["NAXIS2"] == 1: pass else: raise(PE.PyAValError("There is more than one axis (NAXIS = " + str(naxis) + ", actual value is " + str(hl[hdu].header["NAXIS"]) + ").", where="read1dFitsSpec", solution="Check file and HDU.")) hkeys = {"CRVAL1": None, "CRPIX1": CRPIX1, "CDELT1": None} for k in hkeys.keys(): if k not in keymap: keymap[k] = k for k in hkeys.keys(): if hkeys[k] is not None: continue if not keymap[k] in hl[hdu].header: raise(PE.PyAValError("Header does not contain required keyword '" + str(keymap[k]) + "'", where="read1dFitsSpec", solution="Check file and HDU.")) hkeys[k] = hl[hdu].header[keymap[k]] N = int(hl[hdu].header["NAXIS1"]) # Construct wvl axis wvl = ((np.arange(N) + 1.0) - hkeys["CRPIX1"] ) * hkeys["CDELT1"] + hkeys["CRVAL1"] # Get flux if naxis == 1: flx = np.array(hl[hdu].data) elif naxis == 2: flx = np.array(hl[hdu].data[0]) if fullout: # Full output return wvl, flx, hkeys return wvl, flx
def write1dFitsSpec(fn, flux, wvl=None, waveParams=None, fluxErr=None, header=None, clobber=False, refFileName=None, refFileExt=0): """ Write a 1d spectrum with equidistant binning to a fits file. Write a 1d-spectrum to a file. Wavelength axis and header keywords are related through the following expression: wvl = ((np.arange(N) + 1.0) - CRPIX1) * CDELT1 + CRVAL1, where CRPIX1, CDELT1, and CRVAL1 are the relevant header keywords. The function allows to specify an existing fits extension, from which the header will be cloned. Alternatively, an arbitrary, user-defined header may be given. Parameters ---------- fn : string Filename flux : array Flux array wvl : array, optional Wavelength array. Either the wavelength array or the header keywords have to be provided (see `waveParams`). waveParams : dict, optional Wavelength information required in the fits-header. Required keys are CDELT, CRVAL, CRPIX or (CDELT1, CRVAL1, CRPIX1). fluxErr : array, optional Flux errors. If given, the error will be stored in an additional extension. header : dict, optional Dictionary with header information to be transfered to the new file. Note that the wavelength information will be overwritten by the information given to this routine. If both, a reference file to clone the header from and the header parameters are given, the cloned header from the reference file will be overwritten and extended by the keywords specified here. refFileName : string, optional Clone header keywords from a reference file. Note that the wavelength information will be overwritten by the information by the information given to this routine. refFileExt : int, optional Reference-file extension to be used for cloning the header keywords. The default is 0. """ reservedKeyWords = [ "BITPIX", "SIMPLE", "NAXIS", "NAXIS1", "CDELT1", "CRPIX1", "CRVAL1", "EXTEND" ] if (not _ic.check["pyfits"]) and (not _ic.check["astropy.io.fits"]): raise (PE.PyARequiredImport( "Could neither import module 'pyfits' and 'astropy.io.fits'.", where="write1dFitsSpec", solution= "Install pyfits: http://www.stsci.edu/institute/software_hardware/pyfits" )) if wvl is None and waveParams is None: raise (PE.PyAValError( "The wavelength axis is not defined, i.e., neither \'wvl\' nor \'waveParams\' is specified.", where="write1dFitsSpec", solution= "Provide wavelength array as \'wvl\' or wavelength information as \'waveParams\'." )) if wvl is not None and waveParams is not None: raise (PE.PyAValError( "You provided the wavelength axis as \'wvl\' AND wavelength information as \'waveParams\'. Don't know which to use.", where="write1dFitsSpec", solution= "Provide only wavelength array via \'wvl\' or wavelength information via \'waveParams\'." )) if wvl is not None: # Check whether the wavelength axis is equidistant dwl = wvl[1:] - wvl[0:-1] if (np.max(dwl) - np.min(dwl)) / np.max(dwl) > 1e-6: raise (PE.PyAValError( "Wavelength axis seems not to be equidistant.", where="write1dFitsSpec", solution=[ "Check wavelength array.", "Consider passing wavelength information via `waveParams`." ])) if os.path.isfile(fn) and not clobber: raise (PE.PyAFileError( fn, "ae", where="write1dFitsSpec", solution="File exists; set clobber=True to overwrite.")) if _ic.check["pyfits"]: import pyfits else: import astropy.io.fits as pyfits # Put the flux into the output file. # Generate primary HDU hdu = pyfits.PrimaryHDU(flux) if refFileName: # If the is a reference file, its header will be used to populate # the header of the file to be written. if not os.path.isfile(refFileName): raise (PE.PyAFileError(str(refFileName), "ne", where="write1dFitsSpec", solution="Check file name.")) ff = pyfits.open(refFileName)[refFileExt] for k in ff.header.keys(): if k not in reservedKeyWords and k != "COMMENT": try: if len(k) > 8: hdu.header["HIERARCH " + k] = ff.header[k] else: hdu.header[k] = ff.header[k] except: PE.warn( PE.PyAValError("Cannot write keyword <" + str(k) + "> with content " + str(ff.header[k]))) if header is not None: # A use-defined header was specified for k in header.keys(): hdu.header[k] = header[k] # Header keywords relevant for wavelength axis hk = {} # The only allowed type here hk["CTYPE1"] = "Linear" # If wavelength array is provided, create header keywords: if wvl is not None: hk["CRPIX1"] = 1 hk["CRVAL1"] = wvl[0] hk["CDELT1"] = float(wvl[-1] - wvl[0]) / (len(wvl) - 1) elif waveParams is not None: requiredKeys = ["CRPIX", "CRVAL", "CDELT"] # Counts whether all required keywords are provided count = 0 for k in requiredKeys: subCount = 0 for p in six.iterkeys(waveParams): if p.upper() == k or p.upper() == k + "1": hk[k + "1"] = waveParams[p] subCount += 1 if subCount == 1: count += 1 else: # This occurs if, e.g., both "CRVAL" and "CRVAL1" are present raise (PE.PyAValError( "The wavelength parameters provided via \'waveParams\' contain ambiguous parameters. " + "You provided multiple values for " + k + ".", where="write1dFitsSpec", solution= "Check content of \'waveParams\' so that only one value for " + k + " is provided (including lower/upper case and presence of a trailing \'1\'." )) if count < 3: raise (PE.PyAValError( "You provided an incomplete set of waveParams.", where="write1dFitsSpec", solution="Required keywords are CRPIX, CRVAL, and CDELT.")) # (Over-)Write wavelength-related header information for k in hk.keys(): hdu.header[k] = hk[k] if not fluxErr is None: # An error on the flux was given hdue = pyfits.ImageHDU(fluxErr) for k in hk.keys(): # Add wavelength information to error header hdue.header[k] = hk[k] hdulist = pyfits.HDUList([hdu, hdue]) else: hdulist = pyfits.HDUList([hdu]) hdulist.writeto(fn, overwrite=clobber)
def sunpos2(jd, end_jd=None, jd_steps=None, outfile=None, radian=False, plot=False, full_output=False): """ Compute right ascension and declination of the Sun at a given time. Parameters ---------- jd : float The Julian date end_jd : float, optional The end of the time period as Julian date. If given, `sunpos` computes RA and DEC at `jd_steps` time points between `jd` and ending at `end_jd`. jd_steps : integer, optional The number of steps between `jd` and `end_jd` for which RA and DEC are to be calculated. outfile : string, optional If given, the output will be written to a file named according to `outfile`. radian : boolean, optional Results are returned in radian instead of in degrees. Default is False. plot : boolean, optional If True, the result is plotted. full_output: boolean, optional If True, `sunpos`, additionally, returns the elongation and obliquity of the Sun. Returns ------- Time : array The JDs for which calculations where carried out. Ra : array Right ascension of the Sun. Dec : array Declination of the Sun. Elongation : array, optional Elongation of the Sun (only of `full_output` is set to True). Obliquity : array, optional Obliquity of the Sun (only of `full_output` is set to True). Notes ----- .. note:: This function was ported from the IDL Astronomy User's Library. :IDL - Documentation: NAME: SUNPOS PURPOSE: To compute the RA and Dec of the Sun at a given date. CALLING SEQUENCE: SUNPOS, jd, ra, dec, [elong, obliquity, /RADIAN ] INPUTS: jd - The Julian date of the day (and time), scalar or vector usually double precision OUTPUTS: ra - The right ascension of the sun at that date in DEGREES double precision, same number of elements as jd dec - The declination of the sun at that date in DEGREES OPTIONAL OUTPUTS: elong - Ecliptic longitude of the sun at that date in DEGREES. obliquity - the obliquity of the ecliptic, in DEGREES OPTIONAL INPUT KEYWORD: /RADIAN - If this keyword is set and non-zero, then all output variables are given in Radians rather than Degrees NOTES: Patrick Wallace (Rutherford Appleton Laboratory, UK) has tested the accuracy of a C adaptation of the sunpos.pro code and found the following results. From 1900-2100 SUNPOS gave 7.3 arcsec maximum error, 2.6 arcsec RMS. Over the shorter interval 1950-2050 the figures were 6.4 arcsec max, 2.2 arcsec RMS. The returned RA and Dec are in the given date's equinox. Procedure was extensively revised in May 1996, and the new calling sequence is incompatible with the old one. METHOD: Uses a truncated version of Newcomb's Sun. Adapted from the IDL routine SUN_POS by CD Pike, which was adapted from a FORTRAN routine by B. Emerson (RGO). EXAMPLE: (1) Find the apparent RA and Dec of the Sun on May 1, 1982 IDL> jdcnv, 1982, 5, 1,0 ,jd ;Find Julian date jd = 2445090.5 IDL> sunpos, jd, ra, dec IDL> print,adstring(ra,dec,2) 02 31 32.61 +14 54 34.9 The Astronomical Almanac gives 02 31 32.58 +14 54 34.9 so the error in SUNPOS for this case is < 0.5". (2) Find the apparent RA and Dec of the Sun for every day in 1997 IDL> jdcnv, 1997,1,1,0, jd ;Julian date on Jan 1, 1997 IDL> sunpos, jd+ dindgen(365), ra, dec ;RA and Dec for each day MODIFICATION HISTORY: Written by Michael R. Greason, STX, 28 October 1988. Accept vector arguments, W. Landsman April,1989 Eliminated negative right ascensions. MRG, Hughes STX, 6 May 1992. Rewritten using the 1993 Almanac. Keywords added. MRG, HSTX, 10 February 1994. Major rewrite, improved accuracy, always return values in degrees W. Landsman May, 1996 Added /RADIAN keyword, W. Landsman August, 1997 Converted to IDL V5.0 W. Landsman September 1997 """ if end_jd is None: # Form time in Julian centuries from 1900.0 start_jd = (jd - 2415020.0) / 36525.0 # Zime array time = np.array(start_jd) else: if jd >= end_jd: raise(PE.PyAValError("`end_jd` needs to be larger than `jd`.", \ where="sunpos", \ solution="Modify the parameters.")) if jd_steps is None: raise(PE.PyAValError("You specified `end_jd`, but no value for `jd_steps`.", \ where="sunpos", \ solution="Specify `jd_steps`, e.g., given jd_steps=10")) # Form time in Julian centuries from 1900.0 start_jd = (jd - 2415020.0) / 36525.0 end_jd = (end_jd - 2415020.0) / 36525.0 # Time array timestep = (end_jd - start_jd) / float(jd_steps) time = np.arange(start_jd, end_jd, timestep) # Mean solar longitude sunlon = (279.696678 + idlMod((36000.768925 * time), 360.0)) * 3600.0 # Allow for ellipticity of the orbit (equation of center) # using the Earth's mean anomaly ME me = 358.475844 + idlMod((35999.049750 * time), 360.0) ellcor = (6910.1 - 17.2 * time) * np.sin( me * np.pi / 180.) + 72.3 * np.sin(2.0 * me * np.pi / 180.) sunlon += ellcor # Allow for the Venus perturbations using the mean anomaly of Venus MV mv = 212.603219 + idlMod((58517.803875 * time), 360.0) vencorr = 4.8 * np.cos( (299.1017 + mv - me)*np.pi/180. ) + \ 5.5 * np.cos( (148.3133 + 2.0 * mv - 2.0 * me )*np.pi/180. ) + \ 2.5 * np.cos( (315.9433 + 2.0 * mv - 3.0 * me )*np.pi/180. ) + \ 1.6 * np.cos( (345.2533 + 3.0 * mv - 4.0 * me )*np.pi/180. ) + \ 1.0 * np.cos( (318.15 + 3.0 * mv - 5.0 * me )*np.pi/180. ) sunlon += vencorr # Allow for the Mars perturbations using the mean anomaly of Mars MM mm = 319.529425 + idlMod((19139.858500 * time), 360.0) marscorr = 2.0 * np.cos( (343.8883 - 2.0 * mm + 2.0 * me)*np.pi/180. ) + \ 1.8 * np.cos( (200.4017 - 2.0 * mm + me)*np.pi/180. ) sunlon += marscorr # Allow for the Jupiter perturbations using the mean anomaly of Jupiter MJ mj = 225.328328 + idlMod((3034.6920239 * time), 360.0) jupcorr = 7.2 * np.cos( (179.5317 - mj + me )*np.pi/180. ) + \ 2.6 * np.cos( (263.2167 - mj )*np.pi/180. ) + \ 2.7 * np.cos( ( 87.1450 - 2.0 * mj + 2.0 * me )*np.pi/180. ) + \ 1.6 * np.cos( (109.4933 - 2.0 * mj + me )*np.pi/180. ) sunlon += jupcorr # Allow for the Moon's perturbations using the mean elongation of # the Moon from the Sun D d = 350.7376814 + idlMod((445267.11422 * time), 360.0) mooncorr = 6.5 * np.sin(d * np.pi / 180.) sunlon += mooncorr # Allow for long period terms longterm = 6.4 * np.sin((231.19 + 20.20 * time) * np.pi / 180.) sunlon += longterm sunlon = idlMod((sunlon + 2592000.0), 1296000.0) longmed = sunlon / 3600.0 # Allow for Aberration sunlon -= 20.5 # Allow for Nutation using the longitude of the Moons mean node OMEGA omega = 259.183275 - idlMod((1934.142008 * time), 360.0) sunlon = sunlon - 17.2 * np.sin(omega * np.pi / 180.) # Calculate the True Obliquity oblt = 23.452294 - 0.0130125 * time + ( 9.2 * np.cos(omega * np.pi / 180.)) / 3600.0 # Right Ascension and Declination sunlon /= 3600.0 ra = np.arctan2( np.sin(sunlon * np.pi / 180.) * np.cos(oblt * np.pi / 180.), np.cos(sunlon * np.pi / 180.)) neg = np.where(ra < 0.0)[0] nneg = len(neg) if nneg > 0: ra[neg] += 2.0 * np.pi dec = np.arcsin( np.sin(sunlon * np.pi / 180.) * np.sin(oblt * np.pi / 180.)) if radian: oblt *= (np.pi / 180.) longmed *= (np.pi / 180.) else: ra /= (np.pi / 180.) dec /= (np.pi / 180.) jd = time * 36525.0 + 2415020.0 if outfile is not None: # Write results to a file of = open(outfile, 'w') of.write("# File created by 'sunpos'\n") if not full_output: of.write("# 1) JD, 2) ra, 3) dec\n") np.savetxt(of, np.transpose(np.vstack((jd, ra, dec)))) else: of.write("# 1) JD, 2) ra, 3) dec, 4) longitude, 5) obliquity\n") np.savetxt(of, np.transpose(np.vstack( (jd, ra, dec, longmed, oblt)))) of.close() if plot: if not _ic.check["matplotlib"]: raise(PE.PyARequiredImport("Could not import matplotlib.", \ where="sunpos", \ solution=["Install matplotlib", "Switch `plot` flag to False."])) import matplotlib.pylab as plt plt.plot(jd, ra, 'k-', label="RA") plt.plot(jd, dec, 'g-', label="DEC") plt.legend() plt.show() if full_output: return jd, ra, dec, longmed, oblt else: return jd, ra, dec
def transitVisibilityPlot(allData, markTransit=False, plotLegend=True, showMoonDist=True, print2file=False): """ Plot the visibility of transits. This function can conveniently be used with the output of the transitTimes function. Parameters ---------- allData : dictionary Essentially the output of `transitTimes`. A dictionary mapping consecutive numbers (one per transit) to another dictionary providing the following keys: ============ ==================================================== Key Description ------------ ---------------------------------------------------- Planet name Name of the planet Transit jd (Only if `markTransit is True) Array giving JD of start, mid-time, and end of transit. Obs jd Array specifying the HJD of the start, center and end of the observation. Obs cal Equivalent to 'Obs jd', but in the form of the calendar date. In particular, for each date, a list containing [Year, month, day, fractional hours] is given. Obs coord East longitude [deg], latitude [deg], and altitude [m] of the observatory. Star ra Right ascension of the star [deg]. Star dec Declination of the star [deg]. ============ ==================================================== .. note:: To use the list created by transitTimes, the LONGITUDE and LATITUDE of the observatory location must have been specified. markTransit : boolean, optional If True (default is False), the in-transit times will be clearly indicated in the plot. Note that this would not be the case otherwise, which is particularly important if extra off-transit time before and after the transit has been requested. showMoonDist : boolean, optional If True (default), the Moon distance will be shown. print2file : boolean or string, optional If True, the plot will be dumped to a png-file named: "transitVis-"[planetName].png. The default is False. If a string is given, it specifies the name of the output file. """ from PyAstronomy.pyasl import _ic if not _ic.check["matplotlib"]: raise(PE.PyARequiredImport("matplotlib is not installed.", where="transitVisibilityPlot", solution="Install matplotlib (http://matplotlib.org/)")) import matplotlib import matplotlib.pylab as plt from mpl_toolkits.axes_grid1 import host_subplot from matplotlib.ticker import MultipleLocator from matplotlib.font_manager import FontProperties from matplotlib import rcParams rcParams['xtick.major.pad'] = 12 if len(allData) == 0: raise(PE.PyAValError("Input dictionary is empty", where="transitVisibilityPlot", solution=["Use `transitTimes` to generate input dictionary", "Did you forget to supply observer's location?", "If you used `transitTime`, you might need to change the call argument (e.g., times)"])) # Check whether all relevant data have been specified reqK = ["Obs jd", "Obs coord", "Star ra", "Star dec", "Obs cal", "Planet name"] if markTransit: reqK.append("Transit jd") missingK = [] for k in reqK: if not k in allData[1]: missingK.append(k) if len(missingK) > 0: raise(PE.PyAValError("The following keys are missing in the input dictionary: " + ', '.join(missingK), where="transitVisibilityPlot", solution="Did you specify observer's location in `transitTimes`?")) fig = plt.figure(figsize=(15, 10)) fig.subplots_adjust(left=0.07, right=0.8, bottom=0.15, top=0.88) ax = host_subplot(111) font0 = FontProperties() font1 = font0.copy() font0.set_family('sans-serif') font0.set_weight('light') font1.set_family('sans-serif') font1.set_weight('medium') for n in six.iterkeys(allData): # JD array jdbinsize = 1.0 / 24. / 10. jds = np.arange(allData[n]["Obs jd"][0], allData[n]["Obs jd"][2], jdbinsize) # Get JD floating point jdsub = jds - np.floor(jds[0]) # Get alt/az of object altaz = eq2hor.eq2hor(jds, np.ones(jds.size) * allData[n]["Star ra"], np.ones(jds.size) * allData[n]["Star dec"], lon=allData[n]["Obs coord"][0], lat=allData[n]["Obs coord"][1], alt=allData[n]["Obs coord"][2]) # Get alt/az of Sun sunpos_altaz = eq2hor.eq2hor(jds, np.ones(jds.size) * allData[n]["Sun ra"], np.ones(jds.size) * allData[n]["Sun dec"], lon=allData[n]["Obs coord"][0], lat=allData[n]["Obs coord"][1], alt=allData[n]["Obs coord"][2]) # Define plot label plabel = "[%02d] %02d.%02d.%4d" % (n, allData[n]["Obs cal"][0][2], allData[n]["Obs cal"][0][1], allData[n]["Obs cal"][0][0]) # Find periods of: day, twilight, and night day = np.where(sunpos_altaz[0] >= 0.)[0] twi = np.where(np.logical_and( sunpos_altaz[0] > -18., sunpos_altaz[0] < 0.))[0] night = np.where(sunpos_altaz[0] <= -18.)[0] if (len(day) == 0) and (len(twi) == 0) and (len(night) == 0): print() print("transitVisibilityPlot - no points to draw for date %2d.%2d.%4d" % (allData[n]["Obs cal"][0][2], allData[n]["Obs cal"][0][1], allData[n]["Obs cal"][0][0])) print("Skip transit and continue with next") print() continue mpos = moonpos(jds) mpha = moonphase(jds) mpos_altaz = eq2hor.eq2hor(jds, mpos[0], mpos[1], lon=allData[n]["Obs coord"][0], lat=allData[n]["Obs coord"][1], alt=allData[n]["Obs coord"][2]) moonind = np.where(mpos_altaz[0] > 0.)[0] if showMoonDist: mdist = getAngDist(mpos[0], mpos[1], np.ones(jds.size) * allData[n]["Star ra"], np.ones(jds.size) * allData[n]["Star dec"]) bindist = int((2.0 / 24.) / jdbinsize) firstbin = np.random.randint(0, bindist) for mp in range(0, int(len(jds) / bindist)): bind = int(firstbin + float(mp) * bindist) ax.text(jdsub[bind], altaz[0][bind] - 1., str(int(mdist[bind])) + r"$^\circ$", ha="center", va="top", fontsize=8, stretch='ultra-condensed', fontproperties=font0, alpha=1.) if markTransit: # Mark points within transit. These may differ from that pertaining to the # observation if an extra offset was given to provide off-transit time. transit_only_ind = np.where(np.logical_and(jds >= allData[n]["Transit jd"][0], jds <= allData[n]["Transit jd"][2]))[0] ax.plot(jdsub[transit_only_ind], altaz[0] [transit_only_ind], 'g', linewidth=6, alpha=.3) if len(twi) > 1: # There are points in twilight linebreak = np.where( (jdsub[twi][1:] - jdsub[twi][:-1]) > 2.0 * jdbinsize)[0] if len(linebreak) > 0: plotrjd = np.insert(jdsub[twi], linebreak + 1, np.nan) plotdat = np.insert(altaz[0][twi], linebreak + 1, np.nan) ax.plot(plotrjd, plotdat, "-", color='#BEBEBE', linewidth=1.5) else: ax.plot(jdsub[twi], altaz[0][twi], "-", color='#BEBEBE', linewidth=1.5) ax.plot(jdsub[night], altaz[0][night], 'k', linewidth=1.5, label=plabel) ax.plot(jdsub[day], altaz[0][day], color='#FDB813', linewidth=1.5) altmax = np.argmax(altaz[0]) ax.text(jdsub[altmax], altaz[0][altmax], str(n), color="b", fontsize=14, fontproperties=font1, va="bottom", ha="center") if n == 29: ax.text(1.1, 1.0 - float(n) * 0.04, "too many transits", ha="left", va="top", transform=ax.transAxes, fontsize=10, fontproperties=font0, color="r") else: ax.text(1.1, 1.0 - float(n) * 0.04, plabel, ha="left", va="top", transform=ax.transAxes, fontsize=12, fontproperties=font0, color="b") ax.text(1.1, 1.03, "Start of observation", ha="left", va="top", transform=ax.transAxes, fontsize=12, fontproperties=font0, color="b") ax.text(1.1, 1.0, "[No.] Date", ha="left", va="top", transform=ax.transAxes, fontsize=12, fontproperties=font0, color="b") axrange = ax.get_xlim() ax.set_xlabel("UT [hours]") if axrange[1] - axrange[0] <= 1.0: jdhours = np.arange(0, 3, 1.0 / 24.) utchours = (np.arange(0, 72, dtype=int) + 12) % 24 else: jdhours = np.arange(0, 3, 1.0 / 12.) utchours = (np.arange(0, 72, 2, dtype=int) + 12) % 24 ax.set_xticks(jdhours) ax.set_xlim(axrange) ax.set_xticklabels(utchours, fontsize=18) # Make ax2 responsible for "top" axis and "right" axis ax2 = ax.twin() # Set upper x ticks ax2.set_xticks(jdhours) ax2.set_xticklabels(utchours, fontsize=18) ax2.set_xlabel("UT [hours]") # Horizon angle for airmass airmass_ang = np.arange(5., 90., 5.) geo_airmass = airmass.airmassPP(90. - airmass_ang) ax2.set_yticks(airmass_ang) airmassformat = [] for t in range(geo_airmass.size): airmassformat.append("%2.2f" % geo_airmass[t]) ax2.set_yticklabels(airmassformat, rotation=90) ax2.set_ylabel("Relative airmass", labelpad=32) ax2.tick_params(axis="y", pad=10, labelsize=10) plt.text(1.015, -0.04, "Plane-parallel", transform=ax.transAxes, ha='left', va='top', fontsize=10, rotation=90) ax22 = ax.twin() ax22.set_xticklabels([]) ax22.set_frame_on(True) ax22.patch.set_visible(False) ax22.yaxis.set_ticks_position('right') ax22.yaxis.set_label_position('right') ax22.spines['right'].set_position(('outward', 25)) ax22.spines['right'].set_color('k') ax22.spines['right'].set_visible(True) airmass2 = np.array([airmass.airmassSpherical( 90. - ang, allData[n]["Obs coord"][2]) for ang in airmass_ang]) ax22.set_yticks(airmass_ang) airmassformat = [] for t in range(airmass2.size): airmassformat.append("%2.2f" % airmass2[t]) ax22.set_yticklabels(airmassformat, rotation=90) ax22.tick_params(axis="y", pad=10, labelsize=10) plt.text(1.045, -0.04, "Spherical+Alt", transform=ax.transAxes, ha='left', va='top', fontsize=10, rotation=90) ax3 = ax.twiny() ax3.set_frame_on(True) ax3.patch.set_visible(False) ax3.xaxis.set_ticks_position('bottom') ax3.xaxis.set_label_position('bottom') ax3.spines['bottom'].set_position(('outward', 50)) ax3.spines['bottom'].set_color('k') ax3.spines['bottom'].set_visible(True) ltime, ldiff = localtime.localTime(utchours, np.repeat( allData[n]["Obs coord"][0], len(utchours))) jdltime = jdhours - ldiff / 24. ax3.set_xticks(jdltime) ax3.set_xticklabels(utchours) ax3.set_xlim([axrange[0], axrange[1]]) ax3.set_xlabel("Local time [hours]") ax.yaxis.set_major_locator(MultipleLocator(15)) ax.yaxis.set_minor_locator(MultipleLocator(5)) yticks = ax.get_yticks() ytickformat = [] for t in range(yticks.size): ytickformat.append(str(int(yticks[t])) + r"$^\circ$") ax.set_yticklabels(ytickformat, fontsize=20) ax.set_ylabel("Altitude", fontsize=18) yticksminor = np.array(ax.get_yticks(minor=True)) ymind = np.where(yticksminor % 15. != 0.)[0] yticksminor = yticksminor[ymind] # ax.set_yticks(yticksminor, minor=True) m_ytickformat = [] for t in range(yticksminor.size): m_ytickformat.append(str(int(yticksminor[t])) + r"$^\circ$") ax.set_yticklabels(m_ytickformat, minor=True) ax.yaxis.grid(color='gray', linestyle='dashed') ax.yaxis.grid(color='gray', which="minor", linestyle='dotted') ax2.xaxis.grid(color='gray', linestyle='dotted') def decifnec(s): try: r = s.decode("utf8") except AttributeError: r = s return r plt.text(0.5, 0.95, "Transit visibility of " + decifnec(allData[n]["Planet name"]), transform=fig.transFigure, ha='center', va='bottom', fontsize=20) if plotLegend: line1 = matplotlib.lines.Line2D( (0, 0), (1, 1), color='#FDB813', linestyle="-", linewidth=2) line2 = matplotlib.lines.Line2D( (0, 0), (1, 1), color='#BEBEBE', linestyle="-", linewidth=2) line3 = matplotlib.lines.Line2D( (0, 0), (1, 1), color='k', linestyle="-", linewidth=2) line4 = matplotlib.lines.Line2D( (0, 0), (1, 1), color='g', linestyle="-", linewidth=6, alpha=.3) if markTransit: lgd2 = plt.legend((line1, line2, line3, line4), ("day", "twilight", "night", "transit",), bbox_to_anchor=(0.88, 0.15), loc=2, borderaxespad=0., prop={'size': 12}, fancybox=True) else: lgd2 = plt.legend((line1, line2, line3), ("day", "twilight", "night",), bbox_to_anchor=(0.88, 0.13), loc=2, borderaxespad=0., prop={'size': 12}, fancybox=True) lgd2.get_frame().set_alpha(.5) targetco = r"Target coordinates: (%8.4f$^\circ$, %8.4f$^\circ$)" % \ (allData[n]["Star ra"], allData[n]["Star dec"]) obsco = "Obs coord.: (%8.4f$^\circ$, %8.4f$^\circ$, %4d m)" % \ (allData[n]["Obs coord"][0], allData[n] ["Obs coord"][1], allData[n]["Obs coord"][2]) plt.text(0.01, 0.97, targetco, transform=fig.transFigure, ha='left', va='center', fontsize=10) plt.text(0.01, 0.95, obsco, transform=fig.transFigure, ha='left', va='center', fontsize=10) if (print2file == True): outfile = "transVis-" + \ str(allData[n]["Planet name"]).replace(" ", "") + ".png" plt.savefig(outfile, format="png", dpi=300) elif isinstance(print2file, six.string_types): plt.savefig(print2file, format="png", dpi=300) else: plt.show()
def estimateSNR(x, y, xlen, deg=1, controlPlot=False, xlenMode="dataPoints"): """ Estimate Signal to Noise Ratio (SNR) in a data set. This function provides a simple algorithm to estimate the SNR in a data set. The algorithm subdivides the data set into sections with a length determined by the `xlen` parameter. Each individual subsection is fitted using a polynomial of degree `deg`. The SNR is then computed by assuming that the resulting chi square value is properly distributed according to the chi-square distribution. Parameters ---------- x : array The abscissa values. y : array The ordinate values. xlen : int or float Length of the data subsection considered in the computation of the SNR. Whether `xlen` refers to the number of data points or a fixed subsection of the abscissa is determined by the `xlenMode` flag. deg : int, optional Degree of the polynomial used to fit the subsection of the data set. The default is one. controlPlot : boolean, optional If True, a control plot will be shown to verify the validity of the estimate. xlenMode : string, {"dataPoints", "excerpt", "all"}, optional Determines whether `xlen` refers to data points or a fixed length scale on the abscissa. If 'all' is specified, all available data will be used in the estimation. Returns ------- SNR : dictionary Contains the SNR estimate for the individual subsections (key 'snrs') and the final SNR estimate (mean of all 'sub-SNRs', key 'SNR-Estimate'). """ # Stores the SNRs computed from the individual sections. snrs = [] i = 0 lastdof = None if controlPlot: try: import matplotlib.pylab as plt except ImportError: raise(PE.PyARequiredImport("Cannot import matplotlib.pylab", \ where="estimateSNR", \ solution=["Install matplotlib.", "Change `controlPlot` to False."])) while True: if xlenMode == "dataPoints": # xlen is given in units of data points indi = range(i*xlen, (i+1)*xlen) i += 1 if i > len(x)/xlen: break elif xlenMode == "excerpt": # xlen is given in units of the abscissa values indi = np.where(np.logical_and(x >= i*float(xlen), x < (i+1)*float(xlen)))[0] i += 1 if i*float(xlen) > max(x): break elif xlenMode == "all": if i > 1: break indi = range(len(x)) i += 1 else: raise(PE.PyAValError("Unknown xlenMode (" + str(xlenMode) + ").", \ solution = "Use either 'dataPoints' or 'excerpt'.")) # Check whether indi is long enough if len(indi) <= (deg + 1): # Skip this subsection continue # Fit polynomial, calculate model and residuals poly = np.polyfit(x[indi], y[indi], deg) model = np.polyval(poly, x[indi]) residuals = y[indi] - model # The number of "degrees of freedom) dof = len(indi) - (deg + 1) # Calculate reduced chi square ... stdEstimate = np.sqrt((residuals**2).sum() / dof) # A brute-force way to compute the expectation of sqrt(1/(X(d)/d)), # where X(d) is chi-square distributed with d degrees of freedom. if dof != lastdof: ccSample = np.random.chisquare(dof, 1000) corrFac = np.mean(np.sqrt(1.0/(ccSample / dof))) lastdof = dof stdEstimate *= corrFac # ... and SNR snrs.append(np.mean(model) / stdEstimate) if controlPlot: if i == 1: # This is the first call ax1 = plt.subplot(3,1,1) plt.title("Data (blue) and model (red)") ax2 = plt.subplot(3,1,2, sharex=ax1) plt.title("Residuals") ax3 = plt.subplot(3,1,3, sharex=ax1) plt.title("SNR") # Create plot plt.subplot(3,1,1) plt.plot(x[indi], y[indi], 'b.') plt.plot(x[indi], model, 'r-') plt.subplot(3,1,2, sharex=ax1) plt.plot(x[indi], residuals, 'g.') plt.subplot(3,1,3, sharex=ax1) plt.plot(np.mean(x[indi]), snrs[-1], 'bp') if controlPlot: plt.show() return {"SNR-Estimate":np.mean(snrs), "snrs":snrs}
# Check Python version _majV, _minV = sys.version_info[0:2] if (_minV < 7) and (_majV == 2): PE.warn( "funcFit needs 2.7.x or greater. See documentation (Prerequisites) for explanation." ) ic = ImportCheck([ "numpy", "scipy", "pymc", "matplotlib", "matplotlib.pylab", "pyfits", "emcee", "progressbar" ]) # Get out if numpy not present if not ic.check["numpy"]: raise(PE.PyARequiredImport("Numpy cannot be imported.", solution="Install numpy (see http://numpy.scipy.org/, you probably should also install SciPy).", \ addInfo="The numpy package provides array support for Python and is indispensable in many scientific applications.")) # Check whether fitting modules can be imported _scoImport = ic.check["scipy"] _pymcImport = ic.check["pymc"] _mplImport = ic.check["matplotlib"] from PyAstronomy.funcFit.utils import * from .modelRebin import turnIntoRebin, _ModelRebinDocu from .onedfit import * from .gauss1d import * from .gauss2d import * from .respModel import * from .params import * from .sinexp1d import * from .polyModel import *