propagateErrorLN.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. import numpy
  2. import scipy.interpolate
  3. import importlib
  4. import convolveLN
  5. importlib.reload(convolveLN)
  6. def generateSample(mu,cv):
  7. return numpy.array([convolveLN.ln(mu,cv) for i in range(10000)])
  8. def getEdges(a):
  9. #convert array of bin centers a to array of bin edges with one more element than a
  10. a1=numpy.zeros(len(a)+1)
  11. a2=numpy.zeros(a1.shape)
  12. a1[1:]=a
  13. a2[:-1]=a
  14. e=0.5*(a1+a2)
  15. e[0]=2*e[1]-e[2]
  16. e[-1]=2*e[-2]-e[-3]
  17. return e
  18. def deltaDistribution(xTarget):
  19. yTarget=numpy.zeros(len(xTarget))
  20. #fill bin at 0
  21. n=numpy.argmin(numpy.abs(xTarget))
  22. yTarget[n]=1
  23. return yTarget
  24. def scale(xTarget,xOrig,yOrig,scale,sign=1):
  25. f = scipy.interpolate.CubicSpline(xOrig, yOrig)
  26. x0=numpy.min(xOrig)
  27. x1=numpy.max(xOrig)
  28. yTarget=[f(sign*scale*x) if sign*x>x0 and sign*scale*x<x1 else 0 for x in xTarget]
  29. s=numpy.sum(yTarget)
  30. if s==0:
  31. yTarget=deltaDistribution(xTarget)
  32. else:
  33. yTarget/=s
  34. return yTarget
  35. def cDistScaled(gx,qa,cv,sign=+1,nbins=100,umax=5):
  36. sigmaS=numpy.sqrt(numpy.log(1+cv*cv))
  37. muS=numpy.log(qa/numpy.sqrt(1+cv*cv))
  38. q=1/numpy.sum(qa)
  39. muS1=muS+numpy.log(q)
  40. #temporary space to convolve
  41. qx=numpy.linspace(0,umax,nbins)
  42. qy=convolveLN.convolveLN(qx,sigmaS,muS1)
  43. gy=scale(gx,qx,qy,q,sign)
  44. return gy
  45. def shift(xTarget,xOrig,yOrig,shift):
  46. f = scipy.interpolate.CubicSpline(xOrig, yOrig)
  47. x0=numpy.min(xOrig)
  48. x1=numpy.max(xOrig)
  49. yTarget=[f(x-shift) if x-shift>x0 and x-shift<x1 else 0 for x in xTarget]
  50. s=numpy.sum(yTarget)
  51. if s==0:
  52. #fill bin at 0
  53. n=numpy.argmin(numpy.abs(xTarget))
  54. yTarget[n]=1
  55. else:
  56. yTarget/=s
  57. return yTarget
  58. def calculateDistribution(xTarget,muTarget,mu,cv,dydp,nbins=100,umax=5):
  59. #calculate distribution of random variable y with a functional dependence
  60. #on random parameters p, each with a log-normal
  61. #distribution with a mean mu(p) and coefficients of variation cv(p)
  62. #and dydp giving partial derivatives of y on p
  63. #muTarget is the target mean of distribution
  64. #xTarget is the set of values x where distribution is evaluated at
  65. #Parameters mu, cv and dydp are given as vectors/lists
  66. qa=mu*dydp
  67. y0=numpy.max(numpy.abs(qa))
  68. fx=numpy.linspace(-5*y0,5*y0,201)
  69. sign=qa>=0
  70. fy1=cDistScaled(fx,qa[sign],cv[sign],nbins=nbins,umax=umax)
  71. fy2=cDistScaled(fx,-qa[~sign],cv[~sign],sign=-1,nbins=nbins,umax=umax)
  72. fy=scipy.signal.convolve(fy1,fy2,'same')
  73. a=numpy.average(fx,weights=fy)
  74. return shift(xTarget,fx,fy,muTarget-a)
  75. def calculateDistribution1(fy,muTarget,mu,cv,dydp):
  76. #calculate distribution of random variable y with a functional dependence
  77. #on random parameters p, each with a log-normal
  78. #distribution with a mean mu(p) and coefficients of variation cv(p)
  79. #and dydp giving partial derivatives of y on p
  80. #muTarget is the target mean of y distribution
  81. #fy is the set of values y where distribution is evaluated at
  82. #Parameters mu, cv and dydp are given as vectors/lists
  83. print(f'muTarget={muTarget}')
  84. print('mu={}'.format(mu))
  85. #parameters qa should be around 1
  86. qa=mu*dydp
  87. print('qa={}'.format(qa))
  88. sign=qa>=0
  89. Ax=numpy.sum(qa[sign])
  90. Ay=numpy.sum(-qa[~sign])
  91. print('A={},{}'.format(Ax,Ay))
  92. if Ax>0:
  93. qa[sign]/=Ax
  94. if Ay>0:
  95. qa[~sign]/=Ay
  96. cvPrime=cv
  97. print('cvPrime={}'.format(cvPrime))
  98. #convolve qa multiplied (1,cv) distributions
  99. sigmaS=numpy.sqrt(numpy.log(1+cvPrime*cvPrime))
  100. print('sigmaS={}'.format(sigmaS))
  101. muS=numpy.zeros(qa.shape)
  102. muS[sign]=numpy.log(qa[sign]/numpy.sqrt(1+cvPrime[sign]*cvPrime[sign]))
  103. muS[~sign]=numpy.log(-qa[~sign]/numpy.sqrt(1+cvPrime[~sign]*cvPrime[~sign]))
  104. print('muS={}'.format(muS))
  105. #do the convolution
  106. nbins=100
  107. umax=5
  108. fw=numpy.linspace(0,umax,nbins)
  109. wx=numpy.array([])
  110. wy=numpy.array([])
  111. if Ax>0:
  112. wx=convolveLN.convolveLN(fw,sigmaS[sign],muS[sign])
  113. if Ay>0:
  114. wy=convolveLN.convolveLN(fw,sigmaS[~sign],muS[~sign])
  115. #for correlate, binning of wx and wy should be equal, but one has scale A1, the other A2
  116. #how do you deal with that?
  117. if Ax>0:
  118. fwx=Ax*fw
  119. if Ay>0:
  120. fwy=Ay*fw
  121. fw=fwx
  122. if not Ay>Ax:
  123. if wy.size>0:
  124. #re-sample wy
  125. wy = scipy.interpolate.CubicSpline(fwy, wy)(fw)
  126. if Ay>Ax:
  127. fw=fwy
  128. #resample wx
  129. if wx.size>0:
  130. wx = scipy.interpolate.CubicSpline(fwx, wx)(fw)
  131. #output is twice the length of w and symmetric around 0
  132. fw1=numpy.concatenate((numpy.flip(-fw[1:]),fw))
  133. w=numpy.zeros(fw1.size)
  134. if wx.size==0:
  135. w[:len(wy)]=numpy.flip(wy)
  136. if wy.size==0:
  137. w[-len(wx):]=wx
  138. if wx.size>0 and wy.size>0:
  139. w=scipy.signal.correlate(wx,wy)
  140. #w is a convolution of input LN distributions with a mean of A
  141. #now shift it to the target meat muTarget
  142. B=muTarget-(Ax-Ay)
  143. print('B={}'.format(B))
  144. #interpolate on w and fill y
  145. f = scipy.interpolate.CubicSpline(fw1, w)
  146. y=numpy.zeros(fy.shape)
  147. for i in range(len(y)):
  148. try:
  149. y[i]=f(fy[i]-B)
  150. except ValueError:
  151. pass
  152. return y/numpy.sum(y)
  153. def generateDistribution(fx,muTarget,mu,cv,dydp, dbg=0):
  154. #sum randomly generated variables to approximate distribution of
  155. #variable y with a functional dependence y(p) on random parameters p,
  156. #each with a log-normal (LN) distribution
  157. #with a mean mu(p) and coefficient of variation cv(p)
  158. #target mean of y is muTarget
  159. #distribution is evaluated at points fy,
  160. #dydp are derivatives of y with respect to parameters p
  161. qa=mu*dydp
  162. A=numpy.sum(qa)
  163. #qa*=A
  164. print('qa={}'.format(qa))
  165. cvPrime=cv
  166. samples=[a*generateSample(1,c) for a,c in zip(qa,cvPrime)]
  167. mean=[numpy.mean(s) for s in samples]
  168. std=[numpy.std(s) for s in samples]
  169. convSample=numpy.zeros(len(samples[0]))
  170. msg='[{}] mean={:.2g} std={:.2g} cv={:.2f}'
  171. for i in range(len(cv)):
  172. convSample+=samples[i]
  173. if dbg:
  174. print(msg.format(i,mean[i],std[i],std[i]/mean[i]))
  175. convSample+=muTarget-A
  176. convM=numpy.mean(convSample)
  177. convS=numpy.std(convSample)
  178. if dbg:
  179. code="x"
  180. print(msg.format(code,convM,convS,convS/convM))
  181. h,be=numpy.histogram(convSample,bins=getEdges(fx))
  182. #h should be len(fx)
  183. return h/numpy.sum(h)