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/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Copyright (c) 2015-2026 Patricio Cubillos and contributors.
|
|
2
|
+
# mc3 is open-source software under the MIT license (see LICENSE).
|
|
3
|
+
|
|
4
|
+
from .sampler_driver import *
|
|
5
|
+
from .fit_driver import *
|
|
6
|
+
from . import plots
|
|
7
|
+
from . import utils
|
|
8
|
+
from . import stats
|
|
9
|
+
from .version import __version__
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
__all__ = (
|
|
13
|
+
sampler_driver.__all__
|
|
14
|
+
+ fit_driver.__all__
|
|
15
|
+
+ ['plots', 'utils', 'stats']
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Clean up top-level namespace--delete everything that isn't in __all__
|
|
20
|
+
# or is a magic attribute, and that isn't a submodule of this package
|
|
21
|
+
for varname in dir():
|
|
22
|
+
if not ((varname.startswith('__') and varname.endswith('__')) or
|
|
23
|
+
varname in __all__):
|
|
24
|
+
del locals()[varname]
|
|
25
|
+
del(varname)
|
mc3/__main__.py
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Copyright (c) 2015-2026 Patricio Cubillos and contributors.
|
|
2
|
+
# mc3 is open-source software under the MIT license (see LICENSE).
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
import warnings
|
|
7
|
+
import argparse
|
|
8
|
+
import configparser
|
|
9
|
+
|
|
10
|
+
import matplotlib
|
|
11
|
+
import matplotlib.pyplot as plt
|
|
12
|
+
|
|
13
|
+
import mc3
|
|
14
|
+
import mc3.utils as mu
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main():
|
|
18
|
+
"""
|
|
19
|
+
Multi-Core Markov-chain Monte Carlo (mc3) wrapper for the command line
|
|
20
|
+
|
|
21
|
+
Notes
|
|
22
|
+
-----
|
|
23
|
+
To display the full list of arguments, run from the prompt:
|
|
24
|
+
mc3 -h
|
|
25
|
+
|
|
26
|
+
Command-line arguments overwrite the config-file arguments if the
|
|
27
|
+
argument is defined twice.
|
|
28
|
+
"""
|
|
29
|
+
parser = parse()
|
|
30
|
+
|
|
31
|
+
# Parse command-line args (right now, just interested in the config file):
|
|
32
|
+
args, unknown = parser.parse_known_args()
|
|
33
|
+
|
|
34
|
+
# Parse configuration file to a dictionary:
|
|
35
|
+
if args.cfile is not None and not os.path.isfile(args.cfile):
|
|
36
|
+
print(f"Configuration file: '{args.cfile}' not found.")
|
|
37
|
+
sys.exit(0)
|
|
38
|
+
if args.cfile:
|
|
39
|
+
config = configparser.ConfigParser()
|
|
40
|
+
config.read([args.cfile])
|
|
41
|
+
defaults = dict(config.items("MCMC"))
|
|
42
|
+
else:
|
|
43
|
+
defaults = {}
|
|
44
|
+
# Set defaults from the configuration-file values:
|
|
45
|
+
parser.set_defaults(**defaults)
|
|
46
|
+
# Overwrite defaults with the command-line arguments:
|
|
47
|
+
args, unknown = parser.parse_known_args()
|
|
48
|
+
delattr(args, 'cfile')
|
|
49
|
+
# Call MCMC driver:
|
|
50
|
+
mc3.sample(**vars(args))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def parse():
|
|
54
|
+
"""mc3 command-line argument parser"""
|
|
55
|
+
# Parse the config file from the command line:
|
|
56
|
+
parser = argparse.ArgumentParser(
|
|
57
|
+
description=__doc__,
|
|
58
|
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
59
|
+
|
|
60
|
+
# Configuration-file option:
|
|
61
|
+
parser.add_argument(
|
|
62
|
+
"-c", "--cfile", help="Configuration file.", metavar="FILE")
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
"-v", "--version", action="version",
|
|
65
|
+
help="Display mc3's version.",
|
|
66
|
+
version=f'mc3 version {mc3.__version__}.')
|
|
67
|
+
# MCMC Options:
|
|
68
|
+
group = parser.add_argument_group("MCMC General Options")
|
|
69
|
+
group.add_argument(
|
|
70
|
+
"--nsamples", dest="nsamples", action="store", type=eval, default=None,
|
|
71
|
+
help="Number of MCMC samples.")
|
|
72
|
+
group.add_argument(
|
|
73
|
+
"--nchains", dest="nchains", action="store", type=int, default=7,
|
|
74
|
+
help="Number of chains [default: %(default)s]")
|
|
75
|
+
group.add_argument(
|
|
76
|
+
"--ncpu", dest="ncpu", action="store", type=int, default=None,
|
|
77
|
+
help="Number of CPUs for the chains [default: nchains+1]")
|
|
78
|
+
group.add_argument(
|
|
79
|
+
"--sampler", dest="sampler", action="store", type=str, default=None,
|
|
80
|
+
help="Sampler algorithm, select from: ['mrw', 'demc', 'snooker'].")
|
|
81
|
+
group.add_argument(
|
|
82
|
+
"--wlike", dest="wlike", action="store", type=eval, default=False,
|
|
83
|
+
help="Calculate the likelihood in a wavelet base "
|
|
84
|
+
"[default: %(default)s]")
|
|
85
|
+
group.add_argument(
|
|
86
|
+
"--leastsq", dest="leastsq", action="store", type=eval, default=None,
|
|
87
|
+
help="If not None, perform a least-squares optimiztion before the "
|
|
88
|
+
"MCMC run. Select from: 'lm': Levenberg-Marquardt (most "
|
|
89
|
+
"efficient, but does not obey bounds); 'trf': Trust Region "
|
|
90
|
+
"Reflective [default: %(default)s].")
|
|
91
|
+
group.add_argument(
|
|
92
|
+
"--chisqscale", dest="chisqscale", action="store",
|
|
93
|
+
type=eval, default=False,
|
|
94
|
+
help="Scale the data uncertainties such that the reduced "
|
|
95
|
+
"chi-squared = 1. [default: %(default)s]")
|
|
96
|
+
group.add_argument(
|
|
97
|
+
"--grtest", dest="grtest", action="store", type=eval, default=False,
|
|
98
|
+
help="Run Gelman-Rubin test [default: %(default)s]")
|
|
99
|
+
group.add_argument(
|
|
100
|
+
"--grbreak", dest="grbreak", action="store", type=float, default=0.0,
|
|
101
|
+
help="Gelman-Rubin convergence threshold to stop the MCMC. I'd "
|
|
102
|
+
"suggest grbreak ~ 1.001 -- 1.005. Do not break if "
|
|
103
|
+
"grbreak=0.0 (default).")
|
|
104
|
+
group.add_argument(
|
|
105
|
+
"--grnmin", dest="grnmin", action="store", type=eval, default=0.5,
|
|
106
|
+
help="Minimum number of valid samples required for grbreak. If grnmin "
|
|
107
|
+
"is integer, require at least grnmin samples to break out of the "
|
|
108
|
+
"MCMC. If grnmin is a float (in the range 0.0--1.0), require at "
|
|
109
|
+
"least grnmin * maximum number of samples to break out of the "
|
|
110
|
+
"MCMC [default: %(default)s]")
|
|
111
|
+
group.add_argument(
|
|
112
|
+
"--burnin", dest="burnin", action="store", type=eval, default=0,
|
|
113
|
+
help="Number of burn-in iterations (per chain) [default: %(default)s]")
|
|
114
|
+
group.add_argument(
|
|
115
|
+
"--thinning", dest="thinning", action="store", type=int, default=1,
|
|
116
|
+
help="Chains thinning factor (use every thinning-th iteration) for GR "
|
|
117
|
+
"test and plots [default: %(default)s]")
|
|
118
|
+
group.add_argument(
|
|
119
|
+
"--fgamma", dest="fgamma", action="store", type=float, default=1.0,
|
|
120
|
+
help="Scaling factor for DEMC's gamma [default: %(default)s]")
|
|
121
|
+
group.add_argument(
|
|
122
|
+
"--fepsilon", dest="fepsilon", action="store", type=float, default=0.0,
|
|
123
|
+
help="Scaling factor for DEMC's support distribution "
|
|
124
|
+
"[default: %(default)s]")
|
|
125
|
+
group.add_argument(
|
|
126
|
+
"--hsize", dest="hsize", action="store", type=int, default=10,
|
|
127
|
+
help="Number of initial samples per chain [default: %(default)s]")
|
|
128
|
+
group.add_argument(
|
|
129
|
+
"--kickoff", dest="kickoff", action="store", type=str, default="normal",
|
|
130
|
+
help="Chain's starter mode, select between: ['normal', 'uniform']. "
|
|
131
|
+
"[default: %(default)s]")
|
|
132
|
+
group.add_argument(
|
|
133
|
+
"--plots", dest="plots", action="store", type=eval, default=False,
|
|
134
|
+
help="If True, generate output figures. [default: %(default)s]")
|
|
135
|
+
group.add_argument(
|
|
136
|
+
"--theme", dest="theme", action="store", type=str, default='blue',
|
|
137
|
+
help="Color theme for plots, can be any color format recognized "
|
|
138
|
+
"by matplotlib. [default: %(default)s]")
|
|
139
|
+
group.add_argument(
|
|
140
|
+
"--statistics", dest="statistics", action="store", type=str,
|
|
141
|
+
default='med_central',
|
|
142
|
+
help="Which statistics to adopt for the plots, select between: "
|
|
143
|
+
"'med_central' (default, median and central quantile statistics), "
|
|
144
|
+
"'max_like' (mode and HPD statistics), or 'global_max_like' "
|
|
145
|
+
"(best-fit and HPD statistics).")
|
|
146
|
+
group.add_argument(
|
|
147
|
+
"--ioff", dest="ioff", action="store", type=eval, default=False,
|
|
148
|
+
help="If True, set plt.ioff(), i.e., do not display figures on screen "
|
|
149
|
+
"[default: %(default)s]")
|
|
150
|
+
group.add_argument(
|
|
151
|
+
"--showbp", dest="showbp", action="store", type=eval, default=True,
|
|
152
|
+
help="If True, show best-fitting values in histogram and pairwise "
|
|
153
|
+
"plots [default: %(default)s]")
|
|
154
|
+
group.add_argument(
|
|
155
|
+
"--savefile", dest="savefile", action="store", type=str, default=None,
|
|
156
|
+
help="Output npz filename to store the parameter posterior "
|
|
157
|
+
"distributions [default: %(default)s]")
|
|
158
|
+
group.add_argument(
|
|
159
|
+
"-r", "--resume", dest="resume", action="store_true", default=False,
|
|
160
|
+
help="If set, resume a previous run (load output).")
|
|
161
|
+
group.add_argument(
|
|
162
|
+
"--rms", dest="rms", action="store", type=eval, default=False,
|
|
163
|
+
help="If True, calculate RMS-vs-binsize of data--bestmodel residuals "
|
|
164
|
+
"[default: %(default)s]")
|
|
165
|
+
group.add_argument(
|
|
166
|
+
"--log", dest="log", action="store", type=str, default=None,
|
|
167
|
+
help="Output log filename.")
|
|
168
|
+
group.add_argument(
|
|
169
|
+
"--pnames", dest="pnames", action="store", type=mu.parray, default=None,
|
|
170
|
+
help="List of parameter names for screen output (and figures if "
|
|
171
|
+
"texnames is not defined). If pnames is not defined, default "
|
|
172
|
+
"to texnames.")
|
|
173
|
+
group.add_argument(
|
|
174
|
+
"--texnames", dest="texnames", action="store", type=mu.parray,
|
|
175
|
+
default=None,
|
|
176
|
+
help="List of parameter names for figures (may use latex syntax). "
|
|
177
|
+
"[default: None]")
|
|
178
|
+
# Fitting-parameter Options:
|
|
179
|
+
group = parser.add_argument_group("Fitting-function Options")
|
|
180
|
+
group.add_argument(
|
|
181
|
+
"--func", dest="func", action="store", type=mu.parray, default=None,
|
|
182
|
+
help="List of strings with the function name, module "
|
|
183
|
+
"name, and path-to-module [required]")
|
|
184
|
+
group.add_argument(
|
|
185
|
+
"--params", dest="params", action="store", type=mu.parray, default=None,
|
|
186
|
+
help="Filename or list of initial-guess model-fitting "
|
|
187
|
+
"parameter [required]")
|
|
188
|
+
group.add_argument(
|
|
189
|
+
"--pmin", dest="pmin", action="store", type=mu.parray, default=None,
|
|
190
|
+
help="Filename or list of parameter lower boundaries "
|
|
191
|
+
"[default: -inf for each parameter]")
|
|
192
|
+
group.add_argument(
|
|
193
|
+
"--pmax", dest="pmax", action="store", type=mu.parray, default=None,
|
|
194
|
+
help="Filename or list of parameter upper boundaries "
|
|
195
|
+
"[default: +inf for each parameter]")
|
|
196
|
+
group.add_argument(
|
|
197
|
+
"--pstep", dest="pstep", action="store", type=mu.parray, default=None,
|
|
198
|
+
help="Parameter stepping [required]. Additionally, parameters with "
|
|
199
|
+
"pstep=0 are fixed, parameters with negative pstep are "
|
|
200
|
+
"shared (see documentation).")
|
|
201
|
+
group.add_argument(
|
|
202
|
+
"--indparams", dest="indparams", action="store",
|
|
203
|
+
type=mu.parray, default=[],
|
|
204
|
+
help="Filename or list with independent parameters for func "
|
|
205
|
+
"[default: None]")
|
|
206
|
+
group.add_argument(
|
|
207
|
+
"--indparams_dict", dest="indparams_dict", action="store",
|
|
208
|
+
type=dict, default={},
|
|
209
|
+
help="Dictionary with independent keyword parameters for func "
|
|
210
|
+
"[default: {}]")
|
|
211
|
+
# Data Options:
|
|
212
|
+
group = parser.add_argument_group("Data Options")
|
|
213
|
+
group.add_argument(
|
|
214
|
+
"--data", dest="data", action="store", type=mu.parray, default=None,
|
|
215
|
+
help="Filename or array of the data being fitted [required]")
|
|
216
|
+
group.add_argument(
|
|
217
|
+
"--uncert", dest="uncert", action="store", type=mu.parray, default=None,
|
|
218
|
+
help="Filemane or array with the data uncertainties [required]")
|
|
219
|
+
group.add_argument(
|
|
220
|
+
"--prior", dest="prior", action="store", type=mu.parray, default=None,
|
|
221
|
+
help="Filename or array with parameter prior estimates "
|
|
222
|
+
"[default: %(default)s]")
|
|
223
|
+
group.add_argument(
|
|
224
|
+
"--priorlow", dest="priorlow", action="store", type=mu.parray,
|
|
225
|
+
default=None,
|
|
226
|
+
help="Filename or array with prior lower uncertainties "
|
|
227
|
+
"[default: %(default)s]")
|
|
228
|
+
group.add_argument(
|
|
229
|
+
"--priorup", dest="priorup", action="store", type=mu.parray,
|
|
230
|
+
default=None,
|
|
231
|
+
help="Filename or array with prior upper uncertainties "
|
|
232
|
+
"[default: %(default)s]")
|
|
233
|
+
return parser
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
if __name__ == "__main__":
|
|
237
|
+
matplotlib.use('Agg')
|
|
238
|
+
plt.ioff()
|
|
239
|
+
warnings.simplefilter("ignore", RuntimeWarning)
|
|
240
|
+
main()
|
mc3/chain.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# Copyright (c) 2015-2026 Patricio Cubillos and contributors.
|
|
2
|
+
# mc3 is open-source software under the MIT license (see LICENSE).
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
import warnings
|
|
6
|
+
import random
|
|
7
|
+
import multiprocessing as mp
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from . import stats as ms
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Ingnore RuntimeWarnings:
|
|
15
|
+
warnings.simplefilter("ignore", RuntimeWarning)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Chain(mp.get_context('fork').Process):
|
|
19
|
+
"""
|
|
20
|
+
Background process. This guy evaluates the model and calculates chisq.
|
|
21
|
+
"""
|
|
22
|
+
def __init__(self, func, args, kwargs, pipe, data, uncert,
|
|
23
|
+
params, freepars, pstep, pmin, pmax,
|
|
24
|
+
sampler, wlike, prior, priorlow, priorup, thinning,
|
|
25
|
+
fgamma, fepsilon, Z, zsize, log_post, zchain, M0,
|
|
26
|
+
numaccept, outbounds, ncpp,
|
|
27
|
+
chainsize, bestp, best_log_post, ID, ncpu, **kwds):
|
|
28
|
+
"""
|
|
29
|
+
Chain class initializer.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
func: Callable
|
|
34
|
+
Model fitting function.
|
|
35
|
+
args: List
|
|
36
|
+
Additional arguments for func (besides the fitting parameters).
|
|
37
|
+
kwargs: Dict
|
|
38
|
+
Additional keyword arguments for func (if needed).
|
|
39
|
+
pipe: multiprocessing.Pipe object
|
|
40
|
+
Pipe to communicate with mcmc.
|
|
41
|
+
data: 1D shared-ctypes float ndarray
|
|
42
|
+
Dependent data fitted by func.
|
|
43
|
+
uncert: 1D Shared ctypes float ndarray
|
|
44
|
+
Uncertainty of data.
|
|
45
|
+
params: 1D float array
|
|
46
|
+
Array of model parameters (including fixed and shared).
|
|
47
|
+
freepars: 2D shared-ctypes float ndarray
|
|
48
|
+
Current state of fitting parameters (X, as in Braak & Vrugt 2008).
|
|
49
|
+
pstep: 1D float ndarray
|
|
50
|
+
Proposal jump scale.
|
|
51
|
+
pmin: 1D float ndarray
|
|
52
|
+
Lower boundaries of the posteriors.
|
|
53
|
+
pmax: 1D float ndarray
|
|
54
|
+
Upper boundaries of the posteriors.
|
|
55
|
+
sampler: String
|
|
56
|
+
Flag to indicate wich MCMC algorithm to use [mrw, demc, snooker].
|
|
57
|
+
wlike: Boolean
|
|
58
|
+
Flag to use a wavelet-based likelihood function (True) or not (False).
|
|
59
|
+
prior: 1D float ndarray
|
|
60
|
+
Parameter prior.
|
|
61
|
+
priorlow: 1D float ndarray
|
|
62
|
+
Prior lower uncertainties.
|
|
63
|
+
priorup: 1D float ndarray
|
|
64
|
+
Prior uppper uncertainties.
|
|
65
|
+
thinning: Integer
|
|
66
|
+
Thinning factor of the chains.
|
|
67
|
+
fgamma: Float
|
|
68
|
+
Proposals jump scale factor for DEMC's gamma.
|
|
69
|
+
The code computes: gamma = fgamma * 2.38 / sqrt(2*Nfree)
|
|
70
|
+
fepsilon: Float
|
|
71
|
+
Jump scale factor for DEMC's support distribution.
|
|
72
|
+
The code computes: e = fepsilon * Normal(0, pstep)
|
|
73
|
+
Z: 2D shared-ctype float ndarray
|
|
74
|
+
MCMC parameters history (Z, as in Braak & Vrugt 2008).
|
|
75
|
+
zsize: Shared ctypes integer
|
|
76
|
+
Current number of samples in the Z array.
|
|
77
|
+
log_post: Float multiprocessing.Array
|
|
78
|
+
log(posterior) values for the samples in Z.
|
|
79
|
+
zchain: multiprocessing.Array integer
|
|
80
|
+
Chain ID for the given state in the Z array.
|
|
81
|
+
M0: Integer
|
|
82
|
+
Initial number of samples in the Z array.
|
|
83
|
+
numaccept: multiprocessing.Value integer
|
|
84
|
+
Number of accepted MCMC proposals
|
|
85
|
+
outbounds: 1D shared multiprocessing integer Array
|
|
86
|
+
Number of out-of-bound proposals per free parameter.
|
|
87
|
+
ncpp: Integer
|
|
88
|
+
Number of chains for this process.
|
|
89
|
+
chainsize: multiprocessing.Array integer
|
|
90
|
+
The current length of this chain.
|
|
91
|
+
bestp: Shared ctypes float array
|
|
92
|
+
The array with the current best-fitting parameter.
|
|
93
|
+
best_log_post: Float multiprocessing.Value
|
|
94
|
+
The log(posterior) value for bestp.
|
|
95
|
+
ID: Integer
|
|
96
|
+
Identification serial number for this chain.
|
|
97
|
+
ncpu: Integer
|
|
98
|
+
The number of processes running chains.
|
|
99
|
+
"""
|
|
100
|
+
# Multiprocessing setup:
|
|
101
|
+
mp.Process.__init__(self, **kwds)
|
|
102
|
+
self.daemon = True
|
|
103
|
+
self.ID = ID
|
|
104
|
+
self.ncpp = ncpp
|
|
105
|
+
self.ncpu = ncpu
|
|
106
|
+
# MCMC setup:
|
|
107
|
+
self.sampler = sampler
|
|
108
|
+
self.thinning = thinning
|
|
109
|
+
self.fgamma = fgamma
|
|
110
|
+
self.fepsilon = fepsilon
|
|
111
|
+
self.Z = Z
|
|
112
|
+
self.zsize = zsize
|
|
113
|
+
self.log_post = log_post
|
|
114
|
+
self.zchain = zchain
|
|
115
|
+
self.chainsize = chainsize
|
|
116
|
+
self.M0 = M0
|
|
117
|
+
self.numaccept = numaccept
|
|
118
|
+
self.outbounds = outbounds
|
|
119
|
+
# Best values:
|
|
120
|
+
self.bestp = bestp
|
|
121
|
+
self.best_log_post = best_log_post
|
|
122
|
+
# Modeling function:
|
|
123
|
+
self.func = func
|
|
124
|
+
self.args = args
|
|
125
|
+
self.kwargs = kwargs
|
|
126
|
+
# Model, fitting, and shared parameters:
|
|
127
|
+
self.params = params
|
|
128
|
+
self.freepars = freepars
|
|
129
|
+
self.pstep = pstep
|
|
130
|
+
self.pmin = pmin
|
|
131
|
+
self.pmax = pmax
|
|
132
|
+
# Input/output Pipe:
|
|
133
|
+
self.pipe = pipe
|
|
134
|
+
# Data:
|
|
135
|
+
self.data = data
|
|
136
|
+
self.uncert = uncert
|
|
137
|
+
# Chisq function:
|
|
138
|
+
self.wlike = wlike
|
|
139
|
+
|
|
140
|
+
# Index of parameters:
|
|
141
|
+
self.ishare = np.where(self.pstep < 0)[0]
|
|
142
|
+
self.ifree = np.where(self.pstep > 0)[0]
|
|
143
|
+
|
|
144
|
+
# Keep only the priors that count:
|
|
145
|
+
self.prior = prior
|
|
146
|
+
self.priorlow = priorlow
|
|
147
|
+
self.priorup = priorup
|
|
148
|
+
|
|
149
|
+
# Size of variables:
|
|
150
|
+
self.nfree = np.sum(self.pstep > 0) # Number of free parameters
|
|
151
|
+
self.nchains = np.shape(self.freepars)[0]
|
|
152
|
+
self.Zlen = np.shape(Z)[0]
|
|
153
|
+
|
|
154
|
+
# Length of mrw/demc chains:
|
|
155
|
+
self.chainlen = int((self.Zlen) / self.nchains)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def run(self):
|
|
159
|
+
"""
|
|
160
|
+
Process the requests queue until terminated.
|
|
161
|
+
"""
|
|
162
|
+
# Indices in Z-array to start this chains:
|
|
163
|
+
IDs = np.arange(self.ID, self.nchains, self.ncpu)
|
|
164
|
+
self.index = self.M0 + IDs
|
|
165
|
+
for j in range(self.ncpp):
|
|
166
|
+
if np.any(self.zchain==self.ID): # (i.e., resume=True)
|
|
167
|
+
# Set ID to the last iteration for this chain:
|
|
168
|
+
IDs[j] = self.index[j] = np.where(self.zchain==IDs[j])[0][-1]
|
|
169
|
+
self.freepars[self.ID + j*self.ncpu] = np.copy(self.Z[IDs[j]])
|
|
170
|
+
chisq = -2*self.log_post[IDs]
|
|
171
|
+
|
|
172
|
+
nextp = np.copy(self.params) # Array for proposed sample
|
|
173
|
+
nextchisq = 0.0 # Chi-square of nextp
|
|
174
|
+
njump = 0 # Number of jumps since last Z-update
|
|
175
|
+
gamma = self.fgamma * 2.38 / np.sqrt(2*self.nfree)
|
|
176
|
+
|
|
177
|
+
# The numpy random system must have its seed reinitialized in
|
|
178
|
+
# each sub-processes to avoid identical 'random' steps.
|
|
179
|
+
# random.randomint is process- and thread-safe.
|
|
180
|
+
np.random.seed(random.randint(0, 100000))
|
|
181
|
+
|
|
182
|
+
# Run until completing the Z array:
|
|
183
|
+
while True:
|
|
184
|
+
njump += 1
|
|
185
|
+
normal = np.random.normal(0, self.pstep[self.ifree], self.nfree)
|
|
186
|
+
|
|
187
|
+
if self.sampler == "demc":
|
|
188
|
+
b = self.pipe.recv() # Synchronization flag
|
|
189
|
+
|
|
190
|
+
for j in range(self.ncpp):
|
|
191
|
+
ID = self.ID + j*self.ncpu
|
|
192
|
+
mrfactor = 1.0
|
|
193
|
+
|
|
194
|
+
# Algorithm-specific proposals jumps:
|
|
195
|
+
if self.sampler == "snooker":
|
|
196
|
+
# Sampling without replacement (0 <= iR1 != iR2 < zsize):
|
|
197
|
+
iR1 = np.random.randint(0, self.zsize.value)
|
|
198
|
+
iR2 = np.random.randint(1, self.zsize.value)
|
|
199
|
+
if iR2 == iR1:
|
|
200
|
+
iR2 = 0
|
|
201
|
+
sjump = np.random.uniform() < 0.1
|
|
202
|
+
if sjump: # Snooker update:
|
|
203
|
+
iz = np.random.randint(self.zsize.value)
|
|
204
|
+
z = self.Z[iz] # Not to confuse with Z!
|
|
205
|
+
if np.all(z == self.freepars[ID]): # Do not project:
|
|
206
|
+
jump = np.random.uniform(1.2, 2.2) \
|
|
207
|
+
* (self.Z[iR2]-self.Z[iR1])
|
|
208
|
+
else:
|
|
209
|
+
dz = self.freepars[ID] - z
|
|
210
|
+
zp1 = np.dot(self.Z[iR1], dz)
|
|
211
|
+
zp2 = np.dot(self.Z[iR2], dz)
|
|
212
|
+
jump = np.random.uniform(1.2, 2.2) * (zp1-zp2) \
|
|
213
|
+
* dz/np.dot(dz,dz)
|
|
214
|
+
else: # Z update:
|
|
215
|
+
jump = \
|
|
216
|
+
gamma*(self.Z[iR1] - self.Z[iR2]) \
|
|
217
|
+
+ self.fepsilon*normal
|
|
218
|
+
|
|
219
|
+
elif self.sampler == "mrw":
|
|
220
|
+
jump = normal
|
|
221
|
+
elif self.sampler == "demc":
|
|
222
|
+
# Select r1, r2 such that: r1 != r2 != ID:
|
|
223
|
+
r1 = np.random.randint(1, self.nchains)
|
|
224
|
+
if r1 == ID:
|
|
225
|
+
r1 = 0
|
|
226
|
+
# Pick r2 without replacement:
|
|
227
|
+
r2 = (r1 + np.random.randint(2, self.nchains))%self.nchains
|
|
228
|
+
if r2 == ID:
|
|
229
|
+
r2 = (r1 + 1) % self.nchains
|
|
230
|
+
jump = \
|
|
231
|
+
gamma*(self.freepars[r1] - self.freepars[r2]) \
|
|
232
|
+
+ self.fepsilon*normal
|
|
233
|
+
|
|
234
|
+
# Propose next point:
|
|
235
|
+
nextp[self.ifree] = np.copy(self.freepars[ID]) + jump
|
|
236
|
+
|
|
237
|
+
# Check boundaries:
|
|
238
|
+
outpars = np.asarray(
|
|
239
|
+
((nextp < self.pmin) | (nextp > self.pmax))[self.ifree])
|
|
240
|
+
# If any parameter lied out of bounds, skip model evaluation:
|
|
241
|
+
if np.any(outpars):
|
|
242
|
+
with self.outbounds.get_lock():
|
|
243
|
+
self.outbounds[:] += outpars
|
|
244
|
+
else:
|
|
245
|
+
# Update shared parameters:
|
|
246
|
+
for s in self.ishare:
|
|
247
|
+
nextp[s] = nextp[-int(self.pstep[s])-1]
|
|
248
|
+
# Evaluate model:
|
|
249
|
+
nextchisq = self.eval_model(nextp, ret="chisq")
|
|
250
|
+
# Additional factor in Metropolis ratio for Snooker jump:
|
|
251
|
+
if self.sampler == "snooker" and sjump:
|
|
252
|
+
# squared norm of current and next:
|
|
253
|
+
cnorm = np.dot(self.freepars[ID]-z, self.freepars[ID]-z)
|
|
254
|
+
nnorm = np.dot(nextp[self.ifree]-z, nextp[self.ifree]-z)
|
|
255
|
+
mrfactor = (nnorm/cnorm)**(0.5*(self.nfree-1))
|
|
256
|
+
# Evaluate the Metropolis ratio:
|
|
257
|
+
metro = (
|
|
258
|
+
np.exp(0.5*(chisq[j]-nextchisq)) * mrfactor
|
|
259
|
+
> np.random.uniform()
|
|
260
|
+
)
|
|
261
|
+
if metro:
|
|
262
|
+
# Update freepars[ID]:
|
|
263
|
+
self.freepars[ID] = np.copy(nextp[self.ifree])
|
|
264
|
+
chisq[j] = nextchisq
|
|
265
|
+
with self.numaccept.get_lock():
|
|
266
|
+
self.numaccept.value += 1
|
|
267
|
+
# Check lowest chi-square:
|
|
268
|
+
with self.best_log_post.get_lock():
|
|
269
|
+
if chisq[j] < -2*self.best_log_post.value:
|
|
270
|
+
self.bestp[self.ifree] = self.freepars[ID]
|
|
271
|
+
for s in self.ishare:
|
|
272
|
+
k = -int(self.pstep[s]) - 1
|
|
273
|
+
self.bestp[s] = self.bestp[k]
|
|
274
|
+
self.best_log_post.value = -0.5*chisq[j]
|
|
275
|
+
# Update Z if necessary:
|
|
276
|
+
if njump == self.thinning:
|
|
277
|
+
with self.zsize.get_lock():
|
|
278
|
+
# Stop when we fill Z:
|
|
279
|
+
if self.zsize.value == self.Zlen:
|
|
280
|
+
return
|
|
281
|
+
if self.sampler == "snooker":
|
|
282
|
+
self.index[j] = self.zsize.value
|
|
283
|
+
self.zsize.value += 1
|
|
284
|
+
# Update values:
|
|
285
|
+
self.zchain[self.index[j]] = ID
|
|
286
|
+
self.Z[self.index[j]] = np.copy(self.freepars[ID])
|
|
287
|
+
self.log_post[self.index[j]] = -0.5*chisq[j]
|
|
288
|
+
self.index[j] += self.nchains
|
|
289
|
+
self.chainsize[ID] += 1
|
|
290
|
+
|
|
291
|
+
if njump == self.thinning:
|
|
292
|
+
njump = 0 # Reset njump
|
|
293
|
+
|
|
294
|
+
if self.sampler == "demc":
|
|
295
|
+
self.pipe.send(chisq[j])
|
|
296
|
+
# Stop when the chain is complete:
|
|
297
|
+
if self.sampler in ["mrw", "demc"] \
|
|
298
|
+
and self.chainsize[0]==self.chainlen:
|
|
299
|
+
return
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def eval_model(self, params, ret="model"):
|
|
303
|
+
"""
|
|
304
|
+
Evaluate the model for the requested set of parameters.
|
|
305
|
+
|
|
306
|
+
Parameters
|
|
307
|
+
----------
|
|
308
|
+
params: 1D float ndarray
|
|
309
|
+
The set of model fitting parameters.
|
|
310
|
+
ret: String
|
|
311
|
+
Flag to indicate what to return. Valid options:
|
|
312
|
+
- 'model' Return the evaluated model.
|
|
313
|
+
- 'chisq' Return chi-square.
|
|
314
|
+
- 'both' Return a list with the model and chisq.
|
|
315
|
+
"""
|
|
316
|
+
if self.wlike:
|
|
317
|
+
model = self.func(params[0:-3], *self.args, **self.kwargs)
|
|
318
|
+
else:
|
|
319
|
+
model = self.func(params, *self.args, **self.kwargs)
|
|
320
|
+
|
|
321
|
+
# Reject proposed iteration if any model value is infinite:
|
|
322
|
+
if np.any(model == np.inf):
|
|
323
|
+
chisq = np.inf
|
|
324
|
+
else:
|
|
325
|
+
# Calculate chisq:
|
|
326
|
+
if self.wlike:
|
|
327
|
+
chisq = ms.dwt_chisq(
|
|
328
|
+
model, self.data, params,
|
|
329
|
+
self.prior, self.priorlow, self.priorup)
|
|
330
|
+
else:
|
|
331
|
+
chisq = ms.chisq(
|
|
332
|
+
model, self.data, self.uncert, params,
|
|
333
|
+
self.prior, self.priorlow, self.priorup)
|
|
334
|
+
|
|
335
|
+
if ret == "both":
|
|
336
|
+
return [model, chisq]
|
|
337
|
+
elif ret == "chisq":
|
|
338
|
+
return chisq
|
|
339
|
+
else: # ret == "model"
|
|
340
|
+
return model
|