IAP GITLAB

Skip to content
Snippets Groups Projects
testEnvironment.cpp 16.35 KiB
/*
 * (c) Copyright 2020 CORSIKA Project, corsika-project@lists.kit.edu
 *
 * This software is distributed under the terms of the GNU General Public
 * Licence version 3 (GPL Version 3). See file LICENSE for a full version of
 * the license.
 */

#include <corsika/framework/core/ParticleProperties.hpp>
#include <corsika/framework/core/PhysicalUnits.hpp>
#include <corsika/framework/geometry/Line.hpp>
#include <corsika/framework/geometry/RootCoordinateSystem.hpp>
#include <corsika/framework/geometry/Vector.hpp>
#include <corsika/media/DensityFunction.hpp>
#include <corsika/media/FlatExponential.hpp>
#include <corsika/media/HomogeneousMedium.hpp>
#include <corsika/media/MediumPropertyModel.hpp>
#include <corsika/media/UniformMagneticField.hpp>
#include <corsika/media/UniformRefractiveIndex.hpp>
#include <corsika/media/IMediumModel.hpp>
#include <corsika/media/IMediumPropertyModel.hpp>
#include <corsika/media/IMagneticFieldModel.hpp>
#include <corsika/media/IRefractiveIndexModel.hpp>
#include <corsika/media/InhomogeneousMedium.hpp>
#include <corsika/media/LayeredSphericalAtmosphereBuilder.hpp>
#include <corsika/media/LinearApproximationIntegrator.hpp>
#include <corsika/media/NuclearComposition.hpp>
#include <corsika/media/SlidingPlanarExponential.hpp>
#include <corsika/media/VolumeTreeNode.hpp>

#include <SetupTestTrajectory.hpp>

#include <catch2/catch.hpp>

using namespace corsika;

CoordinateSystemPtr const& gCS = get_root_CoordinateSystem();

Point const gOrigin(gCS, {0_m, 0_m, 0_m});

TEST_CASE("HomogeneousMedium") {

  logging::set_level(logging::level::info);
  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");

  NuclearComposition const protonComposition(std::vector<Code>{Code::Proton},
                                             std::vector<float>{1.f});
  HomogeneousMedium<IMediumModel> const medium(19.2_g / cube(1_cm), protonComposition);

  CHECK_THROWS(NuclearComposition({Code::Proton}, {1.1}));
  CHECK_THROWS(NuclearComposition({Code::Proton}, {0.99}));
}

TEST_CASE("FlatExponential") {

  logging::set_level(logging::level::info);
  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");

  NuclearComposition const protonComposition(std::vector<Code>{Code::Proton},
                                             std::vector<float>{1.f});

  Vector const axis(gCS, QuantityVector<dimensionless_d>(0, 0, 1));
  LengthType const lambda = 3_m;
  auto const rho0 = 1_g / static_pow<3>(1_cm);
  FlatExponential<IMediumModel> const medium(gOrigin, axis, rho0, lambda,
                                             protonComposition);
  auto const tEnd = 5_s;

  SECTION("horizontal") {
    Line const line(gOrigin, Vector<SpeedType::dimension_type>(
                                 gCS, {20_cm / second, 0_m / second, 0_m / second}));
    setup::Trajectory const trajectory =
        setup::testing::make_track<setup::Trajectory>(line, tEnd);

    CHECK((medium.getIntegratedGrammage(trajectory, 2_m) / (rho0 * 2_m)) == Approx(1));
    CHECK((medium.getArclengthFromGrammage(trajectory, rho0 * 5_m) / 5_m) == Approx(1));
  }

  SECTION("vertical") {
    Line const line(gOrigin, Vector<SpeedType::dimension_type>(
                                 gCS, {0_m / second, 0_m / second, 5_m / second}));
    setup::Trajectory const trajectory =
        setup::testing::make_track<setup::Trajectory>(line, tEnd);
    LengthType const length = 2 * lambda;
    GrammageType const exact = rho0 * lambda * (exp(length / lambda) - 1);

    CHECK((medium.getIntegratedGrammage(trajectory, length) / exact) == Approx(1));
    CHECK((medium.getArclengthFromGrammage(trajectory, exact) / length) == Approx(1));
  }

  SECTION("escape grammage") {
    Line const line(gOrigin, Vector<SpeedType::dimension_type>(
                                 gCS, {0_m / second, 0_m / second, -5_m / second}));
    setup::Trajectory const trajectory =
        setup::testing::make_track<setup::Trajectory>(line, tEnd);
    GrammageType const escapeGrammage = rho0 * lambda;

    CHECK(trajectory.getDirection(0).dot(axis).magnitude() < 0);
    CHECK(medium.getArclengthFromGrammage(trajectory, 1.2 * escapeGrammage) ==
          std::numeric_limits<typename GrammageType::value_type>::infinity() * 1_m);
  }

  SECTION("inclined") {
    Line const line(gOrigin, Vector<SpeedType::dimension_type>(
                                 gCS, {0_m / second, 5_m / second, 5_m / second}));
    setup::Trajectory const trajectory =
        setup::testing::make_track<setup::Trajectory>(line, tEnd);
    double const cosTheta = M_SQRT1_2;
    LengthType const length = 2 * lambda;
    GrammageType const exact =
        rho0 * lambda * (exp(cosTheta * length / lambda) - 1) / cosTheta;
    CHECK((medium.getIntegratedGrammage(trajectory, length) / exact) == Approx(1));
    CHECK((medium.getArclengthFromGrammage(trajectory, exact) / length) == Approx(1));
  }
}

TEST_CASE("SlidingPlanarExponential") {

  logging::set_level(logging::level::info);
  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");

  NuclearComposition const protonComposition(std::vector<Code>{Code::Proton},
                                             std::vector<float>{1.f});

  LengthType const lambda = 3_m;
  auto const rho0 = 1_g / static_pow<3>(1_cm);
  auto const tEnd = 5_s;

  SlidingPlanarExponential<IMediumModel> const medium(gOrigin, rho0, lambda,
                                                      protonComposition);

  SECTION("density") {
    CHECK(medium.getMassDensity({gCS, {0_m, 0_m, 3_m}}) /
              medium.getMassDensity({gCS, {0_m, 3_m, 0_m}}) ==
          Approx(1));
  }

  SECTION("vertical") {
    Vector const axis(gCS, QuantityVector<dimensionless_d>(0, 0, 1));
    FlatExponential<IMediumModel> const flat(gOrigin, axis, rho0, lambda,
                                             protonComposition);
    Line const line({gCS, {0_m, 0_m, 1_m}},
                    Vector<SpeedType::dimension_type>(
                        gCS, {0_m / second, 0_m / second, 5_m / second}));
    setup::Trajectory const trajectory =
        setup::testing::make_track<setup::Trajectory>(line, tEnd);

    CHECK(medium.getMassDensity({gCS, {0_mm, 0_m, 3_m}}).magnitude() ==
          flat.getMassDensity({gCS, {0_mm, 0_m, 3_m}}).magnitude());
    CHECK(medium.getIntegratedGrammage(trajectory, 2_m).magnitude() ==
          flat.getIntegratedGrammage(trajectory, 2_m).magnitude());
    CHECK(medium.getArclengthFromGrammage(trajectory, rho0 * 5_m).magnitude() ==
          flat.getArclengthFromGrammage(trajectory, rho0 * 5_m).magnitude());
  }
}

auto constexpr rho0 = 1_kg / 1_m / 1_m / 1_m;

struct Exponential {
  auto operator()(Point const& p) const {
    return exp(p.getCoordinates()[0] / 1_m) * rho0;
  }

  template <int N>
  auto getDerivative(Point const& p, DirectionVector const& v) const {
    return v.getComponents()[0] * (*this)(p) / static_pow<N>(1_m);
  }

  auto getFirstDerivative(Point const& p, DirectionVector const& v) const {
    return getDerivative<1>(p, v);
  }

  auto getSecondDerivative(Point const& p, DirectionVector const& v) const {
    return getDerivative<2>(p, v);
  }
};

TEST_CASE("InhomogeneousMedium") {

  logging::set_level(logging::level::info);
  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");

  Vector direction(gCS, QuantityVector<dimensionless_d>(1, 0, 0));

  Line line(gOrigin, Vector<SpeedType::dimension_type>(
                         gCS, {20_m / second, 0_m / second, 0_m / second}));

  auto const tEnd = 5_s;
  setup::Trajectory const trajectory =
      setup::testing::make_track<setup::Trajectory>(line, tEnd);

  Exponential const e;
  DensityFunction<decltype(e), LinearApproximationIntegrator> const rho(e);

  SECTION("DensityFunction") {
    CHECK(e.getDerivative<1>(gOrigin, direction) / (1_kg / 1_m / 1_m / 1_m / 1_m) ==
          Approx(1));
    CHECK(rho.evaluateAt(gOrigin) == e(gOrigin));
  }

  auto const exactGrammage = [](auto l) { return 1_m * rho0 * (exp(l / 1_m) - 1); };
  auto const exactLength = [](auto X) { return 1_m * log(1 + X / (rho0 * 1_m)); };

  auto constexpr l = 15_cm;

  NuclearComposition const composition{{Code::Proton}, {1.f}};
  InhomogeneousMedium<IMediumModel, decltype(rho)> const inhMedium(composition, rho);

  SECTION("Integration") {
    CHECK(rho.getIntegrateGrammage(trajectory, l) / exactGrammage(l) ==
          Approx(1).epsilon(1e-2));
    CHECK(rho.getArclengthFromGrammage(trajectory, exactGrammage(l)) /
              exactLength(exactGrammage(l)) ==
          Approx(1).epsilon(1e-2));
    CHECK(rho.getMaximumLength(trajectory, 1e-2) >
          l); // todo: write reasonable test when implementation is working

    CHECK(rho.getIntegrateGrammage(trajectory, l) ==
          inhMedium.getIntegratedGrammage(trajectory, l));
    CHECK(rho.getArclengthFromGrammage(trajectory, 20_g / (1_cm * 1_cm)) ==
          inhMedium.getArclengthFromGrammage(trajectory, 20_g / (1_cm * 1_cm)));
    CHECK(inhMedium.getNuclearComposition() == composition);
    CHECK(inhMedium.getMassDensity({gCS, {0_m, 0_m, 0_m}}) == 1_kg/static_pow<3>(1_m));
  }
}

TEST_CASE("LayeredSphericalAtmosphereBuilder") {

  logging::set_level(logging::level::info);
  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");

  LayeredSphericalAtmosphereBuilder builder =
      make_layered_spherical_atmosphere_builder<>::create(gOrigin,
                                                          constants::EarthRadius::Mean);

  builder.setNuclearComposition({{{Code::Nitrogen, Code::Oxygen}}, {{.6, .4}}});

  builder.addLinearLayer(1_km, 10_km);
  builder.addLinearLayer(2_km, 20_km);
  builder.addExponentialLayer(540.1778_g / (1_cm * 1_cm), 772170.16_cm, 30_km);

  CHECK_THROWS(builder.addLinearLayer(0.5_km, 5_km));
  
  CHECK(builder.getSize() == 3);

  auto const builtEnv = builder.assemble();
  auto const& univ = builtEnv.getUniverse();

  CHECK(builder.getSize() == 0);

  auto const R = builder.getEarthRadius();

  CHECK(univ->getChildNodes().size() == 1);

  CHECK(univ->getContainingNode(Point(gCS, 0_m, 0_m, R + 35_km)) == univ.get());
  CHECK(dynamic_cast<Sphere const&>(
            univ->getContainingNode(Point(gCS, 0_m, 0_m, R + 8_km))->getVolume())
            .getRadius() == R + 10_km);
  CHECK(dynamic_cast<Sphere const&>(
            univ->getContainingNode(Point(gCS, 0_m, 0_m, R + 12_km))->getVolume())
            .getRadius() == R + 20_km);
  CHECK(dynamic_cast<Sphere const&>(
            univ->getContainingNode(Point(gCS, 0_m, 0_m, R + 24_km))->getVolume())
            .getRadius() == R + 30_km);
}

TEST_CASE("LayeredSphericalAtmosphereBuilder w/ magnetic field") {

  logging::set_level(logging::level::info);
  corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v");

  // setup our interface types
  using ModelInterface = IMagneticFieldModel<IMediumModel>;

  // the composition we use for the homogenous medium
  NuclearComposition const protonComposition(std::vector<Code>{Code::Proton},
                                             std::vector<float>{1.f});

  // create magnetic field vectors
  Vector B0(gCS, 0_T, 0_T, 1_T);

  LayeredSphericalAtmosphereBuilder builder = make_layered_spherical_atmosphere_builder<
      ModelInterface, UniformMagneticField>::create(gOrigin, constants::EarthRadius::Mean,
                                                    B0);

  builder.setNuclearComposition({{{Code::Nitrogen, Code::Oxygen}}, {{.6, .4}}});
  builder.addLinearLayer(1_km, 10_km);
  builder.addExponentialLayer(1222.6562_g / (1_cm * 1_cm), 994186.38_cm, 20_km);

  CHECK(builder.getSize() == 2);

  auto const builtEnv = builder.assemble();
  auto const& univ = builtEnv.getUniverse();

  CHECK(builder.getSize() == 0);
  CHECK(univ->getChildNodes().size() == 1);
  auto const R = builder.getEarthRadius();

  // check magnetic field at several locations
  const Point pTest(gCS, -10_m, 4_m, R + 35_m);
  CHECK(B0.getComponents(gCS) == univ->getContainingNode(pTest)
                                     ->getModelProperties()
                                     .getMagneticField(pTest)
                                     .getComponents(gCS));
  const Point pTest2(gCS, 10_m, -4_m, R + 15_km);
  CHECK(B0.getComponents(gCS) == univ->getContainingNode(pTest2)
                                     ->getModelProperties()
                                     .getMagneticField(pTest2)
                                     .getComponents(gCS));
 }

TEST_CASE("media", "LayeredSphericalAtmosphereBuilder USStd") {
  // setup environment, geometry
  Point const center{gCS, 0_m, 0_m, 0_m};

  // setup our interface types
  auto builder = make_layered_spherical_atmosphere_builder<>::create(
      center, constants::EarthRadius::Mean);

  builder.setNuclearComposition(
      {{Code::Nitrogen, Code::Oxygen},
       {0.7847f, 1.f - 0.7847f}}); // values taken from AIRES manual, Ar removed for now

  builder.addExponentialLayer(1222.6562_g / (1_cm * 1_cm), 994186.38_cm, 4_km);
  builder.addExponentialLayer(1144.9069_g / (1_cm * 1_cm), 878153.55_cm, 10_km);
  builder.addExponentialLayer(1305.5948_g / (1_cm * 1_cm), 636143.04_cm, 40_km);
  builder.addExponentialLayer(540.1778_g / (1_cm * 1_cm), 772170.16_cm, 100_km);
  builder.addLinearLayer(1e9_cm, 112.8_km);

  Environment<IMediumModel> env;
  builder.assemble(env);

  typedef typename Environment<IMediumModel>::BaseNodeType::VTN_type node_type;
  node_type const* universe = env.getUniverse().get();

  // far out ther is the universe
  CHECK(universe->getContainingNode(Point(gCS, {10000_km, 0_m, 0_m})) == universe);
  CHECK(universe->getContainingNode(Point(gCS, {0_m, 10000_km, 0_m})) == universe);

  // at 112.8km there is transition to atmosphere
  CHECK(universe->getContainingNode(
            Point(gCS, {constants::EarthRadius::Mean + 112.8_km + 1_cm, 0_m, 0_m})) ==
        universe);
  CHECK(universe->getContainingNode(
            Point(gCS, {0_m, constants::EarthRadius::Mean + 112.8_km + 1_cm, 0_m})) ==
        universe);

  // check layer transition at 112.8km

  node_type const* layer1_not_yet = universe->getContainingNode(
      Point(gCS, {constants::EarthRadius::Mean + 112.8_km + 1_cm, 0_m, 0_m}));
  node_type const* layer1 = universe->getContainingNode(
      Point(gCS, {constants::EarthRadius::Mean + 112.8_km - 1_cm, 0_m, 0_m}));
  node_type const* layer1_also = universe->getContainingNode(
      Point(gCS, {0_m, constants::EarthRadius::Mean + 112.8_km - 1_cm, 0_m}));

  CHECK(layer1_not_yet == universe);
  CHECK(layer1 != universe);
  CHECK(layer1 == layer1_also);

  // check layer transition at 100km

  node_type const* layer2_not_yet = universe->getContainingNode(
      Point(gCS, {constants::EarthRadius::Mean + 100_km + 1_cm, 0_m, 0_m}));
  node_type const* layer2 = universe->getContainingNode(
      Point(gCS, {constants::EarthRadius::Mean + 100_km - 1_cm, 0_m, 0_m}));
  node_type const* layer2_also = universe->getContainingNode(
      Point(gCS, {0_m, constants::EarthRadius::Mean + 100_km - 1_cm, 0_m}));

  CHECK(layer2_not_yet == layer1);
  CHECK(layer2 != layer1);
  CHECK(layer2 == layer2_also);

  // check layer transition at 40km

  node_type const* layer3_not_yet = universe->getContainingNode(
      Point(gCS, {constants::EarthRadius::Mean + 40_km + 1_cm, 0_m, 0_m}));
  node_type const* layer3 = universe->getContainingNode(
      Point(gCS, {constants::EarthRadius::Mean + 40_km - 1_cm, 0_m, 0_m}));
  node_type const* layer3_also = universe->getContainingNode(
      Point(gCS, {0_m, constants::EarthRadius::Mean + 40_km - 1_cm, 0_m}));

  CHECK(layer3_not_yet == layer2);
  CHECK(layer3 != layer2);
  CHECK(layer3 == layer3_also);

  // check layer transition at 10km

  node_type const* layer4_not_yet = universe->getContainingNode(
      Point(gCS, {constants::EarthRadius::Mean + 10_km + 1_cm, 0_m, 0_m}));
  node_type const* layer4 = universe->getContainingNode(
      Point(gCS, {constants::EarthRadius::Mean + 10_km - 1_cm, 0_m, 0_m}));
  node_type const* layer4_also = universe->getContainingNode(
      Point(gCS, {0_m, constants::EarthRadius::Mean + 10_km - 1_cm, 0_m}));

  CHECK(layer4_not_yet == layer3);
  CHECK(layer4 != layer3);
  CHECK(layer4 == layer4_also);

  // check layer transition at 4km

  node_type const* layer5_not_yet = universe->getContainingNode(
      Point(gCS, {constants::EarthRadius::Mean + 4_km + 1_cm, 0_m, 0_m}));
  node_type const* layer5 = universe->getContainingNode(
      Point(gCS, {constants::EarthRadius::Mean + 4_km - 1_cm, 0_m, 0_m}));
  node_type const* layer5_also = universe->getContainingNode(
      Point(gCS, {0_m, constants::EarthRadius::Mean + 4_km - 1_cm, 0_m}));

  CHECK(layer5_not_yet == layer4);
  CHECK(layer5 != layer4);
  CHECK(layer5 == layer5_also);
}