From 40298d494356429015256f7f0345d401668990c1 Mon Sep 17 00:00:00 2001 From: Allan Denis Date: Fri, 13 Dec 2024 17:48:36 +0100 Subject: [PATCH] AD : Multiple modifications, including : adaptation of the model on a slightly larger wavelength grid, cleaning of the plotting_class function, removing of the lsq function replaced by a forward_model script, possibility of the user to chose (and custom) its own lsq_function to disentangle between planet data and star speckles data --- ForMoSA/adapt/adapt_grid.py | 1 + ForMoSA/adapt/adapt_obs_mod.py | 25 +- ForMoSA/main_utilities.py | 31 ++- ForMoSA/nested_sampling/forward_models.py | 257 ++++++++++++++++++ .../nested_sampling/nested_logL_functions.py | 2 +- ForMoSA/nested_sampling/nested_modif_spec.py | 185 ++----------- ForMoSA/nested_sampling/nested_sampling.py | 117 +++++--- ForMoSA/plotting/plotting_class.py | 92 +++---- RELEASE_NOTES.txt | 19 +- 9 files changed, 471 insertions(+), 258 deletions(-) create mode 100644 ForMoSA/nested_sampling/forward_models.py diff --git a/ForMoSA/adapt/adapt_grid.py b/ForMoSA/adapt/adapt_grid.py index 320b842..53cbed6 100755 --- a/ForMoSA/adapt/adapt_grid.py +++ b/ForMoSA/adapt/adapt_grid.py @@ -124,6 +124,7 @@ def adapt_grid(global_params, res_mod_obs, wav_obs_spectro, res_obs_spectro, wav wav_mod_nativ = ds["wavelength"].values grid = ds['grid'] attr = ds.attrs + attr['res'] = res_obs_spectro grid_np = grid.to_numpy() # create arrays without any assumptions on the number of parameters diff --git a/ForMoSA/adapt/adapt_obs_mod.py b/ForMoSA/adapt/adapt_obs_mod.py index 05e0fbc..5b52848 100755 --- a/ForMoSA/adapt/adapt_obs_mod.py +++ b/ForMoSA/adapt/adapt_obs_mod.py @@ -32,7 +32,7 @@ def launch_adapt(global_params, justobs='no'): res_mod_nativ = attr['res'] ds.close() - # Check if the grid is Nyquist-sampled, else set the resolution to R = wav / 2 Deltawav to make sure we are adding any info + # Check if the grid is Nyquist-sampled, else set the resolution to R = wav / 2 Deltawav to make sure we are adding any info dwav = np.abs(wav_mod_nativ - np.roll(wav_mod_nativ, 1)) dwav[0] = dwav[1] res_Nyquist = wav_mod_nativ / (2 * dwav) @@ -61,11 +61,28 @@ def launch_adapt(global_params, justobs='no'): # Interpolate the resolution onto the wavelength of the data if len(obs_spectro[0]) != 0: - mask_mod_obs = (wav_mod_nativ <= obs_spectro[0][-1]) & (wav_mod_nativ > obs_spectro[0][0]) + if (global_params.rv[indobs*3] == 'NA') or (global_params.rv == 'NA'): + mask_mod_obs = (wav_mod_nativ <= obs_spectro[0][-1]) & (wav_mod_nativ > obs_spectro[0][0]) + else: + # If the user defined an RV prior, we slightly modify the strategy for the adaptation of the model + # Instead of adapting the model to the wavelength of the observations, we adapt the model to an extended version of the wavelength range of the observation + # so that we do not lose data on the edges of the wavelength of the observations when applying the RV correction + mask_mod_obs = (wav_mod_nativ <= 1.02 * obs_spectro[0][-1]) & (wav_mod_nativ >= 0.98 * obs_spectro[0][0]) + wav_obs_spectro = obs_spectro[0] + res_obs_spectro = obs_spectro[3] + while wav_obs_spectro[0] > 0.98 * obs_spectro[0][0]: + wav_obs_spectro = np.insert(wav_obs_spectro, 0, 2*wav_obs_spectro[0]-wav_obs_spectro[1]) + wav_obs_spectro = np.append(wav_obs_spectro, 2*wav_obs_spectro[-1]-wav_obs_spectro[-2]) + res_obs_spectro = np.insert(res_obs_spectro, 0, res_obs_spectro[0]) + res_obs_spectro = np.append(res_obs_spectro, res_obs_spectro[-1]) + while wav_obs_spectro[-1] < 1.02 * obs_spectro[0][-1]: + wav_obs_spectro = np.append(wav_obs_spectro, 2 * wav_obs_spectro[-1]-wav_obs_spectro[-2]) + res_obs_spectro = np.append(res_obs_spectro, res_obs_spectro[-1]) + wav_mod_cut = wav_mod_nativ[mask_mod_obs] res_mod_cut = res_mod_nativ[mask_mod_obs] interp_mod_to_obs = interp1d(wav_mod_cut, res_mod_cut, fill_value='extrapolate') - res_mod_obs = interp_mod_to_obs(obs_spectro[0]) + res_mod_obs = interp_mod_to_obs(wav_obs_spectro) else: res_mod_obs = np.asarray([]) @@ -103,7 +120,7 @@ def launch_adapt(global_params, justobs='no'): print('- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -') print(f"-> Sarting the adaptation of {obs_name}") - adapt_grid(global_params, res_mod_obs, obs_spectro[0], obs_spectro[3], obs_photo[0], obs_photo_ins, obs_name=obs_name, indobs=indobs) + adapt_grid(global_params, res_mod_obs, wav_obs_spectro, res_obs_spectro, obs_photo[0], obs_photo_ins, obs_name=obs_name, indobs=indobs) # ---------------------------------------------------------------------------------------------------------------------- diff --git a/ForMoSA/main_utilities.py b/ForMoSA/main_utilities.py index e1f9792..5b51f4a 100755 --- a/ForMoSA/main_utilities.py +++ b/ForMoSA/main_utilities.py @@ -51,18 +51,28 @@ def __init__(self, config_file_path): model_name = model_name[0] self.model_name = model_name - if type(config['config_adapt']['wav_for_adapt']) != list: # Create lists if only one obs in the loop + if type(config['config_adapt']['wav_for_adapt']) != list: # Create lists if only one obs in the loop # [config_adapt] (5) self.wav_for_adapt = [config['config_adapt']['wav_for_adapt']] self.adapt_method = [config['config_adapt']['adapt_method']] self.custom_reso = [config['config_adapt']['custom_reso']] self.continuum_sub = [config['config_adapt']['continuum_sub']] self.wav_for_continuum = [config['config_adapt']['wav_for_continuum']] - self.use_lsqr = [config['config_adapt']['use_lsqr']] # [config_inversion] (4) self.logL_type = [config['config_inversion']['logL_type']] self.wav_fit = [config['config_inversion']['wav_fit']] + + # [config_forward_models] (3) + try: + self.fm_type = [config['config_forward_models']['fm_type']] + self.fm_continuum_res = [config['config_forward_models']['fm_continuum_res']] + self.bounds_lsq = [config['config_forward_models']['bounds_lsq']] + except KeyError: + self.fm_type = 'NA' + self.fm_continuum_res = 'NA' + self.bounds_lsq = 'NA' + else: # [config_adapt] (5) self.wav_for_adapt = config['config_adapt']['wav_for_adapt'] @@ -70,11 +80,22 @@ def __init__(self, config_file_path): self.custom_reso = config['config_adapt']['custom_reso'] self.continuum_sub = config['config_adapt']['continuum_sub'] self.wav_for_continuum = config['config_adapt']['wav_for_continuum'] - self.use_lsqr = config['config_adapt']['use_lsqr'] # [config_inversion] (4) self.logL_type = config['config_inversion']['logL_type'] self.wav_fit = config['config_inversion']['wav_fit'] + + # [config_forwarf_models] (3) + try: + self.fm_type = config['config_forward_models']['fm_type'] + self.fm_continuum_res = config['config_forward_models']['fm_continuum_res'] + self.bounds_lsq = config['config_forward_models']['bounds_lsq'] + except KeyError: + self.fm_type = 'NA' + self.fm_continuum_res = 'NA' + self.bounds_lsq = 'NA' + + # # parallelisation of adapt try: @@ -129,7 +150,7 @@ def __init__(self, config_file_path): # self.p_context = eval(config['config_pymultinest']['context']) # self.p_write_output = config['config_pymultinest']['write_output'] # self.p_log_zero = eval(config['config_pymultinest']['log_zero']) - # self.p_max_iter = eval(config['config_pymultinest']['max_iter']) + # self.p_max_iter = eval(config['config_pymultinest']['max_iter']) # self.p_init_MPI = config['config_pymultinest']['init_MPI'] # self.p_dump_callback = config['config_pymultinest']['dump_callback'] # self.p_use_MPI = config['config_pymultinest']['use_MPI'] @@ -158,7 +179,7 @@ def __init__(self, config_file_path): # print() # # config_current = self.result_path + '/past_config.ini' - # config.filename = ' ' + # config.filename = ' ' # config['config_path']['stock_interp_grid'] = stock_interp_grid # config['config_path']['stock_result'] = stock_result_subsub_dir # config.write() diff --git a/ForMoSA/nested_sampling/forward_models.py b/ForMoSA/nested_sampling/forward_models.py new file mode 100644 index 0000000..8db629f --- /dev/null +++ b/ForMoSA/nested_sampling/forward_models.py @@ -0,0 +1,257 @@ +import numpy as np +import scipy.optimize as optimize +from ForMoSA.adapt.extraction_functions import continuum_estimate +import ForMoSA.utils as utils + + +def forward_model(global_params, wav_mod_spectro, res_mod_spectro, flx_cont_obs, flx_mod, star_flx_obs, star_flx_cont_obs, err_obs, transm_obs, flx_obs, system_obs, indobs): + ''' + For high-contrast companions, where the star speckles signal contaminate the data + + Args: + global_params (object): Class containing each parameter used in ForMoSA + wav_mod_spectro (array): Wavelength grid of the model + res_mod_spectro (array): Resolution grid of the model + flx_cont_obs (array): Continuum of the data + flx_mod (array): Model of the companion + star_flx_obs (array): Star data + star_flx_cont_obs (array): Continuum of star data + err_obs (array): Noise of the data + transm_obs (array): Transmission + flx_obs (array): Data + system_obs (array): Systematics + + Authors: Allan Denis + ''' + + flx_mod *= transm_obs + flx_cont_mod = continuum_estimate( + global_params, wav_mod_spectro, flx_mod, res_mod_spectro, indobs) + + star_flx_obs_master = star_flx_obs[:, len(star_flx_obs[0]) // 2] + + bounds = (float(global_params.bounds_lsq[indobs][1:-1].split(',')[0]), + float(global_params.bounds_lsq[indobs][1:-1].split(',')[1])) + + if global_params.fm_type[indobs] == 'nonlinear_fit_spec': + res, flx_mod, flx_obs, star_flx_obs, system_obs = forward_model_nonlinear_estimate_speckles( + flx_cont_obs, flx_mod, flx_cont_mod, star_flx_obs_master, star_flx_obs, star_flx_cont_obs, err_obs, flx_obs, system_obs, bounds) + elif global_params.fm_type[indobs] == 'fit_spec': + res, flx_mod, flx_obs, star_flx_obs, system_obs = forward_model_estimate_speckles( + flx_cont_obs, flx_mod, flx_cont_mod, star_flx_obs_master, star_flx_obs, star_flx_cont_obs, err_obs, flx_obs, system_obs, bounds) + elif global_params.fm_type[indobs] == 'rm_spec': + res, flx_mod, flx_obs, star_flx_obs, system_obs = forward_model_remove_speckles( + flx_cont_obs, flx_mod, flx_cont_mod, star_flx_obs_master, star_flx_obs, star_flx_cont_obs, err_obs, flx_obs, system_obs, bounds) + elif global_params.fm_type[indobs] == 'fit_spec_rm_cont': + res, flx_mod, flx_obs, star_flx_obs, system_obs = forward_model_estimate_speckles_remove_continuum( + flx_cont_obs, flx_mod, flx_cont_mod, star_flx_obs_master, star_flx_obs, star_flx_cont_obs, err_obs, flx_obs, system_obs, bounds) + elif global_params.fm_type[indobs] == 'fit_spec_fit_cont': + raise Exception( + 'Continuum fitting-based forward model no implement yet ! Please use another function') + + return res, flx_mod, flx_obs, star_flx_obs, system_obs + + +def forward_model_nonlinear_estimate_speckles(flx_cont_obs, flx_mod, flx_cont_mod, star_flx_obs_master, star_flx_obs, star_flx_cont_obs, err_obs, flx_obs, system_obs, bounds): + ''' + Non linear forward model of planet and star contributions (see Landman et al. 2023) + + Args: + flx_cont_obs: Continuum of the data + flx_mod: Model of the companion + flx_cont_mod: Continuum of the model of the companion + star_flx_obs_master: Master star data + star_flx_obs: Star data + star_flx_cont_obs: Continuum of star data + err_obs: Noise of the data + flx_obs: Data + + Authors: Allan Denis + ''' + + ind_star = 1 + len(star_flx_obs[0]) + + # # # # # # # Solve non linear Least Squares full_model(theta) = flx_obs + + # Definition of f + def f(theta): + return 1 / err_obs * (theta[0] * flx_mod + np.dot(theta[1:ind_star], star_flx_obs / star_flx_cont_obs * (flx_cont_obs - theta[0] * flx_cont_mod)) + np.dot(theta[ind_star:], system_obs) - flx_obs) + + # Solve non linear Least Squares + # Initial guess for the planetary contribution + theta0 = [0] + for i in range(len(star_flx_obs[0])): + # Arbitrary initial guesses for star speckles contribution + theta0.append((i / len(star_flx_obs[0]))**2) + for i in range(len(system_obs[0])): + # Arbitrary initial guesses for systematics contribution + theta0.append(1) + + # Solve non linear Least Squaresr the assumtion that the star speckles dominate the data + res = optimize.least_squares(f, theta0) + + # Full model + flx_mod_full = f(res.x)*err_obs + flx_obs + star_flx_obs = np.dot(res.x[1:ind_star], star_flx_obs / star_flx_cont_obs * flx_cont_obs) + system_obs = np.dot(res.x[ind_star:], system_obs) + + return res.x, flx_mod_full, flx_obs, star_flx_obs, system_obs + + +def forward_model_estimate_speckles(flx_cont_obs, flx_mod, flx_cont_mod, star_flx_obs_master, star_flx_obs, star_flx_cont_obs, err_obs, flx_obs, system_obs, bounds): + ''' + Linear forward model of planet and star contributions under the assumtion that the star speckles dominate the data (see Landman et al. 2023) + + Args: + flx_cont_obs (array): Continuum of the data + flx_mod (array): Model of the companion + flx_cont_mod (array): Continuum of the model of the companion + star_flx_obs_master (array): Master star data + star_flx_obs (array): Star data + star_flx_cont_obs (array): Continuum of star data + err_obs (array): Noise of the data + flx_obs (array): Data + system_obs (array): Systematics + + Authors: Allan Denis + ''' + + ind_star = 1 + len(star_flx_obs[0]) + if len(system_obs) > 0: + ind_system = ind_star + len(system_obs[0]) + else: + ind_system = ind_star + + # # # # # # Solve linear Least Squares A.x = b + + # Build matrix A + A = np.zeros([np.size(flx_obs), ind_system]) + A[:, 0] = 1 / err_obs * (flx_mod - flx_cont_mod * + star_flx_obs_master / star_flx_cont_obs) + + for star_i in range(len(star_flx_obs[0])): + A[:, star_i + 1] = 1 / err_obs * (star_flx_obs[:, star_i] / star_flx_cont_obs * flx_cont_obs) + + for system_i in range(ind_system - ind_star): + A[:, system_i + ind_star] = 1 / err_obs * system_obs[:, system_i] + + # Build vector b + b = 1 / err_obs * flx_obs + # Solve linear Least Squares + res = optimize.lsq_linear(A, b, bounds=bounds) + + # Full model + flx_mod_full = np.dot(A, res.x) * err_obs + star_flx_obs = np.dot(A[:, 1:ind_star], res.x[1:ind_star]) * err_obs + system_obs = np.dot(A[:, ind_star:], res.x[ind_star:]) + + return res.x, flx_mod_full, flx_obs, star_flx_obs, system_obs + + +def forward_model_remove_speckles(flx_cont_obs, flx_mod, flx_cont_mod, star_flx_obs_master, star_flx_cont_obs, err_obs, flx_obs, system_obs, bounds): + ''' + Linear forward model of planet contribution only where the speckles are filtered out from the data (see Landman et al. 2023) + + Args: + flx_cont_obs: Continuum of the data + flx_mod: Model of the companion + flx_cont_mod: Continuum of the model of the companion + star_flx_obs_master: Master star data + star_flx_cont_obs: Continuum of star data + err_obs: Noise of the data + flx_obs: Data + system_obs (array): Systematics + + Authors: Allan Denis + ''' + + if len(system_obs) > 0: + ind_system = 1 + len(system_obs[0]) + else: + ind_system = 1 + + # # # # # # # Solve linear Least Squared A.x = b + A = np.zeros([np.size(flx_obs), ind_system]) + + # Build matrix A + A[:, 0] = 1 / err_obs * (flx_mod - flx_cont_mod * + star_flx_obs_master / star_flx_cont_obs) + + for system_i in range(ind_system-1): + A[:, system_i + 1] = 1 / err_obs * system_obs[:, system_i] + + # Build vector b + b = 1 / err_obs * (flx_obs - star_flx_obs_master / + star_flx_cont_obs * flx_cont_obs) + + # Solve linear Least Squared + res = optimize.lsq_linear(A, b, bounds=bounds) + + # Full model + star_flx_obs = star_flx_obs_master / star_flx_cont_obs + flx_cont_obs + flx_mod_full = np.dot(A[:,0], res.x[0])*err_obs + star_flx_obs + system_obs = np.dot(A[:, 1:], res.x[1:]) + + return res.x, flx_mod_full, flx_obs, star_flx_obs, system_obs + + +def forward_model_estimate_speckles_remove_continuum(flx_cont_obs, flx_mod, flx_cont_mod, star_flx_obs_master, star_flx_obs, star_flx_cont_obs, err_obs, flx_obs, system_obs, bounds): + ''' + Linear forward model of planet and star contributions where we remove the continuums (see Wang et al. 2021) + + Args: + flx_cont_obs: Continuum of the data + flx_mod: Model of the companion + flx_cont_mod: Continuum of the mdoel of the companion + star_flx_obs_master: Master star data + star_flx_obs: Star data + star_flx_cont_obs: Continuum of star data + err_obs: Noise of the data + flx_obs : Data + system_obs (array): Systematics + + Authors: Allan Denis + ''' + ind_star = 1 + len(star_flx_obs[0]) + if len(system_obs) > 0: + ind_system = ind_star + len(system_obs[0]) + else: + ind_system = ind_star + + + # # # # # # Solve linear Least Squares A.x = b + + # Build matrix A + A = np.zeros([np.size(flx_obs), ind_system]) + A[:, 0] = 1 / err_obs * (flx_mod - flx_cont_mod + np.mean(flx_mod)) + + for star_i in range(len(star_flx_obs[0])): + A[:, star_i+1] = 1 / err_obs * (star_flx_obs[:, star_i] - star_flx_cont_obs + np.mean(star_flx_obs[:, star_i])) + + for system_i in range(ind_system-ind_star): + A[:, system_i + ind_star] = 1 / err_obs * system_obs[:, system_i] + + # Build vector b + b = 1 / err_obs * (flx_obs - flx_cont_obs + np.mean(flx_obs)) + + # Solve linear Least Squares + res = optimize.lsq_linear(A, b, bounds=bounds) + + # Full model + flx_mod_full = np.dot(A, res.x) * err_obs + flx_obs = b * err_obs + star_flx_obs = np.dot(A[:, 1:ind_star], res.x[1:ind_star]) + system_obs = np.dot(A[:, ind_star:], res.x[ind_star:]) + + return res.x, flx_mod_full, flx_obs, star_flx_obs, system_obs + + +def forward_model_estimate_speckles_estimate_continuum(): + ''' + Linear forward model of planet and star contributions where we fit the continuum + To Be Defined + + Authors: Allan Denis + ''' + + return diff --git a/ForMoSA/nested_sampling/nested_logL_functions.py b/ForMoSA/nested_sampling/nested_logL_functions.py index 6e581b3..bd508ab 100755 --- a/ForMoSA/nested_sampling/nested_logL_functions.py +++ b/ForMoSA/nested_sampling/nested_logL_functions.py @@ -57,7 +57,7 @@ def logL_chi2_extended(delta_flx, err): N = len(delta_flx) chi2 = np.nansum((delta_flx / err) ** 2) s2 = 1/N * chi2 - logL = -(chi2 / (2*s2) + N/2 * np.log(2*np.pi*s2) + 1/2 * np.log(np.dot(err,err))) + logL = -(chi2 / (2*s2) + N/2 * np.log(2*np.pi*s2)) return logL diff --git a/ForMoSA/nested_sampling/nested_modif_spec.py b/ForMoSA/nested_sampling/nested_modif_spec.py index c337ced..239f5e5 100755 --- a/ForMoSA/nested_sampling/nested_modif_spec.py +++ b/ForMoSA/nested_sampling/nested_modif_spec.py @@ -4,135 +4,9 @@ import astropy.units as u import astropy.constants as const from PyAstronomy.pyasl import rotBroad, fastRotBroad -import scipy.signal as sg -import scipy.optimize as optimize -import matplotlib.pyplot as plt -import time -import scipy +import ForMoSA.nested_sampling.forward_models as fm # ---------------------------------------------------------------------------------------------------------------------- - -def lsq_fct(global_params, wave, indobs, flx_obs_spectro, err_obs_spectro, star_flx_obs, transm_obs, flx_mod_spectro, system_obs, ccf_method = 'continuum_unfiltered'): - """ - Estimation of the contribution of the planet and of the star to a spectrum (Used for HiRISE data) - - Args: - flx_obs_spectro (array): Flux of the data (spectroscopy) - err_obs_spectro (array): Error of the data (spectroscopy) - star_flx_obs (n-array): Flux of star observation data (spectroscopy) - transm_obs (array): Transmission (Atmospheric + Instrumental) - system_obs (n-array): Systematics of the data (spectroscopy) - flx_mod_spectro (array): Flux of interpolated synthetic spectrum (spectroscopy) - Returns: - - cp (array) : Planetary contribution to the data (Spectroscopy) - - cs (array) : Stellar contribution to the data (Spectroscopy) - - flx_mod_spectro (array) : New model of the companion - - flx_obs_spectro (array) : New flux of the data - - star_flx_obs (n-array) : New star flux of the data - - systematics (array) : The systematics - - Author : Allan Denis - - """ - - wave_final, cp_final, cs_final, flx_mod_spectro_final, flx_obs_spectro_final, star_flx_obs_final, systematics_final, flx_mod_spectro_nativ, err_obs_spectro_final = np.array([]), np.array([]), np.array([]), np.array([]), np.array([]), np.array([]), np.array([]), np.array([]), np.array([]) - - for wave_fit_i in global_params.wav_fit[indobs].split('/'): - min_wave_i = float(wave_fit_i.split(',')[0]) - max_wave_i = float(wave_fit_i.split(',')[1]) - ind = np.where((wave <= max_wave_i) & (wave >= min_wave_i)) - - wave_ind = wave[ind] - flx_mod_spectro_ind = flx_mod_spectro[ind] - transm_obs_ind = transm_obs[ind] - star_flx_obs_ind = star_flx_obs[ind,:][0] - star_flx_0_ind = star_flx_obs_ind[:,len(star_flx_obs_ind[0]) // 2] - flx_obs_spectro_ind = flx_obs_spectro[ind] - err_obs_spectro_ind = err_obs_spectro[ind] - - if len(system_obs) > 0: - system_obs_ind = system_obs[ind,:][0] - - flx_mod_spectro_ind *= transm_obs_ind - star_flx_0_ind = star_flx_obs_ind[:,len(star_flx_obs_ind[0]) // 2] - - # # # # # Continuum estimation with lowpass filtering - # - # Low-pass filtering - flx_obs_spectro_continuum = sg.savgol_filter(flx_obs_spectro_ind, 301, 2) - star_flx_obs_continuum = sg.savgol_filter(star_flx_0_ind, 301, 2) - flx_mod_spectro_continuum = sg.savgol_filter(flx_mod_spectro_ind, 301, 2) - # - # # # # # - - if ccf_method == 'continuum_filtered': - # Removal of low-pass filtered data - flx_obs_spectro_ind = flx_obs_spectro_ind - flx_obs_spectro_continuum + np.nanmedian(flx_obs_spectro_ind) - star_flx_obs_ind = star_flx_obs_ind - star_flx_obs_continuum + np.nanmedian(star_flx_obs_ind) - flx_mod_spectro_ind = flx_mod_spectro_ind - flx_mod_spectro_continuum + np.nanmedian(flx_mod_spectro_ind) - elif ccf_method == 'continuum_unfiltered': - flx_mod_spectro_ind = flx_mod_spectro_ind - flx_mod_spectro_continuum * star_flx_0_ind / star_flx_obs_continuum - for i in range(len(star_flx_obs_ind[0])): - star_flx_obs_ind[:,i] = star_flx_obs_ind[:,i] * flx_obs_spectro_continuum / star_flx_obs_continuum - - - # # # # # Least squares estimation - # - # Construction of the matrix - if len(system_obs) > 0: - A = np.zeros([np.size(flx_obs_spectro_ind), 1 + len(star_flx_obs_ind[0]) + len(system_obs_ind[0])]) - for j in range(len(system_obs[0])): - A[:,1+len(star_flx_obs_ind[0])+j] = system_obs_ind[:,j] - else: - A = np.zeros([np.size(flx_obs_spectro_ind), 1 + len(star_flx_obs_ind[0])]) - - for j in range(len(star_flx_obs[0])): - A[:,1+j] = star_flx_obs_ind[:,j] * 1 / err_obs_spectro_ind - - A[:,0] = flx_mod_spectro_ind * 1 / err_obs_spectro_ind - - # Least square - # Solve the linear system A.x = b - b = flx_obs_spectro_ind * 1 / err_obs_spectro_ind - res = optimize.lsq_linear(A, b, bounds = (0, 1)) - - cp_ind = res.x[0] - - - cs_ind = np.array([]) - for i in range(len(star_flx_obs[0])): - cs_ind = np.append(cs_ind, res.x[i+1]) - - systematics_c = np.array([]) - systematics_ind = np.asarray([]) - if len(system_obs) > 0: - for i in range(len(system_obs[0])): - systematics_c = np.append(systematics_c, res.x[1+len(star_flx_obs_ind[0])+i]) - - systematics_ind = np.dot(systematics_c, system_obs_ind.T) - - star_flx_obs_ind = np.dot(cs_ind, star_flx_obs_ind.T) - - flx_mod_spectro_nativ_ind = np.copy(flx_mod_spectro_ind) - flx_mod_spectro_ind *= cp_ind - # - # # # # # - - # Generate final products - - wave_final = np.append(wave_final, wave_ind) - cp_final = np.append(cp_final, cp_ind) - cs_final = np.append(cs_final, cs_ind) - flx_mod_spectro_final = np.append(flx_mod_spectro_final, flx_mod_spectro_ind) - flx_obs_spectro_final = np.append(flx_obs_spectro_final, flx_obs_spectro_ind) - star_flx_obs_final = np.append(star_flx_obs_final, star_flx_obs_ind) - systematics_final = np.append(systematics_final, systematics_ind) - flx_mod_spectro_nativ = np.append(flx_mod_spectro_nativ, flx_mod_spectro_nativ_ind) - err_obs_spectro_final = np.append(err_obs_spectro_final, err_obs_spectro_ind) - - return cp_final, cs_final, flx_mod_spectro_final, flx_obs_spectro_final, star_flx_obs_final, systematics_final, flx_mod_spectro_nativ, wave_final, err_obs_spectro_final - - def calc_ck(flx_obs_spectro, err_obs_spectro, flx_mod_spectro, flx_obs_photo, err_obs_photo, flx_mod_photo, r_picked, d_picked, alpha=1, analytic='no'): """ @@ -190,30 +64,32 @@ def calc_ck(flx_obs_spectro, err_obs_spectro, flx_mod_spectro, flx_obs_photo, er # ---------------------------------------------------------------------------------------------------------------------- -def doppler_fct(wav_obs_spectro, flx_obs_spectro, err_obs_spectro, flx_mod_spectro, rv_picked): +def doppler_fct(wav_obs_spectro, wav_mod_spectro, flx_mod_spectro, rv_picked): """ Application of a Doppler shifting to the interpolated synthetic spectrum using the function pyasl.dopplerShift. - Note: Observation can change due to side effects of the shifting. + The side effects of the Doppler shifting are taking into account by using a model interpolated on a larger wavelength grid as the wavelength grid of the data. + After the Doppler shifting, the model is then cut to the wavelength of the data. Args: wav_obs_spectro (array): Wavelength grid of the data - flx_obs_spectro (array): Flux of the data - err_obs_spectro (array): Error of the data + wav_mod_spectro (array): Wavelength grid of the model flx_mod_spectro (array): Flux of the interpolated synthetic spectrum rv_picked (float): Radial velocity randomly picked by the nested sampling (in km.s-1) Returns: - - wav_obs_spectro (array) : New wavelength grid of the data - - flx_obs_spectro (array) : New flux of the data - - err_obs_spectro (array) : New error of the data - flx_post_doppler (array) : New flux of the interpolated synthetic spectrum - Author: Simon Petrus + Author: Simon Petrus and Allan Denis """ - new_wav = wav_obs_spectro * ((rv_picked / const.c.to(u.km/u.s).value) + 1) - rv_interp = interp1d(new_wav, flx_mod_spectro, fill_value="extrapolate") - flx_post_doppler = rv_interp(wav_obs_spectro) - - return wav_obs_spectro, flx_obs_spectro, err_obs_spectro, flx_post_doppler + + new_wav = wav_mod_spectro * ((rv_picked / const.c.to(u.km/u.s).value) + 1) + rv_interp = interp1d(new_wav, flx_mod_spectro, bounds_error=False) + flx_post_doppler = rv_interp(wav_mod_spectro) + + # We adapt the model to the wavelength of the observations + mask_obs_spectro = (wav_mod_spectro >= np.min(wav_obs_spectro)) & (wav_mod_spectro <= np.max(wav_obs_spectro)) + flx_post_doppler = flx_post_doppler[mask_obs_spectro] + + return flx_post_doppler # ---------------------------------------------------------------------------------------------------------------------- @@ -478,8 +354,8 @@ def planck(wav, T): def modif_spec(global_params, theta, theta_index, - wav_obs_spectro, flx_obs_spectro, err_obs_spectro, flx_mod_spectro, - wav_obs_photo, flx_obs_photo, err_obs_photo, flx_mod_photo, transm_obs = [], star_flx_obs = [], system_obs = [], indobs=0): + wav_obs_spectro, wav_mod_spectro, flx_obs_spectro, flx_cont_obs_spectro, err_obs_spectro, flx_mod_spectro, + wav_obs_photo, flx_obs_photo, err_obs_photo, flx_mod_photo, res_obs_spectro, res_mod_spectro, transm_obs_spectro = [], star_flx_obs_spectro = [], star_flx_cont_obs_spectro = [], system_obs_spectro = [], indobs=0): """ Modification of the interpolated synthetic spectra with the different extra-grid parameters. It can perform : Re-calibration on the data, Doppler shifting, Application of a substellar extinction, Application of a rotational velocity, @@ -522,9 +398,7 @@ def modif_spec(global_params, theta, theta_index, else: ind_theta_rv = np.where(theta_index == f'rv_{indobs}') rv_picked = theta[ind_theta_rv[0][0]] - wav_obs_spectro, flx_obs_spectro, err_obs_spectro, flx_mod_spectro = doppler_fct(wav_obs_spectro, flx_obs_spectro, - err_obs_spectro, flx_mod_spectro, - rv_picked) + flx_mod_spectro = doppler_fct(wav_obs_spectro, wav_mod_spectro, flx_mod_spectro, rv_picked) else: # If you want 1 common rv for all observations if global_params.rv != "NA": if global_params.rv[0] == "constant": @@ -532,9 +406,8 @@ def modif_spec(global_params, theta, theta_index, else: ind_theta_rv = np.where(theta_index == 'rv') rv_picked = theta[ind_theta_rv[0][0]] - wav_obs_spectro, flx_obs_spectro, err_obs_spectro, flx_mod_spectro = doppler_fct(wav_obs_spectro, flx_obs_spectro, - err_obs_spectro, flx_mod_spectro, - rv_picked) + flx_mod_spectro = doppler_fct(wav_obs_spectro, wav_mod_spectro, flx_mod_spectro, rv_picked) + # Application of a synthetic interstellar extinction to the interpolated synthetic spectrum. if global_params.av != "NA": @@ -639,14 +512,14 @@ def modif_spec(global_params, theta, theta_index, # Calculation of the dilution factor Ck and re-normalization of the interpolated synthetic spectrum. # From the radius and the distance. - if global_params.use_lsqr[indobs] == 'True': - planet_contribution, stellar_contribution, flx_mod_spectro, flx_obs_spectro, star_flx_obs, systematics, flx_mod_spectro_nativ, wav_obs_spectro, err_obs_spectro = lsq_fct(global_params, wav_obs_spectro, indobs, flx_obs_spectro, err_obs_spectro, star_flx_obs, transm_obs, flx_mod_spectro, system_obs) - _, _, ck = calc_ck(np.copy(flx_obs_spectro), err_obs_spectro, np.copy(flx_mod_spectro), - flx_obs_photo, err_obs_photo, flx_mod_photo, 0, 0, 0, analytic='yes') - else: - # Set HiRES contribution to 1 if not used - planet_contribution, stellar_contribution, systematics = 1, 1, np.asarray([]) + if global_params.fm_type[indobs] != 'NA': + res, flx_mod_spectro, flx_obs_spectro, star_flx_obs_spectro, system_obs_spectro = fm.forward_model(global_params, wav_obs_spectro, res_obs_spectro, flx_cont_obs_spectro, + flx_mod_spectro, star_flx_obs_spectro, star_flx_cont_obs_spectro, + err_obs_spectro, transm_obs_spectro, flx_obs_spectro, system_obs_spectro, indobs) + _, _, ck = calc_ck(np.copy(flx_obs_spectro), err_obs_spectro, np.copy(flx_mod_spectro), flx_obs_photo, err_obs_photo, flx_mod_photo, 0, 0, 0, analytic='yes') + + else: if global_params.r != "NA" and global_params.d != "NA": if global_params.r[0] == "constant": r_picked = float(global_params.r[1]) @@ -699,7 +572,7 @@ def modif_spec(global_params, theta, theta_index, print('WARNING: You need to define a radius AND a distance, or set them both to "NA"') exit() - return wav_obs_spectro, flx_obs_spectro, err_obs_spectro, flx_mod_spectro, wav_obs_photo, flx_obs_photo, err_obs_photo, flx_mod_photo, ck, planet_contribution, stellar_contribution, star_flx_obs, systematics, transm_obs + return wav_obs_spectro, flx_obs_spectro, err_obs_spectro, flx_mod_spectro, wav_obs_photo, flx_obs_photo, err_obs_photo, flx_mod_photo, ck, star_flx_obs_spectro, system_obs_spectro, res_obs_spectro diff --git a/ForMoSA/nested_sampling/nested_sampling.py b/ForMoSA/nested_sampling/nested_sampling.py index de6bfae..b9ac0c6 100755 --- a/ForMoSA/nested_sampling/nested_sampling.py +++ b/ForMoSA/nested_sampling/nested_sampling.py @@ -11,6 +11,9 @@ from nested_sampling.nested_modif_spec import modif_spec from nested_sampling.nested_prior_function import uniform_prior, gaussian_prior from nested_sampling.nested_logL_functions import * +from scipy.interpolate import interp1d +from ForMoSA.adapt.extraction_functions import continuum_estimate + def import_obsmod(global_params): @@ -39,11 +42,25 @@ def import_obsmod(global_params): wav_obs_spectro = np.asarray(spectrum_obs['obs_spectro'][0], dtype=float) flx_obs_spectro = np.asarray(spectrum_obs['obs_spectro'][1], dtype=float) err_obs_spectro = np.asarray(spectrum_obs['obs_spectro'][2], dtype=float) + res_obs_spectro = np.asarray(spectrum_obs['obs_spectro'][3], dtype=float) + + if global_params.fm_type[indobs] != 'NA': + global_params.wav_for_continuum = global_params.wav_fit + global_params.continuum_sub = global_params.fm_continuum_res + flx_cont_obs_spectro = continuum_estimate(global_params, wav_obs_spectro, flx_obs_spectro, res_obs_spectro, indobs) + else: + flx_cont_obs_spectro = np.asarray([], dtype='float') + # Optional arrays - inv_cov_obs = np.asarray(spectrum_obs['obs_opt'][0], dtype=float) - transm_obs = np.asarray(spectrum_obs['obs_opt'][1], dtype=float) - star_flx_obs = np.asarray(spectrum_obs['obs_opt'][2], dtype=float) - system_obs = np.asarray(spectrum_obs['obs_opt'][3], dtype=float) + inv_cov_obs_spectro = np.asarray(spectrum_obs['obs_opt'][0], dtype=float) + transm_obs_spectro = np.asarray(spectrum_obs['obs_opt'][1], dtype=float) + star_flx_obs_spectro = np.asarray(spectrum_obs['obs_opt'][2], dtype=float) + system_obs_spectro = np.asarray(spectrum_obs['obs_opt'][3], dtype=float) + + if global_params.fm_type[indobs] != 'NA': + star_flx_cont_obs_spectro = continuum_estimate(global_params, wav_obs_spectro, star_flx_obs_spectro[:,len(star_flx_obs_spectro[0]) // 2], res_obs_spectro, indobs) + else: + star_flx_cont_obs_spectro = np.asarray([], dtype='float') if 'obs_photo' in spectrum_obs.keys(): wav_obs_photo = np.asarray(spectrum_obs['obs_photo'][0], dtype=float) @@ -59,6 +76,10 @@ def import_obsmod(global_params): path_grid_photo = os.path.join(global_params.adapt_store_path, f'adapted_grid_photo_{global_params.grid_name}_{obs_name}_nonan.nc') ds = xr.open_dataset(path_grid_spectro, decode_cf=False, engine='netcdf4') grid_spectro = ds['grid'] + res_mod_spectro = ds.attrs['res'] + wav_mod_spectro = ds.coords['wavelength'] + res_mod_spectro_interp = interp1d(wav_mod_spectro, res_mod_spectro) + res_mod_spectro = res_mod_spectro_interp(wav_obs_spectro) ds.close() ds = xr.open_dataset(path_grid_photo, decode_cf=False, engine='netcdf4') grid_photo = ds['grid'] @@ -73,29 +94,39 @@ def import_obsmod(global_params): min_ns_u = float(ns_u.split(',')[0]) max_ns_u = float(ns_u.split(',')[1]) # Indices of each model and data - mask_mod_spectro += (grid_spectro['wavelength'] >= min_ns_u) & (grid_spectro['wavelength'] <= max_ns_u) - mask_mod_photo += (grid_photo['wavelength'] >= min_ns_u) & (grid_photo['wavelength'] <= max_ns_u) + if global_params.rv[indobs] == 'NA' or global_params.rv == 'NA': # If the user didn't define a prior in RV, we juste adapt the model to the values defined in 'wav_fit' + mask_mod_spectro += (grid_spectro['wavelength'] >= min_ns_u) & (grid_spectro['wavelength'] <= max_ns_u) + else: # Otherwise we chose a slightly larger wavelength range to avoid loosing data onf the edges when applying RV correction + mask_mod_spectro += (grid_spectro['wavelength'] >= 0.98 * min_ns_u) & (grid_spectro['wavelength'] <= 1.02 * max_ns_u) + + mask_obs_spectro += (wav_obs_spectro >= min_ns_u) & (wav_obs_spectro <= max_ns_u) + mask_mod_photo += (grid_photo['wavelength'] >= min_ns_u) & (grid_photo['wavelength'] <= max_ns_u) mask_obs_photo += (wav_obs_photo >= min_ns_u) & (wav_obs_photo <= max_ns_u) # Cutting the data to a wavelength grid defined by the parameter 'wav_fit' wav_obs_spectro_ns_u = wav_obs_spectro[mask_obs_spectro] flx_obs_spectro_ns_u = flx_obs_spectro[mask_obs_spectro] err_obs_spectro_ns_u = err_obs_spectro[mask_obs_spectro] - if len(inv_cov_obs) != 0: # Add covariance in the loop (if necessary) - inv_cov_obs_ns_u = inv_cov_obs[np.ix_(mask_obs_spectro, mask_obs_spectro)] + flx_cont_obs_spectro_ns_u = flx_cont_obs_spectro[mask_obs_spectro] + res_obs_spectro_ns_u = res_obs_spectro[mask_obs_spectro] + res_mod_spectro_ns_u = res_mod_spectro[mask_obs_spectro] + if len(inv_cov_obs_spectro) != 0: # Add covariance in the loop (if necessary) + inv_cov_obs_ns_u = inv_cov_obs_spectro[np.ix_(mask_obs_spectro, mask_obs_spectro)] else: inv_cov_obs_ns_u = np.asarray([]) - if len(transm_obs) != 0: # Add the transmission (if necessary) - transm_obs_ns_u = transm_obs[mask_obs_spectro] + if len(transm_obs_spectro) != 0: # Add the transmission (if necessary) + transm_obs_ns_u = transm_obs_spectro[mask_obs_spectro] else: transm_obs_ns_u = np.asarray([]) - if len(star_flx_obs) != 0: # Add star flux (if necessary) - star_flx_obs_ns_u = star_flx_obs[mask_obs_spectro] + if len(star_flx_obs_spectro) != 0: # Add star flux (if necessary) + star_flx_obs_ns_u = star_flx_obs_spectro[mask_obs_spectro] + star_flx_cont_obs_ns_u = star_flx_cont_obs_spectro[mask_obs_spectro] else: star_flx_obs_ns_u = np.asarray([]) - if len(system_obs) != 0: # Add systematics model (if necessary) - system_obs_ns_u = system_obs[mask_obs_spectro] + star_flx_cont_obs_ns_u = np.array([]) + if len(system_obs_spectro) != 0: # Add systematics model (if necessary) + system_obs_ns_u = system_obs_spectro[mask_obs_spectro] else: system_obs_ns_u = np.asarray([]) wav_obs_photo_ns_u = wav_obs_photo[mask_obs_photo] @@ -107,12 +138,11 @@ def import_obsmod(global_params): grid_photo_ns_u = grid_photo.sel(wavelength=grid_photo['wavelength'][mask_mod_photo]) main_file.append([[wav_obs_spectro_ns_u, wav_obs_photo_ns_u], - [flx_obs_spectro_ns_u, flx_obs_photo_ns_u], + [flx_obs_spectro_ns_u, flx_cont_obs_spectro_ns_u, flx_obs_photo_ns_u], [err_obs_spectro_ns_u, err_obs_photo_ns_u], + [transm_obs_ns_u, star_flx_obs_ns_u, star_flx_cont_obs_ns_u, system_obs_ns_u], + [res_obs_spectro_ns_u, res_mod_spectro_ns_u], inv_cov_obs_ns_u, - transm_obs_ns_u, - star_flx_obs_ns_u, - system_obs_ns_u, grid_spectro_ns_u, grid_photo_ns_u]) @@ -149,17 +179,21 @@ def loglike(theta, theta_index, global_params, main_file, for_plot='no'): wav_obs_spectro_ns_u = main_file[indobs][0][0] wav_obs_photo_ns_u = main_file[indobs][0][1] flx_obs_spectro_ns_u = main_file[indobs][1][0] - flx_obs_photo_ns_u = main_file[indobs][1][1] + flx_cont_obs_spectro_ns_u = main_file[indobs][1][1] + flx_obs_photo_ns_u = main_file[indobs][1][2] err_obs_spectro_ns_u = main_file[indobs][2][0] err_obs_photo_ns_u = main_file[indobs][2][1] - inv_cov_obs_ns_u = main_file[indobs][3] - transm_obs_ns_u = main_file[indobs][4] - star_flx_obs_ns_u = main_file[indobs][5] - system_obs_ns_u = main_file[indobs][6] + transm_obs_ns_u = main_file[indobs][3][0] + star_flx_obs_ns_u = main_file[indobs][3][1] + star_flx_cont_obs_ns_u = main_file[indobs][3][2] + system_obs_ns_u = main_file[indobs][3][3] + res_obs_spectro_ns_u = main_file[indobs][4][0] + res_mod_spectro_ns_u = main_file[indobs][4][1] + inv_cov_obs_ns_u = main_file[indobs][5] # Recovery of the spectroscopy and photometry model - grid_spectro_ns_u = main_file[indobs][7] - grid_photo_ns_u = main_file[indobs][8] + grid_spectro_ns_u = main_file[indobs][6] + grid_photo_ns_u = main_file[indobs][7] # Interpolation of the grid at the theta parameters set if global_params.par3 == 'NA': @@ -208,12 +242,14 @@ def loglike(theta, theta_index, global_params, main_file, for_plot='no'): method="linear", kwargs={"fill_value": "extrapolate"})) else: flx_mod_photo_ns_u = np.asarray([]) - + + wav_mod_spectro_ns_u = grid_spectro_ns_u.coords['wavelength'].values + # Modification of the synthetic spectrum with the extra-grid parameters modif_spec_LL = modif_spec(global_params, theta, theta_index, - wav_obs_spectro_ns_u, flx_obs_spectro_ns_u, err_obs_spectro_ns_u, flx_mod_spectro_ns_u, + wav_obs_spectro_ns_u, wav_mod_spectro_ns_u, flx_obs_spectro_ns_u, flx_cont_obs_spectro_ns_u, err_obs_spectro_ns_u, flx_mod_spectro_ns_u, wav_obs_photo_ns_u, flx_obs_photo_ns_u, err_obs_photo_ns_u, flx_mod_photo_ns_u, - transm_obs_ns_u, star_flx_obs_ns_u, system_obs_ns_u, indobs=indobs) + res_obs_spectro_ns_u, res_mod_spectro_ns_u, transm_obs_ns_u, star_flx_obs_ns_u, star_flx_cont_obs_ns_u, system_obs_ns_u, indobs=indobs) @@ -222,16 +258,8 @@ def loglike(theta, theta_index, global_params, main_file, for_plot='no'): err_obs_spectro_modif, err_obs_photo_modif = modif_spec_LL[2], modif_spec_LL[6] inv_cov_obs_modif = inv_cov_obs_ns_u ck = modif_spec_LL[8] - planet_contribution, stellar_contribution, star_flx_obs, systematics = modif_spec_LL[9], modif_spec_LL[10], modif_spec_LL[11], modif_spec_LL[12] - - if global_params.use_lsqr[indobs] == 'True': - # If our data is contaminated by starlight difraction, the model is the sum of the estimated stellar contribution + planet model - - flx_mod_spectro_modif = flx_mod_spectro_modif + star_flx_obs - if len(systematics) > 0: - flx_mod_spectro_modif += systematics - - + + # Computation of the photometry logL if len(flx_obs_photo_modif) != 0: @@ -244,7 +272,8 @@ def loglike(theta, theta_index, global_params, main_file, for_plot='no'): # Computation of the spectroscopy logL if len(flx_obs_spectro_modif) != 0: if global_params.logL_type[indobs] == 'chi2_classic': - logL_spectro = logL_chi2_classic(flx_obs_spectro_modif-flx_mod_spectro_modif, err_obs_spectro_modif) + logL_spectro = logL_chi2_classic(flx_mod_spectro_modif-flx_obs_spectro_modif, err_obs_spectro_modif) + #logL_spectro = logL_chi2_classic(flx_obs_spectro_modif-flx_mod_spectro_modif, err_obs_spectro_modif) elif global_params.logL_type[indobs] == 'chi2_covariance' and len(inv_cov_obs_modif) != 0: logL_spectro = logL_chi2_covariance(flx_obs_spectro_modif-flx_mod_spectro_modif, inv_cov_obs_modif) elif global_params.logL_type[indobs] == 'CCF_Brogi': @@ -533,7 +562,7 @@ def launch_nested_sampling(global_params): print('WARNING. You cannot use CCF mappings without substracting the continuum') print() exit() - elif global_params.logL_type[indobs] == 'CCF_Zucker' and global_params.continuum_sub[indobs] == 'NA': + elif global_params.logL_type[indobs] == 'CCF_Zucker' and global_params.continuum_sub[indobs] == 'NA' and global_params.star_contaminated[indobs] != 'Remove': print('WARNING. You cannot use CCF mappings without substracting the continuum') print() exit() @@ -547,7 +576,6 @@ def launch_nested_sampling(global_params): print() ds = xr.open_dataset(global_params.model_path, decode_cf=False, engine='netcdf4') - # Count the number of free parameters and identify the parameter position in theta if global_params.par1 != 'NA': theta_index = ['par1'] @@ -622,17 +650,18 @@ def launch_nested_sampling(global_params): # - - - - - - - - - - - - - - - - - - - - - - if global_params.av != 'NA' and global_params.av[0] != 'constant': + if global_params.av != 'NA' and global_params.av[indobs] != 'constant': n_free_parameters += 1 theta_index.append('av') ## adding cpd - if global_params.bb_T != 'NA' and global_params.bb_T[0] != 'constant': + if global_params.bb_T != 'NA' and global_params.bb_T[indobs] != 'constant': n_free_parameters += 1 theta_index.append('bb_T') - if global_params.bb_R != 'NA' and global_params.bb_R[0] != 'constant': + if global_params.bb_R != 'NA' and global_params.bb_R[indobs] != 'constant': n_free_parameters += 1 theta_index.append('bb_R') theta_index = np.asarray(theta_index) + # Import all the data (only done once) main_file = import_obsmod(global_params) diff --git a/ForMoSA/plotting/plotting_class.py b/ForMoSA/plotting/plotting_class.py index cbffa0b..1d6cd1e 100644 --- a/ForMoSA/plotting/plotting_class.py +++ b/ForMoSA/plotting/plotting_class.py @@ -15,11 +15,11 @@ from ForMoSA.main_utilities import GlobFile from ForMoSA.nested_sampling.nested_modif_spec import modif_spec from ForMoSA.nested_sampling.nested_modif_spec import doppler_fct -from ForMoSA.nested_sampling.nested_modif_spec import lsq_fct from ForMoSA.nested_sampling.nested_modif_spec import vsini_fct_accurate from ForMoSA.nested_sampling.nested_modif_spec import vsini_fct_rot_broad from ForMoSA.adapt.extraction_functions import resolution_decreasing, adapt_model, decoupe from ForMoSA.adapt.extraction_functions import adapt_observation_range +from ForMoSA.adapt.extraction_functions import continuum_estimate @@ -478,7 +478,6 @@ def _get_spectra(self,theta,return_model=False): # Create a list for each spectra (obs and mod) for each observation + scaling factors modif_spec_MOSAIC = [] CK = [] - flx_mod_spectro_array = np.array([]) for indobs, obs in enumerate(sorted(glob.glob(self.global_params.main_observation_path))): @@ -489,9 +488,24 @@ def _get_spectra(self,theta,return_model=False): wav_obs_spectro = np.asarray(spectrum_obs['obs_spectro'][0], dtype=float) flx_obs_spectro = np.asarray(spectrum_obs['obs_spectro'][1], dtype=float) err_obs_spectro = np.asarray(spectrum_obs['obs_spectro'][2], dtype=float) - transm_obs = np.asarray(spectrum_obs['obs_opt'][1], dtype=float) - star_flx_obs = np.asarray(spectrum_obs['obs_opt'][2], dtype=float) - system_obs = np.asarray(spectrum_obs['obs_opt'][3], dtype=float) + res_obs_spectro = np.asarray(spectrum_obs['obs_spectro'][3], dtype=float) + + if self.global_params.fm_type[indobs] != 'NA': + self.global_params.wav_for_continuum = self.global_params.wav_fit + self.global_params.continuum_sub = self.global_params.fm_continuum_res + flx_cont_obs_spectro = continuum_estimate(self.global_params, wav_obs_spectro, flx_obs_spectro, res_obs_spectro, indobs) + else: + flx_cont_obs_spectro = np.asarray([], dtype='float') + + transm_obs_spectro = np.asarray(spectrum_obs['obs_opt'][1], dtype=float) + star_flx_obs_spectro = np.asarray(spectrum_obs['obs_opt'][2], dtype=float) + system_obs_spectro = np.asarray(spectrum_obs['obs_opt'][3], dtype=float) + + if self.global_params.fm_type[indobs] != 'NA': + star_flx_cont_obs_spectro = continuum_estimate(self.global_params, wav_obs_spectro, star_flx_obs_spectro[:,len(star_flx_obs_spectro[0]) // 2], res_obs_spectro, indobs) + else: + star_flx_cont_obs_spectro = np.asarray([], dtype='float') + if 'obs_photo' in spectrum_obs.keys(): wav_obs_photo = np.asarray(spectrum_obs['obs_photo'][0], dtype=float) flx_obs_photo = np.asarray(spectrum_obs['obs_photo'][1], dtype=float) @@ -506,6 +520,8 @@ def _get_spectra(self,theta,return_model=False): path_grid_photo = os.path.join(self.global_params.adapt_store_path, f'adapted_grid_photo_{self.global_params.grid_name}_{obs_name}_nonan.nc') ds = xr.open_dataset(path_grid_spectro, decode_cf=False, engine='netcdf4') grid_spectro = ds['grid'] + wav_mod_spectro = ds.coords['wavelength'].values + res_mod_spectro = ds.attrs['res'] ds.close() ds = xr.open_dataset(path_grid_photo, decode_cf=False, engine='netcdf4') grid_photo = ds['grid'] @@ -559,23 +575,19 @@ def _get_spectra(self,theta,return_model=False): flx_mod_photo = np.asarray([]) # Modification of the synthetic spectrum with the extra-grid parameters - modif_spec_chi2 = modif_spec(self.global_params, theta, self.theta_index, - wav_obs_spectro, flx_obs_spectro, err_obs_spectro, flx_mod_spectro, - wav_obs_photo, flx_obs_photo, err_obs_photo, flx_mod_photo, - transm_obs, star_flx_obs, system_obs, indobs=indobs) + modif_spec_chi2 = modif_spec(self.global_params, theta, self.theta_index, wav_obs_spectro, + wav_mod_spectro, flx_obs_spectro, flx_cont_obs_spectro, + err_obs_spectro, flx_mod_spectro, wav_obs_photo, flx_obs_photo, + err_obs_photo, flx_mod_photo, res_obs_spectro, res_mod_spectro, transm_obs_spectro, star_flx_obs_spectro, star_flx_cont_obs_spectro, system_obs_spectro, indobs) ck = modif_spec_chi2[8] modif_spec_MOSAIC.append(modif_spec_chi2) CK.append(ck) - flx_mod_spectro_array = np.append(flx_mod_spectro_array, flx_mod_spectro) modif_spec_chi2 = modif_spec_MOSAIC ck = CK - if return_model: - return modif_spec_chi2, ck, flx_mod_spectro_array - else: - return modif_spec_chi2, ck + return modif_spec_chi2, ck def get_FULL_spectra(self, theta, grid_used = 'original', wavelengths=[], N_points=1000, re_interp=False, int_method="linear"): @@ -664,7 +676,7 @@ def get_FULL_spectra(self, theta, grid_used = 'original', wavelengths=[], N_poin - def plot_fit(self, figsize=(10, 5), uncert='no', trans='no', logx='no', logy='no', norm='no'): + def plot_fit(self, figsize=(13, 7), uncert='no', trans='no', logx='no', logy='no', norm='no'): ''' Plot the best fit comparing with the data. @@ -706,16 +718,11 @@ def plot_fit(self, figsize=(10, 5), uncert='no', trans='no', logx='no', logy='no ck = np.full(len(spectra[0][4]), 1) for indobs, obs in enumerate(sorted(glob.glob(self.global_params.main_observation_path))): - if self.global_params.use_lsqr[indobs] == 'True': - # If we used the lsq function, it means that our data is contaminated by the starlight difraction - # so the model is the sum of the planet model + the estimated stellar contribution + if self.global_params.fm_type[indobs] != 'NA': # For high-contrast companion where the star flux speckles contaminate the data spectra = list(spectra) # Transform spectra to a list so that we can modify its values spectra[indobs] = list(spectra[indobs]) - model, planet_contribution, stellar_contribution, star_flx = spectra[indobs][3], spectra[indobs][9], spectra[indobs][10], spectra[indobs][11] - spectra[indobs][3] = model + star_flx - systematics = spectra[indobs][12] - if len(systematics) > 0: - spectra[indobs][3] += systematics + mod_flx, star_flx, system_obs = spectra[indobs][3], spectra[indobs][9], spectra[indobs][10] + if len(spectra[indobs][0]) != 0: iobs_spectro += 1 @@ -725,9 +732,9 @@ def plot_fit(self, figsize=(10, 5), uncert='no', trans='no', logx='no', logy='no ax.plot(spectra[indobs][0], spectra[indobs][1]/ck[indobs], c='k') ax.plot(spectra[indobs][0], spectra[indobs][3]/ck[indobs], c=self.color_out, alpha=0.8) - if self.global_params.use_lsqr[indobs] == 'True': + if self.global_params.fm_type[indobs] != 'NA': ax.plot(spectra[indobs][0], star_flx, c='b') - ax.plot(spectra[indobs][0], model, c='r') + ax.plot(spectra[indobs][0], mod_flx - star_flx - system_obs, c='r') residuals = spectra[indobs][3] - spectra[indobs][1] sigma_res = np.nanstd(residuals) # Replace np.std by np.nanstd if nans are in the array to ignore them @@ -742,7 +749,7 @@ def plot_fit(self, figsize=(10, 5), uncert='no', trans='no', logx='no', logy='no ax.plot(spectra[0][0], np.empty(len(spectra[0][0]))*np.nan, c=self.color_out, label='Spectroscopic model') axr.plot(spectra[0][0], np.empty(len(spectra[0][0]))*np.nan, c=self.color_out, label='Spectroscopic model-data') axr2.hist(residuals/sigma_res, bins=100 ,color=self.color_out, alpha=0.2, density=True, orientation='horizontal', label='density') - if self.global_params.use_lsqr[indobs] == 'True': + if self.global_params.fm_type[indobs] != 'NA': ax.plot(spectra[0][0], np.empty(len(spectra[0][0]))*np.nan, c='b', label='Stellar model') ax.plot(spectra[0][0], np.empty(len(spectra[0][0]))*np.nan, c='r', label='Planetary model') iobs_spectro = -1 @@ -811,7 +818,7 @@ def plot_fit(self, figsize=(10, 5), uncert='no', trans='no', logx='no', logy='no - def plot_HiRes_comp_model(self, figsize=(10, 5), norm='no', data_resolution = 0): + def plot_HiRes_comp_model(self, figsize=(10, 5), norm='no'): ''' Specific function to plot the best fit comparing with the data for high-resolution spectroscopy. @@ -828,8 +835,6 @@ def plot_HiRes_comp_model(self, figsize=(10, 5), norm='no', data_resolution = 0) spectra, ck = self._get_spectra(self.theta_best) - - fig1, ax1 = plt.subplots(1, 1, figsize = figsize) fig, ax = plt.subplots(1, 1, figsize = figsize) @@ -840,39 +845,32 @@ def plot_HiRes_comp_model(self, figsize=(10, 5), norm='no', data_resolution = 0) for indobs, obs in enumerate(sorted(glob.glob(self.global_params.main_observation_path))): - if self.global_params.use_lsqr[indobs] == 'True': + if self.global_params.fm_type[indobs] != 'NA': # If we used the lsq function, it means that our data is contaminated by the starlight difraction # so the model is the sum of the planet model + the estimated stellar contribution spectra = list(spectra) # Transform spectra to a list so that we can modify its values spectra[indobs] = list(spectra[indobs]) - model, planet_contribution, stellar_contribution, star_flx, systematics, transm = spectra[indobs][3], spectra[indobs][9], spectra[indobs][10], spectra[indobs][11], spectra[indobs][12], spectra[indobs][13] + flx_mod, star_flx, system_obs = spectra[indobs][3], spectra[indobs][9], spectra[indobs][10] if len(spectra[indobs][0]) != 0: - if (len(systematics) > 0) and (len(star_flx) > 0): - data = spectra[indobs][1] - star_flx - systematics - elif (len(star_flx) > 0): # if len(systematics) = 0 but len(star_flx) > 0 + if (len(star_flx) > 0): # if len(systematics) = 0 but len(star_flx) > 0 data = spectra[indobs][1] - star_flx - elif (len(systematics) > 0): # if len(star_flx) = 0 but len(systematics) > 0 - data = spectra[indobs][1] - systematics + elif (len(system_obs) > 0): # if len(star_flx) = 0 but len(systematics) > 0 + data = spectra[indobs][1] - system_obs else: # if len(star_flx) = 0 and len(systematics) = 0 data = spectra[indobs][1] wave = spectra[indobs][0] - planet_model = model + planet_model = flx_mod - star_flx - system_obs # Compute intrinsic resolution of the data because of the v.sini resolution = 3.0*1e5 / (self.theta_best[self.theta_index == 'vsini']) resolution = resolution * np.ones(len(wave)) - if data_resolution > 0: - self.global_params.custom_reso[indobs] = 'NA' - resolution_data = data_resolution * np.ones(len(wave)) - data_broadened = vsini_fct_accurate(wave, data, 0.6, self.theta_best[self.theta_index == 'vsini']) - data_broadened = resolution_decreasing(self.global_params, wave, [], resolution, wave, data_broadened, resolution_data, 'mod') - planet_model_broadened = resolution_decreasing(self.global_params, wave, [], resolution, wave, planet_model, resolution_data, 'mod') - - - + + res_obs = spectra[indobs][11] + data_broadened = resolution_decreasing(self.global_params, wave, [], resolution, wave, data, res_obs, 'mod') + ax.plot(wave, data_broadened, c='k') ax.plot(wave, planet_model, c='r') @@ -882,7 +880,7 @@ def plot_HiRes_comp_model(self, figsize=(10, 5), norm='no', data_resolution = 0) ax1.plot(wave, data, c='k') ax1.plot(wave, planet_model, c = 'r') - if self.global_params.use_lsqr[indobs] == 'True': + if self.global_params.fm_type[indobs] != 'NA': legend_data = 'data - star' else: legend_data = 'data' diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt index bf0f9f2..7ff6438 100644 --- a/RELEASE_NOTES.txt +++ b/RELEASE_NOTES.txt @@ -470,4 +470,21 @@ Comments: - Fix import to allow importation of plotting module outside of the ForMoSA package context - Update .gitignore file with more general patterns for a python package - Remove files that should not have been committed - - Small improvements to plot_fit() \ No newline at end of file + - Small improvements to plot_fit() + +- - - + +13/12/2024 + +Allan Denis + +Comments: Several modifications, including : + - Adaptation of the model on a slightly larger wavelength range when we want to apply RV corrections to avoid losing data on the edges of the model + - Removing of the lsq function which has been replaced by a script forward_model.py containing different possible functions to forward model the planet and star speckles for the high-contrast planets + - Addition of 3 new parameters in the config file (fm_type, fm_continuum_res and bounds_lsq) which are related to the high-contrast planets, where the data are contaminated by the star speckles + - Cleaning a big the plotting_class script + +Tests that have been done to checkup the changes: + - Runs with HiRISE data in the high-contrast scenario to check for the forward model scripts + - Different runs to check for the gain associated to the adaptation of the model on a larger wavelength grid to avoid losing data on the edges during the RV application +