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 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
+