Main Page   Namespace List   Class Hierarchy   Compound List   File List   Namespace Members   Compound Members   File Members  

CHIMESpectrum.cxx

Go to the documentation of this file.
00001 // $Header: /nfs/slac/g/glast/ground/cvs/flux/src/CHIMESpectrum.cxx,v 1.12 2001/10/12 07:57:22 srobinsn Exp $
00002 
00003 
00004 #include "flux/CHIMESpectrum.h"
00005 
00006 #include "CHIMESpectrum.inc"  // numerical data: energies, fluxes, gfluxes
00007 
00008 #include <cmath>
00009 #include <algorithm>
00010 #include <functional>
00011 #include "CLHEP/Random/Random.h"
00012 #include "flux/GPS.h"
00013 
00014 // this is needed to include in the executable or dll
00015 #include "SpectrumFactory.h"
00016 
00017 static SpectrumFactory<CHIMESpectrum> factory;
00018 const ISpectrumFactory& CHIMESpectrumFactory = factory;
00019 
00020 
00021 // First deal with subclass CHIMESpectrum::InterpVec::
00022 //  which is a vector<float> with a couple of methods added for
00023 //  searching and interpolation.
00024 //-------------------------- InterpVec constructor
00025 
00026 CHIMESpectrum::InterpVec::InterpVec() : std::vector<float>() {}
00027 // default constructor.
00028 
00029 //-------------------------- InterpVec::search
00030 
00031 CHIMESpectrum::Intrp CHIMESpectrum::InterpVec::search(float x) const {
00032     // Binary search a vector for values that straddle x using STL lower_bound.
00033     // Deal gracefully with values off the end by extrapolation.
00034     // Contents of vector must be monotonic.
00035     // Return integer index for use by another vector
00036     
00037     std::vector<float>::const_iterator loc;  // search. ascending or descending?
00038     if (back() > front()) loc=std::lower_bound(begin(), end(), x, std::less<float>());
00039     else                  loc=std::lower_bound(begin(), end(), x, std::greater<float>());
00040     // lower_bound points to the entry after x
00041     
00042     int ind;  // convert STL iterator to integer index
00043     if (loc == begin())    ind = 1;   // extrapolate below table
00044     else if (loc == end()) ind = (end() - begin()) - 1;  // above table
00045     else                   ind = (loc - begin());  // in the table
00046     
00047     return Intrp(ind, (x-(*this)[ind-1]) / ((*this)[ind]-(*this)[ind-1]));
00048 }
00049 
00050 //-------------------------- InterpVec::interpolate
00051 
00052 float CHIMESpectrum::InterpVec::interpolate(Intrp y) const {
00053     // linear interpolation between two vector values
00054     return (*this)[y.first-1] +
00055         y.second * ((*this)[y.first]-(*this)[y.first-1]);
00056 }
00057 
00058 //-------------------------- Beginning of CHIMESpectrum proper
00059 
00060 const float CHIMESpectrum::m_rearth = 6371.f;  // radius of earth in km
00061 const float CHIMESpectrum::m_altitude = 600.f;  // altitude of circular orbit
00062 
00063 //Initializes parameters during construction
00064 void CHIMESpectrum::init(std::string paramstring) {
00065     
00066     
00067     std::vector<float> params;
00068     
00069     parseParamList(paramstring,params);
00070     
00071     int nen = sizeof(energies)/sizeof(float);
00072     m_en.reserve(nen);
00073     int i;
00074     for (i=0; i< nen; i++) m_en.push_back(energies[i]/1000.);
00075     // convert MeV to GeV
00076     m_normfact = .5*(1.+sqrt(m_altitude*(m_altitude+2.*m_rearth)) /
00077         (m_rearth+m_altitude));
00078     //geometrical shadow factor in 600 km orbit
00079     m_fluxes.reserve(nen);
00080     m_fl.reserve(nen);
00081     
00082     const float width = 0.115f; // CHIME log spacing between standard energies
00083     m_etop = m_en.back() * (1.+.5*width);  // boundary between table & tail
00084     m_expo = -2.75 + 1.0;  // power law exponent (integral) of high-energy tail
00085     
00086     // Populate table of differential free space fluxes -- no geomagnetic cutoff
00087     float tfl = 0.f;
00088     for (i=74; i >= 0; i--) {
00089         tfl += width*1000.*m_en[i]*m_normfact*fluxes[i];
00090         m_fluxes.insert(m_fluxes.begin(), tfl);
00091     }
00092     
00093     // table of total flux as a function of latitude and longitude
00094     for (int ii=0; ii < 73; ii++) {
00095         for (int jj=0; jj < 13; jj++) {
00096             m_fluxTbl[ii][jj] = gfluxes[jj+13*ii];
00097         }
00098     }
00099     /*float*/double lat =  params.size()>0? params[0]: 0.0f;
00100     /*float*/double lon =  params.size()>1? params[1]: 0.0f;
00101     
00102     setPosition(lat, lon);
00103     
00104     // cos(angle between zenith and horizon)
00105     m_coscutoff = -sqrt(m_altitude*m_altitude+2.*m_altitude*m_rearth)
00106         / (m_altitude+m_rearth);
00107     
00108     m_particle_name = "p";
00109     
00110     // set callback to be notified when the position changes
00111     m_observer.setAdapter( new ActionAdapter<CHIMESpectrum>(this,&CHIMESpectrum::askGPS) );
00112     
00113     GPS::instance()->notification().attach( &m_observer );
00114     
00115 }
00116 
00117 
00118 //-------------------------- constructors---------------------
00119 
00120 CHIMESpectrum::CHIMESpectrum(const std::string& params) {
00121     init(params);
00122 }
00123 
00124 
00125 std::string CHIMESpectrum::title() const {
00126     return "CHIMESpectrum";
00127 }
00128 
00129 const char * CHIMESpectrum::particleName() const {
00130     return m_particle_name.c_str();
00131 }
00132 void CHIMESpectrum::setParticleName(std::string name)
00133 {
00134     m_particle_name = name;
00135 }
00136 
00137 //-------------------------- flux()  (specified cutoff value)
00138 
00139 float CHIMESpectrum::flux(float cut) const {
00140     // Total flux in protons / m^2 sec ster
00141     // Interpolate in table if possible, otherwise assume power law
00142     //  tail at high energy.
00143     if (cut > m_etop) return m_upper * pow(cut/m_etop, m_expo);
00144     else              return m_upper + m_fl.interpolate(m_en.search(cut));
00145 }
00146 
00147 //-------------------------- flux() (current cutoff value)
00148 
00149 double CHIMESpectrum::flux() const {
00150     // calculate flux for the current cutoff
00151     return m_flux;
00152 }
00153 
00154 //--------------------------
00155 
00156 double CHIMESpectrum::solidAngle() const
00157 {
00158     // the normalization for the flux calculation (according to doc)
00159     return 4.* M_PI;
00160 }
00161 
00162 //-------------------------- flux() (specified position)
00163 
00164 float CHIMESpectrum::flux(float lat, float lon) const {
00165     // Flux as a function of latitude and longitude in a 600 km orbit.
00166     // Linear interpolate in a table with a 5 degree sampling grid.
00167     int ilat = static_cast<int>(lat/5.+6.);
00168     double/*float*/ a = fmod(lat+30., 5.)/5.;
00169     int ilon = static_cast<int>(lon/5.);
00170     double/*float*/ b = fmod(lon, 5.)/5.;
00171     return m_fluxTbl[ilon][ilat] * (1.-a) * (1.-b) +
00172         m_fluxTbl[ilon+1][ilat] * (1.-a) * b +
00173         m_fluxTbl[ilon][ilat+1] * a * (1.-b) +
00174         m_fluxTbl[ilon+1][ilat+1] * a * b;
00175 }
00176 
00177 //-------------------------- flux() (position given in a pair)
00178 
00179 float CHIMESpectrum::flux(std::pair<double,double> coords) const {
00180     // Flux as a function of latitude and longitude
00181     return flux(coords.first, coords.second);
00182 }
00183 
00184 //-------------------------- operator()  sample an energy value
00185 
00186 float CHIMESpectrum::operator() (float x)const {
00187     // return a random value of energy sampled from the spectrum
00188     
00189     float join = (m_tot-m_upper)/m_tot;
00190     if (x < join) return m_en.interpolate(m_fl.search((1.-x)*m_tot-m_upper));
00191     else          return m_etop*pow((1.-x)/(1.-join), 1./m_expo);
00192 }
00193 
00194 //-------------------------- setPosition (separate coordinates)
00195 
00196 int CHIMESpectrum::askGPS()
00197 {
00198     setPosition(GPS::instance()->lat(), GPS::instance()->lon());
00199     return 0; // can't be void in observer pattern
00200 }
00201 
00202 void CHIMESpectrum::setPosition(double lat, double lon) {
00203     // Do the initialization necessary when moving to a new position:
00204     // look up cutoff energy, build a new table of integral proton
00205     // fluxes
00206     
00207     m_lat = lat;
00208     m_lon = lon;
00209     
00210     // Integrated flux in the power law tail above the table.
00211     m_upper = -0.115*1000.*m_en.back()*m_normfact*fluxes[74]
00212         * pow(1.+.5*0.115,m_expo)/(0.115*m_expo);
00213     
00214     m_cutoff = findCutoff(lat,lon);
00215     
00216     // Populate table of integral fluxes modified by geomagnetic cutoff.
00217     float tfl = 0.;
00218     m_fl.erase(m_fl.begin(), m_fl.end());
00219     for (int i = 74; i >= 0; i--) {
00220         tfl += 0.115*1000.*m_en[i]*m_normfact*fluxes[i]*exposure(m_en[i]);
00221         m_fl.insert(m_fl.begin(), tfl);
00222     }
00223     
00224     m_tot = m_fl.front() + m_upper;
00225     m_flux = flux(m_cutoff);
00226 }
00227 
00228 //-------------------------- setPosition (coordinates packaged in a pair)
00229 
00230 void CHIMESpectrum::setPosition(std::pair<double,double> coords) {
00231     CHIMESpectrum::setPosition(coords.first, coords.second);
00232 }
00233 
00234 //-------------------------- findCutoff (from a flux)
00235 
00236 float CHIMESpectrum::findCutoff(float rflux) const {
00237     // determine the cutoff value which will produce the desired flux
00238     return m_en.interpolate(m_fluxes.search(rflux-m_upper));
00239 }
00240 
00241 //-------------------------- findCutoff (from a position)
00242 
00243 float CHIMESpectrum::findCutoff(float lat, float lon) const {
00244     // determine the cutoff value at a geographical location
00245     return findCutoff(flux(lat,lon));
00246 }
00247 
00248 //------------------------- findCutoff (position given as a pair)
00249 
00250 float CHIMESpectrum::findCutoff(std::pair<double,double> coords) const {
00251     // determine the cutoff value at a geographical location
00252     return findCutoff(flux(coords));
00253 }
00254 
00255 //------------------------- calculate_rate()
00256 
00257 double CHIMESpectrum::calculate_rate(double old_rate)
00258 {
00259     return flux(GPS::instance()->lat(), GPS::instance()->lon());
00260 }
00261 
00262 //------------------------- rad2()
00263 
00264 float CHIMESpectrum::rad2() const {
00265     // square of (distance from center of magnetic dipole / earth radius)
00266     // Dipole is offset from the earth's center
00267     
00268     double/*float*/ d2 =
00269         pow((m_rearth+m_altitude)*sin(m_lat)            - 145.1, 2) +
00270         pow((m_rearth+m_altitude)*cos(m_lat)*cos(m_lon) + 371.2, 2) +
00271         pow((m_rearth+m_altitude)*cos(m_lat)*sin(m_lon) - 233.7, 2);
00272     return d2 / (m_rearth*m_rearth);
00273 }
00274 
00275 //------------------------- cosomega()
00276 
00277 float CHIMESpectrum::cosomega(float E) const {
00278     // Opening angle of geomagnetic cutoff cone.
00279     // This is a pretty backward way to do it.  It starts from a cutoff
00280     // energy which is derived from a desired rate.  Then it calculates
00281     // the magnetic latitude from that.  Really, the cutoff and latitude
00282     // should be derived from the position by looking in tables.
00283     // Also, this is simple Størmer theory, ignoring  the penumbral
00284     // region and multipole effects.
00285     
00286     const float Mp = 0.938f;  // mass of proton in GeV
00287     double/*float*/ pcut = sqrt(m_cutoff*m_cutoff + 2.*Mp*m_cutoff);  // cutoff momentum
00288     const float moment = 59.8f; // magnetic moment of earth in convenient units
00289     double/*float*/ coslambda4 = 4. * pcut  * rad2() / moment;  // magnetic latitude**4
00290     double/*float*/ p = sqrt(E*E + 2.*Mp*E); // momentum
00291     double/*float*/ coso = 4. * (sqrt(pcut/p) - pcut/p) * pow(coslambda4, -0.75);
00292     // opening angle of Størmer cone
00293     if (coso > 1.) return 1.;
00294     else if (coso < -1.) return -1.;
00295     else return coso;
00296 }
00297 
00298 //-------------------------- exposure()
00299 
00300 float CHIMESpectrum::exposure(float E) const {
00301     // Geomagnetic cutoff fraction.  Varies from 0 to 1.
00302     return 0.5 * (1. + cosomega(E));
00303 }
00304 
00305 //-------------------------- dir()
00306 
00307 std::pair<float,float> CHIMESpectrum::dir(float energy)const
00308 {
00309     
00310     // Random particle direction from Stormer cone
00311     
00312     // Rejection method for the direction.  Direction must be inside
00313     // the Stormer cone and above the horizon.
00314     double/*float*/ coszenith, sinpolar, cospolar, azi;
00315     const int try_max = 1000;
00316     static int max_tried = 0;
00317     int trial=0;
00318     do {
00319         // uniform distribution within Stormer cone
00320         cospolar = -1. + HepRandom::getTheGenerator()->flat()*(cosomega(energy)+1.);
00321         sinpolar = sqrt(1.-cospolar*cospolar);
00322         azi = 2.*M_PI* HepRandom::getTheGenerator()->flat();
00323         coszenith = cos(azi)*sinpolar;
00324     } while (coszenith < m_coscutoff && trial++ < try_max);
00325     
00326     max_tried = std::max(trial, max_tried); // keep track
00327     
00328     // Transform to local earth-based coordinates.
00329     float earthazi = atan2(sinpolar*sin(azi), cospolar);
00330     
00331     return std::make_pair<float,float>(coszenith, earthazi);
00332 }

Generated at Wed Nov 21 12:20:33 2001 by doxygen1.2.3 written by Dimitri van Heesch, © 1997-2000