Commit 27670925eedeebeb2ee4fe8768f38fa9fd917561

Authored by Jürgen Knödlseder
1 parent e545180c

Implement D1 multi-scatter transmission (#2186)

inst/com/include/GCOMInstChars.hpp
... ... @@ -85,6 +85,8 @@ private:
85 85 void read_pos(const GFitsTable& table, std::vector<double>& x,
86 86 std::vector<double>& y);
87 87 void read_selfveto(const GFitsTable& table);
  88 + double ne213a_mfpath(const double& energy) const;
  89 + double kn_cross_section(const double& x, const double& alpha) const;
88 90 double min_coeff(const std::vector<double>& coeffs) const;
89 91 double max_coeff(const std::vector<double>& coeffs) const;
90 92  
... ... @@ -111,6 +113,8 @@ private:
111 113 std::vector<double> m_d1pos_y; //!< D1 y-position (cm)
112 114 std::vector<double> m_d2pos_x; //!< D2 x-position (cm)
113 115 std::vector<double> m_d2pos_y; //!< D2 y-position (cm)
  116 + GNodeArray m_mfpath_energies; //!< NE213A mean free path energies (MeV)
  117 + std::vector<double> m_mfpath_coeffs; //!< NE213A mean free path values
114 118 double m_d1dens; //!< D1 density (g/cm^-3)
115 119 double m_d1rad; //!< D1 radius (cm)
116 120 double m_d1thick; //!< D1 thickness (cm)
... ...
inst/com/src/GCOMIaq.cpp
... ... @@ -55,7 +55,7 @@
55 55 //#define G_DEBUG_KLEIN_NISHINA
56 56 //#define G_DEBUG_KLEIN_NISHINA_INTEGRAL
57 57 //#define G_DEBUG_RESPONSE_KERNEL
58   -//#define G_DEBUG_WEIGHT_IAQ
  58 +#define G_DEBUG_WEIGHT_IAQ
59 59 //#define G_DEBUG_SET_CONTINUUM
60 60 //#define G_DEBUG_LOCATION_SMEARING
61 61 //#define G_DEBUG_LOCATION_SPREAD
... ...
inst/com/src/GCOMInstChars.cpp
... ... @@ -705,11 +705,145 @@ double GCOMInstChars::prob_no_selfveto(const double&amp; energy, const double&amp; zenit
705 705 ***************************************************************************/
706 706 double GCOMInstChars::multi_scatter(const double& energy, const double& phigeo) const
707 707 {
708   - // Initialise fraction
709   - double fraction = 1.0;
  708 + // Set integration step size
  709 + const int nz = 10; //!< Number of steps in z-direction
  710 + const int nphi = 20; //!< Number of azimuthal steps
  711 +
  712 + // Compute stuff
  713 + double alpha = energy / gammalib::mec2;
  714 + double sth = std::sin(phigeo * gammalib::deg2rad);
  715 + double cth = std::cos(phigeo * gammalib::deg2rad);
  716 +
  717 + // Get the attenuation coefficient for NE213A
  718 + double mu = 1.0 / ne213a_mfpath(energy);
  719 +
  720 + // Compute the energy after scattering and the corresponding attenuation
  721 + double epsnew = energy / (1.0 + alpha * (1.0 - cth));
  722 + double muscat = 1.0 / ne213a_mfpath(epsnew);
  723 +
  724 + // Compute the full vertical optical depth of the module
  725 + double tau0 = mu * m_d1thick;
  726 +
  727 + // Compute the total number of interactions
  728 + double totint = 1.0 - std::exp(-tau0);
  729 +
  730 + // Perform integration per rho-value (symmetry!) over
  731 + // (1) z (the geometrical depth in the module)
  732 + // (2) phi (azimuth of scatter)
  733 + // at geometrical scatter angle phigeo.
  734 + double deltaz = m_d1thick / double(nz);
  735 + double deltau = deltaz * mu;
  736 + double dltphi = gammalib::pi / double(nphi);
  737 +
  738 + // Initialise integration loop. The binning in radius is based on
  739 + // constant surface per cylinder
  740 + double rho = 1.0;
  741 + double delrho = 2.0 * rho;
  742 + double rholow = rho - 0.5 * delrho;
  743 + double rhoupp = rho + 0.5 * delrho;
  744 + double surstp = 2.0 * delrho * rho;
  745 +
  746 + // Initialise weights
  747 + std::vector<double> w(100, 0.0);
  748 + std::vector<double> r(100*nphi, 0.0);
  749 + w[0] = surstp;
  750 +
  751 + int klast = 0;
  752 + double kappa = deltau / double(nphi); // Integration normalisation value
  753 +
  754 + // Start integration loop
  755 + double conrho = 0.0;
  756 + for (int krho = 0; krho < 100; ++krho) {
  757 +
  758 + // If we have reached the end then exit the loop now
  759 + if (klast > 0) {
  760 + break;
  761 + }
710 762  
711   - // Return fraction
712   - return fraction;
  763 + // Determine radius limits. Do not go beyond D1 module radius
  764 + if (krho > 0) {
  765 + rholow = rhoupp;
  766 + rhoupp = std::sqrt(surstp + rholow*rholow);
  767 + rho = 0.5 * (rhoupp + rholow);
  768 + w[krho] = surstp;
  769 + if (rhoupp > m_d1rad) {
  770 + rhoupp = m_d1rad;
  771 + rho = 0.5 * (rhoupp + rholow);
  772 + w[krho] = (rhoupp*rhoupp - rholow*rholow);
  773 + klast = krho;
  774 + }
  775 + }
  776 +
  777 + // Compute stuff for all azimuth angles
  778 + for (int lphi = 0; lphi < nphi; ++lphi) {
  779 + double phi = dltphi * (lphi + 0.5);
  780 + double term = rho * std::sin(phi);
  781 + r[krho+lphi*100] = -rho * std::cos(phi) +
  782 + std::sqrt(m_d1rad*m_d1rad - term*term);
  783 + }
  784 +
  785 + // Perform integration over depth
  786 + double conz = 0.0;
  787 + for (int kz = 0; kz < nz; ++kz) {
  788 +
  789 + // Calculate depth
  790 + double z = (kz + 0.5) * deltaz;
  791 + double tau = z * mu;
  792 +
  793 + // Perform integration over azimuth
  794 + double conphi = 0.0;
  795 + for (int kphi = 0; kphi < nphi; ++kphi) {
  796 +
  797 + // Test background/forward scattering
  798 + double length = 1000.0;
  799 + if (sth < 0.99) {
  800 + if (cth < 0.0) {
  801 + length = z/std::abs(cth);
  802 + }
  803 + else {
  804 + length = (m_d1thick-z)/cth;
  805 + }
  806 + }
  807 +
  808 + //
  809 + if (length * sth > r[krho+kphi*100]) {
  810 + length = r[krho+kphi*100] / sth;
  811 + }
  812 +
  813 + // Update the contribution to 1 pixel (rho,phi,z)
  814 + // from scatter phigeo
  815 + double tauran = length * muscat;
  816 +
  817 + // conphi determines contribution over phi-rings (KN
  818 + // independent of phi) => Contribution to a (rho,z)
  819 + // pixel
  820 + conphi += std::exp(-tauran);
  821 +
  822 + } // endfor: looped over azimuth
  823 +
  824 + // Average contribution must later be divided by NPHI for
  825 + // phi-pixels and multiplied by DELTAU. Do this at end, to
  826 + // save computation
  827 + conz += conphi * std::exp(-tau);
  828 +
  829 + } // endfor: looped over depth
  830 +
  831 + // Update ...
  832 + conrho += conz * kappa * w[krho];
  833 +
  834 + } // endfor: looped over radius
  835 +
  836 + // Determine sum of weight factors for rho-average
  837 + double sumw = 0.0;
  838 + for (int krho = 0; krho < klast; ++krho) {
  839 + sumw += w[krho];
  840 + }
  841 +
  842 + // Compute average
  843 + double trsing = conrho / (sumw * totint);
  844 +
  845 + // Return
  846 + return trsing;
713 847 }
714 848  
715 849  
... ... @@ -1367,6 +1501,77 @@ void GCOMInstChars::read_selfveto(const GFitsTable&amp; table)
1367 1501  
1368 1502  
1369 1503 /***********************************************************************//**
  1504 + * @brief Return NE213A mean free path
  1505 + *
  1506 + * @param[in] energy Energy (MeV)
  1507 + * @return Mean free path (cm)
  1508 + *
  1509 + * The mean free path as a function of energy, linearly interpolated between
  1510 + * data-points. Table of mean free paths for NE213A taken from L. Kuiper
  1511 + * calculated in JOB ROL-SPDSD1-43. This includes (1) Compton scattering and
  1512 + * (2) Pair production.
  1513 + ***************************************************************************/
  1514 +double GCOMInstChars::ne213a_mfpath(const double& energy) const
  1515 +{
  1516 + // NE213A mean free path values for energies from 1,2,...,30 MeV
  1517 + const double mfpath[] = {16.19, 23.21, 29.01, 34.04, 38.46,
  1518 + 42.35, 45.64, 48.81, 51.53, 54.17,
  1519 + 56.15, 58.09, 59.96, 61.74, 63.41,
  1520 + 64.62, 65.80, 66.94, 68.04, 69.09,
  1521 + 69.86, 70.61, 71.33, 72.03, 72.70,
  1522 + 73.33, 73.93, 74.50, 75.03, 75.51};
  1523 + const double alpat1 = 1.95695;
  1524 +
  1525 + // Initialise mfpath
  1526 + double result = 0.0;
  1527 +
  1528 + // Do a Klein-Nishina extrapolation for small energies
  1529 + if (energy < 2.0) {
  1530 + double alpate = energy / gammalib::mec2;
  1531 + double sigat1 = kn_cross_section(1.0, alpat1) -
  1532 + kn_cross_section(-1.0, alpat1);
  1533 + double sigate = kn_cross_section(1.0, alpate) -
  1534 + kn_cross_section(-1.0, alpate);
  1535 + result = mfpath[0] * sigat1 / sigate;
  1536 + }
  1537 +
  1538 + // ... otherwise do a linear interpolation
  1539 + else {
  1540 + int index = int(energy) - 1;
  1541 + if (index >= 29) {
  1542 + index = 28;
  1543 + }
  1544 + double slope = mfpath[index+1] - mfpath[index];
  1545 + result = mfpath[index] + slope * (energy - double(index + 1));
  1546 + }
  1547 +
  1548 + // Return result
  1549 + return result;
  1550 +}
  1551 +
  1552 +
  1553 +/***********************************************************************//**
  1554 + * @brief Return integrated Klein-Nishina cross section
  1555 + *
  1556 + * @param[in] x cos(theta)
  1557 + * @param[in] alpha E/mc^2
  1558 + * @return Integrated Klein-Nishina cross section
  1559 + ***************************************************************************/
  1560 +double GCOMInstChars::kn_cross_section(const double& x, const double& alpha) const
  1561 +{
  1562 + // Compute integrated Klein-Nishina cross section
  1563 + double y = 1.0 + alpha * (1.0 - x);
  1564 + double kn = 1.0 / (2.0 * alpha * y*y) -
  1565 + std::log(y)/alpha +
  1566 + (x*x - 1)/(alpha*y) +
  1567 + 2.0 * (x + ((1.0 + alpha)/alpha) * std::log(y)) / (alpha*alpha);
  1568 +
  1569 + // Return
  1570 + return kn;
  1571 +}
  1572 +
  1573 +
  1574 +/***********************************************************************//**
1370 1575 * @brief Return minimum coefficient.
1371 1576 *
1372 1577 * @param[in] coeffs Coefficients.
... ...