File indexing completed on 2026-04-09 07:48:46
0001
0002 """
0003 ::
0004
0005 ipython -i ck.py
0006
0007 See ana/steps.py to understand why drawstyle="steps-post" is
0008 appropriate for rindex related plotting as rindex appears to artificially dupe
0009 the last value to give equal number of "values" to "edges".
0010
0011 https://arxiv.org/pdf/1206.5530.pdf
0012
0013 Calculation of the Cherenkov light yield from low energetic secondary particles
0014 accompanying high-energy muons in ice and water with Geant4 simulations
0015
0016
0017 """
0018 import os, logging, numpy as np
0019 import matplotlib.pyplot as plt
0020 from opticks.ana.main import opticks_main
0021 from opticks.ana.key import keydir
0022 from opticks.ana.nload import np_load
0023
0024 log = logging.getLogger(__name__)
0025
0026
0027 class CK(object):
0028 FIGPATH="/tmp/ck/ck_rejection_sampling.png"
0029 PATH="/tmp/ck/ck_%d.npy"
0030
0031 kd = keydir()
0032 rindex_path = os.path.join(kd, "GScintillatorLib/LS_ori/RINDEX.npy")
0033
0034
0035 random_path="/tmp/QCtxTest/rng_sequence_f_ni1000000_nj16_nk16_tranche100000"
0036
0037 def init_random(self, num):
0038
0039 rnd, paths = np_load(self.random_path)
0040 if len(paths) == 0:
0041 log.fatal("failed to find any precooked randoms, create them with : TEST=F QSimTest")
0042 assert 0
0043 pass
0044 if num is None:
0045 num = len(rnd)
0046 else:
0047 enough = num <= len(rnd)
0048 if not enough:
0049 log.fatal("not enough precooked randoms len(rnd) %d num %d " % (len(rnd), num))
0050 pass
0051 assert enough
0052 pass
0053 cursors = np.zeros( num, dtype=np.int32 )
0054
0055 self.cursors = cursors
0056 self.rnd_paths = paths
0057 self.rnd = rnd
0058 self.num = num
0059
0060
0061 def init_rindex(self, BetaInverse):
0062
0063 rindex = np.load(self.rindex_path)
0064 rindex[:,0] *= 1e6
0065 rindex_ = lambda ev:np.interp( ev, rindex[:,0], rindex[:,1] )
0066
0067 Pmin = rindex[0,0]
0068 Pmax = rindex[-1,0]
0069 nMax = rindex[:,1].max()
0070
0071 maxCos = BetaInverse / nMax
0072 maxSin2 = (1.0 - maxCos) * (1.0 + maxCos)
0073
0074 smry = "nMax %6.4f BetaInverse %6.4f maxCos %6.4f maxSin2 %6.4f" % (nMax, BetaInverse, maxCos, maxSin2)
0075 print(smry)
0076
0077 self.BetaInverse = BetaInverse
0078 self.maxCos = maxCos
0079 self.maxCosi = 1. - maxCos
0080 self.maxSin2 = maxSin2
0081
0082 self.rindex = rindex
0083 self.Pmin = Pmin
0084 self.Pmax = Pmax
0085 self.nMax = nMax
0086
0087 self.rindex_ = rindex_
0088 self.p = np.zeros( (num,4,4), dtype=np.float64 )
0089
0090
0091 def __init__(self, num=None, BetaInverse=1.5, random=True):
0092 self.init_rindex(BetaInverse)
0093 if random:
0094 self.init_random(num)
0095 pass
0096
0097 def energy_sample_all(self, method="mxs2"):
0098 for idx in range(self.num):
0099 self.energy_sample(idx, method=method)
0100 if idx % 1000 == 0:
0101 print(" idx %d num %d " % (idx, self.num))
0102 pass
0103 pass
0104
0105 def stepfraction_sample_all(self):
0106 for idx in range(self.num):
0107 self.stepfraction_sample(idx)
0108 if idx % 1000 == 0:
0109 print(" idx %d num %d " % (idx, self.num))
0110 pass
0111 pass
0112
0113 def stepfraction_sample(self, idx):
0114 """
0115 What is the expectation for the stepfraction pdf ?
0116 A linear scaling proportionate to the numbers of
0117 photons at each end.
0118
0119
0120
0121
0122
0123 G4double NumberOfPhotons, N;
0124
0125 do {
0126 rand = G4UniformRand();
0127 NumberOfPhotons = MeanNumberOfPhotons1 - rand *
0128 (MeanNumberOfPhotons1-MeanNumberOfPhotons2);
0129 N = G4UniformRand() *
0130 std::max(MeanNumberOfPhotons1,MeanNumberOfPhotons2);
0131 // Loop checking, 07-Aug-2015, Vladimir Ivanchenko
0132 } while (N > NumberOfPhotons);
0133
0134
0135
0136 N = M1 - u (M1 - M2)
0137 = M1 + u (M2 - M1)
0138
0139 """
0140 MeanNumberOfPhotons = np.array([1000., 1.])
0141 self.MeanNumberOfPhotons = MeanNumberOfPhotons
0142
0143 rnd = self.rnd
0144 cursors = self.cursors
0145 uu = rnd[idx].ravel()
0146
0147 loop = 0
0148
0149 NumberOfPhotons = 0.
0150 N = 0.
0151 stepfraction = 0.
0152
0153 while True:
0154 loop += 1
0155 u0 = uu[cursors[idx]]
0156 cursors[idx] += 1
0157
0158 stepfraction = u0
0159 NumberOfPhotons = MeanNumberOfPhotons[0] - stepfraction*(MeanNumberOfPhotons[0] - MeanNumberOfPhotons[1])
0160
0161
0162
0163 u1 = uu[cursors[idx]]
0164 cursors[idx] += 1
0165
0166 N = u1*MeanNumberOfPhotons.max()
0167
0168
0169
0170
0171 reject = N > NumberOfPhotons
0172 if not reject:
0173 break
0174 pass
0175 pass
0176
0177 p = self.p[idx]
0178 i = self.p[idx].view(np.uint64)
0179
0180 p[1,0] = stepfraction
0181 p[1,1] = NumberOfPhotons
0182 p[1,2] = N
0183
0184 p[2,0] = u0
0185 p[2,1] = u1
0186
0187 i[3,1] = loop
0188
0189
0190 def stepfraction_sample_globals(self):
0191 self.globals(
0192 "p",self.p,
0193 "stepfraction", self.p[:,1,0],
0194 "NumberOfPhotons", self.p[:,1,1],
0195 "N", self.p[:,1,2],
0196
0197 "u0", self.p[:,2,0],
0198 "u1", self.p[:,2,1],
0199 "loop", self.p.view(np.uint64)[:,3,1]
0200 )
0201
0202 def stepfraction_plot(self):
0203
0204 self.stepfraction_sample_globals()
0205
0206 fdom = np.linspace(0,1,100)
0207 frg = np.array( [0, 1])
0208 nrg = self.MeanNumberOfPhotons
0209 xf_ = lambda f:np.interp(f, frg, nrg )
0210 self.xf_ = xf_
0211
0212 h_stepfraction = np.histogram( stepfraction )
0213 h_loop = np.histogram(loop, np.arange(loop.max()+1))
0214
0215 title = "ana/ck.py:stepfraction_plot : sampling stepfraction between extremes MeanNumberOfPhotons : %s " % repr(self.MeanNumberOfPhotons)
0216
0217 fig, axs = plt.subplots(2,3, figsize=ok.figsize )
0218 plt.suptitle(title)
0219
0220 ax = axs[0,0]
0221 ax.scatter( u0, u1, label="(u0,u1)", s=0.1 )
0222 ax.legend()
0223
0224 ax = axs[0,1]
0225 ax.scatter( NumberOfPhotons, N, label="(NumberOfPhotons,N)", s=0.1 )
0226 ax.legend()
0227
0228 ax = axs[0,2]
0229 h = h_stepfraction
0230 ax.plot( h[1][:-1], h[0], label="h_stepfraction", drawstyle="steps-post" )
0231
0232 scale = h[0][0]/xf_(0)
0233 ax.plot( fdom, scale*xf_(fdom), label="xf_ scaled to hist" )
0234 ax.legend()
0235
0236 ax = axs[1,0]
0237 h = h_loop
0238 ax.plot( h[1][:-1], h[0], label="h_loop", drawstyle="steps-post" )
0239 ax.legend()
0240
0241
0242 ax = axs[1,1]
0243 ax.plot( fdom, xf_(fdom), label="xf_" )
0244 ax.legend()
0245
0246 fig.show()
0247
0248
0249
0250 def energy_sample(self, idx, method="mxs2"):
0251 """
0252 Why the small difference between s2 when sampling and "expectation-interpolating"
0253 in energy regions far from achoring points ?
0254
0255 The difference is also visible in ct but less clearly.
0256 Comparising directly the sampled rs and rindex its difficult
0257 to see any difference.
0258
0259 When sampling the energy is a random value taked from a flat
0260 energy distribution and interpolated individually to give the
0261 refractive index.
0262
0263 When "expectation-interpolating" the energy domain is an abstract analytic ideal
0264 sort of like a "sample" taken from an infinity of possible values.
0265
0266 """
0267 rnd = self.rnd
0268 rindex = self.rindex
0269 rindex_ = self.rindex_
0270 cursors = self.cursors
0271 num = self.num
0272 Pmin = self.Pmin
0273 Pmax = self.Pmax
0274 nMax = self.nMax
0275 BetaInverse = self.BetaInverse
0276 maxSin2 = self.maxSin2
0277 maxCosi = self.maxCosi
0278
0279 uu = rnd[idx].ravel()
0280
0281 dump = idx < 10 or idx > num - 10
0282 loop = 0
0283
0284 while True:
0285 u0 = uu[cursors[idx]]
0286 cursors[idx] += 1
0287
0288 u1 = uu[cursors[idx]]
0289 cursors[idx] += 1
0290
0291 sampledEnergy = Pmin + u0*(Pmax-Pmin)
0292 sampledRI = rindex_(sampledEnergy)
0293 cosTheta = BetaInverse/sampledRI
0294 sin2Theta = (1.-cosTheta)*(1.+cosTheta)
0295
0296 if method == "mxs2":
0297 u1_maxSin2 = u1*maxSin2
0298 keep_sampling = u1_maxSin2 > sin2Theta
0299 elif method == "mxct":
0300 u1_maxCosi = u1*maxCosi
0301 keep_sampling = u1_maxCosi > 1.-cosTheta
0302 else:
0303 assert 0
0304 pass
0305
0306 loop += 1
0307
0308 if dump:
0309 fmt = "method %s idx %5d u0 %10.5f sampledEnergy %10.5f sampledRI %10.5f cosTheta %10.5f sin2Theta %10.5f u1 %10.5f"
0310 vals = (method, idx, u0, sampledEnergy, sampledRI, cosTheta, sin2Theta, u1 )
0311 print(fmt % vals)
0312 pass
0313
0314 if not keep_sampling:
0315 break
0316 pass
0317 pass
0318
0319 hc_eVnm = 1239.8418754200
0320
0321 sampledWavelength = hc_eVnm/sampledEnergy
0322
0323 p = self.p[idx]
0324 i = self.p[idx].view(np.uint64)
0325
0326 p[0,0] = sampledEnergy
0327 p[0,1] = sampledWavelength
0328 p[0,2] = sampledRI
0329 p[0,3] = cosTheta
0330
0331 p[1,0] = sin2Theta
0332
0333 p[2,0] = u0
0334 p[2,1] = u1
0335
0336 i[3,1] = loop
0337
0338 def globals(self, *args):
0339 assert len(args) % 2 == 0
0340 for i in range(len(args)//2):
0341 k = args[2*i+0]
0342 v = args[2*i+1]
0343 print("%10s : %s " % (k, str(v.shape)))
0344 globals()[k] = v
0345 pass
0346
0347 def energy_sample_globals(self):
0348 p = self.p
0349 u0 = p[:,2,0]
0350 u1 = p[:,2,1]
0351
0352 en = p[:,0,0]
0353 wl = p[:,0,1]
0354 rs = p[:,0,2]
0355 ct = p[:,0,3]
0356
0357 s2 = p[:,1,0]
0358
0359 self.globals(
0360 "p",p,
0361 "u0",u0,
0362 "u1",u1,
0363 "en",en,
0364 "ct",ct,
0365 "s2",s2,
0366 "rs",rs
0367 )
0368
0369 def save(self):
0370 path = self.PATH % self.num
0371 fold = os.path.dirname(path)
0372 if not os.path.exists(fold):
0373 os.makedirs(fold)
0374 pass
0375 log.info("save to %s " % path)
0376 np.save(path, self.p)
0377
0378 @classmethod
0379 def Load(cls, num):
0380 path = cls.PATH % num
0381 return np.load(path)
0382
0383
0384
0385 if __name__ == '__main__':
0386 logging.basicConfig(level=logging.INFO)
0387 plt.ion()
0388
0389 ok = opticks_main()
0390 kd = keydir(os.environ["OPTICKS_KEY"])
0391
0392 num = 10000
0393
0394
0395
0396 ck = CK(num, BetaInverse=1.5, random=False)
0397
0398 ck.test_GetAverageNumberOfPhotons(1.78)
0399
0400
0401
0402
0403
0404
0405 enplot = False
0406
0407 if enplot:
0408 method = "mxs2"
0409
0410 ck.energy_sample_all(method=method)
0411 ck.save()
0412 ck.energy_sample_globals()
0413
0414 ri = ck.rindex
0415 edom = ri[:,0]
0416
0417
0418 en_lim = np.array([2,10])
0419
0420 s2_lim = np.array([-0.01, 0.31])
0421 ct_lim = np.array([ 0.83, 1.01])
0422 rs_lim = np.array([ ri[:,1].min(), ri[:,1].max() ])
0423
0424
0425
0426 ri_ = ck.rindex_
0427 ct_ = lambda e:ck.BetaInverse/ri_(e)
0428 s2_ = lambda e:(1.-ck.BetaInverse/ri_(e))*(1.+ck.BetaInverse/ri_(e))
0429
0430 ri_interp = ri_(edom)
0431 ct_interp = ct_(edom)
0432 s2_interp = (1. - ct_interp)*(1. + ct_interp )
0433
0434 en_u0 = ck.Pmin+u0*(ck.Pmax-ck.Pmin)
0435 s2_u1 = ck.maxSin2*u1
0436
0437
0438
0439
0440
0441
0442
0443 ebin = [5,5.1]
0444 a_s2 = s2[np.logical_and(en > ebin[0], en < ebin[1])]
0445 a_s2_0 = s2_(ebin[0])
0446 a_s2_1 = s2_(ebin[1])
0447
0448
0449 if enplot:
0450 fig, ax = plt.subplots(figsize=ok.figsize)
0451 fig.suptitle("s2 vs en : deviation between sampling and interpolated, more further from anchor points")
0452
0453 ax.scatter( en, s2, s=0.1, label="sampled en vs s2")
0454
0455 ax.set_xlabel("en")
0456 ax.set_ylabel("s2")
0457 ax.set_xlim( en_lim )
0458 ax.set_ylim( s2_lim )
0459 xlim = ax.get_xlim()
0460
0461 ax.plot( ri[:,0], s2_interp, label="s2_interp", color="r" )
0462 ax.scatter( ri[:,0] , s2_(ri[:,0]), color="b", label="en s2" )
0463
0464
0465 ax.set_xlim(xlim)
0466 ylim = ax.get_ylim()
0467
0468
0469
0470 for i in list(range(len(ri)-1)):
0471 e0,v0 = ri[i]
0472 e1,v1 = ri[i+1]
0473 ax.plot( [e0,e1], [s2_(e0), s2_(e1)], linestyle="dotted", color="b" )
0474
0475 pass
0476
0477 ax.plot( xlim, [0.,0.], linestyle="dotted", color="r" )
0478 ax.legend()
0479
0480 fig.show()
0481
0482
0483
0484 if enplot:
0485 fig, ax = plt.subplots(figsize=ok.figsize)
0486 fig.suptitle("1-ct vs en : deviation between sampling and interpolated, more further from anchor points")
0487
0488 ax.scatter( en, 1-ct, s=0.1, label="sampled en vs 1-ct")
0489
0490 ax.set_xlabel("en")
0491 ax.set_ylabel("1-ct")
0492 ax.set_xlim( en_lim )
0493 ax.set_ylim( 1-ct_lim[::-1] )
0494 xlim = ax.get_xlim()
0495
0496 ax.plot( ri[:,0], 1 - ct_interp, label="1-ct_interp", color="r" )
0497 ax.scatter( ri[:,0] , 1 - ct_(ri[:,0]), color="b", label="en 1-ct" )
0498
0499
0500 ax.set_xlim(xlim)
0501 ylim = ax.get_ylim()
0502
0503
0504
0505 for i in list(range(len(ri)-1)):
0506 e0,v0 = ri[i]
0507 e1,v1 = ri[i+1]
0508 ax.plot( [e0,e1], [1-ct_(e0), 1-ct_(e1)], linestyle="dotted", color="b" )
0509
0510 pass
0511
0512 ax.plot( xlim, [0.,0.], linestyle="dotted", color="r" )
0513 ax.legend()
0514
0515 fig.show()
0516
0517
0518
0519
0520 if enplot:
0521 fig, ax = plt.subplots(figsize=ok.figsize)
0522 fig.suptitle("rs/ri vs en : deviation between sampling and interpolated, more further from anchor points")
0523
0524 ax.scatter( en, rs, s=0.1, label="sampled en vs rs")
0525
0526 ax.set_xlabel("en")
0527 ax.set_ylabel("rs")
0528 ax.set_xlim( en_lim )
0529 ax.set_ylim( rs_lim )
0530 xlim = ax.get_xlim()
0531
0532 ax.plot( ri[:,0], ri_(ri[:,0]), label="ri", color="r" )
0533 ax.scatter( ri[:,0] , ri[:,1], label="en ri", color="b" )
0534
0535
0536 ax.set_xlim(xlim)
0537 ylim = ax.get_ylim()
0538
0539
0540
0541 for i in list(range(len(ri)-1)):
0542 e0,v0 = ri[i]
0543 e1,v1 = ri[i+1]
0544 ax.plot( [e0,e1], [ri_(e0), ri_(e1)], linestyle="dotted", color="b" )
0545
0546 pass
0547
0548 ax.plot( xlim, [0.,0.], linestyle="dotted", color="r" )
0549 ax.legend()
0550 fig.show()
0551
0552
0553
0554
0555
0556
0557
0558
0559
0560 if 0:
0561
0562 fig, axs = plt.subplots(2,3,figsize=ok.figsize)
0563
0564 title = "\n".join(
0565 ["Cerenkov Rejection Sampling, for JUNO LS refractive index (en:energy in eV ct:cosTheta s2:sin2Theta) ",
0566 "2d plots: (u0,u1) (en,ct) (en,ct) (Pmin+u0*(Pmax-Pmin),u1*maxSin2) (s2,en) (s2,en) ",
0567 "RHS compares with interpolated expectation"
0568 ])
0569
0570 fig.suptitle(title)
0571
0572 ax = axs[0,0]
0573 ax.set_xlim(0,1)
0574 ax.set_ylim(0,1)
0575 ax.set_xlabel("u0")
0576 ax.set_ylabel("u1")
0577 ax.scatter( u0, u1, s=0.1)
0578 ax.set_aspect('equal')
0579
0580 ax = axs[1,0]
0581 ax.set_xlabel("en_u0 : Pmin+u0*(Pmax-Pmin)")
0582 ax.set_ylabel("s2_u1 : u1*maxSin2")
0583 ax.set_ylim( s2_lim )
0584 ax.scatter( en_u0, s2_u1, s=0.1)
0585 xlim = ax.get_xlim()
0586 ax.plot( xlim, [ck.maxSin2, ck.maxSin2] , linestyle="dotted", color="r")
0587
0588 ax = axs[0,1]
0589 ax.scatter( en, 1.-ct, s=0.1)
0590 ax.set_xlabel("en")
0591 ax.set_ylabel("1-ct")
0592 ax.set_ylim( 1-ct_lim[::-1] )
0593 xlim = ax.get_xlim()
0594 ylim = ax.get_ylim()
0595 for e in ck.rindex[:,0]:
0596 ax.plot( [e,e], ylim , linestyle="dotted", color="b")
0597 pass
0598 ax.set_xlim(xlim)
0599 ax.plot( xlim, [1.-ck.maxCos, 1.-ck.maxCos] , linestyle="dotted", color="r")
0600
0601 ax = axs[1,1]
0602 ax.scatter( en, s2, s=0.1)
0603 ax.set_xlabel("en")
0604 ax.set_ylabel("s2")
0605 ax.set_ylim( s2_lim )
0606 xlim = ax.get_xlim()
0607 ylim = ax.get_ylim()
0608 for e in ck.rindex[:,0]:
0609 ax.plot( [e,e], ylim , linestyle="dotted", color="b")
0610 pass
0611 ax.set_xlim(xlim)
0612
0613 ax = axs[0,2]
0614 ax.scatter( en, ct, s=0.1)
0615 ax.set_xlabel("en")
0616 ax.set_ylabel("ct")
0617 ax.set_ylim( ct_lim )
0618 xlim = ax.get_xlim()
0619
0620 ax.plot( ck.rindex[:,0], ct_interp, color="r" )
0621 ax.set_xlim(xlim)
0622 ax.plot( xlim, [1.,1.], linestyle="dotted", color="r" )
0623 ylim = ax.get_ylim()
0624 for e in ck.rindex[:,0]:
0625 ax.plot( [e,e], ylim , linestyle="dotted", color="b")
0626 pass
0627
0628 if 0:
0629 for v in ck.BetaInverse/ck.rindex[:,1]:
0630 ax.plot( xlim, [v,v] , linestyle="dotted", color="b")
0631 pass
0632 pass
0633
0634
0635 ax = axs[1,2]
0636 ax.scatter( en, s2, s=0.1)
0637 ax.set_xlabel("en")
0638 ax.set_ylabel("s2")
0639 ax.set_ylim( s2_lim )
0640 xlim = ax.get_xlim()
0641 ax.plot( ck.rindex[:,0], s2_interp, label="s2_interp", color="r" )
0642 ax.set_xlim(xlim)
0643 ylim = ax.get_ylim()
0644 for e in ck.rindex[:,0]:
0645 ax.plot( [e,e], ylim , linestyle="dotted", color="b")
0646 pass
0647 ax.plot( xlim, [0.,0.], linestyle="dotted", color="r" )
0648
0649
0650 fig.show()
0651 path = ck.FIGPATH
0652 print("save to %s " % path)
0653 fig.savefig(path)
0654
0655
0656
0657
0658
0659
0660 if 0:
0661 fig3, ax = plt.subplots(figsize=ok.figsize)
0662
0663 h = np.histogram( en )
0664 ax.plot( h[1][:-1], h[0], label="hist en" )
0665 ax.legend()
0666 fig3.show()
0667
0668
0669
0670 if 0:
0671 fig2, ax = plt.subplots(figsize=ok.figsize)
0672 fig2.suptitle( repr(ck.rindex.T) )
0673
0674 ax.scatter( en, ct, s=0.5)
0675 ax.set_xlabel("en")
0676 ax.set_ylabel("ct")
0677 xlim = ax.get_xlim()
0678
0679 ax.plot( ck.rindex[:,0], ck.BetaInverse/ck.rindex[:,1], drawstyle="steps-post" )
0680 ax.set_xlim(xlim)
0681 ax.plot( xlim, [1.,1.], linestyle="dotted", color="r" )
0682
0683 ylim = ax.get_ylim()
0684 for e in ck.rindex[:,0]:
0685 ax.plot( [e,e], ylim , linestyle="dotted", color="b")
0686 pass
0687 for v in ck.BetaInverse/ck.rindex[:,1]:
0688 ax.plot( xlim, [v,v] , linestyle="dotted", color="b")
0689 pass
0690
0691 fig2.show()
0692
0693