mc3 3.3.0__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.
- mc3/__init__.py +25 -0
- mc3/__main__.py +240 -0
- mc3/chain.py +340 -0
- mc3/fit_driver.py +270 -0
- mc3/mcmc_driver.py +378 -0
- mc3/plots/__init__.py +20 -0
- mc3/plots/colors.py +209 -0
- mc3/plots/plot_functions.py +351 -0
- mc3/plots/posterior.py +1278 -0
- mc3/sampler_driver.py +601 -0
- mc3/stats/__init__.py +25 -0
- mc3/stats/dwt.py +229 -0
- mc3/stats/gelman.py +92 -0
- mc3/stats/prayer.py +28 -0
- mc3/stats/stats.py +1043 -0
- mc3/stats/time_averaging.py +199 -0
- mc3/utils/__init__.py +18 -0
- mc3/utils/log.py +269 -0
- mc3/utils/utils.py +470 -0
- mc3/version.py +9 -0
- mc3-3.3.0.dist-info/METADATA +60 -0
- mc3-3.3.0.dist-info/RECORD +26 -0
- mc3-3.3.0.dist-info/WHEEL +5 -0
- mc3-3.3.0.dist-info/entry_points.txt +2 -0
- mc3-3.3.0.dist-info/licenses/LICENSE +19 -0
- mc3-3.3.0.dist-info/top_level.txt +1 -0
mc3/fit_driver.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Copyright (c) 2015-2026 Patricio Cubillos and contributors.
|
|
2
|
+
# mc3 is open-source software under the MIT license (see LICENSE).
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
'fit',
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import scipy.optimize as so
|
|
10
|
+
|
|
11
|
+
from . import stats as ms
|
|
12
|
+
from . import utils as mu
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def fit(
|
|
16
|
+
data, uncert, func, params, indparams=[], indparams_dict={},
|
|
17
|
+
pstep=None, pmin=None, pmax=None,
|
|
18
|
+
prior=None, priorlow=None, priorup=None, leastsq='lm',
|
|
19
|
+
):
|
|
20
|
+
r"""
|
|
21
|
+
Find the best-fitting params values to the dataset by performing a
|
|
22
|
+
Maximum-A-Posteriori optimization.
|
|
23
|
+
|
|
24
|
+
This is achieved by minimizing the negative log posterior, with:
|
|
25
|
+
log_post = log(posterior)
|
|
26
|
+
= log(likelihood) + log(prior)
|
|
27
|
+
= -0.5*chi-squared + log_prior
|
|
28
|
+
= sum_i -0.5*((data[i] - model[i])/uncert[i])**2 + log_prior
|
|
29
|
+
|
|
30
|
+
where log_prior is defined as:
|
|
31
|
+
log_prior = sum -0.5*((params - prior)/prior_uncert)**2
|
|
32
|
+
for each parameter with a Gaussian prior; parameters with
|
|
33
|
+
uniform priors do not contribute to log_prior.
|
|
34
|
+
|
|
35
|
+
Constant terms have been neglected since they don't affect the
|
|
36
|
+
optimization.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
data: 1D ndarray
|
|
41
|
+
Data fitted by func.
|
|
42
|
+
uncert: 1D ndarray
|
|
43
|
+
1-sigma uncertainties of data.
|
|
44
|
+
func: callable
|
|
45
|
+
The fitting function to model the data. It must be callable as:
|
|
46
|
+
model = func(params, *indparams, **indparams_dict)
|
|
47
|
+
params: 1D ndarray
|
|
48
|
+
The model parameters.
|
|
49
|
+
indparams: tuple
|
|
50
|
+
Additional arguments required by func (if required).
|
|
51
|
+
indparams_dict: dict
|
|
52
|
+
Additional keyword arguments required by func (if required).
|
|
53
|
+
pstep: 1D ndarray
|
|
54
|
+
Parameters fitting behavior.
|
|
55
|
+
If pstep is positive, the parameter is free for fitting.
|
|
56
|
+
If pstep is zero, keep the parameter value fixed.
|
|
57
|
+
If pstep is a negative integer, copy the value from
|
|
58
|
+
params[np.abs(pstep)+1].
|
|
59
|
+
pmin: 1D ndarray
|
|
60
|
+
Model parameters' lower boundaries. Default -np.inf.
|
|
61
|
+
Only for leastsq='trf', since 'lm' does not handle bounds.
|
|
62
|
+
pmax: 1D ndarray
|
|
63
|
+
Model parameters' upper boundaries. Default +np.inf.
|
|
64
|
+
Only for leastsq='trf', since 'lm' does not handle bounds.
|
|
65
|
+
prior: 1D ndarray
|
|
66
|
+
Parameters priors. The type of prior is determined by priorlow
|
|
67
|
+
and priorup:
|
|
68
|
+
Gaussian: if both priorlow>0 and priorup>0
|
|
69
|
+
Uniform: else
|
|
70
|
+
priorlow: 1D ndarray
|
|
71
|
+
Parameters' lower 1-sigma Gaussian prior.
|
|
72
|
+
priorup: 1D ndarray
|
|
73
|
+
Paraneters' upper 1-sigma Gaussian prior.
|
|
74
|
+
leastsq: String
|
|
75
|
+
Optimization algorithm:
|
|
76
|
+
If 'lm': use the Levenberg-Marquardt algorithm
|
|
77
|
+
If 'trf': use the Trust Region Reflective algorithm
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
mc3_output: Dict
|
|
82
|
+
A dictionary containing the fit outputs, including:
|
|
83
|
+
- best_log_post: optimal log of the posterior (as defined above).
|
|
84
|
+
- best_chisq: chi-square for the found best_log_post.
|
|
85
|
+
- best_model: model evaluated at bestp.
|
|
86
|
+
- bestp: Model parameters for the optimal best_log_post.
|
|
87
|
+
- optimizer_res: the output from the scipy optimizer.
|
|
88
|
+
|
|
89
|
+
Examples
|
|
90
|
+
--------
|
|
91
|
+
>>> import mc3
|
|
92
|
+
>>> import numpy as np
|
|
93
|
+
|
|
94
|
+
>>> def quad(p, x):
|
|
95
|
+
>>> '''Quadratic polynomial: y(x) = p0 + p1*x + p2*x^2'''
|
|
96
|
+
>>> return p[0] + p[1]*x + p[2]*x**2.0
|
|
97
|
+
|
|
98
|
+
>>> # Preamble, create a noisy synthetic dataset:
|
|
99
|
+
>>> np.random.seed(10)
|
|
100
|
+
>>> x = np.linspace(0, 10, 100)
|
|
101
|
+
>>> p_true = [4.5, -2.4, 0.5]
|
|
102
|
+
>>> y = quad(p_true, x)
|
|
103
|
+
>>> uncert = np.sqrt(np.abs(y))
|
|
104
|
+
>>> data = y + np.random.normal(0, uncert)
|
|
105
|
+
|
|
106
|
+
>>> # Initial guess for fitting parameters:
|
|
107
|
+
>>> params = np.array([ 3.0, -2.0, 0.1])
|
|
108
|
+
|
|
109
|
+
>>> # Fit data:
|
|
110
|
+
>>> output = mc3.fit(data, uncert, quad, params, indparams=[x])
|
|
111
|
+
>>> print(output['bestp'], output['best_chisq'], -2*output['best_log_post'], sep='\n')
|
|
112
|
+
[ 4.57471072 -2.28357843 0.48341911]
|
|
113
|
+
92.79923183159411
|
|
114
|
+
92.79923183159411
|
|
115
|
+
|
|
116
|
+
>>> # Fit with priors (Gaussian, uniform, uniform):
|
|
117
|
+
>>> prior = np.array([4.0, 0.0, 0.0])
|
|
118
|
+
>>> priorlow = np.array([0.1, 0.0, 0.0])
|
|
119
|
+
>>> priorup = np.array([0.1, 0.0, 0.0])
|
|
120
|
+
>>> output = mc3.fit(data, uncert, quad, params, indparams=[x],
|
|
121
|
+
prior=prior, priorlow=priorlow, priorup=priorup)
|
|
122
|
+
>>> print(output['bestp'], output['best_chisq'], -2*output['best_log_post'], sep='\n')
|
|
123
|
+
[ 4.01743461 -2.00989433 0.45686521]
|
|
124
|
+
93.77082119449915
|
|
125
|
+
93.80121777303248
|
|
126
|
+
"""
|
|
127
|
+
with mu.Log() as log:
|
|
128
|
+
if leastsq not in [None, 'lm', 'trf']:
|
|
129
|
+
log.error(
|
|
130
|
+
f"Invalid 'leastsq' input ({leastsq}). Must select "
|
|
131
|
+
"from ['lm', 'trf']"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Total number of model parameters:
|
|
135
|
+
npars = len(params)
|
|
136
|
+
# Default pstep:
|
|
137
|
+
if pstep is None:
|
|
138
|
+
pstep = np.ones(npars, np.double)
|
|
139
|
+
# Default boundaries (all parameter space):
|
|
140
|
+
if pmin is None:
|
|
141
|
+
pmin = np.tile(-np.inf, npars)
|
|
142
|
+
if pmax is None:
|
|
143
|
+
pmax = np.tile(np.inf, npars)
|
|
144
|
+
# Default priors, must set all or no one:
|
|
145
|
+
if prior is None or priorlow is None or priorup is None:
|
|
146
|
+
prior = priorup = priorlow = np.zeros(npars)
|
|
147
|
+
|
|
148
|
+
# Cast to ndarrays:
|
|
149
|
+
params = np.asarray(params)
|
|
150
|
+
pstep = np.asarray(pstep)
|
|
151
|
+
pmin = np.asarray(pmin)
|
|
152
|
+
pmax = np.asarray(pmax)
|
|
153
|
+
prior = np.asarray(prior)
|
|
154
|
+
priorlow = np.asarray(priorlow)
|
|
155
|
+
priorup = np.asarray(priorup)
|
|
156
|
+
|
|
157
|
+
# Get indices:
|
|
158
|
+
ifree = np.where(pstep > 0)[0]
|
|
159
|
+
ishare = np.where(pstep < 0)[0]
|
|
160
|
+
|
|
161
|
+
fitparams = params[ifree]
|
|
162
|
+
|
|
163
|
+
args = (
|
|
164
|
+
params, func, data, uncert, indparams, indparams_dict,
|
|
165
|
+
pstep, prior, priorlow, priorup, ifree, ishare,
|
|
166
|
+
)
|
|
167
|
+
# Levenberg-Marquardt optimization:
|
|
168
|
+
if leastsq == 'lm':
|
|
169
|
+
lsfit = so.leastsq(
|
|
170
|
+
residuals, fitparams, args=args,
|
|
171
|
+
ftol=3e-16, xtol=3e-16, gtol=3e-16,
|
|
172
|
+
full_output=True,
|
|
173
|
+
)
|
|
174
|
+
output, cov_x, infodict, mesg, err = lsfit
|
|
175
|
+
params[ifree] = lsfit[0]
|
|
176
|
+
resid = lsfit[2]["fvec"]
|
|
177
|
+
|
|
178
|
+
# Bounded optimization:
|
|
179
|
+
elif leastsq == 'trf':
|
|
180
|
+
lsfit = so.least_squares(
|
|
181
|
+
residuals, fitparams,
|
|
182
|
+
bounds=(pmin[ifree], pmax[ifree]), args=args,
|
|
183
|
+
ftol=3e-16, xtol=3e-16, gtol=3e-16,
|
|
184
|
+
method='trf',
|
|
185
|
+
)
|
|
186
|
+
params[ifree] = lsfit["x"]
|
|
187
|
+
resid = lsfit["fun"]
|
|
188
|
+
|
|
189
|
+
# Update shared parameters:
|
|
190
|
+
for s in ishare:
|
|
191
|
+
params[s] = params[-int(pstep[s])-1]
|
|
192
|
+
|
|
193
|
+
# Compute best-fit model:
|
|
194
|
+
best_model = func(params, *indparams, **indparams_dict)
|
|
195
|
+
# Calculate chi-squared for best-fitting values:
|
|
196
|
+
best_log_post = -0.5*np.sum(resid**2.0)
|
|
197
|
+
log_prior = ms.log_prior(params[ifree], prior, priorlow, priorup, pstep)
|
|
198
|
+
best_chisq = -2*(best_log_post - log_prior)
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
'bestp':params,
|
|
202
|
+
'best_log_post':best_log_post,
|
|
203
|
+
'best_chisq':best_chisq,
|
|
204
|
+
'best_model':best_model,
|
|
205
|
+
'optimizer_res':lsfit,
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def residuals(
|
|
210
|
+
fitparams, params, func, data, uncert, indparams, indparams_dict,
|
|
211
|
+
pstep, prior, priorlow, priorup, ifree, ishare,
|
|
212
|
+
):
|
|
213
|
+
"""
|
|
214
|
+
Calculate the weighted residuals between data and a model, accounting
|
|
215
|
+
also for parameter priors.
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
fitparams: 1D ndarray
|
|
220
|
+
The model free parameters.
|
|
221
|
+
params: 1D ndarray
|
|
222
|
+
The model parameters (including fixed and shared parameters).
|
|
223
|
+
func: Callable
|
|
224
|
+
The fitting function to model the data, called as:
|
|
225
|
+
model = func(params, *indparams, **indparams_dict)
|
|
226
|
+
data: 1D ndarray
|
|
227
|
+
Dependent data fitted by func.
|
|
228
|
+
uncert: 1D ndarray
|
|
229
|
+
1-sigma uncertainty of data.
|
|
230
|
+
indparams: tuple
|
|
231
|
+
Additional arguments required by func (if required).
|
|
232
|
+
indparams_dict: dict
|
|
233
|
+
Additional keyword arguments required by func (if required).
|
|
234
|
+
pstep: 1D ndarray
|
|
235
|
+
Parameters' jump scale (same size as params).
|
|
236
|
+
If the pstep is positive, the parameter is free for fitting.
|
|
237
|
+
If the pstep is 0, keep the parameter value fixed.
|
|
238
|
+
If the pstep is a negative integer, copy (share) the parameter value
|
|
239
|
+
from params[np.abs(pstep)+1], which can be free or fixed.
|
|
240
|
+
prior: 1D ndarray
|
|
241
|
+
Model parameters' (Gaussian) prior values (same size as params).
|
|
242
|
+
Considered only when priolow != 0. priorlow and priorup are the
|
|
243
|
+
lower and upper 1-sigma width of the Gaussian prior, respectively.
|
|
244
|
+
priorlow: 1D ndarray
|
|
245
|
+
Parameters' lower 1-sigma Gaussian prior (same size as params).
|
|
246
|
+
priorup: 1D ndarray
|
|
247
|
+
Paraneters' upper 1-sigma Gaussian prior (same size as params).
|
|
248
|
+
ifree: 1D bool ndarray
|
|
249
|
+
Indices of the free parameters in params.
|
|
250
|
+
ishare: 1D bool ndarray
|
|
251
|
+
Indices of the shared parameters in params.
|
|
252
|
+
|
|
253
|
+
Returns
|
|
254
|
+
-------
|
|
255
|
+
Array of weighted data-model and prior-params residuals.
|
|
256
|
+
"""
|
|
257
|
+
# Update params with fitparams:
|
|
258
|
+
params[ifree] = fitparams
|
|
259
|
+
|
|
260
|
+
# Update shared parameters:
|
|
261
|
+
for s in ishare:
|
|
262
|
+
params[s] = params[-int(pstep[s])-1]
|
|
263
|
+
|
|
264
|
+
# Compute model:
|
|
265
|
+
model = func(params, *indparams, **indparams_dict)
|
|
266
|
+
# Calculate residuals:
|
|
267
|
+
residuals = ms.residuals(
|
|
268
|
+
model, data, uncert, params, prior, priorlow, priorup,
|
|
269
|
+
)
|
|
270
|
+
return residuals
|
mc3/mcmc_driver.py
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# Copyright (c) 2015-2026 Patricio Cubillos and contributors.
|
|
2
|
+
# mc3 is open-source software under the MIT license (see LICENSE).
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
'mcmc',
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
import ctypes
|
|
10
|
+
import multiprocessing as mpr
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from . import chain as ch
|
|
15
|
+
from . import stats as ms
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def mcmc(
|
|
19
|
+
data, uncert, func, params, indparams, indparams_dict,
|
|
20
|
+
pmin, pmax, pstep,
|
|
21
|
+
prior, priorlow, priorup,
|
|
22
|
+
nchains, ncpu, nsamples, sampler,
|
|
23
|
+
wlike, fit_output, grtest, grbreak, grnmin, burnin, thinning,
|
|
24
|
+
fgamma, fepsilon, hsize, kickoff, savefile, resume, log,
|
|
25
|
+
pnames, texnames,
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
Mid-level routine called by mc3.sample() to execute Markov-chain Monte
|
|
29
|
+
Carlo run.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
data: 1D float ndarray
|
|
34
|
+
Data to be fit by func.
|
|
35
|
+
uncert: 1D float ndarray
|
|
36
|
+
Uncertainties of data.
|
|
37
|
+
func: Callable or string-iterable
|
|
38
|
+
The callable function that models data as:
|
|
39
|
+
model = func(params, *indparams, **indparams_dict)
|
|
40
|
+
params: 1D float ndarray
|
|
41
|
+
Set of initial fitting parameters for func.
|
|
42
|
+
indparams: tuple
|
|
43
|
+
Additional arguments required by func.
|
|
44
|
+
indparams_dict: dict
|
|
45
|
+
Additional keyword arguments required by func (if required).
|
|
46
|
+
pmin: 1D ndarray
|
|
47
|
+
Lower boundaries for the posterior exploration.
|
|
48
|
+
pmax: 1D ndarray
|
|
49
|
+
Upper boundaries for the posterior exploration.
|
|
50
|
+
pstep: 1D ndarray
|
|
51
|
+
Parameter stepping.
|
|
52
|
+
prior: 1D ndarray
|
|
53
|
+
Parameter prior distribution means.
|
|
54
|
+
priorlow: 1D ndarray
|
|
55
|
+
Lower prior uncertainty values.
|
|
56
|
+
priorup: 1D ndarray
|
|
57
|
+
Upper prior uncertainty values.
|
|
58
|
+
nchains: Scalar
|
|
59
|
+
Number of simultaneous chains to run.
|
|
60
|
+
ncpu: Integer
|
|
61
|
+
Number of processors for the MCMC chains.
|
|
62
|
+
nsamples: Scalar
|
|
63
|
+
Total number of samples.
|
|
64
|
+
sampler: String
|
|
65
|
+
MCMC sampling algorithm select from [mrw, demc, snooker]
|
|
66
|
+
wlike: Bool
|
|
67
|
+
If True, calculate the likelihood in a wavelet-base.
|
|
68
|
+
grtest: Boolean
|
|
69
|
+
Run Gelman & Rubin test.
|
|
70
|
+
grbreak: Float
|
|
71
|
+
Gelman-Rubin convergence threshold to stop the MCMC.
|
|
72
|
+
grnmin: Float
|
|
73
|
+
Minimum number of samples required for grbreak to stop the MCMC.
|
|
74
|
+
burnin: Integer
|
|
75
|
+
Number of burned-in (discarded) iterations.
|
|
76
|
+
thinning: Integer
|
|
77
|
+
Thinning factor of the chains (use every thinning-th iteration).
|
|
78
|
+
fgamma: Float
|
|
79
|
+
Proposals jump scale factor for DEMC's gamma.
|
|
80
|
+
fepsilon: Float
|
|
81
|
+
Jump scale factor for DEMC's support distribution.
|
|
82
|
+
hsize: Integer
|
|
83
|
+
Number of initial samples per chain.
|
|
84
|
+
kickoff: String
|
|
85
|
+
How to start the chains:
|
|
86
|
+
'normal' for normal distribution around initial guess, or
|
|
87
|
+
'uniform' for uniform distribution withing the given boundaries.
|
|
88
|
+
savefile: String
|
|
89
|
+
If not None, filename to store allparams and other MCMC results.
|
|
90
|
+
resume: Boolean
|
|
91
|
+
If True resume a previous run.
|
|
92
|
+
log: mc3.utils.Log instance
|
|
93
|
+
Logging object.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
output: Dict
|
|
98
|
+
A Dictionary containing the MCMC posterior distribution and related
|
|
99
|
+
stats, including:
|
|
100
|
+
- posterior: thinned posterior distribution of shape [nsamples, nfree].
|
|
101
|
+
- zchain: chain indices for each sample in Z.
|
|
102
|
+
- zmask: indices that turn Z into the desired posterior (remove burn-in)
|
|
103
|
+
- chisq: chi^2 value for each sample in Z.
|
|
104
|
+
- log_posterior: log(posterior) for the samples in Z.
|
|
105
|
+
- burnin: number of burned-in samples per chain.
|
|
106
|
+
- bestp: model parameters for the optimal log(posterior) sample.
|
|
107
|
+
- best_model: model evaluated at bestp.
|
|
108
|
+
- best_chisq: chi^2 for the optimal log(posterior) in the sample.
|
|
109
|
+
- best_log_post: optimal log(posterior) in posterior.
|
|
110
|
+
- acceptance_rate: sample's acceptance rate.
|
|
111
|
+
|
|
112
|
+
Examples
|
|
113
|
+
--------
|
|
114
|
+
>>> # See https://mc3.readthedocs.io/en/latest/mcmc_tutorial.html
|
|
115
|
+
"""
|
|
116
|
+
nfree = int(np.sum(pstep > 0))
|
|
117
|
+
ifree = np.where(pstep > 0)[0]
|
|
118
|
+
ishare = np.where(pstep < 0)[0]
|
|
119
|
+
|
|
120
|
+
if resume:
|
|
121
|
+
oldrun = np.load(savefile)
|
|
122
|
+
zold = oldrun["posterior"]
|
|
123
|
+
zchain_old = oldrun["zchain"]
|
|
124
|
+
# Size of posterior (prior to this MCMC sample):
|
|
125
|
+
pre_zsize = M0 = np.shape(zold)[0]
|
|
126
|
+
else:
|
|
127
|
+
pre_zsize = M0 = hsize*nchains
|
|
128
|
+
|
|
129
|
+
# Number of Z samples per chain:
|
|
130
|
+
nzchain = int(np.ceil(nsamples/nchains/thinning))
|
|
131
|
+
# Number of iterations per chain:
|
|
132
|
+
niter = nzchain * thinning
|
|
133
|
+
# Total number of Z samples (initial + chains):
|
|
134
|
+
zlen = pre_zsize + nzchain*nchains
|
|
135
|
+
|
|
136
|
+
burnin = int(burnin)
|
|
137
|
+
if not resume and niter < burnin:
|
|
138
|
+
log.error(
|
|
139
|
+
f"The number of burned-in samples ({burnin}) is greater than "
|
|
140
|
+
f"the number of iterations per chain ({niter})"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Initialize shared-memory variables:
|
|
144
|
+
sm_freepars = mpr.Array(ctypes.c_double, nchains*nfree)
|
|
145
|
+
freepars = np.ctypeslib.as_array(sm_freepars.get_obj())
|
|
146
|
+
freepars = freepars.reshape((nchains, nfree))
|
|
147
|
+
|
|
148
|
+
best_log_post = mpr.Value(ctypes.c_double, np.inf)
|
|
149
|
+
sm_bestp = mpr.Array(ctypes.c_double, np.copy(params))
|
|
150
|
+
bestp = np.ctypeslib.as_array(sm_bestp.get_obj())
|
|
151
|
+
# There seems to be a strange behavior with np.ctypeslib.as_array()
|
|
152
|
+
# when the argument is a single-element array. In this case, the
|
|
153
|
+
# returned value is a two-dimensional array, instead of 1D. The
|
|
154
|
+
# following line fixes(?) that behavior:
|
|
155
|
+
if np.ndim(bestp) > 1:
|
|
156
|
+
bestp = bestp.flatten()
|
|
157
|
+
|
|
158
|
+
numaccept = mpr.Value(ctypes.c_int, 0)
|
|
159
|
+
outbounds = mpr.Array(ctypes.c_int, nfree)
|
|
160
|
+
|
|
161
|
+
# Z array with the chains history:
|
|
162
|
+
sm_Z = mpr.Array(ctypes.c_double, zlen*nfree)
|
|
163
|
+
Z = np.ctypeslib.as_array(sm_Z.get_obj())
|
|
164
|
+
Z = Z.reshape((zlen, nfree))
|
|
165
|
+
|
|
166
|
+
# Chi-square value of Z:
|
|
167
|
+
sm_log_post = mpr.Array(ctypes.c_double, zlen)
|
|
168
|
+
log_post = np.ctypeslib.as_array(sm_log_post.get_obj())
|
|
169
|
+
# Chain index for given state in the Z array:
|
|
170
|
+
sm_zchain = mpr.Array(ctypes.c_int, -np.ones(zlen, int))
|
|
171
|
+
zchain = np.ctypeslib.as_array(sm_zchain.get_obj())
|
|
172
|
+
# Current number of samples in the Z array:
|
|
173
|
+
zsize = mpr.Value(ctypes.c_int, M0)
|
|
174
|
+
# Burned samples in the Z array per chain:
|
|
175
|
+
zburn = int(burnin/thinning)
|
|
176
|
+
|
|
177
|
+
# Include values from previous run:
|
|
178
|
+
if resume:
|
|
179
|
+
Z[0:pre_zsize,:] = zold
|
|
180
|
+
zchain[0:pre_zsize] = oldrun["zchain"]
|
|
181
|
+
log_post[0:pre_zsize] = oldrun["log_post"]
|
|
182
|
+
# Redefine zsize:
|
|
183
|
+
zsize.value = pre_zsize
|
|
184
|
+
numaccept.value = int(oldrun["acceptance_rate"] / 100. * pre_zsize)
|
|
185
|
+
|
|
186
|
+
# Set GR N-min as number of thinned samples:
|
|
187
|
+
if grnmin >= 1:
|
|
188
|
+
grnmin = int(grnmin/thinning)
|
|
189
|
+
elif grnmin > 0:
|
|
190
|
+
grnmin = int(grnmin*nchains*(nzchain-zburn))
|
|
191
|
+
elif grnmin < 0:
|
|
192
|
+
log.error(
|
|
193
|
+
"Invalid 'grnmin' argument (minimum number of samples to "
|
|
194
|
+
"stop the MCMC under GR convergence), must either be grnmin > 1"
|
|
195
|
+
"to set the minimum number of samples, or 0 < grnmin < 1"
|
|
196
|
+
"to set the fraction of samples required to evaluate.")
|
|
197
|
+
# Add these to compare grnmin to zsize (which also include them):
|
|
198
|
+
grnmin += int(M0 + zburn*nchains)
|
|
199
|
+
|
|
200
|
+
# Current length of each chain:
|
|
201
|
+
sm_chainsize = mpr.Array(ctypes.c_int, np.tile(hsize, nchains))
|
|
202
|
+
chainsize = np.ctypeslib.as_array(sm_chainsize.get_obj())
|
|
203
|
+
|
|
204
|
+
# Number of chains per processor:
|
|
205
|
+
ncpp = np.tile(int(nchains/ncpu), ncpu)
|
|
206
|
+
ncpp[0:nchains % ncpu] += 1
|
|
207
|
+
|
|
208
|
+
# Launch Chains:
|
|
209
|
+
mp_context = mpr.get_context('fork')
|
|
210
|
+
pipes = []
|
|
211
|
+
chains = []
|
|
212
|
+
for i in range(ncpu):
|
|
213
|
+
p = mp_context.Pipe()
|
|
214
|
+
pipes.append(p[0])
|
|
215
|
+
chains.append(
|
|
216
|
+
ch.Chain(func, indparams, indparams_dict, p[1], data, uncert,
|
|
217
|
+
params, freepars, pstep, pmin, pmax,
|
|
218
|
+
sampler, wlike, prior, priorlow, priorup, thinning,
|
|
219
|
+
fgamma, fepsilon, Z, zsize, log_post, zchain, M0,
|
|
220
|
+
numaccept, outbounds, ncpp[i],
|
|
221
|
+
chainsize, bestp, best_log_post, i, ncpu))
|
|
222
|
+
|
|
223
|
+
if resume:
|
|
224
|
+
bestp[:] = oldrun['bestp']
|
|
225
|
+
best_log_post.value = oldrun['best_log_post']
|
|
226
|
+
for c in range(nchains):
|
|
227
|
+
chainsize[c] = np.sum(zchain_old==c)
|
|
228
|
+
else:
|
|
229
|
+
def random_pick(kickoff):
|
|
230
|
+
x0 = np.copy(params[ifree])
|
|
231
|
+
sigma = np.copy(pstep[ifree])
|
|
232
|
+
x_min = np.copy(pmin[ifree])
|
|
233
|
+
x_max = np.copy(pmax[ifree])
|
|
234
|
+
while True:
|
|
235
|
+
if kickoff == 'normal':
|
|
236
|
+
yield np.random.normal(x0, sigma)
|
|
237
|
+
elif kickoff == 'uniform':
|
|
238
|
+
yield np.random.uniform(x_min, x_max)
|
|
239
|
+
|
|
240
|
+
# Evaluate models for initial sample of Z:
|
|
241
|
+
values = np.asarray(params)
|
|
242
|
+
i = 0
|
|
243
|
+
j = 0
|
|
244
|
+
nmax_trials = 100 * M0
|
|
245
|
+
for trial in random_pick(kickoff):
|
|
246
|
+
if i == M0 or j == nmax_trials:
|
|
247
|
+
break
|
|
248
|
+
|
|
249
|
+
values[ifree] = trial
|
|
250
|
+
if np.any(values > pmax) or np.any(values < pmin):
|
|
251
|
+
j += 1
|
|
252
|
+
continue
|
|
253
|
+
# Update shared parameters:
|
|
254
|
+
for s in ishare:
|
|
255
|
+
values[s] = values[-int(pstep[s])-1]
|
|
256
|
+
chi_square = -0.5*chains[0].eval_model(values, ret='chisq')
|
|
257
|
+
if not np.isfinite(chi_square):
|
|
258
|
+
j += 1
|
|
259
|
+
continue
|
|
260
|
+
Z[i] = values[ifree]
|
|
261
|
+
log_post[i] = chi_square
|
|
262
|
+
i += 1
|
|
263
|
+
|
|
264
|
+
if i < M0-1:
|
|
265
|
+
log.error(
|
|
266
|
+
'Cannot populate an initial sample set of parameters, try '
|
|
267
|
+
'updating the parameters initial guess to avoid sampling '
|
|
268
|
+
'beyond the parameter boundaries or where the model returns '
|
|
269
|
+
'non-finite values.'
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Best-fitting values (so far):
|
|
273
|
+
izbest = np.argmax(log_post[0:M0])
|
|
274
|
+
best_log_post.value = log_post[izbest]
|
|
275
|
+
bestp[ifree] = np.copy(Z[izbest])
|
|
276
|
+
if fit_output is not None:
|
|
277
|
+
bestp[:] = np.copy(fit_output['bestp'])
|
|
278
|
+
best_log_post.value = fit_output['best_log_post']
|
|
279
|
+
|
|
280
|
+
# The output dict:
|
|
281
|
+
output = {
|
|
282
|
+
'pnames': pnames,
|
|
283
|
+
'texnames': texnames,
|
|
284
|
+
'pstep': pstep,
|
|
285
|
+
'ifree': ifree,
|
|
286
|
+
'burnin': zburn,
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
# ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
|
|
290
|
+
# Start loop:
|
|
291
|
+
print("Yippee Ki Yay Monte Carlo!")
|
|
292
|
+
log.msg(f"Start MCMC chains ({time.ctime()})")
|
|
293
|
+
for chain in chains:
|
|
294
|
+
chain.start()
|
|
295
|
+
bit = bool(1) # Dummy variable to send through pipe for DEMC
|
|
296
|
+
# Intermediate steps to run GR test and print progress report:
|
|
297
|
+
intsteps = (nzchain*nchains) / 10
|
|
298
|
+
report = intsteps
|
|
299
|
+
|
|
300
|
+
while True:
|
|
301
|
+
# Proposal jump:
|
|
302
|
+
if sampler == "demc":
|
|
303
|
+
# Send and receive bit for DEMC synchronization:
|
|
304
|
+
for pipe in pipes:
|
|
305
|
+
pipe.send(bit)
|
|
306
|
+
for pipe in pipes:
|
|
307
|
+
_ = pipe.recv()
|
|
308
|
+
|
|
309
|
+
# Print intermediate info:
|
|
310
|
+
if (zsize.value-pre_zsize >= report) or (zsize.value == zlen):
|
|
311
|
+
report += intsteps
|
|
312
|
+
log.progressbar((zsize.value+1.0-pre_zsize)/(nzchain*nchains))
|
|
313
|
+
|
|
314
|
+
out_of_bounds = np.asarray(outbounds[:])
|
|
315
|
+
chisq = -2*best_log_post.value
|
|
316
|
+
log.msg(
|
|
317
|
+
f"Out-of-bound Trials:\n{out_of_bounds}\n"
|
|
318
|
+
f"Best Parameters: (chisq={chisq:.4f})\n{bestp[ifree]}",
|
|
319
|
+
width=80)
|
|
320
|
+
|
|
321
|
+
# Save intermediate state:
|
|
322
|
+
if savefile is not None:
|
|
323
|
+
ms.update_output(output, chains[0], hsize)
|
|
324
|
+
np.savez(savefile, **output)
|
|
325
|
+
|
|
326
|
+
# Gelman-Rubin statistics:
|
|
327
|
+
if grtest and np.all(chainsize > (zburn+hsize)):
|
|
328
|
+
psrf = ms.gelman_rubin(Z, zchain, zburn)
|
|
329
|
+
log.msg(
|
|
330
|
+
f"Gelman-Rubin statistics for free parameters:\n{psrf}",
|
|
331
|
+
width=80,
|
|
332
|
+
)
|
|
333
|
+
if np.all(psrf < 1.01):
|
|
334
|
+
log.msg("All parameters converged to within 1% of unity.")
|
|
335
|
+
converged = (
|
|
336
|
+
grbreak > 0.0 and
|
|
337
|
+
np.all(psrf < grbreak) and
|
|
338
|
+
zsize.value > grnmin
|
|
339
|
+
)
|
|
340
|
+
if converged:
|
|
341
|
+
with zsize.get_lock():
|
|
342
|
+
zsize.value = zlen
|
|
343
|
+
log.msg(
|
|
344
|
+
"\nAll parameters satisfy the GR convergence "
|
|
345
|
+
f"threshold of {grbreak:g}, stopping the MCMC.")
|
|
346
|
+
break
|
|
347
|
+
if zsize.value == zlen:
|
|
348
|
+
break
|
|
349
|
+
|
|
350
|
+
# Make sure chains finish and release all locks
|
|
351
|
+
for chain in chains:
|
|
352
|
+
chain.join()
|
|
353
|
+
|
|
354
|
+
for chain in chains: # Make sure to terminate the subprocesses
|
|
355
|
+
chain.terminate()
|
|
356
|
+
|
|
357
|
+
# Evaluate model for best fitting parameters:
|
|
358
|
+
posterior = ms.update_output(output, chains[0], hsize)
|
|
359
|
+
|
|
360
|
+
# Print out Summary:
|
|
361
|
+
Z = output['posterior']
|
|
362
|
+
nsample = len(Z)*thinning
|
|
363
|
+
nzsample = len(posterior)
|
|
364
|
+
fmt = len(str(nsample))
|
|
365
|
+
chain_iter = nsample // nchains
|
|
366
|
+
accept_rate = output['acceptance_rate']
|
|
367
|
+
|
|
368
|
+
log.msg('\nMCMC Summary:\n-------------')
|
|
369
|
+
log.msg(
|
|
370
|
+
f"Number of evaluated samples: {nsample:{fmt}d}\n"
|
|
371
|
+
f"Number of parallel chains: {nchains:{fmt}d}\n"
|
|
372
|
+
f"Average iterations per chain: {chain_iter:{fmt}d}\n"
|
|
373
|
+
f"Burned-in iterations per chain: {burnin:{fmt}d}\n"
|
|
374
|
+
f"Thinning factor: {thinning:{fmt}d}\n"
|
|
375
|
+
f"MCMC sample size (thinned, burned): {nzsample:{fmt}d}\n"
|
|
376
|
+
f"Acceptance rate: {accept_rate:.2f}%\n", indent=2)
|
|
377
|
+
|
|
378
|
+
return output
|
mc3/plots/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Copyright (c) 2015-2026 Patricio Cubillos and contributors.
|
|
2
|
+
# mc3 is open-source software under the MIT license (see LICENSE).
|
|
3
|
+
|
|
4
|
+
from .plot_functions import *
|
|
5
|
+
from .posterior import *
|
|
6
|
+
from .colors import *
|
|
7
|
+
|
|
8
|
+
__all__ = (
|
|
9
|
+
plot_functions.__all__
|
|
10
|
+
+ posterior.__all__
|
|
11
|
+
+ colors.__all__
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# Clean up top-level namespace--delete everything that isn't in __all__
|
|
15
|
+
# or is a magic attribute, and that isn't a submodule of this package
|
|
16
|
+
for varname in dir():
|
|
17
|
+
if not ((varname.startswith('__') and varname.endswith('__')) or
|
|
18
|
+
varname in __all__):
|
|
19
|
+
del locals()[varname]
|
|
20
|
+
del(varname)
|