def evaluate(self, co): """ Evaluates the model for current parameter values. Parameters ---------- co : array Specifies the points at which to evaluate the model. """ if (self["sig_core"] <= 0.0) or (self["sig_wing"] <= 0.0): raise(PE.PyAValError("Width(s) of Gaussian must be larger than zero.", \ solution="Change width ('sig_core/y').")) if self["f_core"] > 1.0: raise(PE.PyAValError("The relative normalization of the core and wing components shuold be between 0 and 1.", \ solution="Set limits of f_core.")) f_wing = 1 - self["f_core"] result = self["A"] * ( \ self["f_core"] / \ ((1 + ((co[::,::,0] - self["x0"])**2 + (co[::,::,1] - self["y0"])**2)/(self["sig_core"]**2)\ ) ** 2) + \ f_wing / \ ((1 + ((co[::,::,0] - self["x0"])**2 + (co[::,::,1] - self["y0"])**2)/(self["sig_wing"]**2)\ ) ** 2) ) return result
def getKuruczModel(teff, logg, met, nameadd=""): """ Obtain a Kurucz model Parameters ---------- teff : float Effective temperature [K] logg : float Logarithmic surface gravity [cgs] met : float Logarithmic metallicity, e.g., +0.1. nameadd : string, optional Name extension of the model grid; for instance, "NOVER". Returns ------- Model : list of strings The requested model. """ km = KuruczModels() if not km.gridAvailable(met, nameadd): raise(PE.PyAValError("No model grid with logarithmic metallicity of " + str(met) + \ " and name addition " + str(nameadd) + " available.")) mt = km.requestModelGrid(met, nameadd) if not mt.modelAvailable(teff, logg, met): raise(PE.PyAValError("No model available in grid for parameters: teff = %6.3e, logg = %6.3e, met = %6.3e " \ % (teff, logg, met), \ solution=["Available Teffs: " + str(list(mt.availableTeffs())), \ "Available Loggs: " + str(list(mt.availableLoggs()))])) return mt.getModel(teff, logg, met)
def __init__(self, los='-z'): if isinstance(los, six.string_types): # The input is a string. Check the content. r = re.match("^([-+])([xyz])$", los) if r is None: raise (PE.PyAValError("A line of sight encoded by " + los + " cannot be constructed.")) if r.group(2) == "x": self.los = np.array([1.0, 0.0, 0.0]) elif r.group(2) == "y": self.los = np.array([0.0, 1.0, 0.0]) elif r.group(2) == "z": self.los = np.array([0.0, 0.0, 1.0]) if r.group(1) == "-": self.los *= -1.0 self.losName = los return # Try to convert input to numpy array convlos = np.array(los) # Check length if len(convlos) != 3: raise (PE.PyAValError( "A line of sight argument does not have the right length of 3." )) # Check that it is a unit vector if np.abs(np.sum(convlos**2) - 1.0) > 1e-8: raise (PE.PyAValError( "A line of sight argument is not a unit vector.")) self.los = convlos self.losName = str(convlos)
def hmsToDeg(h, m, s): """ Convert hour-minute-second specification into degrees. Parameters ---------- h : float Hours (0-24) m : float Minutes (time, 0-60) s : float Seconds (time, 0-60) Returns ------- Angle : float The corresponding angle in degrees. """ if (h < 0.0) or (h >= 24.0): raise(PE.PyAValError("Hour (" + str(h) + ") out of range (0 <= h < 24).", \ solution="Specify a value between 0 and 24.")) if (m < 0.0) or (m >= 60.0): raise(PE.PyAValError("Minute (" + str(m) + ") out of range (0 <= m < 60)", \ solution="Specify a value between 0 and 60.")) if (s < 0.0) or (s >= 60.0): raise(PE.PyAValError("Second (" + str(s) + ") out of range (0 <= m < 60)", \ solution="Specify a value between 0 and 60.")) result = h * 15.0 + m * 0.25 + s * (0.25 / 60.0) return result
def isInTransit(time, T0, period, halfDuration, boolOutput=False, secin=False): """ Check whether time is inclosed by transit interval. This function uses the given ephemerides (T0, period, and halfDuration) to check whether the time point(s) specified by `time` are within a transit window or not. The edges of the window are counted as transit times. Parameters ---------- time : float or array like The time(s) which are to be checked. T0 : float The time reference point (center of transit). period : float The orbital period. halfDuration : float The half-duration of the event. Must have same units as `time`. boolOutput : boolean, optional If set True and `time` is an array, the function will return a bool array holding True for time points in- and False for time points out-of-transit. secin : boolean, optional If True, also points associated with the secondary transit (around phase 0.5) will be counted as falling into the transit window. Default is False. Returns ------- inTransit : boolean or array of int If `time` was a float, the return value is a boolean, which is True if the give time falls into a transit interval and False otherwise. If `time` was given as an array, the return value is an array holding the indices of those time points, which fall into a transit window. The `boolOutput` option may be used to obtain a boolean array holding True for in-transit points. """ if halfDuration > period / 2.: raise(PE.PyAValError("The half-duration is longer than half the period. This cannot be true.", where="isInTransit")) if (period <= 0.0) or (halfDuration <= 0.0): raise(PE.PyAValError("Both period and half-duration must be larger 0.", where="isInTransit")) absPhase = np.array(np.abs((np.array(time) - T0) / period), ndmin=1) absPhase -= np.floor(absPhase) dPhase = halfDuration / period isIn = np.logical_or(absPhase <= dPhase, absPhase >= (1. - dPhase)) if secin: isIn = isIn | (np.abs(absPhase - 0.5) < dPhase) indi = np.where(isIn)[0] if isinstance(time, float): return (len(indi) == 1) if boolOutput: return isIn return indi
def getElSymbol(self, atn): """ Convert atomic number into elemental symbol. Parameters ---------- atn : int Atomic number Returns ------- Symbol : string Elemental symbol """ try: atn = int(atn) except ValueError: raise (PE.PyAValError("`atn` needs to be an integer. You gave: " + str(atn))) if not atn in self._data: raise (PE.PyAValError("No such atomic number in the data base: " + str(atn))) return self._data[atn]
def dmsToDeg(d, m, s): """ Convert degree-arcminute-arcsecond specification into degrees. Parameters ---------- d : float Degrees. m : float Arcminutes (0-60) s : float Arcseconds (0-60) Returns ------- Angle : float The corresponding angle in degrees. """ if (m < 0.0) or (m >= 60.0): raise(PE.PyAValError("Minute (" + str(m) + ") out of range (0 <= m < 60)", \ solution="Specify a value between 0 and 60.")) if (s < 0.0) or (s >= 60.0): raise(PE.PyAValError("Second (" + str(s) + ") out of range (0 <= m < 60)", \ solution="Specify a value between 0 and 60.")) sign = 1.0 if d < 0.0: sign = -1.0 result = d + sign * (m / 60.0) + sign * (s / 3600.0) return result
def logRphotNoyes(self, bv, lc="ms"): """ Photospheric contribution to surface flux in the H and K pass-bands. Relation given by Noyes et al. 1984. Parameters ---------- bv : float B-V color [mag] lc : string, {ms, g}, optional Luminosity class. Returns ------- log10(Rphot) : float Logarithm of the photospheric contribution. """ if (bv < 0.44) or (bv > 0.82): PE.warn(PE.PyAValError("Noyes et al. 1984 give a validity range of 0.44 < B-V < 0.82 for the " + \ "photospheric correction. However, the authors use it for B-V > 0.82, " + \ "where it quickly decreases.")) if lc != "ms": PE.warn( PE.PyAValError( "Noyes et al. 1984 specify the photospheric correction only for main-sequence stars." )) rp = -4.898 + 1.918 * bv**2 - 2.893 * bv**3 return rp
def getElementName(self, atn): """ Convert atomic number into elemental name. Parameters ---------- atn : int Atomic number Returns ------- Name : string Name of element """ try: atn = int(atn) except ValueError: raise (PE.PyAValError("`atn` needs to be an integer. You gave: " + str(atn))) if not atn in self._data: raise (PE.PyAValError("No such atomic number in the data base: " + str(atn))) return self._elNames[atn]
def evaluate(self, co): """ Evaluates the model for current parameter values. Parameters ---------- co : array Specifies the points at which to evaluate the model. """ if (self["sigx"] <= 0.0) or (self["sigy"] <= 0.0): raise(PE.PyAValError("Width(s) of Gaussian must be larger than zero.", \ solution="Set limits.")) if self["theta"] > 2. * np.pi: raise(PE.PyAValError("The angle parameter must be 0 <= theta <= 2pi.", \ solution="Set limits.")) a = np.cos(self['theta'])**2 / (2 * self['sigx']**2) + \ np.sin(self['theta'])**2 / (2*self['sigy']**2) b = np.cos(2.*self['theta']) / (4 * self['sigx']**2) + \ np.sin(2.*self['theta']) / (4 * self['sigy']**2) c = np.sin(self['theta'])**2 / (2 * self['sigx']**2) + \ np.cos(self['theta'])**2 / (2 * self['sigy']**2) result = self["A"] * np.exp(\ -1. * (a*(co[::,::,0]-self['x0'])**2 - 2*b*(co[::,::,0]-self['x0'])*(co[::,::,1]-self['y0']) + c*(co[::,::,1]-self['y0'])**2) \ ) + self['const'] return result
def evaluate(self, co): """ Evaluates the model for current parameter values. Parameters ---------- co : array Specifies the points at which to evaluate the model. """ result = ones((shape(co)[0], shape(co)[1])) * self["off"] for i in range(self.n): p = str(i + 1) if (self["sigx" + p] <= 0.0) or (self["sigy" + p] <= 0.0): raise(PE.PyAValError("Width(s) of Gaussian must be larger than zero.", \ solution="Change width ('sigx/y').")) if self["rho" + p] > 1.0: raise(PE.PyAValError("The correlation coefficient must be 0 <= rho <= 1.", \ solution="Change width ('sigx/y').")) result += self["A"+p]/(2.*pi*self["sigx"+p]*self["sigy"+p]*sqrt(1.-self["rho"+p]**2)) * \ exp( ((co[::,::,0]-self["mux"+p])**2/self["sigx"+p]**2 + (co[::,::,1]-self["muy"+p])**2/self["sigy"+p]**2 - \ 2.*self["rho"+p]*(co[::,::,0]-self["mux"+p])*(co[::,::,1]-self["muy"+p])/(self["sigx"+p]*self["sigy"+p])) / \ (-2.*(1.-self["rho"+p]**2)) ) return result
def magToFluxDensity_bessel98(band, mag, mode="nu"): """ Convert magnitude into flux density according to Bessel et al. 1998 The conversion implemented here is based on the data given in Table A2 of Bessel et al. 1998, A&A 333, 231-250, which gives "Effective wavelengths (for an A0 star), absolute fluxes (corresponding to zero magnitude) and zeropoint magnitudes for the UBVRI- JHKL Cousins-Glass-Johnson system". Note that zp(f_nu) and zp(f_lam) are exchanged in the original table. Parameters ---------- band : string Any of U, B, V, R, I, J, H, K, Kp, L, and L* mag : float, array The magnitude value to be converted mode : string, {nu, mod} Determines whether f_nu or f_lam will be calculated. Returns ------- f_nu/lam : float The corresponding flux density in units if erg/cm**2/s/Hz in the case of mode 'nu' and erg/cm**2/s/A in the case of 'lam'. lam_eff : float Effective filter wavelength in Angstrom """ if not mode in ["nu", "lam"]: raise(PE.PyAValError("Unknown mode", \ where="magToFluxDensity_bessel98", \ solution="Use 'nu' or 'lam'")) # Data from Bessel 1998 Table A2 d = """bands U B V R I J H K Kp L L* lam_eff 0.366 0.438 0.545 0.641 0.798 1.22 1.63 2.19 2.12 3.45 3.80 f_nu 1.790 4.063 3.636 3.064 2.416 1.589 1.021 0.640 0.676 0.285 0.238 f_lam 417.5 632 363.1 217.7 112.6 31.47 11.38 3.961 4.479 0.708 0.489 zp(f_nu) 0.770 -0.120 0.000 0.186 0.444 0.899 1.379 1.886 1.826 2.765 2.961 zp(f_lam) -0.152 -0.602 0.000 0.555 1.271 2.655 3.760 4.906 4.780 6.775 7.177""" # Digest table s = d.split() t = {} for i in range(6): t[s[i * 12]] = s[i * 12 + 1:(i + 1) * 12] if s[i * 12] != "bands": t[s[i * 12]] = np.array(t[s[i * 12]], dtype=np.float) if not band in t["bands"]: raise(PE.PyAValError("Unknown passband: " + str(band), \ where="magToFluxDensity_bessel98", \ solution="Use any of: " + ", ".join(t["bands"]))) # Band index bi = t["bands"].index(band) c = {"nu": 48.598, "lam": 21.1} f = 10.0**((mag + c[mode] + t["zp(f_" + mode + ")"][bi]) / -2.5) return f, t["lam_eff"][bi] * 1e4
def getBroadeningFunction(self, flux, wlimit=0.0, asarray=False): """ Calculate the broadening function. .. note:: The `decompose` method has to be called first. On this call, the template is specified. Parameters ---------- flux : array or matrix The observed function (flux). wlimit : float, optional A limit to be applied to the singular values. Values smaller than the specified limit will be discarded in calculating the broadening function. asarray : bool, optional If True, the broadening function will be returned as an array instead of a matrix. The default is False. Returns ------- Broadening function : matrix, array The broadening function. The return type can be set by the `asarray` flag. """ if len(flux) == (len(self.template) - self.bn + 1): validIndi = np.arange(len(self.template) - self.bn + 1) elif len(flux) == len(self.template): validIndi = np.arange(self.bn // 2, len(flux) - self.bn // 2) else: raise(PE.PyAValError("Inappropriate length of flux array.\n" + \ " Either len(template) or len(template)-bn+1 is accepted.", \ solution="Adjust the length of the input spectrum.")) if self.w is None: raise(PE.PyAValError("There is no vector of singular values yet.", \ solution="Call `decompose` first.")) w1 = 1.0 / self.w indi = np.where(self.w < wlimit)[0] w1[indi] = 0.0 b = np.dot( self.v, np.dot(np.diag(w1), np.dot(self.u.T, np.matrix(flux[validIndi]).T))) if not asarray: return b else: return b.getA()[::, 0]
def coordsSexaToDeg(c, fullOut=False): """ Convert sexagesimal coordinate string into degrees. Parameters ---------- c : string The coordinate string. Valid formats are, e.g., "00 05 08.83239 +67 50 24.0135" or "00:05:08.83239 -67:50:24.0135". Spaces or colons are allowed as separators for the individual components of the coordinates. fullOut : boolean, optional If True, two additional tuples holding the individual components of the right ascension and declination specification will be returned. The default is False. Returns ------- ra, dec : float Right ascension and declination in degrees. hms, dms : tuples of three floats If `fullOut` is True, two tuples of three numbers each will be returned, which hold the individual constituents making up the right ascension (hms) and declination (dms) specifiers in sexagesimal representation. """ r = re.match( "^\s*(\d+)([\s:])(\d+)([\s:])(\d+(\.\d*)?)\s+(([+-])(\d+))([\s:])(\d+)([\s:])(\d+(\.\d*)?)\s*$", c) if r is None: raise (PE.PyAValError( "Could not decompose coordinate string: \"" + str(c) + "\"", solution= "Use, e.g., 00 05 08.83239 +67 50 24.0135 or 00:05:08.83239 +67:50:24.0135" )) # Check separator consistency for n in [4, 10, 12]: if r.group(2) != r.group(n): raise (PE.PyAValError( "Separators (space and colon) mixed in coordinate definition.", solution="Use consistent separator.")) ra = (float(r.group(1)), float(r.group(3)), float(r.group(5))) esign = {'+': +1, '-': -1}[r.group(8)] de = (float(r.group(7)), float(r.group(11)), float(r.group(13)), esign) result = (hmsToDeg(*ra), dmsToDeg(*de)) if not fullOut: return result return result[0], result[1], ra, de
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 pattern(self, name, form='array', key="symbol"): """ Get the elemental abundance pattern Parameters ---------- name : string The abbreviation of the abundance pattern. form : string, {array, dict}, optional Return the abundances as a plain array or as a dictionary. The default is 'array'. key : string, {symbol, number}, optional If return type is a dictionary, this parameter determined whether the elemental symbol or the atomic number is used as the key. The default is "symbol". Returns ------- abundances : numpy array or dict (depending on ``form'') Number abundances (not mass) relative to H. """ names = self.availablePatterns() if not name in names: raise(PE.PyAValError("No such pattern: '" + str(name) + "'", \ solution="Choose existing pattern: " + ', '.join(names), \ where="pyasl.AbundancePatterns")) if not form in ["dict", "array"]: raise(PE.PyAValError("No such format: '" + str(form) + "'", \ solution="Choose either 'dict' or 'array'", \ where="pyasl.AbundancePatterns")) if not key in ["symbol", "number"]: raise(PE.PyAValError("No such key: '" + str(key) + "'", \ solution="Choose either 'symbol' or 'number'", \ where="pyasl.AbundancePatterns")) gi = np.where(names == name)[0] # Read abundances dd = np.genfromtxt(self._dataFN(), skip_header=1, usecols=gi+1) # Read element symbols el = np.genfromtxt(self._dataFN(), skip_header=1, usecols=0, dtype=str) if form == 'array': return dd if form == 'dict': if key == "symbol": r = {k: v for (k, v) in zip(el, dd)} elif key == "number": r = {self._an.getAtomicNo(k): v for (k, v) in zip(el, dd)} return r
def getFIP(self, atom): """ Get the first ionization energy. Parameters ---------- atom : string or integer Either a string specifying the elemental symbol (e.g., 'H') or an integer specifying the atomic number. Returns ------- FIP : float First ionization potential [eV]. FIP error : float Error of the FIP [eV]. May also be None if no error is available. """ if isinstance(atom, six.string_types): an = self._an.getAtomicNo(atom) elif isinstance(atom, int): an = atom else: raise(PE.PyAValError("'atom' must be an integer specifying the atomic number are a string holding the elemental symbol.", \ where="FirstIonizationPot::getFIP")) return self._fip[an][0], self._fip[an][1]
def _plotPointAs(self, state, point=None, lbString=None): """ Plot a point in active/inactive color Parameters ---------- state : string, {"active", "inactive"} Which color to use point : Point, optional The point information as an instance of the Point class. lbString : string The point identifier used in the listbox. """ if (point is None) == (lbString is None): raise (PE.PyAValError( "Either `point` or `lbString` have to be specified.")) if point is not None: pli, lli = self._searchPoint(point.lbIdent) else: pli, lli = self._searchPoint(lbString) # Remove 'old' point from plot self.a.lines.pop(lli) if state == "active": style = self.astyle elif state == "inactive": style = self.istyle # Add new point in specified color self.a.plot([self.pointList[pli].xdata], [self.pointList[pli].ydata], style) self.pointList[pli].mplLine = self.a.lines[-1]
def getModel(self, teff, logg, met=None): """ Get a model. Parameters ---------- teff : float Effective temperatures [K] logg : float Logg [cgs] met : float, optional Logarithmic metallicity. If not given, the metallicity of the model grid is used. Returns ------- Model : list of strings The model as found on the file. """ if not self.modelAvailable(teff, logg, met): raise (PE.PyAValError(( "No model for the combination: Teff = %6.3e, logg = %6.3e, met = " + str(met)) % (teff, logg))) if met is None: met = self.met return self.models[(teff, logg, met)][::]
def getCardinalPoint(azimuth): """ Get the cardinal point (North, East, South, or West) for a given azimuth. Here, the azimuth is measured from North to East. N = (315, 45] deg E = (45, 135] deg S = (135, 225] deg W = (225, 315] deg Parameters ---------- azimuth : float Azimuth of an object in deg (0-360). Returns ------- Cardinal point : string, {N,E,S,W} Returns the cardinal point (N, E, S, W) corresponding to the given azimuth of the object. """ if (azimuth < 0.0) or (azimuth > 360.): raise(PE.PyAValError("The azimuth needs to be a number in the range [0,360]. Given azimuth: " + str(azimuth), \ where="getCardinalPoint", \ solution="Specify a number between 0 and 360")) if np.logical_and(azimuth > 315., azimuth <= 360.): return "N" if np.logical_and(azimuth >= 0., azimuth <= 45.): return "N" if np.logical_and(azimuth > 45., azimuth <= 135.): return "E" if np.logical_and(azimuth > 135., azimuth <= 225.): return "S" if np.logical_and(azimuth > 225., azimuth <= 315.): return "W"
def _abundToStr(self, met): """ Convert metallicity into a grid-naming compatible string Parameters ---------- met : float Log10 of metallicity Returns ------- Naming string : string E.g., M01, P00 etc. """ if met < 0: result = "M" else: result = "P" if (met * 10) - int(abs(met) * 10) > 1e-14: raise (PE.PyAValError("Inappropriate met value: " + str(met))) met = int(abs(met) * 10) if met < 10: result += "0" result += str(met) return result
def evalComponent(self, x, p): """ Evaluate the model considering only a single component. Parameters ---------- x : array The abscissa. p : int Component number (starts with one). Returns ------- single component model : array The model considering a single component. """ if p > 0 and p <= self.n: p = str(p) y = self["off"] + self["lin"] * x y += self["A"+p] / numpy.sqrt(2.0*numpy.pi*self["sig"+p]**2) * \ numpy.exp(-(self["mu"+p] - x)**2/(2.0 * self["sig"+p]**2)) return y else: raise(PE.PyAValError("No such component (no. "+str(p)+")", where="MultiGauss1d::evalComponent", \ solution="Use value between 1 and "+str(self.n)))
def ffmodelExplorer(odf, plotter, version="list"): """ Instantiate the model explorer. Parameters ---------- odf : Instance of OneDFit The model to be adapted. plotter : Instance of FFModelPlotFit or custom Class instance managing the actual plotting. version : string, {list} The version of model explorer. Currently, only 'list' is supported. Returns ------- ffmod : Model explorer An instance of the model explorer. """ if version == "list": ffmod = FFModelExplorerList(odf, plotter) return ffmod elif version == "dropdown": PE.warn(PE.PyADeprecationError("Please note that the dropdown version is no longer supported. " + \ "The list version will be called instead.")) ffmod = FFModelExplorerList(odf, plotter) return ffmod else: raise(PE.PyAValError("Unknown version: '" + str(version) + "'", \ where="ffmodelExplorer", \ solution="Choose between 'list' and 'dropdown'."))
def xyzNodes_LOSZ(self, los="+z"): """ Calculate the nodes of the orbit for LOS in +/-z direction. The nodes of the orbit are the points at which the orbit cuts the plane of the sky. In this case, these are the points at which the z-coordinate vanishes, i.e., the x-y plane is regarded the plane of the sky. Parameters ---------- los : string, {+z,-z}, optional Line of sight points either in +z direction (observer at -z) or vice versa. Changing the direction interchanges the ascending and descending node. Returns ------- Nodes : Tuple of two coordinate arrays Returns the xyz coordinates of both nodes. The first is the ascending node and the second is the descending node. """ # f = -w (z-component vanishes there) E = arctan(tan(-self._w / 2.0) * sqrt( (1. - self.e) / (1. + self.e))) * 2. M = E - self.e * sin(E) t = M / self._n + self.tau node1 = self.xyzPos(t) # Velocity is used to distinguish nodes v1 = self.xyzVel(t) # f = -w + pi E = arctan( tan((-self._w + pi) / 2.0) * sqrt( (1. - self.e) / (1. + self.e))) * 2. M = E - self.e * sin(E) t = M / self._n + self.tau node2 = self.xyzPos(t) # Find the ascending and descending node from PyAstronomy.pyasl import LineOfSight l = LineOfSight(los).los if abs(l[2]) != 1.0: raise (PE.PyAValError("Invalid line of sight (LOS): " + str(los), where="xyzNodes_LOSZ", solution="Use '-z' or '+z'.")) if l[2] == 1.0: # Looking in +z direction if v1[2] > 0.0: # First node is ascending return (node1, node2) else: return (node2, node1) else: # Looking in -z direction if v1[2] < 0.0: # First node is ascending return (node1, node2) else: return (node2, node1)
def evalComponent(self, x, p): """ Evaluate the model considering only a single component. Parameters ---------- x : array The abscissa. p : int Component number (starts with one). Returns ------- Single component model : array The model considering a single component. Note that the linear continuum is included. """ if p > 0 and p <= self.n: p = str(p) y = self["off"] + self["lin"] * x self._v1d.assignValues({ "A": self["A" + p], "al": self["al" + p], "ad": self["ad" + p], "mu": self["mu" + p] }) y += self._v1d.evaluate(x) return y else: raise (PE.PyAValError("No such component (no. " + str(p) + ")", where="MultiVoigt1d::evalComponent", solution="Use value between 1 and " + str(self.n)))
def _findRow(self, tab, val, valname): """ Find corrected row in table. Parameters ---------- tab : array The data table, i.e., Table 3 or 4 from Pizzolato et al. 2003. val : float Value to be searched for. valname : string The name of the parameter. Needed in case of error. Returns ------- row : int The index of the correct row. """ row = np.where((val >= tab[::, 0]) & (val < tab[::, 1]))[0] if len(row) == 0: conc = np.concatenate((tab[::, 0], tab[::, 1])) raise (PE.PyAValError(valname + " value of " + str(val) + " is out of range. " + "Allowed range: " + str(np.min(conc)) + " - " + str(np.max(conc)))) return row[0]
def evaluate(self, t): if self["mstar"] == 0.0: raise(PE.PyAValError("Stellar mass has to be specified before evaluation.", \ where="KeplerRVModel")) # Take into account polynomial for i in range(self._deg + 1): p = "c" + str(i) self.poly[p] = self[p] rvnew = self.poly.evaluate(t) for m in range(1, self._mp + 1): # Name add nadd = str(m) if self["per" + nadd] == 0.0: continue for p in ["per", "e", "tau", "w"]: # Collect parameters from current model self.kems[m - 1][p] = self[p + nadd] rvmodel = self.kems[m - 1].evaluate(t) # Prevent dividing by zero imax = np.argmax(np.abs(rvmodel)) cee = np.cos(self.kems[m-1].ke.trueAnomaly(t[imax]) + self._dtr(self.kems[m-1]["w"])) \ + self["e"+nadd]*np.cos(self._dtr(self["w"+nadd])) rvnew += rvmodel / rvmodel[imax] * cee * self["K" + nadd] self["msini" + nadd] = self._getmsini(nadd) self["MA" + nadd] = self._MA(m) self["a" + nadd] = self._geta(nadd) return rvnew
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 evaluate(self, x): """ Calculate the model. Parameters ---------- x : array The abscissa values. Returns ------- model : array, The binned model. Notes ----- This function calculates the model at those time points specified by the `rebinTimes` property and saves the result in the class property `unbinnedModel`. Then it bins according to the definitions in `rebinIdent` and save the resulting model in the `binnedModel` property. """ if (self.rebinTimes is None) or (self.rebinIdent is None): raise(PE.PyAValError("Rebinning parameters (properties rebinTimes and/or rebinIdent) not appropriately defined.", solution=["Use setRebinArray_Ndt method.", "Define the properties by accessing them directly."])) # Calculate the model using current parameters at the time points # defined in the `rebinTimes` array self.unbinnedModel = CO.evaluate(self, self.rebinTimes) # Build up rebinned model self.binnedModel = numpy.zeros(x.size) for i in smo.range(x.size): self.binnedModel[i] = numpy.mean( self.unbinnedModel[self.rebinIdent[i]]) # Return the resulting model return self.binnedModel
def decompose(self, template, bn): """ Carry out the SVD of the "design matrix". This method creates the "design matrix" by applying a bin-wise shift to the template and uses numpy's `svd` algorithm to carry out the decomposition. The design matrix, `des` is written in the form: "`des` = u * w * transpose(v)". The matrices des, w, and u are stored in homonymous attributes. Parameters ---------- template : array or matrix The template with respect to which the broadening function shall be calculated. bn : int Width (number of elements) of the broadening function. Needs to be odd. """ if bn % 2 != 1: raise (PE.PyAValError("`bn` needs to be an odd number.")) self.bn = bn self.template = template.copy() self._createDesignMatrix(template, bn) # Get SVD deconvolution of the design matrix # Note that the `svd` method of numpy returns # v.H instead of v. self.u, self.w, v = np.linalg.svd(self.des, full_matrices=False) self.v = v.T