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
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
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)
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()
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
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
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
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)
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