ExoIris 0.23.2__tar.gz → 1.0.0__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 (58) hide show
  1. {exoiris-0.23.2 → exoiris-1.0.0}/CHANGELOG.md +17 -0
  2. {exoiris-0.23.2 → exoiris-1.0.0}/ExoIris.egg-info/PKG-INFO +1 -1
  3. {exoiris-0.23.2 → exoiris-1.0.0}/ExoIris.egg-info/SOURCES.txt +1 -0
  4. {exoiris-0.23.2 → exoiris-1.0.0}/PKG-INFO +1 -1
  5. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/api/exoiris.rst +54 -4
  6. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/api/tsdata.rst +1 -1
  7. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/index.rst +2 -0
  8. {exoiris-0.23.2 → exoiris-1.0.0}/exoiris/exoiris.py +29 -5
  9. exoiris-1.0.0/exoiris/prtretrieval.py +164 -0
  10. {exoiris-0.23.2 → exoiris-1.0.0}/exoiris/spotmodel.py +2 -2
  11. {exoiris-0.23.2 → exoiris-1.0.0}/exoiris/tslpf.py +71 -13
  12. {exoiris-0.23.2 → exoiris-1.0.0}/.github/workflows/python-package.yml +0 -0
  13. {exoiris-0.23.2 → exoiris-1.0.0}/.gitignore +0 -0
  14. {exoiris-0.23.2 → exoiris-1.0.0}/.readthedocs.yaml +0 -0
  15. {exoiris-0.23.2 → exoiris-1.0.0}/CODE_OF_CONDUCT.md +0 -0
  16. {exoiris-0.23.2 → exoiris-1.0.0}/ExoIris.egg-info/dependency_links.txt +0 -0
  17. {exoiris-0.23.2 → exoiris-1.0.0}/ExoIris.egg-info/requires.txt +0 -0
  18. {exoiris-0.23.2 → exoiris-1.0.0}/ExoIris.egg-info/top_level.txt +0 -0
  19. {exoiris-0.23.2 → exoiris-1.0.0}/LICENSE +0 -0
  20. {exoiris-0.23.2 → exoiris-1.0.0}/README.md +0 -0
  21. {exoiris-0.23.2 → exoiris-1.0.0}/doc/Makefile +0 -0
  22. {exoiris-0.23.2 → exoiris-1.0.0}/doc/make.bat +0 -0
  23. {exoiris-0.23.2 → exoiris-1.0.0}/doc/requirements.txt +0 -0
  24. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/_static/css/custom.css +0 -0
  25. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/api/binning.rst +0 -0
  26. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/conf.py +0 -0
  27. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/e01/01a_not_so_short_intro.ipynb +0 -0
  28. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/e01/01b_short_intro.ipynb +0 -0
  29. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/e01/02_increasing_knot_resolution.ipynb +0 -0
  30. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/e01/03_increasing_data_resolution.ipynb +0 -0
  31. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/e01/04_gaussian_processes.ipynb +0 -0
  32. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/e01/05a_ldtkldm.ipynb +0 -0
  33. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/e01/A2_full_data_resolution.ipynb +0 -0
  34. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/e01/appendix_1_data_preparation.ipynb +0 -0
  35. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/e01/data/README.txt +0 -0
  36. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/e01/data/nirHiss_order_1.h5 +0 -0
  37. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/e01/data/nirHiss_order_2.h5 +0 -0
  38. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/e01/example1.png +0 -0
  39. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/e01/plot_1.ipynb +0 -0
  40. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/figures.ipynb +0 -0
  41. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/friendly_introduction.ipynb +0 -0
  42. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/index.rst +0 -0
  43. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/k_knot_example.svg +0 -0
  44. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/examples/setup_multiprocessing.py +0 -0
  45. {exoiris-0.23.2 → exoiris-1.0.0}/doc/source/install.rst +0 -0
  46. {exoiris-0.23.2 → exoiris-1.0.0}/exoiris/__init__.py +0 -0
  47. {exoiris-0.23.2 → exoiris-1.0.0}/exoiris/binning.py +0 -0
  48. {exoiris-0.23.2 → exoiris-1.0.0}/exoiris/ephemeris.py +0 -0
  49. {exoiris-0.23.2 → exoiris-1.0.0}/exoiris/ldtkld.py +0 -0
  50. {exoiris-0.23.2 → exoiris-1.0.0}/exoiris/loglikelihood.py +0 -0
  51. {exoiris-0.23.2 → exoiris-1.0.0}/exoiris/tsdata.py +0 -0
  52. {exoiris-0.23.2 → exoiris-1.0.0}/exoiris/tsmodel.py +0 -0
  53. {exoiris-0.23.2 → exoiris-1.0.0}/exoiris/util.py +0 -0
  54. {exoiris-0.23.2 → exoiris-1.0.0}/exoiris/wlpf.py +0 -0
  55. {exoiris-0.23.2 → exoiris-1.0.0}/pyproject.toml +0 -0
  56. {exoiris-0.23.2 → exoiris-1.0.0}/requirements.txt +0 -0
  57. {exoiris-0.23.2 → exoiris-1.0.0}/setup.cfg +0 -0
  58. {exoiris-0.23.2 → exoiris-1.0.0}/tests/test_binning.py +0 -0
@@ -5,6 +5,23 @@ All notable changes to ExoIris will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.0] - 2026-01-28
9
+
10
+ ### Added
11
+ - Added support for setting custom interpolation models for limb darkening via `set_limb_darkening_interpolator` method.
12
+ - Added support for setting custom interpolation models for radius ratios via `set_radius_ratio_interpolator` method.
13
+ - Added ability to set priors for offset parameters in the analysis workflow.
14
+ - Added interpolation type tracking in FITS header metadata (`INTERP_LD` for limb darkening interpolation).
15
+
16
+ ### Changed
17
+ - Improved reinitialization logic for MCMC and differential evolution populations with updated limb darkening coefficients and knots.
18
+ - Refactored interpolation handling in `TSLPF` to separately manage radius ratio and limb darkening interpolators.
19
+ - Updated documentation for API methods and workflow.
20
+
21
+ ### Fixed
22
+ - Fixed incorrect FWHM scaling in `spot_model` function. The scaling factor now correctly uses `log(4)^(1/shape)` for
23
+ proper full width at half maximum calculations in generalized Gaussian spot models.
24
+
8
25
  ## [0.23.1] - 2025-12-18
9
26
 
10
27
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ExoIris
3
- Version: 0.23.2
3
+ Version: 1.0.0
4
4
  Summary: Easy and robust exoplanet transmission spectroscopy.
5
5
  Author-email: Hannu Parviainen <hannu@iac.es>
6
6
  License: GPLv3
@@ -46,6 +46,7 @@ exoiris/ephemeris.py
46
46
  exoiris/exoiris.py
47
47
  exoiris/ldtkld.py
48
48
  exoiris/loglikelihood.py
49
+ exoiris/prtretrieval.py
49
50
  exoiris/spotmodel.py
50
51
  exoiris/tsdata.py
51
52
  exoiris/tslpf.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ExoIris
3
- Version: 0.23.2
3
+ Version: 1.0.0
4
4
  Summary: Easy and robust exoplanet transmission spectroscopy.
5
5
  Author-email: Hannu Parviainen <hannu@iac.es>
6
6
  License: GPLv3
@@ -64,9 +64,25 @@ transmission spectrum, and even the observational data can be changed to improve
64
64
  ExoIris.set_data
65
65
  ExoIris.set_radius_ratio_knots
66
66
  ExoIris.add_radius_ratio_knots
67
+ ExoIris.set_limb_darkening_knots
68
+ ExoIris.free_radius_ratio_knot_locations
67
69
  ExoIris.create_dense_radius_ratio_block
68
70
  ExoIris.plot_setup
69
71
 
72
+ Interpolation configuration
73
+ ---------------------------
74
+
75
+ ExoIris uses interpolation to model the wavelength-dependent radius ratio and limb darkening
76
+ parameters. The interpolation method can be customized to balance smoothness against fidelity to the
77
+ data. Available interpolators include: ``nearest``, ``linear``, ``pchip``, ``makima``, ``bspline``,
78
+ ``bspline-quadratic``, and ``bspline-cubic``.
79
+
80
+ .. autosummary::
81
+ :toctree: api/
82
+
83
+ ExoIris.set_radius_ratio_interpolator
84
+ ExoIris.set_limb_darkening_interpolator
85
+
70
86
  Parameterization and priors
71
87
  ---------------------------
72
88
 
@@ -83,10 +99,11 @@ Noise model setup
83
99
  -----------------
84
100
 
85
101
  The noise in the spectroscopic light curves can be modeled as either white noise or time-correlated noise
86
- (using a Gaussian process, GP). The noise model is chosen with the `ExoIris.noise_model` method, and can be
87
- set to either "white" or "fixed_gp." Selecting "fixed_gp" models the noise as a time-correlated Gaussian process
88
- using the `celerite2` package. The corresponding `celerite2.GaussianProcess` object can be accessed directly
89
- via the `ExoIris.gp` attribute.
102
+ (using a Gaussian process, GP). The noise model is chosen with the `ExoIris.set_noise_model` method, and can be
103
+ set to ``"white"``, ``"fixed_gp"``, or ``"free_gp"``. Selecting ``"fixed_gp"`` models the noise as a time-correlated
104
+ Gaussian process using the `celerite2` package with fixed hyperparameters, while ``"free_gp"`` allows the GP
105
+ hyperparameters to be sampled as free parameters. The corresponding `celerite2.GaussianProcess` object can be
106
+ accessed directly via the `ExoIris.gp` attribute.
90
107
 
91
108
  .. autosummary::
92
109
  :toctree: api/
@@ -98,6 +115,23 @@ via the `ExoIris.gp` attribute.
98
115
  ExoIris.gp
99
116
  ExoIris.plot_white_gp_predictions
100
117
 
118
+ Star spot modeling
119
+ ------------------
120
+
121
+ ExoIris supports modeling of star spot crossings during transit and the Transit Light Source Effect (TLSE).
122
+ Star spots can cause both localized bumps in the light curve (when the planet occults a spot) and
123
+ wavelength-dependent baseline variations (TLSE) due to the inhomogeneous stellar surface.
124
+
125
+ To use spot modeling, first initialize the spot model with `ExoIris.initialize_spots`, then add spots
126
+ for specific epoch groups using `ExoIris.add_spot`.
127
+
128
+ .. autosummary::
129
+ :toctree: api/
130
+
131
+ ExoIris.initialize_spots
132
+ ExoIris.add_spot
133
+ ExoIris.nspots
134
+
101
135
 
102
136
  First steps
103
137
  -----------
@@ -155,12 +189,27 @@ Pandas `~pandas.DataFrame`.
155
189
  :toctree: api/
156
190
 
157
191
  ExoIris.transmission_spectrum
192
+ ExoIris.transmission_spectrum_table
158
193
  ExoIris.posterior_samples
159
194
  ExoIris.plot_fit
160
195
  ExoIris.plot_transmission_spectrum
161
196
  ExoIris.plot_residuals
162
197
  ExoIris.plot_limb_darkening_parameters
163
198
 
199
+ Atmospheric retrieval
200
+ ---------------------
201
+
202
+ ExoIris provides tools for atmospheric retrieval by creating a log-likelihood function that can be
203
+ used with external retrieval codes. The `ExoIris.create_loglikelihood_function` method returns a
204
+ callable that evaluates the log-likelihood for a given transmission spectrum model, accounting for
205
+ the full covariance structure of the data.
206
+
207
+ .. autosummary::
208
+ :toctree: api/
209
+
210
+ ExoIris.create_loglikelihood_function
211
+ ExoIris.transmission_spectrum_samples
212
+
164
213
  Utility methods
165
214
  ---------------
166
215
 
@@ -186,6 +235,7 @@ The following properties expose key internal states and parameters of the analys
186
235
  ExoIris.nk
187
236
  ExoIris.nldp
188
237
  ExoIris.npb
238
+ ExoIris.nspots
189
239
  ExoIris.ldmodel
190
240
  ExoIris.sampler
191
241
  ExoIris.optimizer
@@ -28,7 +28,7 @@ Data wrangling
28
28
  TSData.bin_time
29
29
  TSData.crop_wavelength
30
30
  TSData.crop_time
31
- TSData.remove_outliers
31
+ TSData.mask_outliers
32
32
  TSData.normalize_to_median
33
33
  TSData.normalize_to_poly
34
34
  TSData.partition_time
@@ -52,6 +52,8 @@ Documentation
52
52
  api/exoiris
53
53
  api/tsdata
54
54
  api/binning
55
+ api/ldtkld
56
+ api/utilities
55
57
 
56
58
  Support
57
59
  -------
@@ -43,7 +43,7 @@ from uncertainties import UFloat
43
43
 
44
44
  from .ldtkld import LDTkLD
45
45
  from .tsdata import TSData, TSDataGroup
46
- from .tslpf import TSLPF
46
+ from .tslpf import TSLPF, interpolators
47
47
  from .wlpf import WhiteLPF
48
48
  from .loglikelihood import LogLikelihood
49
49
 
@@ -88,7 +88,14 @@ def load_model(fname: Path | str, name: str | None = None):
88
88
  try:
89
89
  ip = hdr['INTERP']
90
90
  except KeyError:
91
- ip = 'bspline'
91
+ ip = 'linear'
92
+
93
+ # Read the interpolation model.
94
+ # =============================
95
+ try:
96
+ ip_ld = hdr['INTERP_LD']
97
+ except KeyError:
98
+ ip_ld = 'bspline-quadratic'
92
99
 
93
100
  # Read the noise model.
94
101
  # =====================
@@ -100,6 +107,7 @@ def load_model(fname: Path | str, name: str | None = None):
100
107
  # Setup the analysis.
101
108
  # ===================
102
109
  a = ExoIris(name or hdr['NAME'], ldmodel=ldm, data=data, noise_model=noise_model, interpolation=ip)
110
+ a.set_limb_darkening_interpolator(ip_ld)
103
111
  a.set_radius_ratio_knots(hdul['K_KNOTS'].data.astype('d'))
104
112
  a.set_limb_darkening_knots(hdul['LD_KNOTS'].data.astype('d'))
105
113
 
@@ -263,15 +271,15 @@ class ExoIris:
263
271
  data = TSDataGroup([data]) if isinstance(data, TSData) else data
264
272
  self._tsa.set_data(data)
265
273
 
266
- def set_prior(self, parameter: Literal['radius ratios', 'baselines', 'wn multipliers'] | str,
274
+ def set_prior(self, parameter: Literal['radius ratios', 'offsets', 'wn multipliers'] | str,
267
275
  prior: str | Any, *nargs) -> None:
268
276
  """Set a prior on a model parameter.
269
277
 
270
278
  Parameters
271
279
  ----------
272
280
  parameter
273
- The name of the parameter to set a prior for. Can also be 'radius ratios', 'baselines', or 'wn multipliers'
274
- to set identical priors on all the radius ratios, baselines, or white noise multipliers.
281
+ The name of the parameter to set a prior for. Can also be 'radius ratios', 'offsets', or 'wn multipliers'
282
+ to set identical priors on all the radius ratios, offsets, or white noise multipliers.
275
283
 
276
284
  prior
277
285
  The prior distribution for the parameter. This can be "NP" for a normal prior, "UP" for a
@@ -287,6 +295,9 @@ class ExoIris:
287
295
  elif parameter == 'wn multipliers':
288
296
  for par in self.ps[self._tsa._sl_wnm]:
289
297
  self.set_prior(par.name, prior, *nargs)
298
+ elif parameter == 'offsets':
299
+ for par in self.ps[self._tsa._sl_bias]:
300
+ self.set_prior(par.name, prior, *nargs)
290
301
  else:
291
302
  self._tsa.set_prior(parameter, prior, *nargs)
292
303
 
@@ -509,6 +520,12 @@ class ExoIris:
509
520
  else:
510
521
  return self._wa.std_errors
511
522
 
523
+ def set_radius_ratio_interpolator(self, interpolator: str) -> None:
524
+ """Set the interpolator for the radius ratio (k) model."""
525
+ if interpolator not in interpolators.keys():
526
+ raise ValueError(f"Interpolator {interpolator} not recognized.")
527
+ self._tsa.set_k_interpolator(interpolator)
528
+
512
529
  def add_radius_ratio_knots(self, knot_wavelengths: Sequence) -> None:
513
530
  """Add radius ratio (k) knots.
514
531
 
@@ -558,6 +575,12 @@ class ExoIris:
558
575
  nk = nk[(nk >= wlmin) & (nk <= wlmax)]
559
576
  self.set_radius_ratio_knots(r_[ck[ck < nk[0]], nk, ck[ck > nk[-1]]])
560
577
 
578
+ def set_limb_darkening_interpolator(self, interpolator: str) -> None:
579
+ """Set the interpolator for the limb darkening model."""
580
+ if interpolator not in interpolators.keys():
581
+ raise ValueError(f"Interpolator {interpolator} not recognized.")
582
+ self._tsa.set_ld_interpolator(interpolator)
583
+
561
584
  def add_limb_darkening_knots(self, knot_wavelengths: Sequence) -> None:
562
585
  """Add limb darkening knots.
563
586
 
@@ -1283,6 +1306,7 @@ class ExoIris:
1283
1306
  pri.header['t14'] = self.transit_duration
1284
1307
  pri.header['ndgroups'] = self.data.size
1285
1308
  pri.header['interp'] = self._tsa.interpolation
1309
+ pri.header['interp_ld'] = self._tsa.ld_interpolation
1286
1310
  pri.header['noise'] = self._tsa.noise_model
1287
1311
 
1288
1312
  if self._tsa.free_k_knot_ids is None:
@@ -0,0 +1,164 @@
1
+ # ExoIris: fast, flexible, and easy exoplanet transmission spectroscopy in Python.
2
+ # Copyright (C) 2025 Hannu Parviainen
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+ import io
18
+ import warnings
19
+ from contextlib import redirect_stdout
20
+ import numpy as np
21
+
22
+ from petitRADTRANS.radtrans import Radtrans
23
+ from petitRADTRANS import physical_constants as cst
24
+ from petitRADTRANS.spectral_model import SpectralModel
25
+ from petitRADTRANS.retrieval.data import Data
26
+
27
+ with warnings.catch_warnings():
28
+ warnings.simplefilter('ignore')
29
+ from exoiris.exoiris import ExoIris
30
+ from pytransit.lpf.logposteriorfunction import LogPosteriorFunction
31
+ from pytransit.param import ParameterSet, GParameter, UniformPrior as UP, NormalPrior as NP
32
+
33
+ class PRTRetrieval(LogPosteriorFunction):
34
+ def __init__(self, name: str, line_species, ei: ExoIris, rstar: float, mplanet: float, temperature_profile='isothermal',
35
+ pres: int = 100, r: int | None = None, quiet: bool = False):
36
+ super().__init__(name)
37
+ self._line_names = line_species
38
+ if r is None:
39
+ self.line_species = line_species
40
+ else:
41
+ self.line_species = [ls + f'.R{r}' for ls in line_species]
42
+
43
+ self.rstar = rstar
44
+ self.mplanet = mplanet
45
+ self.temperature_profile = temperature_profile
46
+ self.pres = pres
47
+ self.ei = ei
48
+ self.r = r
49
+
50
+ with warnings.catch_warnings():
51
+ warnings.simplefilter("ignore")
52
+ if quiet:
53
+ buf = io.StringIO()
54
+ with redirect_stdout(buf):
55
+ self._create_model()
56
+ else:
57
+ self._create_model()
58
+
59
+ self._init_parameters()
60
+
61
+ self.wavelengths = self.model()[0]
62
+ self._loglike = ei.create_loglikelihood_function(self.wavelengths, 'radius_ratio', method='svd')
63
+
64
+ def _init_parameters(self):
65
+ self.ps = ParameterSet([])
66
+ self._init_p_planet()
67
+ self._init_p_temperature()
68
+ self._init_p_clouds()
69
+ self._init_p_species()
70
+
71
+ def _init_p_planet(self):
72
+ self.ps.thaw()
73
+ ps = [GParameter('radius', 'Planet radius', 'R_Jup', NP(1.3, 0.05), (0.0, 2.5)),
74
+ GParameter('logg', 'Planet log g', '', UP(2, 4), (2, 4))]
75
+ self.ps.add_global_block('planet', ps)
76
+ self.ps.freeze()
77
+ self._sl_planet = self.ps.blocks[-1].slice
78
+ self._st_planet = self.ps.blocks[-1].start
79
+
80
+ def _init_p_clouds(self):
81
+ self.ps.thaw()
82
+ ps = [GParameter('pc', 'Cloud top pressure', '', UP(-6, 2), (-6, 2))]
83
+ self.ps.add_global_block('clouds', ps)
84
+ self.ps.freeze()
85
+ self._sl_clouds = self.ps.blocks[-1].slice
86
+ self._st_clouds = self.ps.blocks[-1].start
87
+
88
+ def _init_p_temperature(self):
89
+ self.ps.thaw()
90
+ if self.temperature_profile == 'isothermal':
91
+ ps = [GParameter('tp_teq', 'equilibrium temperature', 'K', UP(800, 1200), (500, 3500))]
92
+ elif self.temperature_profile == 'guillot':
93
+ ps = [GParameter('tp_teq', 'equilibrium temperature', 'K', UP(800, 1200), (500, 3500)),
94
+ GParameter('tp_tin', 'intrinsinc temperature', 'K', UP(800, 1200), (500, 3500)),
95
+ GParameter('tp_mo', 'guillot profile mean opacity', 'K', UP(0, 1), (0, 1)),
96
+ GParameter('tp_g', 'guillot profile gamma', 'K', UP(0, 1), (0, 1))]
97
+ self.ps.add_global_block('temperature_profile', ps)
98
+ self._sl_tprof = self.ps.blocks[-1].slice
99
+ self._st_tprof = self.ps.blocks[-1].start
100
+
101
+ def _init_p_species(self):
102
+ self.ps.thaw()
103
+ ps = [GParameter(n, n, '', UP(-12, -0.2), [-12, 0]) for n in self.line_species]
104
+ self.ps.add_global_block('line_species', ps)
105
+ self.ps.freeze()
106
+ self._sl_species = self.ps.blocks[-1].slice
107
+ self._st_species = self.ps.blocks[-1].start
108
+
109
+ def _c_temperature_profile(self, pv) -> dict:
110
+ pv = pv[self._sl_tprof]
111
+ pars = {}
112
+ pars['temperature'] = pv[0]
113
+ if self.temperature_profile == 'isothermal':
114
+ pass
115
+ elif self.temperature_profile == 'guillot':
116
+ pars['intrinsic_temperature'] = pv[1]
117
+ pars['guillot_temperature_profile_infrared_mean_opacity_solar_metallicity'] = pv[2]
118
+ pars['guillot_temperature_profile_gamma'] = pv[3]
119
+ return pars
120
+
121
+ def _create_model(self):
122
+ self.sm = SpectralModel(
123
+ pressures=np.logspace(-6, 2, self.pres),
124
+ line_species=self.line_species,
125
+ rayleigh_species=['H2', 'He'],
126
+ gas_continuum_contributors=['H2--H2', 'H2--He'],
127
+ wavelength_boundaries=[self.ei.data.wlmin, self.ei.data.wlmax],
128
+ star_radius=self.rstar * cst.r_sun,
129
+ planet_radius=1.0 * cst.r_jup_mean,
130
+ planet_mass=self.mplanet * cst.m_jup,
131
+ reference_gravity=391,
132
+ reference_pressure=1e-2,
133
+ temperature_profile=self.temperature_profile,
134
+ temperature=1000,
135
+ # cloud_mode='power_law',
136
+ # power_law_opacity_350nm = 0.01,
137
+ # power_law_opacity_coefficient = -4.0,
138
+ # opaque_cloud_top_pressure=1e-3,
139
+ # cloud_fraction=1.0,
140
+ haze_factor=100.0,
141
+ # co_ratio=1,
142
+ use_equilibrium_chemistry=False,
143
+ imposed_mass_fractions={s: 1e-4 for s in self.line_species},
144
+ filling_species={'H2': 37, 'He': 12}
145
+ )
146
+
147
+ def model(self, pv=None):
148
+ if pv is not None:
149
+
150
+ self.sm.model_parameters.update(self._c_temperature_profile(pv))
151
+ self.sm.model_parameters['planet_radius'] = pv[self._st_planet] * cst.r_jup_mean
152
+ self.sm.model_parameters['reference_gravity'] = 10 ** pv[self._st_planet + 1]
153
+ self.sm.model_parameters['opaque_cloud_top_pressure'] = 10 ** pv[self._st_clouds]
154
+ for i, ls in enumerate(self.line_species):
155
+ self.sm.model_parameters['imposed_mass_fractions'][ls] = 10 ** pv[self._st_species + i]
156
+ self.sm.update_spectral_calculation_parameters(**self.sm.model_parameters)
157
+ wavelengths, radii = self.sm.calculate_spectrum(mode='transmission')
158
+ return wavelengths[0] * 1e4, radii[0]
159
+
160
+ def lnlikelihood(self, pv):
161
+ with warnings.catch_warnings():
162
+ warnings.simplefilter("ignore")
163
+ model_spectrum = self.model(pv)[1]
164
+ return self._loglike(model_spectrum / (self.rstar * cst.r_sun))
@@ -41,8 +41,8 @@ from exoiris.util import bin2d
41
41
 
42
42
  @njit
43
43
  def spot_model(x, center, amplitude, fwhm, shape):
44
- c = fwhm / 2*(2*log(2))**(1/shape)
45
- return amplitude*exp(-(fabs(x-center) / c)**shape)
44
+ c = log(4)**(1/shape) * fwhm
45
+ return amplitude*exp(-(2*fabs(x-center) / c)**shape)
46
46
 
47
47
 
48
48
  @njit
@@ -191,12 +191,13 @@ class TSLPF(LogPosteriorFunction):
191
191
  self.npt: list[int] | None = None
192
192
  self._baseline_models: list[ndarray] | None = None
193
193
  self.interpolation: str = interpolation
194
+ self.ld_interpolation: str = 'bspline-quadratic'
194
195
 
195
196
  if interpolation not in interpolator_choices:
196
197
  raise ValueError(f'interpolation must be one of {interpolator_choices}')
197
198
 
198
199
  self._ip = interpolators[interpolation]
199
- self._ip_ld = interpolators['bspline']
200
+ self._ip_ld = interpolators[self.ld_interpolation]
200
201
 
201
202
  self._gp: Optional[list[GP]] = None
202
203
  self._gp_time: Optional[list[ndarray]] = None
@@ -468,6 +469,13 @@ class TSLPF(LogPosteriorFunction):
468
469
  """
469
470
  self.set_k_knots(concatenate([self.k_knots, knot_wavelengths]))
470
471
 
472
+ def set_k_interpolator(self, interpolator: str) -> None:
473
+ """Set the interpolator for the radius ratio (k) model."""
474
+ if interpolator not in interpolators.keys():
475
+ raise ValueError(f"Interpolator {interpolator} not recognized.")
476
+ self.interpolation = interpolator
477
+ self._ip = interpolators[interpolator]
478
+
471
479
  def set_k_knots(self, knot_wavelengths) -> None:
472
480
  """Set the radius ratio (k) knot wavelengths for the model.
473
481
 
@@ -607,6 +615,12 @@ class TSLPF(LogPosteriorFunction):
607
615
  return logp
608
616
  self._additional_log_priors.append(k_knot_order_prior)
609
617
 
618
+ def set_ld_interpolator(self, interpolator: str) -> None:
619
+ """Set the interpolator for the limb darkening model."""
620
+ if interpolator not in interpolators.keys():
621
+ raise ValueError(f"Interpolator {interpolator} not recognized.")
622
+ self.ld_interpolation = interpolator
623
+ self._ip_ld = interpolators[interpolator]
610
624
 
611
625
  def add_ld_knots(self, knot_wavelengths) -> None:
612
626
  """Add limb darkening knots to the model.
@@ -626,36 +640,80 @@ class TSLPF(LogPosteriorFunction):
626
640
  knot_wavelengths : array-like
627
641
  Array of knot wavelengths.
628
642
  """
643
+
644
+ # Save the old variables
645
+ # ----------------------
629
646
  xo = self.ld_knots
647
+ pso = self.ps
648
+ deo = self._de_population
649
+ mco = self._mc_chains
650
+ slo = self._sl_ld
651
+ ndo = self.ndim
652
+
630
653
  xn = self.ld_knots = sort(knot_wavelengths)
631
654
  self.nldc = self.ld_knots.size
632
655
 
633
- pvpo = self.de.population.copy() if self.de is not None else None
634
- pso = self.ps
635
- sldo = self._sl_ld
636
656
  self._init_parameters()
637
657
  psn = self.ps
638
- sldn = self._sl_ld
658
+ sln = self._sl_ld
659
+ ndn = self.ndim
660
+
661
+ # Check if we have spots
662
+ # ----------------------
663
+ if self.spot_model is not None:
664
+ spots = self.spot_model
665
+ self.initialize_spots(spots.tphot, spots.wlref, spots.include_tlse)
666
+ for eg in spots.spot_epoch_groups:
667
+ self.spot_model.add_spot(eg)
668
+
669
+ # Set the priors back as they were
670
+ # --------------------------------
639
671
  for po in pso:
640
672
  if po.name in psn.names:
641
673
  self.set_prior(po.name, po.prior)
642
674
 
643
- if self.de is not None:
644
- pvpn = self.create_pv_population(pvpo.shape[0])
675
+ # Resample the DE parameter population
676
+ # ------------------------------------
677
+ if self._de_population is not None:
678
+ den = zeros((deo.shape[0], ndn))
679
+
645
680
  # Copy the old parameter values
646
681
  # -----------------------------
647
682
  for pid_old, p in enumerate(pso):
648
- if p.name in psn:
683
+ if p.name in psn.names:
649
684
  pid_new = psn.find_pid(p.name)
650
- pvpn[:, pid_new] = pvpo[:, pid_old]
685
+ den[:, pid_new] = deo[:, pid_old]
686
+
687
+ # Resample the limb darkening coefficients
688
+ # ----------------------------------------
689
+ for i in range(den.shape[0]):
690
+ den[i, sln][0::2] = self._ip_ld(xn, xo, deo[i, slo][0::2])
691
+ den[i, sln][1::2] = self._ip_ld(xn, xo, deo[i, slo][1::2])
692
+
693
+ self._de_population = den
694
+ self.de = None
695
+
696
+ # Resample the MCMC parameter population
697
+ # --------------------------------------
698
+ if self._mc_chains is not None:
699
+ fmco = mco.reshape([-1, ndo])
700
+ fmcn = zeros((fmco.shape[0], ndn))
701
+
702
+ # Copy the old parameter values
703
+ # -----------------------------
704
+ for pid_old, p in enumerate(pso):
705
+ if p.name in psn.names:
706
+ pid_new = psn.find_pid(p.name)
707
+ fmcn[:, pid_new] = fmco[:, pid_old]
651
708
 
652
709
  # Resample the radius ratios
653
710
  # --------------------------
654
- for i in range(pvpn.shape[0]):
655
- pvpn[i, sldn] = self._ip(xn, xo, pvpo[i, sldo])
711
+ for i in range(fmcn.shape[0]):
712
+ fmcn[i, sln][0::2] = self._ip_ld(xn, xo, fmco[i, slo][0::2])
713
+ fmcn[i, sln][1::2] = self._ip_ld(xn, xo, fmco[i, slo][1::2])
656
714
 
657
- self.de = None
658
- self._de_population = pvpn
715
+ self._mc_chains = fmcn.reshape([mco.shape[0], mco.shape[1], ndn])
716
+ self.sampler = None
659
717
 
660
718
  def _eval_k(self, pvp) -> list[ndarray]:
661
719
  """Evaluate the radius ratio model.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes