File indexing completed on 2026-04-09 07:48:48
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021 """
0022 groupvel.py
0023 ============
0024
0025 * C++ and NumPy implementations of the ported GROUPVEL_
0026 calculation are matching
0027
0028 NB the calc interps back to original bin,
0029 to avoid bin shifting sins that G4 commits
0030
0031
0032 TODO:
0033
0034 * compare actual G4 calc against mt port of the calc
0035
0036
0037 """
0038
0039 import os, logging, numpy as np
0040 log = logging.getLogger(__name__)
0041
0042 from opticks.ana.base import opticks_main
0043 from opticks.ana.proplib import PropLib
0044
0045
0046 hc = 1239.841875
0047 c_light = 299.792458
0048 wl = np.linspace(60,820,39)
0049
0050
0051 def g4_bintrick(aa):
0052 """
0053 :param aa:
0054 :return bb:
0055
0056 This curious bin shifting is used by G4 GROUPVEL calc
0057
0058 In order not to loose a bin
0059 a tricky manoever of using the 1st and last bin and
0060 the average of the body bins
0061 which means the first bin is half width, and last is 1.5 width
0062
0063 ::
0064
0065 0 + 1 + 2 + 3 + 4 + 5 <--- 6 original values
0066 | / / / / |
0067 | / / / / |
0068 0 1 2 3 4 5 <--- still 6
0069
0070 """
0071 bb = np.zeros_like(aa)
0072 bb[0] = aa[0]
0073 bb[1:-1] = (aa[1:-1] + aa[:-2])/2.
0074 bb[-1] = aa[-1]
0075 return bb
0076
0077
0078 def np_gradient(y, edge_order=1):
0079 """
0080 ::
0081
0082 Take a look at np.gradient??
0083
0084 The gradient is computed using second order accurate central differences
0085 in the interior and either first differences or second order accurate
0086 one-sides (forward or backwards) differences at the boundaries. The
0087 returned gradient hence has the same shape as the input array.
0088
0089 0 <--- (0,1)
0090
0091 1 <--- (0,2)/2
0092 2 <--- (1,3)/2
0093 3 <-- (2,4)/2
0094 4 <-- (3,5)/2
0095 5 <-- (4,6)/2
0096 6 <-- (5,7)/2
0097 7 <-- (6,8)/2
0098 8 <-- (7,9)/2
0099
0100 9 <-- (8,9)
0101
0102
0103 In [10]: y = np.arange(10, dtype='f')
0104
0105
0106 In [14]: y[:-2] # skip bins N-2,N-1 : ie N-2 values
0107 Out[14]: array([ 0., 1., 2., 3., 4., 5., 6., 7.], dtype=float32)
0108
0109 In [13]: y[2:] # skip bins 0,1 : ie N-2 values
0110 Out[13]: array([ 2., 3., 4., 5., 6., 7., 8., 9.], dtype=float32)
0111
0112 In [15]: y[2:] - y[:-2] # between N-2 values -> N-2 values
0113 Out[15]: array([ 2., 2., 2., 2., 2., 2., 2., 2.], dtype=float32)
0114
0115 In [16]: (y[2:] - y[:-2])/2.
0116 Out[16]: array([ 1., 1., 1., 1., 1., 1., 1., 1.], dtype=float32)
0117
0118 """
0119 assert edge_order == 1
0120 out = np.zeros_like(y)
0121 out[0] = y[1] - y[0]
0122 out[1:-1] = (y[2:]-y[:-2])/2.0
0123 out[-1] = y[-1] - y[-2]
0124 return o
0125
0126
0127
0128 def GROUPVEL_(ri_, dump=False):
0129 """
0130 Reproduce G4 GROUPVEL calc by migrating from wavelength to energy
0131 domain and duplicating the tricky bin shift manoever
0132
0133 c
0134 vg = --------------- # angular freq proportional to E for light
0135 n + E dn/dE
0136
0137 G4 using this energy domain approach approximating the dispersion part E dn/dE as shown below
0138
0139 c n1 - n0 n1 - n0 dn dn dE
0140 vg = ----------- ds = ------------ = ------------ ~ ------ = ---- ------- = E dn/dE
0141 nn + ds log(E1/E0) log E1 - log E0 d(logE) dE dlogE
0142
0143
0144 Start by convert to energy and flipping order of energy
0145 and refractive values to correspond to increasing energy domain
0146 """
0147
0148 ri = ri_.copy()
0149 en = hc/wl[::-1]
0150 ri = ri[::-1]
0151
0152 assert ri.shape == en.shape and len(ri) == len(en)
0153
0154 n0,n1= ri[:-1],ri[1:]
0155 e0,e1 = en[:-1],en[1:]
0156
0157 nn = g4_bintrick(ri)
0158 ee = g4_bintrick(en)
0159
0160 ds = np.zeros_like(ri)
0161 ds[1:] = (n1-n0)/np.log(e1/e0)
0162 ds[0] = ds[1]
0163
0164
0165
0166 vg0 = c_light/nn
0167 vg = c_light/(nn + ds)
0168
0169 msk = np.logical_or( vg < 0, vg > vg0)
0170 vgm = vg.copy()
0171 vgm[msk] = vg0[msk]
0172
0173 vgi = np.interp( en, ee, vgm )
0174
0175
0176 if dump:
0177 labels = "hc/en,en,hc/ee,hc/ee-hc/en,ee,nn,ds,vg0,vg,msk,vgm,vgi"
0178 print "".join(map(lambda _:"%11s"%_, labels.split(",")))
0179 print np.dstack([hc/en,en,hc/ee,hc/ee-hc/en,ee,nn,ds,vg0,vg,msk,vgm,vgi])
0180
0181 return vgi[::-1]
0182
0183
0184
0185
0186 class PropTree(object):
0187 def __init__(self):
0188 self.names = os.listdir(os.path.expandvars(self.base))
0189 def subdir(self, sub):
0190 return os.path.expandvars(os.path.join(self.base, sub))
0191 def path(self, sub, fname):
0192 return os.path.expandvars(os.path.join(self.base, sub, fname + ".npy"))
0193 def ary(self, sub, fname):
0194 return np.load(self.path(sub, fname))
0195
0196
0197 class replaceGROUPVEL(PropTree):
0198 """
0199 GMaterialLib::replaceGROUPVEL in debug mode writes the
0200 GROUPVEL calculated from refractive index
0201
0202 ::
0203
0204 In [25]: rg.vg("GdDopedLS")
0205 Out[25]:
0206 array([[ 60. , 206.2414],
0207 [ 80. , 206.2414],
0208 [ 100. , 206.2414],
0209 [ 120. , 198.1083],
0210 [ 140. , 181.5257],
0211
0212
0213 """
0214 base = "$TMP/replaceGROUPVEL"
0215 def ri_(self, matname):
0216 return self.ary(matname, "refractive_index")
0217 def vg_(self, matname):
0218 return self.ary(matname, "group_velocity")
0219
0220 def check_all(self, dump=False):
0221 for name in self.names:
0222 self.check(name, dump=dump)
0223
0224 def check(self, name="GdDopedLS", dump=True):
0225
0226 rip = self.ri_(name)
0227
0228 wl = rip[:,0]
0229 ri = rip[:,1]
0230
0231 vgp = self.vg_(name)
0232 wl2 = vgp[:,0]
0233 vg = vgp[:,1]
0234
0235 assert np.allclose(wl,wl2)
0236
0237 vgpy = GROUPVEL_(ri)
0238 vgdf = vg-vgpy
0239
0240 if dump:
0241 print np.dstack([wl,ri,vg,vgpy, vgdf])
0242
0243 log.info("check %30s %s " % (name, vgdf.max()) )
0244
0245
0246 class CGROUPVELTest(PropTree):
0247 """
0248 CMaterialLib::saveGROUPVEL which is invoked by CGROUPVELTest
0249 writes arrays with the G4 calculated GROUPVEL
0250 """
0251 base="$TMP/CGROUPVELTest"
0252 fnam="saveGROUPVEL"
0253
0254 def ri(self, matname):
0255 a = self.ary(matname, self.fnam)
0256 return a[0,::-1,2]
0257
0258 def vg(self, matname):
0259 a = self.ary(matname, self.fnam)
0260 return a[1,::-1,2]
0261
0262
0263
0264 if __name__ == '__main__':
0265 ok = opticks_main()
0266
0267 mlib = PropLib("GMaterialLib")
0268
0269 name = "GdDopedLS"
0270 okmat = mlib(name)
0271 okri = okmat[0,:,0]
0272
0273
0274 cg = CGROUPVELTest()
0275 rg = replaceGROUPVEL()
0276
0277
0278
0279