def test_Atmosphere_band_select(): """Small test to check band selection.""" band = "K" # "K": (2.07, 2.35), atm = Atmosphere([2.0, 2.1, 2.2, 2.3, 2.4, 2.5], np.arange(6), std=None, mask=[1, 0, 0, 1, 1, 0]) atm2 = atm.band_select(band) assert np.allclose(atm2.wl, [2.1, 2.2, 2.3]) assert np.allclose(atm2.mask, [0, 0, 1])
def test_atmosphere_masking(trans, percent): cutoff = 1 - (percent / 100.0) assume(np.any(np.array(trans) < cutoff)) assume(np.any(np.array(trans) >= cutoff)) atmos = Atmosphere(wavelength=np.arange(len(trans)), transmission=trans) org_mask = atmos.mask atmos.mask_transmission(percent) assert np.any(atmos.mask != org_mask) assert np.all(atmos.transmission[atmos.mask] >= cutoff) assert np.all(atmos.transmission[~atmos.mask] < cutoff)
def test_atmosphere_class_nomask(wave, transmission): atmos = Atmosphere(wave, transmission) assert np.all(atmos.wl == wave) assert np.all(atmos.transmission == transmission) assert np.all(atmos.mask == 1) assert len(atmos.mask) == len(atmos.transmission) assert atmos.mask.dtype == np.bool
def real_spec(request): band = request.param wav, flux = load_aces_spectrum([3900, 4.5, 0.0, 0]) wav, flux = wav_selector(wav, flux, *band_limits(band)) flux = flux / snr_constant_band(wav, flux, 100, band) atm = Atmosphere.from_band(band).at(wav) return wav, flux, atm.transmission
def test_atmosphere_class_turns_lists_to_arrays(wave, transmission, std, mask): atmos = Atmosphere(wave, transmission, mask, std) assert isinstance(atmos.wl, np.ndarray) assert isinstance(atmos.transmission, np.ndarray) assert isinstance(atmos.std, np.ndarray) assert isinstance(atmos.mask, np.ndarray) assert atmos.mask.dtype == np.bool
def test_from_band_is_a_constructor(band): """This is testing loading files using from_band method.""" atm = Atmosphere.from_band(band) min_wl, max_wl = band_limits(band) assert isinstance(atm, Atmosphere) assert np.all(atm.wl <= max_wl * 1.01) assert np.all(atm.wl >= min_wl * 0.99)
def test_atmosphere_class(wave, transmission, std, mask): atmos = Atmosphere(np.array(wave), np.array(transmission), np.array(mask), std=np.array(std)) assert np.all(atmos.wl == wave) assert np.all(atmos.transmission == transmission) assert np.all(atmos.mask == mask) assert np.all(atmos.std == std) assert atmos.mask.dtype == np.bool
def main(bands: Optional[List[str]] = None, verbose: bool = False) -> None: """Preform the barycentric shifting of atmosphere masks and saves result. This saves time in the precision determination code. Parameters ---------- bands: list of str Wavelength bands to perform barycenter shifts on. Default is all bands. """ if (bands is None) or ("ALL" in bands): bands_ = config.bands["all"] else: bands_ = bands for band in bands_: unshifted_atmmodel = join( config.pathdir, config.paths["atmmodel"], "{0}_{1}.dat".format(config.atmmodel["base"], band), ) if verbose: print("Reading atmospheric model...", unshifted_atmmodel) atm = Atmosphere.from_file(unshifted_atmmodel) if verbose: print("Calculating impact of Barycentric movement on mask...") org_mask = atm.mask masked_before = np.sum(org_mask) atm.barycenter_broaden(consecutive_test=True) masked_after = np.sum(atm.mask) if verbose: print("Masked fraction before = {0:0.03f}".format( (len(org_mask) - masked_before) / len(org_mask))) print("Masked fraction after = {0:0.03f}".format( (len(atm.mask) - masked_after) / len(atm.mask))) shifted_atmmodel = unshifted_atmmodel.replace(".dat", "_bary.dat") if verbose: print("Saving doppler-shifted atmosphere model to {}".format( shifted_atmmodel)) header = ["# atm_wav(nm)", "atm_flux", "atm_std_flux", "atm_mask"] atm.to_file(fname=shifted_atmmodel, header=header, fmt="%11.8f") if verbose: print("Finished barycentric shifting of atmosphere masks")
def test_weights_clumping_grad(testing_spectrum, grad_flag, band): # Test masked clumping verse weight mask with new gradients # Test on an actual spectrum to check sizes of difference wav, flux = testing_spectrum atm = Atmosphere.from_band(band) mask = np.ones_like(wav) mask = atm.at(wav).mask clumped = RVprec_calc_masked(wav, flux, mask=mask, grad=grad_flag) weighted = RVprec_calc_weights_masked(wav, flux, mask=mask, grad=grad_flag) # They are not exactly the same by are within a specific percentage. ratio = (weighted.value - clumped.value) / clumped.value if grad_flag: # The difference for the new gradient is smaller. assert abs(ratio) < 0.008 else: assert abs(ratio) < 0.04 assert clumped.unit == weighted.unit
def sliced_atmmodel_default_mask(request, atm_model): """To do own masking. Sliced in different places.""" lower, upper = request.param # slice limits atm = Atmosphere.from_file(atm_model) return atm[int(lower) : int(upper)]
def atmosphere_fixture(request, atm_model): percent_cutoff = request.param atm = Atmosphere.from_file(atm_model) atm.mask_transmission(percent_cutoff) return atm
def main( model: str = atmmodel, bands: Optional[List[str]] = None, new_name: Optional[str] = None, data_dir: Optional[str] = None, rv_extend: float = 100, cutoff_depth: float = 2.0, ): """Split the large atmospheric model transmission spectra into the separate bands. Keeps wavelength of atmosphere model as nanometers. Parameters ---------- model: str Telluric model file to load. It has columns wavelength, flux, std_flux, mask. bands: list[str] List bands to split model into separate files. new_name: str New file name base. data_dir: Optional[str] Directory for results. Can also be given in config.yaml "paths:atmmodel:"... rv_extend: float (positive) (default 100) Rv amount to extend wavelength range of telluric band. To later apply barycenter shifting. cutoff_depth: float Telluric line depth cutoff. Default = 2%. """ if (bands is None) or ("ALL" in bands): bands_ = eniric.bands["all"] else: bands_ = bands if new_name is None: new_name = model.split(".")[0] if data_dir is None: data_dir_ = eniric.paths["atmmodel"] else: data_dir_ = str(data_dir) model_name = join(data_dir_, model) # If trying to obtain the provided model extract from and it doesn't yet exist # extract from tar.gz file. (Extracted it is 230 MB which is to large for Git) if "Average_TAPAS_2014.dat" == atmmodel: if not os.path.exists(model_name): print("Unpacking Average_TAPAS_2014.dat.tar.gz...") import tarfile with tarfile.open(str(model_name) + ".tar.gz", "r") as tar: tar.extractall(data_dir_) print("Unpacked") print("Loading from_file {0}".format(model_name)) atm = Atmosphere.from_file(model_name) # Return value from saving each band write_status = np.empty_like(bands_, dtype=int) for i, band in enumerate(bands_): print("Starting {0}".format(band)) filename_band = "{0}_{1}.dat".format(new_name, band) band_min, band_max = band_limits(band) # * 1000 to convert into km/s band_min = doppler_shift_wav(band_min, -rv_extend) band_max = doppler_shift_wav(band_max, rv_extend) split_atm = atm.wave_select(band_min, band_max) # Apply telluric line mask atm.mask_transmission(depth=cutoff_depth) # Save the result to file filename = join(data_dir_, filename_band) header = ["# atm_wav(nm)", "atm_flux", "atm_std_flux", "atm_mask"] print("Saving to_file {}".format(filename)) write_status[i] = split_atm.to_file(filename, header=header, fmt="%11.8f") print("Done Splitting") return np.sum(write_status) # If any extracts fail they will turn up here.
def do_analysis( star_params, vsini: float, R: float, band: str, sampling: float = 3.0, conv_kwargs=None, snr: float = 100.0, ref_band: str = "J", rv: float = 0.0, air: bool = False, model="phoenix", ): """Precision and Quality for specific parameter set. Parameters ---------- air: bool Get model in air wavelengths. model: str Name of synthetic library to use. (phoenix, btsettl). rv: float Radial velocity. Notes: We apply the radial velocity doppler shift after - convolution (rotation and resolution) - resampling - SNR normalization. in this way the RV only effects the precision due to the telluric mask interaction. The RV should maybe come between the rotational and instrumental convolution but we assume this effect is negligible. """ if conv_kwargs is None: conv_kwargs = { "epsilon": 0.6, "fwhm_lim": 5.0, "num_procs": num_procs_minus_1, "normalize": True, } if ref_band.upper() == "SELF": ref_band = band if model == "phoenix": # Full photon count spectrum wav, flux = load_aces_spectrum(star_params, photons=True, air=air) elif model == "btsettl": wav, flux = load_btsettl_spectrum(star_params, photons=True, air=air) else: raise ValueError( "Model name error in '{}'. Valid choices are 'phoenix and 'btsettl'" .format(model)) wav_grid, sampled_flux = convolve_and_resample(wav, flux, vsini, R, band, sampling, **conv_kwargs) # Doppler shift try: if rv != 0: sampled_flux = doppler_shift_flux(wav_grid, sampled_flux, vel=rv) except Exception as e: print("Doppler shift was unsuccessful") raise e # Spectral Quality q = quality(wav_grid, sampled_flux) # Scale normalization for precision wav_ref, sampled_ref = convolve_and_resample(wav, flux, vsini, R, ref_band, sampling, **conv_kwargs) snr_normalize = snr_constant_band(wav_ref, sampled_ref, snr=snr, band=ref_band, sampling=sampling) sampled_flux = sampled_flux / snr_normalize if ref_band == band: mid_point = band_middle(ref_band) index_ref = np.searchsorted( wav_grid, mid_point) # searching for the index closer to 1.25 micron snr_estimate = np.sqrt( np.sum(sampled_flux[index_ref - 1:index_ref + 2])) print( "\tSanity Check: The S/N at {0:4.02} micron = {1:4.2f}, (should be {2:g})." .format(mid_point, snr_estimate, snr)) # Load Atmosphere for this band. atm = Atmosphere.from_band(band=band, bary=True).at(wav_grid) assert np.allclose(atm.wl, wav_grid), "The atmosphere does not cover the wav_grid" # Spectral Quality/Precision q = quality(wav_grid, sampled_flux) # Precision given by the first condition: prec1 = rv_precision(wav_grid, sampled_flux, mask=None) # Precision as given by the second condition prec2 = rv_precision(wav_grid, sampled_flux, mask=atm.mask) # Precision as given by the third condition: M = T**2 prec3 = rv_precision(wav_grid, sampled_flux, mask=atm.transmission**2) # Turn quality back into a Quantity (to give it a .value method) q = q * u.dimensionless_unscaled return [q, prec1, prec2, prec3]
def calculate_prec( spectral_types, bands, vsini, resolution, sampling, plot_atm=False, plot_ste=False, plot_flux=True, paper_plots=True, rv_offset=0.0, use_unshifted=False, snr=100, ref_band="J", new=True, grad=True, ): """Calculate precisions for given combinations. grad: bool Use more precise gradient function. """ # TODO: iterate over band last so that the J band normalization value can be # obtained first and applied to each band. print("using new config.yaml file here!!!!!!!!!!!!!!") results = {} # creating empty dictionary for the results wav_plot_m0 = [] # creating empty lists for the plots flux_plot_m0 = [] wav_plot_m3 = [] flux_plot_m3 = [] wav_plot_m6 = [] flux_plot_m6 = [] wav_plot_m9 = [] flux_plot_m9 = [] for band in bands: if use_unshifted: atmmodel = os.path.join( eniric.paths["atmmodel"], "{0}_{1}.dat".format(eniric.atmmodel["base"], band), ) print("Reading atmospheric model...") atm = Atmosphere.from_file(atmmodel) wav_atm, flux_atm, std_flux_atm, mask_atm = ( atm.wl, atm.transmission, atm.std, atm.mask, ) print( ( "There were {0:d} unmasked pixels out of {1:d}., or {2:.1%}." "" ).format( np.sum(mask_atm), len(mask_atm), np.sum(mask_atm) / len(mask_atm) ) ) print( "The model ranges from {0:4.2f} to {1:4.2f} micron.".format( wav_atm[0], wav_atm[-1] ) ) print("Done.") print("Calculating impact of Barycentric movement on mask...") # mask_atm = atm.old_barycenter_shift(wav_atm, mask_atm, rv_offset=rv_offset) mask_atm = barycenter_shift(wav_atm, mask_atm, rv_offset=rv_offset) else: shifted_atmmodel = os.path.join( eniric.paths["atmmodel"], "{0}_{1}_bary.dat".format(eniric.atmmodel["base"], band), ) print("Reading pre-doppler-shifted atmospheric model...") atm = Atmosphere.from_file(shifted_atmmodel) wav_atm, flux_atm, std_flux_atm, mask_atm = ( atm.wl, atm.transmission, atm.std, atm.mask, ) print("Done.") print( ("There were {0:d} unmasked pixels out of {1:d}, or {2:.1%}." "").format( np.sum(mask_atm), len(mask_atm), np.sum(mask_atm) / len(mask_atm) ) ) if plot_atm: # moved plotting code to separate code, eniric.obsolete.plotting_functions.py plt_functions.plot_atmosphere_model(wav_atm, flux_atm, mask_atm) # theoretical ratios calculation # wav_m0, flux_m0, wav_m3, flux_m3, wav_m6, flux_m6, wav_m9, flux_m9 = read_nIRspectra() iterations = itertools.product(spectral_types, vsini, resolution, sampling) # for star in spectral_types: # for vel in vsini: # for res in resolution: # for smpl in sampling: for (star, vel, res, smpl) in iterations: file_to_read = ( "Spectrum_{0}-PHOENIX-ACES_{1}band_vsini{2}_R{3}" "_res{4:2.01f}.dat" ).format(star, band, vel, res, float(smpl)) # print("Working on "+file_to_read+".") try: wav_stellar, flux_stellar = io.pdread_2col( os.path.join(eniric.paths["resampled"], file_to_read) ) except FileNotFoundError: # Turn list of strings into strings without symbols ["J", "K"] -> J K spectral_str = re.sub(r"[\[\]\"\',]", "", str(spectral_types)) band_str = re.sub(r"[\[\]\"\',]", "", str(bands)) vsini_str = re.sub(r"[\[\]\"\',]", "", str(vsini)) res_str = re.sub(r"[\[\]\"\',]", "", str(resolution)) sampling_str = re.sub(r"[\[\]\"\',]", "", str(sampling)) print( ( "\nFor just this file I suggest you run\n\tpython nIR_run.py -s {0} -b {1} -v {2} -R {3} " "--sample_rate {4}\nOr for all the combinations you ran here\n\tpython nIR_run.py -s {5}" " -b {6} -v {7} -R {8} --sample_rate {9}" "" ).format( star, band, vel, res, smpl, spectral_str, band_str, vsini_str, res_str, sampling_str, ) ) raise if len(wav_stellar) == 0 or len(flux_stellar) == 0: raise Exception("The file {0} is empty".format(file_to_read)) # Removing boundary effects wav_stellar = wav_stellar[2:-2] flux_stellar = flux_stellar[2:-2] # sample was left aside because only one value existed # TODO: Add metallicity and logg into id string id_string = "{0:s}-{1:s}-{2:.1f}-{3:s}".format(star, band, float(vel), res) # Getting the wav, flux and mask values from the atm model # that are the closest to the stellar wav values, see # https://stackoverflow.com/questions/2566412/find-nearest-value-in-numpy-array index_atm = np.searchsorted(wav_atm, wav_stellar) # replace indexes outside the array, at the very end, by the value at the very end # index_atm = [index if(index < len(wav_atm)) else len(wav_atm)-1 for index in index_atm] indx_mask = index_atm >= len(wav_atm) # find broken indexs index_atm[indx_mask] = len(wav_atm) - 1 # replace with index of end. wav_atm_selected = wav_atm[index_atm] flux_atm_selected = flux_atm[index_atm] mask_atm_selected = mask_atm[index_atm] # Check mask masks out deep atmosphere absorption if np.any(flux_atm_selected[mask_atm_selected] < 0.98): print( "####WARNGING####\nThis absorption mask does not mask out deep atmosphere transmission!" ) print( "Min flux_atm_selected[mask_atm_selected] = {} < 0.98\n####".format( np.min(flux_atm_selected[mask_atm_selected]) ) ) # Normalize to SNR 100 in middle of J band 1.25 micron! # flux_stellar = normalize_flux(flux_stellar, id_string) # flux_stellar = snrnorm.normalize_flux(flux_stellar, id_string, new=True) # snr=100, ref_band="J" flux_stellar = eniric.obsolete.snr_norm.normalize_flux( flux_stellar, id_string, new=new, snr=snr, ref_band=ref_band, sampling=smpl, ) if id_string in [ "M0-J-1.0-100k", "M3-J-1.0-100k", "M6-J-1.0-100k", "M9-J-1.0-100k", ]: index_ref = np.searchsorted( wav_stellar, 1.25 ) # searching for the index closer to 1.25 micron snr_estimate = np.sqrt( np.sum(flux_stellar[index_ref - 1 : index_ref + 2]) ) print( "\tSanity Check: The S/N for the {0:s} reference model was of {1:4.2f}.".format( id_string, snr_estimate ) ) elif "J" in id_string: index_ref = np.searchsorted( wav_stellar, 1.25 ) # searching for the index closer to 1.25 micron snr_estimate = np.sqrt( np.sum(flux_stellar[index_ref - 1 : index_ref + 2]) ) print( "\tSanity Check: The S/N for the {0:s} non-reference model was of {1:4.2f}.".format( id_string, snr_estimate ) ) # Precision given by the first method: print("Performing analysis for: ", id_string) prec_1 = Qcalculator.rv_precision(wav_stellar, flux_stellar, grad=grad) # Precision as given by the second_method wav_stellar_chunks, flux_stellar_chunks = eniric.legacy.mask_clumping( wav_stellar, flux_stellar, mask_atm_selected ) prec_2_old = eniric.legacy.RVprec_calc_masked( wav_stellar_chunks, flux_stellar_chunks, grad=grad ) prec_2 = eniric.legacy.RVprec_calc_masked( wav_stellar, flux_stellar, mask_atm_selected, grad=grad ) assert np.all(prec_2_old == prec_2) """ # histogram checking lengths = [len(chunk) for chunk in flux_stellar_chunks_unformatted] n, bins, patches = plt.hist(lengths, 500, range=[0.5, 500.5], histtype='stepfilled') plt.title(id_string) plt.show() """ # Precision as given by the third_method prec_3 = Qcalculator.rv_precision( wav_stellar, flux_stellar, mask=flux_atm_selected ** 2, grad=grad ) # Adding Precision results to the dictionary results[id_string] = [prec_1, prec_2, prec_3] # Prepare/Do for the plotting. if plot_ste or plot_ste == id_string: plt_functions.plot_stellar_spectum( wav_stellar, flux_stellar, wav_atm_selected, mask_atm_selected ) plot_ids = [ "M3-Z-1.0-100k", "M3-Y-1.0-100k", "M3-J-1.0-100k", "M3-H-1.0-100k", "M3-K-1.0-100k", ] if plot_flux and id_string in plot_ids: wav_plot_m0.append(wav_stellar) flux_plot_m0.append(flux_stellar) if plot_flux and id_string in plot_ids: wav_plot_m3.append(wav_stellar) flux_plot_m3.append(flux_stellar) if plot_flux and id_string in plot_ids: wav_plot_m6.append(wav_stellar) flux_plot_m6.append(flux_stellar) if plot_flux and id_string in plot_ids: wav_plot_m9.append(wav_stellar) flux_plot_m9.append(flux_stellar) if plot_flux: plt_functions.plot_nIR_flux() if paper_plots: plt_functions.plot_paper_plots() else: return results
def test_atmosphere_from_file(atm_model): atmos = Atmosphere.from_file(atmmodel=atm_model) assert len(atmos.wl) == len(atmos.transmission) assert len(atmos.transmission[atmos.mask]) != len( atmos.transmission) # mask is not all ones
def do_analysis( star_params, vsini: float, R: float, band: str, sampling: float = 3.0, conv_kwargs=None, snr: float = 100.0, ref_band: str = "J", rv: float = 0.0, air: bool = False, model: str = "aces", verbose: bool = False, ) -> Tuple[Quantity, ...]: """Calculate RV precision and Quality for specific parameter set. Parameters ---------- star_param: Stellar parameters [temp, logg, feh, alpha] for phoenix model libraries. vsini: float Stellar equatorial rotation. R: float Instrumental resolution. band: str Spectral band. sampling: float (default=False) Per pixel sampling (after convolutions) conv_kwargs: Dict (default=None) Arguments specific for the convolutions, 'epsilon', 'fwhm_lim', 'num_procs', 'normalize', 'verbose'. snr: float (default=100) SNR normalization level. SNR per pixel and the center of the ref_band. ref_band: str (default="J") Reference band for SNR normalization. rv: float Radial velocity in km/s (default = 0.0). air: bool Get model in air wavelengths (default=False). model: str Name of synthetic library (aces, btsettl) to use. Default = 'aces'. verbose: Enable verbose (default=False). Returns ------- q: astropy.Quality Spectral quality. result_1: astropy.Quality RV precision under condition 1. result_2 : astropy.Quality RV precision under condition 2. result_3: astropy.Quality RV precision under condition 3. Notes ----- We apply the radial velocity doppler shift after - convolution (rotation and resolution) - resampling - SNR normalization. in this way the RV only effects the precision due to the telluric mask interaction. Physically the RV should be applied between the rotational and instrumental convolution but we assume this effect is negligible. """ if conv_kwargs is None: conv_kwargs = { "epsilon": 0.6, "fwhm_lim": 5.0, "num_procs": num_cpu_minus_1, "normalize": True, "verbose": verbose, } if ref_band.upper() == "SELF": ref_band = band model = check_model(model) if model == "aces": wav, flux = load_aces_spectrum(star_params, photons=True, air=air) elif model == "btsettl": wav, flux = load_btsettl_spectrum(star_params, photons=True, air=air) else: raise Exception("Invalid model name reached.") wav_grid, sampled_flux = convolve_and_resample(wav, flux, vsini, R, band, sampling, **conv_kwargs) # Doppler shift try: if rv != 0: sampled_flux = doppler_shift_flux(wav_grid, sampled_flux, vel=rv) except Exception as e: print("Doppler shift was unsuccessful") raise e # Scale normalization for precision wav_ref, sampled_ref = convolve_and_resample(wav, flux, vsini, R, ref_band, sampling, **conv_kwargs) snr_normalize = snr_constant_band(wav_ref, sampled_ref, snr=snr, band=ref_band, sampling=sampling, verbose=verbose) sampled_flux = sampled_flux / snr_normalize if (ref_band == band) and verbose: mid_point = band_middle(ref_band) index_ref = np.searchsorted( wav_grid, mid_point) # searching for the index closer to 1.25 micron snr_estimate = np.sqrt( np.sum(sampled_flux[index_ref - 1:index_ref + 2])) print( "\tSanity Check: The S/N at {0:4.02} micron = {1:4.2f}, (should be {2:g})." .format(mid_point, snr_estimate, snr)) # Load Atmosphere for this band. atm = Atmosphere.from_band(band=band, bary=True).at(wav_grid) assert np.allclose(atm.wl, wav_grid), "The atmosphere does not cover the wav_grid" # Spectral Quality/Precision q = quality(wav_grid, sampled_flux) # Precision given by the first condition: result_1 = rv_precision(wav_grid, sampled_flux, mask=None) # Precision as given by the second condition result_2 = rv_precision(wav_grid, sampled_flux, mask=atm.mask) # Precision as given by the third condition: M = T**2 result_3 = rv_precision(wav_grid, sampled_flux, mask=atm.transmission**2) # Turn quality back into a Quantity (to give it a .value method) q = q * u.dimensionless_unscaled return q, result_1, result_2, result_3
def test_atmosphere_broaden(R, num_procs): atm = Atmosphere.from_band("K") atm = atm.wave_select(2.3, 2.301) print(len(atm.wl)) atm1 = atm.broaden(resolution=R, num_procs=num_procs) assert atm1 is None