PyTransit 2.9.1__py3-none-any.whl → 2.9.2__py3-none-any.whl
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.
- pytransit/__init__.py +1 -1
- pytransit/contamination/contamination.py +13 -4
- pytransit/contamination/filter.py +48 -7
- pytransit/contamination/instrument.py +26 -0
- pytransit/contamination/plotting.py +3 -3
- pytransit/lpf/baselines/legendrebaseline.py +2 -2
- pytransit/lpf/logposteriorfunction.py +167 -17
- pytransit/lpf/lpf.py +17 -3
- pytransit/lpf/ocltdvlpf.py +3 -3
- pytransit/lpf/oclttvlpf.py +1 -1
- pytransit/lpf/tdvlpf.py +3 -3
- pytransit/lpf/transitanalysis.py +2 -2
- pytransit/models/_deprecation.py +62 -0
- pytransit/models/eclipse_model.py +29 -0
- pytransit/models/gdmodel.py +62 -0
- pytransit/models/general.py +48 -3
- pytransit/models/limb_darkening/exponential.py +16 -0
- pytransit/models/limb_darkening/linear.py +20 -0
- pytransit/models/limb_darkening/logarithmic.py +19 -0
- pytransit/models/limb_darkening/nonlinear.py +23 -0
- pytransit/models/limb_darkening/power_2.py +50 -0
- pytransit/models/limb_darkening/quadratic.py +24 -0
- pytransit/models/limb_darkening/quadratic_tri.py +25 -0
- pytransit/models/limb_darkening/square_root.py +18 -0
- pytransit/models/limb_darkening/uniform.py +18 -0
- pytransit/models/ma_chromosphere.py +32 -0
- pytransit/models/ma_quadratic.py +9 -2
- pytransit/models/ma_quadratic_cl.py +44 -36
- pytransit/models/ma_uniform.py +41 -0
- pytransit/models/ma_uniform_cl.py +14 -35
- pytransit/models/opencltransitmodel.py +115 -0
- pytransit/models/qpower2.py +2 -0
- pytransit/models/qpower2_cl.py +20 -35
- pytransit/models/roadrunner/common.py +417 -19
- pytransit/models/roadrunner/esmodel.py +31 -0
- pytransit/models/roadrunner/model_full.py +36 -28
- pytransit/models/roadrunner/model_simple.py +44 -38
- pytransit/models/roadrunner/model_trspec.py +33 -35
- pytransit/models/roadrunner/opmodel.py +15 -17
- pytransit/models/roadrunner/opmodel_full.py +44 -59
- pytransit/models/roadrunner/rrmodel.cl +353 -130
- pytransit/models/roadrunner/rrmodel.py +116 -56
- pytransit/models/roadrunner/rrmodel_cl.py +360 -152
- pytransit/models/roadrunner/tsmodel.py +44 -9
- pytransit/models/transitmodel.py +71 -5
- pytransit/param/parameter.py +6 -0
- pytransit/stars/btsettl.py +40 -0
- pytransit/stars/husser2013.py +49 -0
- pytransit/utils/de.py +18 -6
- pytransit/utils/io/base.py +51 -5
- pytransit/utils/io/lcdata.py +465 -3
- {pytransit-2.9.1.dist-info → pytransit-2.9.2.dist-info}/METADATA +3 -2
- {pytransit-2.9.1.dist-info → pytransit-2.9.2.dist-info}/RECORD +56 -54
- {pytransit-2.9.1.dist-info → pytransit-2.9.2.dist-info}/WHEEL +0 -0
- {pytransit-2.9.1.dist-info → pytransit-2.9.2.dist-info}/licenses/LICENSE +0 -0
- {pytransit-2.9.1.dist-info → pytransit-2.9.2.dist-info}/top_level.txt +0 -0
pytransit/__init__.py
CHANGED
|
@@ -194,8 +194,9 @@ class BBContamination(_BaseContamination):
|
|
|
194
194
|
self._delta_l = delta_l
|
|
195
195
|
self._wl_grids = []
|
|
196
196
|
for f in self.instrument.filters:
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
wl_min, wl_max = f.bbox
|
|
198
|
+
nwl = int(ceil((wl_max - wl_min) / self._delta_l))
|
|
199
|
+
self._wl_grids.append(linspace(wl_min, wl_max, nwl))
|
|
199
200
|
|
|
200
201
|
@staticmethod
|
|
201
202
|
def absolute_flux(teff: float, wl: Union[float, Iterable]) -> ndarray:
|
|
@@ -294,9 +295,17 @@ class SMContamination(_BaseContamination):
|
|
|
294
295
|
Parameters
|
|
295
296
|
----------
|
|
296
297
|
instrument
|
|
297
|
-
Instrument configuration
|
|
298
|
+
Instrument configuration.
|
|
298
299
|
ref_pb
|
|
299
|
-
|
|
300
|
+
Name of the reference passband, i.e. the passband in which the contamination is
|
|
301
|
+
parametrised.
|
|
302
|
+
data
|
|
303
|
+
Stellar spectrum grid to use, either ``'BT-SETTL'`` (default) or ``'Husser2013'``.
|
|
304
|
+
|
|
305
|
+
Raises
|
|
306
|
+
------
|
|
307
|
+
ValueError
|
|
308
|
+
If `data` is neither ``'BT-SETTL'`` nor ``'Husser2013'``.
|
|
300
309
|
"""
|
|
301
310
|
super().__init__(instrument, ref_pb)
|
|
302
311
|
|
|
@@ -20,6 +20,23 @@ from scipy.interpolate import interp1d
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class Filter:
|
|
23
|
+
"""Base class for the passband transmission profiles.
|
|
24
|
+
|
|
25
|
+
A filter is a callable that maps a wavelength in nanometres to a transmission between 0 and
|
|
26
|
+
1. Subclasses implement `__call__` and `sample`; this class only defines the interface.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
name : str
|
|
31
|
+
Passband name. Used as the passband identifier by :class:`~pytransit.contamination.instrument.Instrument`.
|
|
32
|
+
|
|
33
|
+
Attributes
|
|
34
|
+
----------
|
|
35
|
+
bbox : ndarray
|
|
36
|
+
Wavelength bounding box ``[wl_min, wl_max]`` in nm, outside which the transmission is
|
|
37
|
+
zero.
|
|
38
|
+
"""
|
|
39
|
+
|
|
23
40
|
def __init__(self, name: str):
|
|
24
41
|
self.name: str = name
|
|
25
42
|
self.bbox: ndarray = array([250, 1000], dtype='d')
|
|
@@ -32,6 +49,19 @@ class Filter:
|
|
|
32
49
|
|
|
33
50
|
|
|
34
51
|
class DeltaFilter(Filter):
|
|
52
|
+
"""Monochromatic filter transmitting at a single wavelength.
|
|
53
|
+
|
|
54
|
+
Useful for modeling spectroscopic bins narrow enough that the transmission profile does
|
|
55
|
+
not matter, and as the default passband of the gravity-darkened model.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
name : str
|
|
60
|
+
Passband name.
|
|
61
|
+
wl : float
|
|
62
|
+
Central wavelength in nm.
|
|
63
|
+
"""
|
|
64
|
+
|
|
35
65
|
def __init__(self, name: str, wl: float):
|
|
36
66
|
super().__init__(name)
|
|
37
67
|
self.wl: float = wl
|
|
@@ -60,9 +90,12 @@ class BoxcarFilter(Filter):
|
|
|
60
90
|
"""
|
|
61
91
|
Parameters
|
|
62
92
|
----------
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
93
|
+
name : str
|
|
94
|
+
Passband name.
|
|
95
|
+
wl_min : float
|
|
96
|
+
Minimum wavelength in nm.
|
|
97
|
+
wl_max : float
|
|
98
|
+
Maximum wavelength in nm.
|
|
66
99
|
"""
|
|
67
100
|
super().__init__(name)
|
|
68
101
|
self.bbox = array([wl_min, wl_max], dtype='d')
|
|
@@ -84,10 +117,18 @@ class TabulatedFilter(Filter):
|
|
|
84
117
|
"""
|
|
85
118
|
Parameters
|
|
86
119
|
----------
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
wl
|
|
90
|
-
|
|
120
|
+
name : str
|
|
121
|
+
Passband name.
|
|
122
|
+
wl : array-like
|
|
123
|
+
Wavelengths in nm. Must increase monotonically.
|
|
124
|
+
tm : array-like
|
|
125
|
+
Transmission values in [0, 1], one per wavelength. Interpolated with a cubic spline.
|
|
126
|
+
|
|
127
|
+
Raises
|
|
128
|
+
------
|
|
129
|
+
AssertionError
|
|
130
|
+
If the arrays differ in size, the wavelengths are not monotonically increasing, or
|
|
131
|
+
a transmission value falls outside [0, 1].
|
|
91
132
|
"""
|
|
92
133
|
super().__init__(name)
|
|
93
134
|
self.wl = array(wl)
|
|
@@ -19,6 +19,32 @@ from .filter import Filter, BoxcarFilter
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class Instrument:
|
|
22
|
+
"""A set of passbands with optional detector quantum efficiency curves.
|
|
23
|
+
|
|
24
|
+
An `Instrument` bundles the passbands a contamination model integrates stellar spectra
|
|
25
|
+
over. The order of `filters` fixes the passband order used everywhere downstream: the
|
|
26
|
+
columns of the contamination arrays and the passband indices (`pbids`) given to a transit
|
|
27
|
+
model refer to this order.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
name : str
|
|
32
|
+
Instrument name.
|
|
33
|
+
filters : sequence of Filter
|
|
34
|
+
The passband transmission profiles, one per passband.
|
|
35
|
+
qes : Filter or sequence of Filter, optional
|
|
36
|
+
Detector quantum efficiency profiles. A single `Filter` is applied to every passband,
|
|
37
|
+
a sequence gives one profile per passband. Defaults to a flat unit response over
|
|
38
|
+
0-10000 nm, i.e. no quantum efficiency weighting.
|
|
39
|
+
|
|
40
|
+
Attributes
|
|
41
|
+
----------
|
|
42
|
+
pb_n : int
|
|
43
|
+
Number of passbands.
|
|
44
|
+
pb_names : list of str
|
|
45
|
+
Passband names, taken from the filter names, in the order given.
|
|
46
|
+
"""
|
|
47
|
+
|
|
22
48
|
def __init__(self, name, filters, qes=None):
|
|
23
49
|
self.name = name
|
|
24
50
|
|
|
@@ -99,9 +99,9 @@ def _jplot(hte, cte, cnr, imp, rho, fw=10, nb=30, gs=25, simulation=False, **kwa
|
|
|
99
99
|
ax_rm.hist(rho, bins=nb, alpha=0.5, range=rlim)
|
|
100
100
|
ax_cnm.hist(cnr, bins=nb, alpha=0.5, range=clim, orientation='horizontal')
|
|
101
101
|
|
|
102
|
-
pl.setp(ax_tt, xlabel='Host $T_\mathrm{Eff}$', ylabel='Contaminant $T_\mathrm{Eff}$')
|
|
103
|
-
pl.setp(ax_chj, xlabel='Host $T_\mathrm{Eff}$', ylabel=
|
|
104
|
-
pl.setp(ax_ccj, xlabel='Contaminant $T_\mathrm{Eff}$')
|
|
102
|
+
pl.setp(ax_tt, xlabel=r'Host $T_\mathrm{Eff}$', ylabel=r'Contaminant $T_\mathrm{Eff}$')
|
|
103
|
+
pl.setp(ax_chj, xlabel=r'Host $T_\mathrm{Eff}$', ylabel=r"Contamination in $i'$")
|
|
104
|
+
pl.setp(ax_ccj, xlabel=r'Contaminant $T_\mathrm{Eff}$')
|
|
105
105
|
pl.setp(ax_cbj, xlabel='Impact parameter')
|
|
106
106
|
pl.setp(ax_crj, xlabel='Stellar density')
|
|
107
107
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
# You should have received a copy of the GNU General Public License
|
|
15
15
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
16
|
|
|
17
|
-
from numpy import atleast_2d, zeros, concatenate, ones, inf, full, squeeze, c_
|
|
17
|
+
from numpy import atleast_2d, zeros, concatenate, ones, inf, full, squeeze, c_, ptp
|
|
18
18
|
from numba import njit, prange
|
|
19
19
|
|
|
20
20
|
from ...param import LParameter, NormalPrior as NP
|
|
@@ -52,7 +52,7 @@ class LegendreBaseline:
|
|
|
52
52
|
"""Baseline parameter initialisation.
|
|
53
53
|
"""
|
|
54
54
|
|
|
55
|
-
self._baseline_times = [(t - t.mean()) /
|
|
55
|
+
self._baseline_times = [(t - t.mean()) / ptp(t) for t in self.times]
|
|
56
56
|
self._baseline_timea = concatenate(self._baseline_times)
|
|
57
57
|
|
|
58
58
|
if isinstance(self.nlegendre, int):
|
|
@@ -19,9 +19,12 @@ import pandas as pd
|
|
|
19
19
|
import xarray as xa
|
|
20
20
|
import astropy.io.fits as pf
|
|
21
21
|
|
|
22
|
+
from contextlib import contextmanager
|
|
23
|
+
from multiprocessing import get_all_start_methods, get_context
|
|
22
24
|
from pathlib import Path
|
|
23
25
|
from time import strftime
|
|
24
26
|
from typing import Union, Iterable
|
|
27
|
+
from warnings import warn
|
|
25
28
|
|
|
26
29
|
from astropy.table import Table
|
|
27
30
|
from scipy.optimize import minimize
|
|
@@ -35,6 +38,57 @@ from pytransit.utils.de import DiffEvol
|
|
|
35
38
|
from pytransit.param import ParameterSet, UniformPrior as UP, NormalPrior as NP
|
|
36
39
|
|
|
37
40
|
|
|
41
|
+
def _init_pool_worker():
|
|
42
|
+
"""Initialise a worker process in a pool created by PyTransit.
|
|
43
|
+
|
|
44
|
+
Restricts Numba to a single thread per worker process. Without this, every worker
|
|
45
|
+
would spawn its own set of Numba threads, oversubscribing the machine badly (an
|
|
46
|
+
n-core pool would end up running n x n threads).
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
from numba import set_num_threads
|
|
50
|
+
set_num_threads(1)
|
|
51
|
+
except (ImportError, ValueError):
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _check_parallelisation(pool, ncores, vectorize) -> None:
|
|
56
|
+
"""Raise an error if a pool is requested together with a vectorised posterior.
|
|
57
|
+
"""
|
|
58
|
+
if vectorize and (pool is not None or (ncores is not None and ncores > 1)):
|
|
59
|
+
raise ValueError("Parallelisation using a multiprocessing pool cannot be combined with a vectorised log "
|
|
60
|
+
"posterior function because both DiffEvol and emcee bypass the pool when 'vectorize=True'. "
|
|
61
|
+
"Either set 'vectorize=False' to parallelise the posterior evaluation over the population "
|
|
62
|
+
"using the pool, or drop the 'pool' and 'ncores' arguments and rely on the vectorised "
|
|
63
|
+
"posterior.")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@contextmanager
|
|
67
|
+
def _resolve_pool(pool=None, ncores: int = None, start_method: str = None):
|
|
68
|
+
"""Yield the parallelisation pool to use, creating and closing one if necessary.
|
|
69
|
+
|
|
70
|
+
A user-provided pool takes precedence and is never closed here: its lifetime belongs
|
|
71
|
+
to whoever created it. A pool created from ``ncores`` is owned by this context
|
|
72
|
+
manager and is always terminated on exit, also if the run raises or is interrupted.
|
|
73
|
+
"""
|
|
74
|
+
if pool is not None:
|
|
75
|
+
if ncores is not None:
|
|
76
|
+
warn("Both 'pool' and 'ncores' were given: using the user-provided pool and ignoring 'ncores'.")
|
|
77
|
+
yield pool
|
|
78
|
+
elif ncores is not None and ncores > 1:
|
|
79
|
+
if start_method is None:
|
|
80
|
+
methods = get_all_start_methods()
|
|
81
|
+
start_method = 'forkserver' if 'forkserver' in methods else 'spawn'
|
|
82
|
+
pool = get_context(start_method).Pool(ncores, initializer=_init_pool_worker)
|
|
83
|
+
try:
|
|
84
|
+
yield pool
|
|
85
|
+
finally:
|
|
86
|
+
pool.terminate()
|
|
87
|
+
pool.join()
|
|
88
|
+
else:
|
|
89
|
+
yield None
|
|
90
|
+
|
|
91
|
+
|
|
38
92
|
class LogPosteriorFunction:
|
|
39
93
|
_lpf_name = 'LogPosteriorFunction'
|
|
40
94
|
|
|
@@ -63,6 +117,27 @@ class LogPosteriorFunction:
|
|
|
63
117
|
self._old_de_fitness = None
|
|
64
118
|
self._old_de_population = None
|
|
65
119
|
|
|
120
|
+
def __getstate__(self):
|
|
121
|
+
"""Return the picklable state of the log posterior function.
|
|
122
|
+
|
|
123
|
+
The DE optimiser and the MCMC sampler are excluded from the pickled state. Both
|
|
124
|
+
hold a reference to the parallelisation pool while running, and pool objects
|
|
125
|
+
cannot be pickled, which would make the whole log posterior function unpicklable
|
|
126
|
+
(and thus unusable with a multiprocessing pool) as soon as either exists. The
|
|
127
|
+
workers need only to evaluate the posterior, so neither is of any use to them.
|
|
128
|
+
"""
|
|
129
|
+
state = self.__dict__.copy()
|
|
130
|
+
for key in ('de', 'sampler', '_old_de_population', '_old_de_fitness'):
|
|
131
|
+
state.pop(key, None)
|
|
132
|
+
return state
|
|
133
|
+
|
|
134
|
+
def __setstate__(self, state):
|
|
135
|
+
self.__dict__.update(state)
|
|
136
|
+
self.de = None
|
|
137
|
+
self.sampler = None
|
|
138
|
+
self._old_de_population = None
|
|
139
|
+
self._old_de_fitness = None
|
|
140
|
+
|
|
66
141
|
def print_parameters(self, columns: int = 2):
|
|
67
142
|
columns = max(1, columns)
|
|
68
143
|
for i, p in enumerate(self.ps):
|
|
@@ -136,18 +211,56 @@ class LogPosteriorFunction:
|
|
|
136
211
|
|
|
137
212
|
def optimize_global(self, niter=200, npop=50, population=None, pool=None, lnpost=None, vectorize=True,
|
|
138
213
|
label='Global optimisation', leave=False, plot_convergence: bool = True, use_tqdm: bool = True,
|
|
139
|
-
plot_parameters: tuple = (0, 2, 3, 4), min_ptp: float = 1e-2
|
|
214
|
+
plot_parameters: tuple = (0, 2, 3, 4), min_ptp: float = 1e-2, ncores: int = None,
|
|
215
|
+
start_method: str = None):
|
|
216
|
+
"""Optimise the log posterior function globally using Differential Evolution.
|
|
140
217
|
|
|
218
|
+
Parameters
|
|
219
|
+
----------
|
|
220
|
+
pool
|
|
221
|
+
A parallelisation pool providing a `map` method (`multiprocessing.Pool`,
|
|
222
|
+
`schwimmbad.MPIPool`, etc.). The pool is used only for the duration of this
|
|
223
|
+
call and is not closed: its lifetime belongs to the caller. Requires
|
|
224
|
+
`vectorize=False`.
|
|
225
|
+
ncores
|
|
226
|
+
Number of processes in a pool created for the duration of this call and
|
|
227
|
+
closed afterwards. Ignored if `pool` is given. Requires `vectorize=False`.
|
|
228
|
+
start_method
|
|
229
|
+
Multiprocessing start method used for a pool created from `ncores`. Defaults
|
|
230
|
+
to 'forkserver' if available and 'spawn' otherwise. Forking is avoided by
|
|
231
|
+
default because it is unsafe to fork a process that has already run
|
|
232
|
+
multithreaded Numba code or initialised an OpenCL context. Note that these
|
|
233
|
+
start methods import the calling script in the worker processes, so a script
|
|
234
|
+
using `ncores` must guard its main code with `if __name__ == '__main__':`.
|
|
235
|
+
vectorize
|
|
236
|
+
If True (default), the whole population is passed to the log posterior
|
|
237
|
+
function in a single call and the parallelisation is left to Numba. This is
|
|
238
|
+
usually the fastest option on a single machine, but it is incompatible with
|
|
239
|
+
`pool` and `ncores`.
|
|
240
|
+
"""
|
|
141
241
|
lnpost = lnpost or self.lnposterior
|
|
142
242
|
if self.de is None:
|
|
243
|
+
_check_parallelisation(pool, ncores, vectorize)
|
|
143
244
|
self.de = DiffEvol(lnpost, clip(self.ps.bounds, -1, 1), npop, maximize=True, vectorize=vectorize,
|
|
144
|
-
|
|
245
|
+
min_ptp=min_ptp)
|
|
145
246
|
if population is None:
|
|
146
247
|
self.de._population[:, :] = self.create_pv_population(npop)
|
|
147
248
|
else:
|
|
148
249
|
self.de._population[:, :] = population
|
|
149
|
-
|
|
150
|
-
|
|
250
|
+
else:
|
|
251
|
+
_check_parallelisation(pool, ncores, self.de.vectorize)
|
|
252
|
+
|
|
253
|
+
# The pool is attached to the optimiser only for the duration of the run. Storing
|
|
254
|
+
# it permanently would leave the optimiser holding a reference to a pool that may
|
|
255
|
+
# already have been closed by the caller, and would make the log posterior
|
|
256
|
+
# function unpicklable.
|
|
257
|
+
with _resolve_pool(pool, ncores, start_method) as run_pool:
|
|
258
|
+
self.de.pool = run_pool
|
|
259
|
+
try:
|
|
260
|
+
for _ in tqdm(self.de(niter), total=niter, desc=label, leave=leave, disable=(not use_tqdm)):
|
|
261
|
+
pass
|
|
262
|
+
finally:
|
|
263
|
+
self.de.pool = None
|
|
151
264
|
|
|
152
265
|
if plot_convergence:
|
|
153
266
|
fig, axs = subplots(1, 1 + len(plot_parameters), figsize=(13, 2), constrained_layout=True)
|
|
@@ -174,8 +287,32 @@ class LogPosteriorFunction:
|
|
|
174
287
|
|
|
175
288
|
def sample_mcmc(self, niter: int = 500, thin: int = 5, repeats: int = 1, npop: int = None, population=None,
|
|
176
289
|
label='MCMC sampling', reset=True, leave=True, save=False, use_tqdm: bool = True, pool=None,
|
|
177
|
-
lnpost=None, vectorize: bool = True):
|
|
290
|
+
lnpost=None, vectorize: bool = True, ncores: int = None, start_method: str = None):
|
|
291
|
+
"""Sample the log posterior function using emcee.
|
|
178
292
|
|
|
293
|
+
Parameters
|
|
294
|
+
----------
|
|
295
|
+
pool
|
|
296
|
+
A parallelisation pool providing a `map` method (`multiprocessing.Pool`,
|
|
297
|
+
`schwimmbad.MPIPool`, etc.). The pool is used only for the duration of this
|
|
298
|
+
call and is not closed: its lifetime belongs to the caller. Requires
|
|
299
|
+
`vectorize=False`.
|
|
300
|
+
ncores
|
|
301
|
+
Number of processes in a pool created for the duration of this call and
|
|
302
|
+
closed afterwards. Ignored if `pool` is given. Requires `vectorize=False`.
|
|
303
|
+
start_method
|
|
304
|
+
Multiprocessing start method used for a pool created from `ncores`. Defaults
|
|
305
|
+
to 'forkserver' if available and 'spawn' otherwise. Forking is avoided by
|
|
306
|
+
default because it is unsafe to fork a process that has already run
|
|
307
|
+
multithreaded Numba code or initialised an OpenCL context. Note that these
|
|
308
|
+
start methods import the calling script in the worker processes, so a script
|
|
309
|
+
using `ncores` must guard its main code with `if __name__ == '__main__':`.
|
|
310
|
+
vectorize
|
|
311
|
+
If True (default), the whole ensemble is passed to the log posterior function
|
|
312
|
+
in a single call and the parallelisation is left to Numba. This is usually the
|
|
313
|
+
fastest option on a single machine, but it is incompatible with `pool` and
|
|
314
|
+
`ncores`.
|
|
315
|
+
"""
|
|
179
316
|
if save and self.result_dir is None:
|
|
180
317
|
raise ValueError('The MCMC sampler is set to save the results, but the result directory is not set.')
|
|
181
318
|
|
|
@@ -194,18 +331,31 @@ class LogPosteriorFunction:
|
|
|
194
331
|
pop0 = self.sampler.chain[:, -1, :].copy()
|
|
195
332
|
|
|
196
333
|
if self.sampler is None:
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
334
|
+
_check_parallelisation(pool, ncores, vectorize)
|
|
335
|
+
self.sampler = EnsembleSampler(pop0.shape[0], pop0.shape[1], lnpost, vectorize=vectorize)
|
|
336
|
+
else:
|
|
337
|
+
_check_parallelisation(pool, ncores, self.sampler.vectorize)
|
|
338
|
+
|
|
339
|
+
# The pool is attached to the sampler only for the duration of the run. Storing it
|
|
340
|
+
# permanently would leave the sampler holding a reference to a pool that may
|
|
341
|
+
# already have been closed by the caller, and would make the log posterior
|
|
342
|
+
# function unpicklable.
|
|
343
|
+
with _resolve_pool(pool, ncores, start_method) as run_pool:
|
|
344
|
+
self.sampler.pool = run_pool
|
|
345
|
+
try:
|
|
346
|
+
for i in tqdm(range(repeats), desc=label, disable=(not use_tqdm), leave=leave):
|
|
347
|
+
if (self.sampler is not None and reset) or i > 0:
|
|
348
|
+
self.sampler.reset()
|
|
349
|
+
for _ in tqdm(self.sampler.sample(pop0, iterations=niter, thin=thin,
|
|
350
|
+
skip_initial_state_check=False),
|
|
351
|
+
total=niter, desc='Run {:d}/{:d}'.format(i + 1, repeats), leave=False,
|
|
352
|
+
disable=(not use_tqdm)):
|
|
353
|
+
pass
|
|
354
|
+
if save:
|
|
355
|
+
self.save(self.result_dir)
|
|
356
|
+
pop0 = self.sampler.chain[:, -1, :].copy()
|
|
357
|
+
finally:
|
|
358
|
+
self.sampler.pool = None
|
|
209
359
|
|
|
210
360
|
def posterior_samples(self, burn: int = 0, thin: int = 1):
|
|
211
361
|
fc = self.sampler.chain[:, burn::thin, :].reshape([-1, len(self.ps)])
|
pytransit/lpf/lpf.py
CHANGED
|
@@ -26,7 +26,7 @@ from numba import njit, prange
|
|
|
26
26
|
from numpy import (inf, sqrt, ones, zeros_like, concatenate, diff, log, ones_like, all,
|
|
27
27
|
clip, argsort, any, s_, zeros, arccos, nan, full, pi, sum, repeat, asarray, ndarray, log10,
|
|
28
28
|
array, atleast_2d, isscalar, atleast_1d, where, isfinite, arange, unique, squeeze, ceil, percentile,
|
|
29
|
-
floor, diag, nanstd, seterr, median)
|
|
29
|
+
floor, diag, nanstd, seterr, median, ptp)
|
|
30
30
|
from numpy.random import uniform, normal, permutation, multivariate_normal
|
|
31
31
|
from scipy.stats import norm
|
|
32
32
|
|
|
@@ -606,6 +606,20 @@ class BaseLPF(LogPosteriorFunction):
|
|
|
606
606
|
dd.add_groups({'derived_parameters': ds})
|
|
607
607
|
return dd
|
|
608
608
|
|
|
609
|
+
def _planet_pids(self, planet: int = 1):
|
|
610
|
+
"""Parameter indices of a planet's zero epoch and period.
|
|
611
|
+
|
|
612
|
+
`BaseLPF` names its single planet's parameters `tc` and `p`, while the multiplanet LPFs
|
|
613
|
+
number theirs from one (`tc_1`, `p_1`, ...). Both are accepted, so that the plotting
|
|
614
|
+
methods work whichever parametrisation the LPF uses.
|
|
615
|
+
"""
|
|
616
|
+
names = self.ps.names
|
|
617
|
+
if f'tc_{planet}' in names:
|
|
618
|
+
return self.ps.find_pid(f'tc_{planet}'), self.ps.find_pid(f'p_{planet}')
|
|
619
|
+
if planet == 1 and 'tc' in names:
|
|
620
|
+
return self.ps.find_pid('tc'), self.ps.find_pid('p')
|
|
621
|
+
raise KeyError(f'Could not find the zero epoch and the period of planet {planet}.')
|
|
622
|
+
|
|
609
623
|
def plot_light_curves(self, method='de', ncol: int = 3, width: Optional[float] = None, planet: int = 1,
|
|
610
624
|
max_samples: int = 1000, figsize=None, data_alpha=0.5, ylim=None):
|
|
611
625
|
|
|
@@ -615,13 +629,13 @@ class BaseLPF(LogPosteriorFunction):
|
|
|
615
629
|
|
|
616
630
|
if width is None:
|
|
617
631
|
if self.nlc == 1:
|
|
618
|
-
width = 24 * self.timea
|
|
632
|
+
width = 24 * ptp(self.timea)
|
|
619
633
|
else:
|
|
620
634
|
width = 2.0
|
|
621
635
|
|
|
622
636
|
ncol = min(ncol, self.nlc)
|
|
623
637
|
nrow = int(ceil(self.nlc / ncol))
|
|
624
|
-
tid, pid = self.
|
|
638
|
+
tid, pid = self._planet_pids(planet)
|
|
625
639
|
if method in ('mcmc', 'mc', 'posterior'):
|
|
626
640
|
pvp = self.posterior_samples().posterior.to_array().values.copy().T.reshape([-1, len(self.ps)])
|
|
627
641
|
t0, p = median(pvp[:, tid]), median(pvp[:, pid])
|
pytransit/lpf/ocltdvlpf.py
CHANGED
|
@@ -19,8 +19,8 @@ import pyopencl as cl
|
|
|
19
19
|
import seaborn as sb
|
|
20
20
|
|
|
21
21
|
from matplotlib.pyplot import subplots, setp
|
|
22
|
-
from numpy import sqrt, array, inf,
|
|
23
|
-
newaxis
|
|
22
|
+
from numpy import sqrt, array, inf, s_, percentile, median, mean, round, zeros, isfinite, where, atleast_2d, ceil, \
|
|
23
|
+
newaxis, ptp
|
|
24
24
|
|
|
25
25
|
from ..param.parameter import GParameter, LParameter
|
|
26
26
|
from ..param.parameter import UniformPrior as UP, NormalPrior as NP
|
|
@@ -53,7 +53,7 @@ class OCLTDVLPF(OCLTTVLPF):
|
|
|
53
53
|
def create_tc_prior(t, f, p=5):
|
|
54
54
|
m = f > percentile(f, p)
|
|
55
55
|
m = ~ndi.binary_erosion(m, iterations=6, border_value=1)
|
|
56
|
-
return NP(t[m].mean(), 0.25 * t[m]
|
|
56
|
+
return NP(t[m].mean(), 0.25 * ptp(t[m]))
|
|
57
57
|
|
|
58
58
|
self.tnumber = round((array([t.mean() for t in self.times]) - self.zero_epoch) / self.period).astype(int)
|
|
59
59
|
for t, f, tn in zip(self.times, self.fluxes, self.tnumber):
|
pytransit/lpf/oclttvlpf.py
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from matplotlib.pyplot import subplots, setp
|
|
19
|
-
from numpy import sqrt, array, inf,
|
|
19
|
+
from numpy import sqrt, array, inf, s_, percentile, median, mean, round, zeros, atleast_2d, ceil, poly1d, polyfit
|
|
20
20
|
from numpy.random.mtrand import permutation
|
|
21
21
|
from uncertainties import ufloat, UFloat
|
|
22
22
|
|
pytransit/lpf/tdvlpf.py
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
16
|
|
|
17
17
|
from matplotlib.pyplot import subplots, setp
|
|
18
|
-
from numpy import pi, sign, cos, sqrt, sin, array, arccos, inf, round,
|
|
19
|
-
arange
|
|
18
|
+
from numpy import pi, sign, cos, sqrt, sin, array, arccos, inf, round, s_, percentile, concatenate, median, mean, \
|
|
19
|
+
arange, ptp
|
|
20
20
|
|
|
21
21
|
import scipy.ndimage as ndi
|
|
22
22
|
|
|
@@ -74,7 +74,7 @@ class TDVLPF(TTVLPF):
|
|
|
74
74
|
def create_tc_prior(t, f, p=5):
|
|
75
75
|
m = f > percentile(f, p)
|
|
76
76
|
m = ~ndi.binary_erosion(m, iterations=6, border_value=1)
|
|
77
|
-
return N(t[m].mean(), 0.25 * t[m]
|
|
77
|
+
return N(t[m].mean(), 0.25 * ptp(t[m]))
|
|
78
78
|
|
|
79
79
|
self.tnumber = round((array([t.mean() for t in self.times]) - self.zero_epoch) / self.period).astype(int)
|
|
80
80
|
for t, f, tn in zip(self.times, self.fluxes, self.tnumber):
|
pytransit/lpf/transitanalysis.py
CHANGED
|
@@ -24,7 +24,7 @@ import numpy as np
|
|
|
24
24
|
from matplotlib.pyplot import subplots, setp
|
|
25
25
|
from numba import njit
|
|
26
26
|
from numpy import inf, squeeze, atleast_2d, sqrt, zeros_like, \
|
|
27
|
-
ones, arange, argsort, arctan2, ndarray, median, percentile, ceil
|
|
27
|
+
ones, arange, argsort, arctan2, ndarray, median, percentile, ceil, ptp
|
|
28
28
|
from numpy.random import permutation
|
|
29
29
|
|
|
30
30
|
from .tesslpf import downsample_time
|
|
@@ -136,7 +136,7 @@ class TransitAnalysis(BaseLPF):
|
|
|
136
136
|
|
|
137
137
|
if width is None:
|
|
138
138
|
if self.nlc == 1:
|
|
139
|
-
width = 24 * self.timea
|
|
139
|
+
width = 24 * ptp(self.timea)
|
|
140
140
|
else:
|
|
141
141
|
width = 2.0
|
|
142
142
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# PyTransit: fast and easy exoplanet transit modelling in Python.
|
|
2
|
+
# Copyright (C) 2010-2026 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
|
+
"""Deprecation helpers for the legacy transit model evaluation API.
|
|
18
|
+
|
|
19
|
+
The `evaluate_ps` and `evaluate_pv` methods are the pre-2.9 evaluation interface, superseded by
|
|
20
|
+
the single broadcasting `evaluate` method. They are deprecated in PyTransit 2.9 and will be
|
|
21
|
+
removed in PyTransit 3.0.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from astropy.utils.decorators import deprecated
|
|
25
|
+
|
|
26
|
+
__all__ = ['deprecated_evaluation_method', 'DEPRECATED_IN', 'REMOVED_IN']
|
|
27
|
+
|
|
28
|
+
#: The version the legacy evaluation methods were deprecated in.
|
|
29
|
+
DEPRECATED_IN = '2.9'
|
|
30
|
+
|
|
31
|
+
#: The version the legacy evaluation methods will be removed in.
|
|
32
|
+
REMOVED_IN = '3.0'
|
|
33
|
+
|
|
34
|
+
_MESSAGE = (f'The {{func}} {{obj_type}} is deprecated since PyTransit {DEPRECATED_IN} and will be '
|
|
35
|
+
f'removed in PyTransit {REMOVED_IN}.')
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def deprecated_evaluation_method(alternative: str = 'evaluate'):
|
|
39
|
+
"""Mark a legacy transit model evaluation method as deprecated.
|
|
40
|
+
|
|
41
|
+
Applies `astropy.utils.decorators.deprecated` with the versions and wording PyTransit uses for
|
|
42
|
+
the legacy evaluation API. Besides raising an `AstropyDeprecationWarning` when the method is
|
|
43
|
+
called, the decorator prepends a ``.. deprecated::`` directive to the method's docstring, so
|
|
44
|
+
the deprecation is rendered in the API documentation without any extra markup.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
alternative : str, optional
|
|
49
|
+
Name of the method to use instead, quoted in the warning and in the documentation.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
callable
|
|
54
|
+
A decorator that marks the method deprecated.
|
|
55
|
+
|
|
56
|
+
Notes
|
|
57
|
+
-----
|
|
58
|
+
A deprecated method must never be called from inside PyTransit itself, or a user calling the
|
|
59
|
+
supported API would get the warning. Where `evaluate` shares an implementation with a
|
|
60
|
+
deprecated method, the implementation lives in a private method that both call.
|
|
61
|
+
"""
|
|
62
|
+
return deprecated(DEPRECATED_IN, message=_MESSAGE, obj_type='method', alternative=alternative)
|
|
@@ -19,6 +19,7 @@ from typing import Union, Optional
|
|
|
19
19
|
from numba import njit
|
|
20
20
|
from numpy import ndarray, squeeze, zeros, asarray, ones
|
|
21
21
|
from .numba.ma_uniform_nb import uniform_model_v, uniform_model_s
|
|
22
|
+
from ._deprecation import deprecated_evaluation_method
|
|
22
23
|
from .transitmodel import TransitModel
|
|
23
24
|
|
|
24
25
|
__all__ = ['EclipseModel']
|
|
@@ -27,6 +28,33 @@ npfloat = Union[float, ndarray]
|
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
class EclipseModel(TransitModel):
|
|
31
|
+
"""Secondary eclipse (occultation) model with an explicit planet-star flux ratio.
|
|
32
|
+
|
|
33
|
+
Models the occultation of the planet by the star. The planet is treated as a uniform disk,
|
|
34
|
+
which is a good approximation for the dayside emission of most planets, and the eclipse
|
|
35
|
+
geometry is the transit geometry with the sign of the projected distance reversed.
|
|
36
|
+
|
|
37
|
+
The depth of the event is set by the planet-star flux ratio `fr` passed to `evaluate`. With
|
|
38
|
+
`fr` given, the model returns a light curve normalised to unity out of eclipse and dipping
|
|
39
|
+
to ``1 - fr`` at its bottom, so it can be multiplied straight into a transit model. Without
|
|
40
|
+
`fr`, the model returns the raw uniform-disk occultation profile, which is useful when the
|
|
41
|
+
flux ratio is applied later or fitted separately per passband.
|
|
42
|
+
|
|
43
|
+
Examples
|
|
44
|
+
--------
|
|
45
|
+
::
|
|
46
|
+
|
|
47
|
+
from pytransit import EclipseModel
|
|
48
|
+
|
|
49
|
+
em = EclipseModel()
|
|
50
|
+
em.set_data(time)
|
|
51
|
+
flux = em.evaluate(k=0.1, t0=0.0, p=1.0, a=3.0, i=0.5*pi, fr=1e-3)
|
|
52
|
+
|
|
53
|
+
See Also
|
|
54
|
+
--------
|
|
55
|
+
UniformModel : the same geometry without the flux ratio, via ``eclipse=True``.
|
|
56
|
+
EclipseSpectroscopyModel : eclipse model for spectroscopic time series.
|
|
57
|
+
"""
|
|
30
58
|
|
|
31
59
|
def __init__(self) -> None:
|
|
32
60
|
super().__init__()
|
|
@@ -98,6 +126,7 @@ class EclipseModel(TransitModel):
|
|
|
98
126
|
|
|
99
127
|
return squeeze(flux)
|
|
100
128
|
|
|
129
|
+
@deprecated_evaluation_method()
|
|
101
130
|
def evaluate_ps(self, k: float, t0: float, p: float, a: float, i: float, e: float = 0., w: float = 0., fr: Optional[float] = None) -> ndarray:
|
|
102
131
|
"""Evaluate the transit model for a set of scalar parameters.
|
|
103
132
|
|