GeoMagnetic.cxx
1 #include "GeoMagnetic.h"
2 
3 #include <iostream>
4 #include <fstream>
5 #include "TString.h"
6 #include "TObjArray.h"
7 #include "TMath.h"
8 #include "TObjString.h"
9 #include <map>
10 #include "RampdemReader.h"
11 #include "AntarcticaBackground.h"
12 #include "TF1.h"
13 #include "TLegend.h"
14 
15 #include "AnitaGeomTool.h"
16 #include "UsefulAdu5Pat.h"
17 #include "TGraphAntarctica.h"
18 
19 #include "TDatime.h"
20 #include "TCanvas.h"
21 
22 // --------------------------------------------------------------------------------------------------------------------------------------
23 // Silly globals, kept tucked away from prying eyes
24 // --------------------------------------------------------------------------------------------------------------------------------------
25 
26 bool doneInit = false; // Tells you whether we've read in the data and precalculated the factorials
27 const int numPoly = 14; // there are only 13 polynomial coeffients, but I'm going to start counting from one for simplicity
28 std::vector<double> factorials(2*numPoly, 0);
29 std::map<int, std::vector<double> > g_vs_time; // Gauss coefficients (needed to calc potential)
30 std::map<int, std::vector<double> > h_vs_time; // Gauss coefficients (needed to calc potential)
31 const double earth_radius = 6371.2e3; // earth radius in meters for the magnetic model
32 TF1* fAssocLegendre[numPoly][numPoly] = {{NULL}}; // Associated Legendre polynomials
33 bool debug = false;
34 
35 // for "differentiating" the potential (really it's a difference with small delta values)
36 // I suppose I could differentiate the expression by hand and evaluate that... but that's work
37 const double dr = 1;
38 const double dTheta = 0.01*TMath::DegToRad();
39 const double dPhi = 0.01*TMath::DegToRad();
40 
41 
42 // Globals for the atmospheric model, currently just an exponential fit to some numbers
43 // from a table in the Wikipedia
44 TGraph grAtmosDensity;
45 TF1* fExpAtmos;
46 
47 // --------------------------------------------------------------------------------------------------------------------------------------
48 // Utility functions, for initialisation and internal calculation
49 // --------------------------------------------------------------------------------------------------------------------------------------
50 
59 inline double getIndex(int n, int m){
60  return n*numPoly + m;
61 }
62 
67 void getGaussCoefficients(){
68 
69  const char* anitaUtilInstallDir = getenv("ANITA_UTIL_INSTALL_DIR");
70  if(!anitaUtilInstallDir){
71  std::cerr << "Warning in " << __FILE__ << ", ANITA_UTIL_INSTALL_DIR not set" << std::endl;
72  }
73  TString fileName = TString::Format("%s/share/anitaCalib/igrf12coeffs.txt", anitaUtilInstallDir);
74  std::ifstream coeffs(fileName);
75 
76  if(!coeffs.good()){
77  std::cerr << "Error in " << __FILE__ << " unable to find " << fileName.Data() << std::endl;
78  }
79 
80  const int expectedTokens = 28;
81  std::vector<std::vector<TString> > igrfDataTableStrings;
82  while(!coeffs.eof()){
83  std::string line;
84  std::getline(coeffs, line);
85 
86  if(line[0] != '#'){ // ignore comment lines at start
87 
88  TString s = line.c_str();
89 
90  // remove windows style newline prefix (for \r\n vs. \n)
91  s.ReplaceAll("\r", "");
92 
93  TObjArray* tokens = s.Tokenize(' ');
94  int numTokens = tokens->GetEntries();
95 
96  if(numTokens > 0){ // last line has nothing in
97  igrfDataTableStrings.push_back(std::vector<TString>());
98 
99  if(numTokens!=expectedTokens){
100  std::cerr << "Warning parsing " << fileName << ", found " << numTokens << " tokens in line..." << std::endl;
101  std::cerr << line << std::endl;
102  }
103 
104  for(int i=0; i < numTokens; i++){
105  TString thisS = ((TObjString*)tokens->At(i))->GetString();
106  igrfDataTableStrings.back().push_back(thisS);
107  }
108  }
109  }
110  }
111 
112 
113  // now extract data from strings...
114  const int numRows = igrfDataTableStrings.size();
115 
116  // there are two rows of column titles so start at third row
117  // std::vector<TString>& headerRow1 = igrfDataTableStrings.at(0);
118  std::vector<TString>& headerRow2 = igrfDataTableStrings.at(1);
119 
120  // extract years from the header
121  std::map<unsigned, int> colToYear;
122  for(unsigned col=3; col < headerRow2.size() - 1; col++){
123  int year = atoi(headerRow2[col].Data());
124  colToYear[col] = year;
125  // std::cout << col << "\t" << year << std::endl;
126  }
127 
128  // loop over rows and extract Gauss coefficents
129  for(int row=2; row < numRows; row++){
130  std::vector<TString>& thisRow = igrfDataTableStrings.at(row);
131 
132  // first three rows tell you whether it's g or h, and the m and n Legendre degree and order
133  const TString& g_or_h = thisRow[0];
134  const int n = atoi(thisRow[1].Data());
135  const int m = atoi(thisRow[2].Data());
136 
137  int index = getIndex(n, m);
138 
139  // std::cout << g_or_h << "\t" << m << "\t" << n << "\t" << index << std::endl;
140 
141  for(unsigned col=3; col < thisRow.size() - 1; col++){
142  int year = colToYear[col];
143 
144  std::map<int, std::vector<double> >::iterator it;
145 
146  // put data into Gauss coeffient maps
147  if(g_or_h == "g"){
148 
149  it = g_vs_time.find(year);
150  if(it!=g_vs_time.end()){
151  it->second.at(index) = atof(thisRow.at(col).Data());
152  }
153  else{
154  g_vs_time[year] = std::vector<double>(numPoly*numPoly, 0);
155  g_vs_time[year].at(index) = atof(thisRow.at(col).Data());
156  }
157  }
158 
159  else if (g_or_h == "h"){
160  it = h_vs_time.find(year);
161  if(it!=h_vs_time.end()){
162  it->second.at(index) = atof(thisRow.at(col).Data());
163  }
164  else{
165  h_vs_time[year] = std::vector<double>(numPoly*numPoly, 0);
166  h_vs_time[year].at(index) = atof(thisRow.at(col).Data());
167  }
168  }
169  else{
170  std::cerr << "Warning! Unknown coefficient " << g_or_h << "[" << n << "][" << m << "]" << std::endl;
171  }
172  }
173  }
174 }
175 
176 
177 
178 
179 
180 
181 
188 void prepareGeoMagnetics(){
189 
190  if(!doneInit){
191  getGaussCoefficients();
192  for(int i=0 ; i< 2*numPoly; i++){
193  factorials.at(i) = TMath::Factorial(i);
194  // std::cout << i << "! = " << factorials.at(i) << std::endl;
195  }
196  for(int n=1; n < numPoly; n++){
197  for(int m=0; m <=n; m++){
198  if(!fAssocLegendre[n][m]){
199  TString name = TString::Format("fAssocLegendre_%d_%d", n, m);
200  TString formula = TString::Format("ROOT::Math::assoc_legendre(%d, %d, x)", n, m);
201  fAssocLegendre[n][m] = new TF1(name, formula, -1, 1);
202  // std::cout << m << "\t" << n << std::endl;
203  }
204  }
205  }
206 
207  const int numAtmospherePoints = 8;
208  double heights[numAtmospherePoints] = {-611, 11019, 20063, 32162, 47350, 51413, 71802, 86000};
209  double densities[numAtmospherePoints] = {1.2985, 0.3639, 0.0880, 0.0132, 0.0020, 0, 0, 0 };
210  for(int i=0; i < numAtmospherePoints; i++){
211  grAtmosDensity.SetPoint(i, heights[i], densities[i]);
212  }
213  grAtmosDensity.SetTitle("Atmospheric density (International Standard); Altitude above MSL (m); Densities (kg/m^{3})");
214  grAtmosDensity.SetName("grAtmosDensity");
215  grAtmosDensity.Fit("expo", "Q");
216  fExpAtmos = (TF1*) grAtmosDensity.FindObject("expo");
217  if(!fExpAtmos){
218  std::cerr << "Warning in " << __FILE__ << " unable to find exponential fit to atmosphere..." << std::endl;
219  }
220 
221  doneInit = true;
222  }
223 }
224 
225 
236 double getFactorial(int i){
237  if(i >= 2*numPoly){
238  std::cerr << "Too high factorial requested!" << std::endl;
239  return -1;
240  }
241  return factorials[i];
242 }
243 
244 
245 
246 
255 double unixTimeToFractionalYear(UInt_t unixTime){
256  TDatime t2(unixTime);
257  int thisYear = t2.GetYear();
258  TDatime t1(thisYear, 0, 0, 0, 0, 0);
259  UInt_t unixTimeYearStart = t1.Convert();
260  TDatime t3(thisYear+1, 0, 0, 0, 0, 0);
261  UInt_t unixTimeNextYear = t3.Convert();
262  double year = thisYear;
263  year += double(unixTime - unixTimeYearStart)/double(unixTimeNextYear - unixTimeYearStart);
264  // std::cout << unixTime << "\t" << thisYear << "\t" << unixTimeYearStart << "\t" << unixTimeNextYear << std::endl;
265  return year;
266 }
267 
268 
269 
270 
283 void lonLatAltToSpherical(double lon, double lat, double alt, double& r, double& theta, double& phi){
284  double cartesian[3];
285  AnitaGeomTool::Instance()->getCartesianCoords(lat, lon, alt, cartesian);
286  double x = cartesian[0];
287  double y = cartesian[1];
288  double z = cartesian[2];
289 
290  // AnitaGeomTool confuses and infuriates in equal parts...
291  z = lat >= 0 ? TMath::Abs(z) : -TMath::Abs(z);
292 
293  r = TMath::Sqrt(x*x + y*y + z*z);
294  theta = r > 0 ? TMath::ACos(z/r) : 0;
295  phi = -TMath::ATan2(y, x) + 0.5*TMath::Pi();
296  phi = phi >= TMath::Pi() ? phi - TMath::TwoPi() : phi;
297 
298  // std::cout << lon << "\t" << lat << "\t" << alt << std::endl;
299  // std::cout << phi*TMath::RadToDeg() << "\t" << theta*TMath::RadToDeg() << "\t" << r << std::endl << std::endl;
300 }
301 
302 
303 
304 
314 TVector3 lonLatAltToVector(double lon, double lat, double alt){
315  double r, theta, phi;
316  lonLatAltToSpherical(lon, lat, alt, r, theta, phi);
317  TVector3 v;
318  v.SetMagThetaPhi(r, theta, phi);
319  return v;
320 }
321 
322 
323 
324 
325 
336 void sphericalToLatLonAlt(double& lon, double& lat, double& alt, double r, double theta, double phi){
337 
338  double x = r*TMath::Sin(phi)*TMath::Sin(theta);
339  double y = r*TMath::Cos(phi)*TMath::Sin(theta);
340  double z = r*TMath::Cos(theta);
341  double cartesian[3] = {x, y, z};
342 
344  g->getLatLonAltFromCartesian(cartesian, lat, lon, alt);
345 
346  // fml...
347  lat = theta*TMath::RadToDeg() <= 90 ? -lat : lat;
348 }
349 
350 
351 
360 void vectorToLonLatAlt(double& lon, double& lat, double& alt, const TVector3& v){
361  sphericalToLatLonAlt(lon, lat, alt, v.Mag(), v.Theta(), v.Phi());
362 }
363 
364 
365 
366 
367 
368 
369 
370 
371 
372 
373 // --------------------------------------------------------------------------------------------------------------------------------------
374 // Namespace functions, for public consumption
375 // --------------------------------------------------------------------------------------------------------------------------------------
376 
377 
378 
379 
387 void GeoMagnetic::setDebug(bool db){
388  debug = db;
389 }
390 
391 
392 
393 
394 
406 double GeoMagnetic::g(UInt_t unixTime, int n, int m){
407  prepareGeoMagnetics();
408  int year = 2015;
409  int index = getIndex(n, m);
410  return g_vs_time[year].at(index);
411 }
412 
413 
414 
415 
416 
428 double GeoMagnetic::h(UInt_t unixTime, int n, int m){
429  prepareGeoMagnetics();
430  int year = 2015;
431  int index = getIndex(n, m);
432  return h_vs_time[year].at(index);
433 }
434 
435 
436 
437 
438 
439 
440 
441 
442 
443 
456 double evalSchmidtQuasiNormalisedAssociatedLegendre(int n, int m, double x){
457 
458  double norm = m == 0 ? 1 : TMath::Sqrt(2*getFactorial(n-m)/getFactorial(n+m));
459  double P_n_m = norm*fAssocLegendre[n][m]->Eval(x);
460 
461  if(TMath::IsNaN(P_n_m)){
462  std::cerr << "You got a NaN... " << norm << "\t" << m << "\t" << n << "\t" << x << std::endl;
463  }
464  return P_n_m;
465 }
466 
467 
468 
469 
470 
482 double GeoMagnetic::getPotentialAtSpherical(UInt_t unixTime, double r, double theta, double phi){
483  prepareGeoMagnetics();
484  int year = 2015; // for now, should be a function of time
485  double V = 0; // the potential
486 
487  // sum over the legendre polynomials normalised by the Gauss coefficients g and h
488  for(int n=1; n < numPoly; n++){
489  for(int m=0; m <= n; m++){
490  double mPhi = m*phi;
491  double part = 0;
492 
493  double this_g = GeoMagnetic::g(unixTime, n, m);
494  if(this_g != 0){
495  part += this_g*TMath::Cos(mPhi);
496  }
497  double this_h = GeoMagnetic::h(year, n, m);
498  if(this_h){
499  part += this_h*TMath::Sin(mPhi);
500  }
501 
502  if(part != 0){
503  double cosTheta = TMath::Cos(theta);
504  double P_n_m = evalSchmidtQuasiNormalisedAssociatedLegendre(n, m, cosTheta);
505  part *= earth_radius*pow(earth_radius/r, n+1)*P_n_m;
506  V += part;
507 
508  if(TMath::IsNaN(part)){
509  std::cout << earth_radius << "\t" << r << "\t" << pow(earth_radius/r, n+1) << "\t" << this_g << "\t" << cos(mPhi) << "\t" << this_h << "\t" << sin(mPhi) << "\t" << P_n_m << std::endl;
510  }
511  }
512  }
513  }
514 
515  return V;
516 }
517 
518 
519 
520 
521 
522 
523 
524 
525 
536 double GeoMagnetic::getPotentialAtLonLatAlt(UInt_t unixTime, double lon, double lat, double alt){
537  prepareGeoMagnetics();
538 
539  double r, theta, phi;
540  lonLatAltToSpherical(lon, lat, alt, r, theta, phi);
541  return getPotentialAtSpherical(unixTime, r, theta, phi);
542 }
543 
544 
545 
546 
558 double GeoMagnetic::X_atLonLatAlt(UInt_t unixTime, double lon, double lat, double alt){
559  prepareGeoMagnetics();
560  double r, phi, theta;
561  lonLatAltToSpherical(lon, lat, alt, r, theta, phi);
562 
563  return X_atSpherical(unixTime, r, theta, phi);
564 }
565 
578 double GeoMagnetic::X_atSpherical(UInt_t unixTime, double r, double theta, double phi){
579  prepareGeoMagnetics();
580  double V0 = getPotentialAtSpherical(unixTime, r, theta, phi);
581  double V1 = getPotentialAtSpherical(unixTime, r, theta+dTheta, phi);
582  double BX = (V1-V0)/(dTheta*r);
583  return BX;
584 }
585 
586 
587 
588 
599 double GeoMagnetic::Y_atLonLatAlt(UInt_t unixTime, double lon, double lat, double alt){
600  prepareGeoMagnetics();
601  double r, phi, theta;
602  lonLatAltToSpherical(lon, lat, alt, r, theta, phi);
603  return Y_atSpherical(unixTime, lon, lat, alt);
604 }
605 
606 
617 double GeoMagnetic::Y_atSpherical(UInt_t unixTime, double r, double theta, double phi){
618  prepareGeoMagnetics();
619  double V0 = getPotentialAtSpherical(unixTime, r, theta, phi);
620  double V1 = getPotentialAtSpherical(unixTime, r, theta, phi+dPhi);
621  double BY = -(V1-V0)/(dPhi*r*TMath::Sin(theta));
622  return BY;
623 }
624 
625 
626 
627 
638 double GeoMagnetic::Z_atLonLatAlt(UInt_t unixTime, double lon, double lat, double alt){
639  prepareGeoMagnetics();
640  double r, theta, phi;
641  lonLatAltToSpherical(lon, lat, alt, r, theta, phi);
642  return Z_atSpherical(unixTime, r, theta, phi);
643 }
644 
645 
646 
657 TCanvas* GeoMagnetic::plotFieldAtAltitude(UInt_t unixTime, double altitude){
658 
659  TCanvas* c = new TCanvas();
661 
662  int nx = bg->GetNbinsX();
663  int ny = bg->GetNbinsY();
664  bg->Draw();
665  bg->SetBit(kCanDelete);
666 
667  const int arrowEvery = 20;
668  for(int by=1; by <= ny; by+=arrowEvery){
669  double northing = bg->GetYaxis()->GetBinLowEdge(by);
670  for(int bx=1; bx <= nx; bx+=arrowEvery){
671  double easting = bg->GetXaxis()->GetBinLowEdge(bx);
672  double lon, lat;
673  RampdemReader::EastingNorthingToLonLat(easting, northing, lon, lat);
674  FieldPoint* f = new FieldPoint(unixTime, lon, lat, altitude);
675  f->SetBit(kMustCleanup);
676  f->SetBit(kCanDelete);
677  f->Draw();
678  }
679  }
680  return c;
681 }
682 
683 
684 
685 
686 
687 
688 
689 
701 double GeoMagnetic::Z_atSpherical(UInt_t unixTime, double r, double theta, double phi){
702  prepareGeoMagnetics();
703 
704  double V0 = getPotentialAtSpherical(unixTime, r, theta, phi);
705  double V1 = getPotentialAtSpherical(unixTime, r+dr, theta, phi);
706  double BZ = (V1-V0)/dr; // negative of the gradient of the potential
707  return BZ;
708 }
709 
710 
711 
712 
718 void GeoMagnetic::FieldPoint::Draw(Option_t* opt){
719 
720  double lon, lat, alt;
721  double r = fPosition.Mag();
722  double theta = fPosition.Theta();
723  double phi = fPosition.Phi();
724 
725  sphericalToLatLonAlt(lon, lat, alt, r, theta, phi);
726 
727  RampdemReader::LonLatToEastingNorthing(lon, lat, fX1, fY1);
728 
729  TVector3 position = fPosition;
730  position += fDrawScaleFactor*fField;
731  sphericalToLatLonAlt(lon, lat, alt, position.Mag(), position.Theta(), position.Phi());
732  RampdemReader::LonLatToEastingNorthing(lon, lat, fX2, fY2);
733 
734  TArrow::Draw(opt);
735 }
736 
737 
738 
739 
740 
741 
748 GeoMagnetic::FieldPoint::FieldPoint(UInt_t unixTime, const TVector3& position) : TArrow(0, 0, 0, 0, 0.001, "|>"), fPosition(),fField(), fDrawScaleFactor(10)
749 {
750  fPosition = position;
751  fUnixTime = unixTime;
752  calculateFieldAtPosition();
753 }
754 
755 
756 
765 GeoMagnetic::FieldPoint::FieldPoint(UInt_t unixTime, double lon, double lat, double alt) : TArrow(0, 0, 0, 0, 0.001, "|>"), fPosition(),fField(), fDrawScaleFactor(10)
766 {
767  double r, theta, phi;
768  lonLatAltToSpherical(lon, lat, alt, r, theta, phi);
769  fPosition.SetMagThetaPhi(r, theta, phi);
770  fUnixTime = unixTime;
771  calculateFieldAtPosition();
772 }
773 
774 
775 
776 
781 void GeoMagnetic::FieldPoint::calculateFieldAtPosition(){
782  // each of these functions calcuates calculates V0, so you could save 2 of 6 calculations here...
783  double r = fPosition.Mag();
784  double theta = fPosition.Theta();
785  double phi = fPosition.Phi();
786 
787  double X = X_atSpherical(fUnixTime, r, theta, phi); // north
788  double Y = Y_atSpherical(fUnixTime, r, theta, phi); // east
789  double Z = Z_atSpherical(fUnixTime, r, theta, phi); // down
790 
791  // now convert into proper spherical polar coordinates..
792 
793  // X points north, but theta_hat increases to the south, so *= -1
794  double B_theta = -X;
795  // Y points east, if you add positive values of phi, you're going east, which is the same as spherical coordinates so the sign is the same
796  double B_phi = Y;
797  // Z points down, but we want the radial component to point away from the origin, so *=-1
798  double B_r = -Z;
799 
800  // Red for radially upwards pointing B-field
801  // Blue for radially downwards pointing B-field
802  SetLineColor(B_r > 0 ? kRed : kBlue);
803 
804  // now I'm going to rotate into a cartesian coordinate system with the +ve z-axis running through the geographic north pole
805  double cos_theta = TMath::Cos(theta);
806  double sin_theta = TMath::Sin(theta);
807  double cos_phi = TMath::Cos(phi);
808  double sin_phi = TMath::Sin(phi);
809 
810  // this rotates the field components pointing along RHat, thetaHat, phiHat into the Cartesian coordinate system
811  double x = sin_theta*cos_phi*B_r + cos_theta*cos_phi*B_theta - sin_phi*B_phi;
812  double y = sin_theta*sin_phi*B_r + cos_theta*sin_phi*B_theta + cos_phi*B_phi;
813  double z = cos_theta*B_r - sin_theta*B_theta + 0;
814 
815  fField.SetXYZ(x, y, z);
816 }
817 
818 
819 
820 
821 
830 TVector3 GeoMagnetic::specularReflection(const TVector3& incidentPoyntingVector, const TVector3& surfaceNormal){
831 
832  // https://en.wikipedia.org/wiki/Specular_reflection#Vector_formulation
833  TVector3 reflectionPointToSource = -1*incidentPoyntingVector;
834  TVector3 reflectionPointToDestination = 2*(reflectionPointToSource.Dot(surfaceNormal))*surfaceNormal - reflectionPointToSource;
835  return reflectionPointToDestination;
836 }
837 
838 
839 
840 
848 TCanvas* GeoMagnetic::plotFresnelReflection(){
849 
850  const int nSteps = 90; //1000;
851  double d_theta_i = TMath::PiOver2()/nSteps;
852 
853  const TVector3 surfaceNormal(0, 0, 1); // reflection in the x-y plane, == z_hat
854  const TVector3 x_hat(1, 0, 0);
855  const TVector3 y_hat(0, 1, 0);
856 
857  const double pol_angle = 45*TMath::DegToRad();
858 
859  const double E_mag = 1;
860 
861  TGraph* grThetas = new TGraph();
862  TGraph* gr_r_p = new TGraph();
863  TGraph* gr_r_s = new TGraph();
864 
865  for(int i=0; i <= nSteps; i++){
866  double theta_i = d_theta_i * i;
867 
868  TVector3 incidentRay = -surfaceNormal; // *-1 to make it downgoing
869  incidentRay.RotateY(theta_i); // this should rotate the vector into the x-z plane, the plane of incidence
870 
871  TVector3 E = E_mag*y_hat; // y_hat should be perpendicular to the incident ray in the x-z plane
872  E.Rotate(pol_angle, incidentRay);
873 
874  if(debug){
875  std::cout << "theta_i = " << theta_i*TMath::RadToDeg() << " degrees, (incidentRay . E) = " << incidentRay.Dot(E) << std::endl;
876  std::cout << "Incident ray = (" << incidentRay.X() << ", " << incidentRay.Y() << ", " << incidentRay.Z() << ")" << std::endl;
877  std::cout << "E = (" << E.X() << ", " << E.Y() << ", " << E.Z() << ")" << std::endl;
878  }
879 
880  double E_s_initial = E.Y();
881  double E_p_initial = TMath::Sqrt(E.X()*E.X() + E.Z()*E.Z());
882 
883  TVector3 reflectedRay = fresnelReflection(incidentRay, surfaceNormal, E);
884 
885  if(debug){
886  TVector3 npi = incidentRay.Cross(reflectedRay).Unit();
887  std::cout << "normal to the plane of incidence = (" << npi.X() << ", " << npi.Y() << ", " << npi.Z() << ")" << std::endl;
888  }
889 
890  double theta_r = surfaceNormal.Angle(reflectedRay);
891  double theta_i_2 = surfaceNormal.Angle(-incidentRay);
892 
893  grThetas->SetPoint(grThetas->GetN(), theta_i_2*TMath::RadToDeg(), theta_r*TMath::RadToDeg());
894 
895  double E_s_reflected = E.Y();
896  double E_p_reflected = TMath::Sqrt(E.X()*E.X() + E.Z()*E.Z());
897 
898  double r_p = TMath::Abs(E_p_initial) > 0 ? E_p_reflected/E_p_initial : 0; // might be a better value to choose here?
899  double r_s = TMath::Abs(E_s_initial) > 0 ? E_s_reflected/E_s_initial : 0; // might be a better value to choose here?
900 
901  gr_r_p->SetPoint(gr_r_p->GetN(), theta_i_2*TMath::RadToDeg(), r_p);
902  gr_r_s->SetPoint(gr_r_s->GetN(), theta_i_2*TMath::RadToDeg(), r_s);
903  }
904 
905  TCanvas* c1 = new TCanvas();
906  c1->Divide(2);
907  c1->cd(1);
908  grThetas->SetTitle("Law of reflection; Angle of incidence, #theta_{i} (Degrees); Angle of reflection #theta_{r} (Degrees)");
909  grThetas->Draw();
910  grThetas->SetBit(kCanDelete);
911 
912  c1->cd(2);
913 
914  TLegend* l1b = new TLegend(0.15, 0.55, 0.6, 0.85);
915  gr_r_p->SetTitle("E-field amplitude reflection coefficients; Incident angle, #theta_{i} (Degrees); Reflection coefficient");
916  gr_r_p->SetLineColor(kRed);
917  gr_r_p->Draw("al");
918  gr_r_p->SetBit(kCanDelete);
919  double r_min = -1;
920  double r_max = 1;
921  gr_r_p->SetMaximum(r_max);
922  gr_r_p->SetMinimum(r_min);
923  gr_r_p->GetXaxis()->SetRangeUser(0, 90);
924  gr_r_s->Draw("lsame");
925  gr_r_s->SetBit(kCanDelete);
926  TGraph* grBrewster = new TGraph();
927  double brewster_angle = TMath::RadToDeg()*TMath::ATan2(n_ice, n_air);
928  grBrewster->SetPoint(0, brewster_angle, r_min);
929  grBrewster->SetPoint(1, brewster_angle, r_max);
930  grBrewster->SetLineStyle(2);
931  grBrewster->SetLineColor(kMagenta);
932  grBrewster->Draw("lsame");
933  grBrewster->SetBit(kCanDelete);
934 
935  l1b->AddEntry(gr_r_s, "r_s (E-field perpendicular to plane of incidence)", "l");
936  l1b->AddEntry(gr_r_p, "r_p (H-field perpendicular to plane of incidence)", "l");
937  l1b->AddEntry(grBrewster, "Brewster angle", "l");
938  l1b->SetBit(kCanDelete);
939 
940  l1b->Draw();
941 
942  c1->cd();
943 
944  return c1;
945 }
946 
947 
948 
960 TVector3 GeoMagnetic::fresnelReflection(const TVector3& incidentPoyntingVector, const TVector3& surfaceNormal, TVector3& electricFieldVec, double n1, double n2){
961 
962  double theta_i = surfaceNormal.Angle(-incidentPoyntingVector); // factor of -1 since the incident ray is downgoing
963  double cos_theta_i = TMath::Cos(theta_i);
964  double sin_theta_i = TMath::Sin(theta_i);
965 
966  // Snell's law + trig identities
967  double cos_theta_t = TMath::Sqrt(1 - (n1*n1*sin_theta_i*sin_theta_i/(n2*n2)));
968 
969  const TVector3 reflectedPoyntingVector = specularReflection(incidentPoyntingVector, surfaceNormal);
970 
971  // from the Wikipedia...
972  // In this treatment, the coefficient r is the ratio of the reflected wave's complex electric field amplitude to that of the incident wave.
973  // The light is split into s and p polarizations
974  // s => E field is perpendicular to plane of incidence
975  // p => E field is parallel to the plane of incidence
976  // For s polarization, a POSITIVE r or t means that the ELECTRIC fields of the incoming and reflected (or transmitted) waves are PARALLEL, while negative means anti-parallel.
977  // For p polarization, a POSITIVE r or t means that the MAGNETIC fields of the incoming and reflected (or transmitted) waves are PARALLEL, while negative means anti-parallel.
978 
979  // +ve r_s means E field (perpendicular to plane of incidence) is parallel on the way out (-ve means anti-parallel)
980  // +ve r_p means H field (perpendicular to plane of incidence) is parallel on the way out (-ve means anti-parallel)
981  // in the case of normal incidence, if the poynting vector is reversed but the H field is parallel, the E field must be anti-parallel...
982  // therefore there should be a factor of -1 applied to the reflected electric field vector in front of r_p.
983 
984  TVector3 normalToPlaneOfIncidence = incidentPoyntingVector.Cross(reflectedPoyntingVector).Unit();
985 
986  if(normalToPlaneOfIncidence.Mag()==0){
987  // then the incoming and outgoing vectors are anti-parallel and we are free to chose a plane of incidence.
988  normalToPlaneOfIncidence.SetXYZ(0, 1, 0);
989  }
990 
991  double r_s = (n1*cos_theta_i - n2*cos_theta_t)/(n1*cos_theta_i + n2*cos_theta_t);
992  double r_p = (n2*cos_theta_i - n1*cos_theta_t)/(n2*cos_theta_i + n1*cos_theta_t);
993 
994  const TVector3 z_hat_local = surfaceNormal.Unit();
995  const TVector3 y_hat_local = normalToPlaneOfIncidence; // therefore y_hat is definitionally perpendicular to the plane of incidence
996  const TVector3 x_hat_local = y_hat_local.Cross(z_hat_local);
997 
998  double E_s = electricFieldVec.Dot(y_hat_local);
999  double E_p_x = electricFieldVec.Dot(x_hat_local);
1000  double E_p_z = electricFieldVec.Dot(z_hat_local);
1001 
1002  double reflected_E_s = r_s*E_s; // +ve r_s means E field is parallel
1003  double reflected_E_p_x = r_p*E_p_x; // +ve r_p means H field is parallel, which means E-field is flipped
1004  double reflected_E_p_z = r_p*E_p_z; // +ve r_p means H field is parallel, which means E-field is flipped
1005 
1006  TVector3 reflectedElectricFieldVec = reflected_E_p_x*x_hat_local + reflected_E_s*y_hat_local + reflected_E_p_z*z_hat_local;
1007 
1008  if(debug){
1009  std::cout << "incident angle = " << theta_i*TMath::RadToDeg() << " degrees, transmission angle = " << TMath::ACos(cos_theta_t)*TMath::RadToDeg() << " degrees" << std::endl;
1010  std::cout << "cos(theta_i) = " << cos_theta_i << ", cos(theta_t) = " << cos_theta_t << std::endl;
1011  std::cout << "r_s(theta_i) = " << r_s << ", r_p(theta_i) = " << r_p << std::endl;
1012 
1013  if(TMath::Abs((x_hat_local.Cross(y_hat_local) - z_hat_local).Mag()) > 1e-19){
1014  std::cout << "Checking that my unit vectors make sense..." << std::endl;
1015  std::cout << "|x| = " << x_hat_local.Mag() << ", |y| = " << y_hat_local.Mag() << ", |z| = " << z_hat_local.Mag() << std::endl;
1016  std::cout << "x.y = " << x_hat_local.Dot(y_hat_local) << ", x.z = " << x_hat_local.Dot(z_hat_local) << ", x.z = " << y_hat_local.Dot(z_hat_local) << std::endl;
1017  std::cout << "|(x.Cross(y)) - z| = " << (x_hat_local.Cross(y_hat_local) - z_hat_local).Mag() << std::endl;
1018  }
1019  std::cout << "Original electric field = (" << electricFieldVec.X() << ", " << electricFieldVec.Y() << ", " << electricFieldVec.Z() << ")" << std::endl;
1020  std::cout << "Reflected electric field = (" << reflectedElectricFieldVec.X() << ", " << reflectedElectricFieldVec.Y() << ", " << reflectedElectricFieldVec.Z() << ")" << std::endl;
1021  std::cout << std::endl;
1022  }
1023 
1024  // modify the input electric field vector with the reflected value
1025  electricFieldVec = reflectedElectricFieldVec;
1026 
1027  // return the reflected poynting vector...
1028  return reflectedPoyntingVector;
1029 }
1030 
1031 
1032 
1043 TVector3 GeoMagnetic::getUnitVectorAlongThetaWavePhiWave(UsefulAdu5Pat& usefulPat, double phiWave, double thetaWave){
1044  prepareGeoMagnetics();
1045 
1046  TVector3 anitaPosition = lonLatAltToVector(usefulPat.longitude, usefulPat.latitude, usefulPat.altitude);
1047 
1048  // now I need to get a vector pointing along thetaWave and phiWave from ANITA's position
1049  // so let's get theta and phi wave from an arbitrary position close to the payload,
1050  // evaluate theta/phi expected and rotate that vector around ANITA's position
1051  // until it aligns with phiWave...
1052 
1053  // This is just due north of ANITA
1054  double testLon = usefulPat.longitude;
1055  double testLat = usefulPat.latitude - 0.1; // if ANITA could be at the north pole, this might not work
1056  double testAlt = usefulPat.altitude;
1057 
1058  double testThetaWave, testPhiWave;
1059  usefulPat.getThetaAndPhiWave(testLon, testLat, testAlt, testThetaWave, testPhiWave);
1060 
1061  TVector3 testVector = lonLatAltToVector(testLon, testLat, testAlt);
1062 
1063  if(debug){
1064  std::cout << "Before rotation..." << std::endl;
1065  std::cout << testLon << "\t" << testLat << "\t" << testAlt << std::endl;
1066  std::cout << testThetaWave << "\t" << testPhiWave << std::endl;
1067  std::cout << testThetaWave*TMath::RadToDeg() << "\t" << testPhiWave*TMath::RadToDeg() << std::endl;
1068  }
1069 
1070  // payload phi increases anticlockwise (around +ve z axis)
1071  // the TVector3 phi increases clockwise (around +ve z axis)
1072 
1073  const TVector3 unitAnita = anitaPosition.Unit();
1074  testVector.Rotate(-testPhiWave, unitAnita); // if we were to recalculate the phiWave expected, it would now point to 0
1075  testVector.Rotate(phiWave, unitAnita); // if we were to recalculate the phiWave expected, it would now point to phiWave
1076 
1077  vectorToLonLatAlt(testLon, testLat, testAlt, testVector);
1078  usefulPat.getThetaAndPhiWave(testLon, testLat, testAlt, testThetaWave, testPhiWave);
1079 
1080  if(debug){
1081  std::cout << "After phi rotation..." << std::endl;
1082  std::cout << testLon << "\t" << testLat << "\t" << testAlt << std::endl;
1083  std::cout << testThetaWave << "\t" << testPhiWave << std::endl;
1084  std::cout << testThetaWave*TMath::RadToDeg() << "\t" << testPhiWave*TMath::RadToDeg() << std::endl;
1085  }
1086 
1087  // now need to raise/lower the point described by testVector such that thetaWave is correct
1088  // i.e. set the magnitude of the testVector such that thetaWave is correct
1089  //
1090  //
1091  // O
1092  // Earth Centre (Origin) o
1093  // |\ a
1094  // t | \
1095  // | \
1096  // ANITA o---o "Test Vector"
1097  // A T
1098 
1099  // O, A, T are the angles
1100  // o, a, t are the lengths. I'm trying to find the length a for a given angle A.
1101  // a / sin(A) = t / sin(T)
1102  //
1103  // t = anitaPosition.Mag();
1104  // A = (pi/2 - thetaWave);
1105  // O = angle between ANITA and the test vector
1106  // T = pi - A - O
1107  // so...
1108  // a = sin(A) * t / (pi - A - O)
1109  double A = TMath::PiOver2() - thetaWave;
1110  double O = testVector.Angle(anitaPosition); //angle between the vectors
1111  double T = TMath::Pi() - A - O;
1112  double t = anitaPosition.Mag();
1113  double a = TMath::Sin(A)*(t/TMath::Sin(T));
1114 
1115  if(debug){
1116  std::cout << thetaWave*TMath::RadToDeg() << "\t" << A*TMath::RadToDeg() << "\t" << O*TMath::RadToDeg() << "\t" << T*TMath::RadToDeg() << std::endl;
1117  std::cout << a << "\t" << t << "\t" << a - t << std::endl;
1118  }
1119 
1120  testVector.SetMag(a);
1121 
1122  if(debug){
1123  vectorToLonLatAlt(testLon, testLat, testAlt, testVector);
1124  usefulPat.getThetaAndPhiWave(testLon, testLat, testAlt, testThetaWave, testPhiWave);
1125  std::cout << "After setting mangitude..." << std::endl;
1126  std::cout << testLon << "\t" << testLat << "\t" << testAlt << std::endl;
1127  std::cout << testThetaWave << "\t" << testPhiWave << std::endl;
1128  std::cout << testThetaWave*TMath::RadToDeg() << "\t" << testPhiWave*TMath::RadToDeg() << std::endl;
1129  }
1130 
1131  TVector3 deltaVec = testVector - anitaPosition;
1132  return deltaVec.Unit();
1133 }
1134 
1135 
1136 
1137 
1138 
1149 double GeoMagnetic::getExpectedPolarisation(UsefulAdu5Pat& usefulPat, double phiWave, double thetaWave,
1150  double xMax){
1151 
1152  prepareGeoMagnetics();
1153 
1154  if(debug){
1155  std::cout << "Anita position = " << usefulPat.longitude << "\t" << usefulPat.latitude << "\t" << usefulPat.altitude << std::endl;
1156  }
1157 
1158  // use the silly UsefulAdu5Pat convention that -ve theta is down...
1159  // phiWave is in radians relative to ADU5 Aft Fore line
1160 
1161  double reflectionLon=0, reflectionLat=0, reflectionAlt=0, deltaTheta=100; // need non-zero deltaTheta when testing whether things intersectg, as theta < 0 returns instantly
1162 
1163  //histGround==1 or 2 means it hits ground, 0 means it doesn't
1164  int hitsGround = usefulPat.traceBackToContinent(phiWave, thetaWave, &reflectionLon, &reflectionLat, &reflectionAlt, &deltaTheta);
1165 
1166  TVector3 destination; // ANITA position if direct cosmic ray or surface position if reflected cosmic ray
1167  TVector3 destinationToSource; // unit vector from ANITA (if direct) or reflection position (if indirect) which points in the direction the cosmic ray signal came from
1168  bool directCosmicRay = hitsGround == 0 ? true : false; // direct cosmic ray?
1169 
1170  TVector3 anitaPosition = lonLatAltToVector(usefulPat.longitude, usefulPat.latitude, usefulPat.altitude);
1171 
1172  // Only used in the reflected case...
1173  TVector3 surfaceNormal;
1174  TVector3 reflectionToAnita;
1175 
1176  // here we do the geometry slightly differently for the direct vs. reflected case
1177  if(directCosmicRay){
1178  if(debug){
1179  std::cout << "I'm a direct Cosmic Ray! (" << hitsGround << ") " << deltaTheta << "\t" << reflectionLon << "\t" << reflectionLat << "\t" << reflectionAlt << std::endl;
1180  }
1181  destination = anitaPosition;
1182  destinationToSource = getUnitVectorAlongThetaWavePhiWave(usefulPat, phiWave, thetaWave);
1183  }
1184  else{ // reflected cosmic ray
1185  if(debug){
1186  std::cout << "I'm a reflected Cosmic Ray! (" << hitsGround << ") " << deltaTheta << "\t" << reflectionLon << "\t" << reflectionLat << "\t" << reflectionAlt << std::endl;
1187  }
1188  destination = lonLatAltToVector(reflectionLon, reflectionLat, reflectionAlt); // i.e. the reflection point
1189 
1190  reflectionToAnita = anitaPosition - destination; // from reflection to anita...
1191 
1192  // here I find the normal to the geoid surface by getting the vector difference between
1193  // a point 1 m above the reflection and the reflection
1194  surfaceNormal = (lonLatAltToVector(reflectionLon, reflectionLat, reflectionAlt + 1) - destination).Unit();
1195 
1196  // Reflect the incoming vector...
1197  TVector3 incomingVector = specularReflection(reflectionToAnita, surfaceNormal);
1198 
1199  // but I want the vector pointing from the reflection out towards where the incoming signal came from
1200  destinationToSource = -incomingVector.Unit();
1201  }
1202 
1203  // Here we get the position the cosmic ray was on top of the atmosphere...
1204  TVector3 cosmicRayAtmosphericEntry = getInitialPosition(destination, destinationToSource);
1205 
1206  // ...And the direction it travelled through the atmosphere...
1207  const TVector3 cosmicRayDirection = -destinationToSource.Unit();
1208 
1209  // We integrate along that vector with our atmospheric model until we get to xMax, where the cosmic ray would interact, on average
1210  TVector3 xMaxPosition = getXMaxPosition(cosmicRayAtmosphericEntry, cosmicRayDirection, xMax);
1211 
1212  // Calculate the geo-magnetic field field at x-max
1213  FieldPoint fp(usefulPat.realTime, xMaxPosition);
1214  if (debug){
1215  std::cout << "FieldPoint position:" << fp.posX() << "," << fp.posY() << ","<< fp.posZ() << std::endl;
1216  std::cout << "FieldPoint vector:" << fp.componentX() << "," << fp.componentY() << ","<< fp.componentZ() << std::endl;
1217  }
1218  // This is our electric field vector!
1219  // If I cared about getting the magnitude correct in addition to the orientation, there are some missing factors
1220  // It should be: B_vec x S_vec = (1/mu0)B^{2} E_vec
1221  // But there's no radio cherenkov/geomagnetic shower model or anything so for now just the cross product will do.
1222  TVector3 EVec = fp.field().Cross(cosmicRayDirection).Unit();
1223  if (debug) {
1224  std::cout << "EVec: (" << EVec.X() << "," << EVec.Y() << "," << EVec.Z() << ")" << std::endl;
1225  }
1226 
1227  if(!directCosmicRay){
1228  // Modifies EVec by applying the Fresnel coefficients during the reflection
1229  TVector3 reflectionToAnita2 = fresnelReflection(cosmicRayDirection, surfaceNormal, EVec);
1230 
1231  if(debug){
1232  double shouldBeZero = reflectionToAnita.Angle(reflectionToAnita2);
1233  std::cout << "Doing the reflection on the way back... the angle between reflectionToAnita and reflectionToAnita2 = " << shouldBeZero << std::endl;
1234  }
1235  }
1236 
1237  // Here I find the VPol and HPol antenna axes.
1238  // And I'm going to pretend that one of ANITA's antennas points exactly at phiWave
1239  // getting an off axis response will be more complicated
1240 
1241  // Since the antennas points down at -10 degrees, the VPol axis is 80 degrees above the horizontal plane
1242  TVector3 vPolAxis = getUnitVectorAlongThetaWavePhiWave(usefulPat, phiWave, -80*TMath::DegToRad());
1243  // The VPol feed is up... (if) the HPol feed is to the right (looking down the boresight) then it points anticlockwise around the payload
1244  // phi increases anti-clockwise in payload coordinates, therefore
1245  TVector3 hPolAxis = getUnitVectorAlongThetaWavePhiWave(usefulPat, phiWave + TMath::PiOver2(), 0);//-10*TMath::DegToRad());
1246 
1247  if(debug){
1248  TVector3 antennaAxis = getUnitVectorAlongThetaWavePhiWave(usefulPat, phiWave, -10*TMath::DegToRad());
1249  std::cout << "The dot products of any pair of the antenna axis, hPolAxis, vPolAxis should be zero..." << std::endl;
1250  std::cout << "antennaAxis dot hPolAxis = " << antennaAxis.Dot(hPolAxis) << std::endl;
1251  std::cout << "antennaAxis dot vPolAxis = " << antennaAxis.Dot(vPolAxis) << std::endl;
1252  std::cout << "hPolAxis dot vPolAxis = " << hPolAxis.Dot(vPolAxis) << std::endl;
1253  }
1254 
1255  // Dot the electric field with the antenna polarisation vectors...
1256  double vPolComponent = EVec.Dot(vPolAxis);
1257  double hPolComponent = EVec.Dot(hPolAxis);
1258  if (debug) {
1259  std::cout << "vPolComponent: " << vPolComponent << std::endl;
1260  std::cout << "hPolComponent: " << hPolComponent << std::endl;
1261  }
1262 
1263  // et voila
1264  double polarisationAngle = TMath::ATan(vPolComponent/hPolComponent);
1265 
1266  return polarisationAngle;
1267 }
1268 
1269 
1270 
1282 double GeoMagnetic::getExpectedPolarisationUpgoing(UsefulAdu5Pat& usefulPat, double phiWave, double thetaWave,
1283  double pathLength){
1284  if (debug) {
1285  std::cout << "----------------GeoMagnetic::getExpectedPolarisationUpgoing(): Begin." << std::endl;
1286  }
1287  prepareGeoMagnetics();
1288 
1289 
1290  if(debug){
1291  std::cout << "Anita position = " << usefulPat.longitude << "\t" << usefulPat.latitude << "\t" << usefulPat.altitude << std::endl;
1292  }
1293 
1294  // use the silly UsefulAdu5Pat convention that -ve theta is down...
1295  // phiWave is in radians relative to ADU5 Aft Fore line
1296 
1297  double reflectionLon=0, reflectionLat=0, reflectionAlt=0, deltaTheta=100; // need non-zero deltaTheta when testing whether things intersectg, as theta < 0 returns instantly
1298  int hitsGround = usefulPat.traceBackToContinent(phiWave, thetaWave, &reflectionLon, &reflectionLat, &reflectionAlt, &deltaTheta);
1299 
1300  TVector3 destination; // ANITA position if direct cosmic ray or surface position if reflected cosmic ray
1301  TVector3 destinationToSource; // unit vector from ANITA (if direct) or reflection position (if indirect) which points in the direction the cosmic ray signal came from
1302  bool directCosmicRay = hitsGround == 0 ? true : false; // direct cosmic ray?
1303 
1304  TVector3 anitaPosition = lonLatAltToVector(usefulPat.longitude, usefulPat.latitude, usefulPat.altitude);
1305 
1306  // here we do the geometry slightly differently for the direct vs. reflected case
1307  if(directCosmicRay){
1308  if (debug) {
1309  std::cout << "I'm pointed above the horizon! I can't be an upgoing event! Exiting..." << std::endl;
1310  std::cout << "deltaTheta=" << deltaTheta << " hitsGround=" << hitsGround << std::endl;
1311  }
1312  return -9999;
1313  }
1314  else{ // reflected cosmic ray
1315  if(debug){
1316  std::cout << "I'm pointed at the continent! " << deltaTheta << "\t" << reflectionLon << "\t" << reflectionLat << "\t" << reflectionAlt << std::endl;
1317  }
1318  destination = lonLatAltToVector(reflectionLon, reflectionLat, reflectionAlt); // i.e. the source on the ice
1319  destinationToSource = getUnitVectorAlongThetaWavePhiWave(usefulPat, phiWave, thetaWave); // from ANITA to ice
1320  }
1321 
1322  const TVector3 surfaceNormal = (lonLatAltToVector(reflectionLon, reflectionLat, reflectionAlt + 1) - destination).Unit();
1323 
1324  //the cosmic ray is traveling in the opposite direction as the direction from ANITA to the ice
1325  const TVector3 cosmicRayDirection = -destinationToSource.Unit();
1326 
1327  double emergenceAngle = surfaceNormal.Angle(cosmicRayDirection);
1328  if (debug) {
1329  std::cout << "surfaceNormal: (" << surfaceNormal.X() << "," << surfaceNormal.Y() << "," << surfaceNormal.Z() << ")" << std::endl;
1330  std::cout << "cosmicRayDirection: (" << cosmicRayDirection.X() << "," << cosmicRayDirection.Y() << "," << cosmicRayDirection.Z() << ")" << std::endl;
1331  std::cout << "Emergence Angle: " << emergenceAngle << std::endl;
1332 }
1333 
1334 
1335 
1336  //calculate where the shower max is, which is from the ice in the direction of the shower pathLength away
1337  TVector3 xMaxPosition = destination+cosmicRayDirection*pathLength;
1338 
1339 
1340  if (debug) {
1341  std::cout << "anitaPosition: (" << anitaPosition.X() << "," << anitaPosition.Y() << "," << anitaPosition.Z() << ")" << std::endl;
1342  std::cout << "destination: (" << destination.X() << "," << destination.Y() << "," << destination.Z() << ")" << std::endl;
1343  std::cout << "destinationToSource: (" << destinationToSource.X() << "," << destinationToSource.Y() << "," << destinationToSource.Z() << ")" << std::endl;
1344  std::cout << "cosmicRayDirection: (" << cosmicRayDirection.X() << "," << cosmicRayDirection.Y() << "," << cosmicRayDirection.Z() << ")" << std::endl;
1345  std::cout << "xMaxPosition: (" << xMaxPosition.X() << "," << xMaxPosition.Y() << "," << xMaxPosition.Z() << ")" << std::endl;
1346  }
1347 
1348 
1349  // Calculate the geo-magnetic field field at x-max
1350  FieldPoint fp(usefulPat.realTime, xMaxPosition);
1351  if (debug) {
1352  std::cout << "FieldPoint: pos=(" << fp.posX() << "," << fp.posY() << "," << fp.posZ() << ")" << std::endl;
1353  std::cout << " comp=(" << fp.componentX() << "," << fp.componentY() << "," << fp.componentZ() << std::endl;
1354  }
1355 
1356 
1357  // This is our electric field vector!
1358  // If I cared about getting the magnitude correct in addition to the orientation, there are some missing factors
1359  // It should be: B_vec x S_vec = (1/mu0)B^{2} E_vec
1360  // But there's no radio cherenkov/geomagnetic shower model or anything so for now just the cross product will do.
1361  TVector3 EVec = fp.field().Cross(cosmicRayDirection).Unit();
1362  if (debug) {
1363  std::cout << "EVec: (" << EVec.X() << "," << EVec.Y() << "," << EVec.Z() << ")" << std::endl;
1364  }
1365 
1366  // Here I find the VPol and HPol antenna axes.
1367  // And I'm going to pretend that one of ANITA's antennas points exactly at phiWave
1368  // getting an off axis response will be more complicated
1369 
1370  // Since the antennas points down at -10 degrees, the VPol axis is 80 degrees above the horizontal plane
1371  TVector3 vPolAxis = getUnitVectorAlongThetaWavePhiWave(usefulPat, phiWave, -80*TMath::DegToRad());
1372  // The VPol feed is up... (if) the HPol feed is to the right (looking down the boresight) then it points anticlockwise around the payload
1373  // phi increases anti-clockwise in payload coordinates, therefore
1374  TVector3 hPolAxis = getUnitVectorAlongThetaWavePhiWave(usefulPat, phiWave + TMath::PiOver2(), 0);
1375 
1376  // Dot the electric field with the antenna polarisation vectors...
1377  double vPolComponent = EVec.Dot(vPolAxis);
1378  double hPolComponent = EVec.Dot(hPolAxis);
1379  if (debug) {
1380  std::cout << "vPolComponent: " << vPolComponent << std::endl;
1381  std::cout << "hPolComponent: " << hPolComponent << std::endl;
1382  }
1383 
1384  // et voila
1385  double polarisationAngle = TMath::ATan(vPolComponent/hPolComponent);
1386 
1387  return polarisationAngle;
1388 
1389 
1390 }
1391 
1392 
1393 
1394 
1401 TCanvas* GeoMagnetic::plotAtmosphere(){
1402  prepareGeoMagnetics();
1403 
1404  static int maxCanvas = 0;
1405  TCanvas* c1 = NULL;
1406  if(maxCanvas < 10){
1407  c1 = new TCanvas();
1408  grAtmosDensity.Draw("al");
1409  maxCanvas++;
1410  }
1411  return c1;
1412 }
1413 
1414 
1415 
1416 double GeoMagnetic::getAtmosphericDensity(double altitude){
1417  prepareGeoMagnetics();
1418  // TODO, convert altitude (above geoid) to height above MSL...
1419  return fExpAtmos->Eval(altitude);
1420 }
1421 
1422 
1423 
1424 
1425 
1435 TVector3 GeoMagnetic::getInitialPosition(const TVector3& destination, const TVector3& destinationToSource){
1436  prepareGeoMagnetics();
1437 
1438  if(TMath::Abs(destinationToSource.Mag() -1) > 1e-14){
1439  std::cerr << "Warning in " << __PRETTY_FUNCTION__ << ", was expecting a unit vector, didn't get one. "
1440  << "Got " << 1.0-destinationToSource.Mag() << " away from mag==1... This calculation might be wonky... " << std::endl;
1441  }
1442 
1443 
1444  // moves out from the destination in 1km steps until the altitude is 80km
1445  const double desiredAlt = 80e3; // 100 km
1446  TVector3 initialPosition = destination;
1447  double initialLon, initialLat, initialAlt;
1448  vectorToLonLatAlt(initialLon, initialLat, initialAlt, initialPosition);
1449  if(debug){
1450  std::cout << "Initial alt = " << initialAlt << ", (desiredAlt = " << desiredAlt << ")" << std::endl;
1451  }
1452  while(initialAlt < desiredAlt){
1453  initialPosition += 1e3*destinationToSource;
1454  vectorToLonLatAlt(initialLon, initialLat, initialAlt, initialPosition);
1455  // std::cout << initialLon << "\t" << initialLat << "\t" << initialAlt << std::endl;
1456  }
1457  if(debug){
1458  std::cout << "Got initial position... " << initialLon << "\t" << initialLat << "\t" << initialAlt << std::endl;
1459  }
1460 
1461  return initialPosition;
1462 }
1463 
1464 
1465 
1466 
1467 
1477 TVector3 GeoMagnetic::getXMaxPosition(const TVector3& initialPosition, const TVector3& cosmicRayDirection,
1478  double xMax){
1479  prepareGeoMagnetics();
1480 
1481  TVector3 currentPosition = initialPosition;
1482  double currentAtmosphereTraversed = 0; // kg / m ^{2}
1483  double dx = cosmicRayDirection.Mag();
1484 
1485  int numSteps = 0;
1486 
1487  TGraph* grAltPath = debug ? new TGraph() : NULL;
1488 
1489  double lastAlt = 0;
1490  double initialAlt = 0;
1491  while(currentAtmosphereTraversed < xMax){
1492 
1493  currentPosition += cosmicRayDirection;
1494  double currentLon, currentLat, currentAlt;
1495  vectorToLonLatAlt(currentLon, currentLat, currentAlt, currentPosition);
1496 
1497  if(initialAlt==0){
1498  initialAlt= currentAlt;
1499  }
1500 
1501 
1502  if(debug && currentAtmosphereTraversed == 0){
1503  std::cout << "Finding xMax position... " << xMax << " Initial position... " << currentLon << "\t" << currentLat << "\t" << currentAlt << "\t" << currentAtmosphereTraversed << std::endl;
1504  }
1505 
1506  currentAtmosphereTraversed += getAtmosphericDensity(currentAlt)*dx;
1507 
1508  if(debug && currentAtmosphereTraversed >= xMax){
1509  std::cout << "Found xMax position... " << xMax << " Initial position... " << currentLon << "\t" << currentLat << "\t" << currentAlt << "\t" << currentAtmosphereTraversed << std::endl;
1510  }
1511 
1512  // then we're ascending without reaching xMax, which is bad
1513  if(currentAlt > lastAlt && lastAlt > initialAlt){
1514  std::cerr << "Warning in " <<__PRETTY_FUNCTION__ << ", unable to find xMax, terminating loop." << std::endl;
1515  std::cerr << "Current position = " << currentLon << "\t" << currentLat << "\t" << currentAlt << "\t" << currentAtmosphereTraversed << std::endl;
1516  break;
1517  }
1518 
1519  if(debug){
1520  grAltPath->SetPoint(numSteps, dx*numSteps, currentAlt);
1521  numSteps++;
1522  }
1523  lastAlt = currentAlt;
1524  }
1525 
1526  if(debug){
1527  static int maxCanvas = 0;
1528  TCanvas* c1 = NULL;
1529  if(maxCanvas < 10){
1530  c1 = new TCanvas();
1531  grAltPath->SetTitle("Cosmic Ray Altitude vs. distance traversed; Distance through atmosphere (m); Altitude (m)");
1532  grAltPath->SetBit(kCanDelete);
1533  grAltPath->Draw("al");
1534  maxCanvas++;
1535  }
1536  }
1537 
1538  return currentPosition;
1539 }
1540 
1541 
virtual void Draw(Option_t *opt="")
Float_t latitude
In degrees.
Definition: Adu5Pat.h:42
Float_t longitude
In degrees.
Definition: Adu5Pat.h:43
UInt_t realTime
Time from the GPS unit.
Definition: Adu5Pat.h:37
void getThetaAndPhiWave(Double_t sourceLon, Double_t sourceLat, Double_t sourceAlt, Double_t &thetaWave, Double_t &phiWave)
Float_t altitude
In metres.
Definition: Adu5Pat.h:44
FieldPoint(UInt_t unixTime, double lon, double lat, double alt)
Inelasticity distributions: stores parametrizations and picks inelasticities.
Definition: Primaries.h:38
As UsefulAnitaEvent is to RawAnitaEvent, UsefulAdu5Pat is to Adu5Pat. Well not quite as useful but yo...
Definition: UsefulAdu5Pat.h:39
int traceBackToContinent(Double_t phiWave, Double_t thetaWave, Double_t *lon, Double_t *lat, Double_t *alt, Double_t *theta_adjustment_required, Double_t max_theta_adjustment=TMath::Pi()/180, Int_t max_iter=10) const
static void LonLatToEastingNorthing(Double_t lon, Double_t lat, Double_t &easting, Double_t &northing)
virtual void Draw(Option_t *opt="")
static AnitaGeomTool * Instance(int anita_version=0)
Instance generator. If version_number == 0, uses AnitaVersion::get();.
AnitaGeomTool – The ANITA Geometry Tool.
Definition: AnitaGeomTool.h:48
static void EastingNorthingToLonLat(Double_t easting, Double_t northing, Double_t &lon, Double_t &lat)