fitData.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. import numpy
  2. import scipy.optimize
  3. import functools
  4. import fitModel
  5. class initialValueGenerator:
  6. def __init__(self):
  7. self.map={'gaus':initialValueGenerator.drawGauss,
  8. 'loggaus':initialValueGenerator.drawLogGauss,
  9. 'poisson':initialValueGenerator.drawPoisson,
  10. 'flat':initialValueGenerator.drawFlat,
  11. 'const':initialValueGenerator.drawConst}
  12. def add(self,gtype='gaus',parameters=[0,1],bounds=[-numpy.inf,numpy.inf]):
  13. v={'type':gtype,'parameters':parameters,'bounds':bounds}
  14. try:
  15. self.vals.append(v)
  16. except AttributeError:
  17. self.vals=[v]
  18. def getN(self):
  19. return len(self.vals)
  20. def drawGauss(p):
  21. return numpy.random.normal(p[0],p[1])
  22. def drawLogGauss(p):
  23. return numpy.power(10,initialValueGenerator.drawGauss(p))
  24. def drawPoisson(p):
  25. return numpy.random.poisson(p[0])
  26. def drawFlat(p):
  27. return p[0]+numpy.random.random()*(p[1]-p[0])
  28. def drawConst(p):
  29. return p[0]
  30. def draw(self,typ,p,lb,ub):
  31. f=self.map[typ]
  32. while True:
  33. x=f(p)
  34. if x<lb:
  35. continue
  36. if x>ub:
  37. continue
  38. return x
  39. def generate(self):
  40. n=len(self.vals)
  41. par=numpy.zeros(n)
  42. lb=numpy.zeros(n)
  43. ub=numpy.zeros(n)
  44. for i in range(n):
  45. x=self.vals[i]
  46. p=x['parameters']
  47. lb[i]=x['bounds'][0]
  48. ub[i]=x['bounds'][1]
  49. par[i]=self.draw(x['type'],p,lb[i],ub[i])
  50. return par, (lb,ub)
  51. def getBoundsScalar(self):
  52. gbounds=[]
  53. for x in self.vals:
  54. gbounds.append(x['bounds'])
  55. return gbounds
  56. def generateIVF():
  57. ig=initialValueGenerator()
  58. ig.add('poisson',[200],[0,numpy.inf])
  59. ig.add('loggaus',[1,0.5],[0,numpy.inf])
  60. ig.add('loggaus',[-1,1],[0,numpy.inf])
  61. ig.add('const',[6,1],[0,numpy.inf])
  62. ig.add('poisson',[200],[0,numpy.inf])
  63. ig.add('loggaus',[-2,1],[0,0.1])
  64. return ig
  65. def generateIVFFinite():
  66. ig=initialValueGenerator()
  67. #A
  68. ig.add('poisson',[200],[0,10000])
  69. #tau
  70. ig.add('loggaus',[1,0.5],[0,40])
  71. #alpha
  72. ig.add('loggaus',[-1,1],[0,5])
  73. #dt
  74. ig.add('const',[6,1],[6,50])
  75. #B
  76. ig.add('poisson',[200],[0,10000])
  77. #gamma
  78. ig.add('loggaus',[-2,1],[0,0.1])
  79. return ig
  80. def generateCModel():
  81. #generate approx candidate
  82. ig=initialValueGenerator()
  83. ig.add('loggaus',[-3,1],[0,0.1])
  84. ig.add('flat',[0,1],[0,1])
  85. ig.add('loggaus',[-7,2],[0,0.1])
  86. ig.add('gaus',[10,5],[0,30])
  87. return ig
  88. def generateCModelFinite():
  89. #generate approx candidate
  90. ig=initialValueGenerator()
  91. #k1
  92. ig.add('loggaus',[-3,1],[0,0.1])
  93. #BVF
  94. ig.add('flat',[0,1],[0,1])
  95. #k2
  96. ig.add('loggaus',[-7,2],[0,0.1])
  97. #dt
  98. ig.add('gaus',[10,2],[5,50])
  99. return ig
  100. def generateGauss(fit,bounds,n=1):
  101. #generate n realizations of a gaussian multivariate distribution with average (vector) mu
  102. #and (co)variance (matrix) cov
  103. #output is a (r,n) matrix where r is size of vector mu and n are realizations
  104. sig=scipy.linalg.sqrtm(fit.cov)
  105. r=fit.mu.shape[0]
  106. out=numpy.ones((r,n))
  107. i=0
  108. while True:
  109. s=numpy.matmul(sig,numpy.random.normal(size=r))
  110. s+=fit.mu
  111. if in_bounds(s,bounds[0],bounds[1]):
  112. out[:,i]=s
  113. i+=1
  114. if i==n:
  115. break
  116. return out
  117. #return numpy.outer(fit.mu,numpy.ones((1,n)))+numpy.matmul(sig,s)
  118. def in_bounds(x0,lb,ub):
  119. r=x0.shape[0]
  120. for i in range(r):
  121. if x0[i]<lb[i]:
  122. return False
  123. if x0[i]>ub[i]:
  124. return False
  125. return True
  126. def getFit(samples,threshold=numpy.inf,verbose=0):
  127. class fit:pass
  128. chi2=samples[0,:]
  129. #mScore=numpy.min(chi2)
  130. fit.mu=numpy.mean(samples[1:,chi2<threshold],axis=1)
  131. fit.cov=numpy.cov(samples[1:,chi2<threshold])
  132. if verbose>0:
  133. print(fit.mu)
  134. print(fit.cov)
  135. return fit
  136. #fit input function
  137. def fitIVF(t, centers,nfit=10,nbatch=20):
  138. #t,dt=loadData.loadTime(r,xsetup)
  139. #centers=loadCenters(r,xsetup)
  140. #find center with maximum content/uptake
  141. m=numpy.unravel_index(numpy.argmax(centers),centers.shape)[0]
  142. #treat it as ivf sample
  143. ivf=centers[m]
  144. #create a partial specialization to be used in optimization
  145. w=numpy.ones(t.shape)
  146. fun=functools.partial(fitModel.fDiff,fitModel.fIVF,t,ivf,w)
  147. jac=functools.partial(fitModel.jacIVF,t)
  148. #generate approx candidate
  149. ig=generateIVF()
  150. n=ig.getN()
  151. samples=numpy.zeros((n+1,nfit))
  152. for j in range(nfit):
  153. fMin=1e30
  154. for i in range(nbatch):
  155. par,bounds=ig.generate()
  156. result=scipy.optimize.least_squares(fun=fun,x0=par,bounds=bounds,jac=jac)
  157. #result=scipy.optimize.least_squares(fun=fun,x0=par,bounds=bounds)
  158. #fit is invariant on 1/p[1]==p[2], so
  159. xm=[x for x in result.x]
  160. xm[1]=numpy.max([result.x[1],1/result.x[2]])
  161. xm[2]=numpy.max([1/result.x[1],result.x[2]])
  162. if result.cost<fMin:
  163. fMin=result.cost
  164. x1=xm
  165. samples[1:,j]=x1
  166. samples[0,j]=fMin
  167. return m,samples
  168. #global version with annealing
  169. def fitIVFGlobal(t, centers,nfit=10, qLambda=0):
  170. #find center with maximmum point
  171. m=numpy.unravel_index(numpy.argmax(centers),centers.shape)[0]
  172. #treat it as ivf sample
  173. ivf=centers[m]
  174. #this is the (relative) weight of each time point in fit
  175. w=numpy.ones(t.shape)
  176. #create a partial specialization to be used in optimizations
  177. #funcion, a difference between model prediction fIVF and measured values ivf at points t, using point weights w
  178. funScalar=functools.partial(fitModel.fDiffScalar,fitModel.fIVF,t,ivf,w)
  179. #add regulizer on A
  180. funScalarRegularized=functools.partial(fitModel.fDiffScalarRegularized,funScalar,fitModel.fIVFRegA,qLambda)
  181. #Jacobi
  182. jac=functools.partial(fitModel.jacIVF,t)
  183. #convert it to scalar for global minimization
  184. jacScalar=functools.partial(fitModel.jacScalar,fitModel.fIVF,t,ivf,w,jac)
  185. #add regularization on A
  186. jacScalarRegularized=functools.partial(fitModel.jacScalarRegularized,jacScalar,fitModel.jacIVFRegA,qLambda)
  187. ig=generateIVFFinite()
  188. boundsScalar=ig.getBoundsScalar()
  189. #par,bounds=ig.generate()
  190. n=ig.getN()
  191. samples=numpy.zeros((n+1,nfit))
  192. minSetup=dict(method='L-BFGS-B',jac=jacScalar)
  193. if qLambda>0:
  194. minSetup['jac']=jacScalarRegularized
  195. for j in range(nfit):
  196. if qLambda>0:
  197. result=scipy.optimize.dual_annealing(func=funScalarRegularized,bounds=boundsScalar,minimizer_kwargs=minSetup)
  198. else:
  199. result=scipy.optimize.dual_annealing(func=funScalar,bounds=boundsScalar,minimizer_kwargs=minSetup)
  200. xm=[qx for qx in result.x]
  201. xm[1]=numpy.max([result.x[1],1/result.x[2]])
  202. xm[2]=numpy.max([1/result.x[1],result.x[2]])
  203. if result.x[2]!=xm[2]:
  204. pass
  205. #print(f'[{j}] switch')
  206. samples[1:,j]=xm
  207. samples[0,j]=result.fun
  208. #print(f'[{j}] {result.fun}')
  209. return m,samples
  210. #fit compartment
  211. def fitCompartment(ivfFit, t, qf ,nfit=10,nbatch=20, useJac=False):
  212. igIVF=generateIVF()
  213. pars,boundsIVF=igIVF.generate()
  214. ivfSamples=generateGauss(ivfFit,boundsIVF,nfit)
  215. ig=generateCModel()
  216. nC=ig.getN()
  217. boundsScalar=ig.getBoundsScalar()
  218. samples=numpy.zeros((1+nC+nIVF,nfit))
  219. for j in range(nfit):
  220. #create a partial specialization to be used in optimization
  221. ivfPar=ivfSamples[:,j]
  222. w=numpy.ones(t.shape)
  223. fc1=functools.partial(fitModel.fCompartment,ivfPar)
  224. fun=functools.partial(fitModel.fDiff,fc1,t,qf,w)
  225. jac=functools.partial(fitModel.jacDep,ivfPar,t)
  226. #minimize requires scalar function
  227. funScalar=functools.partial(fitData.fDiffScalar,fc1,t,qf,w)
  228. jacScalar=functools.partial(fitData.jacScalar,fc1,t,qf,w,jac)
  229. fMin=1e30
  230. for i in range(nbatch):
  231. #generate approx candidate
  232. par,bounds=ig.generate()
  233. if useJac:
  234. result=scipy.optimize.least_squares(fun=fun,x0=par,bounds=bounds,jac=jac)
  235. #scalar, just for reference
  236. #result=scipy.optimize.minimize(fun=funScalar,x0=par,bounds=boundsScalar,
  237. # jac=jacScalar,method='L-BFGS-B')
  238. else:
  239. result=scipy.optimize.least_squares(fun=fun,x0=par,bounds=bounds)
  240. if result.cost<fMin:
  241. fMin=result.cost
  242. x1=result.x
  243. samples[0,j]=fMin
  244. samples[1:nC+1,j]=x1
  245. samples[(1+nC):,j]=ivfPar
  246. return samples
  247. def fitCompartmentGlobal(ivfFit, t, qf ,nfit=10,nbatch=20, useJac=False,qLambda=0):
  248. #nclass=setup['nclass'][0]
  249. #t,dt=loadData.loadTime(r,setup)
  250. #centers=loadData.loadCenters(r,setup,ir)
  251. #qf=centers[m]
  252. igIVF=generateIVF()
  253. xs,boundsIVF=igIVF.generate()
  254. ivfSamples=generateGauss(ivfFit,boundsIVF,nfit)
  255. #samples=numpy.zeros((4+1,nfit))
  256. nIVF=igIVF.getN()
  257. ig=generateCModelFinite()
  258. nC=ig.getN()
  259. samples=numpy.zeros((1+nC+nIVF,nfit))
  260. boundsScalar=ig.getBoundsScalar()
  261. #optimize has a hard time dealing with small values, so scale the target up and later scale the parameters (approximately correct)
  262. scale=1
  263. fi=qf.sum()
  264. if fi<0.1:
  265. scale=10/fi
  266. print('scale={}'.format(scale))
  267. #print(qf.sum()*scale)
  268. qf*=scale
  269. for j in range(nfit):
  270. ivfPar=ivfSamples[:,j]
  271. w=numpy.ones(t.shape)
  272. fc1=functools.partial(fitModel.fCompartment,ivfPar)
  273. funScalar=functools.partial(fitModel.fDiffScalar,fc1,t,qf,w)
  274. funScalarRegularized=functools.partial(fitModel.fDiffScalarRegularized,funScalar,fitModel.fCRegBVF,qLambda)
  275. jac=functools.partial(fitModel.jacDep,ivfPar,t)
  276. jacScalar=functools.partial(fitModel.jacScalar,fc1,t,qf,w,jac)
  277. jacScalarRegularized=functools.partial(fitModel.jacScalarRegularized,jacScalar,fitModel.jacDepRegBVF,qLambda)
  278. #minSetup=dict(method='L-BFGS-B',jac=jacScalar)
  279. minSetup=dict(method='L-BFGS-B',jac=jacScalarRegularized)
  280. result=scipy.optimize.dual_annealing(func=funScalarRegularized,bounds=boundsScalar,minimizer_kwargs=minSetup)
  281. print(f'[{j}/{nfit}]')
  282. samples[0,j]=result.fun/scale
  283. qx=result.x
  284. qx[0]/=scale
  285. qx[1]/=scale
  286. print('{} {} {}'.format(scale,result.fun/scale,qx))
  287. samples[1:nC+1,j]=qx
  288. samples[(1+nC):,j]=ivfPar
  289. return samples