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