Millepede-II V04-16-03
tinypede.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2
3
24
25import numpy as np
26from scipy.stats import chi2
27import array
28import math
29import time
30
31
32
75class MilleRecord(object):
76
77
79 def __init__(self, doublePrec=False):
80
81 self.__doublePrecision = doublePrec
82
83 self.__position = 1
84
85 self.__numData = 0
86
87 self.__recLen = 0
88
89 self.__iMeas = 0
90
91 self.__iErr = 0
92
93 self.__inder = array.array('i')
94
95 self.__glder = array.array('d' if doublePrec else 'f')
96
97
101 def addData(self, dataList):
102 if (self.__numData == 0): # first word is error counter
103 self.__inder.append(0)
104 self.__glder.append(0.)
105 self.__numData += 1
106
107 aMeas, aPrec, indLocal, derLocal, labGlobal, derGlobal = dataList
108 self.__inder.append(0)
109 self.__glder.append(aMeas)
110 self.__inder.fromlist(indLocal)
111 self.__glder.fromlist(derLocal)
112 self.__inder.append(0)
113 self.__glder.append(1.0 / math.sqrt(aPrec)) # convert to error
114 self.__inder.fromlist(labGlobal)
115 self.__glder.fromlist(derGlobal)
116
117
121 def getData(self):
122 aMeas = self.__glder[self.__iMeas]
123 indLocal = []
124 derLocal = []
125 for i in range(self.__iMeas + 1, self.__iErr):
126 indLocal.append(self.__inder[i])
127 derLocal.append(self.__glder[i])
128 aPrec = 1.0 / self.__glder[self.__iErr] ** 2 # convert to precision
129 indGlobal = []
130 derGlobal = []
131 for i in range(self.__iErr + 1, self.__position):
132 indGlobal.append(self.__inder[i])
133 derGlobal.append(self.__glder[i])
134 return aMeas, aPrec, indLocal, derLocal, indGlobal, derGlobal
135
136
137 def printRecord(self):
138 print(" MilleRecord, len: ", len(self.__inder))
139 print(self.__inder)
140 print(self.__glder)
141
142
146 def writeRecord(self, aFile):
147 header = array.array('i') # header with number of words
148 header.append(-len(self.__inder) * 2 if self.__doublePrecision else len(self.__inder) * 2)
149 header.tofile(aFile)
150 self.__glder.tofile(aFile)
151 self.__inder.tofile(aFile)
152
153
158 def readRecord(self, aFile, aType):
159 if aType == 'F':
160 # FOrtran record info
161 lenf = array.array('i')
162 lenf.fromfile(aFile, 1)
163 header = array.array('i') # header with number of words
164 header.fromfile(aFile, 1)
165 self.__recLen = abs(header[0] // 2)
166 if header[0] < 0:
167 self.__glder = array.array('d')
168 else:
169 self.__glder = array.array('f')
170 self.__glder.fromfile(aFile, self.__recLen)
171 self.__inder.fromfile(aFile, self.__recLen)
172 if aType == 'F':
173 # FOrtran record info
174 lenf = array.array('i')
175 lenf.fromfile(aFile, 1)
176
177
181 def moreData(self):
182 if (self.__position < self.__recLen):
183 while (self.__position < self.__recLen and self.__inder[self.__position] != 0):
184 self.__position += 1
185 self.__iMeas = self.__position
186 self.__position += 1
187 while (self.__position < self.__recLen and self.__inder[self.__position] != 0):
188 self.__position += 1
189 self.__iErr = self.__position
190 self.__position += 1
191 # special data?
192 if (self.__iMeas + 1 == self.__iErr and self.__glder[self.__iErr] < 0):
193 self.__position += int(-self.__glder[self.__iErr])
194 else:
195 while (self.__position < self.__recLen and self.__inder[self.__position] != 0):
196 self.__position += 1
197 self.__numData += 1
198 return True
199 else:
200 return False
201
202
206 def specialDataTag(self):
207 aTag = -1
208 if (self.__iMeas + 1 == self.__iErr and self.__glder[self.__iErr] < 0):
209 aTag = int(-self.__glder[self.__iErr] * 10. + 0.5) % 10
210 return aTag
211
212
213
221class Pede(object):
222
223
225 def __init__(self):
226
228
229 self.__numPar = 0
230
232
233 self.__parIndices = {}
234
235 self.__matrix = None
236
237 self.__vector = None
238
239 self.__sumChi2 = 0.
240
241 self.__sumNdf = 0
242
243 self.__numRec = 0
244
246
247 self.__numBad = 0
248
249 self.__numCons = 0
250
251 self.__listCons = []
252
253 #
254 print()
255 print(" TinyPede - a simple PEDE implementation in python")
256 print()
257
258
267 def addBinaryFile(self, binaryFileName, maxRec=1000000, fileType='C'):
268 self.__binaryFiles.append((binaryFileName, maxRec, fileType))
269
270
279 def defineParameters(self, entriesCut=25, fixedPar=[]):
280 for fileName, maxRecord, fileType in self.__binaryFiles:
281 # open binary file
282 if fileType == 'F':
283 print(" Reading Fortran binary file ", fileName)
284 else:
285 print(" Reading binary file ", fileName)
286 binaryFile = open(fileName, "rb")
287 nRec = 0
288 try:
289
290 while nRec < maxRecord:
291 nRec += 1
292 # read record
293 rec = MilleRecord()
294 rec.readRecord(binaryFile, fileType)
295 nData = 0
296 while (rec.moreData()):
297 aTag = rec.specialDataTag()
298 if (aTag >= 0):
299 continue
300 # get data
301 nData += 1
302 indGlobal = rec.getData()[4]
303 for label in indGlobal:
304 if label not in self.__parCounters:
305 self.__parCounters[label] = 0
306 self.__parCounters[label] += 1
307
308 except EOFError:
309 pass
310
311 print(" records found ", nRec)
312 binaryFile.close()
313 # fix parameters
314 for label in fixedPar:
315 if label in self.__parCounters:
316 self.__parCounters[label] *= -1
317 # active parameters
318 for l in sorted(self.__parCounters):
319 if self.__parCounters[l] >= entriesCut:
320 self.__parIndices[l] = self.__numPar
321 self.__numPar += 1
322
323 print(" Found in binary files")
324 print(" total number of parameters ", len(self.__parCounters))
325 print(" minimum number of entries ", entriesCut)
326 print(" number of free parameters ", self.__numPar)
327 print()
328
329
336 def addConstraints(self, textFile):
337 if self.__numPar == 0:
338 print(" no variable parameters defined ")
339 return
340 # read text file
341 print(" Reading constraint file ", textFile)
342 nline = 0
343 nCon = 0
344 pairs = [] # (index, coefficient)
345 tf = open(textFile)
346 for line in iter(tf):
347 nline += 1
348 fields = line.split()
349 if len(fields) < 2:
350 continue
351 if fields[0] == 'Constraint':
352 # next constraint
353 nCon += 1
354 # previous constraint had variable parameters (pairs)?
355 if len(pairs) > 0:
356 self.__numCons += 1
357 self.__listCons.append(pairs)
358 pairs = []
359 continue
360 if fields[0] == '*':
361 continue
362 # label and coefficient
363 label = int(fields[0])
364 value = float(fields[1])
365 if label in self.__parIndices:
366 # variable parameter (label)
367 pairs.append((self.__parIndices[label], value))
368
369 tf.close()
370 # last constraint
371 if len(pairs) > 0:
372 self.__numCons += 1
373 self.__listCons.append(pairs)
374
375 print(" number of all constraints ", nCon)
376 print(" number of accepted constr. ", self.__numCons)
377 #print self.__listCons
378
379
386 def construct(self, chi2Factor=50.):
387 # create global matrix and vector
388 # size: number of parameters + number of Lagrange multipliers for constraints
389 self.__vector = np.zeros(self.__numPar + self.__numCons)
390 self.__matrix = np.zeros((self.__numPar + self.__numCons, self.__numPar + self.__numCons))
391 print()
392 print(" Constructing linear equations system")
393 print(" local chi2 scaling factor ", chi2Factor)
394 for fileName, maxRecord, fileType in self.__binaryFiles:
395 # open binary file
396 binaryFile = open(fileName, "rb")
397 nRec = 0
398 try:
399 while nRec < maxRecord:
400 # read record
401 rec = MilleRecord()
402 rec.readRecord(binaryFile, fileType)
403 nRec += 1
404 dataBlocks = []
405 # number of local parameters
406 numLoc = 0
407 # number of measurements
408 numMeas = 0
409
410 while (rec.moreData()):
411 aTag = rec.specialDataTag()
412 if (aTag >= 0):
413 continue
414 # get data
415 dataBlocks.append(rec.getData())
416 numLoc = max(numLoc, dataBlocks[-1][2][-1])
417 numMeas += 1
418
419 #print(" numLoc ", nRec, numLoc, numMeas)
420 vecMeas = np.zeros(numMeas)
421 vecMeasW = np.zeros(numMeas)
422 locDer = np.zeros((numMeas, numLoc))
423 locDerW = np.zeros((numMeas, numLoc))
424 gloDer = np.zeros((numMeas, self.__numPar))
425 gloDerW = np.zeros((numMeas, self.__numPar))
426 # loop over data blocks
427 for iData, data in enumerate(dataBlocks):
428 #aRes = data.getResidual()[0]
429 aMeas, aPrec, indLocal, derLocal, indGlobal, derGlobal = data
430 # update local-local matrix
431 for i, der in zip(indLocal, derLocal):
432 locDer[iData, i - 1] = der
433 locDerW[iData, i - 1] = der * aPrec
434 vecMeas[iData] = aMeas
435 vecMeasW[iData] = aMeas * aPrec
436 # update local-global matrix
437 for l, der in zip(indGlobal, derGlobal):
438 if l not in self.__parIndices:
439 continue
440 i = self.__parIndices[l]
441 gloDer[iData, i] = der
442 gloDerW[iData, i] = der * aPrec
443
444 # local fit
445 matLocLoc = np.dot(locDerW.T, locDer)
446 vecLoc = np.dot(locDerW.T, vecMeas)
447 try:
448 locCov = np.linalg.inv(matLocLoc)
449 except np.linalg.LinAlgError as e:
450 print(" local fit failed:", e)
451 self.__numBad += 1
452 continue
453 locSol = np.dot(locCov, vecLoc)
454 # chi2, ndf
455 locNdf = numMeas - numLoc
456 locChi2 = np.dot(vecMeas - np.dot(locDer, locSol), vecMeasW - np.dot(locDerW, locSol))
457 prob = 1. - chi2.cdf(locChi2 / chi2Factor, locNdf)
458 # check rank
459 nRank = np.linalg.matrix_rank(matLocLoc)
460 if nRank < numLoc:
461 self.__numBad += 1
462 print(" ??? bad local fit ", nRec, locNdf, locChi2, numLoc, nRank)
463 for i in range(numLoc):
464 gcor2 = 1.0 - 1.0 / (matLocLoc[i, i] * locCov[i, i]) # global correlation (squared)
465 print(" locPar ", i + 1, matLocLoc[i, i], locCov[i, i], gcor2)
466 continue
467 # reject (at 3 sigma)
468 if prob < 0.0027:
469 self.__numRejects += 1
470 continue
471 # accepted
472 self.__sumNdf += locNdf
473 self.__sumChi2 += locChi2
474 # update global matrix, vector (parameter part)
475 self.__matrix[:self.__numPar,:self.__numPar] += np.dot(gloDerW.T, gloDer)
476 self.__vector[:self.__numPar] += np.dot(gloDerW.T, vecMeas)
477 # account for correlations (via local parameters)
478 matGloLoc = np.dot(gloDerW.T, locDer)
479 self.__matrix[:self.__numPar,:self.__numPar] -= np.dot(matGloLoc, np.dot(locCov, matGloLoc.T))
480 self.__vector[:self.__numPar] -= np.dot(matGloLoc, locSol)
481
482 except EOFError:
483 pass
484
485 binaryFile.close()
486 self.__numRec += nRec
487
488 # add Lagrange multipliers
489 indexCons = self.__numPar
490 for pairs in self.__listCons:
491 for idx, val in pairs:
492 self.__matrix[indexCons, idx] = val
493 self.__matrix[idx, indexCons] = val
494 indexCons += 1
495
496 # record statistics
497 print(" total number of records ", self.__numRec)
498 print(" number of rejected records ", self.__numRejects)
499 print(" number of bad records ", self.__numBad)
500 # matrix information
501 matSize = self.__numPar + self.__numCons
502 nonZero = np.count_nonzero(self.__matrix)
503 print(" size of global matrix ", matSize)
504 print(" fraction of elements <> 0. ", int(100.*nonZero / matSize ** 2), '%')
505
506
507 def dump(self):
508 print(" Global matrix, non-zero elements")
509 for i in range(self.__numPar + self.__numCons):
510 for j in range(i, self.__numPar + self.__numCons):
511 if self.__matrix[i, j] != 0.:
512 print(" ", i, j, self.__matrix[i, j])
513
514
518 def solve(self):
519 print()
520 print(" Solving linear equations system")
521 # solve equation system
522 try:
523 aCov = np.linalg.inv(self.__matrix)
524 except np.linalg.LinAlgError as e:
525 print(" matrix inversion failed:", e)
526 print(" >>> improve input data <<<")
527 return
528 aSol = np.dot(aCov, self.__vector)
529 print(" sum(ndf) ", self.__sumNdf - self.__numPar)
530 print(" initial sum(chi2) ", self.__sumChi2)
531 deltaChi2 = np.dot(self.__vector, aSol)
532 print(" chi2 reduction by fit ", deltaChi2)
533 print(" final sum(chi2) ", self.__sumChi2 - deltaChi2)
534 print(" final sum(chi2)/sum(ndf) ", (self.__sumChi2 - deltaChi2) / (self.__sumNdf - self.__numPar))
535 print()
536 print(" Solution")
537 print(" parameter label, #entries, correction, error")
538 for l in sorted(self.__parCounters):
539 if l in self.__parIndices:
540 i = self.__parIndices[l]
541 print(" ", l, self.__parCounters[l], aSol[i], math.sqrt(aCov[i, i]) if aCov[i, i] > 0. else -math.sqrt(-aCov[i, i]))
542 else:
543 print(" ", l, self.__parCounters[l], " fixed")
544
545
546if __name__ == '__main__':
547 print(time.asctime())
548 #
549 # Some steering parameters
550 #
551 # maximum number of records to read (for each file)
552 maxRec = 10000
553 # minimum number of entries (appearances in binary files) for variable global parameters
554 entries = 25
555
556 #
557 # get pede instance
558 #
559 p = Pede()
560
561 #
562 # MP2 test case (from pede -t)
563 #
564 # add (Fortran) binary file(s)
565 p.addBinaryFile("mp2tst.bin", maxRec, 'F')
566 # define parameters
567 p.defineParameters(entries, fixedPar=[])
568 # add constraints
569 p.addConstraints("mp2con.txt")
570 # construct linear equation system
571 p.construct()
572 #p.dump()
573 # solve linear equation system
574 p.solve()
575
576 print()
577 print(time.asctime())
Millepede-II (binary) record.
Definition: tinypede.py:75
def readRecord(self, aFile, aType)
Read record from file.
Definition: tinypede.py:158
__glder
array with values, errors and derivatives; (float32 or float64)
Definition: tinypede.py:95
def printRecord(self)
Print record.
Definition: tinypede.py:137
__inder
array with markers (0) and labels; array(int32)
Definition: tinypede.py:93
__iMeas
position of value in current data block; int
Definition: tinypede.py:89
__recLen
record length; int
Definition: tinypede.py:87
__iErr
position of error in current data block; int
Definition: tinypede.py:91
__numData
number of data blocks in record; int
Definition: tinypede.py:85
def moreData(self)
Locate next data block.
Definition: tinypede.py:181
def getData(self)
Get data block from current position in record.
Definition: tinypede.py:121
def __init__(self, doublePrec=False)
Create MP-II binary record.
Definition: tinypede.py:79
def writeRecord(self, aFile)
Write record to file.
Definition: tinypede.py:146
def addData(self, dataList)
Add data block to (end of) record.
Definition: tinypede.py:101
def specialDataTag(self)
Get special data tag from block.
Definition: tinypede.py:206
__position
position in record, usually start of next data block; int
Definition: tinypede.py:83
__doublePrecision
flag for storage in as double values
Definition: tinypede.py:81
__parIndices
parameter indices
Definition: tinypede.py:233
def solve(self)
Solve linear equation system.
Definition: tinypede.py:518
def construct(self, chi2Factor=50.)
Construct linear equation system.
Definition: tinypede.py:386
def defineParameters(self, entriesCut=25, fixedPar=[])
Define Parameters.
Definition: tinypede.py:279
__numRejects
number of rejects (Chi2,ndf)
Definition: tinypede.py:245
__sumChi2
sum(chi2)
Definition: tinypede.py:239
def addConstraints(self, textFile)
Add constraints.
Definition: tinypede.py:336
def addBinaryFile(self, binaryFileName, maxRec=1000000, fileType='C')
Add binary file.
Definition: tinypede.py:267
__numCons
rank of global matrix
Definition: tinypede.py:249
__sumNdf
sum(ndf)
Definition: tinypede.py:241
__vector
vector
Definition: tinypede.py:237
__binaryFiles
binary files
Definition: tinypede.py:227
__numRec
number of records
Definition: tinypede.py:243
__parCounters
parameter counters
Definition: tinypede.py:231
__numBad
bad local fits (rank deficit)
Definition: tinypede.py:247
__listCons
list constraints
Definition: tinypede.py:251
def __init__(self)
Constructor.
Definition: tinypede.py:225
__matrix
matrix
Definition: tinypede.py:235
def dump(self)
Dump global matrix.
Definition: tinypede.py:507
__numPar
number of parameters
Definition: tinypede.py:229