taurex-pcq 1.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.
taurex_pcq-1.0/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Mael Voyer
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.4
2
+ Name: taurex-pcq
3
+ Version: 1.0
4
+ Summary: A TauREx plugin implementing Pre Computed Qext grids for cloud models
5
+ Author-email: Maël Voyer <mael.voyer@cea.fr>
6
+ License: MIT
7
+ Keywords: taurex,exoplanets,retrieval,atmospheres,jwst
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: numpy
18
+ Requires-Dist: taurex
19
+ Requires-Dist: numba
20
+ Requires-Dist: scipy
21
+ Requires-Dist: h5py
22
+ Provides-Extra: dev
23
+ Requires-Dist: build; extra == "dev"
24
+ Requires-Dist: twine; extra == "dev"
25
+ Requires-Dist: pytest; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ # Faster clouds for TauREx 3
29
+ ---
30
+
31
+ This plugin provides **a new nethod to inclue aerosols** in [TauREx 3](https://github.com/ucl-exoplanets/TauREx3_public), extending the TauREx-PyMieScatt plugin. It significantly speeds up the cloud models by using precomputed extinction efficiency (`Q_ext`) grids. TauREx-PCQ also considerably improves the computation scaling with the number of clouds in the models. The speed-ups for single cloud retrievals are betwwen 1.4 and 2.7. Although, a single-cloud retrieval using Qext grids achieved a speed-up of 1.4, the same retrieval with four clouds became 17 times faster than the corresponding retrieval using direct Mie calculations.
32
+ The grids details and validation can be found in Voyer & Changeat (2026), if you use TauREx-PCQ or the grids please cite this paper. The species already available are Mg2SiO4, MgSiO3 (amorph sol-gel and amorph glass), SiO2 (alpha and amorph), SiO, the Titan tholins and water ice. They can be found at: [10.5281/zenodo.17456673](https://doi.org/10.5281/zenodo.17456673) .
33
+
34
+ For any inquiries, please contact: mael.voyer@u-paris.fr
35
+
36
+ ---
37
+
38
+ ## 🔧 Features
39
+
40
+ - ✅ Compatible with `transit` and `emmsion` models.
41
+ - ✅ Works with any aerosol specie given that the user provides a `.h5` file with :
42
+
43
+ - A `radius_grid` dataset with the particule sizes in microns ( length `a` ).
44
+ - A `wavenumber_grid` dataset with the wavenumber at which the `Q_ext` were computed in cm-1 ( length `b` )
45
+ - A `Qext_grid` dataset with the computed `Q_ext` from PyMieScat ( length (`a`, `b`) )
46
+
47
+ ## As an extension of TauREx-PyMieScatt, this pulgin includes the same capabilities
48
+
49
+ - ✅ **Supports multiple particle size distributions:**
50
+ - `normal` (log-normal)
51
+ - `budaj` (2015)
52
+ - `deirmendjian` (1964)
53
+ - ✅ **Multiple species and per-species fitting**
54
+ - ✅ **Particle decay with altitude** (`exp_decay` based on Whitten 2008 / Atreya 2005)
55
+ - ✅ **Computes exctinction** using the species optical constant via **Effective Medium Theory (Bohren & Huffman 1983)**
56
+ - ✅ **Multiple fittable parameters** for TauREx retrievals
57
+
58
+ ---
59
+
60
+ ## 🔧 Model Parameters
61
+
62
+ | Name | Description |
63
+ |------|-------------|
64
+ | `species` | Your name for the species included through the `mie_species_path` parameter. This name will be used as suffixes added to the other parameters to distinguish between included species. |
65
+ | `mie_species_path` | Paths to the `Q_ext` grids of the aerosols you want to include |
66
+ | `mie_particle_radius_distribution` | `"normal"`, `"budaj"`, or `"deirmendjian"` |
67
+ | `mie_particle_mean_radius` | Mean particle radius (µm) |
68
+ | `mie_particle_logstd_radius` | Log-normal std dev (for `"normal"` distribution) |
69
+ | `mie_particle_paramA/B/C/D` | Parameters for Deirmendjian distribution |
70
+ | `mie_particle_mix_ratio` | Number density (molecules/m³) |
71
+ | `mie_midP` | Pressure at cloud center (Pa) |
72
+ | `mie_rangeP` | Extend of the clouds in log scale around `mie_midP`. If `mie_midP` = 1e5 Pa and `mie_rangeP` = 1 then clouds extend from 1e6 to 1e4 Pa |
73
+ | `mie_particle_altitude_distrib` | Currently supports `'exp_decay'` or `'linear'` |
74
+ | `mie_particle_altitude_decay` | Decay exponent per species e.g `-5` |
75
+
76
+ ---
77
+
78
+ ## 💡 Usage Example in a TauREx parameter file or 'parfile'
79
+
80
+ ```python
81
+ [Model]
82
+ model_type = transit
83
+
84
+ [[PyMieScattGridExtinction]]
85
+
86
+ species = SiO, Mg2SiO4_glass, custom_molecule
87
+ mie_species_path = path_to_SiO.h5 , path_to_Mg2SiO4_glass.h5 , path_to_custom_molecule.h5 #e.g. You can use the optical constant from Kitzmann and Heng 2018
88
+ mie_particle_radius_distribution = budaj
89
+ mie_particle_mean_radius = 0.1, 0.4 , 10
90
+ mie_midP = 1e5, 1e2, 1
91
+ mie_rangeP = 3 , 1 , 2
92
+ mie_particle_mix_ratio = 1e5, 1e8 , 10e3
93
+ mie_particle_radius_Nsampling = 5
94
+ mie_particle_altitude_distrib = linear
95
+ ```
96
+ ---
97
+ ## Limitations
98
+
99
+ As the `Q_ext` are computed for a range of radii between 1 nm and 30 microns, it is strongly recomended that the `mie_particle_mean_radius` prior does not extend beyond this range. Any radius outside of the range will use the `Q_ext` of the closest radius present in the grid.
@@ -0,0 +1,72 @@
1
+ # Faster clouds for TauREx 3
2
+ ---
3
+
4
+ This plugin provides **a new nethod to inclue aerosols** in [TauREx 3](https://github.com/ucl-exoplanets/TauREx3_public), extending the TauREx-PyMieScatt plugin. It significantly speeds up the cloud models by using precomputed extinction efficiency (`Q_ext`) grids. TauREx-PCQ also considerably improves the computation scaling with the number of clouds in the models. The speed-ups for single cloud retrievals are betwwen 1.4 and 2.7. Although, a single-cloud retrieval using Qext grids achieved a speed-up of 1.4, the same retrieval with four clouds became 17 times faster than the corresponding retrieval using direct Mie calculations.
5
+ The grids details and validation can be found in Voyer & Changeat (2026), if you use TauREx-PCQ or the grids please cite this paper. The species already available are Mg2SiO4, MgSiO3 (amorph sol-gel and amorph glass), SiO2 (alpha and amorph), SiO, the Titan tholins and water ice. They can be found at: [10.5281/zenodo.17456673](https://doi.org/10.5281/zenodo.17456673) .
6
+
7
+ For any inquiries, please contact: mael.voyer@u-paris.fr
8
+
9
+ ---
10
+
11
+ ## 🔧 Features
12
+
13
+ - ✅ Compatible with `transit` and `emmsion` models.
14
+ - ✅ Works with any aerosol specie given that the user provides a `.h5` file with :
15
+
16
+ - A `radius_grid` dataset with the particule sizes in microns ( length `a` ).
17
+ - A `wavenumber_grid` dataset with the wavenumber at which the `Q_ext` were computed in cm-1 ( length `b` )
18
+ - A `Qext_grid` dataset with the computed `Q_ext` from PyMieScat ( length (`a`, `b`) )
19
+
20
+ ## As an extension of TauREx-PyMieScatt, this pulgin includes the same capabilities
21
+
22
+ - ✅ **Supports multiple particle size distributions:**
23
+ - `normal` (log-normal)
24
+ - `budaj` (2015)
25
+ - `deirmendjian` (1964)
26
+ - ✅ **Multiple species and per-species fitting**
27
+ - ✅ **Particle decay with altitude** (`exp_decay` based on Whitten 2008 / Atreya 2005)
28
+ - ✅ **Computes exctinction** using the species optical constant via **Effective Medium Theory (Bohren & Huffman 1983)**
29
+ - ✅ **Multiple fittable parameters** for TauREx retrievals
30
+
31
+ ---
32
+
33
+ ## 🔧 Model Parameters
34
+
35
+ | Name | Description |
36
+ |------|-------------|
37
+ | `species` | Your name for the species included through the `mie_species_path` parameter. This name will be used as suffixes added to the other parameters to distinguish between included species. |
38
+ | `mie_species_path` | Paths to the `Q_ext` grids of the aerosols you want to include |
39
+ | `mie_particle_radius_distribution` | `"normal"`, `"budaj"`, or `"deirmendjian"` |
40
+ | `mie_particle_mean_radius` | Mean particle radius (µm) |
41
+ | `mie_particle_logstd_radius` | Log-normal std dev (for `"normal"` distribution) |
42
+ | `mie_particle_paramA/B/C/D` | Parameters for Deirmendjian distribution |
43
+ | `mie_particle_mix_ratio` | Number density (molecules/m³) |
44
+ | `mie_midP` | Pressure at cloud center (Pa) |
45
+ | `mie_rangeP` | Extend of the clouds in log scale around `mie_midP`. If `mie_midP` = 1e5 Pa and `mie_rangeP` = 1 then clouds extend from 1e6 to 1e4 Pa |
46
+ | `mie_particle_altitude_distrib` | Currently supports `'exp_decay'` or `'linear'` |
47
+ | `mie_particle_altitude_decay` | Decay exponent per species e.g `-5` |
48
+
49
+ ---
50
+
51
+ ## 💡 Usage Example in a TauREx parameter file or 'parfile'
52
+
53
+ ```python
54
+ [Model]
55
+ model_type = transit
56
+
57
+ [[PyMieScattGridExtinction]]
58
+
59
+ species = SiO, Mg2SiO4_glass, custom_molecule
60
+ mie_species_path = path_to_SiO.h5 , path_to_Mg2SiO4_glass.h5 , path_to_custom_molecule.h5 #e.g. You can use the optical constant from Kitzmann and Heng 2018
61
+ mie_particle_radius_distribution = budaj
62
+ mie_particle_mean_radius = 0.1, 0.4 , 10
63
+ mie_midP = 1e5, 1e2, 1
64
+ mie_rangeP = 3 , 1 , 2
65
+ mie_particle_mix_ratio = 1e5, 1e8 , 10e3
66
+ mie_particle_radius_Nsampling = 5
67
+ mie_particle_altitude_distrib = linear
68
+ ```
69
+ ---
70
+ ## Limitations
71
+
72
+ As the `Q_ext` are computed for a range of radii between 1 nm and 30 microns, it is strongly recomended that the `mie_particle_mean_radius` prior does not extend beyond this range. Any radius outside of the range will use the `Q_ext` of the closest radius present in the grid.
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "taurex-pcq"
7
+ version = "1.0"
8
+ description = "A TauREx plugin implementing Pre Computed Qext grids for cloud models"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "Maël Voyer", email = "mael.voyer@cea.fr" }
14
+ ]
15
+ keywords = ["taurex", "exoplanets", "retrieval", "atmospheres", "jwst"]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Intended Audience :: Science/Research",
22
+ "Topic :: Scientific/Engineering :: Astronomy",
23
+ ]
24
+
25
+ dependencies = [
26
+ "numpy",
27
+ "taurex",
28
+ "numba",
29
+ "scipy",
30
+ "h5py"
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ dev = [
35
+ "build",
36
+ "twine",
37
+ "pytest",
38
+ ]
39
+
40
+ [project.entry-points."taurex.plugins"]
41
+ taurex-pcq = "taurex_pcq"
42
+
43
+ [tool.setuptools]
44
+ package-dir = {"" = "src"}
45
+
46
+ [tool.setuptools.packages.find]
47
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from .contributions import PyMieScattGridExtinctionContribution
2
+
3
+ __all__ = ["PyMieScattGridExtinctionContribution"]
@@ -0,0 +1 @@
1
+ from .pymiescatt_grid import PyMieScattGridExtinctionContribution
@@ -0,0 +1,393 @@
1
+ from taurex.contributions.contribution import Contribution
2
+ import numpy as np
3
+ from taurex.data.fittable import fitparam
4
+ import numba
5
+ import scipy.stats as stats
6
+ import h5py
7
+ from taurex.exceptions import InvalidModelException
8
+ from taurex.util.util import create_grid_res
9
+
10
+
11
+ @numba.jit(nopython=True, nogil=True)
12
+ def contribute_mie_tau(startK, endK, sigma, path, ngrid, layer, tau):
13
+ for k in range(startK, endK):
14
+ _path = path[k]
15
+ for wn in range(ngrid):
16
+ tau[layer, wn] += sigma[k+layer, wn]*_path
17
+
18
+
19
+ class InvalidPyMieScattGridException(InvalidModelException):
20
+ """
21
+ Exception that is called when the contributio fails
22
+ """
23
+ pass
24
+
25
+ class PyMieScattGridExtinctionContribution(Contribution):
26
+ """
27
+ Computes Mie scattering contribution to optical depth
28
+ using the Bohren and Huffmann in its PyMieScatt implementation.
29
+
30
+ Parameters
31
+ ----------
32
+ mie_particle_mean_radius: Mean radius of the particles in um
33
+ mie_particle_mix_ratio: Number density in molecules/m^3 --> Divide this number by 1,000,000 to get this in more common molecules/cm^3
34
+ mie_midP: Middle of the clouds in Pa
35
+ mie_rangeP: Extend of the clouds in log scale. If mie_midP = 1e5 Pa and mie_rangeP = 1 then clouds extend from 1e6 to 1e4 Pa
36
+ """
37
+
38
+ def __init__(self, mie_particle_mean_radius=[0.01,],
39
+ mie_particle_logstd_radius = [0.001], ## Serves for the normaly distributed particle size distribution.
40
+ mie_particle_paramA = [1., ], mie_particle_paramB = [6.,], mie_particle_paramC = [6.,], mie_particle_paramD = [1.,], ## Serves for Deirmendjian particle size distribution.
41
+ mie_particle_radius_Nsampling = 5, mie_particle_radius_Dsampling = 2, ## Is used for sampling the particle distribution.
42
+ mie_particle_radius_distribution = 'normal', ## choices are 'normal', 'budaj', 'deirmendjian'.
43
+ mie_species_path=None, species = ['Mg2SiO4'],
44
+ mie_particle_mix_ratio=[1e-10],
45
+ mie_porosity = None,
46
+ mie_midP = [1e3],
47
+ mie_rangeP = [1],
48
+ mie_nMedium=1,
49
+ mie_resolution = 100,
50
+ mie_particle_altitude_distrib = 'exp_decay',
51
+ mie_particle_altitude_decay = [-5], ## Controls the decay rate, inspired by Whitten et al. 2008 / Attreya et al. 2005
52
+ name = 'PyMieScattGridExtinction'):
53
+ super().__init__(name)
54
+
55
+ self._mie_particle_mean_radius = mie_particle_mean_radius
56
+ self._mie_particle_std_radius = mie_particle_logstd_radius
57
+ self._mie_particle_paramA = mie_particle_paramA
58
+ self._mie_particle_paramB = mie_particle_paramB
59
+ self._mie_particle_paramC = mie_particle_paramC
60
+ self._mie_particle_paramD = mie_particle_paramD
61
+
62
+ if mie_particle_radius_distribution == 'deirmendjian':
63
+ self._mie_particle_mean_radius = None
64
+ self.warning('The Rmean parameter is being disabled because not needed for Deirmendjian 1964 particle distribution')
65
+ else:
66
+ self._mie_particle_paramA = None
67
+ self.warning('The mie_particle_paramA parameter is being disabled because only used in Deirmendjian 1964 particle distribution')
68
+
69
+ if mie_particle_radius_distribution == 'budaj':
70
+ self._mie_particle_std_radius = None
71
+ self.warning('The Rlogstd parameter is being disabled because not needed for Bujaj 2015 particle distribution')
72
+
73
+ self._mie_particle_radius_distribution = mie_particle_radius_distribution
74
+
75
+ self._mie_particle_mix_ratio = mie_particle_mix_ratio
76
+ self._mie_porosity = mie_porosity
77
+
78
+ self._mie_midP = mie_midP
79
+ self._mie_rangeP = mie_rangeP
80
+
81
+ self._Nsampling = int(mie_particle_radius_Nsampling)
82
+ self._Dsampling = mie_particle_radius_Dsampling
83
+
84
+ self._mie_species_path = mie_species_path
85
+ self._species = species
86
+
87
+ self._particle_alt_distib = mie_particle_altitude_distrib
88
+ self._particle_alt_decay = mie_particle_altitude_decay
89
+ self._mie_nMedium = mie_nMedium
90
+
91
+ self._resolution = mie_resolution
92
+
93
+ self._radius_grid, self._Qext, self._Qext_wn = self.load_input_files(self._mie_species_path)
94
+
95
+ self.generate_particle_fitting_params()
96
+
97
+ def load_input_files(self, paths : list[str] ):
98
+ radius_grids, Qexts, wavenumber_grids = [], [], []
99
+
100
+ for path in paths:
101
+ grid_file = h5py.File(path)
102
+ radius_grids.append( grid_file["radius_grid"][()] )
103
+ Qexts.append( grid_file["Qext"][()] )
104
+ wavenumber_grids.append( grid_file["wavenumber_grid"][()] )
105
+ grid_file.close()
106
+
107
+ return radius_grids, Qexts, wavenumber_grids
108
+
109
+ def contribute(self, model, start_layer, end_layer,
110
+ density_offset, layer, density, tau, path_length=None):
111
+
112
+ contribute_mie_tau(start_layer, end_layer, self.sigma_xsec, path_length, self._ngrid, layer, tau)
113
+
114
+ def generate_particle_fitting_params(self):
115
+
116
+ bounds_Rm = [0.01, 10]
117
+ bounds_Rstd = [0.01, 0.2]
118
+ bounds_X = [1e0, 1e12]
119
+ bounds_midP = [1e6, 1e0]
120
+ bounds_rangeP = [0.0, 3]
121
+ bounds_decayP = [-7, 0]
122
+ bounds_poro = [0,1]
123
+
124
+ ### CREATE JOINED FITPARAMS
125
+ if self._mie_particle_mean_radius is not None:
126
+ param_name = 'Rmean_share'
127
+ param_latex = '$Rmean_share$'
128
+ def read_RmeanShare(self):
129
+ return np.mean(self._mie_particle_mean_radius)
130
+ def write_RmeanShare(self, value):
131
+ self._mie_particle_mean_radius[:] = [value]*len(self._mie_particle_mean_radius)
132
+ default_fit = False
133
+ self.add_fittable_param(param_name, param_latex, read_RmeanShare,
134
+ write_RmeanShare, 'log', default_fit, bounds_Rm)
135
+ if self._mie_particle_std_radius is not None:
136
+ param_name = 'Rlogstd_share'
137
+ param_latex = '$Rlogstd_share$'
138
+ def read_RstdShare(self):
139
+ return np.mean(self._mie_particle_std_radius)
140
+ def write_RstdShare(self, value):
141
+ self._mie_particle_std_radius[:] = [value]*len(self._mie_particle_std_radius)
142
+ default_fit = False
143
+ self.add_fittable_param(param_name, param_latex, read_RstdShare,
144
+ write_RstdShare, 'linear', default_fit, bounds_Rstd)
145
+
146
+ param_name = 'X_share'
147
+ param_latex = '$X_share$'
148
+ def read_XShare(self):
149
+ return np.mean(self._mie_particle_mix_ratio)
150
+ def write_XShare(self, value):
151
+ self._mie_particle_mix_ratio = [value]*len(self._mie_particle_mix_ratio)
152
+ default_fit = False
153
+ self.add_fittable_param(param_name, param_latex, read_XShare,
154
+ write_XShare, 'log', default_fit, bounds_X)
155
+
156
+ param_name = 'midP_share'
157
+ param_latex = '$midP_share$'
158
+ def read_midPShare(self):
159
+ return np.mean(self._mie_midP)
160
+ def write_midPShare(self, value):
161
+ self._mie_midP[:] = [value]*len(self._mie_midP)
162
+ default_fit = False
163
+ self.add_fittable_param(param_name, param_latex, read_midPShare,
164
+ write_midPShare, 'log', default_fit, bounds_midP)
165
+
166
+ param_name = 'rangeP_share'
167
+ param_latex = '$rangeP_share$'
168
+ def read_rangePShare(self):
169
+ return np.mean(self._mie_rangeP)
170
+ def write_rangePShare(self, value):
171
+ self._mie_rangeP[:] = [value]*len(self._mie_rangeP)
172
+ default_fit = False
173
+ self.add_fittable_param(param_name, param_latex, read_rangePShare,
174
+ write_rangePShare, 'linear', default_fit, bounds_rangeP)
175
+
176
+ param_name = 'decayP_share'
177
+ param_latex = '$decayP_share$'
178
+ def read_decayPShare(self):
179
+ return np.mean(self._particle_alt_decay)
180
+ def write_decayPShare(self, value):
181
+ self._particle_alt_decay[:] = [value]*len(self._particle_alt_decay)
182
+ default_fit = False
183
+ self.add_fittable_param(param_name, param_latex, read_decayPShare,
184
+ write_decayPShare, 'linear', default_fit, bounds_decayP)
185
+
186
+ ### CREATE INDIVIDUAL SPECIES FITPARAMS
187
+ for idx, val in enumerate(self._species):
188
+
189
+ if self._mie_particle_mean_radius is not None:
190
+ param_name = 'Rmean_{}'.format(val)
191
+ param_latex = '$Rmean_{}$'.format(val)
192
+ def read_Rmean(self, idx=idx):
193
+ return self._mie_particle_mean_radius[idx]
194
+ def write_Rmean(self, value, idx=idx):
195
+ self._mie_particle_mean_radius[idx] = value
196
+ default_fit = False
197
+ self.add_fittable_param(param_name, param_latex, read_Rmean,
198
+ write_Rmean, 'log', default_fit, bounds_Rm)
199
+
200
+ if self._mie_particle_std_radius is not None:
201
+ param_name = 'Rlogstd_{}'.format(val)
202
+ param_latex = '$Rlogstd_{}$'.format(val)
203
+ def read_Rstd(self, idx=idx):
204
+ return self._mie_particle_std_radius[idx]
205
+ def write_Rstd(self, value, idx=idx):
206
+ self._mie_particle_std_radius[idx] = value
207
+ default_fit = False
208
+ self.add_fittable_param(param_name, param_latex, read_Rstd,
209
+ write_Rstd, 'linear', default_fit, bounds_Rstd)
210
+
211
+ if self._mie_porosity is not None:
212
+ param_name = 'Porosity_{}'.format(val)
213
+ param_latex = '$Porosity_{}$'.format(val)
214
+ def read_Poro(self, idx=idx):
215
+ return self._mie_porosity[idx]
216
+ def write_Poro(self, value, idx=idx):
217
+ self._mie_porosity[idx] = value
218
+ default_fit = False
219
+ self.add_fittable_param(param_name, param_latex, read_Poro,
220
+ write_Poro, 'linear', default_fit, bounds_poro)
221
+
222
+ param_name = 'X_{}'.format(val)
223
+ param_latex = '$X_{}$'.format(val)
224
+ def read_X(self, idx=idx):
225
+ return self._mie_particle_mix_ratio[idx]
226
+ def write_X(self, value, idx=idx):
227
+ self._mie_particle_mix_ratio[idx] = value
228
+ default_fit = False
229
+ self.add_fittable_param(param_name, param_latex, read_X,
230
+ write_X, 'log', default_fit, bounds_X)
231
+
232
+ param_name = 'midP_{}'.format(val)
233
+ param_latex = '$midP_{}$'.format(val)
234
+ def read_midP(self, idx=idx):
235
+ return self._mie_midP[idx]
236
+ def write_midP(self, value, idx=idx):
237
+ self._mie_midP[idx] = value
238
+ default_fit = False
239
+ self.add_fittable_param(param_name, param_latex, read_midP,
240
+ write_midP, 'log', default_fit, bounds_midP)
241
+
242
+ param_name = 'rangeP_{}'.format(val)
243
+ param_latex = '$rangeP_{}$'.format(val)
244
+ def read_rangeP(self, idx=idx):
245
+ return self._mie_rangeP[idx]
246
+ def write_rangeP(self, value, idx=idx):
247
+ self._mie_rangeP[idx] = value
248
+ default_fit = False
249
+ self.add_fittable_param(param_name, param_latex, read_rangeP,
250
+ write_rangeP, 'linear', default_fit, bounds_rangeP)
251
+
252
+ param_name = 'decayP_{}'.format(val)
253
+ param_latex = '$decayP_{}$'.format(val)
254
+ def read_decayP(self, idx=idx):
255
+ return self._particle_alt_decay[idx]
256
+ def write_decayP(self, value, idx=idx):
257
+ self._particle_alt_decay[idx] = value
258
+ default_fit = False
259
+ self.add_fittable_param(param_name, param_latex, read_decayP,
260
+ write_decayP, 'linear', default_fit, bounds_decayP)
261
+
262
+
263
+ def prepare_each(self, model, wngrid):
264
+
265
+ self._nlayers = model.nLayers
266
+ self._ngrid = wngrid.shape[0]
267
+
268
+ pressure_profile = model.pressureProfile
269
+
270
+ sigma_xsec = np.zeros(shape=(self._nlayers, wngrid.shape[0]))
271
+
272
+
273
+ for specie_idx, s in enumerate(self._species):
274
+ wn = self._Qext_wn[specie_idx]
275
+ Rmean = self._mie_particle_mean_radius[specie_idx]
276
+
277
+ ## GET A LOG DISTRIBUTION OF THE PARTICLE RADIUS
278
+ if self._mie_particle_radius_distribution == 'budaj': ## This distribution can be found in Budaj et al. 2015
279
+ LogRsigma = 0.2 ## since the distribution is fixed in width, this can be set to approx 0.2 for the sampling
280
+ radii_log = np.linspace(10**(np.log10(Rmean)+self._Dsampling*LogRsigma), 10**(np.log10(Rmean)-self._Dsampling*LogRsigma), self._Nsampling)
281
+ weights = ((radii_log/Rmean)**6)*np.exp(-6*radii_log/Rmean)
282
+ elif self._mie_particle_radius_distribution == 'deirmendjian': ## This distribution can be found in Deirmendjian 1964 (modified Gamma distribution)
283
+ LogRsigma = self._mie_particle_std_radius[specie_idx]
284
+ radii_log = np.linspace(10**(np.log10(Rmean)+self._Dsampling*LogRsigma), 10**(np.log10(Rmean)-self._Dsampling*LogRsigma), self._Nsampling)
285
+ weights = self._mie_particle_paramA[specie_idx]*(radii_log**self._mie_particle_paramB[specie_idx])*np.exp(-self._mie_particle_paramC[specie_idx]*(radii_log**self._mie_particle_paramD[specie_idx]))
286
+ else: ## This is simply a normal distribution.
287
+ LogRsigma = self._mie_particle_std_radius[specie_idx]
288
+ radii_log = np.linspace(10**(np.log10(Rmean)+self._Dsampling*LogRsigma), 10**(np.log10(Rmean)-self._Dsampling*LogRsigma), self._Nsampling)
289
+ weights = stats.norm.pdf(np.log10(radii_log), np.log10(Rmean), LogRsigma)
290
+ Qexts = []
291
+
292
+ for radius in radii_log:
293
+ idx = np.searchsorted(self._radius_grid[specie_idx], radius) - 1
294
+
295
+ R1 = self._radius_grid[specie_idx][int(idx)]
296
+ R2 = self._radius_grid[specie_idx][int(idx)+1]
297
+ delta_R = R1 - R2
298
+
299
+ Q1_arr = self._Qext[specie_idx][int(idx)] # shape: (n_wavelengths,)
300
+ Q2_arr = self._Qext[specie_idx][int(idx)+1]
301
+
302
+ a = (Q1_arr - Q2_arr) / delta_R
303
+ b = Q1_arr - a * R1
304
+
305
+ # Interpolate at current log radius
306
+ Qexts.append(a * radius + b)
307
+
308
+ Qexts = np.array(Qexts) * np.power(radii_log[:, np.newaxis] * 1e3, 2) # As Qext was coomputed with radii in mm the radii here also needs to be in mm not um.
309
+ Qext_mean = np.average(Qexts, axis=0, weights=weights)
310
+ Qext_int = np.interp(wngrid, wn[::-1], Qext_mean[::-1], left=0, right=0)
311
+ sigma_mie = np.zeros((len(wngrid)))
312
+
313
+ sigma_mie[Qext_int!=0] = Qext_int[Qext_int!=0]* np.pi * 1e-18
314
+ ## So here sigma_mie is in m2 (nm2 to m2 conversion is 1e-18)
315
+
316
+ if self._mie_midP[specie_idx] == -1:
317
+ bottom_pressure = pressure_profile[0]
318
+ top_pressure = pressure_profile[-1]
319
+ else:
320
+ bottom_pressure = 10**(np.log10(self._mie_midP[specie_idx]) + self._mie_rangeP[specie_idx]/2)
321
+ top_pressure = 10**(np.log10(self._mie_midP[specie_idx]) - self._mie_rangeP[specie_idx]/2)
322
+
323
+ cloud_filter = (pressure_profile <= bottom_pressure) & \
324
+ (pressure_profile >= top_pressure)
325
+
326
+ sigma_xsec_int = np.zeros(shape=(self._nlayers, wngrid.shape[0]))
327
+
328
+ ## This line implied that self._mie_particle_mix_ratio is expressed in m-3
329
+ if self._particle_alt_distib == 'exp_decay':
330
+ ## if we want it with exp decay style Whitten et al. 2008 / Attreya et al. 2005
331
+ decay = self._particle_alt_decay[specie_idx]
332
+ #mix = self._mie_particle_mix_ratio[idx]*(1-np.exp(decay*(pressure_profile-top_pressure)/(bottom_pressure-top_pressure)))
333
+ mix = self._mie_particle_mix_ratio[specie_idx]*(press/bottom_pressure)**(- decay)
334
+ sigma_xsec_int[cloud_filter, :] = sigma_mie[None] * mix[cloud_filter, None]
335
+ else:
336
+ sigma_xsec_int[cloud_filter, ...] = sigma_mie * self._mie_particle_mix_ratio[specie_idx]
337
+
338
+ sigma_xsec += sigma_xsec_int
339
+
340
+ self.sigma_xsec = sigma_xsec
341
+
342
+ self.debug('final xsec %s', self.sigma_xsec)
343
+
344
+ yield 'PyMieScattGridExt', sigma_xsec
345
+
346
+ def write(self, output):
347
+ contrib = super().write(output)
348
+
349
+ if self._mie_particle_mean_radius is not None:
350
+ contrib.write_array('particle_mean_radius', np.array(self._mie_particle_mean_radius))
351
+ if self._mie_particle_std_radius is not None:
352
+ contrib.write_array('particle_std_radius', np.array(self._mie_particle_std_radius))
353
+ contrib.write_array('particle_mix_ratio', np.array(self._mie_particle_mix_ratio))
354
+ contrib.write_array('particle_midP', np.array(self._mie_midP))
355
+ contrib.write_array('particle_rangeP', np.array(self._mie_rangeP))
356
+ contrib.write_string_array('cloud_species', self._species)
357
+ contrib.write_scalar('radius_Nsampling', self._Nsampling)
358
+ contrib.write_scalar('radius_Dsampling', self._Dsampling)
359
+ contrib.write_scalar('mie_nMedium', self._mie_nMedium)
360
+ return contrib
361
+
362
+ @classmethod
363
+ def input_keywords(self):
364
+ return ['PyMieScattGridExtinction', ]
365
+
366
+ BIBTEX_ENTRIES = [
367
+ """
368
+ @BOOK{1983asls.book.....B,
369
+ author = {{Bohren}, Craig F. and {Huffman}, Donald R.},
370
+ title = "{Absorption and scattering of light by small particles}",
371
+ year = 1983,
372
+ adsurl = {https://ui.adsabs.harvard.edu/abs/1983asls.book.....B},
373
+ adsnote = {Provided by the SAO/NASA Astrophysics Data System}
374
+ }
375
+ @ARTICLE{2026A&A...707A.127V,
376
+ author = {{Voyer}, M. and {Changeat}, Q.},
377
+ title = "{Precomputed aerosol extinction, scattering, and asymmetry grids for scalable atmospheric retrievals}",
378
+ journal = {Astronomy and Astrophysics},
379
+ keywords = {radiative transfer, methods: numerical, planets and satellites: atmospheres, planets and satellites: gaseous planets, Earth and Planetary Astrophysics, Instrumentation and Methods for Astrophysics},
380
+ year = 2026,
381
+ month = mar,
382
+ volume = {707},
383
+ eid = {A127},
384
+ pages = {A127},
385
+ doi = {10.1051/0004-6361/202558469},
386
+ archivePrefix = {arXiv},
387
+ eprint = {2601.14177},
388
+ primaryClass = {astro-ph.EP},
389
+ adsurl = {https://ui.adsabs.harvard.edu/abs/2026A&A...707A.127V},
390
+ adsnote = {Provided by the SAO/NASA Astrophysics Data System}
391
+ }
392
+ """,
393
+ ]
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.4
2
+ Name: taurex-pcq
3
+ Version: 1.0
4
+ Summary: A TauREx plugin implementing Pre Computed Qext grids for cloud models
5
+ Author-email: Maël Voyer <mael.voyer@cea.fr>
6
+ License: MIT
7
+ Keywords: taurex,exoplanets,retrieval,atmospheres,jwst
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Topic :: Scientific/Engineering :: Astronomy
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: numpy
18
+ Requires-Dist: taurex
19
+ Requires-Dist: numba
20
+ Requires-Dist: scipy
21
+ Requires-Dist: h5py
22
+ Provides-Extra: dev
23
+ Requires-Dist: build; extra == "dev"
24
+ Requires-Dist: twine; extra == "dev"
25
+ Requires-Dist: pytest; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ # Faster clouds for TauREx 3
29
+ ---
30
+
31
+ This plugin provides **a new nethod to inclue aerosols** in [TauREx 3](https://github.com/ucl-exoplanets/TauREx3_public), extending the TauREx-PyMieScatt plugin. It significantly speeds up the cloud models by using precomputed extinction efficiency (`Q_ext`) grids. TauREx-PCQ also considerably improves the computation scaling with the number of clouds in the models. The speed-ups for single cloud retrievals are betwwen 1.4 and 2.7. Although, a single-cloud retrieval using Qext grids achieved a speed-up of 1.4, the same retrieval with four clouds became 17 times faster than the corresponding retrieval using direct Mie calculations.
32
+ The grids details and validation can be found in Voyer & Changeat (2026), if you use TauREx-PCQ or the grids please cite this paper. The species already available are Mg2SiO4, MgSiO3 (amorph sol-gel and amorph glass), SiO2 (alpha and amorph), SiO, the Titan tholins and water ice. They can be found at: [10.5281/zenodo.17456673](https://doi.org/10.5281/zenodo.17456673) .
33
+
34
+ For any inquiries, please contact: mael.voyer@u-paris.fr
35
+
36
+ ---
37
+
38
+ ## 🔧 Features
39
+
40
+ - ✅ Compatible with `transit` and `emmsion` models.
41
+ - ✅ Works with any aerosol specie given that the user provides a `.h5` file with :
42
+
43
+ - A `radius_grid` dataset with the particule sizes in microns ( length `a` ).
44
+ - A `wavenumber_grid` dataset with the wavenumber at which the `Q_ext` were computed in cm-1 ( length `b` )
45
+ - A `Qext_grid` dataset with the computed `Q_ext` from PyMieScat ( length (`a`, `b`) )
46
+
47
+ ## As an extension of TauREx-PyMieScatt, this pulgin includes the same capabilities
48
+
49
+ - ✅ **Supports multiple particle size distributions:**
50
+ - `normal` (log-normal)
51
+ - `budaj` (2015)
52
+ - `deirmendjian` (1964)
53
+ - ✅ **Multiple species and per-species fitting**
54
+ - ✅ **Particle decay with altitude** (`exp_decay` based on Whitten 2008 / Atreya 2005)
55
+ - ✅ **Computes exctinction** using the species optical constant via **Effective Medium Theory (Bohren & Huffman 1983)**
56
+ - ✅ **Multiple fittable parameters** for TauREx retrievals
57
+
58
+ ---
59
+
60
+ ## 🔧 Model Parameters
61
+
62
+ | Name | Description |
63
+ |------|-------------|
64
+ | `species` | Your name for the species included through the `mie_species_path` parameter. This name will be used as suffixes added to the other parameters to distinguish between included species. |
65
+ | `mie_species_path` | Paths to the `Q_ext` grids of the aerosols you want to include |
66
+ | `mie_particle_radius_distribution` | `"normal"`, `"budaj"`, or `"deirmendjian"` |
67
+ | `mie_particle_mean_radius` | Mean particle radius (µm) |
68
+ | `mie_particle_logstd_radius` | Log-normal std dev (for `"normal"` distribution) |
69
+ | `mie_particle_paramA/B/C/D` | Parameters for Deirmendjian distribution |
70
+ | `mie_particle_mix_ratio` | Number density (molecules/m³) |
71
+ | `mie_midP` | Pressure at cloud center (Pa) |
72
+ | `mie_rangeP` | Extend of the clouds in log scale around `mie_midP`. If `mie_midP` = 1e5 Pa and `mie_rangeP` = 1 then clouds extend from 1e6 to 1e4 Pa |
73
+ | `mie_particle_altitude_distrib` | Currently supports `'exp_decay'` or `'linear'` |
74
+ | `mie_particle_altitude_decay` | Decay exponent per species e.g `-5` |
75
+
76
+ ---
77
+
78
+ ## 💡 Usage Example in a TauREx parameter file or 'parfile'
79
+
80
+ ```python
81
+ [Model]
82
+ model_type = transit
83
+
84
+ [[PyMieScattGridExtinction]]
85
+
86
+ species = SiO, Mg2SiO4_glass, custom_molecule
87
+ mie_species_path = path_to_SiO.h5 , path_to_Mg2SiO4_glass.h5 , path_to_custom_molecule.h5 #e.g. You can use the optical constant from Kitzmann and Heng 2018
88
+ mie_particle_radius_distribution = budaj
89
+ mie_particle_mean_radius = 0.1, 0.4 , 10
90
+ mie_midP = 1e5, 1e2, 1
91
+ mie_rangeP = 3 , 1 , 2
92
+ mie_particle_mix_ratio = 1e5, 1e8 , 10e3
93
+ mie_particle_radius_Nsampling = 5
94
+ mie_particle_altitude_distrib = linear
95
+ ```
96
+ ---
97
+ ## Limitations
98
+
99
+ As the `Q_ext` are computed for a range of radii between 1 nm and 30 microns, it is strongly recomended that the `mie_particle_mean_radius` prior does not extend beyond this range. Any radius outside of the range will use the `Q_ext` of the closest radius present in the grid.
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/taurex_pcq/__init__.py
5
+ src/taurex_pcq.egg-info/PKG-INFO
6
+ src/taurex_pcq.egg-info/SOURCES.txt
7
+ src/taurex_pcq.egg-info/dependency_links.txt
8
+ src/taurex_pcq.egg-info/entry_points.txt
9
+ src/taurex_pcq.egg-info/requires.txt
10
+ src/taurex_pcq.egg-info/top_level.txt
11
+ src/taurex_pcq/contributions/__init__.py
12
+ src/taurex_pcq/contributions/pymiescatt_grid.py
@@ -0,0 +1,2 @@
1
+ [taurex.plugins]
2
+ taurex-pcq = taurex_pcq
@@ -0,0 +1,10 @@
1
+ numpy
2
+ taurex
3
+ numba
4
+ scipy
5
+ h5py
6
+
7
+ [dev]
8
+ build
9
+ twine
10
+ pytest
@@ -0,0 +1 @@
1
+ taurex_pcq