def __init__(self, path, air=True, wl_range=(150, 300000 - 50)): super().__init__( name="CIFIST", points=[ np.concatenate( (np.arange(1200, 2351, 50), np.arange(2400, 7001, 100)), axis=0), np.arange(2.5, 5.6, 0.5), ], param_names=["T", "logg"], wave_units="AA", flux_units="", air=air, wl_range=wl_range, path=path, ) self.par_dicts = [None, None] self.rname = "lte{0:0>5.1f}-{1:.1f}-0.0a+0.0.BT-Settl.spec.fits.gz" self.full_rname = os.path.join(self.path, self.rname) wl_dict = create_log_lam_grid(dv=0.08, start=self.wl_range[0], end=self.wl_range[1]) self.wl = wl_dict["wl"]
def test_many_fluxes(self, mock_data): dv = calculate_dv(mock_data[0]) new_wave = create_log_lam_grid(dv, mock_data[0].min(), mock_data[0].max())["wl"] flux_stack = np.tile(mock_data[1], (4, 1)) fluxes = resample(mock_data[0], flux_stack, new_wave) assert fluxes.shape == (4, len(new_wave))
def __init__(self, path, air=True, wl_range=(2999, 13000)): super().__init__( name="BTSettl", points={ "T": np.arange(3000, 7001, 100), "logg": np.arange(2.5, 5.6, 0.5), "Z": np.arange(-0.5, 0.6, 0.5), "alpha": np.array([0.0]), }, wave_units="AA", flux_units="", air=air, wl_range=wl_range, path=path, ) # Normalize to 1 solar luminosity? self.rname = "CIFIST2011/M{Z:}/lte{T:0>3.0f}-{logg:.1f}{Z:}.BT-Settl.spec.7.bz2" self.full_rname = os.path.join(self.path, self.rname) # self.Z_dict = {-2:'-2.0', -1.5:'-1.5', -1:'-1.0', -0.5:'-0.5', 0.0: '-0.0', 0.5: '+0.5', 1: '+1.0'} self.Z_dict = {-0.5: "-0.5a+0.2", 0.0: "-0.0a+0.0", 0.5: "+0.5a0.0"} wl_dict = create_log_lam_grid(0.08 / C.c_kms, start=self.wl_range[0], end=self.wl_range[1]) self.wl = wl_dict["wl"]
def __init__( self, emulator: Union[str, Emulator], data: Union[str, Spectrum], grid_params: Sequence[float], max_deque_len: int = 100, name: str = "SpectrumModel", **params, ): if isinstance(emulator, str): emulator = Emulator.load(emulator) if isinstance(data, str): data = Spectrum.load(data) if len(data) > 1: raise ValueError( "Multiple orders detected in data, please use EchelleModel") self.emulator: Emulator = emulator self.data_name = data.name self.data = data[0] dv = calculate_dv(self.data.wave) self.min_dv_wave = create_log_lam_grid(dv, self.emulator.wl.min(), self.emulator.wl.max())["wl"] self.bulk_fluxes = resample(self.emulator.wl, self.emulator.bulk_fluxes, self.min_dv_wave) self.residuals = deque(maxlen=max_deque_len) # manually handle cheb coeffs to offset index by 1 chebs = params.pop("cheb", []) cheb_idxs = [str(i) for i in range(1, len(chebs) + 1)] params["cheb"] = dict(zip(cheb_idxs, chebs)) # load rest of params into FlatterDict self.params = FlatterDict(params) self.frozen = [] self.name = name # Unpack the grid parameters self.n_grid_params = len(grid_params) self.grid_params = grid_params # None means "yet to be calculated", do not use NaN self._lnprob = None self._glob_cov = None self._loc_cov = None self.log = logging.getLogger(self.__class__.__name__) self.flux_scalar_func = flux_scalar = LinearNDInterpolator( self.emulator.grid_points, self.emulator.flux_scalar)
def test_grid_dv_regression(dv): grid = create_log_lam_grid(dv, 1e4, 4e4) dv_ = calculate_dv(grid["wl"]) dv_d = calculate_dv_dict(grid) assert np.isclose(dv_, dv_d) assert dv_ <= dv
def test_invalid_points_grid(start, end): with pytest.raises(ValueError): create_log_lam_grid(1000, start, end)
def test_grid_keys(): grid = create_log_lam_grid(1000, 3000, 3e4) assert "wl" in grid assert "CRVAL1" in grid assert "CDELT1" in grid assert "NAXIS1" in grid
def test_benchmark(self, benchmark, benchmark_data): wave, flux = benchmark_data dv = calculate_dv(wave) new_wave = create_log_lam_grid(dv, wave.min(), wave.max())["wl"] benchmark(resample, wave, flux, new_wave)
def test_resample(self, mock_data): dv = calculate_dv(mock_data[0]) new_wave = create_log_lam_grid(dv, mock_data[0].min(), mock_data[0].max())["wl"] flux = resample(*mock_data, new_wave) assert flux.shape == new_wave.shape
def __init__( self, grid_interface, filename, instrument=None, wl_range=None, ranges=None, key_name=None, ): self.log = logging.getLogger(self.__class__.__name__) self.grid_interface = grid_interface self.filename = os.path.expandvars(filename) self.instrument = instrument # The flux formatting key will always have alpha in the name, regardless # of whether or not the library uses it as a parameter. if key_name is None: key_name = ( self.grid_interface.rname.replace("/", "__") .replace(".fits", "") .replace(".FITS", "") ) self.key_name = key_name if ranges is None: self.points = self.grid_interface.points else: # Take only those points of the GridInterface that fall within the ranges specified self.points = [] # We know which subset we want, so use these. for i, (low, high) in enumerate(ranges): valid_points = self.grid_interface.points[i] ind = (valid_points >= low) & (valid_points <= high) self.points.append(valid_points[ind]) # Note that at this point, this is just the grid points that fall within the rectangular # bounds set by ranges. If the raw library is actually irregular (e.g. CIFIST), # then self.points will contain points that don't actually exist in the raw library. # the raw wl from the spectral library self.wl_native = self.grid_interface.wl # raw grid self.dv_native = calculate_dv(self.wl_native) self.hdf5 = h5py.File(self.filename, "w") self.hdf5.attrs["grid_name"] = grid_interface.name self.flux_group = self.hdf5.create_group("flux") self.flux_group.attrs["units"] = grid_interface.flux_units self.flux_group.attrs["key_name"] = self.key_name # We'll need a few wavelength grids # 1. The original synthetic grid: ``self.wl_native`` # 2. A finely spaced log-lambda grid respecting the ``dv`` of # ``self.wl_native``, onto which we can interpolate the flux values # in preperation of the FFT: ``self.wl_loglam`` # [ DO FFT ] # 3. A log-lambda spaced grid onto which we can downsample the result # of the FFT, spaced with a ``dv`` such that we respect the remaining # Fourier modes: ``self.wl_final`` # There are three ranges to consider when wanting to make a grid: # 1. The full range of the synthetic library # 2. The full range of the instrument/dataset # 3. The range specified by the user in config.yaml # For speed reasons, we will always truncate to to wl_range. If either # the synthetic library or the instrument library is smaller than this range, # raise an error. if wl_range is None: wl_min, wl_max = 0, np.inf else: wl_min, wl_max = wl_range buffer = 50 # [AA] wl_min -= buffer wl_max += buffer # If the raw synthetic grid doesn't span the full range of the user # specified grid, raise an error. # Instead, let's choose the maximum limit of the synthetic grid? if self.instrument is not None: inst_min, inst_max = self.instrument.wl_range else: inst_min, inst_max = 0, np.inf imposed_min = np.max([self.wl_native[0], inst_min]) imposed_max = np.min([self.wl_native[-1], inst_max]) if wl_min < imposed_min: self.log.info( "Given minimum wavelength ({}) is less than instrument or grid minimum. Truncating to {}".format( wl_min, imposed_min ) ) wl_min = imposed_min if wl_max > imposed_max: self.log.info( "Given maximum wavelength ({}) is greater than instrument or grid maximum. Truncating to {}".format( wl_max, imposed_max ) ) wl_max = imposed_max if wl_max < wl_min: raise ValueError("Minimum wavelength must be less than maximum wavelength") # Calculate wl_loglam # use the dv that preserves the native quality of the raw PHOENIX grid wl_dict = create_log_lam_grid(self.dv_native, wl_min, wl_max) wl_loglam = wl_dict["wl"] # - wl_FFT dv_loglam = calculate_dv_dict(wl_dict) # - dv_FFT self.log.info( "FFT grid stretches from {} to {}".format(wl_loglam[0], wl_loglam[-1]) ) self.log.info("wl_loglam dv is {} km/s".format(dv_loglam)) if self.instrument is None: mask = (self.wl_native > wl_min) & (self.wl_native < wl_max) self.wl_final = self.wl_native[mask] self.dv_final = self.dv_native def inst_broaden(w, f): return (w, f) else: # The final wavelength grid, onto which we will interpolate the # Fourier filtered wavelengths, is part of the instrument object dv_temp = self.instrument.FWHM / self.instrument.oversampling wl_dict = create_log_lam_grid(dv_temp, wl_min, wl_max) self.wl_final = wl_dict["wl"] self.dv_final = calculate_dv_dict(wl_dict) if (self.instrument is None) or ('SPEX_PRISM' not in self.instrument.name): def inst_broaden(w, f): return (w, instrumental_broaden(w, f, self.instrument.FWHM)) else: def inst_broaden(w, f): return (self.wl_final, instrumental_broaden(wave=w, flux=f, fwhm=self.instrument.FWHM, res_gradient= self.instrument.res_gradient, res_wl_step=8, wave_final=self.wl_final)) def resample_loglam(w, f): return (wl_loglam, resample(w, f, wl_loglam)) # - fl def resample_final(w, f): return (self.wl_final, resample(w, f, self.wl_final)) if 'SPEX_PRISM' not in self.instrument.name: self.transform = lambda flux: resample_final( *inst_broaden(*resample_loglam(self.wl_native, flux)) ) else: self.transform = lambda flux: inst_broaden( *resample_loglam(self.wl_native, flux) ) # Create the wl dataset separately using float64 due to rounding errors w/ interpolation. wl_dset = self.hdf5.create_dataset("wl", data=self.wl_final, compression=9) wl_dset.attrs["air"] = self.grid_interface.air wl_dset.attrs["dv"] = self.dv_final wl_dset.attrs["units"] = self.grid_interface.wave_units
def mock_data(mock_hdf5_interface): wave = mock_hdf5_interface.wl new_wave = create_log_lam_grid(1e3, wave.min(), wave.max())["wl"] flux = resample(wave, next(mock_hdf5_interface.fluxes), new_wave) yield new_wave, flux