ForMoSA 2.0.0__tar.gz → 2.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- formosa-2.0.1/ForMoSA/__init__.py +7 -0
- formosa-2.0.1/ForMoSA/analysis.py +512 -0
- {formosa-2.0.0/ForMoSA/adapt → formosa-2.0.1/ForMoSA/config}/__init__.py +0 -0
- formosa-2.0.1/ForMoSA/config/global_config.py +1575 -0
- formosa-2.0.1/ForMoSA/config/paths.py +160 -0
- {formosa-2.0.0/ForMoSA/nested_sampling → formosa-2.0.1/ForMoSA/core}/__init__.py +0 -0
- formosa-2.0.1/ForMoSA/core/config.py +477 -0
- formosa-2.0.1/ForMoSA/core/enums.py +372 -0
- formosa-2.0.1/ForMoSA/core/errors.py +7 -0
- formosa-2.0.1/ForMoSA/core/loggings.py +79 -0
- formosa-2.0.1/ForMoSA/filter/filter.py +593 -0
- formosa-2.0.1/ForMoSA/grid/__init__.py +0 -0
- formosa-2.0.1/ForMoSA/grid/grid_loader.py +150 -0
- formosa-2.0.1/ForMoSA/grid/model_grid.py +597 -0
- formosa-2.0.1/ForMoSA/grid/subgrid_base.py +596 -0
- formosa-2.0.1/ForMoSA/grid/subgrid_photometry.py +461 -0
- formosa-2.0.1/ForMoSA/grid/subgrid_set.py +266 -0
- formosa-2.0.1/ForMoSA/grid/subgrid_spectroscopy.py +383 -0
- formosa-2.0.1/ForMoSA/nested_sampling/__init__.py +0 -0
- formosa-2.0.1/ForMoSA/nested_sampling/nested_sampling.py +691 -0
- formosa-2.0.1/ForMoSA/nested_sampling/ns_analysis.py +469 -0
- formosa-2.0.1/ForMoSA/nested_sampling/plotting.py +608 -0
- formosa-2.0.1/ForMoSA/nested_sampling/results.py +411 -0
- formosa-2.0.1/ForMoSA/observation/__init__.py +0 -0
- formosa-2.0.1/ForMoSA/observation/observation_base.py +507 -0
- formosa-2.0.1/ForMoSA/observation/observation_loader.py +432 -0
- formosa-2.0.1/ForMoSA/observation/observation_photometry.py +349 -0
- formosa-2.0.1/ForMoSA/observation/observation_set.py +778 -0
- formosa-2.0.1/ForMoSA/observation/observation_spectroscopy.py +589 -0
- formosa-2.0.1/ForMoSA/parameter/__init__.py +0 -0
- formosa-2.0.1/ForMoSA/parameter/parameter.py +209 -0
- formosa-2.0.1/ForMoSA/parameter/parameter_set.py +358 -0
- formosa-2.0.1/ForMoSA/parameter/prior.py +665 -0
- formosa-2.0.1/ForMoSA/transform/__init__.py +0 -0
- formosa-2.0.1/ForMoSA/transform/apply_effects.py +302 -0
- formosa-2.0.1/ForMoSA/transform/observed.py +441 -0
- formosa-2.0.1/ForMoSA/transform/photometric_effects.py +178 -0
- formosa-2.0.1/ForMoSA/transform/spectroscopic_effects.py +224 -0
- formosa-2.0.1/ForMoSA/utils/__init__.py +0 -0
- formosa-2.0.1/ForMoSA/utils/logL_functions.py +232 -0
- formosa-2.0.1/ForMoSA/utils/misc.py +309 -0
- formosa-2.0.1/ForMoSA/utils/prior_functions.py +90 -0
- formosa-2.0.1/ForMoSA/utils/spec.py +1079 -0
- formosa-2.0.1/ForMoSA.egg-info/PKG-INFO +289 -0
- formosa-2.0.1/ForMoSA.egg-info/SOURCES.txt +60 -0
- formosa-2.0.1/ForMoSA.egg-info/requires.txt +33 -0
- formosa-2.0.1/ForMoSA.egg-info/top_level.txt +4 -0
- {formosa-2.0.0 → formosa-2.0.1}/LICENSE +6 -2
- formosa-2.0.1/PKG-INFO +289 -0
- formosa-2.0.1/README.md +237 -0
- formosa-2.0.1/docs/conf.py +172 -0
- formosa-2.0.1/pyproject.toml +66 -0
- formosa-2.0.0/ForMoSA.egg-info/requires.txt → formosa-2.0.1/requirements.txt +6 -1
- formosa-2.0.1/tests/test_grid.py +262 -0
- formosa-2.0.1/tests/test_grid_set.py +175 -0
- formosa-2.0.1/tests/test_observation.py +357 -0
- formosa-2.0.1/tests/test_observation_set.py +206 -0
- formosa-2.0.1/tests/test_observed.py +128 -0
- formosa-2.0.1/tests/test_parameter_set.py +148 -0
- formosa-2.0.0/ForMoSA/__init__.py +0 -5
- formosa-2.0.0/ForMoSA/adapt/adapt_grid.py +0 -314
- formosa-2.0.0/ForMoSA/adapt/adapt_obs_mod.py +0 -152
- formosa-2.0.0/ForMoSA/adapt/extraction_functions.py +0 -568
- formosa-2.0.0/ForMoSA/main.py +0 -66
- formosa-2.0.0/ForMoSA/main_utilities.py +0 -181
- formosa-2.0.0/ForMoSA/nested_sampling/nested_logL_functions.py +0 -134
- formosa-2.0.0/ForMoSA/nested_sampling/nested_modif_spec.py +0 -562
- formosa-2.0.0/ForMoSA/nested_sampling/nested_prior_function.py +0 -33
- formosa-2.0.0/ForMoSA/nested_sampling/nested_sampling.py +0 -767
- formosa-2.0.0/ForMoSA/plotting/plotting_class.py +0 -1000
- formosa-2.0.0/ForMoSA.egg-info/PKG-INFO +0 -22
- formosa-2.0.0/ForMoSA.egg-info/SOURCES.txt +0 -23
- formosa-2.0.0/ForMoSA.egg-info/not-zip-safe +0 -1
- formosa-2.0.0/ForMoSA.egg-info/top_level.txt +0 -1
- formosa-2.0.0/PKG-INFO +0 -22
- formosa-2.0.0/README.md +0 -96
- formosa-2.0.0/setup.py +0 -23
- {formosa-2.0.0/ForMoSA/plotting → formosa-2.0.1/ForMoSA/filter}/__init__.py +0 -0
- {formosa-2.0.0 → formosa-2.0.1}/ForMoSA.egg-info/dependency_links.txt +0 -0
- {formosa-2.0.0 → formosa-2.0.1}/setup.cfg +0 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
from ForMoSA.config.paths import Paths
|
|
5
|
+
from ForMoSA.core.errors import ForMoSAError
|
|
6
|
+
from ForMoSA.grid.model_grid import ModelGrid
|
|
7
|
+
from ForMoSA.core.loggings import setup_logging
|
|
8
|
+
from ForMoSA.grid.subgrid_set import SubGridSet
|
|
9
|
+
from ForMoSA.filter.filter import PhotometryFilter
|
|
10
|
+
from ForMoSA.nested_sampling.results import NSResults
|
|
11
|
+
from ForMoSA.nested_sampling.plotting import Plotting
|
|
12
|
+
from ForMoSA.core.config import PLOTS_CONFIG, MAIN_PLOT
|
|
13
|
+
from ForMoSA.parameter.parameter_set import ParameterSet
|
|
14
|
+
from ForMoSA.nested_sampling.ns_analysis import NSAnalysis
|
|
15
|
+
from ForMoSA.grid.subgrid_photometry import SubGridPhotometry
|
|
16
|
+
from ForMoSA.observation.observation_set import ObservationSet
|
|
17
|
+
from ForMoSA.grid.subgrid_spectroscopy import SubGridSpectroscopy
|
|
18
|
+
from ForMoSA.nested_sampling.nested_sampling import NestedSampling
|
|
19
|
+
from ForMoSA.core.enums import ObservationType, NestedAlgorithm, LogLikelihoodType
|
|
20
|
+
from ForMoSA.config.global_config import ConfigPath, ConfigAdapt, ConfigInversion, ConfigParameters, Config_NS
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Analysis(object):
|
|
24
|
+
'''
|
|
25
|
+
ForMoSA data analysis class.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
config_path : ConfigPath
|
|
30
|
+
Instance of class ConfigPath representing the configuration paths.
|
|
31
|
+
adapted : bool
|
|
32
|
+
Whether the model is adapted to the data, by default False. Can be set to True if the model has already been adapted to the data
|
|
33
|
+
fitted : bool
|
|
34
|
+
Whether the data have already been fitted for
|
|
35
|
+
logger : logging.Logger
|
|
36
|
+
Logger
|
|
37
|
+
log_level : str
|
|
38
|
+
Log level of the handler, by default ``'info'`` for all important informations.
|
|
39
|
+
|
|
40
|
+
Notes
|
|
41
|
+
-----
|
|
42
|
+
Authors: Allan Denis
|
|
43
|
+
'''
|
|
44
|
+
|
|
45
|
+
def __init__(self, config_path: ConfigPath, adapted: bool = False, fitted: bool = False, logger: logging.Logger = None, log_level: str = 'info') -> None:
|
|
46
|
+
|
|
47
|
+
self._logger = logger or setup_logging(level=log_level, name='ForMoSA Analysis')
|
|
48
|
+
|
|
49
|
+
self._config_path = config_path
|
|
50
|
+
self._adapted = adapted
|
|
51
|
+
self._fitted = fitted
|
|
52
|
+
self._ns = None
|
|
53
|
+
self._parameters = None
|
|
54
|
+
self._ns_analysis = None
|
|
55
|
+
|
|
56
|
+
# Paths
|
|
57
|
+
self._paths = Paths(config_path, logger=self._logger)
|
|
58
|
+
|
|
59
|
+
# ModelGrid
|
|
60
|
+
self._grid = ModelGrid.from_file(self._paths.model_path, logger=self._logger)
|
|
61
|
+
|
|
62
|
+
# Adapted Observations
|
|
63
|
+
# When running with adapted=True, prefer adapted observations saved on disk
|
|
64
|
+
# because they include continuum metadata (wave_cont/res_cont) required by
|
|
65
|
+
# high-contrast modeling.
|
|
66
|
+
if self._adapted:
|
|
67
|
+
try:
|
|
68
|
+
self._observations = ObservationSet.from_npz(self._paths.result_path, logger=self._logger)
|
|
69
|
+
self._logger.info(' Loaded adapted observations from result path')
|
|
70
|
+
except ForMoSAError as e:
|
|
71
|
+
self._logger.warning(f'Recovery of adapted observations from result path {self._paths.result_path} produced the following error: {e}. Trying with the raw FITS observations')
|
|
72
|
+
self._observations = ObservationSet.from_fits(self._paths.observation_path, logger=self._logger)
|
|
73
|
+
else:
|
|
74
|
+
self._observations = ObservationSet.from_fits(self._paths.observation_path, logger=self._logger)
|
|
75
|
+
|
|
76
|
+
# Adapted SubGrids
|
|
77
|
+
if self._adapted:
|
|
78
|
+
try:
|
|
79
|
+
self._subgrids = SubGridSet.from_path(self.paths.adapt_store_path, self.grid, logger=self._logger)
|
|
80
|
+
except ForMoSAError as e:
|
|
81
|
+
raise ForMoSAError(f'Recovering SubGridSet from path {self.paths.adapt_store_path} produced the following error: {e}', self.logger)
|
|
82
|
+
|
|
83
|
+
# Non adapted SubGrids
|
|
84
|
+
else:
|
|
85
|
+
self._subgrids = SubGridSet(parent_grid=self._grid, logger=self._logger)
|
|
86
|
+
|
|
87
|
+
# observations, ns, parameters and ns_analysis
|
|
88
|
+
if self.fitted:
|
|
89
|
+
try:
|
|
90
|
+
self._observations = ObservationSet.from_npz(self._paths.result_path, logger=self._logger)
|
|
91
|
+
self._ns = NestedSampling.from_json(self.paths.result_path, observations=self._observations, subgrids=self._subgrids, logger=self._logger)
|
|
92
|
+
self._parameters = self._ns.parameters
|
|
93
|
+
self._ns_analysis = NSAnalysis(self.ns, logger=self.logger)
|
|
94
|
+
except ForMoSAError as e:
|
|
95
|
+
raise ForMoSAError(f'Recovering NestedSampling from path {self.paths.result_path} produced the following error: {e}. You probably want to fit your data first (set fitted to False)', self._logger)
|
|
96
|
+
|
|
97
|
+
# Update configurations for plotting
|
|
98
|
+
for obs in self._observations:
|
|
99
|
+
obs.plot_config.set_plot_config(color=obs.plot_config.cmap(self._observations.mcolors_normalize(obs.central_wavelength)))
|
|
100
|
+
|
|
101
|
+
# Propagate the computed colors to restricted_observations.
|
|
102
|
+
# These are deep-copied from self._observations before the loop above,
|
|
103
|
+
# so they keep the default color unless explicitly updated here.
|
|
104
|
+
if self._ns is not None:
|
|
105
|
+
color_by_name = {obs.name: obs.plot_config.color for obs in self._observations}
|
|
106
|
+
for obs in self._ns.restricted_observations:
|
|
107
|
+
if obs.name in color_by_name:
|
|
108
|
+
obs.plot_config.set_plot_config(color=color_by_name[obs.name])
|
|
109
|
+
|
|
110
|
+
# Upade main plot configuration
|
|
111
|
+
MAIN_PLOT.legend_ncol = max(1, (np.sum(
|
|
112
|
+
[obs.nb_filters for obs in self.observations.photometry_observations])
|
|
113
|
+
+ np.sum([obs.nb_instruments for obs in self.observations.spectral_observations])
|
|
114
|
+
+ 6 - len(self.observations.high_contrast_observations)) // 7)
|
|
115
|
+
|
|
116
|
+
MAIN_PLOT.legend_hc_ncol = max(1, (np.sum([obs.nb_instruments for obs in self.observations.high_contrast_observations]) + 6) // 7)
|
|
117
|
+
|
|
118
|
+
MAIN_PLOT.legend_filt_ncol = max(1, (np.sum([obs.nb_filters for obs in self.observations.photometry_observations]) + 4) // 5)
|
|
119
|
+
|
|
120
|
+
# =======================
|
|
121
|
+
# Properties
|
|
122
|
+
# =======================
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def adapted(self) -> bool:
|
|
126
|
+
"""Whether data and model are adapted."""
|
|
127
|
+
return self._adapted
|
|
128
|
+
|
|
129
|
+
@adapted.setter
|
|
130
|
+
def adapted(self, adapted_status: bool) -> bool:
|
|
131
|
+
"""Setter for adapted."""
|
|
132
|
+
self._adapted = adapted_status
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def fitted(self) -> bool:
|
|
136
|
+
"""Whether models have been fitted to the data."""
|
|
137
|
+
return self._fitted
|
|
138
|
+
|
|
139
|
+
@fitted.setter
|
|
140
|
+
def fitted(self, fitted_status: bool) -> bool:
|
|
141
|
+
self._fitted = fitted_status
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def logger(self) -> logging.Logger:
|
|
145
|
+
"""Logger."""
|
|
146
|
+
return self._logger
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def config_path(self) -> ConfigPath:
|
|
150
|
+
"""ConfigLoader."""
|
|
151
|
+
return self._config_path
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def observations(self) -> ObservationSet:
|
|
155
|
+
"""Set of observations."""
|
|
156
|
+
return self._observations
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def grid(self) -> ModelGrid:
|
|
160
|
+
"""ModelGrid."""
|
|
161
|
+
return self._grid
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def parameters(self) -> ParameterSet:
|
|
165
|
+
"""Set of parameters."""
|
|
166
|
+
return self._parameters
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def paths(self) -> Paths:
|
|
170
|
+
"""ForMoSAPaths."""
|
|
171
|
+
return self._paths
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def subgrids(self) -> SubGridSet:
|
|
175
|
+
"""Set of subgrids."""
|
|
176
|
+
return self._subgrids
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def ns(self) -> NestedSampling:
|
|
180
|
+
"""Nested Sampling."""
|
|
181
|
+
return self._ns
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def ns_analysis(self) -> NSAnalysis:
|
|
185
|
+
"""NSAnalysis."""
|
|
186
|
+
return self._ns_analysis
|
|
187
|
+
|
|
188
|
+
# =======================
|
|
189
|
+
# Representation
|
|
190
|
+
# =======================
|
|
191
|
+
|
|
192
|
+
def __repr__(self) -> str:
|
|
193
|
+
return 'Analysis()'
|
|
194
|
+
|
|
195
|
+
# =======================
|
|
196
|
+
# Methods
|
|
197
|
+
# =======================
|
|
198
|
+
|
|
199
|
+
def adapt(self, config_adapt: ConfigAdapt, config_inversion: ConfigInversion, to_json: bool = False) -> None:
|
|
200
|
+
'''
|
|
201
|
+
Adapt the grid of model to each observation.
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
config_adapt : ConfigAdapt
|
|
206
|
+
Instance of ConfigAdapt
|
|
207
|
+
|
|
208
|
+
Notes
|
|
209
|
+
-----
|
|
210
|
+
Authors: Simon Petrus, Matthieu Ravet and Allan Denis
|
|
211
|
+
'''
|
|
212
|
+
|
|
213
|
+
# ==================
|
|
214
|
+
# Checks
|
|
215
|
+
# ==================
|
|
216
|
+
|
|
217
|
+
# config_adapt type and config_inversion types
|
|
218
|
+
if not isinstance(config_adapt, ConfigAdapt):
|
|
219
|
+
raise ForMoSAError(f'Wrong type for config_adapt: {type(config_adapt)}. Expected a ConfigAdapt', self.logger)
|
|
220
|
+
|
|
221
|
+
# Check that lengths of MOSAIC parameters of config_adapt and config_inversion are consistent with number of observations
|
|
222
|
+
try:
|
|
223
|
+
config_adapt._check_with_n_obs(self.observations.n_observations)
|
|
224
|
+
config_inversion._check_with_n_obs(self.observations.n_observations)
|
|
225
|
+
except ForMoSAError as e:
|
|
226
|
+
raise ForMoSAError(e, self.logger)
|
|
227
|
+
|
|
228
|
+
# Compute target resolution to reach for the observations
|
|
229
|
+
target_resolution = config_adapt._compute_obs_target_resolution(self.observations, self.grid)
|
|
230
|
+
# Adapt observations
|
|
231
|
+
self.observations.adapt_all(target_resolution = target_resolution, wave_cont = config_adapt.wav_cont, res_cont = config_adapt.res_cont)
|
|
232
|
+
|
|
233
|
+
# Save observations
|
|
234
|
+
self.observations.save_all(self.paths.result_path, to_json=to_json)
|
|
235
|
+
|
|
236
|
+
if not self.adapted:
|
|
237
|
+
try:
|
|
238
|
+
# Compute target wavelength and resolutions to reach for the subgrids
|
|
239
|
+
target_wave, target_res = target_wave, target_res = config_adapt._compute_model_target_wavelength_and_resolution(self.observations, self.grid)
|
|
240
|
+
# Compute whether to remove continuum for the subgrids
|
|
241
|
+
remove_continuum = config_adapt._determine_remove_continuum(self.observations)
|
|
242
|
+
|
|
243
|
+
self._logger.debug(' Generate a set of subgrids from the observations')
|
|
244
|
+
subgrid_set = SubGridSet(self.grid, logger=self.logger)
|
|
245
|
+
|
|
246
|
+
# ==================
|
|
247
|
+
# Adapt subgrids
|
|
248
|
+
# ==================
|
|
249
|
+
|
|
250
|
+
# Loop in observations
|
|
251
|
+
for obs, wave, res, remove_cont in zip(self.observations.observations, target_wave, target_res, remove_continuum):
|
|
252
|
+
# Spectroscopic observation
|
|
253
|
+
if obs.ObsType == ObservationType.SPECTROSCOPIC.obstype:
|
|
254
|
+
subgrid = SubGridSpectroscopy.from_parent(parent_grid = self.grid, target_wavelength=wave, target_resolution=res, name = obs.name, logger = self.logger, remove_continuum=remove_cont, res_cont=obs._res_cont, wave_cont=obs._wave_cont, backend=config_adapt.backend, n_jobs=config_adapt.n_jobs)
|
|
255
|
+
# Photometric observation
|
|
256
|
+
elif obs.ObsType == ObservationType.PHOTOMETRIC.obstype:
|
|
257
|
+
Filter_list = []
|
|
258
|
+
for facility, instrument, filter_id in zip(obs.facility, obs.instrument, obs.filter_id):
|
|
259
|
+
Filter_list.append(PhotometryFilter(facility, instrument, filter_id, logger=self.logger))
|
|
260
|
+
subgrid = SubGridPhotometry.from_parent(parent_grid = self.grid, Filter = Filter_list, name = obs.name, logger = self.logger, backend=config_adapt.backend, n_jobs=config_adapt.n_jobs)
|
|
261
|
+
# Unknown type
|
|
262
|
+
else:
|
|
263
|
+
raise ForMoSAError(f'Unknown ObservationType: {obs.ObsType}')
|
|
264
|
+
|
|
265
|
+
subgrid_set.add_subgrid(subgrid)
|
|
266
|
+
|
|
267
|
+
except ForMoSAError as e:
|
|
268
|
+
raise ForMoSAError(e, self.logger)
|
|
269
|
+
|
|
270
|
+
self._logger.info(f' Set of subgrids generated: {subgrid_set.subgrid_names}')
|
|
271
|
+
self._subgrids = subgrid_set
|
|
272
|
+
|
|
273
|
+
# Interpolate mmissing values in the subgrids
|
|
274
|
+
self.subgrids.interpolate_all(config_adapt.method)
|
|
275
|
+
|
|
276
|
+
# Save all the subgrids
|
|
277
|
+
self.subgrids.save_all(self.paths.adapt_store_path)
|
|
278
|
+
|
|
279
|
+
# Set adapted to True
|
|
280
|
+
self._adapted = True
|
|
281
|
+
|
|
282
|
+
def nested_sampling(self, config_parameters: ConfigParameters, config_adapt: ConfigAdapt = ConfigAdapt(), config_inversion: ConfigInversion = ConfigInversion(), config_NS: Config_NS = Config_NS()) -> None:
|
|
283
|
+
'''
|
|
284
|
+
Launch nested sampling.
|
|
285
|
+
|
|
286
|
+
Parameters
|
|
287
|
+
----------
|
|
288
|
+
config_adapt : ConfigAdapt
|
|
289
|
+
Instance of class ConfigAdapt
|
|
290
|
+
config_inversion : ConfigInversion
|
|
291
|
+
Instance of class ConfigInversion
|
|
292
|
+
config_parameters : ConfigParameters
|
|
293
|
+
Instance of class ConfigParameters
|
|
294
|
+
|
|
295
|
+
Notes
|
|
296
|
+
-----
|
|
297
|
+
Authors: Allan Denis
|
|
298
|
+
'''
|
|
299
|
+
|
|
300
|
+
for config, config_type in zip([config_parameters, config_adapt, config_inversion, config_NS], [ConfigParameters, ConfigAdapt, ConfigInversion, Config_NS]):
|
|
301
|
+
if not isinstance(config, config_type):
|
|
302
|
+
raise ForMoSAError(f'Wrong type for {config} : {type(config)}. Expected a {config_type}')
|
|
303
|
+
|
|
304
|
+
config_adapt._check_with_n_obs(self.observations.n_observations)
|
|
305
|
+
config_inversion._check_with_n_obs(self.observations.n_observations)
|
|
306
|
+
|
|
307
|
+
# Propagate the configurations to restricted_observations.
|
|
308
|
+
# In case they have been changed in the observations, they need to be deep copied in the restricted observations
|
|
309
|
+
if self._ns is not None:
|
|
310
|
+
config_by_name = {obs.name: obs._plot_config.to_dict() for obs in self._observations}
|
|
311
|
+
|
|
312
|
+
for obs in self._ns.restricted_observations.observations:
|
|
313
|
+
if obs.name in config_by_name:
|
|
314
|
+
obs._plot_config.set_plot_config(**config_by_name[obs.name])
|
|
315
|
+
|
|
316
|
+
if not self.fitted:
|
|
317
|
+
|
|
318
|
+
# ==================
|
|
319
|
+
# Checks
|
|
320
|
+
# ==================
|
|
321
|
+
|
|
322
|
+
# Build set of parameters
|
|
323
|
+
self._parameters = ParameterSet.from_config(config_parameters, logger=self.logger)
|
|
324
|
+
|
|
325
|
+
# Replace 'parX' names by associated physical parameters ('Teff', 'logg', ...)
|
|
326
|
+
for i, name in enumerate(self.parameters.names):
|
|
327
|
+
if name.startswith('par'): # Detect grid parameters
|
|
328
|
+
self.parameters.parameters[i]._title = self.grid.titles[self.grid.keys.index(name)] # Rename parameter with title associated to 'parX'
|
|
329
|
+
|
|
330
|
+
algorithm, npoints, logL_type_list = config_inversion.ns_algo, config_inversion.npoints, config_inversion.logL_type
|
|
331
|
+
algorithm = NestedAlgorithm[algorithm.upper()]
|
|
332
|
+
logL_type = [LogLikelihoodType[logL.upper()] for logL in logL_type_list]
|
|
333
|
+
|
|
334
|
+
# Create instance of NestedSampling
|
|
335
|
+
self._ns = NestedSampling(
|
|
336
|
+
algorithm=algorithm,
|
|
337
|
+
npoints=npoints,
|
|
338
|
+
logL_type=logL_type,
|
|
339
|
+
config_NS= config_NS,
|
|
340
|
+
observations=self.observations,
|
|
341
|
+
subgrids=self.subgrids,
|
|
342
|
+
parameters=self.parameters,
|
|
343
|
+
wave_fit=config_inversion.wav_fit,
|
|
344
|
+
interp_method=config_adapt.method,
|
|
345
|
+
bounds_lsq=config_inversion.hc_bounds,
|
|
346
|
+
logger=self.logger
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# Launch NestedSampling
|
|
350
|
+
self._ns.run(results_path=self.paths.result_path)
|
|
351
|
+
|
|
352
|
+
# Create instance of NSAnalysis
|
|
353
|
+
self._ns_analysis = NSAnalysis(self._ns, logger=self.logger)
|
|
354
|
+
|
|
355
|
+
# Save results
|
|
356
|
+
self._ns.save_results(self.paths.result_path)
|
|
357
|
+
|
|
358
|
+
# Set fitted to True
|
|
359
|
+
self._fitted = True
|
|
360
|
+
|
|
361
|
+
def plot(self, results: NSResults, save: bool = True, plot_native_model: bool = False) -> None:
|
|
362
|
+
'''
|
|
363
|
+
Plot the results.
|
|
364
|
+
|
|
365
|
+
Parameters
|
|
366
|
+
----------
|
|
367
|
+
results : NSResults
|
|
368
|
+
An instance of NSResults
|
|
369
|
+
save : bool
|
|
370
|
+
Whether to save the results
|
|
371
|
+
plot_native_model : bool
|
|
372
|
+
Whether to plot the native model
|
|
373
|
+
|
|
374
|
+
Notes
|
|
375
|
+
-----
|
|
376
|
+
Authors: Allan Denis
|
|
377
|
+
'''
|
|
378
|
+
|
|
379
|
+
# Initial checks
|
|
380
|
+
if not isinstance(results, NSResults):
|
|
381
|
+
raise ForMoSAError(f'Wrong type for results: {type(results)}. Expected NSResults')
|
|
382
|
+
|
|
383
|
+
self.plots = Plotting(results, self.logger)
|
|
384
|
+
|
|
385
|
+
fig_corner = self.plots.plot_corner()
|
|
386
|
+
|
|
387
|
+
if save:
|
|
388
|
+
path = self.paths.result_path / 'corner.pdf'
|
|
389
|
+
fig_corner.savefig(path, dpi=300, bbox_inches='tight')
|
|
390
|
+
|
|
391
|
+
fig_chains, axs = self.plots.plot_chains()
|
|
392
|
+
|
|
393
|
+
if save:
|
|
394
|
+
path = self.paths.result_path / 'chains.pdf'
|
|
395
|
+
fig_chains.savefig(path, dpi=300, bbox_inches='tight')
|
|
396
|
+
|
|
397
|
+
fig_radar, ax = self.plots.plot_radars()
|
|
398
|
+
|
|
399
|
+
if save:
|
|
400
|
+
path = self.paths.result_path / 'radar.pdf'
|
|
401
|
+
fig_radar.savefig(path, dpi=300, bbox_inches='tight')
|
|
402
|
+
|
|
403
|
+
# Get native best fit from ns_analysis if plot_native_model is True, otherwise set it to None to avoid unnecessary computations in the plot_fit function
|
|
404
|
+
native_best_fit = None
|
|
405
|
+
if plot_native_model:
|
|
406
|
+
native_best_fit = self.ns_analysis.native_best_fit
|
|
407
|
+
|
|
408
|
+
# Plot best fit for each observation, with the native model if requested
|
|
409
|
+
fig_best_fit, ax, ax_filt, axr, axr2 = self.plots.plot_fit(self.ns.restricted_observations, self.ns_analysis.best_fit, plot_native_model=plot_native_model, native_model=native_best_fit)
|
|
410
|
+
|
|
411
|
+
# If requested, plot the 1-sigma and 2-sigma confidence intervals of the best fit in the best fit plot
|
|
412
|
+
if plot_native_model:
|
|
413
|
+
lower_1_sigma, higher_1_sigma = self.ns_analysis.best_fit_interval(perc=0.68)
|
|
414
|
+
lower_2_sigma, higher_2_sigma = self.ns_analysis.best_fit_interval(perc=0.95)
|
|
415
|
+
|
|
416
|
+
ax.fill_between(lower_1_sigma.wave, lower_1_sigma.flux, higher_1_sigma.flux, color='grey', alpha=0.5, zorder=PLOTS_CONFIG.BestFitPlot.zorder, label='1-sig interval')
|
|
417
|
+
ax.fill_between(lower_2_sigma.wave, lower_2_sigma.flux, higher_2_sigma.flux, color='grey', alpha=0.2, zorder=PLOTS_CONFIG.BestFitPlot.zorder, label='2-sig interval')
|
|
418
|
+
|
|
419
|
+
if save:
|
|
420
|
+
path = self.paths.result_path / 'best_fit.pdf'
|
|
421
|
+
fig_best_fit.savefig(path, dpi=300, bbox_inches='tight')
|
|
422
|
+
|
|
423
|
+
# =========================================
|
|
424
|
+
# CCF Plotting Functions
|
|
425
|
+
# =========================================
|
|
426
|
+
def plot_ccf(self, rv_grid: np.ndarray, save_fig: bool = True, save_results: bool = False) -> None:
|
|
427
|
+
'''
|
|
428
|
+
Compute and optionally plot the Cross-Correlation Function (CCF).
|
|
429
|
+
|
|
430
|
+
Parameters
|
|
431
|
+
----------
|
|
432
|
+
rv_grid : np.ndarray
|
|
433
|
+
Grid of radial velocity values (in km/s)
|
|
434
|
+
save_fig : bool
|
|
435
|
+
Whether to save the figure
|
|
436
|
+
save_results : bool
|
|
437
|
+
Whether to save the results of the CCF computation
|
|
438
|
+
|
|
439
|
+
Notes
|
|
440
|
+
-----
|
|
441
|
+
Authors: Bhavesh Rajpoot (adapted from Allan Denis)
|
|
442
|
+
'''
|
|
443
|
+
|
|
444
|
+
if self.ns is None or self.ns.results is None:
|
|
445
|
+
raise ForMoSAError('Please first run the Nested Sampling before computing the CCF', self.logger)
|
|
446
|
+
|
|
447
|
+
if not hasattr(self, 'plots') or self.plots is None:
|
|
448
|
+
self.plots = Plotting(self.ns.results, self.logger)
|
|
449
|
+
|
|
450
|
+
# initialize save_path based on save_results or save_fig
|
|
451
|
+
save_path = self.paths.result_path if (save_results or save_fig) else None
|
|
452
|
+
|
|
453
|
+
for index in range(self.observations.n_observations):
|
|
454
|
+
ccf_dict = self.ns_analysis.compute_ccf(rv_grid, index=index)
|
|
455
|
+
file_tag = list(ccf_dict.keys())[0]
|
|
456
|
+
rv_grid, ccf, acf, ccf_star, _, _ = list(ccf_dict[file_tag].values())
|
|
457
|
+
fig, ax = self.plots.plot_ccf(rv_grid, ccf, acf, ccf_star=ccf_star, title=file_tag)
|
|
458
|
+
|
|
459
|
+
if save_path:
|
|
460
|
+
path = self.paths.result_path / f'ccf_{file_tag}.pdf'
|
|
461
|
+
fig.savefig(path)
|
|
462
|
+
|
|
463
|
+
if save_results:
|
|
464
|
+
results_path = self.paths.result_path / f'ccf_results_{file_tag}.npz'
|
|
465
|
+
|
|
466
|
+
# save the ccf_dict to a .npz file
|
|
467
|
+
np.savez(results_path, **ccf_dict[file_tag])
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def plot_rv_vsini_map(self, rv_grid: np.ndarray, vsini_grid: np.ndarray, save_fig: bool = True, save_results: bool = False) -> None:
|
|
471
|
+
'''
|
|
472
|
+
Compute and optionally plot the RV vs v.sin(i) loglikelihood map.
|
|
473
|
+
|
|
474
|
+
Parameters
|
|
475
|
+
----------
|
|
476
|
+
rv_grid : np.ndarray
|
|
477
|
+
Grid of radial velocity values (in km/s)
|
|
478
|
+
vsini_grid : np.ndarray
|
|
479
|
+
Grid of v.sin(i) values (in km/s)
|
|
480
|
+
save_fig : bool
|
|
481
|
+
Whether to save the figure
|
|
482
|
+
save_results : bool
|
|
483
|
+
Whether to save the results of the RV-vsini map computation
|
|
484
|
+
|
|
485
|
+
Notes
|
|
486
|
+
-----
|
|
487
|
+
Authors: Bhavesh Rajpoot (adapted from Allan Denis)
|
|
488
|
+
'''
|
|
489
|
+
|
|
490
|
+
if self.ns is None or self.ns.results is None:
|
|
491
|
+
raise ForMoSAError('Please first run the Nested Sampling before computing the RV-vsini map', self.logger)
|
|
492
|
+
|
|
493
|
+
if not hasattr(self, 'plots') or self.plots is None:
|
|
494
|
+
self.plots = Plotting(self.ns.results, self.logger)
|
|
495
|
+
|
|
496
|
+
save_path = self.paths.result_path if (save_fig or save_results) else None
|
|
497
|
+
|
|
498
|
+
for index in range(self.observations.n_observations):
|
|
499
|
+
rv_vsini_map = self.ns_analysis.compute_rv_vsini_map(rv_grid, vsini_grid, index=index)
|
|
500
|
+
file_tag = list(rv_vsini_map.keys())[0]
|
|
501
|
+
rv_grid, vsini_grid, logL_map, _, _ = tuple(rv_vsini_map[file_tag].values())
|
|
502
|
+
fig, ax = self.plots.plot_rv_vsini_map(rv_grid, vsini_grid, logL_map, title=file_tag)
|
|
503
|
+
|
|
504
|
+
if save_path:
|
|
505
|
+
path = self.paths.result_path / f'rv_vsini_map_{file_tag}.pdf'
|
|
506
|
+
fig.savefig(path, dpi=300, bbox_inches='tight')
|
|
507
|
+
|
|
508
|
+
if save_results:
|
|
509
|
+
results_path = self.paths.result_path / f'rv_vsini_map_results_{file_tag}.npz'
|
|
510
|
+
|
|
511
|
+
# save the rv_vsini_map to a .npz file
|
|
512
|
+
np.savez(results_path, **rv_vsini_map[file_tag])
|
|
File without changes
|