File indexing completed on 2026-04-09 07:48:49
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021 """
0022 env/graphics/ciexyz/planck.py
0023 ==================================
0024
0025 Generated photons feature a plateau from 80nm to 205nm followed
0026 by step up to join the curve.
0027
0028 * pushing out generation to 10M, 100M doesnt change this feature
0029 other than scaling the plateau level
0030
0031 * low bins of pk.pdf feature very small numbers...,
0032 attempt to avoid numerical issues using arbitrary scaling moves the cut
0033 to 210nm, also a step structure in the generated distribution is
0034 apparent
0035
0036 * problem presumably from too great a probability range... so that
0037 never get to generate any at lower bins somehow convolved with
0038 numerical precision to cause steps ?
0039
0040 ::
0041
0042 In [11]: pk.cdf[0]
0043 Out[11]: 0.0
0044
0045 In [12]: pk.cdf[1]
0046 Out[12]: 1.0708856422955714e-17
0047
0048 In [13]: pk.cdf[-1]
0049 Out[13]: 0.99999999999999045
0050
0051
0052 In [18]: u.min()
0053 Out[18]: 3.6289631744068629e-07
0054
0055 In [19]: u.max()
0056 Out[19]: 0.99999976714026029
0057
0058 In [20]: u[u>3.6289631744068629e-07].min()
0059 Out[20]: 1.9915794777780604e-06
0060
0061
0062 Huh 200nm is on the plateau but the cdf is not outrageously small there ?::
0063
0064 In [27]: pk.cdf[15001]
0065 Out[27]: 0.0065557782251502248
0066
0067 In [28]: np.where(np.logical_and(w > 200.,w<200.1) )
0068 Out[28]: (array([15001, 15002, 15003, 15004, 15005, 15006, 15007, 15008, 15009, 15010]),)
0069
0070
0071 Moving the low wavelength up from 80nm to 200nm avoids the objectionable plateau and cliff,
0072 but there is still a stepping structure though.
0073
0074
0075 **RESOLVED**
0076
0077 The interpolation was using too coarse a binning that
0078 worked fine for most of the distribution, but not good enough
0079 for the low wavelength turn on
0080
0081
0082
0083
0084 * http://www.yale.edu/ceo/Documentation/ComputingThePlanckFunction.pdf
0085
0086 ::
0087
0088
0089 2hc^2 w^-5
0090 Bw(T) = ----------------
0091 e^(beta) - 1
0092
0093
0094 hc/kT
0095 beta = -----
0096 w
0097
0098
0099 Where e^(beta) >> 1 (Wien approximation) Plank tends to
0100
0101
0102 Bw(T) = 2hc^2 w^-5 e^(-beta)
0103
0104
0105
0106
0107
0108 Need inverse CDF of Planck to put into texture
0109 follow ggeo-/GProperty<T>::createInverseCDF
0110
0111 * even domain on 0:1 (needed for quick texture lookup)
0112 * sample the CDF across this domain
0113
0114 * env/geant4/geometry/collada/g4daeview/sample_cdf.py
0115
0116 """
0117
0118 import os
0119 import matplotlib.pyplot as plt
0120 import numpy as np
0121
0122 try:
0123 from scipy.constants import h,c,k
0124 except ImportError:
0125 h = 6.62606957e-34
0126 c = 299792458.0
0127 k = 1.3806488e-23
0128
0129
0130 def planck(nm, K=6500., arbitrary=True):
0131 if arbitrary:
0132 wav = nm
0133 a = np.power(200,5)
0134 else:
0135 wav = nm/1e9
0136 a = 2.0*h*c*c
0137 pass
0138
0139 hc_over_kT = (h*c)/(k*K)
0140
0141 b = hc_over_kT*1e9/nm
0142
0143 return a/(np.power(wav,5) * np.expm1(b))
0144
0145
0146 def planck_plot():
0147 w = np.arange(100,1000,1)
0148 for temp in np.arange(4000,10000,500):
0149 intensity = planck(w, temp)
0150 plt.plot(w, intensity, '-')
0151
0152
0153
0154 def construct_inverse_cdf_0( bva, bdo, N ):
0155 """
0156 :param bva: basis values normalized to unity
0157 :param bdo: basis domain
0158 :param N: number of bins to use across 0:1 range
0159
0160 Note the np.interp xp<->fp inversion,
0161
0162 (x) ido: freshly minted 0:1 domain
0163 (xp) cbva: monotonic CDF in range 0:1,
0164 (fp) bdo: basis domain
0165
0166 """
0167 assert np.allclose( bva.sum(), 1.0 )
0168 assert len(bva) == len(bdo)
0169
0170 cbva = np.cumsum(bva)
0171
0172 ido = np.linspace(0,1,N)
0173
0174 iva = np.interp( ido, cbva, bdo )
0175
0176 return iva, ido
0177
0178
0179
0180 class Planck(object):
0181 def __init__(self, w, K=6500, N_idom=4096):
0182
0183 bb = planck(w, K=K)
0184
0185
0186 avg = (bb[:-1]+bb[1:])/2.
0187 wid = np.diff(w)
0188 mid = (w[:-1]+w[1:])/2.
0189
0190 pdf = avg*wid
0191
0192 pdf /= pdf.sum()
0193
0194 dom = w
0195 cdf = np.empty(len(dom))
0196 cdf[0] = 0.
0197 np.cumsum(pdf, out=cdf[1:])
0198
0199 idom = np.linspace(0,1, N_idom)
0200 icdf = np.interp( idom, cdf, dom )
0201
0202
0203 self.avg = avg
0204 self.wid = wid
0205 self.mid = mid
0206 self.pdf = pdf
0207
0208 self.cdf = cdf
0209 self.dom = dom
0210
0211 self.idom = idom
0212 self.icdf = icdf
0213
0214
0215 def __call__(self, u ):
0216 gen = np.interp( u, self.idom, self.icdf )
0217 self.u = u
0218 self.gen = gen
0219 return gen
0220
0221
0222
0223 def cf_gsrclib():
0224 p = os.path.expandvars("$TMP/ggeo/GSourceLibTest/gsrclib.npy")
0225 sl = np.load(p)
0226 return sl[0,:,0]
0227
0228
0229
0230 if __name__ == '__main__':
0231
0232 plt.ion()
0233
0234 w = np.arange(80.,801,.1, dtype=np.float64)
0235
0236 pk = Planck(w, K=6500)
0237
0238 u = np.random.rand(1000000)
0239
0240 gen = pk(u)
0241
0242
0243 nm = 100
0244 wb = w[::nm]
0245 hn, hd = np.histogram(gen, wb)
0246 assert np.all(hd == wb)
0247 assert len(hn) == len(wb) - 1
0248
0249 s_avg = pk.pdf * hn.sum() * nm
0250
0251
0252
0253
0254 plt.plot( hd[:-1], hn , drawstyle="steps-post")
0255
0256 plt.plot( pk.mid, s_avg )
0257
0258 plt.axis([w.min()-100, w.max()+100, 0, s_avg.max()*1.1])
0259
0260
0261 plt.show()
0262
0263
0264
0265