Exemplo n.º 1
0
def interpForVelocity(kuruczAtms, pradks, TEff, LogG, Metal, VTurb):
    # This functions finds the set of 'bracketing models for mass, and interpolates
    # between them.
    # Note that we only have ATLAS models for VTurb = 2.0...
    loVIdx, hiVIdx = u.bracket(VTurb, sorted(kuruczAtms.keys()))
    loV = sorted(kuruczAtms.keys())[loVIdx]
    hiV = sorted(kuruczAtms.keys())[hiVIdx]

    if VTurb == loV or loV == hiV:
        # if False: print('\tMatched low VTurb {0:+4.2f} :'.format(loV))
        return interpForMetal(kuruczAtms[loV], pradks[loV], Metal, TEff, LogG)
    elif VTurb == hiV:
        # if False: print('\tMatched high VTurb {0:+4.2f} :'.format(hiV))
        return interpForMetal(kuruczAtms[hiV], pradks[hiV], Metal, TEff, LogG)
    else:
        # if False: print('\tLow VTurb {0:+4.2f} :'.format(loV))
        loVModel, lopradk = interpForMetal(kuruczAtms[loV], pradks[loV], Metal,
                                           TEff, LogG)
        # if False: print('\tHigh VTurb {0:+4.2f} :'.format(hiV))
        hiVModel, hipradk = interpForMetal(kuruczAtms[hiV], pradks[hiV], Metal,
                                           TEff, LogG)
        linInterpFactor = (VTurb - loV) / (hiV - loV)
        return ((1 - linInterpFactor) * loVModel +
                linInterpFactor * hiVModel), ((1 - linInterpFactor) * lopradk +
                                              linInterpFactor * hipradk)
Exemplo n.º 2
0
def interpForTEff(kuruczAtms, pradks, TEff, LogG):
    # Perform a linear interpolation between the closest two TEff models
    TeffLogGChoices = kuruczAtms.keys()
    TeffChoices = sorted(
        set([
            tLogGTuple[0] for tLogGTuple in TeffLogGChoices
            if tLogGTuple[1] == LogG
        ]))
    loTeffIdx, hiTeffIdx = u.bracket(TEff, TeffChoices)
    loTeff = TeffChoices[loTeffIdx]
    hiTeff = TeffChoices[hiTeffIdx]

    if TEff == loTeff or hiTeff == loTeff:
        # # if False: print('\t\t\tMatched low TEff. {0:5.0f} :'.format(loTeff))
        theModel = np.array(kuruczAtms[(loTeff, LogG)]), pradks[(loTeff, LogG)]
    elif TEff == hiTeff:
        # # if False: print('\t\t\tMatched high TEff. {0:5.0f} :'.format(hiTeff))
        theModel = np.array(kuruczAtms[(hiTeff, LogG)]), pradks[(hiTeff, LogG)]
    else:
        # # if False: print('\t\t\tLow TEff {0:5.0f} :'.format(loTeff))
        loTEffModel = np.array(kuruczAtms[(loTeff, LogG)])
        lopradk = pradks[(loTeff, LogG)]
        # # if False: print('\t\t\tHigh TEff {0:5.0f} :'.format(hiTeff))
        hiTEffModel = np.array(kuruczAtms[(hiTeff, LogG)])
        hipradk = pradks[(hiTeff, LogG)]
        linInterpFactor = (TEff - loTeff) / (hiTeff - loTeff)
        theModel = (
            (1 - linInterpFactor) * loTEffModel + hiTEffModel * linInterpFactor
        ), (1 - linInterpFactor) * lopradk + hipradk * linInterpFactor

    return theModel
Exemplo n.º 3
0
def interpForLogG(kuruczAtms, pradks, TEff, LogG):
    # Perform a log interpolation between the closest two LogG models
    TeffLogGChoices = kuruczAtms.keys()
    logGChoices = sorted(set([tLogGTuple[1]
                              for tLogGTuple in TeffLogGChoices]))
    loLogGIdx, hiLogGIdx = u.bracket(LogG, logGChoices)
    loLogG = logGChoices[loLogGIdx]
    hiLogG = logGChoices[hiLogGIdx]

    if len(logGChoices) == 1 or LogG == loLogG or hiLogGIdx == 0:
        # This will also handle calls where LogG is below the range
        # # if False: print('\t\tMatched low logG. {0:+4.2f} :'.format(loLogG))
        return interpForTEff(kuruczAtms, pradks, TEff, loLogG)
    elif LogG == hiLogG:
        # # if False: print('\t\tMatched high logG. {0:+4.2f} :'.format(hiLogG))
        return interpForTEff(kuruczAtms, pradks, TEff, hiLogG)
    elif LogG > hiLogG:
        # In the case of LogGs above the available range, we will extrapolate
        # using the highest two available tables. This should be the only case
        # where the linear interpolation factor is greater than 1.
        # Note that we assume that the limit of how high we will interpolate
        # is set and enforced elsewhere. It's probably not good to
        # interpolate more than 0.5-1.0 above the actual grids' range.

        # Included, but not required
        # hiLogGIdx = -1; loLogGIdx = -2
        loLogG = logGChoices[-2]
        hiLogG = logGChoices[-1]
        loLogGModel, lopradk = interpForTEff(kuruczAtms, pradks, TEff, loLogG)
        hiLogGModel, hipradk = interpForTEff(kuruczAtms, pradks, TEff, hiLogG)
        # Note the interpolation is slightly different, as we are above the high value,
        # and we want to use the high value, as opposed to the "low", as our "base".
        linInterpFactor = ((10**LogG - 10**hiLogG) / (10**hiLogG - 10**loLogG))
        return ((1 + linInterpFactor) * hiLogGModel -
                loLogGModel * linInterpFactor), (
                    (1 + linInterpFactor) * hipradk -
                    lopradk * linInterpFactor)
    else:
        # # if False: print('\t\tLow logG {0:+4.2f} :'.format(loLogG))
        loLogGModel, lopradk = interpForTEff(kuruczAtms, pradks, TEff, loLogG)
        # # if False: print('\t\tHigh logG {0:+4.2f} :'.format(hiLogG))
        hiLogGModel, hipradk = interpForTEff(kuruczAtms, pradks, TEff, hiLogG)
        linInterpFactor = u.linlogfraction(loLogG, hiLogG, LogG)
        return ((1 - linInterpFactor) * loLogGModel +
                hiLogGModel * linInterpFactor), (
                    (1 - linInterpFactor) * lopradk +
                    hipradk * linInterpFactor)
Exemplo n.º 4
0
def interpForMass(kuruczAtms, pradks, TEff, LogG, Metal, VTurb, mass):
    # The atmospheric models we are passed are 5-D (MARCS Spherical), 4-D
    # (MARCS Plane Parallel), or 3-D (ATLAS) grids with Mass, (micro-) turbulent
    # velocity, metallicity, Teff, and LogG as axes. 4-D models omit mass, and 3-D
    # models also omit turbulent velocity.
    # This functions finds the set of 'bracketing models for mass, and interpolates
    # between them.
    # Note: 4-D MARCS, and 3-D ATLAS models assume mass=1.0, but index it as 0.0
    loMassIdx, hiMassIdx = u.bracket(mass, sorted(kuruczAtms.keys()))
    loMass = sorted(kuruczAtms.keys())[loMassIdx]
    hiMass = sorted(kuruczAtms.keys())[hiMassIdx]

    if mass == loMass or loMass == hiMass:
        # if False: print('\tMatched low mass {0:+4.2f} :'.format(loMass))
        return interpForVelocity(kuruczAtms[loMass], pradks[loMass], TEff,
                                 LogG, Metal, VTurb)
    elif mass == hiMass:
        # if False: print('\tMatched high mass {0:+4.2f} :'.format(hiMass))
        return interpForVelocity(kuruczAtms[hiMass], pradks[hiMass], TEff,
                                 LogG, Metal, VTurb)
    else:
        # if False: print('\tLow mass {0:+4.2f} :'.format(loMass))
        loMassModel, lopradk = interpForVelocity(kuruczAtms[loMass],
                                                 pradks[loMass], TEff, LogG,
                                                 Metal, VTurb)
        # if False: print('\tHigh mass {0:+4.2f} :'.format(hiMass))
        hiMassModel, hipradk = interpForVelocity(kuruczAtms[hiMass],
                                                 pradks[hiMass], TEff, LogG,
                                                 Metal, VTurb)
        linInterpFactor = (mass - loMass) / (hiMass - loMass)
        return ((1 - linInterpFactor) * loMassModel +
                linInterpFactor * hiMassModel), (
                    (1 - linInterpFactor) * lopradk +
                    linInterpFactor * hipradk)

    return interpForVelocity(kuruczAtms[mass], pradks[mass], TEff, LogG, Metal,
                             VTurb)
Exemplo n.º 5
0
def interpForMetal(kuruczAtms, pradks, Metal, TEff, LogG):
    # Perform a log interpolation between the closest two metallicity models
    loMetIdx, hiMetIdx = u.bracket(Metal, sorted(kuruczAtms.keys()))
    loMetal = sorted(kuruczAtms.keys())[loMetIdx]
    hiMetal = sorted(kuruczAtms.keys())[hiMetIdx]

    if Metal == loMetal or loMetal == hiMetal:
        # if False: print('\tMatched low Met. {0:+4.2f} :'.format(loMetal))
        return interpForLogG(kuruczAtms[loMetal], pradks[loMetal], TEff, LogG)
    elif Metal == hiMetal:
        # if False: print('\tMatched high Met. {0:+4.2f} :'.format(hiMetal))
        return interpForLogG(kuruczAtms[hiMetal], pradks[hiMetal], TEff, LogG)
    else:
        # if False: print('\tLow Met. {0:+4.2f} :'.format(loMetal))
        loMetModel, lopradk = interpForLogG(kuruczAtms[loMetal],
                                            pradks[loMetal], TEff, LogG)
        # if False: print('\tHigh Met. {0:+4.2f} :'.format(hiMetal))
        hiMetModel, hipradk = interpForLogG(kuruczAtms[hiMetal],
                                            pradks[hiMetal], TEff, LogG)
        linInterpFactor = u.linlogfraction(loMetal, hiMetal, Metal)
        return ((1 - linInterpFactor) * loMetModel +
                linInterpFactor * hiMetModel), (
                    (1 - linInterpFactor) * lopradk +
                    linInterpFactor * hipradk)
Exemplo n.º 6
0
def EvenSpacedIso(IsoPoints, numPoints=200):
    # The PARSEC isochrones have points placed along the isochrone in such a way as
    # to produce an exact curve as possible to the model. This means that there are
    # a higher concentration of points along areas of the curve that are changing
    # rapidly. For probability calculations, we need points that are equidistantly
    # spaced along the curve (in terms of arc length). This function takes a passed
    # set of isochrone data and returns data set with the passed number of points
    # spaced along the isochrone. Note: for future reference, it would probably be
    # more physically appropriate to concentrate more points along the curve, based
    # on the (IMF) stellar population.
    #
    x = IsoPoints[:, 0]
    xmin = min(x)
    x = x - xmin
    xnorm = max(x)
    x = [i / xnorm for i in x]

    y = IsoPoints[:, 1]
    ymin = min(y)
    y = y - ymin
    ynorm = max(y)
    y = [i / ynorm for i in y]

    Dx = np.diff(x)
    Dy = np.diff(y)
    Dl = np.sqrt(Dx**2 + Dy**2)

    # L contains the arclength coordinate for our given X-Y system
    L = np.cumsum(Dl)
    totalArclen = L[-1]  # also = np.sum(Dl)

    # S contains the interpolated, evenly spaced arclength coordinates
    S = np.linspace(0., totalArclen, numPoints)

    xS = [x[0]]
    yS = [y[0]]

    # Remember that x and y are not monotonic, so the usual interpolation
    # won't work...
    for sLen in S[1:-1]:
        loCoord, hiCoord = u.bracket(sLen, L.tolist())
        if (L[hiCoord] - L[loCoord]) != 0.:
            delta = (sLen - L[loCoord]) / (L[hiCoord] - L[loCoord])
        elif loCoord == 0 and sLen < L[0]:
            # Want a point before we started measuring
            hiCoord = 1
            delta = (sLen - L[0]) / (L[1] - L[0])  # delta will be negative
        else:
            delta = 0.
        loCoord = loCoord + 1
        hiCoord = hiCoord + 1
        xS.append(x[loCoord] + delta * (x[hiCoord] - x[loCoord]))
        yS.append(y[loCoord] + delta * (y[hiCoord] - y[loCoord]))
    xS.append(x[-1])
    yS.append(y[-1])

    xN = [i * xnorm + xmin for i in xS]
    yN = [i * ynorm + ymin for i in yS]

    evenIso = np.array(zip(xN, yN))
    return evenIso
Exemplo n.º 7
0
def measureVoigtEQWs(specFilename, lineData=None):
    global gBaselineSpline

    objectName, numApertures, apertureSize, apBoundaries = SD.ReadSpectraInfo(
        specFilename)
    specList = SD.LoadAndSmoothSpectra(specFilename)
    measuredLines = []
    badLines = []
    dopplerCheck = 0.
    dopplerCount = 0.

    for smoothSpec in specList:
        wls = smoothSpec[0]
        fluxes = smoothSpec[1]
        if not SD.IsGoodAperture(smoothSpec):
            continue
        if lineData == None:
            lineData, unusedRefs = LL.getLines(wavelengthRange=[(wls[0],
                                                                 wls[-1])],
                                               dataFormat=None)

        for line in lineData:
            # lineData entries are variable length, depending on how many
            # optional parameters are included. So, we have to manually assign
            # the ones we need.
            wl = line[0]
            ion = line[1]
            ep = line[2]
            gf = line[3]
            if len(line) > 4:
                # This is the only optional parameter we might need.
                c6 = line[4]
            else:
                c6 = ''

            wlUpper = wl + k.HalfWindowWidth
            wlLower = wl - k.HalfWindowWidth

            loApPix = u.closestIndex(wls, wlLower)
            centerPix = u.closestIndex(wls, wl)
            hiApPix = u.closestIndex(wls, wlUpper)

            # The algorithm for curve_fit needs values "close" to one. So,
            # we normalize our wavelength-calibrated spectrum window to be
            # centered at "0", and range from -2 to +2. We also assume that
            # our continuum normalized spectrum has amplitudes near 1.
            x = wls[loApPix:hiApPix + 1] - wls[centerPix]
            y = fluxes[loApPix:hiApPix + 1]
            # At some point, I suppose we should use the actual S/N of the spectra
            # for the error array...
            e = 0.01 * np.ones(len(x))

            if len(x) < 10:
                # Line is too close to the order boundary, so skip it
                badLines.append([
                    wl, ion, k.badSpectralRegion, 'Too close to order boundary'
                ])
                continue

            try:
                # Because of the normalization needed for curve fitting, we need to perform
                # some shenanigans on the baseline spline
                rawSpline = BL.FitSplineBaseline(loApPix, hiApPix, smoothSpec)
                gBaselineSpline = interp.UnivariateSpline(
                    x, rawSpline(x + wls[centerPix]))
            except:
                # We've had errors when trying to fit bad regions - just skip to the next one
                print('Fitting error for {0:4.3f}, ({1:2.1f}).'.format(
                    wl, ion))
                continue

            maxIdxs = [
                mx for mx in sig.argrelmax(y)[0].tolist() if y[mx] < 2.5
            ]
            maxIdxs.append(0)
            maxIdxs.sort()
            maxIdxs.append(-1)

            minIdxs = [
                mn for mn in sig.argrelmin(y)[0].tolist() if y[mn] > 0.05
            ]
            minIdxs.append(0)
            minIdxs.sort()
            minIdxs.append(-1)
            mins = [x[idx] for idx in minIdxs]

            closestMin = mins.index(u.closest(0, mins))

            # Find the two local maxes which bracket the central wavelength (offset=0.)
            maxRange = u.bracket(0., [x[idx] for idx in maxIdxs])
            fitRange = [maxIdxs[maxRange[0]], maxIdxs[maxRange[1]] + 1]

            # guess that the scale is the difference between the local max and mins,
            # although we could opt to use the difference between the continuum and the min...
            scaleGuess = (y[maxIdxs[maxRange[0]]] + y[maxIdxs[maxRange[1]]]
                          ) / 2. - y[minIdxs[closestMin]]
            # The line center should occur at, or really close to the offset
            offsetGuess = 0.0
            # Gamma and sigma are Lorenzian and Gaussian parms...need a better idea for guesses
            gammaGuess = 0.10
            sigmaGuess = 0.10
            # The core fraction is basically how much of the line is "pure" Gaussian
            coreFractionGuess = 0.65
            guessParms = [
                scaleGuess, offsetGuess, gammaGuess, sigmaGuess,
                coreFractionGuess
            ]
            curveFitted = False

            if len(x[fitRange[0]:fitRange[1]]) < 5:
                # Way too few points to even do an interpolation. Try extending to the
                # two "maxes" on either side of the current
                loRange = 0 if maxRange[0] < 2 else maxRange[0] - 1
                hiRange = -1 if maxRange[1] > len(
                    maxIdxs) - 2 or maxRange[1] == -1 else maxRange[1] + 1

                fitRange = [
                    maxIdxs[loRange],
                    maxIdxs[hiRange] + 1 if hiRange > 0 else -1
                ]
                xToFit = x[fitRange[0]:fitRange[1]]
                yToFit = y[fitRange[0]:fitRange[1]]
                eToFit = e[fitRange[0]:fitRange[1]]

            if len(x[fitRange[0]:fitRange[1]]) < 5:
                # STILL too small of a range - Bail, with a bad line.
                badLines.append(
                    [wl, ion, k.badSpectralRegion, 'Line region too small'])
                continue

            if len(x[fitRange[0]:fitRange[1]]) < 20:
                # Too few points?
                # Try adding more (interpolated) data points, along a spline
                if fitRange[1] == -1:
                    ySpline = interp.interp1d(x[fitRange[0]:],
                                              y[fitRange[0]:],
                                              kind='cubic')
                    eSpline = interp.interp1d(x[fitRange[0]:],
                                              e[fitRange[0]:],
                                              kind='cubic')

                    xToFit = np.linspace(x[fitRange[0]], x[-1], num=100)
                    yToFit = ySpline(xToFit)
                    eToFit = eSpline(xToFit)
                else:
                    ySpline = interp.interp1d(x[fitRange[0]:fitRange[1] + 1],
                                              y[fitRange[0]:fitRange[1] + 1],
                                              kind='cubic')
                    eSpline = interp.interp1d(x[fitRange[0]:fitRange[1] + 1],
                                              e[fitRange[0]:fitRange[1] + 1],
                                              kind='cubic')
                    xToFit = np.linspace(x[fitRange[0]],
                                         x[fitRange[1]],
                                         num=100)
                    yToFit = ySpline(xToFit)
                    eToFit = eSpline(xToFit)
            else:
                xToFit = x[fitRange[0]:fitRange[1]]
                yToFit = y[fitRange[0]:fitRange[1]]
                eToFit = e[fitRange[0]:fitRange[1]]

            try:
                popt, pcov = fitCurve(xToFit, yToFit, eToFit, guessParms)
                if popt[4] > 0. and popt[4] < 1.:
                    # Even though we have a fit, if it's "pure" Lorenzian or Gaussian
                    # pretend we can do better with some data manipulation
                    curveFitted = True
#                else:
#                    curveFitted = False
            except RuntimeError:
                # Occurs when curve_fit cannot converge (Line not fit)
                # We need to catch it in order to try our own data manipulation
                # for a good fit.
                #                print('Initial fit failure for line at: %.3f' % wl)
                pass

            if not curveFitted:
                # Try to refit, by addressing typical issue(s)
                # Assume not enough of the line is visible (no wings)
                # Add ~10 points along the continuum, in the "wings"
                loBound = fitRange[0] - 5
                if loBound < 0: loBound = 0

                if fitRange[1] == -1:
                    hiBound = -1
                    newY = np.concatenate(
                        (gBaselineSpline(x[loBound:fitRange[0]]),
                         y[fitRange[0]:hiBound]))
                else:
                    hiBound = fitRange[1] + 5
                    if hiBound > len(x) - 1: hiBound = -1
                    newY = np.concatenate(
                        (gBaselineSpline(x[loBound:fitRange[0]]),
                         y[fitRange[0]:fitRange[1] + 1],
                         gBaselineSpline(x[fitRange[1] + 1:hiBound])))

                newX = x[loBound:hiBound]
                newE = e[loBound:hiBound]

                if len(newX) != len(newY) or len(newX) != len(newE):
                    # Probably just have an indexing problem in the code immediately above,
                    # but just skip the line for now.
                    badLines.append([wl, ion, k.unknownReason, k.unknownStr])
                    continue

                try:
                    popt, pcov = fitCurve(newX, newY, newE, guessParms)
                    # As long as something fits, here - we'll take it.
                    curveFitted = True
                except RuntimeError:
                    # Well, and truly screwed?
                    #                pyplot.errorbar(x+smoothSpec.xarr[centerPix], y, yerr=e, linewidth=1, color='black', fmt='none')
                    #                pyplot.show()
                    badLines.append([wl, ion, k.unknownReason, k.unknownStr])
                    continue

            eqw, eqwError = calcEQW(popt, pcov, x)

            #            showLineFit(popt, pcov, x, y, e, smoothSpec.xarr[centerPix].value)
            # FWHM of the Gaussian core is sqrt(8*ln(2))*sigma = 2.355*sigma
            # The factor of 1000 is to convert to miliangstroms.
            measuredLines.append(
                [wl, ion, ep, gf, c6, eqw, wl + popt[1], 2355. * popt[3]])
            thisDoppler = ((popt[1]) / wl) * k.SpeedofLight
            dopplerCheck += thisDoppler
            dopplerCount += 1

    #        if popt[4] <= 0.:
    #            print('Best fit is a pure Lorenzian')
    #        elif popt[4] >= 1.:
    #            print('Best fit is a pure Gaussian')

#    nans = len([x for x in measuredLines if np.isnan(x[5])])
    badLines.extend([[x[0], x[1], k.badEQWMValue, k.nanStr]
                     for x in measuredLines if np.isnan(x[5])])
    badLines.extend(
        [[x[0], x[1], k.emissionLine,
          "Emission strength: %.3f" % (abs(x[5]))] for x in measuredLines
         if x[5] < 0.])
    badLines.extend([[
        x[0], x[1], k.centerWLOff,
        "Line center delta: %.3f" % (x[0] - x[6])
    ] for x in measuredLines if abs(x[0] - x[6]) > 0.25])
    #    print("nan eqw: %d  -  emission: %d  - bad offset: %d" % (nans, emiss, bads))
    #
    #    for line in sorted(measuredLines, key=lambda x: x[0]):
    #        if not np.isnan(line[5]) and line[5]>0. and abs(line[0]-line[6]) < 0.25:
    #            print("%2.1f :%4.3f(%4.3f)  =  %.1f (%.1f)" % (line[1], line[0], line[6], line[5], line[7]))
    # Measured WL within 250mA of expected, non-emission lines, only
    filteredLines = [
        l for l in measuredLines
        if not np.isnan(l[5]) and l[5] > 0. and abs(l[0] - l[6]) < 0.25
    ]
    # Want ascending wls within ascending ions
    filteredLines.sort(key=lambda x: x[0])
    filteredLines.sort(key=lambda x: x[1])
    #    print "*****************"
    #    print "*******{0} bad *********".format(len(badLines))
    #    print "*******{0} measured *********".format(len(measuredLines))
    #    print "*******{0} filtered **********".format(len(filteredLines))

    return filteredLines, badLines, dopplerCount, dopplerCheck