From 956d5ae4cb7e6364f27ca1947cb0be8e17dba2b8 Mon Sep 17 00:00:00 2001
From: ralfulrich <ralf.ulrich@kit.edu>
Date: Tue, 21 Dec 2021 11:17:00 +0100
Subject: [PATCH] update to magnetic field model testing

---
 corsika/detail/media/GeomagneticModel.inl | 51 +++++++++++++----------
 corsika/media/GeomagneticModel.hpp        |  5 ++-
 tests/media/CMakeLists.txt                |  6 +++
 tests/media/EmptyGeomagneticData.COF      |  1 +
 tests/media/testMagneticField.cpp         | 17 ++++++--
 5 files changed, 53 insertions(+), 27 deletions(-)
 create mode 100644 tests/media/EmptyGeomagneticData.COF

diff --git a/corsika/detail/media/GeomagneticModel.inl b/corsika/detail/media/GeomagneticModel.inl
index 910792b7f..9e15b454d 100644
--- a/corsika/detail/media/GeomagneticModel.inl
+++ b/corsika/detail/media/GeomagneticModel.inl
@@ -7,9 +7,6 @@
  */
 
 #include <corsika/framework/core/Logging.hpp>
-#include <corsika/framework/utility/CorsikaData.hpp>
-
-#include <boost/filesystem.hpp>
 
 #include <stdexcept>
 #include <string>
@@ -18,16 +15,15 @@
 namespace corsika {
 
   inline GeomagneticModel::GeomagneticModel(Point const& center,
-                                            std::string const& dataFile)
+                                            boost::filesystem::path const path)
       : center_(center) {
 
     // Read in coefficients
-    boost::filesystem::path const path = corsika::corsika_data(dataFile);
     boost::filesystem::ifstream file(path, std::ios::in);
 
     // Exit if file opening failed
     if (!file.is_open()) {
-      CORSIKA_LOG_ERROR("Failed opening data file {}", dataFile);
+      CORSIKA_LOG_ERROR("Failed opening data file {}", path);
       throw std::runtime_error("Cannot load GeomagneticModel data.");
     }
 
@@ -93,6 +89,11 @@ namespace corsika {
       }
     }
     file.close();
+
+    if (parameters_.size() == 0) {
+      CORSIKA_LOG_ERROR("No input data read!");
+      throw std::runtime_error("No input data read");
+    }
   }
 
   inline MagneticFieldVector GeomagneticModel::getField(double const year,
@@ -101,17 +102,14 @@ namespace corsika {
                                                         double const longitude) {
 
     int iYear = int(year);
-    int iEpoch = 0;
-    for (auto parIt = parameters_.rbegin(); parIt != parameters_.rend(); ++parIt) {
-      if (parIt->first <= iYear) {
-        iEpoch = parIt->first;
-        break;
-      }
+    auto iEpoch = parameters_.rbegin();
+    for (; iEpoch != parameters_.rend(); ++iEpoch) {
+      if (iEpoch->first <= iYear) { break; }
     }
-    double epoch = double(iEpoch);
-    CORSIKA_LOG_DEBUG("Found Epoch {} for year {}", iEpoch, year);
-    if (iEpoch == 0) {
+    CORSIKA_LOG_DEBUG("Found Epoch {} for year {}", iEpoch->first, year);
+    if (iEpoch == parameters_.rend()) {
       CORSIKA_LOG_WARN("Year {} is before first EPOCH. Results unclear.", year);
+      iEpoch--; // move one epoch back
     }
     if (altitude < -1_km || altitude > 600_km) {
       CORSIKA_LOG_WARN("Altitude should be between -1_km and 600_km.");
@@ -125,6 +123,7 @@ namespace corsika {
     if (longitude < -180 || longitude > 180) {
       CORSIKA_LOG_WARN("Longitude should be between -180 and 180 degree.");
     }
+    double epoch = double(iEpoch->first);
 
     const double lat_geo = latitude * constants::pi / 180;
     const double lon = longitude * constants::pi / 180;
@@ -142,17 +141,23 @@ namespace corsika {
     double legendre, next_legendre, derivate_legendre;
     double magneticfield[3] = {0, 0, 0};
 
-    for (size_t j = 0; j < parameters_[iEpoch].size(); j++) {
+    for (size_t j = 0; j < iEpoch->second.size(); j++) {
 
-      ParameterLine p = parameters_[iEpoch][j];
+      ParameterLine p = iEpoch->second[j];
 
       // Time interpolation
-      p.g = p.g + (year - epoch) * p.dg;
-      p.h = p.h + (year - epoch) * p.dh;
-      if (iEpoch < 2020) {
-        ParameterLine next_p = parameters_[iEpoch + 5][j];
-        p.g = p.g + (next_p.g - p.g) * (year - epoch) / 5;
-        p.h = p.h + (next_p.h - p.h) * (year - epoch) / 5;
+      if (iEpoch == parameters_.rbegin() || p.dg != 0 || p.dh != 0) {
+        // this is the latest epoch in time, or time-dependence (dg/dh) was specified
+        // we use the extrapolation factors dg/dh:
+        p.g = p.g + (year - epoch) * p.dg;
+        p.h = p.h + (year - epoch) * p.dh;
+      } else {
+        // we linearly interpolate between two epochs
+        auto const nextEpoch = --iEpoch; // next epoch
+        ParameterLine const next_p = nextEpoch->second[j];
+        const double length = nextEpoch->first - epoch;
+        p.g = p.g + (next_p.g - p.g) * (year - epoch) / length;
+        p.h = p.h + (next_p.h - p.h) * (year - epoch) / length;
       }
 
       legendre = pow(-1, p.m) * std::assoc_legendre(p.n, p.m, sin(lat_sph));
diff --git a/corsika/media/GeomagneticModel.hpp b/corsika/media/GeomagneticModel.hpp
index 0c3d76096..bb9b3b923 100644
--- a/corsika/media/GeomagneticModel.hpp
+++ b/corsika/media/GeomagneticModel.hpp
@@ -10,7 +10,9 @@
 
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/PhysicalGeometry.hpp>
+#include <corsika/framework/utility/CorsikaData.hpp>
 
+#include <boost/filesystem.hpp>
 #include <fstream>
 #include <string>
 
@@ -44,7 +46,8 @@ namespace corsika {
      * @param center Center of Earth.
      * @param data Data table to read.
      */
-    GeomagneticModel(Point const& center, std::string const& data = "GeoMag/WMM.COF");
+    GeomagneticModel(Point const& center, boost::filesystem::path const path =
+                                              corsika::corsika_data("GeoMag/WMM.COF"));
 
     /**
      * Calculates the value of the magnetic field.
diff --git a/tests/media/CMakeLists.txt b/tests/media/CMakeLists.txt
index c1265193c..5a8442123 100644
--- a/tests/media/CMakeLists.txt
+++ b/tests/media/CMakeLists.txt
@@ -12,3 +12,9 @@ set (test_media_sources
 CORSIKA_ADD_TEST (testMedia SOURCES ${test_media_sources})
 
 target_link_libraries (testMedia CONAN_PKG::boost) # for math/quadrature/gauss_kronrod.hpp
+
+target_compile_definitions (
+  testMedia
+  PRIVATE
+  REFDATADIR="${CMAKE_CURRENT_SOURCE_DIR}"
+)
diff --git a/tests/media/EmptyGeomagneticData.COF b/tests/media/EmptyGeomagneticData.COF
new file mode 100644
index 000000000..ba3fabdf8
--- /dev/null
+++ b/tests/media/EmptyGeomagneticData.COF
@@ -0,0 +1 @@
+# no data here...
\ No newline at end of file
diff --git a/tests/media/testMagneticField.cpp b/tests/media/testMagneticField.cpp
index f46297fa6..3d311e64b 100644
--- a/tests/media/testMagneticField.cpp
+++ b/tests/media/testMagneticField.cpp
@@ -9,6 +9,7 @@
 #include <corsika/framework/core/ParticleProperties.hpp>
 #include <corsika/framework/core/PhysicalUnits.hpp>
 #include <corsika/framework/geometry/RootCoordinateSystem.hpp>
+#include <corsika/framework/utility/CorsikaData.hpp>
 #include <corsika/media/HomogeneousMedium.hpp>
 #include <corsika/media/IMediumModel.hpp>
 #include <corsika/media/UniformMagneticField.hpp>
@@ -19,10 +20,13 @@
 #include <SetupTestTrajectory.hpp>
 #include <corsika/setup/SetupTrajectory.hpp>
 
+#include <boost/filesystem.hpp>
 #include <catch2/catch.hpp>
 
 using namespace corsika;
 
+const std::string refDataDir = std::string(REFDATADIR); // from cmake
+
 TEST_CASE("UniformMagneticField w/ Homogeneous Medium") {
 
   logging::set_level(logging::level::trace);
@@ -75,15 +79,19 @@ TEST_CASE("UniformMagneticField w/ Homogeneous Medium") {
 
   SECTION("GeomagneticModel") {
 
-    CHECK_THROWS(GeomagneticModel(gOrigin, "GeoMag/IDoNotExist.COF"));
+    // non existing data file
+    CHECK_THROWS(GeomagneticModel(gOrigin, corsika_data("GeoMag/IDoNotExist.COF")));
+    // and empty data file
+    CHECK_THROWS(GeomagneticModel(gOrigin, refDataDir + "/EmptyGeomagneticData.COF"));
 
     {
-      GeomagneticModel wmm(gOrigin, "GeoMag/WMM.COF");
+      GeomagneticModel wmm(gOrigin, corsika_data("GeoMag/WMM.COF"));
 
       // create earth magnetic field vector
       MagneticFieldVector Earth_B_1 = wmm.getField(2022.5, 100_km, -80, -120);
       MagneticFieldVector Earth_B_2 = wmm.getField(2022.5, 0_km, 0, 120);
       MagneticFieldVector Earth_B_3 = wmm.getField(2020, 100_km, 80, 0);
+      MagneticFieldVector Earth_B_4 = wmm.getField(2019.999, 100_km, 80, 0);
       CHECK(Earth_B_1.getX(gCS) / 1_nT == Approx(5815).margin(0.05));
       CHECK(Earth_B_1.getY(gCS) / 1_nT == Approx(-14803).margin(0.05));
       CHECK(Earth_B_1.getZ(gCS) / 1_nT == Approx(49755.3).margin(0.05));
@@ -93,9 +101,12 @@ TEST_CASE("UniformMagneticField w/ Homogeneous Medium") {
       CHECK(Earth_B_3.getX(gCS) / 1_nT == Approx(6261.8).margin(0.05));
       CHECK(Earth_B_3.getY(gCS) / 1_nT == Approx(185.5).margin(0.05));
       CHECK(Earth_B_3.getZ(gCS) / 1_nT == Approx(-52429.1).margin(0.05));
+      CHECK(Earth_B_4.getX(gCS) / 1_nT == Approx(6261.8).margin(0.05));
+      CHECK(Earth_B_4.getY(gCS) / 1_nT == Approx(185.5).margin(0.05));
+      CHECK(Earth_B_4.getZ(gCS) / 1_nT == Approx(-52429.1).margin(0.05));
     }
     {
-      GeomagneticModel igrf(gOrigin, "GeoMag/IGRF13.COF");
+      GeomagneticModel igrf(gOrigin, corsika_data("GeoMag/IGRF13.COF"));
 
       // create earth magnetic field vector
       MagneticFieldVector Earth_B_1 = igrf.getField(2022.5, 100_km, -80, -120);
-- 
GitLab