Esempio n. 1
0
def wavefunc(par, grad):
    '''
    Takes Telfitted template and uses an input wavelength solution to rebin it for direct comparison with Livingston.

    Inputs:
    par  : array of polynomial coefficients specifying wavelength solution
    grad : Always "None" (has to be this way for NLOpt)

    Outputs reduced chisq of model fit.
    '''

    global watm_Liv, satm_Liv, satmLivGen, x
    #Make the wavelength scale
    f = np.poly1d(par)
    w = f(x)

    if (w[-1] < w[0]) or (w[-1] > watm_Liv[-1] + 5):
        return 1e3

    satmTel2 = rebin_jv(w, satmLivGen, watm_Liv, False)
    return np.sum((satm_Liv - satmTel2)**2) / (len(satmTel2) - len(par))
def ini_MPinst(args, inparam, orders, order_use, trk, step2or3, i):

    # Main function for RV fitting that will be threaded over by multiprocessing

    nights = inparam.nights
    night = nights[i]  # current looped night

    order = order_use
    xbounds = inparam.xbounddict[order]
    print('Working on order {:02d}, night {:03d}/{:03d} ({}) PID:{}...'.format(
        int(order), i + 1, len(inparam.nights), night,
        mp.current_process().pid))

    #-------------------------------------------------------------------------------

    # Collect initial RV guesses
    if type(inparam.initguesses) == dict:
        initguesses = inparam.initguesses[night]
    elif type(inparam.initguesses) == float:
        initguesses = inparam.initguesses
    else:
        sys.exit(
            'ERROR! EXPECING SINGAL NUMBER OR FILE FOR INITGUESSES! QUITTING!')

    # Collect relevant beam and filenum info
    tagsnight = []
    beamsnight = []
    for tag in inparam.tagsA[night]:
        tagsnight.append(tag)
        beamsnight.append('A')
    for tag in inparam.tagsB[night]:
        tagsnight.append(tag)
        beamsnight.append('B')

    # Load synthetic telluric template generated during Step 1
    # [:8] here is to ensure program works under Night_Split mode
    A0loc = f'../Output/{args.targname}_{args.band}_tool/A0Fits/{night[:8]}A0_treated_{args.band}.fits'
    try:
        hdulist = fits.open(A0loc)
    except IOError:
        logger.warning(
            f'  --> No A0-fitted template for night {night}, skipping...')
        return night, np.nan, np.nan

    # Find corresponding table in fits file, given the tables do not go sequentially by order number due to multiprocessing in Step 1
    num_orders = 0
    for i in range(25):
        try:
            hdulist[i].columns[0].name[9:]
            num_orders += 1
        except:
            continue

    # Check whether Telfit hit critical error in Step 1 for the chosen order with this night. If so, try another order. If all hit the error, skip the night.
    nexto = 0
    ordertry = order
    while 1 == 1:
        fits_layer = [
            i for i in np.arange(num_orders) + 1
            if int(hdulist[i].columns[0].name[9:]) == ordertry
        ][0]

        tbdata = hdulist[fits_layer].data
        flag = np.array(tbdata[f'ERRORFLAG{ordertry}'])[0]

        if flag == 1:  # If Telfit hit unknown critical error in Step 1, this order can't be used for this night. Try another.
            orderbad = ordertry
            ordertry = orders[nexto]
            logger.warning(
                f'  --> TELFIT ENCOUNTERED CRITICAL ERROR IN ORDER: {orderbad} NIGHT: {night}, TRYING ORDER {ordertry} INSTEAD...'
            )
        else:  # All good, continue
            break

        nexto += 1
        if nexto == len(orders):
            logger.warning(
                f'  --> TELFIT ENCOUNTERED CRITICAL ERROR IN ALL ORDERS FOR NIGHT: {night}, skipping...'
            )
            return night, np.nan, np.nan

    watm = tbdata['WATM' + str(order)]
    satm = tbdata['SATM' + str(order)]
    a0contx = tbdata['X' + str(order)]
    continuum = tbdata['BLAZE' + str(order)]

    # Remove extra rows leftover from having columns of unequal length
    satm = satm[(watm != 0)]
    watm = watm[(watm != 0)]
    satm[(
        satm < 1e-4
    )] = 0.  # set very low points to zero so that they don't go to NaN when taken to an exponent by template power in fmodel_chi
    a0contx = a0contx[(continuum != 0)]
    continuum = continuum[(continuum != 0)]

    # Use instrumental profile FWHM dictionary corresponding to whether IGRINS mounting was loose or not
    if int(night[:8]) < 20180401 or int(night[:8]) > 20190531:
        IPpars = inparam.ips_tightmount_pars[args.band][order]
    else:
        IPpars = inparam.ips_loosemount_pars[args.band][order]
#-------------------------------------------------------------------------------
### Initialize parameter array for optimization as well as half-range values for each parameter during the various steps of the optimization.
### Many of the parameters initialized here will be changed throughout the code before optimization and in between optimization steps.
    pars0 = np.array([
        np.nan,  # 0: The shift of the stellar template (km/s)
        0.3,  # 1: The scale factor for the stellar template
        0.0,  # 2: The shift of the telluric template (km/s)
        0.6,  # 3: The scale factor for the telluric template
        inparam.initvsini,  # 4: vsini (km/s)
        IPpars[2],  # 5: The instrumental resolution (FWHM) in pixels
        np.nan,  # 6: Wavelength 0-pt
        np.nan,  # 7: Wavelength linear component
        np.nan,  # 8: Wavelength quadratic component
        np.nan,  # 9: Wavelength cubic component
        1.0,  #10: Continuum zero point
        0.,  #11: Continuum linear component
        0.,  #12: Continuum quadratic component
        IPpars[1],  #13: Instrumental resolution linear component
        IPpars[0]
    ])  #14: Instrumental resolution quadratic component

    rvsmini = []
    vsinismini = []

    # Iterate over all A/B exposures
    for t in np.arange(len(tagsnight)):
        tag = tagsnight[t]
        beam = beamsnight[t]

        # Retrieve pixel bounds for where within each other significant telluric absorption is present.
        # If these bounds were not applied, analyzing some orders would give garbage fits.
        if args.band == 'K':
            if int(order) in [11, 12, 13, 14]:
                bound_cut = inparam.bound_cut_dic[args.band][order]
            else:
                bound_cut = [150, 150]

        elif args.band == 'H':
            if int(order) in [
                    7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
            ]:
                bound_cut = inparam.bound_cut_dic[args.band][order]
            else:
                bound_cut = [150, 150]

        # Load target spectrum
        x, wave, s, u = init_fitsread(f'{inparam.inpath}{night}/{beam}/',
                                      'target', 'separate', night, order, tag,
                                      args.band, bound_cut)

        #-------------------------------------------------------------------------------

        # Execute S/N cut
        s2n = s / u
        if np.nanmedian(s2n) < float(args.SN_cut):
            logger.warning(
                '  --> Bad S/N {:1.3f} < {} for {}{} {}, SKIP'.format(
                    np.nanmedian(s2n), args.SN_cut, night, beam, tag))
            continue

        # Trim obvious outliers above the blaze (i.e. cosmic rays)
        nzones = 5
        x = basicclip_above(x, s, nzones)
        wave = basicclip_above(wave, s, nzones)
        u = basicclip_above(u, s, nzones)
        s = basicclip_above(s, s, nzones)
        x = basicclip_above(x, s, nzones)
        wave = basicclip_above(wave, s, nzones)
        u = basicclip_above(u, s, nzones)
        s = basicclip_above(s, s, nzones)

        # Cut spectrum to within wavelength regions defined in input list
        s_piece = s[(x > xbounds[0]) & (x < xbounds[-1])]
        u_piece = u[(x > xbounds[0]) & (x < xbounds[-1])]
        wave_piece = wave[(x > xbounds[0]) & (x < xbounds[-1])]
        x_piece = x[(x > xbounds[0]) & (x < xbounds[-1])]

        # Trim stellar template to relevant wavelength range
        mwave_in, mflux_in = stellarmodel_setup(wave_piece, inparam.mwave0,
                                                inparam.mflux0)

        # Trim telluric template to relevant wavelength range
        satm_in = satm[(watm > min(wave_piece) * 1e4 - 11)
                       & (watm < max(wave_piece) * 1e4 + 11)]
        watm_in = watm[(watm > min(wave_piece) * 1e4 - 11)
                       & (watm < max(wave_piece) * 1e4 + 11)]

        # Make sure data is within telluric template range (shouldn't do anything)
        s_piece = s_piece[(wave_piece * 1e4 > min(watm_in) + 5)
                          & (wave_piece * 1e4 < max(watm_in) - 5)]
        u_piece = u_piece[(wave_piece * 1e4 > min(watm_in) + 5)
                          & (wave_piece * 1e4 < max(watm_in) - 5)]
        x_piece = x_piece[(wave_piece * 1e4 > min(watm_in) + 5)
                          & (wave_piece * 1e4 < max(watm_in) - 5)]
        wave_piece = wave_piece[(wave_piece * 1e4 > min(watm_in) + 5)
                                & (wave_piece * 1e4 < max(watm_in) - 5)]

        #-------------------------------------------------------------------------------

        par = pars0.copy()

        # Get initial guess for cubic wavelength solution from reduction pipeline
        f = np.polyfit(x_piece, wave_piece, 3)
        par9in = f[0] * 1e4
        par8in = f[1] * 1e4
        par7in = f[2] * 1e4
        par6in = f[3] * 1e4
        par[9] = par9in
        par[8] = par8in
        par[7] = par7in
        par[6] = par6in

        par[0] = initguesses - inparam.bvcs[
            night + tag]  # Initial RV with barycentric correction

        # Arrays defining parameter variations during optimization steps.
        # Optimization will cycle twice. In the first cycle, the RVs can vary more than in the second.
        dpars1 = {
            'cont':
            np.array([
                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0., 1e7, 1, 1, 0,
                0
            ]),
            'wave':
            np.array([
                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 10.0, 5.00000e-5, 0., 0, 0,
                0, 0, 0
            ]),
            't':
            np.array([
                0.0, 0.0, 5.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0
            ]),
            'ip':
            np.array(
                [0.0, 0.0, 0.0, 0.0, 0, 0.5, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0]),
            's':
            np.array([
                20.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0
            ]),
            'v':
            np.array([
                0.0, 0.0, 0.0, 0.0, inparam.vsinivary, 0.0, 0.0, 0.0, 0.0, 0,
                0, 0, 0, 0, 0
            ])
        }
        dpars2 = {
            'cont':
            np.array([
                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0., 1e7, 1, 1, 0,
                0
            ]),
            'wave':
            np.array([
                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 10.0, 5.00000e-5, 0., 0, 0,
                0, 0, 0
            ]),
            't':
            np.array([
                0.0, 0.0, 5.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0
            ]),
            'ip':
            np.array(
                [0.0, 0.0, 0.0, 0.0, 0, 0.5, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0]),
            's':
            np.array([
                5.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0
            ]),
            'v':
            np.array([
                0.0, 0.0, 0.0, 0.0, inparam.vsinivary, 0.0, 0.0, 0.0, 0.0, 0,
                0, 0, 0, 0, 0
            ])
        }

        continuum_in = rebin_jv(a0contx, continuum, x_piece, False)
        s_piece /= np.median(s_piece)
        fitobj = fitobjs(s_piece, x_piece, u_piece, continuum_in, watm_in,
                         satm_in, mflux_in, mwave_in,
                         ast.literal_eval(inparam.maskdict[order]))

        #-------------------------------------------------------------------------------
        # Initialize an array that puts hard bounds on vsini and the instrumental resolution to make sure they do not diverge to unphysical values
        optimize = True
        par_in = par.copy()
        hardbounds = [
            par_in[4] - dpars1['v'][4], par_in[4] + dpars1['v'][4],
            par_in[5] - dpars1['ip'][5], par_in[5] + dpars1['ip'][5]
        ]
        if hardbounds[0] < 0:
            hardbounds[0] = 0
        if hardbounds[3] < 0:
            hardbounds[3] = 1

        # Begin optimization. Fit the blaze, the wavelength solution, the telluric template power and RV, the stellar template power and RV, the
        # zero point for the instrumental resolution, and the vsini of the star separately, iterating and cycling between each set of parameter fits.
        cycles = 2

        optgroup = [
            'cont', 'wave', 't', 'cont', 's', 'cont', 'wave', 't', 's', 'cont',
            'wave', 'ip', 'v', 'ip', 'v', 't', 's', 't', 's'
        ]

        for nc, cycle in enumerate(np.arange(cycles), start=1):
            if cycle == 0:
                parstart = par_in.copy()
                dpars = dpars1
            else:
                dpars = dpars2

            for optkind in optgroup:
                parfit_1 = optimizer(parstart, dpars[optkind], hardbounds,
                                     fitobj, optimize)
                parstart = parfit_1.copy()
                if args.debug:
                    logger.debug(f'{order}_{tag}_{nc}_{optkind}:\n {parfit_1}')

        parfit = parfit_1.copy()

        #-------------------------------------------------------------------------------

        # if best fit stellar template power is very low, throw out result
        if parfit[1] < 0.1:
            logger.warning(f'  --> parfit[1] < 0.1, {night} parfit={parfit}')
            continue

        # if best fit stellar or telluric template powers are exactly equal to their starting values, optimization failed, throw out result
        if parfit[1] == par_in[1] or parfit[3] == par_in[3]:
            logger.warning(
                f'  --> parfit[1] == par_in[1] or parfit[3] == par_in[3], {night}'
            )
            continue

        # if best fit model dips below zero at any point, we're too close to edge of blaze, fit may be comrpomised, throw out result
        smod, chisq = fmod(parfit, fitobj)
        if len(smod[(smod < 0)]) > 0:
            logger.warning(f'  --> len(smod[(smod < 0)]) > 0, {night}')
            continue

        rv0 = parfit[0] - parfit[
            2]  # Correct for RV of the atmosphere, since we're using that as the basis for the wavelength scale

        rvsmini.append(rv0 + inparam.bvcs[night + tag] +
                       rv0 * inparam.bvcs[night + tag] /
                       (3e5**2))  # Barycentric correction
        vsinismini.append(parfit[4])

    bestguess = round(np.nanmean(rvsmini), 5)
    vsinimini = round(np.nanmean(vsinismini), 5)
    return night, bestguess, vsinimini
Esempio n. 3
0
def telfitter(watm_in, satm_in, a0ucut, inparam, night, order, args,
              masterbeam, logger):
    '''
    Produce synthetic telluric template from fit to telluric standard observation. How and why it works is detailed in comments throughout the code.

    Inputs:
    watm_in    : Wavelength scale of telluric standard spectrum
    satm_in    : Corresponding flux of telluric standard spectrum
    a0ucut     : Corresponding uncertainty of telluric standard spectrum
    inparam    : Class containing variety of information (e.g. on observing conditions)
    night      : Date of observation in YYYYMMDD
    order      : Echelle order, as characterized by file index (as opposed to m number; for conversion between the two, see Stahl et al. 2021)
    args       : Information as input by user from command line
    masterbeam : A or B frame

    Outputs:
    wavefitted : Wavelength scale of synethetic telluric spectrum
    satmTel    : Corresponding flux of synthetic telluric spectrum
    names      : Descriptors of Telfit parameters
    parfitted  : Values of best-fit Telfit parameters
    wcont1     : Wavelength scale corresponding to best-fit continuum (from intermediate Telfit step)
    cont1      : Flux corresponding to best-fit continuum (from intermediate Telfit step)
    '''

    os.environ['PYSYN_CDBS'] = inparam.cdbsloc
    fitter = TelluricFitter(debug=False, print_lblrtm_output=args.debug)

    #Set the observatory location with a keyword
    DCT_props = {"latitude": 34.744, "altitude": 2.36}  #altitude in km
    McD_props = {"latitude": 30.710, "altitude": 2.07}
    GaminiS_props = {"latitude": -30.241, "altitude": 2.72}

    if inparam.obses[night] == 'DCT':
        fitter.SetObservatory(DCT_props)
    elif inparam.obses[night] == 'McD':
        fitter.SetObservatory(McD_props)
    elif inparam.obses[night] == 'GeminiS':
        fitter.SetObservatory(GaminiS_props)
    else:
        sys.exit(
            'TELFIT OBSERVATORY ERROR, OLNY SUPPORT DCT, McD & GeminiS IN THIS VERSION!'
        )

    # Read in data
    watm_in = watm_in / 10  # AA --> nm
    data = DataStructures.xypoint(x=watm_in, y=satm_in, cont=None,
                                  err=a0ucut)  # input wavelength in nm

    # DCT data has parameters describing night of observation that the McDonald data does not.
    if inparam.temps[night] != 'NOINFO':  # If such information is available:

        angle = np.float(inparam.zds[night])  #Zenith distance
        pressure = np.float(inparam.press[night])  #Pressure, in hPa
        humidity = np.float(inparam.humids[night]
                            )  #Percent humidity, at the observatory altitude
        temperature = np.float(
            inparam.temps[night]) + 273.15  #Temperature in Kelvin

        if (order <= 4):
            resolution = 55000.0  #Resolution lambda/delta-lambda
        else:
            resolution = 45000.0  #Resolution lambda/delta-lambda

        # Ideally, we'd fit resolution as well since that varies across the detector.
        # But in practice the Telfit's resolution fits often diverge to unphysically high values.
        # Ultimately, we only want accurate estimates for the chemical compositions, which are unaffacted
        # by fixing the resolution at 45000. The final telluric template we'll be synthesizing from this
        # will be at a set high resolution, anyway, and when we need/have needed to estimate resolution
        # across the detector, we (have) done so via the fits of the telluric and stellar templates to the
        # observed A0 and GJ281 spectra.

        # Only 3 molecules present in chosen IGRINS orders' wavelength range are H2O, CH4, and CO.
        if (3 < order < 9) & (args.band == 'K'):
            num_fit = 4
            # Only 3 molecules present in chosen IGRINS orders' wavelength range are H2O, CH4, and CO.
            fitter.FitVariable({
                "h2o": humidity,
                "ch4": 1.8,
                "co": 5e-3,
                "n2o": 5e-2
            })

            #Adjust parameters that will not be fit, but are important
            fitter.AdjustValue({"angle": angle,\
                                "pressure": pressure,\
                                "temperature": temperature,\
                                "resolution": resolution,
                                "wavestart": data.x[0]-0.001,\
                                "waveend": data.x[-1]+0.001,\
                                "co2": 3.675e2,\
                                "o3": 7.6e-4,\
                                "o2": 2.1e5,\
                                "no": 0.,\
                                "so2": 5e-9,\
                                "no2": 5e-9,\
                                "nh3": 5e-9,\
                                "hno3": 1.56e-7})

            #Set bounds on the variables being fit
            fitter.SetBounds({"h2o": [1.0, 99.0],\
                              "ch4": [.1,  10.0],\
                              "n2o": [1e-5,1e2],\
                              "co": [ 1e-6,1e2]})
        elif (order >= 9 or order <= 3) & (args.band == 'K'):
            num_fit = 4
            # Only molecules present in chosen IGRINS orders' wavelength range are H2O, CH4, N2O, and CO2.
            fitter.FitVariable({
                "h2o": humidity,
                "ch4": 1.8,
                "co2": 3.675e2,
                "n2o": 5e-2
            })

            #Adjust parameters that will not be fit, but are important
            fitter.AdjustValue({"angle": angle,\
                                "pressure": pressure,\
                                "temperature": temperature,\
                                "resolution": resolution,
                                "wavestart": data.x[0]-0.001,\
                                "waveend": data.x[-1]+0.001,\
                                "co": 5e-3,\
                                "o3": 7.6e-4,\
                                "o2": 2.1e5,\
                                "no": 0.,\
                                "so2": 5e-9,\
                                "no2": 5e-9,\
                                "nh3": 5e-9,\
                                "hno3": 1.56e-7})

            #Set bounds on the variables being fit
            fitter.SetBounds({"h2o": [1.0, 99.0],\
                              "ch4": [.1,  10.0],\
                              "n2o": [1e-5,1e2],\
                              "co2": [1.0, 1e4]})
        elif args.band == 'H':
            num_fit = 3
            fitter.FitVariable({
                "h2o": humidity,
                "ch4": 1.8,
                "co": 5e-3,
                "co2": 3.675e2,
                "n2o": 5e-2
            })

            #Adjust parameters that will not be fit, but are important
            fitter.AdjustValue({"angle": angle,\
                                "pressure": pressure,\
                                "temperature": temperature,\
                                "resolution": resolution,
                                "wavestart": data.x[0]-0.001,\
                                "waveend": data.x[-1]+0.001,\
                                "o3": 7.6e-4,\
                                "o2": 2.1e5,\
                                "no": 0.,\
                                "so2": 5e-9,\
                                "no2": 5e-9,\
                                "nh3": 5e-9,\
                                "hno3": 1.56e-7})

            #Set bounds on the variables being fit
            fitter.SetBounds({"h2o": [1.0, 99.0],\
                              "ch4": [.1,  10.0],\
                              "n2o": [1e-6,1e2],\
                              "co": [1e-6,1e2],\
                              "co2": [1.0, 1e4]})

    elif inparam.zds[
            night] != 'NOINFO':  # If GeminiS data, some but not all parameters are in fits file.
        # If parameters are not in fits file, use initial guesses and letting them vary.
        # Guesses are taken from mean of parameters from DCT GJ281 data.

        angle = np.float(inparam.zds[night])  #Zenith distance
        humidity = np.float(inparam.humids[night]
                            )  #Percent humidity, at the observatory altitude

        if (order <= 4):
            resolution = 55000.0  #Resolution lambda/delta-lambda
        else:
            resolution = 45000.0  #Resolution lambda/delta-lambda

        # Only 3 molecules present in chosen IGRINS orders' wavelength range are H2O, CH4, and CO.
        if (3 < order < 9) & (args.band == 'K'):
            num_fit = 4
            # Only 3 molecules present in chosen IGRINS orders' wavelength range are H2O, CH4, and CO.
            fitter.FitVariable({
                "h2o": humidity,
                "ch4": 1.8,
                "co": 5e-3,
                "n2o": 5e-2,
                "pressure": 1023.,
                "temperature": 280.87
            })

            #Adjust parameters that will not be fit, but are important
            fitter.AdjustValue({"angle": angle,\
                                "resolution": resolution,
                                "wavestart": data.x[0]-0.001,\
                                "waveend": data.x[-1]+0.001,\
                                "co2": 3.675e2,\
                                "o3": 7.6e-4,\
                                "o2": 2.1e5,\
                                "no": 0.,\
                                "so2": 5e-9,\
                                "no2": 5e-9,\
                                "nh3": 5e-9,\
                                "hno3": 1.56e-7})

            #Set bounds on the variables being fit
            fitter.SetBounds({"h2o": [1.0, 99.0],\
                              "ch4": [.1,  10.0],\
                              "n2o": [1e-5,1e2],\
                              "temperature": [265.,300.],\
                              "pressure": [1010.,1035.],\
                              "co": [ 1e-6,1e2]})

        elif (order >= 9 or order <= 3) & (args.band == 'K'):
            num_fit = 4
            # Only molecules present in chosen IGRINS orders' wavelength range are H2O, CH4, N2O, and CO2.
            fitter.FitVariable({
                "h2o": humidity,
                "ch4": 1.8,
                "co2": 3.675e2,
                "n2o": 5e-2,
                "pressure": 1023.,
                "temperature": 280.87
            })

            #Adjust parameters that will not be fit, but are important
            fitter.AdjustValue({"angle": angle,\
                                "resolution": resolution,
                                "wavestart": data.x[0]-0.001,\
                                "waveend": data.x[-1]+0.001,\
                                "co": 5e-3,\
                                "o3": 7.6e-4,\
                                "o2": 2.1e5,\
                                "no": 0.,\
                                "so2": 5e-9,\
                                "no2": 5e-9,\
                                "nh3": 5e-9,\
                                "hno3": 1.56e-7})

            #Set bounds on the variables being fit
            fitter.SetBounds({"h2o": [1.0, 99.0],\
                              "ch4": [.1,  10.0],\
                              "n2o": [1e-5,1e2],\
                              "temperature": [265.,300.],\
                              "pressure": [1010.,1035.],\
                              "co2": [1.0, 1e4]})
        elif args.band == 'H':
            num_fit = 3
            fitter.FitVariable({
                "h2o": humidity,
                "ch4": 1.8,
                "co": 5e-3,
                "co2": 3.675e2,
                "n2o": 5e-2,
                "pressure": 1023.,
                "temperature": 280.87
            })

            #Adjust parameters that will not be fit, but are important
            fitter.AdjustValue({"angle": angle,\
                                "resolution": resolution,
                                "wavestart": data.x[0]-0.001,\
                                "waveend": data.x[-1]+0.001,\
                                "o3": 7.6e-4,\
                                "o2": 2.1e5,\
                                "no": 0.,\
                                "so2": 5e-9,\
                                "no2": 5e-9,\
                                "nh3": 5e-9,\
                                "hno3": 1.56e-7})

            #Set bounds on the variables being fit
            fitter.SetBounds({"h2o": [1.0, 99.0],\
                              "ch4": [.1,  10.0],\
                              "n2o": [1e-6,1e2],\
                              "co": [1e-6,1e2],\
                              "temperature": [265.,300.],\
                              "pressure": [1010.,1035.],\
                              "co2": [1.0, 1e4]})

    else:  # If parameters are not in fits file, use initial guesses and letting them vary.
        # Guesses are taken from mean of parameters from DCT GJ281 data.

        if (order <= 4):
            resolution = 55000.0  #Resolution lambda/delta-lambda
        else:
            resolution = 45000.0  #Resolution lambda/delta-lambda

        # Only 3 molecules present in chosen IGRINS orders' wavelength range are H2O, CH4, and CO.
        if (3 < order < 9) & (args.band == 'K'):
            num_fit = 6
            fitter.FitVariable({
                "h2o": 43.,
                "ch4": 1.8,
                "co": 5e-3,
                "n2o": 5e-2,
                "angle": 39.,
                "pressure": 1023.,
                "temperature": 280.87
            })

            #Adjust parameters that will not be fit, but are important
            fitter.AdjustValue({"resolution": resolution,\
                                "wavestart": data.x[0]-0.001,\
                                "waveend": data.x[-1]+0.001,\
                                "co2": 3.675e2,\
                                "o3": 7.6e-4,\
                                "o2": 2.1e5,\
                                "no": 0.,\
                                "so2": 5e-9,\
                                "no2": 5e-9,\
                                "nh3": 5e-9,\
                                "hno3": 1.56e-7})

            #Set bounds on the variables being fit
            fitter.SetBounds({"h2o": [1.0, 99.0],\
                              "ch4": [.1,10.0],\
                              "n2o": [1e-5,1e2],\
                              "temperature": [265.,300.],\
                              "angle": [1.,75.],\
                              "pressure": [1010.,1035.],\
                              "co": [ 1e-6,1e2]})

        elif (order >= 9 or order <= 3) & (args.band == 'K'):
            num_fit = 7
            # Only molecules present in chosen IGRINS orders' wavelength range are H2O, CH4, N2O, and CO2.
            fitter.FitVariable({
                "h2o": 43.,
                "ch4": 1.8,
                "co2": 3.675e2,
                "n2o": 5e-2,
                "angle": 39.,
                "pressure": 1023.,
                "temperature": 280.87
            })

            #Adjust parameters that will not be fit, but are important
            fitter.AdjustValue({"resolution": resolution,\
                                "wavestart": data.x[0]-0.001,\
                                "waveend": data.x[-1]+0.001,\
                                "co": 5e-3,\
                                "o3": 7.6e-4,\
                                "o2": 2.1e5,\
                                "no": 0.,\
                                "so2": 5e-9,\
                                "no2": 5e-9,\
                                "nh3": 5e-9,\
                                "hno3": 1.56e-7})

            #Set bounds on the variables being fit
            fitter.SetBounds({"h2o": [1.0, 99.0],\
                              "ch4": [.1,10.0],\
                              "temperature": [265.,300.],\
                              "angle": [1.,75.],\
                              "n2o":[1e-5,1e2],\
                              "pressure": [1010.,1035.],\
                              "co2": [1.0, 1e4]})

        elif args.band == 'H':
            num_fit = 6
            fitter.FitVariable({
                "h2o": 43.,
                "ch4": 1.8,
                "co": 5e-3,
                "co2": 3.675e2,
                "n2o": 5e-2,
                "angle": 39.,
                "pressure": 1023.,
                "temperature": 280.87
            })

            #Adjust parameters that will not be fit, but are important
            fitter.AdjustValue({"resolution": resolution,\
                                "wavestart": data.x[0]-0.001,\
                                "waveend": data.x[-1]+0.001,\
                                "o3": 7.6e-4,\
                                "o2": 2.1e5,\
                                "no": 0.,\
                                "so2": 5e-9,\
                                "no2": 5e-9,\
                                "nh3": 5e-9,\
                                "hno3": 1.56e-7})

            #Set bounds on the variables being fit
            fitter.SetBounds({"h2o": [1.0, 99.0],\
                              "ch4": [.1,10.0],\
                              "n2o": [1e-6,1e2],\
                              "co": [1e-6,1e2],\
                              "temperature": [265.,300.],\
                              "angle": [1.,75.],\
                              "pressure": [1010.,1035.],\
                              "co2": [ 1,1e4]})

    try:
        if args.debug:
            model = fitter.Fit(data=data,
                               resolution_fit_mode="SVD",
                               adjust_wave="model",
                               air_wave=False)
        else:
            model = suppress_Fit(fitter, data)
    except TypeError:
        return [np.nan], [np.nan], [np.nan], [np.nan], [np.nan], [np.nan]
    '''
      resolution_fit_mode = SVD ought to give faster, more accurate fits for the deep telluric lines we mostly see in K band
      air_wave = False because data in vacuum wavelengths
      adjust_wave =
                    From Telfit comments: "Can be set to either 'data' or 'model'. To wavelength calibrate the
                    data to the telluric lines, set to 'data'. If you think the wavelength
                    calibration is good on the data (such as Th-Ar lines in the optical),
                    then set to 'model' Note that currently, the vacuum --> air conversion
                    for the telluric model is done in a very approximate sense, so
                    adjusting the data wavelengths may introduce a small (few km/s) offset
                    from what it should be. That is fine for relative RVs, but probably not
                    for absolute RVs."

                    As it turns out, the model internal to Telfit is still not very precise in wavelength space, since it relies on the HITRAN
                    database. Some lines are accurate to 1 m/s, but some less so.

                    Hence, our procedure is as follows:

                    1) Fit the A0 spectrum using the Livingston telluric template. Get out a precisely calibrated wavelength solution for the spectrum.
                        (This is done in A0Fitter, not Telfitter)
                    2) Use that wavelength solution was input for Telfit, and let the wavelength scale of the model vary with respect to it.
                    3) Using Telfit's best fit parameters, generate a telluric template at high resolution.
                       Note: Telfit's default when generating a model is to employ a vacuum/air conversion. In order to avoid that,
                             I have manually edited one of Telfit's files.
                    4) To properly calibrate this template in wavelength space, we fit it to a Telfit'd version of the Livingston telluric template, only allowing
                       the wavelength solution to vary.
    '''

    #Get the improved continuum from the fitter
    cont1 = fitter.data.cont
    wcont1 = model.x * 10  # nm-->AA

    # chi_new = np.sum((satm_in - model.y*cont1)**2. / model.u**2.)
    # chi_new = chisq / (len(model.y) - num_fit)

    if args.plotfigs:
        fig, axes = plt.subplots(1,
                                 1,
                                 figsize=(6, 3),
                                 facecolor='white',
                                 dpi=300)

        axes.plot(10 * watm_in,
                  satm_in,
                  color='black',
                  alpha=.8,
                  label='data',
                  lw=0.7)
        axes.plot(10 * model.x,
                  model.y * cont1,
                  color='tab:red',
                  alpha=.8,
                  label='model fit',
                  lw=0.7)
        axes.plot(10 * model.x,
                  cont1,
                  color='tab:blue',
                  alpha=.8,
                  label='blaze fit',
                  lw=0.7)

        axes.xaxis.set_minor_locator(AutoMinorLocator(5))
        axes.yaxis.set_minor_locator(AutoMinorLocator(2))
        axes.tick_params(axis='both',
                         which='both',
                         labelsize=6,
                         right=True,
                         top=True,
                         direction='in')
        axes.set_ylabel(r'Flux', size=6, style='normal', family='sans-serif')
        axes.set_xlabel(r'Wavelength [$\AA$]',
                        size=6,
                        style='normal',
                        family='sans-serif')
        axes.legend(fontsize=5, edgecolor='white')
        axes.set_title('A0Telfit_Order{}_{}_{}.png'.format(
            order, night, masterbeam),
                       size=6,
                       style='normal',
                       family='sans-serif')
        # fig.text(0.65, 0.2, r'$\rm \chi^{{2}}_{{\nu}}$ = {:1.2f}'.format(chi_new),
        #                     size=6, style='normal', family='sans-serif')
        fig.savefig('{}/figs_{}/A0Telfit_Order{}_{}_{}.png'.format(
            inparam.outpath, args.band, order, night, masterbeam),
                    format='png',
                    bbox_inches='tight',
                    overwrite=True)

    ############### Generate template with these parameters but at higher resolution

    names = [
        "pressure", "temperature", "angle", "resolution", 'wavestart',
        'waveend', "h2o", "co2", "o3", "n2o", "co", "ch4", "o2", "no", "so2",
        "no2", "nh3", "hno3"
    ]

    parfitted = np.ones_like(names, dtype=float)
    for k in range(len(names)):
        parfitted[k] = np.float(fitter.GetValue(names[k]))

    fitter2 = TelluricFitter(debug=False, print_lblrtm_output=args.debug)

    if inparam.obses[night] == 'DCT':
        fitter2.SetObservatory(DCT_props)
    elif inparam.obses[night] == 'McD':
        fitter2.SetObservatory(McD_props)
    elif inparam.obses[night] == 'GeminiS':
        fitter2.SetObservatory(GaminiS_props)

    # Compute telluric template with highest resolution of Livingston template.
    # Add extra space at ends to make sure template covers wider range than data.
    Livingston_minimum_wsep = .035 / 10
    IGRINS_minimum_wsep = .130  # <-- This would compute template with IGRINS resolution, sensibly coarser than Livingston

    newwave = np.arange(
        np.min(watm_in) - 2.5,
        np.max(watm_in) + 2.5, Livingston_minimum_wsep)  #in nm

    data2 = DataStructures.xypoint(x=newwave, y=None, cont=None, err=None)
    params = {}
    for k in range(len(names)):
        params[names[k]] = np.float(parfitted[k])

    params['wavestart'] = data2.x[0] - 0.001
    params['waveend'] = data2.x[-1] + 0.001

    fitter2.AdjustValue(params)
    fitter2.ImportData(data2)

    # Call the modeller. On rare occasions, this returns an error. I have no idea what is causing this error, as the
    # FORTRAN readout is quite unhelpful and anyone else who apepars to have experienced this problem had it randomly go away at some point.
    # If this happens, simply deliver NAN arrays, and in later parts of the RV analysis A0 fits from the nearest compatible observation will be used.
    try:
        if args.debug:
            model2 = fitter2.GenerateModel(parfitted,
                                           nofit=True,
                                           air_wave=False)
        else:
            model2 = suppress_GenerateModel(fitter2, parfitted, args)

    except TypeError:
        return [np.nan], [np.nan], [np.nan], [np.nan], [np.nan], [np.nan]

    watm_save = watm_in.copy()
    satm_save = satm_in.copy()
    newwave1 = newwave[(newwave > watm_in[0] - 1.0)
                       & (newwave < watm_in[-1] + 1.0)]

    # Parameters for reproducing Livingston template with Telfit
    if args.band == 'K':
        telparsdict = {
            '1':
            np.array([
                1.01469894e+03, 2.86278974e+02, 2.57160505e+01, 6.00000000e+05,
                2.42400187e+03, 2.50999640e+03, 1.44631214e+01, 3.67500000e+02,
                7.60000000e-04, 5.00000000e-02, 7.06751837e+00, 2.21814724e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '2':
            np.array([
                1.01000565e+03, 2.86022221e+02, 1.02518785e+01, 6.00000000e+05,
                2.39100388e+03, 2.47599903e+03, 1.60984192e+01, 3.67500000e+02,
                7.60000000e-04, 5.00000000e-02, 9.51814513e-02, 2.80273169e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '3':
            np.array([
                1.01208206e+03, 2.83372443e+02, 1.30994741e+01, 6.00000000e+05,
                2.36000083e+03, 2.44399739e+03, 2.03888166e+01, 3.67500000e+02,
                7.60000000e-04, 5.00000000e-02, 1.78694998e-01, 2.63207260e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '4':
            np.array([
                1.01044914e+03, 3.09376975e+02, 5.00457250e+01, 6.00000000e+05,
                2.32700464e+03, 2.41199998e+03, 2.23826025e+00, 3.67500000e+02,
                7.60000000e-04, 5.00000000e-02, 1.44749523e-01, 1.78797014e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '5':
            np.array([
                1.01028238e+03, 2.80000941e+02, 5.02034212e+01, 6.00000000e+05,
                2.29700026e+03, 2.38099471e+03, 2.00001482e+01, 3.67500000e+02,
                7.60000000e-04, 5.00000000e-02, 5.04544799e-03, 1.79978608e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '6':
            np.array([
                1.01003245e+03, 2.82986081e+02, 4.69226227e+01, 6.00000000e+05,
                2.26700430e+03, 2.35099941e+03, 1.83825870e+01, 3.67500000e+02,
                7.60000000e-04, 5.00000000e-02, 1.33618090e-01, 1.75621792e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '7':
            np.array([
                1.01676398e+03, 2.80004945e+02, 5.00497774e+01, 6.00000000e+05,
                2.23800235e+03, 2.32199758e+03, 1.99995159e+01, 3.67500000e+02,
                7.60000000e-04, 5.00000000e-02, 5.00089318e-03, 1.79986683e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '8':
            np.array([
                1.02000000e+03, 2.80000000e+02, 5.00000000e+01, 6.00000000e+05,
                2.21000474e+03, 2.29299818e+03, 2.00000000e+01, 3.67500000e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.80000000e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '9':
            np.array([
                1.01010028e+03, 2.83521347e+02, 4.94584722e+01, 6.00000000e+05,
                2.20299994e+03, 2.24499864e+03, 1.62628503e+01, 2.09026148e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.84204544e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '10':
            np.array([
                1.02057631e+03, 2.80065296e+02, 4.97608855e+01, 6.00000000e+05,
                2.17600055e+03, 2.21800093e+03, 1.99862859e+01, 3.75841930e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.77986802e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '11':
            np.array([
                1.01175621e+03, 2.80258981e+02, 5.01757000e+01, 6.00000000e+05,
                2.15000090e+03, 2.19200022e+03, 1.94041560e+01, 3.17393616e+02,
                7.60000000e-04, 1.54427423e-01, 5.00000000e-03, 1.69487644e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '12':
            np.array([
                1.01939759e+03, 3.03543029e+02, 5.16166925e+01, 6.00000000e+05,
                2.12399934e+03, 2.16599710e+03, 4.88527175e+00, 3.62082287e+02,
                7.60000000e-04, 4.24872185e-01, 5.00000000e-03, 1.76259774e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '13':
            np.array([
                1.01289532e+03, 2.97875731e+02, 3.68795863e+01, 6.00000000e+05,
                2.09899925e+03, 2.14099888e+03, 8.74545147e+00, 4.70227887e+02,
                7.60000000e-04, 4.02655042e-01, 5.00000000e-03, 4.78062748e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '14':
            np.array([
                1.01088512e+03, 2.82651527e+02, 4.97893186e+01, 6.00000000e+05,
                2.07500079e+03, 2.11599814e+03, 1.70464824e+01, 3.75172445e+02,
                7.60000000e-04, 3.21242442e-01, 5.00000000e-03, 1.80000000e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '15':
            np.array([
                1.01015623e+03, 3.09948799e+02, 3.32690854e+01, 6.00000000e+05,
                2.05100270e+03, 2.09199736e+03, 4.38724419e+00, 3.98166105e+02,
                7.60000000e-04, 4.99995596e-02, 5.00000000e-03, 1.79687720e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '16':
            np.array([
                1.01020405e+03, 2.99035342e+02, 6.85450975e+01, 6.00000000e+05,
                2.02800148e+03, 2.06800000e+03, 3.16442794e+00, 1.73470928e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.80000000e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
        }
    elif args.band == 'H':
        telparsdict = {
            '1':
            np.array([
                1.01099787e+03, 2.77972664e+02, 1.74812183e+01, 6.00000000e+05,
                1.76500270e+03, 1.84299940e+03, 2.59940438e+01, 3.67500000e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 2.43429720e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '2':
            np.array([
                1.01488875e+03, 2.81026692e+02, 5.96311953e+01, 6.00000000e+05,
                1.76800166e+03, 1.80500074e+03, 1.22804054e+01, 2.59351413e+03,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.33456803e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '3':
            np.array([
                1.01303120e+03, 2.85356979e+02, 4.55001103e+01, 6.00000000e+05,
                1.75099969e+03, 1.78799814e+03, 1.24741562e+01, 2.10505343e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.74585431e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '4':
            np.array([
                1.01010048e+03, 2.79724074e+02, 4.64849179e+01, 6.00000000e+05,
                1.73399959e+03, 1.77100071e+03, 1.89163537e+01, 2.06761848e+03,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 2.04884875e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '5':
            np.array([
                1.01201335e+03, 2.80571184e+02, 5.12883875e+01, 6.00000000e+05,
                1.71800032e+03, 1.75399961e+03, 1.61496968e+01, 2.30222114e+03,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.88354988e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '6':
            np.array([
                1.01002060e+03, 2.79834802e+02, 4.92317644e+01, 6.00000000e+05,
                1.70199977e+03, 1.73800017e+03, 1.94448331e+01, 1.44128653e+03,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.78512125e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '7':
            np.array([
                1.02349233e+03, 2.82841942e+02, 4.84055036e+01, 6.00000000e+05,
                1.68600077e+03, 1.72200035e+03, 1.56440323e+01, 2.29711325e+03,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.75546431e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '8':
            np.array([
                1.02535183e+03, 2.84690731e+02, 6.38909473e+01, 6.00000000e+05,
                1.67000105e+03, 1.70600005e+03, 8.62895867e+00, 2.93112111e+03,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.08243949e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '9':
            np.array([
                1.01005569e+03, 2.85368493e+02, 4.69600789e+01, 6.00000000e+05,
                1.65500012e+03, 1.68999935e+03, 1.34436019e+01, 6.84155129e+01,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.76576528e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '10':
            np.array([
                1.01048567e+03, 2.97058150e+02, 4.33608311e+01, 6.00000000e+05,
                1.64000122e+03, 1.67500013e+03, 6.53445899e+00, 4.08655037e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.90785867e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '11':
            np.array([
                1.02491139e+03, 2.85559174e+02, 4.08532668e+01, 6.00000000e+05,
                1.62600054e+03, 1.65999850e+03, 2.00545756e+01, 4.11449098e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.88659154e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '12':
            np.array([
                1.01003290e+03, 3.09888667e+02, 4.54061158e+01, 6.00000000e+05,
                1.61100114e+03, 1.64599985e+03, 3.64022137e+00, 3.81309830e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.91039536e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '13':
            np.array([
                1.01342132e+03, 3.08696226e+02, 4.20101993e+01, 6.00000000e+05,
                1.59700075e+03, 1.63200043e+03, 1.87270207e+00, 3.92945895e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.57637068e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '14':
            np.array([
                1.01388819e+03, 3.08306291e+02, 3.84989496e+01, 6.00000000e+05,
                1.58299931e+03, 1.61799908e+03, 3.67128385e+00, 4.27356912e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 2.23969000e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '15':
            np.array([
                1.01014958e+03, 3.09907179e+02, 4.01340902e+01, 6.00000000e+05,
                1.57000020e+03, 1.60399922e+03, 3.71266195e+00, 4.13591102e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 4.45900885e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '16':
            np.array([
                1.02110710e+03, 2.95717915e+02, 5.10020728e+01, 6.00000000e+05,
                1.55600027e+03, 1.59000003e+03, 3.33875074e+01, 3.23470908e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 4.38118995e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '17':
            np.array([
                1.01016410e+03, 3.07744028e+02, 4.29708519e+01, 6.00000000e+05,
                1.54300110e+03, 1.57699913e+03, 3.10294397e+00, 3.89081633e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.80000000e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '18':
            np.array([
                1.02523331e+03, 2.80364698e+02, 4.91323141e+01, 6.00000000e+05,
                1.52999979e+03, 1.56400054e+03, 2.00474401e+01, 3.58065516e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.80000000e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '19':
            np.array([
                1.01003349e+03, 2.81022630e+02, 4.79869931e+01, 6.00000000e+05,
                1.51799946e+03, 1.55100021e+03, 2.00658592e+01, 3.75629220e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.80000000e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '20':
            np.array([
                1.01002788e+03, 2.79946092e+02, 4.98150515e+01, 6.00000000e+05,
                1.50500004e+03, 1.53799890e+03, 1.98874188e+01, 3.63105569e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 2.08959964e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '21':
            np.array([
                1.01198904e+03, 2.75314098e+02, 4.99899615e+01, 6.00000000e+05,
                1.49300048e+03, 1.52599904e+03, 2.58987815e+01, 5.00528381e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 2.74730881e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '22':
            np.array([
                1.01503380e+03, 2.77013372e+02, 5.99130176e+01, 6.00000000e+05,
                1.48099946e+03, 1.51400039e+03, 1.72525774e+01, 2.29210179e+01,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 2.36183449e+00,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
            '23':
            np.array([
                1.01136214e+03, 2.86167482e+02, 5.33964947e+01, 6.00000000e+05,
                1.47015399e+03, 1.50200068e+03, 9.83566652e+00, 2.71708854e+02,
                7.60000000e-04, 5.00000000e-02, 5.00000000e-03, 1.06492323e-01,
                2.10000000e+05, 0.00000000e+00, 5.00000000e-09, 5.00000000e-09,
                5.00000000e-09, 1.56000000e-07
            ]),
        }

    fitterL = TelluricFitter(debug=False, print_lblrtm_output=args.debug)

    NSO_props = {"latitude": 31.958, "altitude": 2.096}  #alt in km
    fitterL.SetObservatory(NSO_props)

    dataL = DataStructures.xypoint(x=newwave1, y=None, cont=None, err=None)

    parfittedL = telparsdict[str(order)]
    paramsL = {}
    for k in range(len(names)):
        paramsL[names[k]] = np.float(parfittedL[k])

    paramsL['wavestart'] = dataL.x[0]  # nm
    paramsL['waveend'] = dataL.x[-1]  # nm
    fitterL.AdjustValue(paramsL)
    fitterL.ImportData(data2)

    try:
        if args.debug:
            modelL = fitterL.GenerateModel(parfittedL,
                                           nofit=True,
                                           air_wave=False)
        else:
            modelL = suppress_GenerateModel(fitterL, parfittedL, args)

    except TypeError:
        return [np.nan], [np.nan], [np.nan], [np.nan], [np.nan], [np.nan]

    global x, satmLivGen, watm_Liv, satm_Liv

    satmTel = rebin_jv(model2.x * 10,
                       model2.y,
                       newwave1 * 10,
                       True,
                       logger=logger)  # nm --> AA
    satmLivGen = rebin_jv(modelL.x * 10,
                          modelL.y,
                          newwave1 * 10,
                          True,
                          logger=logger)  # nm --> AA
    watmLivGen = newwave1.copy()
    watmLivGen *= 10  # nm --> AA

    # Fit wavelength scale to Telfit'd Livingston
    x = np.arange(len(satmLivGen))
    initguess = np.polyfit(x, watmLivGen, 6)

    #    print('watmLivGen=\n', watmLivGen)
    #    print('x=\n', x)

    watm_Liv = inparam.watm[(inparam.watm > watmLivGen[0] + 1)
                            & (inparam.watm < watmLivGen[-1] - 1)]
    satm_Liv = inparam.satm[(inparam.watm > watmLivGen[0] + 1)
                            & (inparam.watm < watmLivGen[-1] - 1)]
    dpar = np.abs(initguess) * 10
    dpar[-1] = 5

    waveparfit = wavefit(initguess, dpar)
    f = np.poly1d(waveparfit)
    wavefitted = f(x)

    satmTel[(
        satmTel < 1e-4
    )] = 0.  # set very low points to zero so that they don't go to NaN when taken to an exponent by template power in fmodel_chi

    return wavefitted, satmTel, names, parfitted, wcont1, cont1
Esempio n. 4
0
def WVsol(band):
    # Enter here the name of all A0_Fits directories you'd like to be used (multiple targets would be nice, for
    # consistency, but only if they use the same orders.
    sourcelist = ['A0_Fits_CITau_TEST']

    basedir = os.getcwd()
    filesndirs = os.listdir(basedir)
    first = True
    orders = []

    xmaster = np.arange(2048)
    wmaster = {}

    for f in sourcelist:
        os.chdir(f)
        for a0 in glob.glob('*.fits'):
            hdulist = fits.open(a0)

            if first == True:
                for o in np.arange(1, 24):
                    try:
                        tbdata = hdulist[o]
                    except IndexError:
                        break
                    strhead = tbdata.columns[0].name
                    orders.append(int(strhead[9:]))

            for o in range(len(orders)):
                tbdata = hdulist[o + 1].data
                try:
                    flag = np.array(tbdata['ERRORFLAG{}'.format(orders[o])])[0]
                except KeyError:
                    print(
                        'Warning, file {} does not have the same set of orders as others!'
                    )
                    continue
                if flag == 1:
                    continue
                x = np.array(tbdata['X{}'.format(orders[o])])
                w = np.array(tbdata['WAVE{}'.format(orders[o])])
                x = x[(w != 0)]
                w = w[(w != 0)]
                w1 = rebin_jv(x, w, xmaster, False)

                if first == True:
                    wmaster[o] = w1
                else:
                    wmaster[o] = np.vstack((wmaster[o], w1))

            first = False

        os.chdir(basedir)

    filew = open('./WaveSolns.csv', 'w')

    for o in range(len(orders)):

        wmaster[o] = [
            np.mean(wmaster[o][:, i]) for i in range(len(wmaster[o][0, :]))
        ]
        wmaster[o] = wmaster[o][5:-5]

        test = np.array(wmaster[o])
        diff = test[1:] - test[:-1]
        if len(diff[(diff < 0)]) > 1:
            sys.exit(
                'ERROR! COMBINED WAVELENGTH SOLUTIONS NOT MONOTONIC! QUITTING!'
            )

        if o != len(orders) - 1:
            filew.write('x{},w{},'.format(orders[o], orders[o]))
        else:
            filew.write('x{},w{}'.format(orders[o], orders[o]))

    filew.write('\n')
    xmaster = xmaster[5:-5]

    for i in range(len(xmaster)):
        for o in range(len(orders)):
            if o != len(orders) - 1:
                filew.write('{},{},'.format(xmaster[i], wmaster[o][i] / 1e4))
            else:
                filew.write('{},{}'.format(xmaster[i], wmaster[o][i] / 1e4))
                filew.write('\n')
    filew.close()
def rv_MPinst(args, inparam, orders, order_use, trk, step2or3, i):

    # Main function for RV fitting that will be threaded over by multiprocessing

    nights = inparam.nights
    night = nights[i]  # current looped night

    order = orders[order_use]
    xbounds = inparam.xbounddict[order]
    firstorder = orders[
        0]  # First order that will be analyzed, related to file writing

    print(
        'Working on order {:02d}/{:02d} ({}), night {:03d}/{:03d} ({}) PID:{}...'
        .format(
            int(order_use) + 1, len(orders), order, i + 1, len(inparam.nights),
            night,
            mp.current_process().pid))

    #-------------------------------------------------------------------------------

    # Collect relevant beam and filenum info
    tagsnight = []
    beamsnight = []
    for tag in inparam.tagsA[night]:
        tagsnight.append(tag)
        beamsnight.append('A')
    for tag in inparam.tagsB[night]:
        tagsnight.append(tag)
        beamsnight.append('B')

    nightsout = []

    wminibox = np.ones(2048)
    sminibox = np.ones(2048)
    flminibox_tel = np.ones(2048)
    flminibox_ste = np.ones(2048)
    contiminibox = np.ones(2048)
    flminibox_mod = np.ones(2048)

    wminibox[:] = np.nan
    sminibox[:] = np.nan
    flminibox_tel[:] = np.nan
    flminibox_ste[:] = np.nan
    contiminibox[:] = np.nan
    flminibox_mod[:] = np.nan

    for t in tagsnight:
        nightsout.append(night)

#-------------------------------------------------------------------------------
# Collect initial RV guesses
    if type(inparam.initguesses) == dict:
        initguesses = inparam.initguesses[night]
    elif type(inparam.initguesses) == float:
        initguesses = inparam.initguesses
    else:
        sys.exit(
            'ERROR! EXPECING SINGAL NUMBER OR FILE FOR INITGUESSES! QUITTING!')

    if np.isnan(initguesses) == True:
        logger.warning(
            f'  --> Previous run of {night} found it inadequate, skipping...')
        return nightsout, rvsminibox, parfitminibox, vsiniminibox, tagsminibox

    # start at bucket loc = 1250 +- 100, width = 250 +- 100, depth = 100 +- 5000 but floor at 0
    if args.band == 'H':
        centerloc = 1250
    else:
        centerloc = 1180

#-------------------------------------------------------------------------------
### Initialize parameter array for optimization as well as half-range values for each parameter during the various steps of the optimization.
### Many of the parameters initialized here will be changed throughout the code before optimization and in between optimization steps.
    pars0 = np.array([
        np.nan,  # 0: The shift of the stellar template (km/s) [assigned later]
        0.3,  # 1: The scale factor for the stellar template
        0.0,  # 2: The shift of the telluric template (km/s)
        0.6,  # 3: The scale factor for the telluric template
        inparam.initvsini,  # 4: vsini (km/s)
        np.nan,  # 5: The instrumental resolution (FWHM) in pixels
        np.nan,  # 6: Wavelength 0-pt
        np.nan,  # 7: Wavelength linear component
        np.nan,  # 8: Wavelength quadratic component
        np.nan,  # 9: Wavelength cubic component
        1.0,  #10: Continuum zero point
        0.,  #11: Continuum linear component
        0.,  #12: Continuum quadratic component
        np.nan,  #13: Instrumental resolution linear component
        np.nan,  #14: Instrumental resolution quadratic component
        centerloc,  #15: Blaze dip center location
        330,  #16: Blaze dip full width
        0.05,  #17: Blaze dip depth
        90,  #18: Secondary blaze dip full width
        0.05,  #19: Blaze dip depth
        0.0,  #20: Continuum cubic component
        0.0,  #21: Continuum quartic component
        0.0,  #22: Continuum pentic component
        0.0
    ])  #23: Continuum hexic component

    # This one specific order is small and telluric dominated, start with greater stellar template power to ensure good fits
    if int(order) == 13:
        pars0[1] = 0.8

    # Iterate over all A/B exposures
    for t in [0]:
        tag = tagsnight[t]
        beam = beamsnight[t]
        masterbeam = beam

        # Load synthetic telluric template generated during Step 1
        # [:8] here is to ensure program works under Night_Split mode

        # Use instrumental profile dictionary corresponding to whether IGRINS mounting was loose or not
        if np.int(night[:8]) < 20180401 or np.int(night[:8]) > 20190531:
            IPpars = inparam.ips_tightmount_pars[args.band][masterbeam][order]
        else:
            IPpars = inparam.ips_loosemount_pars[args.band][masterbeam][order]

        if beam == 'A':
            antibeam = 'B'
        elif beam == 'B':
            antibeam = 'A'
        else:
            sys.exit('uhoh')

        A0loc = f'../Output/{args.targname}_{args.band}/A0Fits/{night[:8]}A0_{beam}treated_{args.band}.fits'

        try:
            hdulist = fits.open(A0loc)
        except IOError:
            logger.warning(
                f'  --> No A0-fitted template for night {night}, skipping...')
            return wminibox, sminibox, flminibox_mod, flminibox_tel, flminibox_ste, contiminibox

        # Find corresponding table in fits file, given the tables do not go sequentially by order number due to multiprocessing in Step 1
        num_orders = 0
        for i in range(25):
            try:
                hdulist[i].columns[0].name[9:]
                num_orders += 1
            except:
                continue

        fits_layer = [
            i for i in np.arange(num_orders) + 1
            if np.int(hdulist[i].columns[0].name[9:]) == order
        ][0]

        tbdata = hdulist[fits_layer].data
        flag = np.array(tbdata[f'ERRORFLAG{order}'])[0]

        # Check whether Telfit hit critical error in Step 1 for the chosen order with this night. If so, skip.
        if flag == 1:
            logger.warning(
                f'  --> TELFIT ENCOUNTERED CRITICAL ERROR IN ORDER: {order} NIGHT: {night}, skipping...'
            )
            return wminibox, sminibox, flminibox_mod, flminibox_tel, flminibox_ste, contiminibox

        watm = tbdata['WATM' + str(order)]
        satm = tbdata['SATM' + str(order)]
        a0contx = tbdata['X' + str(order)]
        continuum = tbdata['BLAZE' + str(order)]

        # Remove extra rows leftover from having columns of unequal length
        satm = satm[(watm != 0)]
        watm = watm[(watm != 0)]
        satm[(
            satm < 1e-4
        )] = 0.  # set very low points to zero so that they don't go to NaN when taken to an exponent by template power in fmodel_chi
        a0contx = a0contx[(continuum != 0)]
        continuum = continuum[(continuum != 0)]

        # Retrieve pixel bounds for where within each other significant telluric absorption is present.
        # If these bounds were not applied, analyzing some orders would give garbage fits.
        if args.band == 'K':
            if int(order) in [3, 13, 14]:
                bound_cut = inparam.bound_cut_dic[args.band][order]
            else:
                bound_cut = [150, 150]

        elif args.band == 'H':
            if int(order) in [6, 10, 11, 13, 14, 16, 17, 20, 21, 22]:
                bound_cut = inparam.bound_cut_dic[args.band][order]
            else:
                bound_cut = [150, 150]

        # Load target spectrum
        x, wave, s, u = init_fitsread(f'{inparam.inpath}/{night}/{beam}/',
                                      'target', 'separate', night, order, tag,
                                      args.band, bound_cut)

        #-------------------------------------------------------------------------------

        # Execute S/N cut
        s2n = s / u
        if np.nanmedian(s2n) < np.float(args.SN_cut):
            logger.warning(
                '  --> Bad S/N {:1.3f} < {} for {}{} {}, SKIP'.format(
                    np.nanmedian(s2n), args.SN_cut, night, beam, tag))
            continue

        # Trim obvious outliers above the blaze (i.e. cosmic rays)
        nzones = 5
        x = basicclip_above(x, s, nzones)
        wave = basicclip_above(wave, s, nzones)
        u = basicclip_above(u, s, nzones)
        s = basicclip_above(s, s, nzones)
        x = basicclip_above(x, s, nzones)
        wave = basicclip_above(wave, s, nzones)
        u = basicclip_above(u, s, nzones)
        s = basicclip_above(s, s, nzones)

        # Cut spectrum to within wavelength regions defined in input list
        s_piece = s[(x > xbounds[0]) & (x < xbounds[-1])]
        u_piece = u[(x > xbounds[0]) & (x < xbounds[-1])]
        wave_piece = wave[(x > xbounds[0]) & (x < xbounds[-1])]
        x_piece = x[(x > xbounds[0]) & (x < xbounds[-1])]

        # Trim stellar template to relevant wavelength range
        mwave_in, mflux_in = stellarmodel_setup(wave_piece, inparam.mwave0,
                                                inparam.mflux0)

        # Trim telluric template to relevant wavelength range
        satm_in = satm[(watm > np.min(wave_piece) * 1e4 - 11)
                       & (watm < np.max(wave_piece) * 1e4 + 11)]
        watm_in = watm[(watm > np.min(wave_piece) * 1e4 - 11)
                       & (watm < np.max(wave_piece) * 1e4 + 11)]

        # Make sure data is within telluric template range (shouldn't do anything)
        s_piece = s_piece[(wave_piece * 1e4 > np.min(watm_in) + 5)
                          & (wave_piece * 1e4 < np.max(watm_in) - 5)]
        u_piece = u_piece[(wave_piece * 1e4 > np.min(watm_in) + 5)
                          & (wave_piece * 1e4 < np.max(watm_in) - 5)]
        x_piece = x_piece[(wave_piece * 1e4 > np.min(watm_in) + 5)
                          & (wave_piece * 1e4 < np.max(watm_in) - 5)]
        wave_piece = wave_piece[(wave_piece * 1e4 > np.min(watm_in) + 5)
                                & (wave_piece * 1e4 < np.max(watm_in) - 5)]

        # Normalize continuum from A0 to flux scale of data
        continuum /= np.nanmedian(continuum)
        continuum *= np.nanpercentile(s_piece, 99)

        # --------------------------------------------------------------

        par = pars0.copy()

        # Get initial guess for cubic wavelength solution from reduction pipeline
        f = np.polyfit(x_piece, wave_piece, 3)
        par9in = f[0] * 1e4
        par8in = f[1] * 1e4
        par7in = f[2] * 1e4
        par6in = f[3] * 1e4
        par[9] = par9in
        par[8] = par8in
        par[7] = par7in
        par[6] = par6in

        par[0] = initguesses - inparam.bvcs[
            night + tag]  # Initial RV with barycentric correction
        par[5] = IPpars[2]
        par[13] = IPpars[1]
        par[14] = IPpars[0]

        # Arrays defining parameter variations during optimization steps
        #                            | 0    1    2    3 |  | ------ 4 ------ |  | 5 |   | 6     7     8           9  |  |10  11  12| |13 14|  |15   16   17   18    19 |  |20   21   22   23 |
        dpars = {
            'cont':
            np.array([
                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1e7, 1, 1, 0,
                0, 10., 20., 0.2, 50.0, 0.2, 1.0, 1.0, 1.0, 1.0
            ]),
            'twave':
            np.array([
                0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 10.0, 10.0, 5.00000e-5, 1e-7, 0,
                0, 0, 0, 0, 0., 0., 0.0, 0., 0.0, 0.0, 0.0, 0.0, 0.0
            ]),
            'ip':
            np.array([
                0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0,
                0, 0., 0., 0.0, 0., 0.0, 0.0, 0.0, 0.0, 0.0
            ]),
            's':
            np.array([
                5.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0,
                0, 0., 0., 0.0, 0., 0.0, 0.0, 0.0, 0.0, 0.0
            ]),
            'v':
            np.array([
                0.0, 0.0, 0.0, 0.0, inparam.vsinivary, 0.0, 0.0, 0.0, 0.0, 0.0,
                0, 0, 0, 0, 0, 0., 0., 0.0, 0., 0.0, 0.0, 0.0, 0.0, 0.0
            ]),
            'ts':
            np.array([
                5.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0,
                0, 0., 0., 0.0, 0., 0.0, 0.0, 0.0, 0.0, 0.0
            ])
        }
        if masterbeam == 'B':
            dpars['cont'] = np.array([
                0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1e7, 1, 1, 0,
                0, 0., 0., 0.0, 0., 0.0, 1.0, 1.0, 1.0, 1.0
            ])

        # Use quadratic blaze correction for order 13; cubic for orders 6, 14, 21; quartic for orders 16 and 22
        if args.band == 'H':
            if np.int(order) in [13]:
                dpars['cont'][20] = 0.
                dpars['cont'][21] = 0.
                dpars['cont'][22] = 0.
                dpars['cont'][23] = 0.
            elif np.int(order) in [6, 14, 21]:
                dpars['cont'][21] = 0.
                dpars['cont'][22] = 0.
                dpars['cont'][23] = 0.
            else:
                pass
        else:
            if np.int(order) in [3]:
                dpars['cont'][20] = 0.
                dpars['cont'][21] = 0.
                dpars['cont'][22] = 0.
                dpars['cont'][23] = 0.
            elif np.int(order) in [4, 5]:
                dpars['cont'][21] = 0.
                dpars['cont'][22] = 0.
                dpars['cont'][23] = 0.
            elif np.int(order) in [6]:
                dpars['cont'][22] = 0.
                dpars['cont'][23] = 0.
            else:
                pass

        continuum_in = rebin_jv(a0contx, continuum, x_piece, False)
        fitobj = fitobjs(s_piece, x_piece, u_piece, continuum_in, watm_in,
                         satm_in, mflux_in, mwave_in,
                         ast.literal_eval(inparam.maskdict[order]), masterbeam,
                         np.array([], dtype=int))

        #-------------------------------------------------------------------------------

        # Initialize an array that puts hard bounds on vsini and the instrumental resolution to make sure they do not diverge to unphysical values
        optimize = True
        par_in = par.copy()
        if masterbeam == 'B':
            hardbounds = [
                par_in[4] - dpars['v'][4], par_in[4] + dpars['v'][4],
                par_in[5] - dpars['ip'][5], par_in[5] + dpars['ip'][5]
            ]
        else:
            hardbounds = [
                par_in[4] - dpars['v'][4], par_in[4] + dpars['v'][4],
                par_in[5] - dpars['ip'][5], par_in[5] + dpars['ip'][5],
                par_in[15] - dpars['cont'][15], par_in[15] + dpars['cont'][15],
                par_in[16] - dpars['cont'][16], par_in[16] + dpars['cont'][16],
                0., par_in[17] + dpars['cont'][17],
                par_in[18] - dpars['cont'][18], par_in[18] + dpars['cont'][18],
                0., par_in[19] + dpars['cont'][19]
            ]
        if hardbounds[0] < 0.5:
            hardbounds[0] = 0.5
        if hardbounds[2] < 1:
            hardbounds[2] = 1

        # Begin optimization. Fit the blaze, the wavelength solution, the telluric template power and RV, the stellar template power and RV, the
        # zero point for the instrumental resolution, and the vsini of the star separately, iterating and cycling between each set of parameter fits.

        cycles = 4

        optgroup = [
            'cont', 'twave', 'cont', 'ts', 'cont', 'twave', 's', 'cont',
            'twave', 'ip', 'v', 'ip', 'v', 'twave', 's', 'twave', 'ts'
        ]

        nk = 1
        for nc, cycle in enumerate(np.arange(cycles), start=1):
            if cycle == 0:
                parstart = par_in.copy()

            for optkind in optgroup:
                parfit_1 = optimizer(parstart, dpars[optkind], hardbounds,
                                     fitobj, optimize)
                parstart = parfit_1.copy()
                if args.debug == True:
                    outplotter_23(
                        parfit_1, fitobj,
                        '{}_{}_{}_parfit_{}{}'.format(order, night, tag, nk,
                                                      optkind), trk, inparam,
                        args, step2or3, order)
                    logger.debug(f'{order}_{tag}_{nk}_{optkind}:\n {parfit_1}')
                nk += 1

            ## After first cycle, use best fit model to identify CRs/hot pixels
            if nc == 1:
                parfit = parfit_1.copy()
                fit, chi = fmod(parfit, fitobj)

                # Everywhere where data protrudes high above model, check whether slope surrounding protrusion is /\ and mask if sufficiently steep
                residual = fitobj.s / fit
                MAD = np.median(np.abs(np.median(residual) - residual))
                CRmask = np.array(
                    np.where(residual > np.median(residual) + 2 * MAD)[0])

                CRmaskF = []
                CRmask = list(CRmask)

                for hit in [0, len(fitobj.x) - 1]:
                    if hit in CRmask:
                        CRmaskF.append(hit)
                        CRmask.remove(hit)
                CRmask = np.array(CRmask, dtype=np.int)
                CRmaskF = np.array(CRmaskF, dtype=np.int)

                for group in mit.consecutive_groups(CRmask):
                    group = np.array(list(group))
                    if len(group) == 1:
                        gL = group - 1
                        gR = group + 1
                    else:
                        peaks = detect_peaks(fitobj.s[group])
                        if len(peaks) < 1:
                            group = np.concatenate(
                                (np.array([group[0] - 1]), group,
                                 np.array([group[-1] + 1])))
                            peaks = detect_peaks(fitobj.s[group])
                            if len(peaks) < 1:
                                continue
                        if len(peaks) > 1:
                            continue
                        gL = group[:peaks[0]]
                        gR = group[peaks[0] + 1:]

                    slopeL = (fitobj.s[gL + 1] -
                              fitobj.s[gL]) / (fitobj.x[gL + 1] - fitobj.x[gL])
                    slopeR = (fitobj.s[gR] - fitobj.s[gR - 1]) / (
                        fitobj.x[gR] - fitobj.x[gR - 1])
                    try:
                        if (np.min(slopeL) > 300) and (
                                np.max(slopeR) < -300) and len(group) < 6:
                            CRmaskF = np.concatenate((CRmaskF, group))
                    except ValueError:
                        if (slopeL > 300) and (slopeR < -300):
                            CRmaskF = np.concatenate((CRmaskF, group))

                fitobj = fitobjs(s_piece, x_piece, u_piece, continuum_in,
                                 watm_in, satm_in, mflux_in, mwave_in,
                                 ast.literal_eval(inparam.maskdict[order]),
                                 masterbeam, CRmaskF)

        parfit = parfit_1.copy()

        #-------------------------------------------------------------------------------

        # if best fit stellar template power is very low, throw out result
        if parfit[1] < 0.1:
            logger.warning(f'  --> parfit[1] < 0.1, {night} parfit={parfit}')
            continue

        # if best fit stellar or telluric template powers are exactly equal to their starting values, fit failed, throw out result
        if parfit[1] == par_in[1] or parfit[3] == par_in[3]:
            logger.warning(
                f'  --> parfit[1] == par_in[1] or parfit[3] == par_in[3], {night}'
            )
            continue

        # if best fit model dips below zero at any point, we're to close to edge of blaze, fit may be comrpomised, throw out result
        smod, chisq = fmod(parfit, fitobj)
        if len(smod[(smod < 0)]) > 0:
            logger.warning(f'  --> len(smod[(smod < 0)]) > 0, {night}')
            continue

        #-------------------------------------------------------------------------------

        # Compute model and divide for residual
        fullmodel, chisq = fmod(parfit, fitobj)

        # Set both stellar and telluric template powers to 0 to compute only continuum
        parcont = parfit.copy()
        parcont[1] = 0.
        parcont[3] = 0.
        contmodel, chisq = fmod(parcont, fitobj)

        # Set stellar tempalte power to 0 to compute only telluric, and vice versa
        parS = parfit.copy()
        parT = parfit.copy()
        parT[1] = 0.
        parS[3] = 0.
        stellmodel, chisq = fmod(parS, fitobj)
        tellmodel, chisq = fmod(parT, fitobj)

        # Divide everything by continuum model except residual
        dataflat = fitobj.s / contmodel
        modelflat = fullmodel / contmodel
        stellflat = stellmodel / contmodel
        tellflat = tellmodel / contmodel

    w = parfit[6] + parfit[7] * fitobj.x + parfit[8] * (
        fitobj.x**2.) + parfit[9] * (fitobj.x**3.)

    wminibox[:len(w)] = w
    sminibox[:len(w)] = dataflat
    flminibox_mod[:len(w)] = modelflat
    flminibox_tel[:len(w)] = tellflat
    flminibox_ste[:len(w)] = stellflat
    contiminibox[:len(w)] = contmodel
    # residualbox[:len(w)]     = residual

    # Save results in fits file
    c1 = fits.Column(name='wavelength', array=wminibox, format='D')
    c2 = fits.Column(name='s', array=sminibox, format='D')
    c3 = fits.Column(name='model_fl', array=flminibox_mod, format='D')
    c4 = fits.Column(name='tel_fl', array=flminibox_tel, format='D')
    c5 = fits.Column(name='ste_fl', array=flminibox_ste, format='D')
    c6 = fits.Column(name='conti_fl', array=contiminibox, format='D')

    cols = fits.ColDefs([c1, c2, c3, c4, c5, c6])
    hdu_1 = fits.BinTableHDU.from_columns(cols)

    if order == firstorder:  # If first time writing fits file, make up filler primary hdu
        bleh = np.ones((3, 3))
        primary_hdu1 = fits.PrimaryHDU(bleh)
        hdul = fits.HDUList([primary_hdu1, hdu_1])
        hdul.writeto(inparam.outpath + '/' + name +
                     '/RVresultsRawBox_fit_wl_{}_{}_{}_{}.fits'.format(
                         args.targname, args.band, night, tag))
    else:
        hh = fits.open(inparam.outpath + '/' + name +
                       '/RVresultsRawBox_fit_wl_{}_{}_{}_{}.fits'.format(
                           args.targname, args.band, night, tag))
        hh.append(hdu_1)
        hh.writeto(inparam.outpath + '/' + name +
                   '/RVresultsRawBox_fit_wl_{}_{}_{}_{}.fits'.format(
                       args.targname, args.band, night, tag),
                   overwrite=True)

    return wminibox, sminibox, flminibox_mod, flminibox_tel, flminibox_ste, contiminibox
def MPinst(i, order0, order):
    #    nights = inparam.nights
    nights = i[0]
    print('Working on {} band, order {}/{}, night {} ...'.format(
        args.band, order, len(order0), i[0]))
    night = str(nights)

    if int(night[:8]) < 20180401 or int(night[:8]) > 20190531:
        IPpars = inparam.ips_tightmount_pars[args.band][order]
    else:
        IPpars = inparam.ips_loosemount_pars[args.band][order]
    ### Load relevant A0 spectrum
    # x (list of wavelength used position)
    if args.band == 'K':
        if order == 11:
            bound_cut = [200, 100]
        elif order == 12:
            bound_cut = [900, 300]
        elif order == 13:
            bound_cut = [200, 400]
        elif order == 14:
            bound_cut = [150, 300]
        else:
            bound_cut = [150, 100]
    elif args.band == 'H':
        if order == 10:
            bound_cut = [250, 150]  #ok
        elif order == 11:
            bound_cut = [600, 150]
        elif order == 13:
            bound_cut = [200, 600]  #ok
        elif order == 14:
            bound_cut = [700, 100]
        elif order == 16:
            bound_cut = [400, 100]
        elif order == 17:
            bound_cut = [1000, 100]
        elif order == 20:
            bound_cut = [500, 150]
        elif (order == 7) or (order == 8) or (order == 9) or (order == 12) or (
                order == 15) or (order == 18) or (order == 19):
            bound_cut = [500, 500]
        else:
            bound_cut = [150, 100]
    x, a0wavelist, a0fluxlist, u = init_fitsread(
        inparam.inpath, 'A0', 'separate', night, order,
        '{:04d}'.format(int(inparam.tags[night])), args.band, bound_cut)

    nzones = 12
    a0wavelist = basicclip_above(a0wavelist, a0fluxlist, nzones)
    a0x = basicclip_above(x, a0fluxlist, nzones)
    a0u = basicclip_above(u, a0fluxlist, nzones)
    a0fluxlist = basicclip_above(a0fluxlist, a0fluxlist, nzones)

    # do twice?
    a0wavelist = basicclip_above(a0wavelist, a0fluxlist, nzones)
    a0x = basicclip_above(a0x, a0fluxlist, nzones)
    a0u = basicclip_above(a0u, a0fluxlist, nzones)
    a0fluxlist = basicclip_above(a0fluxlist, a0fluxlist, nzones)

    # Normalize
    a0fluxlist /= np.median(a0fluxlist)

    # Compute rough blaze fn estimate
    continuum = A0cont(a0wavelist, a0fluxlist, night, order)
    a0contwave = a0wavelist.copy()
    a0masterwave = a0wavelist.copy()
    a0masterwave *= 1e4

    # Trim stellar template to relevant wavelength range
    mwave_in, mflux_in = stellarmodel_setup(a0wavelist, inparam.mwave0,
                                            inparam.mflux0)

    # Trim telluric template to relevant wavelength range
    satm_in = inparam.satm[(inparam.watm > min(a0wavelist) * 1e4 - 11)
                           & (inparam.watm < max(a0wavelist) * 1e4 + 11)]
    watm_in = inparam.watm[(inparam.watm > min(a0wavelist) * 1e4 - 11)
                           & (inparam.watm < max(a0wavelist) * 1e4 + 11)]

    ### Initialize parameter array for optimization as well as half-range values for each parameter during
    ### the various steps of the optimization.
    ### Many of the parameters initialized here will be changed throughout the code before optimization and
    ### in between optimization steps.
    pars0 = np.array([
        np.nan,  # 0: The shift of the sunspot spectrum (km/s)
        1.0,  # 1: The scale factor for the sunspot spectrum
        0.0,  # 2: The shift of the telluric spectrum (km/s)
        1.0,  # 3: The scale factor for the telluric spectrum
        0.0,  # 4: vsini (km/s)
        IPpars[2],  # 5: The instrumental resolution (FWHM) in pixels
        2.29315012e+04,  # 6: Wavelength 0-pt
        1.75281163e-01,  # 7: Wavelength linear component
        -9.92637874e-06,  # 8: Wavelength quadratic component
        0,  # 9: Wavelength cubic component
        1.0,  #10: Continuum zero point
        0.,  #11: Continuum linear component
        0.,  #12: Continuum quadratic component
        IPpars[1],  #13: IP linear component
        IPpars[0],  #14: IP quadratic component
        0.0
    ])  #15: Differential Rotation Coefficient

    # Save a copy of initial parameter array. Make sure stellar template isn't being used.
    parA0 = pars0.copy()
    parA0[0] = 0.
    parA0[1] = 0.

    # Cut target spectrum to be within telluric template wavelengths (should be unncessary)
    a0fluxlist = a0fluxlist[(a0wavelist * 1e4 > min(watm_in) + 5)
                            & (a0wavelist * 1e4 < max(watm_in) - 5)]
    a0u = a0u[(a0wavelist * 1e4 > min(watm_in) + 5)
              & (a0wavelist * 1e4 < max(watm_in) - 5)]
    a0x = a0x[(a0wavelist * 1e4 > min(watm_in) + 5)
              & (a0wavelist * 1e4 < max(watm_in) - 5)]
    a0wavelist = a0wavelist[(a0wavelist * 1e4 > min(watm_in) + 5)
                            & (a0wavelist * 1e4 < max(watm_in) - 5)]

    ##Set initial wavelength guess. If order not in initwavedict, fit cubic polynomial to wavelengths given in file
    # 6: Wavelength 0-pt
    # 7: Wavelength linear component
    # 8: Wavelength quadratic component
    # 9: Wavelength cubic component

    f = np.polyfit(a0x, a0wavelist, 3)
    parA0[9] = f[0] * 1e4
    parA0[8] = f[1] * 1e4
    parA0[7] = f[2] * 1e4
    parA0[6] = f[3] * 1e4

    # Define main spectrum parameters
    s = a0fluxlist.copy()
    x = a0x.copy()
    u = a0u.copy()

    # Collect all fit variables into one class
    #global fitobj, optimize;
    fitobj = fitobjs(s, x, u, continuum, watm_in, satm_in, mflux_in, mwave_in)

    # Arrays defining parameter variations during optimization steps
    dpar_cont = np.array(
        [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0., 1e7, 1, 1, 0, 0, 0])
    dpar_wave = np.array([
        0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 10.0, 5.00000e-5, 1e-7, 0, 0, 0, 0,
        0, 0
    ])
    dpar = np.array(
        [0.0, 0.0, 5.0, 3.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0, 1e4, 1, 1, 0, 0, 0])
    dpar_st = np.array(
        [0.0, 0.0, 5.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 1e4, 1, 1, 0, 0, 0])
    dpar_ip = np.array(
        [0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0, 0])

    #-------------------------------------------------------------------------------
    # For every pre-Telfit spectral fit, first fit just template strength/rv/continuum, then just wavelength soln, then template/continuum again, then ip,
    # then finally wavelength. Normally would fit for all but wavelength at the end, but there's no need for the pre-Telfit fit, since all we want
    # is the wavelength solution.

    optimize = True
    par_in = parA0.copy()
    hardbounds = [
        par_in[4] - dpar[4], par_in[4] + dpar[4], par_in[5] - dpar[5],
        par_in[5] + dpar[5], par_in[15] - dpar[15], par_in[15] + dpar[15]
    ]
    if hardbounds[0] < 0:
        hardbounds[0] = 0
    if hardbounds[3] < 0:
        hardbounds[3] = 1


#        if args.plotfigs == True:#
#            outplotter(targname,par_in,fitobj,'{}_{}_{}_1'.format(label,night,tag))

    parfit_1 = optimizer(par_in, dpar_st, hardbounds, fitobj, optimize)
    parfit_2 = optimizer(parfit_1, dpar_wave, hardbounds, fitobj, optimize)
    parfit_3 = optimizer(parfit_2, dpar_st, hardbounds, fitobj, optimize)
    parfit_4 = optimizer(parfit_3, dpar, hardbounds, fitobj, optimize)
    parfit = optimizer(parfit_4, dpar_wave, hardbounds, fitobj, optimize)

    # if inparam.plotfigs == True:
    #     outplotter(parfit, fitobj, '{}_{}_1'.format(label,night), 0)
    #-------------------------------------------------------------------------------
    # Get fitted wavelength solution
    a0w_out_fit = parfit[6] + parfit[7] * x + parfit[8] * (
        x**2.) + parfit[9] * (x**3.)

    # Trim stellar template to relevant wavelength range
    mwave_in, mflux_in = stellarmodel_setup(a0w_out_fit / 1e4, inparam.mwave0,
                                            inparam.mflux0)

    # Using this new wavelength solution, get Telfit'd telluric template, parameters of that best fit, and blaze fn best fit
    watm1, satm1, telfitparnames, telfitpars, a0contwave, continuum = telfitter(
        a0w_out_fit, a0fluxlist, a0u, inparam, night, order, args)
    # watm1, satm1 from Telfit, fack one.

    if (args.band == 'H') & (
        (order == 7) | (order == 8) | (order == 9) | (order == 12) |
        (order == 15) | (order == 18) | (order == 19)
    ):  # If Telfit encountered error mentioned in Telfitter.py, skip night/order combo
        # Write out table to fits header with errorflag = 1
        c0 = fits.Column(name='ERRORFLAG' + str(order),
                         array=np.array([1]),
                         format='K')
        cols = fits.ColDefs([c0])
        hdu_1 = fits.BinTableHDU.from_columns(cols)
        #        if order == order0[0]: # If first time writing fits file, make up filler primary hdu
        bleh = np.ones((3, 3))
        primary_hdu = fits.PrimaryHDU(bleh)
        hdul = fits.HDUList([primary_hdu, hdu_1])
        hdul.writeto('{}/{}A0_treated_{}_order{}.fits'.format(
            inparam.outpath, night, args.band, order),
                     overwrite=True)

    elif len(
            watm1
    ) == 1:  # If Telfit encountered error mentioned in Telfitter.py, skip night/order combo

        print('TELFIT ENCOUNTERED CRITICAL ERROR, ORDER ' + str(order) +
              ' NIGHT ' + str(night))

        # Write out table to fits header with errorflag = 1
        c0 = fits.Column(name='ERRORFLAG' + str(order),
                         array=np.array([1]),
                         format='K')
        cols = fits.ColDefs([c0])
        hdu_1 = fits.BinTableHDU.from_columns(cols)
        #        if order == order0[0]: # If first time writing fits file, make up filler primary hdu
        bleh = np.ones((3, 3))
        primary_hdu = fits.PrimaryHDU(bleh)
        hdul = fits.HDUList([primary_hdu, hdu_1])
        hdul.writeto('{}/{}A0_treated_{}_order{}.fits'.format(
            inparam.outpath, night, args.band, order),
                     overwrite=True)

    else:

        a0contwave /= 1e4
        continuum = rebin_jv(a0contwave, continuum, a0wavelist, False)

        # Fit whole A0 again to get even better wave soln to use for a0contwave and tweak blaze fn fit as
        # needed with quadratic adjustment
        fitobj = fitobjs(s, x, u, continuum, watm1, satm1, mflux_in, mwave_in)

        parfit_1 = optimizer(par_in, dpar_st, hardbounds, fitobj, optimize)
        parfit_2 = optimizer(parfit_1, dpar_wave, hardbounds, fitobj, optimize)
        parfit_3 = optimizer(parfit_2, dpar_st, hardbounds, fitobj, optimize)
        parfit_4 = optimizer(parfit_3, dpar_wave, hardbounds, fitobj, optimize)
        parfit = optimizer(parfit_4, dpar, hardbounds, fitobj, optimize)

        if inparam.plotfigs == True:
            fig, axes = plt.subplots(1,
                                     1,
                                     figsize=(5, 3),
                                     facecolor='white',
                                     dpi=300)
            axes.plot(fitobj.x,
                      parfit[5] + parfit[13] * (fitobj.x) + parfit[14] *
                      (fitobj.x**2),
                      '-k',
                      lw=0.5,
                      label='data',
                      alpha=.6)

            axes.tick_params(axis='both',
                             labelsize=4.5,
                             right=True,
                             top=True,
                             direction='in')
            axes.set_ylabel(r'Normalized Flux',
                            size=5,
                            style='normal',
                            family='sans-serif')
            axes.set_xlabel('Wavelength',
                            size=5,
                            style='normal',
                            family='sans-serif')
            axes.legend(fontsize=4, edgecolor='white')
            fig.savefig('{}/figs_{}/IP_{}_{}.png'.format(
                inparam.outpath, args.band, order, night),
                        bbox_inches='tight',
                        format='png',
                        overwrite=True)

            outplotter(parfit, fitobj,
                       'Post_parfit_{}_{}'.format(order, night))

        a0w_out = parfit[6] + parfit[7] * x + parfit[8] * (
            x**2.) + parfit[9] * (x**3.)
        cont_adj = parfit[10] + parfit[11] * x + parfit[12] * (x**2.)

        c2 = rebin_jv(a0contwave * 1e4, continuum, a0w_out, False)
        c2 /= np.median(c2)
        cont_save = c2 * cont_adj

        # Write out table to fits file with errorflag = 0
        c0 = fits.Column(name='ERRORFLAG' + str(order),
                         array=np.array([0]),
                         format='K')
        cc = fits.Column(name='WAVE_pretel' + str(order),
                         array=a0w_out_fit,
                         format='D')
        c1 = fits.Column(name='WAVE' + str(order), array=a0w_out, format='D')
        c2 = fits.Column(name='BLAZE' + str(order),
                         array=cont_save,
                         format='D')
        c3 = fits.Column(name='X' + str(order), array=a0x, format='D')
        c4 = fits.Column(name='INTENS' + str(order),
                         array=a0fluxlist,
                         format='D')
        c5 = fits.Column(name='SIGMA' + str(order), array=a0u, format='D')
        c6 = fits.Column(name='WATM' + str(order), array=watm1, format='D')
        c7 = fits.Column(name='SATM' + str(order), array=satm1, format='D')
        c8 = fits.Column(name='TELFITPARNAMES' + str(order),
                         array=np.array(telfitparnames),
                         format='8A')
        c9 = fits.Column(name='TELFITPARS' + str(order),
                         array=telfitpars,
                         format='D')
        cols = fits.ColDefs([c0, cc, c1, c2, c3, c4, c5, c6, c7, c8, c9])
        hdu_1 = fits.BinTableHDU.from_columns(cols)

        #        if order == 1: # If first time writing fits file, make up filler primary hdu
        bleh = np.ones((3, 3))
        primary_hdu = fits.PrimaryHDU(bleh)
        hdul = fits.HDUList([primary_hdu, hdu_1])
        # hdul.writeto(inparam.outpath+'/'+night+'A0_treated_{}_order{}.fits'.format(args.band, order),overwrite=True)
        hdul.writeto('{}/A0_Fits/{}A0_treated_{}_order{}.fits'.format(
            inparam.outpath, night, args.band, order),
                     overwrite=True)
Esempio n. 7
0
def IPval(tar, band, args):

    inparam = inparamsA0(None, None, None, None, None, None, None, None, None,
                         None, None, None, None, None, None, None, None)
    xbounddict = inparam.bound_cut_dic[band]

    for a in range(27):
        try:
            xbounddict[a]
        except KeyError:
            xbounddict[a] = np.array([150, 150])

    TdirsA = np.array([])
    TdirsB = np.array([])
    LdirsA = np.array([])
    LdirsB = np.array([])
    for tt in tars:
        filesndirs = os.listdir('../Output/{}_tool/A0Fits_IP'.format(tt))

        filesndirs_A = [
            j for j in filesndirs if j[-15:] == f'Atreated_{band}.fits'
        ]
        filesndirs_B = [
            j for j in filesndirs if j[-15:] == f'Btreated_{band}.fits'
        ]

        nights = np.array([int(j[:8]) for j in filesndirs_A])
        nightsT = np.where((nights < 20180401) | (nights > 20190531))
        nightsL = np.where((nights >= 20180401) & (nights < 20190531))

        TdirsA = np.append(TdirsA, [
            '../Output/{}_tool/A0Fits_IP/{}A0_Atreated_{}.fits'.format(
                tt, nn, band) for nn in nights[nightsT]
        ])
        TdirsB = np.append(TdirsB, [
            '../Output/{}_tool/A0Fits_IP/{}A0_Btreated_{}.fits'.format(
                tt, nn, band) for nn in nights[nightsT]
        ])

        LdirsA = np.append(LdirsA, [
            '../Output/{}_tool/A0Fits_IP/{}A0_Atreated_{}.fits'.format(
                tt, nn, band) for nn in nights[nightsL]
        ])
        LdirsB = np.append(LdirsB, [
            '../Output/{}_tool/A0Fits_IP/{}A0_Btreated_{}.fits'.format(
                tt, nn, band) for nn in nights[nightsL]
        ])

        print(f'We have Tight nights with {tt}: {nights[nightsT]}')
        print(f'We have Loose nights with {tt}: {nights[nightsL]}')

    print(
        f'Total nights used for normal = {len(TdirsA)}, loose = {len(LdirsA)}')

    filew = open('./Tool_output/IP_{}.txt'.format(band), 'w')

    if len(TdirsA) != 0:
        for Tdirs, nodd in zip([TdirsA, TdirsB],
                               ['A', 'B']):  # loop throught A B nodding

            ipmaster = {}

            for a0 in Tdirs:
                hdulist = fits.open(a0)

                tt = 1
                orders = []
                while 1 == 1:
                    try:
                        orders.append(int(hdulist[tt].columns[1].name[4:]))
                        tt += 1
                    except:
                        break

                for o in np.arange(len(orders)):
                    tbdata = hdulist[o + 1].data
                    x = np.array(tbdata['X{}'.format(orders[o])])
                    w = np.array(tbdata['WAVE{}'.format(orders[o])])
                    parfit = np.array(tbdata['PARFIT'])
                    x = x[(w != 0)]
                    ip = parfit[5] + parfit[13] * x + parfit[14] * (x**2)
                    xorder = np.arange(xbounddict[orders[o]][0],
                                       2048 - xbounddict[orders[o]][1])
                    ip1 = rebin_jv(x, ip, xorder, False)
                    try:
                        ipmaster[orders[o]] = np.vstack(
                            (ipmaster[orders[o]], ip1))
                    except KeyError:
                        ipmaster[orders[o]] = ip1

            filew.write(f'Tight {nodd}\n')
            for order in list(sorted(ipmaster.keys())):
                xorder = np.arange(xbounddict[order][0],
                                   2048 - xbounddict[order][1])

                fig, axes = plt.subplots(1,
                                         1,
                                         figsize=(12, 12),
                                         facecolor='white')
                for i in range(len(ipmaster[order][:, 0])):
                    axes.plot(xorder,
                              ipmaster[order][i, :],
                              alpha=0.5,
                              color='black')
                ipmedian = [
                    np.median(ipmaster[order][:, i])
                    for i in range(len(ipmaster[order][0, :]))
                ]
                axes.plot(xorder, ipmedian, alpha=0.75, color='red')

                if order == 3 and band == 'K':
                    f = np.polyfit(xorder, ipmedian, 1)
                else:
                    f = np.polyfit(xorder, ipmedian, 2)
                q = np.poly1d(f)

                axes.plot(xorder, q(xorder), alpha=0.75, color='blue')
                fig.savefig('./Tool_output/Tight_{}_IPs_{}_{}.png'.format(
                    nodd, order, band))

                filew.write(
                    '{}: np.array([{:+1.10f}, {:+1.10f}, {:+1.10f}]),\n'.
                    format(order, q[2], q[1], q[0]))

    if len(LdirsA) != 0:
        for Ldirs, nodd in zip([LdirsA, LdirsB],
                               ['A', 'B']):  # loop throught A B nodding

            ipmaster = {}

            for a0 in Ldirs:

                hdulist = fits.open(a0)

                tt = 1
                orders = []
                while 1 == 1:
                    try:
                        orders.append(int(hdulist[tt].columns[1].name[4:]))
                        tt += 1
                    except:
                        break

                for o in np.arange(len(orders)):
                    tbdata = hdulist[o + 1].data
                    x = np.array(tbdata['X{}'.format(orders[o])])
                    w = np.array(tbdata['WAVE{}'.format(orders[o])])
                    parfit = np.array(tbdata['PARFIT'])
                    x = x[(w != 0)]
                    ip = parfit[5] + parfit[13] * x + parfit[14] * (x**2)
                    xorder = np.arange(xbounddict[orders[o]][0],
                                       2048 - xbounddict[orders[o]][1])
                    ip1 = rebin_jv(x, ip, xorder, False)
                    try:
                        ipmaster[orders[o]] = np.vstack(
                            (ipmaster[orders[o]], ip1))
                    except KeyError:
                        ipmaster[orders[o]] = ip1

            filew.write(f'Loose {nodd}\n')
            for order in list(sorted(ipmaster.keys())):
                xorder = np.arange(xbounddict[order][0],
                                   2048 - xbounddict[order][1])

                fig, axes = plt.subplots(1,
                                         1,
                                         figsize=(12, 12),
                                         facecolor='white')
                for i in range(len(ipmaster[order][:, 0])):
                    axes.plot(xorder,
                              ipmaster[order][i, :],
                              alpha=0.5,
                              color='black')
                ipmedian = [
                    np.median(ipmaster[order][:, i])
                    for i in range(len(ipmaster[order][0, :]))
                ]
                axes.plot(xorder, ipmedian, alpha=0.75, color='red')

                if order == 3 and band == 'K':
                    f = np.polyfit(xorder, ipmedian, 1)
                else:
                    f = np.polyfit(xorder, ipmedian, 2)
                q = np.poly1d(f)

                axes.plot(xorder, q(xorder), alpha=0.75, color='blue')
                fig.savefig('./Tool_output/Loose_{}_IPs_{}_{}.png'.format(
                    nodd, order, band))

                filew.write(
                    '{}: np.array([{:+1.10f}, {:+1.10f}, {:+1.10f}]),\n'.
                    format(order, q[2], q[1], q[0]))

    filew.close()
Esempio n. 8
0
def fmod_conti(par,fitobj):
    '''
    Same as fmod(), but provides best fit continuum model. For use in plotting.
    '''

    watm = fitobj.watm_in;
    satm = fitobj.satm_in;
    mwave = fitobj.mwave_in;
    mflux = fitobj.mflux_in;

    w = par[6] + par[7]*fitobj.x + par[8]*(fitobj.x**2.) + par[9]*(fitobj.x**3.)

    if w[-1] < w[0]:
        sys.exit('WAVE ERROR 1 {}'.format(par[6:10]))
        return 1e10

    c = 2.99792458e5
    npts = len(w)

    wspot = mwave*(1.+par[0]/c)
    sspot = mflux**par[1]
    watm = watm*(1.+par[2]/c)
    satm = satm**par[3]

    if (w[0] < watm[0]) or (w[-1] > watm[-1]):
        return 1e10

    vsini = par[4]

    # Rotationally broaden stellar template
    if vsini != 0:
        wspot2,rspot2 = rotint(wspot,sspot,vsini)
    else:
        wspot2 = wspot
        rspot2 = sspot

    sspot2 = rebin_jv(wspot2,rspot2,watm,False)

    smod = sspot2*satm

    #Find mean observed wavelength and create a telluric velocity scale
    mnw = np.mean(w)
    dw = (w[-1] - w[0])/(npts-1.)
    vel = (watm-mnw)/mnw*c

    fwhmraw = par[5] + par[13]*(fitobj.x) + par[14]*(fitobj.x**2)
    if np.min(fwhmraw) < 1 or np.max(fwhmraw) > 7:
        sys.exit('IP ERROR 1 {} {} {} {} {}'.format(par[5],par[13],par[14],np.min(fwhmraw),np.max(fwhmraw) ))
        return 1e10
    try:
        spl = splrep(w,fwhmraw)
    except ValueError:
        sys.exit('IP ERROR 2 {} {} {}'.format(par[5],par[13],par[14]))
        return 1e10
    fwhm = splev(watm,spl)

    vhwhm = dw*np.abs(fwhm)/mnw*c/2.
    nsmod = macbro_dyn(vel,smod,vhwhm)

    #Rebin model to observed wavelength scale
    smod = rebin_jv(watm,nsmod,w,False)

    # Load saved continuum
    c2 = fitobj.continuum
    smod *= c2

    # Apply continuum adjustment
    cont = par[10] + par[11]*fitobj.x+ par[12]*(fitobj.x**2) + par[20]*(fitobj.x**3) + par[21]*(fitobj.x**4) + par[22]*(fitobj.x**5) + par[23]*(fitobj.x**6)
    if fitobj.masterbeam == 'A':
        bucket = np.zeros_like(cont)
        bucket[(fitobj.x >= (par[15]-par[16]/2)) & (fitobj.x <= (par[15]+par[16]/2))] = par[17]
        bucket[(fitobj.x >= (par[15]+par[16]/2-par[18])) & (fitobj.x <= (par[15]+par[16]/2))] += par[19]
        cont -= bucket
    smod *= cont

    mask = np.ones_like(smod,dtype=bool)
    mask[(fitobj.s < .05)] = False

    return w, smod, cont, c2
Esempio n. 9
0
def fmod(par,fitobj):
    '''
    Same as fmodel_chi(), but meant to provide best fit model, not for optimization. Always returns both smod and chisq.
    '''

    watm = fitobj.watm_in;
    satm = fitobj.satm_in;
    mwave = fitobj.mwave_in;
    mflux = fitobj.mflux_in;

    w = par[6] + par[7]*fitobj.x + par[8]*(fitobj.x**2.) + par[9]*(fitobj.x**3.)

    if w[-1] < w[0]:
        sys.exit('WAVE ERROR 1 {}'.format(par[6:10]))
        return 1e10

    c = 2.99792458e5
    npts = len(w)

    wspot = mwave*(1.+par[0]/c)
    sspot = mflux**par[1]
    watm = watm*(1.+par[2]/c)
    satm = satm**par[3]

    if (w[0] < watm[0]) or (w[-1] > watm[-1]):
        sys.exit('WAVE ERROR 2 {} {} {} {} {}'.format(par[6:10],watm[0],watm[-1],w[0],w[-1]))
        return 1e10

    vsini = par[4]

    # Rotationally broaden stellar template
    if vsini >= 0.5:
        wspot2,rspot2 = rotint(wspot,sspot,vsini)
    else:
        wspot2 = wspot
        rspot2 = sspot

    sspot2 = rebin_jv(wspot2,rspot2,watm,False)

    smod = sspot2*satm

    #Find mean observed wavelength and create a telluric velocity scale
    mnw = np.mean(w)
    dw = (w[-1] - w[0])/(npts-1.)
    vel = (watm-mnw)/mnw*c

    fwhmraw = par[5] + par[13]*(fitobj.x) + par[14]*(fitobj.x**2)
    if np.round(np.min(fwhmraw),5) < 1 or np.round(np.max(fwhmraw),5) > 7:
        sys.exit('IP ERROR 1 {} {} {} {} {}'.format(par[5],par[13],par[14],np.min(fwhmraw),np.max(fwhmraw) ))
        return 1e10
    try:
        spl = splrep(w,fwhmraw)
    except ValueError:
        sys.exit('IP ERROR 2 {} {} {}'.format(par[5],par[13],par[14]))
        return 1e10

    fwhm = splev(watm,spl)

    vhwhm = dw*np.abs(fwhm)/mnw*c/2.
    nsmod = macbro_dyn(vel,smod,vhwhm)

    #Rebin model to observed wavelength scale
    smod = rebin_jv(watm,nsmod,w,False)

    # Load saved continuum
    c2 = fitobj.continuum
    smod *= c2#/np.median(c2)

    # Apply continuum adjustment
    cont = par[10] + par[11]*fitobj.x+ par[12]*(fitobj.x**2) + par[20]*(fitobj.x**3) + par[21]*(fitobj.x**4) + par[22]*(fitobj.x**5) + par[23]*(fitobj.x**6)
    if fitobj.masterbeam == 'A':
        bucket = np.zeros_like(cont)
        bucket[(fitobj.x >= (par[15]-par[16]/2)) & (fitobj.x <= (par[15]+par[16]/2))] = par[17]
        bucket[(fitobj.x >= (par[15]+par[16]/2-par[18])) & (fitobj.x <= (par[15]+par[16]/2))] += par[19]
        cont -= bucket
    smod *= cont

    mask = np.ones_like(smod,dtype=bool)
    mask[(fitobj.s < .0)] = False

    if len(fitobj.mask) != 0:
        for maskbounds in fitobj.mask:
            mask[(fitobj.x > maskbounds[0]) & (fitobj.x < maskbounds[1]) ] = False

    mask[fitobj.CRmask] = False

    chisq = np.sum((fitobj.s[mask] - smod[mask])**2. / fitobj.u[mask]**2.)
    chisq = chisq / (len(smod[mask]) - len(par))

    return smod,chisq
Esempio n. 10
0
def fmodel_chi(par,grad):
    '''
    Function to be optimized. Computes model spectrum and compares it with data to calculate reduced chisq.

    INPUTS:
       w - The observed wavelength scale (air) in Angstroms.
       x - The array of pixel indices from 0 to npts-1
       par - The model parameters:
          0: The shift of the sunspot spectrum (km/s)
          1: The scale factor for the sunspot spectrum
          2: The shift of the telluric spectrum (km/s)
          3: The scale factor for the telluric spectrum
          4: vsini (km/s)
          5: The instrumental resolution (FWHM) in pixels
          6: Wavelength 0-pt
          7: Wavelength linear component
          8: Wavelength quadratic component
          9: Wavelength cubic component
          10: Continuum zero point
          11: Continuum linear component
          12: Continuum quadratic component
          13: IP linear component
          14: IP quadratic component
          15: Blaze dip center location        \
          16: Blaze dip full width              |
          17: Blaze dip depth                   | <-- If beam is A
          18: Secondary blaze dip full width    |
          19: Blaze dip depth                  /
          20: Continuum cubic component      \
          21: Continuum quartic component     |  <-- Only enabled for some orders, depending on size of region being fit
          22: Continuum pentic component      |
          23: Continuum hexic component      /

     OUTPUTS:
       chisq: Reduced chisq
       (If global optimize_cp is False) smod: The model spectrum on the observed wavelength scale.
    '''

    # Bring in global class of variables needed to generate model.
    # Can't call these directly in function, as NLopt doesn't allow anything to be in the model function call besides par and grad.

    #global fitobj, optimize
    global fitobj_cp, optimize_cp

    watm = fitobj_cp.watm_in;
    satm = fitobj_cp.satm_in;
    mwave = fitobj_cp.mwave_in;
    mflux = fitobj_cp.mflux_in;

    #Make the wavelength scale
    w = par[6] + par[7]*fitobj_cp.x + par[8]*(fitobj_cp.x**2.) + par[9]*(fitobj_cp.x**3.)

    if w[-1] < w[0]:
        # print(f'{nc_cp}, {nk_cp}, {optkind_cp}: Hitting negative wavelength solution for some reason !')
        return 1e10

    # Define the speed of light in km/s and other useful quantities
    c = 2.99792458e5
    npts = len(w)

    # Apply velocity shifts and scale
    wspot = mwave*(1.+par[0]/c)
    sspot = mflux**par[1]
    watm = watm*(1.+par[2]/c)
    satm = satm**par[3]

    #Verify that new wavelength scale is a subset of old wavelength scale.
    if (w[0] < watm[0]) or (w[-1] > watm[-1]):
        # print(f'{nc_cp}, {nk_cp}, {optkind_cp}: w not subset of watm, w goes from '+str(w[0])+' to '+str(w[-1])+' and watm goes from '+str(watm[0])+' to '+str(watm[-1]))
        return 1e10

    vsini = par[4]

    # Rotationally broaden stellar template
    if vsini >= 0.5:
        wspot2,rspot2 = rotint(wspot,sspot,vsini)
    else:
        wspot2 = wspot
        rspot2 = sspot

    #Now rebin the spot spectrum onto the telluric wavelength scale
    sspot2 = rebin_jv(wspot2,rspot2,watm,False)

    #Mutliply rotationally broadened spot by telluric to create total spectrum
    smod = sspot2*satm

    #Find mean observed wavelength and create a telluric velocity scale
    mnw = np.mean(w)
    dw = (w[-1] - w[0])/(npts-1.)
    vel = (watm-mnw)/mnw*c

    fwhmraw = par[5] + par[13]*(fitobj_cp.x) + par[14]*(fitobj_cp.x**2)
    try:
        spl = splrep(w,fwhmraw)
    except:
        return 1e10
    fwhm = splev(watm,spl)
    if (np.min(fwhm) < 1) or (np.max(fwhm) > 7):
        return 1e10

    #Handle instrumental broadening
    vhwhm = dw*np.abs(fwhm)/mnw*c/2.
    nsmod = macbro_dyn(vel,smod,vhwhm)

    #Rebin model to observed wavelength scale
    smod = rebin_jv(watm,nsmod,w,False)

    # Load saved continuum
    c2 = fitobj_cp.continuum
    smod *= c2

    # Apply continuum adjustment
    cont = par[10] + par[11]*fitobj_cp.x+ par[12]*(fitobj_cp.x**2) + par[20]*(fitobj_cp.x**3) + par[21]*(fitobj_cp.x**4) + par[22]*(fitobj_cp.x**5) + par[23]*(fitobj_cp.x**6)
    if fitobj_cp.masterbeam == 'A':
        bucket = np.zeros_like(cont)
        bucket[(fitobj_cp.x >= (par[15]-par[16]/2))         & (fitobj_cp.x <= (par[15]+par[16]/2))] = par[17]
        bucket[(fitobj_cp.x >= (par[15]+par[16]/2-par[18])) & (fitobj_cp.x <= (par[15]+par[16]/2))] += par[19]
        cont -= bucket
    smod *= cont

    mask = np.ones_like(smod,dtype=bool)
    mask[(fitobj_cp.s < .0)] = False

    if len(fitobj_cp.mask) != 0:
        for maskbounds in fitobj_cp.mask:
            mask[(fitobj_cp.x > maskbounds[0]) & (fitobj_cp.x < maskbounds[1]) ] = False

    mask[fitobj_cp.CRmask] = False

    # Compute chisq
    chisq = np.sum((fitobj_cp.s[mask] - smod[mask])**2. / fitobj_cp.u[mask]**2.)
    chisq = chisq / (len(smod[mask]) - len(par))

    if optimize_cp == True:
        return chisq
    else:
        return smod,chisq
Esempio n. 11
0
def MPinst(args, inparam, i, order0, order):
    #    nights = inparam.nights
    nights = i[0]
    print('Working on {} band, order {}/{}, night {} ...'.format(
        args.band, order, len(order0), i[0]))
    night = str(nights)

    # Retrieve pixel bounds for where within each other significant telluric absorption is present.
    # If these bounds were not applied, analyzing some orders would give garbage fits.
    if args.band == 'K':
        if int(order) in [14]:
            bound_cut = inparam.bound_cut_dic[args.band][order]
        else:
            bound_cut = [150, 150]

    elif args.band == 'H':
        if int(order) in [13, 14, 16, 20]:
            bound_cut = inparam.bound_cut_dic[args.band][order]
        else:
            bound_cut = [150, 150]
    ### Load relevant A0 spectrum
    x, a0wavelist, a0fluxlist, u = init_fitsread(
        inparam.inpath, 'A0', 'separate', night, order,
        f'{int(inparam.tags[night]):04d}', args.band, bound_cut)

    #-------------------------------------------------------------------------------
    nzones = 12
    a0wavelist = basicclip_above(a0wavelist, a0fluxlist, nzones)
    a0x = basicclip_above(x, a0fluxlist, nzones)
    a0u = basicclip_above(u, a0fluxlist, nzones)
    a0fluxlist = basicclip_above(a0fluxlist, a0fluxlist, nzones)
    a0wavelist = basicclip_above(a0wavelist, a0fluxlist, nzones)
    a0x = basicclip_above(a0x, a0fluxlist, nzones)
    a0u = basicclip_above(a0u, a0fluxlist, nzones)
    a0fluxlist = basicclip_above(a0fluxlist, a0fluxlist, nzones)

    # Normalize
    a0fluxlist /= np.median(a0fluxlist)

    # Compute rough blaze fn estimate
    continuum = A0cont(a0wavelist, a0fluxlist, night, order)
    a0contwave = a0wavelist.copy()
    a0masterwave = a0wavelist.copy()
    a0masterwave *= 1e4

    # Trim stellar template to relevant wavelength range
    mwave_in, mflux_in = stellarmodel_setup(a0wavelist, inparam.mwave0,
                                            inparam.mflux0)

    # Trim telluric template to relevant wavelength range
    satm_in = inparam.satm[(inparam.watm > min(a0wavelist) * 1e4 - 11)
                           & (inparam.watm < max(a0wavelist) * 1e4 + 11)]
    watm_in = inparam.watm[(inparam.watm > min(a0wavelist) * 1e4 - 11)
                           & (inparam.watm < max(a0wavelist) * 1e4 + 11)]

    # Get initial guess for cubic wavelength solution from reduction pipeline
    f = np.polyfit(a0x, a0wavelist, 3)
    par9in = f[0] * 1e4
    par8in = f[1] * 1e4
    par7in = f[2] * 1e4
    par6in = f[3] * 1e4

    # Determine whether IGRINS mounting was loose or night for the night in question
    if (int(night) < 20180401) or (int(night) > 20190531):
        IPpars = inparam.ips_tightmount_pars[args.band][order]
    else:
        IPpars = inparam.ips_loosemount_pars[args.band][order]

    ### Initialize parameter array for optimization as well as half-range values for each parameter during
    ### the various steps of the optimization.
    ### Many of the parameters initialized here will be changed throughout the code before optimization and
    ### in between optimization steps.
    parA0 = np.array([
        0.0,  # 0: The shift of the stellar template (km/s)
        0.0,  # 1: The scale factor for the stellar template
        0.0,  # 2: The shift of the telluric  template (km/s)
        1.0,  # 3: The scale factor for the telluric template
        0.0,  # 4: vsini (km/s)
        IPpars[2],  # 5: The instrumental resolution (FWHM) in pixels
        par6in,  # 6: Wavelength 0-pt
        par7in,  # 7: Wavelength linear component
        par8in,  # 8: Wavelength quadratic component
        par9in,  # 9: Wavelength cubic component
        1.0,  #10: Continuum zero point
        0.,  #11: Continuum linear component
        0.,  #12: Continuum quadratic component
        IPpars[1],  #13: Insrumental resolution linear component
        IPpars[0]
    ])  #14: Insrumental resolution quadratic component

    # Make sure data is within telluric template range (shouldn't do anything)
    a0fluxlist = a0fluxlist[(a0wavelist * 1e4 > min(watm_in) + 5)
                            & (a0wavelist * 1e4 < max(watm_in) - 5)]
    a0u = a0u[(a0wavelist * 1e4 > min(watm_in) + 5)
              & (a0wavelist * 1e4 < max(watm_in) - 5)]
    a0x = a0x[(a0wavelist * 1e4 > min(watm_in) + 5)
              & (a0wavelist * 1e4 < max(watm_in) - 5)]
    continuum = continuum[(a0wavelist * 1e4 > min(watm_in) + 5)
                          & (a0wavelist * 1e4 < max(watm_in) - 5)]
    a0wavelist = a0wavelist[(a0wavelist * 1e4 > min(watm_in) + 5)
                            & (a0wavelist * 1e4 < max(watm_in) - 5)]

    # Define main spectrum
    s = a0fluxlist.copy()
    x = a0x.copy()
    u = a0u.copy()

    # Collect all fit variables into one class
    fitobj = fitobjs(s, x, u, continuum, watm_in, satm_in, mflux_in, mwave_in,
                     [])

    # Arrays defining parameter variations during optimization steps
    dpar_cont = np.array(
        [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0., 1e7, 1, 1, 0, 0])
    dpar_wave = np.array([
        0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10.0, 10.0, 5.00000e-5, 1e-7, 0, 0, 0, 0,
        0
    ])
    dpar = np.array(
        [0.0, 0.0, 5.0, 3.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0, 1e4, 1, 1, 0, 0])
    dpar_st = np.array(
        [0.0, 0.0, 5.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 1e4, 1, 1, 0, 0])
    dpar_ip = np.array(
        [0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, 0])

    #-------------------------------------------------------------------------------

    # Initialize an array that puts hard bounds on vsini and the instrumental resolution to make sure they do not diverge to unphysical values
    optimize = True
    par_in = parA0.copy()
    hardbounds = [
        par_in[4] - dpar[4], par_in[4] + dpar[4], par_in[5] - dpar[5],
        par_in[5] + dpar[5]
    ]
    if hardbounds[0] < 0:
        hardbounds[0] = 0
    if hardbounds[3] < 0:
        hardbounds[3] = 1

    # Begin optimization.
    # For every pre-Telfit spectral fit, first fit just template strength/rv/continuum, then just wavelength solution, then template/continuum again, then ip,
    # then finally wavelength. Normally would fit for all but wavelength at the end, but there's no need for the pre-Telfit fit, since all we want
    # is a nice wavelength solution to feed into Telfit.
    parfit_1 = optimizer(par_in, dpar_st, hardbounds, fitobj, optimize)
    parfit_2 = optimizer(parfit_1, dpar_wave, hardbounds, fitobj, optimize)
    parfit_3 = optimizer(parfit_2, dpar_st, hardbounds, fitobj, optimize)
    parfit_4 = optimizer(parfit_3, dpar, hardbounds, fitobj, optimize)
    parfit = optimizer(parfit_4, dpar_wave, hardbounds, fitobj, optimize)

    #-------------------------------------------------------------------------------

    # Get best fit wavelength solution
    a0w_out_fit = parfit[6] + parfit[7] * x + parfit[8] * (
        x**2.) + parfit[9] * (x**3.)

    # Trim stellar template to new relevant wavelength range
    mwave_in, mflux_in = stellarmodel_setup(a0w_out_fit / 1e4, inparam.mwave0,
                                            inparam.mflux0)

    # Feed this new wavelength solution into Telfit. Returns high-res synthetic telluric template, parameters of that best fit, and blaze function best fit
    watm1, satm1, telfitparnames, telfitpars, a0contwave, continuum = telfitter(
        a0w_out_fit, a0fluxlist, a0u, inparam, night, order, args)

    #-------------------------------------------------------------------------------
    #-------------------------------------------------------------------------------

    # If Telfit encountered error (details in Telfitter.py), skip night/order combo
    if len(watm1) == 1:
        logger.warning(
            f'TELFIT ENCOUNTERED CRITICAL ERROR IN ORDER: {order} NIGHT: {night}'
        )

        # Write out table to fits header with errorflag = 1
        c0 = fits.Column(name=f'ERRORFLAG{order}',
                         array=np.array([1]),
                         format='K')
        cols = fits.ColDefs([c0])
        hdu_1 = fits.BinTableHDU.from_columns(cols)

        # If first time writing fits file, make up filler primary hdu
        if order == firstorder:
            bleh = np.ones((3, 3))
            primary_hdu = fits.PrimaryHDU(bleh)
            hdul = fits.HDUList([primary_hdu, hdu_1])
            hdul.writeto('{}/{}A0_treated_{}.fits'.format(
                inparam.outpath, night, args.band))
        else:
            hh = fits.open('{}/{}A0_treated_{}.fits'.format(
                inparam.outpath, night, args.band))
            hh.append(hdu_1)
            hh.writeto('{}/{}A0_treated_{}.fits'.format(
                inparam.outpath, night, args.band),
                       overwrite=True)

    else:  # If Telfit exited normally, proceed.

        #  Save best blaze function fit
        a0contwave /= 1e4
        continuum = rebin_jv(a0contwave, continuum, a0wavelist, False)

        # Fit the A0 again using the new synthetic telluric template.
        # This allows for any tweaks to the blaze function fit that may be necessary.
        fitobj = fitobjs(s, x, u, continuum, watm1, satm1, mflux_in, mwave_in,
                         [])

        parfit_1 = optimizer(par_in, dpar_st, hardbounds, fitobj, optimize)
        parfit_2 = optimizer(parfit_1, dpar_wave, hardbounds, fitobj, optimize)
        parfit_3 = optimizer(parfit_2, dpar_st, hardbounds, fitobj, optimize)
        parfit_4 = optimizer(parfit_3, dpar_wave, hardbounds, fitobj, optimize)
        parfit = optimizer(parfit_4, dpar, hardbounds, fitobj, optimize)

        #-------------------------------------------------------------------------------

        a0w_out = parfit[6] + parfit[7] * x + parfit[8] * (
            x**2.) + parfit[9] * (x**3.)
        cont_adj = parfit[10] + parfit[11] * x + parfit[12] * (x**2.)

        c2 = rebin_jv(a0contwave * 1e4, continuum, a0w_out, False)
        c2 /= np.median(c2)
        cont_save = c2 * cont_adj

        # Write out table to fits file with errorflag = 0
        c0 = fits.Column(name='ERRORFLAG' + str(order),
                         array=np.array([0]),
                         format='K')
        cc = fits.Column(name='WAVE_pretel' + str(order),
                         array=a0w_out_fit,
                         format='D')
        c1 = fits.Column(name='WAVE' + str(order), array=a0w_out, format='D')
        c2 = fits.Column(name='BLAZE' + str(order),
                         array=cont_save,
                         format='D')
        c3 = fits.Column(name='X' + str(order), array=a0x, format='D')
        c4 = fits.Column(name='INTENS' + str(order),
                         array=a0fluxlist,
                         format='D')
        c5 = fits.Column(name='SIGMA' + str(order), array=a0u, format='D')
        c6 = fits.Column(name='WATM' + str(order), array=watm1, format='D')
        c7 = fits.Column(name='SATM' + str(order), array=satm1, format='D')
        c8 = fits.Column(name='TELFITPARNAMES' + str(order),
                         array=np.array(telfitparnames),
                         format='8A')
        c9 = fits.Column(name='TELFITPARS' + str(order),
                         array=telfitpars,
                         format='D')
        cols = fits.ColDefs([c0, cc, c1, c2, c3, c4, c5, c6, c7, c8, c9])
        hdu_1 = fits.BinTableHDU.from_columns(cols)

        #        if order == 1: # If first time writing fits file, make up filler primary hdu
        bleh = np.ones((3, 3))
        primary_hdu = fits.PrimaryHDU(bleh)
        hdul = fits.HDUList([primary_hdu, hdu_1])
        # hdul.writeto(inparam.outpath+'/'+night+'A0_treated_{}_order{}.fits'.format(args.band, order),overwrite=True)
        hdul.writeto('{}/{}A0_treated_{}_order{}.fits'.format(
            inparam.outpath, night, args.band, order),
                     overwrite=True)
Esempio n. 12
0
def rv_MPinst(args, inparam, orders, order_use, trk, step2or3, i):

    # Main function for RV fitting that will be threaded over by multiprocessing

    nights = inparam.nights
    night = nights[i]  # current looped night

    order = order_use
    xbounds = inparam.xbounddict[order]

    if args.debug:
        print('Working on order {:02d}, night {:03d}/{:03d} ({}) PID:{}...'.
              format(int(order), i + 1, len(inparam.nights), night,
                     mp.current_process().pid))

    #-------------------------------------------------------------------------------

    # Collect initial RV guesses
    if type(inparam.initguesses) == dict:
        initguesses = inparam.initguesses[night]
    elif type(inparam.initguesses) == float:
        initguesses = inparam.initguesses
    else:
        sys.exit(
            'ERROR! EXPECING SINGAL NUMBER OR FILE FOR INITGUESSES! QUITTING!')

    if np.isnan(initguesses) == True:
        logger.warning(
            f'  --> Previous run of {night} found it inadequate, skipping...')
        return night, np.nan, np.nan

    # Collect relevant beam and filenum info
    tagsnight = []
    beamsnight = np.array([])
    for tag in inparam.tagsB[night]:
        tagsnight.append(tag)
        beamsnight = np.append(beamsnight, 'B')

    # Only do B exposures, and just use first B nodding
    masterbeam = 'B'
    beam = 'B'
    try:
        tag = tagsnight[0]
    except IndexError:
        logger.warning(
            f'  --> No B nodding(frame) for night {night}, skipping...')
        return night, np.nan, np.nan

    #-------------------------------------------------------------------------------
    ### Initialize parameter array for optimization as well as half-range values for each parameter during the various steps of the optimization.
    ### Many of the parameters initialized here will be changed throughout the code before optimization and in between optimization steps.
    pars0 = np.array([
        np.nan,  # 0: The shift of the stellar template (km/s) [assigned later]
        0.3,  # 1: The scale factor for the stellar template
        0.0,  # 2: The shift of the telluric template (km/s)
        0.6,  # 3: The scale factor for the telluric template
        inparam.initvsini,  # 4: vsini (km/s)
        np.nan,  # 5: The instrumental resolution (FWHM) in pixels
        np.nan,  # 6: Wavelength 0-pt
        np.nan,  # 7: Wavelength linear component
        np.nan,  # 8: Wavelength quadratic component
        np.nan,  # 9: Wavelength cubic component
        1.0,  #10: Continuum zero point
        0.,  #11: Continuum linear component
        0.,  #12: Continuum quadratic component
        np.nan,  #13: Instrumental resolution linear component
        np.nan,  #14: Instrumental resolution quadratic component
        0,  #15: Blaze dip center location
        0,  #16: Blaze dip full width
        0,  #17: Blaze dip depth
        0,  #18: Secondary blaze dip full width
        0,  #19: Blaze dip depth
        0.0,  #20: Continuum cubic component
        0.0,  #21: Continuum quartic component
        0.0,  #22: Continuum pentic component
        0.0
    ])  #23: Continuum hexic component

    # This one specific order is small and telluric dominated, start with greater stellar template power to ensure good fits
    if int(order) == 13:
        pars0[1] = 0.8

    # Load synthetic telluric template generated during Step 1
    # [:8] here is to ensure program works under Night_Split mode

    A0loc = f'./Output/{args.targname}_{args.band}/A0Fits/{night[:8]}A0_{beam}treated_{args.band}.fits'

    try:
        hdulist = fits.open(A0loc)
    except IOError:
        logger.warning(
            f'  --> No A0-fitted template for night {night}, skipping...')
        return night, np.nan, np.nan

    # Find corresponding table in fits file, given the tables do not go sequentially by order number due to multiprocessing in Step 1
    num_orders = 0
    for i in range(25):
        try:
            hdulist[i].columns[0].name[9:]
            num_orders += 1
        except:
            continue

    fits_layer = [
        i for i in np.arange(num_orders) + 1
        if np.int(hdulist[i].columns[0].name[9:]) == order
    ][0]

    tbdata = hdulist[fits_layer].data
    flag = np.array(tbdata[f'ERRORFLAG{order}'])[0]

    # Check whether Telfit hit critical error in Step 1 for the chosen order with this night. If so, try another order. If all hit the error, skip the night.
    nexto = 0
    ordertry = order
    while 1 == 1:
        fits_layer = [
            i for i in np.arange(num_orders) + 1
            if np.int(hdulist[i].columns[0].name[9:]) == ordertry
        ][0]

        tbdata = hdulist[fits_layer].data
        flag = np.array(tbdata[f'ERRORFLAG{ordertry}'])[0]

        if flag == 1:  # If Telfit hit unknown critical error in Step 1, this order can't be used for this night. Try another.
            orderbad = ordertry
            ordertry = orders[nexto]
            logger.warning(
                f'  --> TELFIT ENCOUNTERED CRITICAL ERROR IN ORDER: {orderbad} NIGHT: {night}, TRYING ORDER {ordertry} INSTEAD...'
            )

        else:  # All good, continue
            order = ordertry
            break

        nexto += 1
        if nexto == len(orders):
            logger.warning(
                f'  --> TELFIT ENCOUNTERED CRITICAL ERROR IN ALL ORDERS FOR NIGHT: {night}, skipping...'
            )
            return night, np.nan, np.nan

    watm = tbdata['WATM' + str(order)]
    satm = tbdata['SATM' + str(order)]
    a0contx = tbdata['X' + str(order)]
    continuum = tbdata['BLAZE' + str(order)]

    # Remove extra rows leftover from having columns of unequal length
    satm = satm[(watm != 0)]
    watm = watm[(watm != 0)]
    satm[(
        satm < 1e-4
    )] = 0.  # set very low points to zero so that they don't go to NaN when taken to an exponent by template power in fmodel_chi
    a0contx = a0contx[(continuum != 0)]
    continuum = continuum[(continuum != 0)]

    # Use instrumental profile dictionary corresponding to whether IGRINS mounting was loose or not
    if (np.int(night[:8]) < 20180401) or (np.int(night[:8]) > 20190531):
        IPpars = inparam.ips_tightmount_pars[args.band][masterbeam][order]
    else:
        IPpars = inparam.ips_loosemount_pars[args.band][masterbeam][order]

    # Retrieve pixel bounds for where within each other significant telluric absorption is present.
    # If these bounds were not applied, analyzing some orders would give garbage fits.
    if args.band == 'K':
        if int(order) in [3, 13, 14]:
            bound_cut = inparam.bound_cut_dic[args.band][order]
        else:
            bound_cut = [150, 150]

    elif args.band == 'H':
        if int(order) in [6, 10, 11, 13, 14, 16, 17, 20, 21, 22]:
            bound_cut = inparam.bound_cut_dic[args.band][order]
        else:
            bound_cut = [150, 150]

    # Load target spectrum
    x, wave, s, u = init_fitsread(f'{inparam.inpath}/', 'target',
                                  'combined' + str(masterbeam), night, order,
                                  inparam.tagsB[night][0], args.band,
                                  bound_cut)

    #-------------------------------------------------------------------------------

    # Execute S/N cut
    s2n = s / u
    if np.nanmedian(s2n) < np.float(args.SN_cut):
        logger.warning('  --> Bad S/N {:1.3f} < {} for {}{} {}... '.format(
            np.nanmedian(s2n), args.SN_cut, night, beam, tag))
        pass

    # Trim obvious outliers above the blaze (i.e. cosmic rays)
    nzones = 5
    x = basicclip_above(x, s, nzones)
    wave = basicclip_above(wave, s, nzones)
    u = basicclip_above(u, s, nzones)
    s = basicclip_above(s, s, nzones)
    x = basicclip_above(x, s, nzones)
    wave = basicclip_above(wave, s, nzones)
    u = basicclip_above(u, s, nzones)
    s = basicclip_above(s, s, nzones)

    # Cut spectrum to within wavelength regions defined in input list
    s_piece = s[(x > xbounds[0]) & (x < xbounds[-1])]
    u_piece = u[(x > xbounds[0]) & (x < xbounds[-1])]
    wave_piece = wave[(x > xbounds[0]) & (x < xbounds[-1])]
    x_piece = x[(x > xbounds[0]) & (x < xbounds[-1])]

    # Trim stellar template to relevant wavelength range
    mwave_in, mflux_in = stellarmodel_setup(wave_piece, inparam.mwave0,
                                            inparam.mflux0)

    # Trim telluric template to relevant wavelength range
    satm_in = satm[(watm > np.min(wave_piece) * 1e4 - 11)
                   & (watm < np.max(wave_piece) * 1e4 + 11)]
    watm_in = watm[(watm > np.min(wave_piece) * 1e4 - 11)
                   & (watm < np.max(wave_piece) * 1e4 + 11)]

    # Make sure data is within telluric template range (shouldn't do anything)
    s_piece = s_piece[(wave_piece * 1e4 > np.min(watm_in) + 5)
                      & (wave_piece * 1e4 < np.max(watm_in) - 5)]
    u_piece = u_piece[(wave_piece * 1e4 > np.min(watm_in) + 5)
                      & (wave_piece * 1e4 < np.max(watm_in) - 5)]
    x_piece = x_piece[(wave_piece * 1e4 > np.min(watm_in) + 5)
                      & (wave_piece * 1e4 < np.max(watm_in) - 5)]
    wave_piece = wave_piece[(wave_piece * 1e4 > np.min(watm_in) + 5)
                            & (wave_piece * 1e4 < np.max(watm_in) - 5)]

    # Normalize continuum from A0 to flux scale of data
    continuum /= np.nanmedian(continuum)
    continuum *= np.nanpercentile(s_piece, 99)

    # --------------------------------------------------------------

    par = pars0.copy()

    # Get initial guess for cubic wavelength solution from reduction pipeline
    f = np.polyfit(x_piece, wave_piece, 3)
    par9in = f[0] * 1e4
    par8in = f[1] * 1e4
    par7in = f[2] * 1e4
    par6in = f[3] * 1e4
    par[9] = par9in
    par[8] = par8in
    par[7] = par7in
    par[6] = par6in

    par[0] = initguesses - inparam.bvcs[
        night + tag]  # Initial RV with barycentric correction
    par[5] = IPpars[2]
    par[13] = IPpars[1]
    par[14] = IPpars[0]

    # Arrays defining parameter variations during optimization steps
    # Optimization will cycle twice. In the first cycle, the RVs can vary more than in the second.
    #                             | 0    1    2    3 |  | ------ 4 ------ |  | 5 |   | 6     7     8           9  |  |10  11  12| |13 14|  |15  16  17  18  19|  |20   21   22   23 |
    dpars1 = {
        'cont':
        np.array([
            0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1e7, 1, 1, 0, 0,
            0., 0., 0., 0., 0., 1.0, 1.0, 1.0, 1.0
        ]),
        'twave':
        np.array([
            0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 10.0, 10.0, 5.00000e-5, 1e-7, 0, 0,
            0, 0, 0, 0., 0., 0., 0., 0., 0.0, 0.0, 0.0, 0.0
        ]),
        'ip':
        np.array([
            0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0,
            0., 0., 0., 0., 0., 0.0, 0.0, 0.0, 0.0
        ]),
        's':
        np.array([
            20.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0,
            0., 0., 0., 0., 0., 0.0, 0.0, 0.0, 0.0
        ]),
        'v':
        np.array([
            0.0, 0.0, 0.0, 0.0, inparam.vsinivary, 0.0, 0.0, 0.0, 0.0, 0.0, 0,
            0, 0, 0, 0, 0., 0., 0., 0., 0., 0.0, 0.0, 0.0, 0.0
        ])
    }

    dpars2 = {
        'cont':
        np.array([
            0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1e7, 1, 1, 0, 0,
            0., 0., 0., 0., 0., 1.0, 1.0, 1.0, 1.0
        ]),
        'twave':
        np.array([
            0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 10.0, 10.0, 5.00000e-5, 1e-7, 0, 0,
            0, 0, 0, 0., 0., 0., 0., 0., 0.0, 0.0, 0.0, 0.0
        ]),
        'ip':
        np.array([
            0.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0,
            0., 0., 0., 0., 0., 0.0, 0.0, 0.0, 0.0
        ]),
        's':
        np.array([
            5.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0,
            0., 0., 0., 0., 0., 0.0, 0.0, 0.0, 0.0
        ]),
        'v':
        np.array([
            0.0, 0.0, 0.0, 0.0, inparam.vsinivary, 0.0, 0.0, 0.0, 0.0, 0.0, 0,
            0, 0, 0, 0, 0., 0., 0., 0., 0., 0.0, 0.0, 0.0, 0.0
        ])
    }

    # Use quadratic blaze correction for order 13; cubic for orders 6, 14, 21; quartic for orders 16 and 22
    if args.band == 'H':
        if int(order) in [13]:
            dpars1['cont'][20] = 0.
            dpars1['cont'][21] = 0.
            dpars1['cont'][22] = 0.
            dpars1['cont'][23] = 0.
            dpars2['cont'][20] = 0.
            dpars2['cont'][21] = 0.
            dpars2['cont'][22] = 0.
            dpars2['cont'][23] = 0.
        elif int(order) in [6, 14, 21]:
            dpars1['cont'][21] = 0.
            dpars1['cont'][22] = 0.
            dpars1['cont'][23] = 0.
            dpars2['cont'][21] = 0.
            dpars2['cont'][22] = 0.
            dpars2['cont'][23] = 0.
        else:
            pass
    else:
        if np.int(order) in [3]:
            dpars1['cont'][20] = 0.
            dpars1['cont'][21] = 0.
            dpars1['cont'][22] = 0.
            dpars1['cont'][23] = 0.
            dpars2['cont'][20] = 0.
            dpars2['cont'][21] = 0.
            dpars2['cont'][22] = 0.
            dpars2['cont'][23] = 0.
        elif np.int(order) in [4, 5]:
            dpars1['cont'][21] = 0.
            dpars1['cont'][22] = 0.
            dpars1['cont'][23] = 0.
            dpars2['cont'][21] = 0.
            dpars2['cont'][22] = 0.
            dpars2['cont'][23] = 0.
        elif np.int(order) in [6]:
            dpars1['cont'][22] = 0.
            dpars1['cont'][23] = 0.
            dpars2['cont'][22] = 0.
            dpars2['cont'][23] = 0.
        else:
            pass

    continuum_in = rebin_jv(a0contx, continuum, x_piece, False)
    fitobj = fitobjs(s_piece, x_piece, u_piece, continuum_in, watm_in, satm_in,
                     mflux_in, mwave_in,
                     ast.literal_eval(inparam.maskdict[order]), masterbeam,
                     np.array([], dtype=int))

    #-------------------------------------------------------------------------------

    # Initialize an array that puts hard bounds on vsini and the instrumental resolution to make sure they do not diverge to unphysical values
    optimize = True
    par_in = par.copy()
    hardbounds = [
        par_in[4] - dpars1['v'][4], par_in[4] + dpars1['v'][4],
        par_in[5] - dpars1['ip'][5], par_in[5] + dpars1['ip'][5]
    ]

    if hardbounds[0] < 0.5:
        hardbounds[0] = 0.5
    if hardbounds[2] < 1:
        hardbounds[2] = 1

    # Begin optimization. Fit the blaze, the wavelength solution, the telluric template power and RV, the stellar template power and RV, the
    # zero point for the instrumental resolution, and the vsini of the star separately, iterating and cycling between each set of parameter fits.

    cycles = 2

    optgroup = [
        'cont', 'twave', 'cont', 's', 'cont', 'twave', 's', 'cont', 'twave',
        'ip', 'v', 'ip', 'v', 'twave', 's', 'twave', 's'
    ]

    nk = 1
    for nc, cycle in enumerate(np.arange(cycles), start=1):
        if cycle == 0:
            parstart = par_in.copy()
            dpars = dpars1
        else:
            dpars = dpars2

        for optkind in optgroup:
            parfit_1 = optimizer(parstart, dpars[optkind], hardbounds, fitobj,
                                 optimize)
            parstart = parfit_1.copy()
            if args.debug == True:
                outplotter_23(
                    parfit_1, fitobj,
                    '{}_{}_{}_parfit_{}{}'.format(order, night, tag, nk,
                                                  optkind), trk, inparam, args,
                    step2or3, order)
                logger.debug(f'{order}_{tag}_{nk}_{optkind}:\n {parfit_1}')
            nk += 1

    parfit = parfit_1.copy()

    #-------------------------------------------------------------------------------

    # if best fit stellar template power is very low, throw out result
    if parfit[1] < 0.1:
        logger.warning(
            f'  --> Stellar template power is low for {night}! Data likely being misfit! Throwing out result...'
        )
        return night, np.nan, np.nan

    # if best fit stellar or telluric template powers are exactly equal to their starting values, fit failed, throw out result
    if parfit[1] == par_in[1] or parfit[3] == par_in[3]:
        logger.warning(
            f'  --> Stellar or telluric template powers have not budged from starting values for {night}! Fit is broken! Optimizer bounds may be unfeasible, or chi-squared may be NaN? Throwing out result...'
        )
        return night, np.nan, np.nan

    # if best fit model dips below zero at any point, we're to close to edge of blaze, fit may be comrpomised, throw out result
    smod, chisq = fmod(parfit, fitobj)
    if len(smod[(smod < 0)]) > 0:
        logger.warning(
            f'  --> Best fit model dips below 0 for {night}! May be too close to edge of blaze, throwing out result...'
        )
        return night, np.nan, np.nan

    #-------------------------------------------------------------------------------

    if args.plotfigs == True:
        parfitS = parfit.copy()
        parfitS[3] = 0
        parfitT = parfit.copy()
        parfitT[1] = 0
        outplotter_23(parfitS, fitobj,
                      'parfitS_{}_{}_{}'.format(order, night, tag), trk,
                      inparam, args, step2or3, order)
        outplotter_23(parfitT, fitobj,
                      'parfitT_{}_{}_{}'.format(order, night, tag), trk,
                      inparam, args, step2or3, order)
        outplotter_23(parfit, fitobj,
                      'parfit_{}_{}_{}'.format(order, night, tag), trk,
                      inparam, args, step2or3, order)

    rv0 = parfit[0]
    rvsmini = rv0 + inparam.bvcs[night +
                                 tag] + rv0 * inparam.bvcs[night + tag] / (
                                     2.99792458e5**2)  # Barycentric correction
    vsinismini = parfit[4]

    bestguess = np.round(rvsmini, 5)
    vsinimini = np.round(vsinismini, 5)
    return night, bestguess, vsinimini