Ejemplo n.º 1
0
    def __init__(self):
        kd = keydir(os.environ["OPTICKS_KEY"])
        ri = np.load(os.path.join(kd, "GScintillatorLib/LS_ori/RINDEX.npy"))
        ri[:,0] *= 1e6  

        ri_ = lambda e:np.interp(e, ri[:,0], ri[:,1] )
        self.ri = ri 
        self.ri_ = ri_
        self.s2x = np.repeat( np.nan, len(self.ri) )
Ejemplo n.º 2
0
    def __init__(self):
        kd = keydir(os.environ["OPTICKS_KEY"])
        ri = np.load(os.path.join(kd, "GScintillatorLib/LS_ori/RINDEX.npy"))
        ri[:, 0] *= 1e6
        ri_ = lambda e: np.interp(e, ri[:, 0], ri[:, 1])
        nMax = ri[:, 1].max()
        nMin = ri[:, 1].min()

        self.ri = ri
        self.ri_ = ri_
        self.nMax = nMax
        self.nMin = nMin
Ejemplo n.º 3
0
    def scint_wavelength(self):
        """
        See::
 
             ana/wavelength.py
             ana/wavelength_cfplot.py

        """
        w0 = np.load(os.path.join(self.FOLD, "wavelength_scint_hd20.npy"))

        path1 = "/tmp/G4OpticksAnaMgr/wavelength.npy"
        w1 = np.load(path1) if os.path.exists(path1) else None

        kd = keydir(os.environ["OPTICKS_KEY"])
        aa = np.load(os.path.join(kd, "GScintillatorLib/GScintillatorLib.npy"))
        a = aa[0, :, 0]
        b = np.linspace(0, 1, len(a))
        u = np.random.rand(1000000)
        w2 = np.interp(u, b, a)

        #bins = np.arange(80, 800, 4)
        bins = np.arange(300, 600, 4)

        h0 = np.histogram(w0, bins)
        h1 = np.histogram(w1, bins)
        h2 = np.histogram(w2, bins)

        fig, ax = plt.subplots()

        ax.plot(bins[:-1], h0[0], drawstyle="steps-post", label="OK.QCtxTest")
        ax.plot(bins[:-1], h1[0], drawstyle="steps-post", label="G4")
        ax.plot(bins[:-1],
                h2[0],
                drawstyle="steps-post",
                label="OK.GScint.interp")

        ylim = ax.get_ylim()

        for w in [320, 340, 360, 380, 400, 420, 440, 460, 480, 500, 520, 540]:
            ax.plot([w, w], ylim)
        pass

        ax.legend()

        plt.show()

        self.w0 = w0
        self.w1 = w1
        self.w2 = w2
Ejemplo n.º 4
0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import sys, os, numpy as np, logging, argparse
log = logging.getLogger(__name__)
tx_load = lambda _: list(map(str.strip,
                             open(_).readlines())
                         )  # py3 needs the list, otherwise stays as map

from opticks.ana.key import keydir
KEYDIR = keydir()


class BLib(object):
    @classmethod
    def parse_args(cls, doc, **kwa):
        np.set_printoptions(suppress=True, precision=3)
        parser = argparse.ArgumentParser(doc)
        #parser.add_argument(     "path",  nargs="?", help="Geocache directory", default=kwa.get("path",None) )
        parser.add_argument("--level", default="info", help="logging level")
        parser.add_argument("-b",
                            "--brief",
                            action="store_true",
                            default=False)
        parser.add_argument("-n",
                            "--names",
Ejemplo n.º 5
0
::

    ipython -i tests/reemissionTest.py 

"""
import os, sys, logging, numpy as np
import matplotlib.pyplot as plt
from opticks.ana.nload import np_load
from opticks.ana.key import keydir

log = logging.getLogger(__name__)

if __name__ == '__main__':

    ok = os.environ["OPTICKS_KEY"]
    kd = keydir(ok)
    aa = np_load(os.path.join(kd, "GScintillatorLib/GScintillatorLib.npy"))
    fc = np_load(os.path.join(kd, "GScintillatorLib/LS/FASTCOMPONENT.npy"))
    sc = np_load(os.path.join(kd, "GScintillatorLib/LS/SLOWCOMPONENT.npy"))

    print("aa:%s" % str(aa.shape))
    print("fc:%s" % str(fc.shape))
    print("sc:%s" % str(sc.shape))
    assert aa.shape == (1, 4096, 1)
    assert np.all(fc == sc)

    path = os.path.expandvars("$TMP/optixrap/reemissionTest/out.npy")
    w = np.load(path)

    plt.ion()
Ejemplo n.º 6
0
                print(str(p))
                continue
            #print(sh)
            for pa in sh.patches():
                ax.add_patch(pa)
                if not art3d is None:
                    art3d.pathpatch_2d_to_3d(pa, z=0, zdir="y")
                pass
            pass
        pass


if __name__ == '__main__':

    logging.basicConfig(level=logging.INFO)
    kd = keydir()
    log.info(kd)
    assert os.path.exists(kd), kd

    os.environ[
        "IDPATH"] = kd  ## TODO: avoid having to do this, due to prim internals

    mm0 = Geom2d(kd, ridx=0)

    import matplotlib.pyplot as plt
    from mpl_toolkits.mplot3d import Axes3D
    import mpl_toolkits.mplot3d.art3d as art3d

    plt.ion()
    fig = plt.figure(figsize=(6, 5.5))
    ax = fig.add_subplot(111, projection='3d')
Ejemplo n.º 7
0
                self.prim.shape), repr(self.part.shape), repr(self.tran.shape))
        ])


if __name__ == '__main__':

    logging.basicConfig(level=logging.INFO)

    ddir = "/usr/local/opticks/geocache/DayaBay_VGDX_20140414-1300/g4_00.dae/96ff965744a2f6b78c24e33c80d3a4cd/103/GPartsAnalytic/5"
    dir_ = sys.argv[1] if len(sys.argv) > 1 else ddir
    sli_ = sys.argv[2] if len(sys.argv) > 2 else "0:10"
    sli = slice(*map(int, sli_.split(":")))

    if dir_ == ddir:
        log.warning("using hardcoded dir")
    pass

    from opticks.ana.key import keydir
    kd = keydir(os.environ["OPTICKS_KEY"])

    d = Dir(dir_, kd)
    print("Dir(dir_)", d)

    pp = d.prims

    print("dump sliced prims from the dir slice %s " % repr(sli))
    for p in pp[sli]:
        print(p)
    pass
    #print(d.tran)
Ejemplo n.º 8
0
class CK(object):
    FIGPATH = "/tmp/ck/ck_rejection_sampling.png"
    PATH = "/tmp/ck/ck_%d.npy"

    kd = keydir()
    rindex_path = os.path.join(kd, "GScintillatorLib/LS_ori/RINDEX.npy")

    #random_path = os.path.expandvars("/tmp/$USER/opticks/TRngBufTest_0.npy")
    random_path = "/tmp/QCtxTest/rng_sequence_f_ni1000000_nj16_nk16_tranche100000"

    def init_random(self, num):

        rnd, paths = np_load(self.random_path)
        if len(paths) == 0:
            log.fatal(
                "failed to find any precooked randoms, create them with : TEST=F QSimTest"
            )
            assert 0
        pass
        if num is None:
            num = len(rnd)
        else:
            enough = num <= len(rnd)
            if not enough:
                log.fatal("not enough precooked randoms len(rnd) %d num %d " %
                          (len(rnd), num))
            pass
            assert enough
        pass
        cursors = np.zeros(num, dtype=np.int32)

        self.cursors = cursors
        self.rnd_paths = paths
        self.rnd = rnd
        self.num = num

    def init_rindex(self, BetaInverse):

        rindex = np.load(self.rindex_path)
        rindex[:, 0] *= 1e6  # into eV
        rindex_ = lambda ev: np.interp(ev, rindex[:, 0], rindex[:, 1])

        Pmin = rindex[0, 0]
        Pmax = rindex[-1, 0]
        nMax = rindex[:, 1].max()

        maxCos = BetaInverse / nMax
        maxSin2 = (1.0 - maxCos) * (1.0 + maxCos)

        smry = "nMax %6.4f BetaInverse %6.4f maxCos %6.4f maxSin2 %6.4f" % (
            nMax, BetaInverse, maxCos, maxSin2)
        print(smry)

        self.BetaInverse = BetaInverse
        self.maxCos = maxCos
        self.maxCosi = 1. - maxCos
        self.maxSin2 = maxSin2

        self.rindex = rindex
        self.Pmin = Pmin
        self.Pmax = Pmax
        self.nMax = nMax

        self.rindex_ = rindex_
        self.p = np.zeros((num, 4, 4), dtype=np.float64)

    def __init__(self, num=None, BetaInverse=1.5, random=True):
        self.init_rindex(BetaInverse)
        if random:
            self.init_random(num)
        pass

    def energy_sample_all(self, method="mxs2"):
        for idx in range(self.num):
            self.energy_sample(idx, method=method)
            if idx % 1000 == 0:
                print(" idx %d num %d " % (idx, self.num))
            pass
        pass

    def stepfraction_sample_all(self):
        for idx in range(self.num):
            self.stepfraction_sample(idx)
            if idx % 1000 == 0:
                print(" idx %d num %d " % (idx, self.num))
            pass
        pass

    def stepfraction_sample(self, idx):
        """
        What is the expectation for the stepfraction pdf ?
        A linear scaling proportionate to the numbers of
        photons at each end.


            


          G4double NumberOfPhotons, N;

          do { 
             rand = G4UniformRand();
             NumberOfPhotons = MeanNumberOfPhotons1 - rand *
                                    (MeanNumberOfPhotons1-MeanNumberOfPhotons2);
             N = G4UniformRand() *
                            std::max(MeanNumberOfPhotons1,MeanNumberOfPhotons2);
            // Loop checking, 07-Aug-2015, Vladimir Ivanchenko
          } while (N > NumberOfPhotons);



        N = M1 - u (M1 - M2)
          = M1 + u (M2 - M1) 

        """
        MeanNumberOfPhotons = np.array([1000., 1.])
        self.MeanNumberOfPhotons = MeanNumberOfPhotons

        rnd = self.rnd
        cursors = self.cursors
        uu = rnd[idx].ravel()

        loop = 0

        NumberOfPhotons = 0.
        N = 0.
        stepfraction = 0.

        while True:
            loop += 1
            u0 = uu[cursors[idx]]
            cursors[idx] += 1

            stepfraction = u0
            NumberOfPhotons = MeanNumberOfPhotons[0] - stepfraction * (
                MeanNumberOfPhotons[0] - MeanNumberOfPhotons[1])
            # stepfraction=0  ->  MeanNumberOfPhotons[0]
            # stepfraction=1  ->  MeanNumberOfPhotons[1]

            u1 = uu[cursors[idx]]
            cursors[idx] += 1

            N = u1 * MeanNumberOfPhotons.max()
            # why is this range not from .min to .max  ?
            # because the sampled number can and will be less that the mean
            # in some places

            reject = N > NumberOfPhotons
            if not reject:
                break
            pass
        pass

        p = self.p[idx]
        i = self.p[idx].view(np.uint64)

        p[1, 0] = stepfraction
        p[1, 1] = NumberOfPhotons
        p[1, 2] = N

        p[2, 0] = u0
        p[2, 1] = u1

        i[3, 1] = loop

    def stepfraction_sample_globals(self):
        self.globals("p", self.p, "stepfraction", self.p[:, 1, 0],
                     "NumberOfPhotons", self.p[:, 1, 1], "N", self.p[:, 1, 2],
                     "u0", self.p[:, 2, 0], "u1", self.p[:, 2, 1], "loop",
                     self.p.view(np.uint64)[:, 3, 1])

    def stepfraction_plot(self):

        self.stepfraction_sample_globals()

        fdom = np.linspace(0, 1, 100)
        frg = np.array([0, 1])
        nrg = self.MeanNumberOfPhotons
        xf_ = lambda f: np.interp(f, frg, nrg)
        self.xf_ = xf_

        h_stepfraction = np.histogram(stepfraction)
        h_loop = np.histogram(loop, np.arange(loop.max() + 1))

        title = "ana/ck.py:stepfraction_plot : sampling stepfraction between extremes MeanNumberOfPhotons : %s " % repr(
            self.MeanNumberOfPhotons)

        fig, axs = plt.subplots(2, 3, figsize=ok.figsize)
        plt.suptitle(title)

        ax = axs[0, 0]
        ax.scatter(u0, u1, label="(u0,u1)", s=0.1)
        ax.legend()

        ax = axs[0, 1]
        ax.scatter(NumberOfPhotons, N, label="(NumberOfPhotons,N)", s=0.1)
        ax.legend()

        ax = axs[0, 2]
        h = h_stepfraction
        ax.plot(h[1][:-1],
                h[0],
                label="h_stepfraction",
                drawstyle="steps-post")

        scale = h[0][0] / xf_(0)
        ax.plot(fdom, scale * xf_(fdom), label="xf_ scaled to hist")
        ax.legend()

        ax = axs[1, 0]
        h = h_loop
        ax.plot(h[1][:-1], h[0], label="h_loop", drawstyle="steps-post")
        ax.legend()

        ax = axs[1, 1]
        ax.plot(fdom, xf_(fdom), label="xf_")
        ax.legend()

        fig.show()

    def energy_sample(self, idx, method="mxs2"):
        """
        Why the small difference between s2 when sampling and "expectation-interpolating" 
        in energy regions far from achoring points ? 

        The difference is also visible in ct but less clearly.
        Comparising directly the sampled rs and rindex its difficult 
        to see any difference. 

        When sampling the energy is a random value taked from a flat 
        energy distribution and interpolated individually to give the 
        refractive index.

        When "expectation-interpolating" the energy domain is an abstract analytic ideal
        sort of like a "sample" taken from an infinity of possible values.

        """
        rnd = self.rnd
        rindex = self.rindex
        rindex_ = self.rindex_
        cursors = self.cursors
        num = self.num
        Pmin = self.Pmin
        Pmax = self.Pmax
        nMax = self.nMax
        BetaInverse = self.BetaInverse
        maxSin2 = self.maxSin2
        maxCosi = self.maxCosi

        uu = rnd[idx].ravel()

        dump = idx < 10 or idx > num - 10
        loop = 0

        while True:
            u0 = uu[cursors[idx]]
            cursors[idx] += 1

            u1 = uu[cursors[idx]]
            cursors[idx] += 1

            sampledEnergy = Pmin + u0 * (Pmax - Pmin)
            sampledRI = rindex_(sampledEnergy)
            cosTheta = BetaInverse / sampledRI
            sin2Theta = (1. - cosTheta) * (1. + cosTheta)

            if method == "mxs2":
                u1_maxSin2 = u1 * maxSin2
                keep_sampling = u1_maxSin2 > sin2Theta
            elif method == "mxct":  ## CANNOT DO THIS : MUST USE THE "CONTROLLING" S2 PDF
                u1_maxCosi = u1 * maxCosi
                keep_sampling = u1_maxCosi > 1. - cosTheta
            else:
                assert 0
            pass

            loop += 1

            if dump:
                fmt = "method %s idx %5d u0 %10.5f sampledEnergy %10.5f sampledRI %10.5f cosTheta %10.5f sin2Theta %10.5f u1 %10.5f"
                vals = (method, idx, u0, sampledEnergy, sampledRI, cosTheta,
                        sin2Theta, u1)
                print(fmt % vals)
            pass

            if not keep_sampling:
                break
            pass
        pass

        hc_eVnm = 1239.8418754200  # G4: h_Planck*c_light/(eV*nm)

        sampledWavelength = hc_eVnm / sampledEnergy

        p = self.p[idx]
        i = self.p[idx].view(np.uint64)

        p[0, 0] = sampledEnergy
        p[0, 1] = sampledWavelength
        p[0, 2] = sampledRI
        p[0, 3] = cosTheta

        p[1, 0] = sin2Theta

        p[2, 0] = u0
        p[2, 1] = u1

        i[3, 1] = loop

    def globals(self, *args):
        assert len(args) % 2 == 0
        for i in range(len(args) // 2):
            k = args[2 * i + 0]
            v = args[2 * i + 1]
            print("%10s : %s " % (k, str(v.shape)))
            globals()[k] = v
        pass

    def energy_sample_globals(self):
        p = self.p
        u0 = p[:, 2, 0]
        u1 = p[:, 2, 1]

        en = p[:, 0, 0]
        wl = p[:, 0, 1]
        rs = p[:, 0, 2]
        ct = p[:, 0, 3]

        s2 = p[:, 1, 0]

        self.globals("p", p, "u0", u0, "u1", u1, "en", en, "ct", ct, "s2", s2,
                     "rs", rs)

    def save(self):
        path = self.PATH % self.num
        fold = os.path.dirname(path)
        if not os.path.exists(fold):
            os.makedirs(fold)
        pass
        log.info("save to %s " % path)
        np.save(path, self.p)

    @classmethod
    def Load(cls, num):
        path = cls.PATH % num
        return np.load(path)
Ejemplo n.º 9
0
class CKN(object):
    """
    Reproduces the G4Cerenkov Frank-Tamm integration to give average number of photons
    for a BetaInverse and RINDEX profile.
    """
    FOLD = os.path.expandvars("/tmp/$USER/opticks/ana/ckn")
    kd = keydir()
    rindex_path = os.path.join(kd, "GScintillatorLib/LS_ori/RINDEX.npy")

    def __init__(self):
        ri = np.load(self.rindex_path)
        ri[:, 0] *= 1e6  # into eV
        ri_ = lambda ev: np.interp(ev, ri[:, 0], ri[:, 1])

        self.ri = ri
        self.ri_ = ri_
        self.BuildThePhysicsTable()
        self.BuildThePhysicsTable_2()
        assert np.allclose(self.cai, self.cai2)
        self.paths = []

    def BuildThePhysicsTable(self, dump=False):
        """
        See G4Cerenkov_modified::BuildThePhysicsTable


        This is applying the composite trapezoidal rule to do a 
        numerical energy integral  of  n^(-2) = 1./(ri[:,1]*ri[:,1])
        """
        ri = self.ri
        en = ri[:, 0]

        ir2 = 1. / (ri[:, 1] * ri[:, 1])
        mir2 = 0.5 * (ir2[1:] + ir2[:-1])

        de = en[1:] - en[:-1]

        assert len(mir2) == len(ri) - 1  # averaging points looses one value
        mir2_de = mir2 * de

        cai = np.zeros(len(ri))  # leading zero regains one value
        np.cumsum(mir2_de, out=cai[1:])

        if dump:
            print("cai", cai)
        pass

        self.cai = cai
        self.ir2 = ir2
        self.mir2 = mir2
        self.de = de
        self.mir2_de = mir2_de

    def BuildThePhysicsTable_2(self, dump=False):
        """
        np.trapz does the same thing as above : applying composite trapezoidal integration

        https://numpy.org/doc/stable/reference/generated/numpy.trapz.html
        """
        ri = self.ri
        en = ri[:, 0]
        ir2 = 1. / (ri[:, 1] * ri[:, 1])

        cai2 = np.zeros(len(ri))
        for i in range(len(ri)):
            cai2[i] = np.trapz(ir2[:i + 1], en[:i + 1])
        pass
        self.cai2 = cai2

        if dump:
            print("cai2", cai2)
        pass

    @classmethod
    def BuildThePhysicsTable_s2i(cls, ri, BetaInverse, dump=False):
        """
        No need for a physics table when do integral directly on s2
        """
        en = ri[:, 0]
        s2i = np.zeros(len(ri))
        for i in range(len(ri)):
            s2i[i] = np.trapz(s2[:i + 1], en[:i + 1])
        pass
        return s2i

    def GetAverageNumberOfPhotons_s2(self,
                                     BetaInverse,
                                     charge=1,
                                     dump=False,
                                     s2cross=False):
        """
        Simplfied Alternative to _s2messy following C++ implementation. 
        Allowed regions are identified by s2 being positive avoiding the need for 
        separately getting crossings. Instead get the crossings and do the trapezoidal 
        numerical integration in one pass, improving simplicity and accuracy.  
    
        See opticks/examples/Geant4/CerenkovStandalone/G4Cerenkov_modified.cc
        """
        s2integral = 0.
        for i in range(len(self.ri) - 1):
            en = np.array([self.ri[i, 0], self.ri[i + 1, 0]])
            ri = np.array([self.ri[i, 1], self.ri[i + 1, 1]])
            ct = BetaInverse / ri
            s2 = (1. - ct) * (1. + ct)

            en_0, en_1 = en
            ri_0, ri_1 = ri
            s2_0, s2_1 = s2

            if s2_0 * s2_1 < 0.:
                en_cross_A = (s2_1 * en_0 - s2_0 * en_1) / (s2_1 - s2_0)
                en_cross_B = en_0 + (BetaInverse -
                                     ri_0) * (en_1 - en_0) / (ri_1 - ri_0)
                en_cross = en_cross_A if s2cross else en_cross_B
            else:
                en_cross = np.nan
            pass

            if s2_0 <= 0. and s2_1 <= 0.:
                pass
            elif s2_0 < 0. and s2_1 > 0.:
                s2integral += (en_1 - en_cross) * s2_1 * 0.5
            elif s2_0 >= 0. and s2_1 >= 0.:
                s2integral += (en_1 - en_0) * (s2_0 + s2_1) * 0.5
            elif s2_0 > 0. and s2_1 < 0.:
                s2integral += (en_cross - en_0) * s2_0 * 0.5
            else:
                print(
                    " en_0 %10.5f ri_0 %10.5f s2_0 %10.5f  en_1 %10.5f ri_1 %10.5f s2_1 %10.5f "
                    % (en_0, ri_0, s2_0, en_1, ri_1, s2_1))
                assert 0
            pass
        pass
        Rfact = 369.81 / 10.  #        Geant4 mm=1 cm=10
        NumPhotons = Rfact * charge * charge * s2integral
        return NumPhotons

    def GetAverageNumberOfPhotons_s2messy(self,
                                          BetaInverse,
                                          charge=1,
                                          dump=False):
        """
        NB see GetAverageNumberOfPhotons_s2 it gives exactly the same results as this and is simpler

        Alternate approach doing the numerical integration directly of s2 rather than 
        doing it on n^-2 and combining it later with the integral of the 1 and the 
        BetaInverse*BetaInverse

        Doing the integral of s2 avoids inconsistencies in the numerical approximations
        which prevents the average number of photons going negative in the region when the 
        BetaInverse "sea level" rises to almost engulf the last rindex peak.:: 

                                                  BetaInverse*BetaInverse
              Integral ( s2 )  = Integral(  1 - ---------------------------  )  = Integral ( 1 - c2 )
                                                          ri*ri 
        """
        self.BetaInverse = BetaInverse
        ckn = self
        ri = ckn.ri
        en = ri[:, 0]

        s2 = np.zeros((len(ri), 2), dtype=np.float64)
        ct = BetaInverse / ri[:, 1]
        s2[:, 0] = ri[:, 0]
        s2[:, 1] = (1. - ct) * (1. + ct)

        cross = ckn.FindCrossings(s2, 0.)
        s2integral = 0.
        for i in range(len(cross) // 2):
            en0 = cross[2 * i + 0]
            en1 = cross[2 * i + 1]
            # select bins within the range
            s2_sel = s2[np.logical_and(s2[:, 0] >= en0, s2[:, 0] <= en1)]

            # fabricate partial bins before and after the full ones
            # that correspond to s2 zeros
            fs2 = np.zeros((2 + len(s2_sel), 2), dtype=np.float64)
            fs2[0] = [en0, 0.]
            fs2[1:-1] = s2_sel
            fs2[-1] = [en1, 0.]

            s2integral += np.trapz(fs2[:, 1],
                                   fs2[:, 0])  # trapezoidal integration
        pass
        Rfact = 369.81  #  (eV * cm)^-1
        Rfact *= 0.1  # cm to mm ?  Geant4: mm = 1. cm = 10.

        NumPhotons = Rfact * charge * charge * s2integral
        self.NumPhotons = NumPhotons
        if dump:
            print(" s2integral %10.4f " % (s2integral))
        pass
        return NumPhotons

    def GetAverageNumberOfPhotons_asis(self,
                                       BetaInverse,
                                       charge=1,
                                       dump=False):
        """
        This duplicates the results from G4Cerenkov_modified::GetAverageNumberOfPhotons
        including negative numbers of photons for BetaInverse close to the rindex peak.

        Frank-Tamm formula gives number of Cerenkov photons per mm as an energy::

                                                        BetaInverse^2    
              N_photon  =     370.     Integral ( 1 - -----------------  )  dE      
                                                          ri(E)^2      

        Where the integration is over regions where :    ri(E) > BetaInverse 
        which corresponds to a real cone angle and the above bracket being positive::
        
                        BetaInverse
              cos th = --------------   < 1  
                           ri(E) 

        The bracket above is in fact :    1 - cos^2 th = sin^2 th  which must be +ve 
        so getting -ve numbers of photons is clearly a bug from the numerical approximations
        being made.  Presumably the problem is due to the splitting of the integral into  
        CerenkovAngleIntegral "cai" which is the cumulative integral of   1./ri(E)^2
        followed by linear interpolation of this in order to get the integral between 
        crossings.

        G4Cerenkov::

             Rfact = 369.81/(eV * cm);

        https://www.nevis.columbia.edu/~haas/frank_epe_course/cherenkov.ps
        has 370(eV.cm)^-1

        ~/opticks_refs/nevis_cherenkov.ps

        hc = 1240 eV nm = 1240 eV cm * 1e-7    ( nm:1e-9 cm 1e-2)

        In [8]: 2*np.pi*1e7/(137*1240)     # fine-structure-constant 1/137 and hc = 1240 eV nm 
        Out[8]: 369.860213514221

        alpha/hc = 370 (eV.cm)^-1

        See ~/opticks/examples/UseGeant4/UseGeant4.cc UseGeant4::physical_constants::

            UseGeant4::physical_constants
                                                   eV 1e-06
                                                   cm 10
                                 fine_structure_const 0.00729735
                        one_over_fine_structure_const 137.036
              fine_structure_const_over_hbarc*(eV*cm) 369.81021
                      fine_structure_const_over_hbarc 36981020.84589
                            Rfact =  369.81/(eV * cm) 36981000.00000[as used by G4Cerenkov::GetAverageNumberOfPhotons] 
                                  2*pi*1e7/(1240*137) 369.86021
                                                eplus 1.00000
                                     electron_mass_c2 0.51099891
                                       proton_mass_c2 938.27201300
                                      neutron_mass_c2 939.56536000


        Crossing points from similar triangles:: 


             x - prevPM                            currentPM - prevPM
             ------------------------------   =     ------------------------ 
             BetaInverse - prevRI                   currentRI - prevRI 


             x - prevPM =  (BetaInverse-prevRI)/(currentRI-prevRI)*(currentPM-prevPM) 



                             (currentPM, currentRI)
                                +                   
                               /
                              /
                             /
                            /
                           /
                          /
                         *
                        /  (x,BetaInverse)
                       /                
                      /
                     /
                    /
                   +                                
            (prevPM, prevRI)            


        """
        self.BetaInverse = BetaInverse
        ri = self.ri
        cai = self.cai
        en = ri[:, 0]

        cross = self.FindCrossings(ri, BetaInverse)
        if dump:
            print(" cross %s " % repr(cross))
        pass
        self.cross = cross

        dp1 = 0.
        ge1 = 0.

        for i in range(len(cross) // 2):
            en0 = cross[2 * i + 0]
            en1 = cross[2 * i + 1]
            dp1 += en1 - en0

            # interpolating the cai is an approximation that is the probable cause of NumPhotons
            # going negative for BetaInverse close to the "peak" of rindex

            cai0 = np.interp(en0, en, cai)
            cai1 = np.interp(en1, en, cai)
            ge1 += cai1 - cai0
        pass
        Rfact = 369.81  #  (eV * cm)^-1
        Rfact *= 0.1  # cm to mm ?

        NumPhotons = Rfact * charge * charge * (
            dp1 - ge1 * BetaInverse * BetaInverse)

        self.dp1 = dp1
        self.ge1 = ge1
        self.NumPhotons = NumPhotons

        if dump:
            print(" dp1 %10.4f ge1 %10.4f " % (dp1, ge1))
        pass

        return NumPhotons

    @classmethod
    def FindCrossings(cls, pq, pv):
        """
        :param pq: property array of shape (n,2)
        :param pv: scalar value
        :return cross: array of values where pv crosses the linear interpolated pq 
        """
        assert len(pq.shape) == 2 and pq.shape[1] == 2

        mx = pq[:, 1].max()
        mi = pq[:, 1].min()

        cross = []
        if pv <= mi:
            cross.append(pq[0, 0])
            cross.append(pq[-1, 0])
        elif pv >= mx:
            pass
        else:
            if pq[0, 1] >= pv:
                cross.append(pq[0, 0])
            pass
            assert len(pq) > 2
            for ii in range(1, len(pq) - 1):
                prevPM, prevRI = pq[ii - 1]
                currPM, currRI = pq[ii]

                down = prevRI >= pv and currRI < pv
                up = prevRI < pv and currRI >= pv
                if down or up:
                    cross.append((pv - prevRI) / (currRI - prevRI) *
                                 (currPM - prevPM) + prevPM)
                pass
            pass
            if pq[-1, 1] >= pv:
                cross.append(pq[-1, 1])
            pass
        pass
        assert len(cross) % 2 == 0, cross
        return cross

    def test_GetAverageNumberOfPhotons(self, BetaInverse, dump=False):
        NumPhotons_asis = self.GetAverageNumberOfPhotons_asis(BetaInverse)
        NumPhotons_s2 = self.GetAverageNumberOfPhotons_s2(BetaInverse)
        NumPhotons_s2messy = self.GetAverageNumberOfPhotons_s2messy(
            BetaInverse)
        res = np.array(
            [BetaInverse, NumPhotons_asis, NumPhotons_s2, NumPhotons_s2messy])
        if dump:
            fmt = "BetaInverse %6.4f _asis %6.4f  _s2 %6.4f _s2messy %6.4f    "
            print(fmt % tuple(res))
        pass
        return res

    def scan_GetAverageNumberOfPhotons(self, x0=1., x1=2., nx=1001):
        """
        Creates ckn.scan comparing GetAverageNumberOfPhotons from three algorithms
        across the domain of BetaInverse.

        * GetAverageNumberOfPhotons_asis 
        * GetAverageNumberOfPhotons_s2
        * GetAverageNumberOfPhotons_s2messy

        ::

            In [12]: ckn.scan
            Out[12]: 
            array([[  1.    , 293.2454, 293.2454, 293.2454],
                   [  1.001 , 292.7999, 292.7999, 292.7999],
                   [  1.002 , 292.354 , 292.354 , 292.354 ],
                   ...,
                   [  1.998 ,   0.    ,   0.    ,   0.    ],
                   [  1.999 ,   0.    ,   0.    ,   0.    ],
                   [  2.    ,   0.    ,   0.    ,   0.    ]])

            In [13]: ckn.scan.shape
            Out[13]: (1001, 4)

        """
        scan = np.zeros((nx, 4), dtype=np.float64)
        for i, BetaInverse in enumerate(np.linspace(x0, x1, nx)):
            NumPhotons_asis = self.GetAverageNumberOfPhotons_asis(BetaInverse)
            NumPhotons_s2 = self.GetAverageNumberOfPhotons_s2(BetaInverse,
                                                              s2cross=False)
            NumPhotons_s2cross = self.GetAverageNumberOfPhotons_s2(
                BetaInverse, s2cross=True)
            #NumPhotons_s2messy = self.GetAverageNumberOfPhotons_s2messy(BetaInverse)
            scan[i] = [
                BetaInverse, NumPhotons_asis, NumPhotons_s2, NumPhotons_s2cross
            ]
            fmt = " bi %7.3f _asis %7.3f _s2 %7.3f _s2cross %7.3f "
            if i % 100 == 0: print("%5d : %s " % (i, fmt % tuple(scan[i])))
        pass
        self.scan = scan
        self.numPhotonASIS_ = lambda bi: np.interp(bi, self.scan[:, 0], self.
                                                   scan[:, 1])
        self.numPhotonS2_ = lambda bi: np.interp(bi, self.scan[:, 0], self.
                                                 scan[:, 2])
        self.nMin = self.ri[:, 1].min()
        self.nMax = self.ri[:, 1].max()

        d23 = scan[:, 2] - scan[:, 3]
        log.info(" d23.max %10.4f d23.min %10.4f " % (d23.max(), d23.min()))

    def scan_GetAverageNumberOfPhotons_plot(self, bir=None):

        ckn = self
        en = ckn.scan[:, 0]

        bi = [self.nMin, self.nMax] if bir is None else bir
        numPhotonMax = self.numPhotonS2_(np.linspace(
            bi[0], bi[1], 101)).max()  # max in the BetaInverse range

        extra = "%6.4f_%6.4f" % (bi[0], bi[1])

        titls = [
            "ana/ckn.py : scan_GetAverageNumberOfPhotons_plot %s " % extra,
            "_asis goes slightly negative near rindex peak due to linear interpolation approx on top of trapezoidal integration",
            "_s2 avoids that by doing the integration in one pass directly on s2 (sin^2 theta) and using s2 zero crossings to improve accuracy"
        ]

        title = "\n".join(titls)

        fig, ax = plt.subplots(figsize=ok.figsize)
        fig.suptitle(title)

        ax.set_xlim(*bi)
        ax.set_ylim(-1., numPhotonMax)

        ax.scatter(en,
                   ckn.scan[:, 1],
                   label="GetAverageNumberOfPhotons_asis",
                   s=3)
        ax.plot(en, ckn.scan[:, 1], label="GetAverageNumberOfPhotons_asis")

        ax.plot(en, ckn.scan[:, 2], label="GetAverageNumberOfPhotons_s2")
        ax.scatter(en,
                   ckn.scan[:, 2],
                   label="GetAverageNumberOfPhotons_s2",
                   s=3)

        xlim = ax.get_xlim()
        ylim = ax.get_ylim()
        ax.plot(xlim, [0, 0], linestyle="dotted", label="zero")

        for n in ckn.ri[:, 1]:
            ax.plot([n, n], ylim)
            #ax.plot( [n, n], ylim, label="%s" % n  )
        pass

        ax.legend()
        fig.show()

        self.save_fig(fig,
                      "scan_GetAverageNumberOfPhotons_plot_%s.png" % extra)

    def save_fig(self, fig, name):
        path = os.path.join(self.FOLD, name)
        if not os.path.exists(os.path.dirname(path)):
            os.makedirs(os.path.dirname(path))
        pass
        log.info("save to %s " % path)
        fig.savefig(path)
        assert os.path.exists(path)
        self.paths.append(path)

    def scan_GetAverageNumberOfPhotons_difference_plot(self):

        ckn = self
        bi = ckn.scan[:, 0]

        titls = [
            "ana/ckn.py : scan_GetAverageNumberOfPhotons_difference_plot : GetAverageNumberOfPhotons_s2 - GetAverageNumberOfPhotons_asis vs BetaInverse ",
            "refractive index values show by vertical lines",
            "parabolic nature of the difference because  _asis as using a linear approximation for a parabolic integral "
        ]

        title = "\n".join(titls)

        bi_range = [self.nMin, self.nMax]

        fig, ax = plt.subplots(figsize=ok.figsize)
        fig.suptitle(title)

        ax.set_xlim(*bi_range)
        #ax.set_ylim(  -1., numPhotonMax )

        ax.scatter(bi, ckn.scan[:, 2] - ckn.scan[:, 1], s=3)
        ax.plot(bi, ckn.scan[:, 2] - ckn.scan[:, 1])

        ylim = ax.get_ylim()

        for n in ckn.ri[:, 1]:
            #ax.plot( [n, n], ylim, label="%s" % n  )
            ax.plot([n, n], ylim)
        pass
        ax.legend()
        fig.show()

        self.save_fig(fig,
                      "scan_GetAverageNumberOfPhotons_difference_plot.png")

    def compareOtherScans(self):
        """
        This compares the python and C++ implementations 
        of the same double precision algorithms,
        agreement should be (and is) very good eg 1e-13 level 
        """
        self.compare_with_QCerenkovTest()
        self.compare_with_G4Cerenkov_modified()

    def compare_with_QCerenkovTest(self):
        """
        Create/update QCerenkovTest scan with::

            qu
            cd tests
            ./QCerenkovTest.sh 

        """
        qck_path = os.path.expandvars(
            "/tmp/$USER/opticks/QCerenkovTest/test_GetAverageNumberOfPhotons_s2.npy"
        )
        if os.path.exists(qck_path):
            self.qck_scan = np.load(qck_path)
        else:
            log.error("missing %s " % qck_path)
        pass

        ckn = self
        cf_qck_dom = np.abs(ckn.scan[:, 0] - ckn.qck_scan[:, 0])
        cf_qck_num = np.abs(ckn.scan[:, 2] - ckn.qck_scan[:, 1])

        log.info("cf_qck_dom.min*1e6 %10.4f cf_qck_dom.max*1e6 %10.4f " %
                 (cf_qck_dom.min() * 1e6, cf_qck_dom.max() * 1e6))
        log.info("cf_qck_num.min*1e6 %10.4f cf_qck_num.max*1e6 %10.4f " %
                 (cf_qck_num.min() * 1e6, cf_qck_num.max() * 1e6))

        assert cf_qck_dom.max() < 1e-10
        assert cf_qck_num.max() < 1e-10

    def compare_with_G4Cerenkov_modified(self):
        """
        Create/update scan arrays with::

            cks
            ./G4Cerenkov_modifiedTest.sh

        """
        cks_path = "/tmp/G4Cerenkov_modifiedTest/scan_GetAverageNumberOfPhotons.npy"
        if os.path.exists(cks_path):
            self.cks_scan = np.load(cks_path)
        else:
            log.error("missing %s " % cks_path)
        pass

        ckn = self
        cf_cks_dom = np.abs(ckn.scan[:, 0] - ckn.cks_scan[:, 0])
        cf_cks_num_asis = np.abs(ckn.scan[:, 1] - ckn.cks_scan[:, 1])
        cf_cks_num_s2 = np.abs(ckn.scan[:, 2] - ckn.cks_scan[:, 2])

        log.info("cf_cks_dom.min*1e6 %10.4f cf_cks_dom.max*1e6 %10.4f " %
                 (cf_cks_dom.min() * 1e6, cf_cks_dom.max() * 1e6))
        log.info(
            "cf_cks_num_asis.min*1e6 %10.4f cf_cks_num_asis.max*1e6 %10.4f " %
            (cf_cks_num_asis.min() * 1e6, cf_cks_num_asis.max() * 1e6))
        log.info("cf_cks_num_s2.min*1e6 %10.4f cf_cks_num_s2.max*1e6 %10.4f " %
                 (cf_cks_num_s2.min() * 1e6, cf_cks_num_s2.max() * 1e6))

        assert cf_cks_dom.max() < 1e-10
        assert cf_cks_num_asis.max() < 1e-10
        assert cf_cks_num_s2.max() < 1e-10

    def test_GetAverageNumberOfPhotons_plot(self, BetaInverse=1.7):
        """
        runs test_GetAverageNumberOfPhotons for a particular BetaInverse 
        in order to get the internals : cross, cai 
        """
        ckn = self
        res = ckn.test_GetAverageNumberOfPhotons(BetaInverse)

        titls = [
            "ana/ckn.py : test_GetAverageNumberOfPhotons_plot : %s " %
            str(res),
            "attempt to understand how _asis manages to go negative  "
        ]

        title = "\n".join(titls)

        cross = ckn.cross
        ri = ckn.ri
        cai = ckn.cai

        fig, axs = plt.subplots(1, 2, figsize=ok.figsize)
        fig.suptitle(title)

        ax = axs[0]
        ax.plot(ri[:, 0], ri[:, 1], label="linear interpolation")
        ax.scatter(ri[:, 0], ri[:, 1], label="ri")
        xlim = ax.get_xlim()
        ylim = ax.get_ylim()
        ax.plot(xlim, [BetaInverse, BetaInverse],
                label="BetaInverse:%6.4f" % BetaInverse)
        for e in cross:
            ax.plot([e, e], ylim, label="cross")
        pass

        ax.legend()

        ax = axs[1]
        ax.plot(ri[:, 0], cai, label="cai")
        ylim = ax.get_ylim()
        for e in cross:
            ax.plot([e, e], ylim, label="cross")
        pass
        ax.legend()

        fig.show()
        self.save_fig(fig, "test_GetAverageNumberOfPhotons_plot.png")

    def load_QCerenkov_s2slv(self):
        """
        s2slv: sliver integrals across domains of BetaInverse and energy 

            In [4]: ckn.s2slv.shape
            Out[4]: (1001, 280)           

        cs2slv: is cumulative sum of the above sliver integrals with the same shape

            In [5]: ckn.cs2slv.shape
            Out[5]: (1001, 280)

        s2slv_sum: sum over energy domain of s2slv

            In [8]: ckn.s2slv_sum.shape
            Out[8]: (1001,)

        cs2slv_last: last of the (energy axis) cumulative sum values : this very closely matches s2slv_sum

            In [9]: ckn.cs2slv_last.shape
            Out[9]: (1001,)

        c2slv_norm: energy axis cumsum divided by the last : making this the CDF 

            In [10]: ckn.cs2slv_norm.shape
            Out[10]: (1001, 280)

            Notice lots of nan, for CK disallowed BetaInverse

        """
        s2slv_path = "/tmp/blyth/opticks/QCerenkovTest/test_getS2SliverIntegrals_many.npy"
        log.info("load %s " % s2slv_path)
        s2slv = np.load(s2slv_path) if os.path.exists(s2slv_path) else None
        globals()["ss"] = s2slv

        cs2slv_path = "/tmp/blyth/opticks/QCerenkovTest/test_getS2SliverIntegrals_many_cs2slv.npy"
        log.info("load %s " % cs2slv_path)
        cs2slv = np.load(cs2slv_path) if os.path.exists(cs2slv_path) else None
        cs2slv_last = cs2slv[:, -1]

        cs2slv_norm_path = "/tmp/blyth/opticks/QCerenkovTest/test_getS2SliverIntegrals_many_cs2slv_norm.npy"
        log.info("load %s " % cs2slv_norm_path)
        cs2slv_norm = np.load(cs2slv_norm_path) if os.path.exists(
            cs2slv_norm_path) else None

        globals()["cs"] = cs2slv
        globals()["csl"] = cs2slv_last
        globals()["csn"] = cs2slv_norm

        self.s2slv = s2slv
        self.cs2slv = cs2slv
        self.cs2slv_last = cs2slv_last
        self.cs2slv_norm = cs2slv_norm

        s2slv_sum = s2slv.sum(axis=1)
        self.s2slv_sum = s2slv_sum

        sum_vs_last = np.abs(s2slv_sum - cs2slv_last)
        sum_vs_last_mx = sum_vs_last.max()
        log.info("sum_vs_last_mx %s " % sum_vs_last_mx)
        assert sum_vs_last_mx < 1e-10

    def load_QCerenkov_s2slv_plot(self):
        """

        """
        titls = [
            "ana/ckn.py : load_QCerenkov_s2slv_plot : compare sum of s2slv from many sliver bins to the rindex big bin result",
            "deviation of up to 0.4 of a photon between the sum of sliver integrals and the big bin integrals is as yet unexplained"
        ]
        title = "\n".join(titls)

        fig, ax = plt.subplots(figsize=ok.figsize)
        fig.suptitle(title)

        ax.plot(self.qck_scan[:, 0], self.qck_scan[:, 1], label="qck_scan")
        ax.plot(self.qck_scan[:, 0], self.s2slv_sum, label="s2slv_sum")
        ax.plot(self.qck_scan[:, 0], self.cs2slv_last, label="cs2slv_last")
        ax.legend()

        axr = ax.twinx()
        axr.plot(self.qck_scan[:, 0],
                 self.s2slv_sum - self.qck_scan[:, 1],
                 label="diff",
                 linestyle="dotted")
        axr.legend(loc='lower right')

        fig.show()

        self.ax = ax
Ejemplo n.º 10
0
class CKS(object):
    PATH = "/tmp/cks/cks.npy"

    kd = keydir()
    rindex_path = os.path.join(kd, "GScintillatorLib/LS_ori/RINDEX.npy")
    random_path = os.path.expandvars("/tmp/$USER/opticks/TRngBufTest_0.npy")

    def __init__(self):
        rnd = np.load(self.random_path)
        num = len(rnd)
        cursors = np.zeros(num, dtype=np.int32)

        self.rnd = rnd
        self.num = num

        rindex = np.load(self.rindex_path)
        rindex[:, 0] *= 1e6  # into eV
        rindex_ = lambda ev: np.interp(ev, rindex[:, 0], rindex[:, 1])

        Pmin = rindex[0, 0]
        Pmax = rindex[-1, 0]
        nMax = rindex[:, 1].max()

        self.rindex = rindex
        self.Pmin = Pmin
        self.Pmax = Pmax
        self.nMax = nMax

        self.rindex_ = rindex_
        self.cursors = cursors
        self.p = np.zeros((num, 4, 4), dtype=np.float64)

    def energy_sample_all(self, BetaInverse=1.5):
        for idx in range(self.num):
            self.energy_sample(idx, BetaInverse=BetaInverse)
        pass

    def energy_sample(self, idx, BetaInverse=1.5):
        rnd = self.rnd
        rindex = self.rindex
        rindex_ = self.rindex_
        cursors = self.cursors
        num = self.num
        Pmin = self.Pmin
        Pmax = self.Pmax
        nMax = self.nMax

        uu = rnd[idx].ravel()
        maxCos = BetaInverse / nMax
        maxSin2 = (1.0 - maxCos) * (1.0 + maxCos)

        dump = idx < 10 or idx > num - 10
        loop = 0

        while True:
            u0 = uu[cursors[idx]]
            cursors[idx] += 1

            u1 = uu[cursors[idx]]
            cursors[idx] += 1

            sampledEnergy = Pmin + u0 * (Pmax - Pmin)
            sampledRI = rindex_(sampledEnergy)
            cosTheta = BetaInverse / sampledRI
            sin2Theta = (1. - cosTheta) * (1. + cosTheta)

            u1_maxSin2 = u1 * maxSin2
            keep_sampling = u1_maxSin2 > sin2Theta

            loop += 1

            if dump:
                fmt = "idx %5d u0 %10.5f sampledEnergy %10.5f sampledRI %10.5f cosTheta %10.5f sin2Theta %10.5f u1 %10.5f"
                vals = (idx, u0, sampledEnergy, sampledRI, cosTheta, sin2Theta,
                        u1)
                print(fmt % vals)
            pass

            if not keep_sampling:
                break
            pass
        pass

        hc_eVnm = 1239.8418754200  # G4: h_Planck*c_light/(eV*nm)

        sampledWavelength = hc_eVnm / sampledEnergy

        p = self.p[idx]
        i = self.p[idx].view(np.uint64)

        p[0, 0] = sampledEnergy
        p[0, 1] = sampledWavelength
        p[0, 2] = sampledRI
        p[0, 3] = cosTheta

        p[1, 0] = sin2Theta
        i[3, 1] = loop

    def save(self):
        fold = os.path.dirname(self.PATH)
        if not os.path.exists(fold):
            os.makedirs(fold)
        pass
        log.info("save to %s " % self.PATH)
        np.save(self.PATH, self.p)

    @classmethod
    def Load(cls):
        return np.load(cls.PATH)
Ejemplo n.º 11
0
            In [44]: nrpo[nrpo[:,1] == 5]                                                                                                                                                                    
            Out[44]: 
            array([[ 3199,     5,     0,     0],
                   [ 3200,     5,     0,     1],
                   [ 3201,     5,     0,     2],
                   ...,
                   [11410,     5,   671,     2],
                   [11411,     5,   671,     3],
                   [11412,     5,   671,     4]], dtype=uint32)

        """
        nidx = np.arange(len(tid), dtype=np.uint32)
        ridx,pidx,oidx = cls.Decode(tid)  
        nrpo = np.zeros( (len(tid),4), dtype=np.uint32 )
        nrpo[:,0] = nidx
        nrpo[:,1] = ridx
        nrpo[:,2] = pidx
        nrpo[:,3] = oidx
        return nrpo

     
if __name__ == '__main__':
    import os, numpy as np
    from opticks.ana.key import keydir
    avi = np.load(os.path.join(keydir(),"GNodeLib/all_volume_identity.npy"))
    tid = avi[:,1] 
    nrpo = OpticksIdentity.NRPO(tid)