File indexing completed on 2026-10-30 08:33:53
0001 #ifndef LFHCAL_ADAPTIVE_LANGAU_H
0002 #define LFHCAL_ADAPTIVE_LANGAU_H
0003
0004
0005
0006
0007
0008 #include <algorithm>
0009 #include <array>
0010 #include <cmath>
0011 #include <functional>
0012 #include <stdexcept>
0013 #include <vector>
0014
0015 namespace lfhcal { namespace adaptive_langau {
0016 using Function = std::function<double(double)>;
0017 struct Parameters { double width, mp, area, sigma; };
0018 struct IntegrationConfig {
0019 double relative_tolerance = 1e-10;
0020 double gaussian_span = 5.;
0021 int max_calls = 500000;
0022 int max_depth = 18;
0023 double absolute_tolerance_fraction = .01;
0024 };
0025 struct PeakWidth { double peak, left, right, fwhm, height; };
0026
0027 inline void require(bool ok, const char* message) {
0028 if (!ok) throw std::runtime_error(message);
0029 }
0030 inline double checked(const Function& f, double x) {
0031 const double y = f(x);
0032 require(std::isfinite(y) && y >= 0., "nonfinite or negative Langau evaluation");
0033 return y;
0034 }
0035
0036 namespace detail {
0037
0038 constexpr std::array<double,8> nodes = {
0039 .095012509837637440185, .28160355077925891323,
0040 .45801677765722738634, .61787624440264374845,
0041 .75540440835500303390, .86563120238783174388,
0042 .94457502307323257608, .98940093499164993260};
0043 constexpr std::array<double,8> weights = {
0044 .18945061045506849629, .18260341504492358887,
0045 .16915651939500253819, .14959598881657673208,
0046 .12462897125553387205, .095158511682492784810,
0047 .062253523938647892863, .027152459411754094852};
0048 constexpr std::array<double,16> breakpoints = {
0049 -16.,-8.,-5.5,-4.,-2.,-1.,0.,1.,2.,4.,5.,8.,12.,16.,50.,300.};
0050
0051 inline double gauss16(const Function& f, double a, double b,
0052 int& calls, int maximum) {
0053 calls += 16;
0054 require(calls <= maximum, "Langau quadrature evaluation budget exhausted");
0055 const double c=(a+b)/2, d=(b-a)/2;
0056 double sum=0.;
0057 for (std::size_t i=0; i<nodes.size(); ++i)
0058 sum += weights[i]*(checked(f,c-d*nodes[i])+checked(f,c+d*nodes[i]));
0059 return d*sum;
0060 }
0061 inline double subdivide(const Function& f, double a, double b, double coarse,
0062 double atol, double rtol, int depth,
0063 int& calls, int maximum) {
0064 const double mid=(a+b)/2;
0065 const double l=gauss16(f,a,mid,calls,maximum);
0066 const double r=gauss16(f,mid,b,calls,maximum);
0067 const double fine=l+r;
0068 if (std::abs(fine-coarse) <= atol+rtol*std::abs(fine)) return fine;
0069 require(depth>0 && a<mid && mid<b, "Langau quadrature did not converge");
0070 return subdivide(f,a,mid,l,atol/2,rtol,depth-1,calls,maximum)
0071 + subdivide(f,mid,b,r,atol/2,rtol,depth-1,calls,maximum);
0072 }
0073 }
0074
0075
0076
0077 class Convolution {
0078 public:
0079 Convolution(Parameters p, Function standardLandau, IntegrationConfig cfg = {})
0080 : p_(p), pdf_(standardLandau), cfg_(cfg) {
0081 require(std::isfinite(p.width) && p.width>0 && std::isfinite(p.sigma) && p.sigma>0
0082 && std::isfinite(p.mp) && std::isfinite(p.area) && p.area>0,
0083 "invalid Langau parameters");
0084 require(cfg.relative_tolerance>0 && cfg.relative_tolerance<1
0085 && cfg.gaussian_span>=5 && cfg.gaussian_span<=12
0086 && cfg.max_calls>0 && cfg.max_depth>0
0087 && cfg.absolute_tolerance_fraction>0, "invalid Langau integration settings");
0088 }
0089 double operator()(double x) const {
0090 require(std::isfinite(x), "nonfinite Langau coordinate");
0091 const double mpc=p_.mp+.22278298*p_.width;
0092 const double lo=(x-cfg_.gaussian_span*p_.sigma-mpc)/p_.width;
0093 const double hi=(x+cfg_.gaussian_span*p_.sigma-mpc)/p_.width;
0094 std::vector<double> edges{lo,hi};
0095 const auto add=[&](double u) { if (u>lo && u<hi) edges.push_back(u); };
0096 for (double u:detail::breakpoints) add(u);
0097 const double extent=std::max(std::abs(lo),std::abs(hi));
0098 require(std::isfinite(extent) && extent<1e12, "Langau parameter ratio exceeds numerical domain");
0099 for (double u=32.; u<extent; u*=2.) { add(u); add(-u); }
0100 for (int k=-static_cast<int>(cfg_.gaussian_span);
0101 k<=static_cast<int>(cfg_.gaussian_span); ++k)
0102 add((x+k*p_.sigma-mpc)/p_.width);
0103 std::sort(edges.begin(),edges.end());
0104 edges.erase(std::unique(edges.begin(),edges.end()),edges.end());
0105 Function integrand=[&](double u) {
0106 const double z=(x-mpc-p_.width*u)/p_.sigma;
0107 return pdf_(u)*std::exp(-.5*z*z);
0108 };
0109 int calls=0;
0110 double sum=0.;
0111 const double atol=cfg_.relative_tolerance*cfg_.absolute_tolerance_fraction/(edges.size()-1);
0112 for (std::size_t i=1; i<edges.size(); ++i) {
0113 const double a=edges[i-1], b=edges[i];
0114 const double coarse=detail::gauss16(integrand,a,b,calls,cfg_.max_calls);
0115 sum += detail::subdivide(integrand,a,b,coarse,atol,cfg_.relative_tolerance,
0116 cfg_.max_depth,calls,cfg_.max_calls);
0117 }
0118 const double value=p_.area*sum/(p_.sigma*std::sqrt(2*std::acos(-1.)));
0119 require(std::isfinite(value) && value>=0., "invalid Langau integral");
0120 return value;
0121 }
0122 private:
0123 Parameters p_;
0124 Function pdf_;
0125 IntegrationConfig cfg_;
0126 };
0127
0128
0129
0130
0131 inline PeakWidth peakWidth(const Function& f, double centre, double scale) {
0132 require(std::isfinite(centre) && std::isfinite(scale) && scale>0.,
0133 "invalid Langau peak-search scale");
0134 constexpr int scanBins=160;
0135 double a=centre-8*scale, b=centre+12*scale, lo=0., hi=0.;
0136 bool bracketed=false;
0137 for (int expand=0; expand<8; ++expand) {
0138 std::vector<double> y(scanBins+1);
0139 int best=0;
0140 for (int i=0; i<=scanBins; ++i) {
0141 y[i]=checked(f,a+(b-a)*i/scanBins);
0142 if (y[i]>y[best]) best=i;
0143 }
0144 require(y[best]>0., "zero curve across Langau peak-search window");
0145 if (best>0 && best<scanBins) {
0146 int peaks=0;
0147 for (int i=1; i<scanBins; ++i)
0148 if (y[i]>y[i-1] && y[i]>=y[i+1] && y[i]>1e-5*y[best]) ++peaks;
0149 require(peaks==1, "multiple resolved peaks in Langau model");
0150 lo=a+(b-a)*(best-1)/scanBins;
0151 hi=a+(b-a)*(best+1)/scanBins;
0152 bracketed=true;
0153 break;
0154 }
0155 a=centre+2*(a-centre); b=centre+2*(b-centre);
0156 }
0157 require(bracketed, "unable to bracket Langau peak");
0158 const double golden=(std::sqrt(5.)-1)/2, xtol=1e-8*std::max(1.,scale);
0159 double l=hi-golden*(hi-lo), r=lo+golden*(hi-lo);
0160 double fl=checked(f,l), fr=checked(f,r);
0161 for (int k=0; k<160 && hi-lo>xtol; ++k) {
0162 if (fl>fr) {hi=r; r=l; fr=fl; l=hi-golden*(hi-lo); fl=checked(f,l);}
0163 else {lo=l; l=r; fl=fr; r=lo+golden*(hi-lo); fr=checked(f,r);}
0164 }
0165 require(hi-lo<=xtol, "Langau peak search did not converge");
0166 const double peak=(lo+hi)/2, height=checked(f,peak);
0167 require(height>0., "nonpositive Langau peak height");
0168 const auto crossing=[&](int direction) {
0169 double inside=peak, outside=peak, distance=scale;
0170 bool found=false;
0171 for (int k=0; k<64; ++k) {
0172 outside=peak+direction*distance;
0173 if (checked(f,outside)<height/2) {found=true; break;}
0174 distance*=1.8;
0175 }
0176 require(found, "unable to bracket Langau half height");
0177 for (int k=0; k<200; ++k) {
0178 const double mid=(inside+outside)/2, residual=checked(f,mid)/height-.5;
0179 if (std::abs(residual)<1e-10) return mid;
0180 if (residual>0) inside=mid; else outside=mid;
0181 if (std::abs(inside-outside)<xtol*.01) return (inside+outside)/2;
0182 }
0183 throw std::runtime_error("Langau half-height search did not converge");
0184 };
0185 const double left=crossing(-1), right=crossing(1);
0186 require(left<peak && peak<right && std::abs(checked(f,left)/height-.5)<1e-8
0187 && std::abs(checked(f,right)/height-.5)<1e-8, "invalid Langau half-height crossings");
0188 return {peak,left,right,right-left,height};
0189 }
0190 }}
0191 #endif