Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-09 07:48:49

0001 #!/usr/bin/env python
0002 """
0003 piecewise.py
0004 ==============
0005 
0006 ::
0007 
0008     ipython -i piecewise.py
0009 
0010 
0011 This succeeds to create a piecewise defined rindex using sympy 
0012 and symbolically obtains Cerenkov s2 from that, 
0013 using fixed BetaInverse = 1 
0014 
0015 However, attempts to integrate that fail.
0016 
0017 Hmm seems that sympy doesnt like a mix of symbolic and floating point, 
0018 see cumtrapz.py for attempt to use scipy.integrate.cumtrapz to
0019 check my s2 integrals.
0020 
0021 
0022 https://stackoverflow.com/questions/43852159/wrong-result-when-integrating-piecewise-function-in-sympy
0023 
0024 
0025 Programming for Computations, Hans Petter Langtangen
0026 ------------------------------------------------------
0027 
0028 http://hplgit.github.io
0029 
0030 http://hplgit.github.io/prog4comp/doc/pub/p4c-sphinx-Python/._pylight004.html
0031 
0032 http://hplgit.github.io/prog4comp/doc/pub/p4c-sphinx-Python/index.html
0033 
0034 
0035 Solvers seem not to work with sympy, so role simple bisection solver ?
0036 -------------------------------------------------------------------------
0037 
0038 https://personal.math.ubc.ca/~pwalls/math-python/roots-optimization/bisection/
0039 
0040 
0041 
0042 QCerenkov : Whacky Parabolic Ideas
0043 -------------------------------------
0044 
0045 * https://en.wikipedia.org/wiki/Simpson%27s_rule
0046 * https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method
0047 
0048 Looks like storing the mid-bin value of the cumulative integral 
0049 would be sufficient to give you the parabola. Allowing in principal
0050 to recover the equation of the parabola from 3 points and giving 
0051 energy lookup. 
0052 
0053 * y = a x^2 + b x + c 
0054 
0055 * 3 points -> 3 equations in 3 unknowns (a,b,c) : matrix inversion gives you (a,b,c)
0056 
0057 
0058                   2   
0059              .    
0060           .  
0061         1
0062       .  
0063     0
0064 
0065 
0066 Obtaining piecewise parabolic cumulative integral by storing 
0067 (a,b,c) parameters of the parabola ? 
0068 Which would avoid the need for loadsa bins.
0069 
0070 * TODO: explore this by ana/piecewise.py sympy expts handling 
0071   each bin separately to avoid symbolic integration troubles
0072   and construct the symbolic cumulative integral   
0073 
0074 * hmm: better to solve once and store (a,b,c) for each bin 
0075 * then can do energy lookup by solving quadratic, it will be monotonic
0076   so no problem of picking the parabolic piece applicable and then 
0077   picking the root ?
0078 
0079 
0080 
0081 
0082 
0083 
0084 
0085 
0086 
0087 """
0088 import logging, os
0089 log = logging.getLogger(__name__)
0090 import numpy as np
0091 import matplotlib.pyplot as plt 
0092 
0093 from opticks.ana.edges import divide_bins
0094 
0095 from sympy.plotting import plot
0096 from sympy import Piecewise, piecewise_fold, Symbol, Interval, integrate, Max, Min
0097 from sympy import lambdify, ccode 
0098 from sympy.utilities.codegen import codegen
0099 
0100 from sympy.abc import x, y
0101 from sympy.solvers import solve
0102 
0103 e = Symbol('e', positive=True)
0104 
0105 
0106 
0107 from opticks.ana.bisect import bisect
0108 
0109 
0110 def ri_load():
0111     ri_approx = np.array([
0112            [ 1.55 ,  1.478],
0113            [ 1.795,  1.48 ],
0114            [ 2.105,  1.484],
0115            [ 2.271,  1.486],
0116            [ 2.551,  1.492],
0117            [ 2.845,  1.496],
0118            [ 3.064,  1.499],
0119            [ 4.133,  1.526],
0120            [ 6.2  ,  1.619],
0121            [ 6.526,  1.618],
0122            [ 6.889,  1.527],
0123            [ 7.294,  1.554],
0124            [ 7.75 ,  1.793],
0125            [ 8.267,  1.783],
0126            [ 8.857,  1.664],
0127            [ 9.538,  1.554],
0128            [10.33 ,  1.454],
0129            [15.5  ,  1.454]
0130           ])
0131 
0132     ri_path = os.path.expandvars("$OPTICKS_KEYDIR/GScintillatorLib/LS_ori/RINDEX.npy")
0133     if os.path.exists(ri_path):
0134         log.info("load from %s " % ri_path)
0135         ri = np.load(ri_path) 
0136         ri[:,0] *= 1e6      # MeV to eV 
0137     else:
0138         log.fatal("default to approximate ri")
0139         ri = ri_approx
0140     pass
0141     return ri 
0142 pass
0143 
0144 ri = ri_load()
0145 
0146 
0147 def make_piece(i, b=1.5):
0148     """
0149     Max(,0.) complicates the integrals obtained, 
0150     but is a great simplification in that do not need to manually control 
0151     the range to skip regions where s2 dips negative 
0152 
0153     Note that with b=1.5 the the last piece from make_piece(16)
0154     reduces to zero because v0 = v1 which are both -ve 
0155     and sympy succeeds to simplify to zero.  
0156 
0157     Curiously make_piece(0) does not similarly reduce to zero
0158     although it could do. 
0159     """
0160     assert i >= 0 
0161     assert i <= len(ri)-1
0162 
0163 
0164     e0, r0 = ri[i]
0165     e1, r1 = ri[i+1]
0166 
0167     v0 = ( 1 - b/r0 ) * ( 1 + b/r0 )
0168     v1 = ( 1 - b/r1 ) * ( 1 + b/r1 )
0169 
0170     fr = (e-e0)/(e1-e0)   # e = e0, fr=0,  e = e1, fr=1 
0171     pt = ( Max(v0*(1-fr) + v1*fr,0),  (e > e0) & (e < e1) )    
0172     ot = (0, True )
0173 
0174     pw = Piecewise( pt, ot ) 
0175     return pw
0176 
0177 
0178 
0179 def get_check(BetaInverse=1.55):
0180     ck = np.zeros_like(ri)  
0181     ck[:,0] = ri[:,0]
0182     ck[:,1] = (1. - BetaInverse/ri[:,1])*(1.+BetaInverse/ri[:,1])    
0183     return ck 
0184 
0185 
0186 class S2Integral(object):
0187     """
0188     Attemps to treat BetaInverse symbolically rather than as a constant cause integration to fail
0189     or just not happen ... kinda makes sense the BetaInverse has a drastic effect on the s2 
0190     so seems that must subs it before integrating. Or for maximum simplicity just handle as float constant. 
0191 
0192     BUT: the integrals are just parabolas and the BetaInverse just changes the coefficients
0193     of the piecewise linerar s2 so with patience could write down the piecewise integral function 
0194     with the BetaInverse still symbolic.  The problem is the Max(_,0.)  
0195 
0196     * https://math.stackexchange.com/questions/3714720/if-f-and-g-are-both-continuous-then-the-functions-max-f-g-and-min
0197 
0198     ::
0199                          a + b + | a - b |                         a + |a|
0200          max( a, b ) =  --------------------      max( a, 0 ) =   ----------
0201                                  2                                    2 
0202 
0203                           a + b - | a - b |                        a - |a|
0204          min( a, b ) =   --------------------     min( a, 0 ) =   ------------
0205                                  2                                    2 
0206 
0207 
0208          max( a, b ) + min( a, b) = a + b        max(a, 0) + min(a, 0) = a 
0209     
0210 
0211 
0212     """
0213     FINE_STRUCTURE_OVER_HBARC_EVMM = 36.981
0214 
0215     def __init__(self, BetaInverse=1.55, symbolic_add=False):
0216         self.b = BetaInverse
0217 
0218         s2 = {}
0219         is2 = {}
0220 
0221         emn = ri[0,0]
0222         emx = ri[-1,0]
0223         emd = (emn+emx)/2.
0224 
0225         log.info("s2, is2...")
0226         for i in range( len(ri)-1 ):
0227             s2[i] = make_piece( i, b=BetaInverse ) 
0228             is2[i] = integrate( s2[i], e )   # do integral for each piece separately as doesnt work when added together
0229             print("s2[%d]" % i, s2[i])
0230             print("is2[%d]" % i, is2[i])
0231             pass
0232         pass
0233 
0234         if symbolic_add:
0235             log.info("[ is2a...")   # hmm symbolically adding up the pieces takes a long time 
0236             is2a = sum(is2.values())  
0237             log.info("] is2a...")
0238 
0239             norm = is2a.subs(e, emx)
0240             is2n = is2a/norm
0241             pass
0242             log.info("lambdify")  
0243             g = lambdify(e, is2n, "numpy")    
0244             vg = np.vectorize( g ) 
0245             # lambdify generated lambda does not support numpy array aguments in sympy 1.6.2, docs suggest it does on 1.8, hence vectorize   
0246         else:
0247             is2a = None
0248             is2n = None
0249             norm = None
0250             g = None
0251             vg = None
0252         pass
0253 
0254         qwns = "s2 is2 is2a norm is2n emn emx emd g vg".split()
0255         for qwn in qwns: 
0256             setattr(self, qwn, locals()[qwn])  
0257         pass
0258 
0259     def is2c(self, ee):
0260          return self.vg(ee)
0261 
0262     def cliff_plot(self):
0263         is2 = self.is2
0264         emn = self.emn 
0265         emx = self.emx 
0266 
0267         plot( *is2.values(), (e, emn, emx), show=True, adaptive=False, nb_of_points=500)   # cliff edges from each bin  
0268 
0269     def s2_plot(self):
0270         is2n = self.is2n
0271         emn = self.emn 
0272         emx = self.emx 
0273 
0274         plot(is2n[BetaInverse], (e, emn, emx), show=True )
0275 
0276     def energy_lookup(self, u=0.5):
0277         is2n = self.is2n
0278         emn = self.emn 
0279         emx = self.emx 
0280 
0281         fn = lambda x:is2n.subs(e, x) - u 
0282         en = bisect( fn, emn, emx, 20 )         # energy lookup 
0283         return en 
0284 
0285     def is2a_(self, en):
0286         """
0287         multiplying by a float constant slows sympy down so do scaling here 
0288         """
0289         is2a = self.is2a
0290         Rfact = self.FINE_STRUCTURE_OVER_HBARC_EVMM 
0291         return Rfact*is2a.subs(e, en)
0292 
0293     def is2sum_(self, en):
0294         """
0295         try numerical adding instead of symbolic adding  
0296         """
0297         is2 = self.is2
0298         Rfact = self.FINE_STRUCTURE_OVER_HBARC_EVMM 
0299         tot = 0. 
0300         for i in range( len(ri)-1 ):
0301              tot += Rfact*is2[i].subs(e, en)
0302         pass
0303         return tot
0304 
0305 
0306     def export_as_c_code(self, name="s2integral", base="/tmp"):
0307         """ 
0308         # see ~/np/tests/s2integralTest.cc for testing the generated code 
0309         """
0310         is2n = self.is2n
0311         expr = is2n 
0312         cg = codegen( (name, expr), "C99", name, header=True )
0313         open(os.path.join(base, cg[0][0]), "w").write(cg[0][1]) 
0314         open(os.path.join(base, cg[1][0]), "w").write(cg[1][1]) 
0315 
0316 
0317     def plot_is2c(self):
0318         emn = self.emn 
0319         emx = self.emx 
0320         ee = np.linspace( emn, emx, 200 )
0321         ie = self.is2c(ee)
0322         BetaInverse = self.b 
0323 
0324         title = "ana/piecewise.py : plot_is2c : BetaInverse %7.4f " % BetaInverse
0325 
0326         fig, ax = plt.subplots(figsize=[12.8, 7.2])
0327         fig.suptitle(title)
0328         ax.plot( ee, ie, label="plot_is2c" )
0329         ax.legend()
0330         fig.show() 
0331 
0332     @classmethod
0333     def Scan(cls, tt, mul):
0334         """
0335         see QUDARap/tests/QCerenkovTest.cc .py for comparison with numerical integral  
0336         """
0337         ee = divide_bins(ri[:,0], mul)   
0338         pass
0339         sc = np.zeros( (len(tt), len(ee), 2) )
0340         bis = np.zeros( len(tt) )
0341 
0342         for i, k in enumerate(sorted(tt)):
0343             t = tt[k]
0344             BetaInverse = t.b 
0345             bis[i] = BetaInverse
0346             for j,en in enumerate(ee):
0347                 is2sum_val = t.is2sum_(en)
0348                 sc[i, j] = en, is2sum_val 
0349                 print("BetaInverse %10.5f en %10.5f is2sum_val : %10.5f " % (BetaInverse, en, is2sum_val) )
0350             pass
0351         pass
0352         fold = "/tmp/ana/piecewise"
0353         if not os.path.exists(fold):
0354             os.makedirs(fold)
0355         pass
0356         log.info("save scan to %s " % fold )
0357         np.save(os.path.join(fold, "scan.npy"), sc)
0358         np.save(os.path.join(fold, "bis.npy"), bis)
0359         return sc
0360 
0361 
0362 if __name__ == '__main__':
0363     logging.basicConfig(level=logging.INFO)    
0364     plt.ion()
0365 
0366     bis_ = "1 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.792"
0367     #bis_ = "1"
0368     mul = 10
0369 
0370     tt = {}
0371     bis = list(map(float, bis_.split()))
0372     for BetaInverse in bis:
0373         tt[BetaInverse] = S2Integral(BetaInverse=BetaInverse)
0374     pass
0375  
0376     #t = tt[1.]
0377     #t.cliff_plot()
0378     #t.plot_is2c()    
0379     #t.export_as_c_code()
0380     #is2a_val = t.is2a_(t.emx)
0381 
0382     S2Integral.Scan(tt, mul=mul)
0383 
0384