ForMoSA 2.0.0__tar.gz → 2.0.2__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.
Files changed (80) hide show
  1. formosa-2.0.2/ForMoSA/__init__.py +7 -0
  2. formosa-2.0.2/ForMoSA/analysis.py +512 -0
  3. {formosa-2.0.0/ForMoSA/adapt → formosa-2.0.2/ForMoSA/config}/__init__.py +0 -0
  4. formosa-2.0.2/ForMoSA/config/global_config.py +1575 -0
  5. formosa-2.0.2/ForMoSA/config/paths.py +160 -0
  6. {formosa-2.0.0/ForMoSA/nested_sampling → formosa-2.0.2/ForMoSA/core}/__init__.py +0 -0
  7. formosa-2.0.2/ForMoSA/core/config.py +477 -0
  8. formosa-2.0.2/ForMoSA/core/enums.py +372 -0
  9. formosa-2.0.2/ForMoSA/core/errors.py +7 -0
  10. formosa-2.0.2/ForMoSA/core/loggings.py +79 -0
  11. formosa-2.0.2/ForMoSA/filter/filter.py +593 -0
  12. formosa-2.0.2/ForMoSA/grid/__init__.py +0 -0
  13. formosa-2.0.2/ForMoSA/grid/grid_loader.py +150 -0
  14. formosa-2.0.2/ForMoSA/grid/model_grid.py +597 -0
  15. formosa-2.0.2/ForMoSA/grid/subgrid_base.py +596 -0
  16. formosa-2.0.2/ForMoSA/grid/subgrid_photometry.py +461 -0
  17. formosa-2.0.2/ForMoSA/grid/subgrid_set.py +266 -0
  18. formosa-2.0.2/ForMoSA/grid/subgrid_spectroscopy.py +383 -0
  19. formosa-2.0.2/ForMoSA/nested_sampling/__init__.py +0 -0
  20. formosa-2.0.2/ForMoSA/nested_sampling/nested_sampling.py +691 -0
  21. formosa-2.0.2/ForMoSA/nested_sampling/ns_analysis.py +469 -0
  22. formosa-2.0.2/ForMoSA/nested_sampling/plotting.py +608 -0
  23. formosa-2.0.2/ForMoSA/nested_sampling/results.py +411 -0
  24. formosa-2.0.2/ForMoSA/observation/__init__.py +0 -0
  25. formosa-2.0.2/ForMoSA/observation/observation_base.py +507 -0
  26. formosa-2.0.2/ForMoSA/observation/observation_loader.py +432 -0
  27. formosa-2.0.2/ForMoSA/observation/observation_photometry.py +349 -0
  28. formosa-2.0.2/ForMoSA/observation/observation_set.py +778 -0
  29. formosa-2.0.2/ForMoSA/observation/observation_spectroscopy.py +589 -0
  30. formosa-2.0.2/ForMoSA/parameter/__init__.py +0 -0
  31. formosa-2.0.2/ForMoSA/parameter/parameter.py +209 -0
  32. formosa-2.0.2/ForMoSA/parameter/parameter_set.py +358 -0
  33. formosa-2.0.2/ForMoSA/parameter/prior.py +665 -0
  34. formosa-2.0.2/ForMoSA/transform/__init__.py +0 -0
  35. formosa-2.0.2/ForMoSA/transform/apply_effects.py +302 -0
  36. formosa-2.0.2/ForMoSA/transform/observed.py +441 -0
  37. formosa-2.0.2/ForMoSA/transform/photometric_effects.py +178 -0
  38. formosa-2.0.2/ForMoSA/transform/spectroscopic_effects.py +224 -0
  39. formosa-2.0.2/ForMoSA/utils/__init__.py +0 -0
  40. formosa-2.0.2/ForMoSA/utils/logL_functions.py +232 -0
  41. formosa-2.0.2/ForMoSA/utils/misc.py +309 -0
  42. formosa-2.0.2/ForMoSA/utils/prior_functions.py +90 -0
  43. formosa-2.0.2/ForMoSA/utils/spec.py +1079 -0
  44. formosa-2.0.2/ForMoSA.egg-info/PKG-INFO +304 -0
  45. formosa-2.0.2/ForMoSA.egg-info/SOURCES.txt +60 -0
  46. formosa-2.0.2/ForMoSA.egg-info/requires.txt +33 -0
  47. formosa-2.0.2/ForMoSA.egg-info/top_level.txt +4 -0
  48. {formosa-2.0.0 → formosa-2.0.2}/LICENSE +6 -2
  49. formosa-2.0.2/PKG-INFO +304 -0
  50. formosa-2.0.2/README.md +252 -0
  51. formosa-2.0.2/docs/conf.py +172 -0
  52. formosa-2.0.2/pyproject.toml +66 -0
  53. formosa-2.0.0/ForMoSA.egg-info/requires.txt → formosa-2.0.2/requirements.txt +6 -1
  54. formosa-2.0.2/tests/test_grid.py +262 -0
  55. formosa-2.0.2/tests/test_grid_set.py +175 -0
  56. formosa-2.0.2/tests/test_observation.py +357 -0
  57. formosa-2.0.2/tests/test_observation_set.py +206 -0
  58. formosa-2.0.2/tests/test_observed.py +128 -0
  59. formosa-2.0.2/tests/test_parameter_set.py +148 -0
  60. formosa-2.0.0/ForMoSA/__init__.py +0 -5
  61. formosa-2.0.0/ForMoSA/adapt/adapt_grid.py +0 -314
  62. formosa-2.0.0/ForMoSA/adapt/adapt_obs_mod.py +0 -152
  63. formosa-2.0.0/ForMoSA/adapt/extraction_functions.py +0 -568
  64. formosa-2.0.0/ForMoSA/main.py +0 -66
  65. formosa-2.0.0/ForMoSA/main_utilities.py +0 -181
  66. formosa-2.0.0/ForMoSA/nested_sampling/nested_logL_functions.py +0 -134
  67. formosa-2.0.0/ForMoSA/nested_sampling/nested_modif_spec.py +0 -562
  68. formosa-2.0.0/ForMoSA/nested_sampling/nested_prior_function.py +0 -33
  69. formosa-2.0.0/ForMoSA/nested_sampling/nested_sampling.py +0 -767
  70. formosa-2.0.0/ForMoSA/plotting/plotting_class.py +0 -1000
  71. formosa-2.0.0/ForMoSA.egg-info/PKG-INFO +0 -22
  72. formosa-2.0.0/ForMoSA.egg-info/SOURCES.txt +0 -23
  73. formosa-2.0.0/ForMoSA.egg-info/not-zip-safe +0 -1
  74. formosa-2.0.0/ForMoSA.egg-info/top_level.txt +0 -1
  75. formosa-2.0.0/PKG-INFO +0 -22
  76. formosa-2.0.0/README.md +0 -96
  77. formosa-2.0.0/setup.py +0 -23
  78. {formosa-2.0.0/ForMoSA/plotting → formosa-2.0.2/ForMoSA/filter}/__init__.py +0 -0
  79. {formosa-2.0.0 → formosa-2.0.2}/ForMoSA.egg-info/dependency_links.txt +0 -0
  80. {formosa-2.0.0 → formosa-2.0.2}/setup.cfg +0 -0
@@ -0,0 +1,7 @@
1
+ from .analysis import Analysis
2
+
3
+ __all__ = [
4
+ "Analysis"
5
+ ]
6
+
7
+ __version__ = "2.0.2"
@@ -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])