hoppMCMC 2.0.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.
- hoppMCMC/__init__.py +734 -0
- hoppMCMC/doc/hoppMCMC_manual.pdf +0 -0
- hoppMCMC/examples/example1.py +31 -0
- hoppMCMC/examples/example2.py +87 -0
- hoppMCMC/examples/example3.py +76 -0
- hoppmcmc-2.0.0.dist-info/METADATA +75 -0
- hoppmcmc-2.0.0.dist-info/RECORD +12 -0
- hoppmcmc-2.0.0.dist-info/WHEEL +5 -0
- hoppmcmc-2.0.0.dist-info/licenses/LICENSE +675 -0
- hoppmcmc-2.0.0.dist-info/top_level.txt +3 -0
- inferFun/__init__.py +549 -0
- myMPI/__init__.py +112 -0
hoppMCMC/__init__.py
ADDED
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
adaptive basin-hopping Markov-chain Monte Carlo for Bayesian optimisation
|
|
4
|
+
|
|
5
|
+
This is the python (v2.7) implementation of the hoppMCMC algorithm aiming to identify and sample from the high-probability regions of a posterior distribution. The algorithm combines three strategies: (i) parallel MCMC, (ii) adaptive Gibbs sampling and (iii) simulated annealing. Overall, hoppMCMC resembles the basin-hopping algorithm implemented in the optimize module of scipy, but it is developed for a wide range of modelling approaches including stochastic models with or without time-delay.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__version__ = '2.0.0'
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import numpy
|
|
14
|
+
from struct import *
|
|
15
|
+
from scipy.stats import ttest_1samp as ttest
|
|
16
|
+
import myMPI as myMPI
|
|
17
|
+
|
|
18
|
+
def Abort(str):
|
|
19
|
+
print("ERROR: "+str)
|
|
20
|
+
myMPI.MPI.COMM_WORLD.Abort(1)
|
|
21
|
+
|
|
22
|
+
EPS_PULSE_VAR_MIN = 1e-12
|
|
23
|
+
EPS_VARMAT_MIN = 1e-7
|
|
24
|
+
EPS_VARMAT_MAX = 1e7
|
|
25
|
+
|
|
26
|
+
class errorMCMC(Exception):
|
|
27
|
+
def __init__(self, value):
|
|
28
|
+
self.value = value
|
|
29
|
+
def __str__(self):
|
|
30
|
+
return repr(self.value)
|
|
31
|
+
|
|
32
|
+
class binfile():
|
|
33
|
+
def __init__(self,fname,mode,rowsize=1):
|
|
34
|
+
self.headsize = 4
|
|
35
|
+
self.bitsize = 8
|
|
36
|
+
self.fname = fname
|
|
37
|
+
self.mode = mode
|
|
38
|
+
self.rowsize = rowsize
|
|
39
|
+
if self.mode=='r':
|
|
40
|
+
try:
|
|
41
|
+
self.f = open(self.fname,"rb")
|
|
42
|
+
except IOError:
|
|
43
|
+
Abort("File not found: "+self.fname)
|
|
44
|
+
self.rowsize = unpack('<i',self.f.read(self.headsize))[0]
|
|
45
|
+
elif self.mode=='w':
|
|
46
|
+
try:
|
|
47
|
+
self.f = open(self.fname,"w+b")
|
|
48
|
+
except IOError:
|
|
49
|
+
Abort("File not found: "+self.fname)
|
|
50
|
+
tmp = self.f.read(self.headsize)
|
|
51
|
+
if not tmp:
|
|
52
|
+
self.f.write(pack('<i',self.rowsize))
|
|
53
|
+
else:
|
|
54
|
+
self.rowsize = unpack('<i',tmp)[0]
|
|
55
|
+
else:
|
|
56
|
+
Abort("Wrong i/o mode: "+self.mode)
|
|
57
|
+
self.fmt = '<'+'d'*self.rowsize
|
|
58
|
+
self.size = self.bitsize*self.rowsize
|
|
59
|
+
print("Success: %s opened for %s size %d double rows" %(self.fname,"reading" if self.mode=='r' else "writing",self.rowsize))
|
|
60
|
+
# ---
|
|
61
|
+
def writeRow(self,row):
|
|
62
|
+
self.f.write(pack(self.fmt,*row))
|
|
63
|
+
self.f.flush()
|
|
64
|
+
# ---
|
|
65
|
+
def readRows(self):
|
|
66
|
+
self.f.seek(0,os.SEEK_END)
|
|
67
|
+
filesize = self.f.tell()
|
|
68
|
+
self.f.seek(self.headsize)
|
|
69
|
+
tmp=self.f.read(filesize-self.headsize)
|
|
70
|
+
ret=numpy.array(unpack('<'+'d'*int(numpy.floor((filesize-self.headsize)/self.bitsize)),tmp),dtype=numpy.float64).reshape((int(numpy.floor(((filesize-self.headsize)/self.bitsize)/self.rowsize)),self.rowsize))
|
|
71
|
+
return ret
|
|
72
|
+
# ---
|
|
73
|
+
def close(self):
|
|
74
|
+
self.f.close()
|
|
75
|
+
print("Success: %s closed" %(self.fname if self.fname else "file"))
|
|
76
|
+
|
|
77
|
+
def readFile(filename):
|
|
78
|
+
"""
|
|
79
|
+
Reads a binary output file and returns all rows/columns
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
|
|
84
|
+
filename:
|
|
85
|
+
name of the output file
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
|
|
90
|
+
a numpy array with all the rows/columns
|
|
91
|
+
|
|
92
|
+
"""
|
|
93
|
+
a=binfile(filename,"r")
|
|
94
|
+
b=a.readRows()
|
|
95
|
+
a.close()
|
|
96
|
+
return b
|
|
97
|
+
|
|
98
|
+
def parMin(parmat):
|
|
99
|
+
prm = min(parmat[:,0])
|
|
100
|
+
prmi = numpy.where(parmat[:,0]==prm)[0][0]
|
|
101
|
+
return {'i':prmi,'f':prm}
|
|
102
|
+
|
|
103
|
+
def diagdot(mat,vec):
|
|
104
|
+
for n in numpy.arange(mat.shape[0]):
|
|
105
|
+
mat[n,n] *= vec[n]
|
|
106
|
+
|
|
107
|
+
def covariance(mat):
|
|
108
|
+
return numpy.cov(mat,rowvar=False)
|
|
109
|
+
|
|
110
|
+
def coefVar(mat):
|
|
111
|
+
mean0 = 1.0/numpy.mean(mat,0)
|
|
112
|
+
return mean0*(numpy.cov(mat,rowvar=False).T*mean0).T
|
|
113
|
+
|
|
114
|
+
def sensVar(mat):
|
|
115
|
+
mean0 = numpy.mean(mat,0)
|
|
116
|
+
return mean0*(numpy.linalg.inv(numpy.cov(mat,rowvar=False)).T*mean0).T
|
|
117
|
+
|
|
118
|
+
cov = covariance
|
|
119
|
+
rnorm = numpy.random.multivariate_normal
|
|
120
|
+
determinant = numpy.linalg.det
|
|
121
|
+
|
|
122
|
+
def join(a,s):
|
|
123
|
+
return s.join(["%.16g" %(x) for x in a])
|
|
124
|
+
|
|
125
|
+
def logsumexp(x):
|
|
126
|
+
a = numpy.max(x)
|
|
127
|
+
return a+numpy.log(numpy.sum(numpy.exp(x-a)))
|
|
128
|
+
|
|
129
|
+
def compareAUC(parmat0,parmat1,T):
|
|
130
|
+
from scipy.stats import gaussian_kde
|
|
131
|
+
parmats = numpy.vstack((parmat0,parmat1))
|
|
132
|
+
parmat0cp = parmat0[:,numpy.var(parmats,axis=0)!=0].copy()
|
|
133
|
+
parmat1cp = parmat1[:,numpy.var(parmats,axis=0)!=0].copy()
|
|
134
|
+
parmats = parmats[:,numpy.var(parmats,axis=0)!=0]
|
|
135
|
+
try:
|
|
136
|
+
kde = gaussian_kde(parmats[:,1:].T)
|
|
137
|
+
wt1 = numpy.array([kde.evaluate(pr) for pr in parmat1cp[:, 1:]])
|
|
138
|
+
except:
|
|
139
|
+
print("Warning: Problem encountered in compareAUC!")
|
|
140
|
+
return {'acc':0, 'favg0':0, 'favg1':0}
|
|
141
|
+
try:
|
|
142
|
+
wt0 = numpy.array([kde.evaluate(pr) for pr in parmat0cp[:, 1:]])
|
|
143
|
+
except:
|
|
144
|
+
print("Warning: Problem encountered in compareAUC!")
|
|
145
|
+
return {'acc':1, 'favg0':0, 'favg1':0}
|
|
146
|
+
mn = numpy.min([wt0,wt1])
|
|
147
|
+
wt0 /= mn
|
|
148
|
+
wt1 /= mn
|
|
149
|
+
# Importance sampling for Monte Carlo integration:
|
|
150
|
+
favg0 = numpy.mean([numpy.exp(-parmat0cp[m,0]/T) / wt0[m] for m in range(parmat0cp.shape[0])])
|
|
151
|
+
favg1 = numpy.mean([numpy.exp(-parmat1cp[m,0]/T) / wt1[m] for m in range(parmat1cp.shape[0])])
|
|
152
|
+
acc = (not numpy.isnan(favg0) and not numpy.isnan(favg1) and
|
|
153
|
+
favg0 > 0 and favg1 >= 0 and
|
|
154
|
+
(favg1 >= favg0 or
|
|
155
|
+
(numpy.random.uniform() < (favg1/favg0))))
|
|
156
|
+
return {'acc':acc, 'favg0':favg0, 'favg1':favg1}
|
|
157
|
+
|
|
158
|
+
def anneal_exp(y0,y1,steps):
|
|
159
|
+
return y0*numpy.exp(-(numpy.arange(steps,dtype=numpy.float64)/(steps-1.0))*numpy.log(numpy.float64(y0)/y1))
|
|
160
|
+
|
|
161
|
+
def anneal_linear(y0,y1,steps):
|
|
162
|
+
return numpy.append(numpy.arange(y0,y1,(y1-y0)/(steps-1),dtype=numpy.float64),y1)
|
|
163
|
+
|
|
164
|
+
def anneal_sigma(y0,y1,steps):
|
|
165
|
+
return y0+(y1-y0)*(1.0-1.0/(1.0+numpy.exp(-(numpy.arange(steps)-(0.5*steps)))))
|
|
166
|
+
|
|
167
|
+
def anneal_sigmasoft(y0,y1,steps):
|
|
168
|
+
return y0+(y1-y0)*(1.0-1.0/(1.0+numpy.exp(-12.5*(numpy.arange(steps)-(0.5*steps))/steps)))
|
|
169
|
+
|
|
170
|
+
def ldet(mat):
|
|
171
|
+
try:
|
|
172
|
+
det = determinant(mat)
|
|
173
|
+
except:
|
|
174
|
+
return -numpy.Inf
|
|
175
|
+
if det==0:
|
|
176
|
+
return -numpy.Inf
|
|
177
|
+
else:
|
|
178
|
+
return numpy.log(det)
|
|
179
|
+
|
|
180
|
+
def finalTest(fitFun,param,testnum=10):
|
|
181
|
+
for n in range(testnum):
|
|
182
|
+
f = fitFun(param)
|
|
183
|
+
if not numpy.isnan(f) and not numpy.isinf(f):
|
|
184
|
+
return f
|
|
185
|
+
Abort("Incompatible parameter set (%g): %s" %(f,join(param,",")))
|
|
186
|
+
|
|
187
|
+
# --- Class: hoppMCMC
|
|
188
|
+
class hoppMCMC:
|
|
189
|
+
def __init__(self,
|
|
190
|
+
fitFun,
|
|
191
|
+
param,
|
|
192
|
+
varmat,
|
|
193
|
+
inferpar=None,
|
|
194
|
+
num_hopp=3,
|
|
195
|
+
num_adapt=25,
|
|
196
|
+
num_chain=12,
|
|
197
|
+
chain_length=50,
|
|
198
|
+
rangeT=None,
|
|
199
|
+
model_comp=1000.0,
|
|
200
|
+
outfilename='',
|
|
201
|
+
gibbs=True):
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
Adaptive Basin-Hopping MCMC Algorithm
|
|
205
|
+
|
|
206
|
+
Parameters
|
|
207
|
+
----------
|
|
208
|
+
|
|
209
|
+
fitFun:
|
|
210
|
+
fitFun(x) - objective function which takes a numpy array as the only argument
|
|
211
|
+
|
|
212
|
+
param:
|
|
213
|
+
initial parameter vector
|
|
214
|
+
|
|
215
|
+
varmat:
|
|
216
|
+
2-dimensional array of initial covariance matrix
|
|
217
|
+
|
|
218
|
+
inferpar:
|
|
219
|
+
an array of indexes of parameter dimensions to be inferred
|
|
220
|
+
(all parameters are inferred by default)
|
|
221
|
+
|
|
222
|
+
num_hopp:
|
|
223
|
+
number of hopp-steps (default=3)
|
|
224
|
+
|
|
225
|
+
num_adapt:
|
|
226
|
+
number of adaptation steps (default=25)
|
|
227
|
+
|
|
228
|
+
num_chain:
|
|
229
|
+
number of MCMC chains (default=12)
|
|
230
|
+
|
|
231
|
+
chain_length:
|
|
232
|
+
size of each chain (default=50)
|
|
233
|
+
|
|
234
|
+
rangeT:
|
|
235
|
+
[min,max] - range of annealing temperatures for each hopp-step (default=[1,1000])
|
|
236
|
+
min should be as low as possible but not lower
|
|
237
|
+
max should be sufficiently permissive to be able to jump between posterior modes
|
|
238
|
+
|
|
239
|
+
model_comp:
|
|
240
|
+
tolerance for accepting subsequent hopp-steps (default=1000)
|
|
241
|
+
this should ideally be equal to or higher than max(rangeT)
|
|
242
|
+
|
|
243
|
+
outfilename:
|
|
244
|
+
name of the output file (default='')
|
|
245
|
+
use this option for a detailed account of the results
|
|
246
|
+
|
|
247
|
+
outfilename.final lists information on hopp-steps:
|
|
248
|
+
hopp-step
|
|
249
|
+
acceptance (0/1)
|
|
250
|
+
weighted average of exp{-f0/model_comp}
|
|
251
|
+
weighted average of exp{-f1/model_comp}
|
|
252
|
+
outfilename.parmat lists chain status at the end of each adaptation step:
|
|
253
|
+
adaptation step
|
|
254
|
+
chain id
|
|
255
|
+
annealing temperature
|
|
256
|
+
score (f)
|
|
257
|
+
parameter values
|
|
258
|
+
both files can be read using the readFile function
|
|
259
|
+
|
|
260
|
+
gibbs:
|
|
261
|
+
for backward compatibility
|
|
262
|
+
|
|
263
|
+
Returns
|
|
264
|
+
-------
|
|
265
|
+
|
|
266
|
+
A hoppMCMC object with
|
|
267
|
+
|
|
268
|
+
parmat: an array of num_chain x (1+len(param))
|
|
269
|
+
current score (f) and parameter values for each chain
|
|
270
|
+
varmat: an array of len(inferpar) x len(inferpar)
|
|
271
|
+
latest proposal distribution
|
|
272
|
+
parmats: a list of parameter values (i.e. parmat) accepted at the end of hopp-steps
|
|
273
|
+
|
|
274
|
+
See also
|
|
275
|
+
--------
|
|
276
|
+
|
|
277
|
+
the documentation (doc/hoppMCMC_manual.pdf) for more information and examples
|
|
278
|
+
|
|
279
|
+
"""
|
|
280
|
+
#
|
|
281
|
+
self.fitFun = fitFun
|
|
282
|
+
#
|
|
283
|
+
if myMPI.MPI_RANK == myMPI.MPI_MASTER:
|
|
284
|
+
self.fun_master_init(param,
|
|
285
|
+
varmat,
|
|
286
|
+
inferpar,
|
|
287
|
+
num_hopp,
|
|
288
|
+
num_adapt,
|
|
289
|
+
num_chain,
|
|
290
|
+
chain_length,
|
|
291
|
+
rangeT,
|
|
292
|
+
model_comp,
|
|
293
|
+
outfilename)
|
|
294
|
+
#
|
|
295
|
+
print("Optimum number of cores: %d" %((self.num_chain*len(self.inferpar))+1), flush=True)
|
|
296
|
+
#
|
|
297
|
+
myMPI.mpi(self.fun_master, self.fun_slave)
|
|
298
|
+
#
|
|
299
|
+
def fun_slave(self, mpi, task, opt):
|
|
300
|
+
chain_id = task['chain_id']
|
|
301
|
+
param_id = task['param_id']
|
|
302
|
+
param1 = task['param1']
|
|
303
|
+
f1 = self.fitFun(param1)
|
|
304
|
+
#
|
|
305
|
+
return {
|
|
306
|
+
'chain_id': chain_id,
|
|
307
|
+
'param_id': param_id,
|
|
308
|
+
'f1': f1,
|
|
309
|
+
'param1': param1
|
|
310
|
+
}
|
|
311
|
+
#
|
|
312
|
+
def fun_master(self, mpi, opt):
|
|
313
|
+
for hopp_step in range(self.num_hopp):
|
|
314
|
+
self.anneal = anneal_sigmasoft(self.rangeT[0],self.rangeT[1],self.num_adapt)
|
|
315
|
+
for adapt_step in range(self.num_adapt):
|
|
316
|
+
self.runAdaptStep(mpi, hopp_step*self.num_adapt+adapt_step)
|
|
317
|
+
test = {'acc':True, 'favg0':numpy.nan, 'favg1':numpy.nan} if len(self.parmats)==0 else compareAUC(self.parmats[-1][:,[0]+(1+self.inferpar).tolist()],self.parmat[:,[0]+(1+self.inferpar).tolist()],self.model_comp)
|
|
318
|
+
if test['acc']:
|
|
319
|
+
self.parmats.append(self.parmat)
|
|
320
|
+
else:
|
|
321
|
+
self.parmat = self.parmats[-1].copy()
|
|
322
|
+
self.param = self.parmat[parMin(self.parmat)['i'],1:].copy()
|
|
323
|
+
# ---
|
|
324
|
+
if self.outfinal:
|
|
325
|
+
self.outfinal.writeRow([hopp_step,test['acc'],test['favg0'],test['favg1']])
|
|
326
|
+
else:
|
|
327
|
+
print("parMatAcc.final: %d,%s" %(hopp_step,join([test['acc'],test['favg0'],test['favg1']],",")))
|
|
328
|
+
# ---
|
|
329
|
+
if self.outparmat:
|
|
330
|
+
self.outparmat.close()
|
|
331
|
+
if self.outfinal:
|
|
332
|
+
self.outfinal.close()
|
|
333
|
+
#
|
|
334
|
+
mpi.clean()
|
|
335
|
+
#
|
|
336
|
+
def fun_master_init(self,
|
|
337
|
+
param,
|
|
338
|
+
varmat,
|
|
339
|
+
inferpar,
|
|
340
|
+
num_hopp,
|
|
341
|
+
num_adapt,
|
|
342
|
+
num_chain,
|
|
343
|
+
chain_length,
|
|
344
|
+
rangeT,
|
|
345
|
+
model_comp,
|
|
346
|
+
outfilename):
|
|
347
|
+
self.multi = {'cov': covariance,
|
|
348
|
+
'rnorm': numpy.random.multivariate_normal,
|
|
349
|
+
'det': numpy.linalg.det}
|
|
350
|
+
self.single = {'cov': covariance,
|
|
351
|
+
'rnorm': numpy.random.normal,
|
|
352
|
+
'det': abs}
|
|
353
|
+
self.stat = self.multi
|
|
354
|
+
self.num_hopp = num_hopp
|
|
355
|
+
self.num_adapt = num_adapt
|
|
356
|
+
self.num_chain = num_chain
|
|
357
|
+
self.chain_length = chain_length
|
|
358
|
+
self.rangeT = numpy.sort([1.0,1000.0] if rangeT is None else rangeT)
|
|
359
|
+
self.model_comp = model_comp
|
|
360
|
+
# ---
|
|
361
|
+
self.param = numpy.array(param,dtype=numpy.float64,ndmin=1)
|
|
362
|
+
f0 = finalTest(self.fitFun,self.param)
|
|
363
|
+
self.parmat = numpy.array([[f0]+self.param.tolist() for n in range(self.num_chain)],dtype=numpy.float64)
|
|
364
|
+
self.varmat = numpy.array(varmat,dtype=numpy.float64,ndmin=2)
|
|
365
|
+
self.parmats = []
|
|
366
|
+
# ---
|
|
367
|
+
if inferpar is None:
|
|
368
|
+
self.inferpar = numpy.arange(len(self.param),dtype=numpy.int32)
|
|
369
|
+
else:
|
|
370
|
+
self.inferpar = numpy.array(inferpar,dtype=numpy.int32)
|
|
371
|
+
print("Parameters to infer: %s" %(join(self.inferpar,",")))
|
|
372
|
+
# ---
|
|
373
|
+
self.outfilename = outfilename
|
|
374
|
+
self.outparmat = None
|
|
375
|
+
self.outfinal = None
|
|
376
|
+
if self.outfilename:
|
|
377
|
+
self.outparmat = binfile(self.outfilename+'.parmat','w',self.parmat.shape[1]+3)
|
|
378
|
+
self.outfinal = binfile(self.outfilename+'.final','w',4)
|
|
379
|
+
# ---
|
|
380
|
+
def runAdaptStep(self, mpi, adapt_step):
|
|
381
|
+
pm = parMin(self.parmat)
|
|
382
|
+
self.param = self.parmat[pm['i'],1:].copy()
|
|
383
|
+
self.parmat = numpy.array([self.parmat[pm['i'],:].tolist() for n in range(self.num_chain)],dtype=numpy.float64)
|
|
384
|
+
# ---
|
|
385
|
+
mcmcs = [
|
|
386
|
+
chainMCMC(self.fitFun,
|
|
387
|
+
self.param,
|
|
388
|
+
self.varmat,
|
|
389
|
+
chain_id=chain_id,
|
|
390
|
+
pulsevar=1.0,
|
|
391
|
+
anneal=self.anneal[0],
|
|
392
|
+
accthr=0.5,
|
|
393
|
+
inferpar=self.inferpar,
|
|
394
|
+
varmat_change=0,
|
|
395
|
+
pulse_change=10,
|
|
396
|
+
pulse_change_ratio=2,
|
|
397
|
+
print_iter=0)
|
|
398
|
+
for chain_id in range(self.num_chain)
|
|
399
|
+
]
|
|
400
|
+
# ---
|
|
401
|
+
for m in range(self.chain_length):
|
|
402
|
+
jobs = []
|
|
403
|
+
for chain_id in range(self.num_chain):
|
|
404
|
+
partest = mcmcs[chain_id].iterate()
|
|
405
|
+
for param_id, param1 in partest:
|
|
406
|
+
jobs.append({
|
|
407
|
+
'chain_id': chain_id,
|
|
408
|
+
'param_id': param_id,
|
|
409
|
+
'param1': param1
|
|
410
|
+
})
|
|
411
|
+
#
|
|
412
|
+
testouts = [[] for chain_id in range(self.num_chain)]
|
|
413
|
+
for ret in mpi.exec(jobs, multiple=True, verbose=False):
|
|
414
|
+
chain_id = ret['chain_id']
|
|
415
|
+
param_id = ret['param_id']
|
|
416
|
+
f1 = ret['f1']
|
|
417
|
+
param1 = ret['param1']
|
|
418
|
+
testouts[chain_id].append([
|
|
419
|
+
param_id,
|
|
420
|
+
f1,
|
|
421
|
+
param1
|
|
422
|
+
])
|
|
423
|
+
#
|
|
424
|
+
for chain_id in range(self.num_chain):
|
|
425
|
+
mcmcs[chain_id].iterate(testout=testouts[chain_id])
|
|
426
|
+
#
|
|
427
|
+
for chain_id in range(self.num_chain):
|
|
428
|
+
self.parmat[chain_id,:] = mcmcs[chain_id].getParam()
|
|
429
|
+
# ---
|
|
430
|
+
self.varmat = numpy.array(self.stat['cov'](self.parmat[:,1+self.inferpar]),ndmin=2)
|
|
431
|
+
self.varmat[numpy.abs(self.varmat)<EPS_VARMAT_MIN] = 1.0
|
|
432
|
+
# ---
|
|
433
|
+
for chain_id in range(self.num_chain):
|
|
434
|
+
if self.outparmat:
|
|
435
|
+
tmp = [adapt_step,chain_id,self.anneal[0]]+self.parmat[chain_id,:].tolist()
|
|
436
|
+
self.outparmat.writeRow(tmp)
|
|
437
|
+
else:
|
|
438
|
+
print("param.mat.step: %d,%d,%g,%s" %(adapt_step,chain_id,self.anneal[0],join(self.parmat[chain_id,:],",")))
|
|
439
|
+
# ---
|
|
440
|
+
if len(self.anneal)>1:
|
|
441
|
+
self.anneal = self.anneal[1:]
|
|
442
|
+
# ---
|
|
443
|
+
|
|
444
|
+
# --- Class: chainMCMC
|
|
445
|
+
class chainMCMC:
|
|
446
|
+
def __init__(self,
|
|
447
|
+
fitFun,
|
|
448
|
+
param,
|
|
449
|
+
varmat,
|
|
450
|
+
inferpar=None,
|
|
451
|
+
chain_id=0,
|
|
452
|
+
pulsevar=1.0,
|
|
453
|
+
anneal=1,
|
|
454
|
+
accthr=0.5,
|
|
455
|
+
varmat_change=0,
|
|
456
|
+
pulse_change=10,
|
|
457
|
+
pulse_change_ratio=2,
|
|
458
|
+
pulse_allow_decrease=True,
|
|
459
|
+
pulse_allow_increase=True,
|
|
460
|
+
pulse_min=1e-7,
|
|
461
|
+
pulse_max=1e7,
|
|
462
|
+
print_iter=0):
|
|
463
|
+
"""
|
|
464
|
+
|
|
465
|
+
MCMC Chain with Adaptive Proposal Distribution
|
|
466
|
+
|
|
467
|
+
Usage
|
|
468
|
+
-----
|
|
469
|
+
|
|
470
|
+
Once created, a chainMCMC is iterated using the iterate method, calling iterateCollective.
|
|
471
|
+
|
|
472
|
+
Parameters
|
|
473
|
+
----------
|
|
474
|
+
|
|
475
|
+
fitFun:
|
|
476
|
+
fitFun(x) - objective function which takes a numpy array as the only argument
|
|
477
|
+
|
|
478
|
+
param:
|
|
479
|
+
initial parameter vector
|
|
480
|
+
|
|
481
|
+
varmat:
|
|
482
|
+
2-dimensional array of initial covariance matrix
|
|
483
|
+
|
|
484
|
+
inferpar:
|
|
485
|
+
an array of indexes of parameter dimensions to be inferred
|
|
486
|
+
(all parameters are inferred by default)
|
|
487
|
+
|
|
488
|
+
chain_id:
|
|
489
|
+
a chain identifier (default=0)
|
|
490
|
+
|
|
491
|
+
pulsevar:
|
|
492
|
+
scaling factor for the variance of the proposal distribution (default=1)
|
|
493
|
+
|
|
494
|
+
anneal:
|
|
495
|
+
annealing temperature (default=1)
|
|
496
|
+
|
|
497
|
+
accthr:
|
|
498
|
+
desired acceptance rate (default=0.5)
|
|
499
|
+
|
|
500
|
+
varmat_change:
|
|
501
|
+
how often variance should be updated? (default=0)
|
|
502
|
+
varmat_change=0 - fixed variance
|
|
503
|
+
varmat_change=n - variance is updated at each nth step
|
|
504
|
+
|
|
505
|
+
pulse_change:
|
|
506
|
+
how often pulsevar should be updated? (default=10)
|
|
507
|
+
pulse_change=0 - fixed pulsevar
|
|
508
|
+
pulse_change=n - pulsevar is updated at each nth step
|
|
509
|
+
|
|
510
|
+
pulse_change_ratio:
|
|
511
|
+
how should pulsevar be updated? (default=2)
|
|
512
|
+
(pulsevar *= pulse_change_ratio)
|
|
513
|
+
|
|
514
|
+
pulse_allow_increase:
|
|
515
|
+
allow pulse to increase (default=True)
|
|
516
|
+
|
|
517
|
+
pulse_allow_decrease:
|
|
518
|
+
allow pulse to decrease (default=True)
|
|
519
|
+
|
|
520
|
+
pulse_min:
|
|
521
|
+
minimum value of pulse (default=1e-7)
|
|
522
|
+
|
|
523
|
+
pulse_max:
|
|
524
|
+
maximum value of pulse (default=1e7)
|
|
525
|
+
|
|
526
|
+
print_iter:
|
|
527
|
+
how often chain status should be printed? (default=0)
|
|
528
|
+
print_iter=0 - do not print status
|
|
529
|
+
print_iter=n - print status at each nth step
|
|
530
|
+
(default=0)
|
|
531
|
+
|
|
532
|
+
Returns
|
|
533
|
+
-------
|
|
534
|
+
|
|
535
|
+
A chainMCMC object with
|
|
536
|
+
|
|
537
|
+
getParam: a method for obtaining the latest iteration (f + parameter values)
|
|
538
|
+
|
|
539
|
+
getVarmat: a method for obtaining the latest proposal distribution (varmat * pulsevar)
|
|
540
|
+
|
|
541
|
+
See also
|
|
542
|
+
--------
|
|
543
|
+
|
|
544
|
+
the documentation (doc/hoppMCMC_manual.pdf) for more information and examples
|
|
545
|
+
|
|
546
|
+
"""
|
|
547
|
+
self.multi = {'cov': covariance,
|
|
548
|
+
'rnorm': numpy.random.multivariate_normal,
|
|
549
|
+
'det': numpy.linalg.det}
|
|
550
|
+
self.single = {'cov': covariance,
|
|
551
|
+
'rnorm': numpy.random.normal,
|
|
552
|
+
'det': abs}
|
|
553
|
+
# ---
|
|
554
|
+
self.chain_id = chain_id
|
|
555
|
+
self.fitFun = fitFun
|
|
556
|
+
self.parmat = numpy.array(param,dtype=numpy.float64,ndmin=1)
|
|
557
|
+
self.varmat = numpy.array(varmat,dtype=numpy.float64,ndmin=2)
|
|
558
|
+
if inferpar is None:
|
|
559
|
+
self.inferpar = numpy.arange(len(param),dtype=numpy.int32)
|
|
560
|
+
else:
|
|
561
|
+
self.inferpar = numpy.array(inferpar,dtype=numpy.int32)
|
|
562
|
+
self.anneal = numpy.array(anneal,dtype=numpy.float64,ndmin=1)
|
|
563
|
+
self.accthr = numpy.array(accthr,dtype=numpy.float64,ndmin=1)
|
|
564
|
+
# ---
|
|
565
|
+
self.pulse_change_ratio = pulse_change_ratio
|
|
566
|
+
self.pulse_nochange = numpy.float64(1)
|
|
567
|
+
self.pulse_increase = numpy.float64(self.pulse_change_ratio)
|
|
568
|
+
self.pulse_decrease = numpy.float64(1.0/self.pulse_change_ratio)
|
|
569
|
+
self.pulse_change = pulse_change
|
|
570
|
+
self.pulse_collect = max(1,self.pulse_change)
|
|
571
|
+
self.allow_pincr = pulse_allow_increase
|
|
572
|
+
self.allow_pdecr = pulse_allow_decrease
|
|
573
|
+
self.pulse_min = pulse_min
|
|
574
|
+
self.pulse_max = pulse_max
|
|
575
|
+
self.varmat_change = varmat_change
|
|
576
|
+
self.varmat_collect = max(1,self.varmat_change)
|
|
577
|
+
# ---
|
|
578
|
+
if self.parmat.ndim==1:
|
|
579
|
+
f0 = finalTest(self.fitFun,self.parmat)
|
|
580
|
+
self.parmat = numpy.array([[f0]+self.parmat.tolist() for i in range(self.varmat_collect)])
|
|
581
|
+
elif self.parmat.shape[0]!=self.varmat_collect:
|
|
582
|
+
Abort("Dimension mismatch in chainMCMC! parmat.shape[0]=%d collect=%d" %(self.parmat.shape[0],self.varmat_collect))
|
|
583
|
+
if self.varmat.shape and self.inferpar.shape[0] != self.varmat.shape[0]:
|
|
584
|
+
Abort("Dimension mismatch in chainMCMC! inferpar.shape[0]=%d varmat.shape[0]=%d" %(self.inferpar.shape[0],self.varmat.shape[0]))
|
|
585
|
+
# ---
|
|
586
|
+
self.pulsevar = numpy.array(numpy.repeat(pulsevar,len(self.inferpar)),dtype=numpy.float64)
|
|
587
|
+
self.acc_vecs = [numpy.repeat(False,self.pulse_collect) for n in range(len(self.inferpar))]
|
|
588
|
+
self.iterate = self.iterateCollective
|
|
589
|
+
self.pulsevar0 = self.pulsevar
|
|
590
|
+
self.varmat = self.varmat*self.pulsevar
|
|
591
|
+
# ---
|
|
592
|
+
self.halfa = 0.025
|
|
593
|
+
if self.pulse_change<25:
|
|
594
|
+
self.halfa = 0.05
|
|
595
|
+
self.print_iter = print_iter
|
|
596
|
+
self.step = 0
|
|
597
|
+
self.index = 0
|
|
598
|
+
self.index_acc = 0
|
|
599
|
+
|
|
600
|
+
def getParam(self):
|
|
601
|
+
return self.parmat[self.index,:].copy()
|
|
602
|
+
|
|
603
|
+
def getVarmat(self):
|
|
604
|
+
return (self.varmat*self.pulsevar).copy()
|
|
605
|
+
|
|
606
|
+
def getVarPar(self):
|
|
607
|
+
return ldet(self.multi['cov'](self.parmat[:,1+self.inferpar]))
|
|
608
|
+
|
|
609
|
+
def getVarVar(self):
|
|
610
|
+
return ldet(self.varmat)
|
|
611
|
+
|
|
612
|
+
def getAcc(self):
|
|
613
|
+
return numpy.array([numpy.mean(acc_vec) for acc_vec in self.acc_vecs])
|
|
614
|
+
|
|
615
|
+
def setParam(self,parmat):
|
|
616
|
+
self.parmat[self.index,:] = numpy.array(parmat,dtype=numpy.float64,ndmin=1).copy()
|
|
617
|
+
|
|
618
|
+
def newParamSingle(self,param,param_id):
|
|
619
|
+
try:
|
|
620
|
+
param1 = self.single['rnorm'](param,
|
|
621
|
+
self.varmat[param_id,param_id]*self.pulsevar[param_id])
|
|
622
|
+
except:
|
|
623
|
+
print("Warning: Failed to generate a new parameter set")
|
|
624
|
+
param1 = numpy.copy(param)
|
|
625
|
+
return param1
|
|
626
|
+
|
|
627
|
+
def newParamMulti(self):
|
|
628
|
+
try:
|
|
629
|
+
param1 = self.multi['rnorm'](self.parmat[self.index,1:][self.inferpar],self.varmat*self.pulsevar)
|
|
630
|
+
except numpy.linalg.linalg.LinAlgError:
|
|
631
|
+
print("Warning: Failed to generate a new parameter set")
|
|
632
|
+
param1 = numpy.copy(self.parmat[self.index,1:][self.inferpar])
|
|
633
|
+
return param1
|
|
634
|
+
|
|
635
|
+
def checkMove(self,f0,f1):
|
|
636
|
+
acc = (not numpy.isnan(f1) and not numpy.isinf(f1) and
|
|
637
|
+
f1 >= 0 and
|
|
638
|
+
(f1 <= f0 or
|
|
639
|
+
(numpy.log(numpy.random.uniform()) < (f0-f1)/self.anneal[0])))
|
|
640
|
+
# --- f0 = 0.5*SS_0
|
|
641
|
+
# --- f = 0.5*SS
|
|
642
|
+
# --- sqrt(anneal) == st.dev.
|
|
643
|
+
# --- 0.5*x^2/(T*s^2)
|
|
644
|
+
# --- return exp(-0.5*SS/anneal)/exp(-0.5*SS_0/anneal)
|
|
645
|
+
return(acc)
|
|
646
|
+
|
|
647
|
+
def pulsevarUpdate(self,acc_vec):
|
|
648
|
+
# --- Test if mean(acc_vec) is equal to accthr
|
|
649
|
+
try:
|
|
650
|
+
r = ttest(acc_vec,self.accthr)
|
|
651
|
+
except ZeroDivisionError:
|
|
652
|
+
if all(acc_vec)<=0 and self.allow_pdecr:
|
|
653
|
+
return self.pulse_decrease
|
|
654
|
+
elif all(acc_vec)>=0 and self.allow_pincr:
|
|
655
|
+
return self.pulse_increase
|
|
656
|
+
else:
|
|
657
|
+
return self.pulse_nochange
|
|
658
|
+
# ---
|
|
659
|
+
if r[1]>=self.halfa: return self.pulse_nochange
|
|
660
|
+
if r[0]>0 and self.allow_pincr: return self.pulse_increase
|
|
661
|
+
if r[0]<0 and self.allow_pdecr: return self.pulse_decrease
|
|
662
|
+
# --- Return default
|
|
663
|
+
return self.pulse_nochange
|
|
664
|
+
|
|
665
|
+
def iterateCollective(self, testout=[], nompi=False):
|
|
666
|
+
self.step += 1
|
|
667
|
+
self.index_acc = (self.index_acc+1)%self.pulse_collect
|
|
668
|
+
# ---
|
|
669
|
+
acc_steps = False
|
|
670
|
+
f0 = self.parmat[self.index,0]
|
|
671
|
+
param0 = self.parmat[self.index,1:].copy()
|
|
672
|
+
#
|
|
673
|
+
if len(testout) == 0:
|
|
674
|
+
partest = []
|
|
675
|
+
for param_id in numpy.arange(len(self.inferpar)):
|
|
676
|
+
param1 = param0.copy()
|
|
677
|
+
param1[self.inferpar[param_id]] = self.newParamSingle(param1[self.inferpar[param_id]],param_id)
|
|
678
|
+
partest.append([
|
|
679
|
+
param_id,
|
|
680
|
+
param1
|
|
681
|
+
])
|
|
682
|
+
#
|
|
683
|
+
if nompi:
|
|
684
|
+
testout = []
|
|
685
|
+
for param_id, param1 in partest:
|
|
686
|
+
f1 = self.fitFun(param1)
|
|
687
|
+
testout.append([
|
|
688
|
+
param_id,
|
|
689
|
+
f1,
|
|
690
|
+
param1
|
|
691
|
+
])
|
|
692
|
+
partest = testout
|
|
693
|
+
else:
|
|
694
|
+
return partest
|
|
695
|
+
else:
|
|
696
|
+
partest = testout
|
|
697
|
+
#
|
|
698
|
+
for param_id, f1, param1 in partest:
|
|
699
|
+
acc = self.checkMove(f0,f1)
|
|
700
|
+
if acc:
|
|
701
|
+
acc_steps = True
|
|
702
|
+
f0 = f1
|
|
703
|
+
param0[self.inferpar[param_id]] = numpy.copy(param1[self.inferpar[param_id]])
|
|
704
|
+
self.acc_vecs[param_id][self.index_acc] = acc
|
|
705
|
+
# ---
|
|
706
|
+
if acc_steps:
|
|
707
|
+
self.index = (self.index+1)%self.varmat_collect
|
|
708
|
+
self.parmat[self.index,0] = f0
|
|
709
|
+
self.parmat[self.index,1:] = param0
|
|
710
|
+
if numpy.isnan(f0) or numpy.isinf(f0):
|
|
711
|
+
Abort("Iterate single failed with %g: %s" %(f0,join(param0,",")))
|
|
712
|
+
# ---
|
|
713
|
+
if self.print_iter and (self.step%self.print_iter)==0:
|
|
714
|
+
print("param.mat.chain: %d,%d,%s" %(self.step,self.chain_id,join(self.parmat[self.index,:],",")))
|
|
715
|
+
# ---
|
|
716
|
+
if self.step>1:
|
|
717
|
+
# ---
|
|
718
|
+
if self.pulse_change and (self.step%self.pulse_change)==0:
|
|
719
|
+
for param_id in numpy.arange(len(self.inferpar)):
|
|
720
|
+
tmp = min(self.pulse_max,max(self.pulse_min,self.pulsevar[param_id]*self.pulsevarUpdate(self.acc_vecs[param_id])))
|
|
721
|
+
if numpy.abs(self.varmat[param_id,param_id]*tmp) >= EPS_PULSE_VAR_MIN:
|
|
722
|
+
self.pulsevar[param_id] = tmp
|
|
723
|
+
# ---
|
|
724
|
+
if self.varmat_change and (self.step%self.varmat_change)==0:
|
|
725
|
+
for param_id in numpy.arange(len(self.inferpar)):
|
|
726
|
+
tmp = max(EPS_VARMAT_MIN,self.single['cov'](self.parmat[:,1+self.inferpar[param_id]]))
|
|
727
|
+
if numpy.abs(tmp*self.pulsevar[param_id]) >= EPS_PULSE_VAR_MIN:
|
|
728
|
+
self.varmat[param_id,param_id] = tmp
|
|
729
|
+
# ---
|
|
730
|
+
if self.print_iter and (self.step%self.print_iter)==0:
|
|
731
|
+
print("parMatAcc.chain: %s" %(join([self.step,self.chain_id,ldet(self.multi['cov'](self.parmat[:,1+self.inferpar])),ldet(self.varmat)],",")))
|
|
732
|
+
print("parMatAcc.chain.accs: %d,%d,%s" %(self.step,self.chain_id,join([numpy.mean(acc_vec) for acc_vec in self.acc_vecs],",")))
|
|
733
|
+
print("parMatAcc.chain.pulses: %d,%d,%s" %(self.step,self.chain_id,join(self.pulsevar,",")))
|
|
734
|
+
|