Opticks+JUNO : PMT Mask Bugs, GPU Multilayer TMM

Opticks+JUNO : PMT Mask Bugs, GPU Multilayer TMM

Open source, https://bitbucket.org/simoncblyth/opticks

Opticks replaces Geant4 optical photon simulation with an equivalent implementation that benefits from state-of-the-art GPU ray tracing from NVIDIA OptiX, which can yield performance >1000x Geant4.

All optically relevant aspects of Geant4 context are translated+copied to GPU:

  • geometry : solids, structure, material+surface properties
  • generation : Cerenkov + Scintillation (using Gensteps from Geant4)
  • propagation : Rayleigh scattering, absorption, reemission, boundary, +multi-layer TMM (WIP)

Tests Reveal : More Opticks+JUNO Geom. Issues ALL KNOWN, FIXED

  • WIP : Bringing multi-layer TMM "FastSim" into Opticks

Simon C Blyth, IHEP, CAS — 17 Nov 2022


JUNO + Opticks : Summary of Bugs Fixed

OPTICKS PRIM ISSUES

nmskSolidMask : ellipsoid hole at "apex" issue

nmskSolidMaskTail : very thin cylinder "lip" issues

nmskSolidMaskVirtual : cone precision + near-apex issues


nmskSolidMask_RandomSpherical10_another_zsphere_apex_miss_perhaps.png

rare close to ellipsoid "apex" rays missed : fixed with zcut+safety

nmskSolidMask__U0,nmskSolidMaskTail__U0_without_uncoincide.png

without uncoincidence the subtraction has coincident edge

nmskSolidMask__U1,nmskSolidMaskTail__U1_U1_NNVTMaskManager_with_uncoincide.png

much better after uncoinciding, but upper lip still has small issue

some spurious intersects in center and at the edge

nmskSolidMaskTail_RandomSpherical10_cehigh_problem_areas.png

illuminate problem areas with more gensteps, issues all in plane Z=-39 mm

nmskSolidMaskTail__U1_thin_lip_issue.png

extg4/x4t.sh : Geant4 reference intersects

nmskTailOuter__U1_nmskTailInner__U1_tail_outer_inner_subtraction.png

extg4/cf_x4t.sh : blue:nmskTailOuter orange:nmskTailInner

nmskTailOuter__U1_nmskTailInner__U1_spurious_lip_halo.png

CSG/cf_ct.sh : Opticks CSG running on CPU

Missing thin cylinder edge intersects

CAUSED : by mis-translation of very thin cylinder as "disc" not "cylinder"

nmskSolidMaskTail__U1_thin_cylinder_lip_splash.png

CSG/ct.sh : PMT mask "lip" reveals issue with thin cylinder (hz 0.15 mm)

nmskTailOuterITube__U1_thin_cylinder_precision_issue.png

CSG/ct.sh : hz 0.15 mm thin cylinder precision loss issue

nmskTailOuterITube__U1_thin_cylinder_spurious_intersects_from_near_axial_rays.png

cd ~/opticks/CSG ; UNEXPECTED=1 NOLEGEND=1 ./ct.sh ana

near axial rays suffer precision loss -> spurious

FIX : reimplemented CSG_CYLINDER to avoid precision loss

Loss of precision issue : only apparent with very thin cylinders

CSG_OLDCYLINDER : CSG/csg_intersect_leaf_oldcylinder.h::intersect_leaf_oldcylinder

CSG_CYLINDER : CSG/csg_intersect_leaf.h::intersect_leaf_cylinder

CSG/tests/CSGIntersectComparisonTest.sh

Comparison of cylinder implementations

nmskSolidMaskTail__U1_new_cylinder_impl_avoids_precision_loss.png

FOCUS=-257,-39,7 ./ct.sh ana  ## using CSG/tests/CSGSimtraceTest.{cc/py}

new cylinder impl : avoids spurious + improves precision + simpler

nmskSolidMaskVirtual_spurious_sprinkle.png

Even with uncoincidence : still left with sprinkle between cylinder and cone

nmskSolidMaskVirtual_oops_uncoincidence_geom_change.png

Uncoincidence : expanded cylinder up, in this case changing geometry

nmskSolidMaskVirtual_without_uncoincidence.png

Without Uncoincidence : clear spurious lines cyl-cyl-cone

nmskSolidMaskVirtual_spurious_from_quadratic_precision_loss.png

c2 t^2 + 2 c1 t + c0 = 0

disc = c1*c1 - c0*c2,  for small c0 or c2 get precision loss in one root

csg_robust_quadratic_roots.h

Solution : pick t OR 1/t quadratic roots depending on coefficients : avoiding precision loss


Normal Quadratic                    Alternative quadratic in 1/t
-----------------                   --------------------------------

d t^2 + 2b t + c = 0                c (1/t)^2 + 2b (1/t) + d  = 0


    -2b +- sqrt((2b)^2 - 4dc )             -2b +- sqrt( (2b)^2 - 4dc )
t =  --------------------------     1/t =  ----------------------------
               2d                                    2c

     -b +- sqrt( b^2 - d c )        1/t =  -b  +- sqrt( b^2 - d c )
t =  -----------------------               -------------------------
             d                                       c

                                                   c
                                    t =  ---------------------------
                                         -b  +-  sqrt( b^2 - d c )


Debug Precision Loss With CSG/nmskSolidMaskVirtual.sh

c # cd ~/opticks/CSG

./nmskSolidMaskVirtual.sh withnudge # GeoChain translate

./nmskSolidMaskVirtual.sh ct # intersect using ct.sh, CSGSimtraceTest.cc

./nmskSolidMaskVirtual.sh ana # plot with CSGSimtraceTest.py

./nmskSolidMaskVirtual.sh unx # select unexpected, save to array

./nmskSolidMaskVirtual.sh sample # rerun intersect for selected, using CSGSimtraceSample.cc

 //intersect_leaf_oldcone t_near_alt      477.8 t_far_alt  1.225e+09 t_near_alt-t_near         17 t_far_alt-t_far          0
 //intersect_leaf_oldcone r1   264.0500 z1    97.0000 r2   132.0250 z2   194.0500 : z0   291.1000
 //intersect_leaf_oldcone c2    -0.0000 c1   365.0782 c0 -348871.4688 disc 133281.9219 disc > 0.f 1 : tth    -1.3604
 //intersect_leaf_oldcone c0 -3.489e+05 c1      365.1 c2  -5.96e-07 t_near      460.8 t_far  1.225e+09 sdisc   365.0780 (-c1-sdisc)     -730.2 (-c1+sdisc) -0.0002747
 //intersect_leaf_oldcone t_near_alt      477.8 t_far_alt  1.225e+09 t_near_alt-t_near         17 t_far_alt-t_far          0
                              - HIT
                     q0 norm t (   -0.5457    0.0000    0.8380  460.8000)
                    q1 ipos sd ( -212.8504    0.0000  114.4940    0.0000)- sd < SD_CUT :    -0.0010
              q2 ray_ori t_min (  158.4300    0.0000 -158.4300)
               q3 ray_dir gsid (   -0.8057    0.0000    0.5923 C4U (     0    0    0  255 ) )

  o.x            158.4300 v.x             -0.8057 t0(-o.x/v.x)   196.6291 z0             -41.9699
 2022-09-17 16:05:30.725 INFO  [9037364] [CSGSimtraceSample::intersect@89] CSGSimtraceSample::desc

nmskSolidMaskVirtual_apex_glancers.png

18 rays with unexpected cyl-cone intersects : all extend close to cone apex

CSG_CONE reimplemented : avoid apex issue + precision loss

CSG_OLDCONE

makes incorrect assumption that all rays intersect infinite cone

NOT TRUE FOR RAYS CLOSE TO APEX

https://bitbucket.org/simoncblyth/opticks/src/master/CSG/csg_intersect_leaf_oldcone.h

CSG_CONE

cap intersects indep of cone intersects + adopt robust_quadratic_roots

https://bitbucket.org/simoncblyth/opticks/src/master/CSG/csg_intersect_leaf_newcone.h

nmskSolidMaskVirtual_new_CSG_CONE_avoids_apex_and_precision_loss_issues.png

New CSG_CONE avoids apex and precision loss issues in nmskSolidMaskVirtual

J003_DownXZ1000_before_fixes.png

nmsk_nnvt_solids_STUVWXY_single_together_for_overlap.png

NNVT : TWO overlap issues visible, one was fixed by updating to latest junosw, see next page

nmsk_nnvt_solids_STUVWXY_nnvt_virtualMask_Mask_overlap.png

NNVT : ONE overlap issue visible, MaskTail impinges MaskVirtual

(using ct.sh : Opticks CSG on CPU)

nmsk_nnvt_solids_STUVWXY_x4t_reference.png

NNVT : x4t.sh Geant4 spurious intersects visible (cf prior)

(using x4t.sh : Geant4 intersects)

hmsk_hama_solids_STUVWXY_BodySolid_x_MaskTail.png

HAMA : ONE overlap issue, BodySolid impinges MaskTail

(using ct.sh : Opticks CSG on CPU)

hmsk_hama_solids_STUVWXY_fix-bug-33.png

HAMA : following fix of bug 33 using ellipsoid shift

(using ct.sh : Opticks CSG on CPU)

hmsk_hama_solids_STUVWXY_zoom_impinge.png

                              HAMA : BodySolid impinges MaskTail (mct.sh)

hmsk_hama_solids_STUVWXY_after_bug_33_fix.png

                              HAMA : after bug 33 fix

nmsk_nnvt_solids_STUVWXY_n_issue_mask_virtual_not_mask_solid.png

                              NNVT : MaskTail impinges MaskVirtual (mct.sh)

J004_Hama:0:1000_insitu.png

                              J004_Hama:0:1000 g4cx/gxt.sh

J004_Hama:0:1000_insitu_legend.png

                              J004_Hama:0:1000 g4cx/gxt.sh

J004_NNVT:0:1000_insitu_legend.png

                              J004_NNVT:0:1000 g4cx/gxt.sh

Multi-Layer TMM "FastSim" with Opticks ?

TMM "ART" calc : complex math

Access to thickness and energy dependent : rindex, kindex

CPU : using NP interpolation : STATUS : works fine

GPU : using qprop.h interpolation : STATUS : works in isolation

"FastSim" integration of ART calc ? : STATUS : Initial development of CPU test machinery

Standalone test of single PMT with junoPMTOpticalModel : integrating Opticks photon history recording

Layr.h : GPU/CPU header-only re-implementation of MultiFilmSimSvc

template<typename T> struct Layr
{
    T  d   ; // thickness : zero means incoherent
    T  pad ;

#ifdef WITH_THRUST
    thrust::complex  n, st, ct, rs, rp, ts, tp ;
#else
    std::complex     n, st, ct, rs, rp, ts, tp ;
#endif
    Matx S, P ;
};

template <typename T, int N> struct Stack
{
    Layr<T> ll[N] ; // stack of N layers   
    Layr<T> comp ;  // composite for the N layers  
    ART_<T>  art ;  // results eg A,R,T

    Stack(T wl, T minus_cos_theta, const StackSpec<T>& ss);
    // all calculations in ctor
};

https://github.com/simoncblyth/j/blob/main/Layr/Layr.h Implemented simply : compiles with nvcc/gcc for GPU/CPU

Complex math on GPU/CPU USING thrust::complex/std::complex


template<typename T, int N>
Stack<T,N>::Stack(
     T wl,
     T minus_cos_theta,
     const StackSpec<T>& ss )
{
    // minus_cos_theta, aka dot(mom,normal)
#ifdef WITH_THRUST
    using thrust::complex ;
    using thrust::norm ;
    using thrust::conj ;
    using thrust::exp ;
    using thrust::sqrt ;
    using thrust::sin ;
    using thrust::cos ;
#else
    using std::complex ;
    using std::norm ;
    using std::conj ;
    using std::exp ;
    using std::sqrt ;
    using std::sin ;
    using std::cos ;
#endif

    //  same complex TMM math works on both GPU and CPU 

}

LayrMinimal.cc : Minimal test of Layr.h on CPU

#include "Layr.h"

//typedef double T ;
typedef float T ;

int main(int argc, char** argv)
{
    T mct = argc > 1 ? std::atof(argv[1]) : -1.f  ;
    // minus_cos_theta

    T wl  = argc > 2 ? std::atof(argv[2]) : 440.f ;
    // wavelength_nm

    StackSpec<T> spec(StackSpec<T>::EGet());
    // sensitive to L0, L1, L2, L3 envvars

    Stack<T,4> stack(wl, mct, spec );
    // ART calc done in ctor

    std::cout << spec << stack ;
    return 0 ;
}

LayrTest.{sh,cc,h,cu,py} : AOI scan with float/double on CPU/GPU ...

 epsilon:Layr blyth$ ./LayrTest.sh ana
 CFLayrTest
  a :            EGet : scan__EGet__cpu_thr_double
  b :            EGet : scan__EGet__cpu_thr_float
  c :            EGet : scan__EGet__gpu_thr_double
  d :            EGet : scan__EGet__gpu_thr_float
  ...
  m :          R12860 : scan__R12860__cpu_pom_double
  n :          R12860 : scan__R12860__cpu_thr_double
  o :          R12860 : scan__R12860__cpu_thr_float
  p :          R12860 : scan__R12860__gpu_thr_double
  q :          R12860 : scan__R12860__gpu_thr_float

 In [1]: CF(m,q,0.05)
 Out[1]:
 CF(m,q,0.05) : scan__R12860__cpu_pom_double vs scan__R12860__gpu_thr_float
        lls :   0.000442 :   0.000442 :  -0.000414
      comps :   0.000341 :   0.000341 :  -6.17e-05
       arts :    6.2e-05 :    6.2e-05 :   -6.2e-05

 pmtcat:R12860 tt:5 lt:q : j/Layr/LayrTest scan__R12860__gpu_thr_float ni 900 wl 440
 +------------------------------+----------+----------+----------+----------+----------+
 |        R12860 arts\comps 0.05|     m:cpd|     n:ctd|     o:ctf|     p:gtd|     q:gtf|
 +==============================+==========+==========+==========+==========+==========+
 |                         m:cpd|         0| 0.0003325|  0.000301| 0.0003325| 0.0003407|
 +------------------------------+----------+----------+----------+----------+----------+   max difference of all param between scans 
 |                         n:ctd| 6.064e-05|         0| 4.829e-05| 7.445e-14| 4.829e-05|
 +------------------------------+----------+----------+----------+----------+----------+
 |                         o:ctf| 5.454e-05| 6.101e-06|         0| 4.829e-05| 3.977e-05|
 +------------------------------+----------+----------+----------+----------+----------+
 |                         p:gtd| 6.064e-05| 1.321e-14| 6.101e-06|         0| 4.829e-05|
 +------------------------------+----------+----------+----------+----------+----------+
 |                         q:gtf| 6.199e-05| 1.523e-06| 7.451e-06| 1.523e-06|         0|
 +------------------------------+----------+----------+----------+----------+----------+

 In [1]: ARTPlot(m, 0.05)

LayrTest_R12860_scan.png

LayrTest_NNVTMCP_scan.png

LayrTest_NNVTMCP_HiQE_scan.png

LayrTest_EGet_scan.png

QPMT.hh/qpmt.h : CPU/GPU -> on device PMT property interpolation

All LayrTest use CPU interpolated rindex, uploaded to device (OK for testing, not for production)

  1. uploads rindex, thickness arrays from JPMT.h
  2. on device interpolation using QProp.hh/qprop.h

https://bitbucket.org/simoncblyth/opticks/src/master/qudarap/QPMT.hh

https://bitbucket.org/simoncblyth/opticks/src/master/qudarap/tests/QPMTTest.sh

gpu_QPMTTest_interpolation.png

Standalone test of single PMT with junoPMTOpticalModel

j/PMTFastSim

u4/tests/U4PMTFastSimTest.sh

TODO:

Standalone test of single PMT with junoPMTOpticalModel Output


epsilon:~ blyth$ cd ~/opticks/u4/tests
epsilon:tests blyth$ ./U4PMTFastSimTest.sh
...
2022-11-16 19:13:45.319 INFO  [57385009] [U4Recorder::PostUserTrackingAction@85]
2022-11-16 19:13:45.319 INFO  [57385009] [U4Recorder::PostUserTrackingAction_Optical@173]
2022-11-16 19:13:45.319 INFO  [57385009] [U4Recorder::PreUserTrackingAction@84]
2022-11-16 19:13:45.319 INFO  [57385009] [U4Recorder::PreUserTrackingAction_Optical@126]
2022-11-16 19:13:45.319 INFO  [57385009] [U4Recorder::PreUserTrackingAction_Optical@141]  labelling photon spho (gs:ix:id:gn   0   0    0  0)
2022-11-16 19:13:45.319 INFO  [57385009] [U4Recorder::PreUserTrackingAction_Optical@155]  label.id 0
junoPMTOpticalModel::ModelTrigger
junoPMTOpticalModel::ModelTrigger WRONG VOLUME -> false
2022-11-16 19:13:45.320 INFO  [57385009] [U4Recorder::UserSteppingAction_Optical@232]
2022-11-16 19:13:45.320 INFO  [57385009] [U4Recorder::Check_TrackStatus_Flag@295]  step.tstat fAlive BOUNDARY_TRANSMIT
junoPMTOpticalModel::ModelTrigger
junoPMTOpticalModel::ModelTrigger ret 1
junoPMTOpticalModel::DoIt pmtid 0
junoPMTOpticalModel::DoIt dir (0.0000,0.0000,1.0000) norm (0.0131,0.0151,0.9998) _cos_theta1  0.9998 _aoi 1.1446 T 0.4088 R 0.3460 A 0.2452
junoPMTOpticalModel::DoIt stack
Stack
...
comp
Layr
  n:(    0.0000     0.0000)s  d:    0.0000
 st:(    0.0000     0.0000)s ct:(    0.0000     0.0000)s
 rs:(   -0.2237    -0.2554)s rp:(    0.2237     0.2554)s
 ts:(    0.0587     0.7751)s tp:(    0.0587     0.7751)s
S
| (    0.0972    -1.2828)s (    0.0617    -0.7541)s |
| (   -0.3494     0.2621)s (   -0.1667     0.6774)s |
P
| (    0.0972    -1.2828)s (   -0.0617     0.7541)s |
| (    0.3494    -0.2621)s (   -0.1667     0.6774)s |
ART_
 R_s     0.1153 R_p     0.1153
 T_s     0.4089 T_p     0.4089
 A_s     0.4759 A_p     0.4759
 R       0.1153 T       0.4089 A       0.4759 A_R_T     1.0000
 wl    501.0000
 mct     0.9998

2022-11-16 19:13:45.320 INFO  [57385009] [U4Recorder::UserSteppingAction_Optical@232]

Other Work : More Direct Geometry Translation + Launch Optimization

Current Geometry Translation

More Direct Translation Huge code reduction is feasible

U4Tree.hh/stree.h

minimal approach to geometry factorization

  • succeeds to factorize geometry
  • serialized n-ary tree, retains full structure info in translation
  • all transforms match GGeo/GInstancer
  • faster+simpler

Optimization of Initialization Time : using log parsing to find issues

More Detail on other work: https://bitbucket.org/simoncblyth/opticks/src/master/notes/progress.rst

hmsk_hama_solids_STUVWXY_fix-bug-33.png.2

                              Fake Vacuum/Vacuum boundary

                Is fake really needed ? Can easily anti-select opaque hits ? Motivation for U4PMTFastSimTest

Near Coincident Pyrex/Pyrex Boundary

m_enable_optical_model == false

                             pyrex
                             .
          |                 | |       |           |
          |                 | |       |           |
          |                 | |       |           |
     vac  |     pyrex       | | water |  acrylic  | water
          |                 | |       |           |
          |                 | |       |           |
          |                 | |       |           |
        inner            body pmt         mask

        -5 mm             0mm +1e-3mm


m_enable_optical_model == true

           pyrex
           .
          | |                 |       |          |
          | |                 |       |          |
          | |                 |       |          |
     vac  | |    pyrex        | water | acrylic  |  water
          | |                 |       |          |
          | |                 |       |          |
          | |                 |       |          |
      inner body             pmt         mask
        -5 -5+1e-3            +1e-3

Summary

Many Opticks + JUNO geometry bugs fixed

Multi-Layer TTM on GPU

ART approaches:

  1. calculation from uploaded props
  2. lookups from multi-Gigabyte textures