Program Listing for File gcpm_interface.cc

Return to documentation for file (environment/plasma/gcpm/gcpm_interface.cc)

#include "lupnt/environment/plasma/gcpm/gcpm_interface.h"

#include <array>
#include <cmath>
#include <filesystem>
#include <iostream>
#include <vector>

#include "lupnt/environment/plasma/core/math_utils.h"
#include "lupnt/environment/plasma/core/user_filepath.h"
#include "lupnt/environment/plasma/env/kp.h"
#include "lupnt/environment/plasma/gcpm/constants_gcpm.h"

namespace pecsim {

  extern "C" {
  void gcpm_v24_(int* itime, float* r, float* amlt, float* alatr, float* akp, float* outn);
  }

  std::vector<double> gcpm_v24(DateTime datetime, double r, double amlt, double alatr, double akp) {
    // first move to the data directory
    std::string curr_dir = std::filesystem::current_path();
    std::filesystem::current_path(get_iri_data_dir());

    // get kp index
    if (akp < 0) {
      akp = get_kp_index(datetime);
    }

    // Convert to itime
    std::array<int, 2> itime;
    itime[0] = datetime.year * 1000 + datetime.doy;  // Year and day of the year
    int ihr = datetime.hour, imin = datetime.min, isec = datetime.sec;
    itime[1] = int((ihr * 3600 + imin * 60 + isec) * 1000);  // Milliseconds of the day

    // Constants
    const double re = RE;  // Earth radius in km
    double clat, al, aheight;

    // the half width in L-shell of over which the transition
    // takes place between the trough and polar cap models.
    // The L-shell at which this transition is centered is
    // obtained from PN, which hold empirical locations for
    // the polarward edge of the auroral zone for several
    // values of Kp and magnetic local time.
    double altrans = 2.0;

    // Get f107 value from IRI
    IRIParams params = iri_sm(0.0, 0.0, 1.01, itime);
    double f107 = params.f107;
    double rz12 = params.rz12;
    double hmf2_km = params.hmf2_km;  // F2 peak height in km

// Persistent variables
#if USE_STATIC_GCPM_CPP
    static double oldmlt = -1.0, oldkp = -1.0;
    static double tranlow = 0.0, tranhigh = 0.0;
    static double alcrit;
#else
    double oldmlt = -1.0, oldkp = -1.0;
    double tranlow = 0.0, tranhigh = 0.0;
    double alcrit;
#endif

    // Will execute this section if we need new a new value for the
    // invarient latitude of the polarward edge of the auroral zone.
    // This location is determined as a function of MLT and Kp from
    // the array PN.
    oldmlt = amlt;
    oldkp = akp;
    double bmlt = amlt * 3.0 + 1.0;

    int imlt = std::floor(bmlt);
    double diffmlt = bmlt - static_cast<double>(imlt);
    if (imlt > 72) imlt = 1;
    int jmlt = imlt + 1;
    if (jmlt > 72) jmlt = 1;

    int ikp = static_cast<int>(akp + 1.0);
    double diffkp = akp - std::floor(akp);
    if (ikp > 10) ikp = 10;
    int jkp = ikp + 1;
    if (jkp > 10) jkp = 10;

    // Fortran indexing to C++ indexing
    imlt = imlt - 1;
    jmlt = jmlt - 1;
    ikp = ikp - 1;
    jkp = jkp - 1;

    double pn1 = (PN(jmlt, ikp) - PN(imlt, ikp)) * diffmlt + PN(imlt, ikp);
    double pn2 = (PN(jmlt, jkp) - PN(imlt, jkp)) * diffmlt + PN(imlt, jkp);
    double ps1 = (PS(jmlt, ikp) - PS(imlt, ikp)) * diffmlt + PS(imlt, ikp);
    double ps2 = (PS(jmlt, jkp) - PS(imlt, jkp)) * diffmlt + PS(imlt, jkp);

    double alatcrits = (ps2 - ps1) * diffkp + ps1;
    double alatcritn = (pn2 - pn1) * diffkp + pn1;

    alcrit = 1.0 / pow(cos(alatcritn * DEG2RAD), 2);
    double aurora_mlat = alatcritn;
    tranlow = alcrit - altrans;
    tranhigh = alcrit + altrans;

    // We need to obtain the L-shell of the location given, while limiting
    // the maximum L-shell that will be used.  Higher latitudes and L-shells
    // technically extending to infinity will be described by the maximum
    // acceptable L-shell value, without causing problems.
    clat = pow(cos(alatr), 2);
    if (clat < 1.0e-5) clat = 1.0e-5;
    al = r / clat;
    aheight = (r - 1.0) * re;

    // if (r > 1.0485) {
    //     std::cout << "altitude high" << std::endl;
    // }

    // The model contains elements for the plasmasphere and plasmapause, the
    // trough, and the polar cap.  The IRI is used for the ionosphere.
    // The polar cap - trough boundary is chosen to be at L=alcrit.  The function
    // subroutine switchon is used to transition between these regions.  This
    // function is also used to transition between the IRI and the other exterior
    // models.
    double edensity = 0.0;
    std::string call_type = "";
    if (al < tranlow) {
      // execute this section if we are equatorward of the polar cap
      edensity = ne_iri_ps_trough(r, al, alatr, amlt, akp, itime);
      call_type = "low";
    } else if (al <= tranhigh) {
      // execute this section if we are in the transition region between the
      // trough and polar cap regions
      double ps_edensity = ne_iri_ps_trough(r, al, alatr, amlt, akp, itime);
      double cap_edensity = ne_iri_cap(r, alatr, amlt, itime);
      double switch_val = switchon(al, alcrit, altrans);
      edensity = ps_edensity * (1.0 - switch_val) + cap_edensity * switch_val;
      call_type = "mid";
    } else {
      // execute this section if we are polarward of the trough
      edensity = ne_iri_cap(r, alatr, amlt, itime);
      call_type = "polar";
    }

    // Convert to particles per cm^3
    double den = edensity / 1.0e6;

    // Compute He+ to H+ density ratio in the plasmasphere
    double aHeH = pow(10.0, (-1.541 - 0.176 * r + 8.557e-3 * f107 - 1.458e-5 * f107 * f107));

    // Helium concentration drops dramatically with transition to high latitudes
    // and open field lines
    aHeH *= (1.0 - switchon(al, (tranlow + tranhigh) / 2.0, altrans));

    // Compute relative O+ density
    double alphaO = 0.995 / pow((1.0 + pow((aheight - 350.0) / 531.25, 2)), 3) + 0.005;

    // Compute relative He+ concentration in the plasmasphere
    double alphaHe = (aHeH != 0.0) ? (1.0 - alphaO) / (1.0 + 1.0 / aHeH) : 0.0;

    // Compute densities of H+, He+, and O+
    double heden = alphaHe * den;        // Helium density [2]
    double oxden = alphaO * den;         // Oxygen density  [3]
    double hyden = den - heden - oxden;  // Hydrogen density [1]

    double call_type_idx = 0;
    if (call_type == "low") {
      call_type_idx = 0;  // Low latitude
    } else if (call_type == "mid") {
      call_type_idx = 1;  // Mid latitude
    } else if (call_type == "polar") {
      call_type_idx = 2;  // Polar latitude
    }

    std::vector<double> outn = {den, hyden, heden, oxden, f107, rz12, hmf2_km, call_type_idx};

    // move back to the original directory
    std::filesystem::current_path(curr_dir);

    return outn;
  }

  std::vector<double> gcpm_v24_fortran(DateTime datetime, double r_RE, double amlt, double alatr,
                                       double akp) {
    // Convert the input arguments to float
    // first move to the data directory
    std::string curr_dir = std::filesystem::current_path();
    std::filesystem::current_path(get_iri_data_dir());

    // get kp index
    if (akp < 0) {
      akp = get_kp_index(datetime);
    }

    //
    int itime[2];
    itime[0] = datetime.year * 1000 + datetime.doy;  // Year and day of the year
    int ihr = datetime.hour, imin = datetime.min, isec = datetime.sec;
    itime[1] = (ihr * 3600 + imin * 60 + isec) * 1000;  // Milliseconds of the day
    float r_f = static_cast<float>(r_RE);               // Geo-centric distance in Earth radii
    float amlt_f = static_cast<float>(amlt);            // Magnetic local time in hours
    float alatr_f = static_cast<float>(alatr);          // Magnetic latitude in radians
    float akp_f = static_cast<float>(akp);              // KP index

    float outn[4];

    // Call the Fortran function
    gcpm_v24_(itime, &r_f, &amlt_f, &alatr_f, &akp_f, outn);

    std::vector<double> outvec;
    for (int i = 0; i < 4; ++i) {
      outvec.push_back(static_cast<double>(outn[i]));
    }

    // move back to the original directory
    std::filesystem::current_path(curr_dir);

    return outvec;
  }

}  // namespace pecsim