diff --git a/Environment/CMakeLists.txt b/Environment/CMakeLists.txt
index ef1d24035859fc6668d902ae89ee7aebc994a95b..47b973cb138cbc7a52ba11e36b8347ec8497e883 100644
--- a/Environment/CMakeLists.txt
+++ b/Environment/CMakeLists.txt
@@ -9,6 +9,7 @@ set (
   LinearApproximationIntegrator.h
   DensityFunction.h
   Environment.h
+  FlatExponential.h
   )
 
 set (
diff --git a/Environment/FlatExponential.h b/Environment/FlatExponential.h
new file mode 100644
index 0000000000000000000000000000000000000000..7079a22bfa39f4b061e1bfe111553c34e595d8c7
--- /dev/null
+++ b/Environment/FlatExponential.h
@@ -0,0 +1,85 @@
+
+/*
+ * (c) Copyright 2018 CORSIKA Project, corsika-project@lists.kit.edu
+ *
+ * See file AUTHORS for a list of contributors.
+ *
+ * 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.
+ */
+
+#ifndef _include_Environment_FlatExponential_h_
+#define _include_Environment_FlatExponential_h_
+
+#include <corsika/environment/NuclearComposition.h>
+#include <corsika/geometry/Line.h>
+#include <corsika/geometry/Point.h>
+#include <corsika/geometry/Trajectory.h>
+#include <corsika/particles/ParticleProperties.h>
+#include <corsika/random/RNGManager.h>
+#include <corsika/units/PhysicalUnits.h>
+
+#include <cassert>
+
+/**
+ *
+ */
+
+namespace corsika::environment {
+
+  template <class T>
+  class FlatExponential : public T {
+    corsika::units::si::MassDensityType const fRho0;
+    units::si::LengthType const fLambda;
+    units::si::InverseLengthType const fInvLambda;
+    NuclearComposition const fNuclComp;
+    geometry::Vector<units::si::dimensionless_d> const fAxis;
+    geometry::Point const fP0;
+
+  public:
+    FlatExponential(geometry::Point const& p0,
+                    geometry::Vector<units::si::dimensionless_d> const& axis,
+                    units::si::MassDensityType rho, units::si::LengthType lambda,
+                    NuclearComposition pNuclComp)
+        : fRho0(rho)
+        , fLambda(lambda)
+        , fInvLambda(1 / lambda)
+        , fNuclComp(pNuclComp)
+        , fAxis(axis)
+        , fP0(p0) {}
+
+    corsika::units::si::MassDensityType GetMassDensity(
+        corsika::geometry::Point const& p) const override {
+      return fRho0 * exp(fInvLambda * (p - fP0).dot(fAxis));
+    }
+    NuclearComposition const& GetNuclearComposition() const override { return fNuclComp; }
+
+    corsika::units::si::GrammageType IntegratedGrammage(
+        corsika::geometry::Trajectory<corsika::geometry::Line> const& line,
+        corsika::units::si::LengthType pTo) const override {
+      auto const vDotA = line.NormalizedDirection().dot(fAxis).magnitude();
+
+      if (vDotA == 0) {
+        return pTo * GetMassDensity(line.GetR0());
+      } else {
+        return GetMassDensity(line.GetR0()) * (fLambda / vDotA) *
+               (exp(vDotA * pTo / fLambda) - 1);
+      }
+    }
+
+    corsika::units::si::LengthType ArclengthFromGrammage(
+        corsika::geometry::Trajectory<corsika::geometry::Line> const& line,
+        corsika::units::si::GrammageType pGrammage) const override {
+      auto const vDotA = line.NormalizedDirection().dot(fAxis).magnitude();
+
+      if (vDotA == 0) {
+        return pGrammage / GetMassDensity(line.GetR0());
+      } else {
+        return fLambda / vDotA * log(pGrammage * vDotA / (fRho0 * fLambda) + 1);
+      }
+    }
+  };
+
+} // namespace corsika::environment
+#endif
diff --git a/Environment/testEnvironment.cc b/Environment/testEnvironment.cc
index 5bb18bfe09e48d4865f5aafcd8ad4aa01f4afeb8..5beab6587a20199a24bb8946b96ccedfe4b8d293 100644
--- a/Environment/testEnvironment.cc
+++ b/Environment/testEnvironment.cc
@@ -13,6 +13,7 @@
                           // cpp file
 
 #include <corsika/environment/DensityFunction.h>
+#include <corsika/environment/FlatExponential.h>
 #include <corsika/environment/HomogeneousMedium.h>
 #include <corsika/environment/IMediumModel.h>
 #include <corsika/environment/InhomogeneousMedium.h>
@@ -30,6 +31,12 @@ using namespace corsika::geometry;
 using namespace corsika::environment;
 using namespace corsika::particles;
 using namespace corsika::units::si;
+using namespace corsika;
+
+CoordinateSystem const& gCS =
+    RootCoordinateSystem::GetInstance().GetRootCoordinateSystem();
+
+Point const gOrigin(gCS, {0_m, 0_m, 0_m});
 
 TEST_CASE("HomogeneousMedium") {
   NuclearComposition const protonComposition(std::vector<Code>{Code::Proton},
@@ -37,6 +44,51 @@ TEST_CASE("HomogeneousMedium") {
   HomogeneousMedium<IMediumModel> const medium(19.2_g / cube(1_cm), protonComposition);
 }
 
+TEST_CASE("FlatExponential") {
+  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 / units::si::detail::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}));
+    Trajectory<Line> const trajectory(line, tEnd);
+
+    REQUIRE((medium.IntegratedGrammage(trajectory, 2_m) / (rho0 * 2_m)) == Approx(1));
+    REQUIRE((medium.ArclengthFromGrammage(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}));
+    Trajectory<Line> const trajectory(line, tEnd);
+    LengthType const length = 2 * lambda;
+    GrammageType const exact = rho0 * lambda * (exp(length / lambda) - 1);
+
+    REQUIRE((medium.IntegratedGrammage(trajectory, length) / exact) == Approx(1));
+    REQUIRE((medium.ArclengthFromGrammage(trajectory, exact) / length) == Approx(1));
+  }
+
+  SECTION("vertical") {
+    Line const line(gOrigin, Vector<SpeedType::dimension_type>(
+                                 gCS, {0_m / second, 5_m / second, 5_m / second}));
+    Trajectory<Line> const 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;
+
+    REQUIRE((medium.IntegratedGrammage(trajectory, length) / exact) == Approx(1));
+    REQUIRE((medium.ArclengthFromGrammage(trajectory, exact) / length) == Approx(1));
+  }
+}
+
 auto constexpr rho0 = 1_kg / 1_m / 1_m / 1_m;
 
 struct Exponential {
@@ -60,15 +112,10 @@ struct Exponential {
 };
 
 TEST_CASE("InhomogeneousMedium") {
-  CoordinateSystem const& cs =
-      RootCoordinateSystem::GetInstance().GetRootCoordinateSystem();
-
-  Point const origin(cs, {0_m, 0_m, 0_m});
-
-  Vector direction(cs, QuantityVector<dimensionless_d>(1, 0, 0));
+  Vector direction(gCS, QuantityVector<dimensionless_d>(1, 0, 0));
 
-  Line line(origin, Vector<SpeedType::dimension_type>(
-                        cs, {20_m / second, 0_m / second, 0_m / second}));
+  Line line(gOrigin, Vector<SpeedType::dimension_type>(
+                         gCS, {20_m / second, 0_m / second, 0_m / second}));
 
   auto const tEnd = 5_s;
   Trajectory<Line> const trajectory(line, tEnd);
@@ -77,9 +124,9 @@ TEST_CASE("InhomogeneousMedium") {
   DensityFunction<decltype(e), LinearApproximationIntegrator> const rho(e);
 
   SECTION("DensityFunction") {
-    REQUIRE(e.Derivative<1>(origin, direction) / (1_kg / 1_m / 1_m / 1_m / 1_m) ==
+    REQUIRE(e.Derivative<1>(gOrigin, direction) / (1_kg / 1_m / 1_m / 1_m / 1_m) ==
             Approx(1));
-    REQUIRE(rho.EvaluateAt(origin) == e(origin));
+    REQUIRE(rho.EvaluateAt(gOrigin) == e(gOrigin));
   }
 
   auto const exactGrammage = [](auto l) { return 1_m * rho0 * (exp(l / 1_m) - 1); };
diff --git a/Framework/Geometry/Vector.h b/Framework/Geometry/Vector.h
index 737b7830821df05cdf0fa5fd93918741f0cf731e..ca97abb50feec457527751d79cd9e76b760cb4f2 100644
--- a/Framework/Geometry/Vector.h
+++ b/Framework/Geometry/Vector.h
@@ -125,17 +125,9 @@ namespace corsika::geometry {
 
     template <typename ScalarDim>
     auto operator*(phys::units::quantity<ScalarDim, double> const p) const {
-      using ProdQuantity = phys::units::detail::Product<dim, ScalarDim, double, double>;
+      using ProdDim = phys::units::detail::product_d<dim, ScalarDim>;
 
-      if constexpr (std::is_same<ProdQuantity, double>::value) // result dimensionless,
-                                                               // not a "Quantity" anymore
-      {
-        return Vector<phys::units::dimensionless_d>(*BaseVector<dim>::cs,
-                                                    BaseVector<dim>::qVector * p);
-      } else {
-        return Vector<typename ProdQuantity::dimension_type>(
-            *BaseVector<dim>::cs, BaseVector<dim>::qVector * p);
-      }
+      return Vector<ProdDim>(*BaseVector<dim>::cs, BaseVector<dim>::qVector * p);
     }
 
     template <typename ScalarDim>
@@ -173,16 +165,8 @@ namespace corsika::geometry {
       auto const c2 = pV.GetComponents(*BaseVector<dim>::cs).eVector;
       auto const bareResult = c1.cross(c2);
 
-      using ProdQuantity = phys::units::detail::Product<dim, dim2, double, double>;
-
-      if constexpr (std::is_same<ProdQuantity, double>::value) // result dimensionless,
-                                                               // not a "Quantity" anymore
-      {
-        return Vector<phys::units::dimensionless_d>(*BaseVector<dim>::cs, bareResult);
-      } else {
-        return Vector<typename ProdQuantity::dimension_type>(*BaseVector<dim>::cs,
-                                                             bareResult);
-      }
+      using ProdDim = phys::units::detail::product_d<dim, dim2>;
+      return Vector<ProdDim>(*BaseVector<dim>::cs, bareResult);
     }
 
     template <typename dim2>
@@ -191,9 +175,10 @@ namespace corsika::geometry {
       auto const c2 = pV.GetComponents(*BaseVector<dim>::cs).eVector;
       auto const bareResult = c1.dot(c2);
 
-      using ProdQuantity = phys::units::detail::Product<dim, dim2, double, double>;
+      using ProdDim = phys::units::detail::product_d<dim, dim2>;
 
-      return ProdQuantity(phys::units::detail::magnitude_tag, bareResult);
+      return phys::units::quantity<ProdDim, double>(phys::units::detail::magnitude_tag,
+                                                    bareResult);
     }
   };
 
diff --git a/ThirdParty/phys/units/quantity.hpp b/ThirdParty/phys/units/quantity.hpp
index bec55318685ffb0e69bfeeb19260ef20e6d1eed1..06fbb4b2c760dfc8641d58392aa30062ff7c4e84 100644
--- a/ThirdParty/phys/units/quantity.hpp
+++ b/ThirdParty/phys/units/quantity.hpp
@@ -144,9 +144,18 @@ namespace phys {
           d8 = DX::dim8 + DY::dim8
         };
 
-        typedef Collapse<dimensions<d1, d2, d3, d4, d5, d6, d7, d8>, T> type;
+        typedef dimensions<d1, d2, d3, d4, d5, d6, d7, d8> dim;
+
+        typedef Collapse<dim, T> type;
       };
 
+      /**
+       * convenience type for product dimension
+       */
+
+      template <typename DX, typename DY>
+      using product_d = typename product<DX, DY, Rep>::dim;
+
       template <typename DX, typename DY, typename X, typename Y>
       using Product = typename product<DX, DY, PromoteMul<X, Y>>::type;
 
@@ -166,9 +175,18 @@ namespace phys {
           d8 = DX::dim8 - DY::dim8
         };
 
-        typedef Collapse<dimensions<d1, d2, d3, d4, d5, d6, d7, d8>, T> type;
+        typedef dimensions<d1, d2, d3, d4, d5, d6, d7, d8> dim;
+
+        typedef Collapse<dim, T> type;
       };
 
+      /**
+       * convenience type for quotient dimension
+       */
+
+      template <typename DX, typename DY>
+      using quotient_d = typename quotient<DX, DY, Rep>::dim;
+
       template <typename DX, typename DY, typename X, typename Y>
       using Quotient = typename quotient<DX, DY, PromoteMul<X, Y>>::type;
 
@@ -188,9 +206,18 @@ namespace phys {
           d8 = -D::dim8
         };
 
-        typedef Collapse<dimensions<d1, d2, d3, d4, d5, d6, d7, d8>, T> type;
+        typedef dimensions<d1, d2, d3, d4, d5, d6, d7, d8> dim;
+
+        typedef Collapse<dim, T> type;
       };
 
+      /**
+       * convenience type for reciprocal dimension
+       */
+
+      template <typename DX, typename DY>
+      using reciprocal_d = typename reciprocal<DX, Rep>::dim;
+
       template <typename D, typename X, typename Y>
       using Reciprocal = typename reciprocal<D, PromoteMul<X, Y>>::type;
 
@@ -210,11 +237,20 @@ namespace phys {
           d8 = N * D::dim8
         };
 
-        typedef Collapse<dimensions<d1, d2, d3, d4, d5, d6, d7, d8>, T> type;
+        typedef dimensions<d1, d2, d3, d4, d5, d6, d7, d8> dim;
+
+        typedef Collapse<dim, T> type;
       };
 
+      /**
+       * convenience type for power dimension
+       */
+
+      template <typename DX, int N>
+      using power_d = typename power<DX, N, Rep>::dim;
+
       template <typename D, int N, typename T>
-      using Power = typename detail::power<D, N, T>::type;
+      using Power = typename power<D, N, T>::type;
 
       /**
        * root type generator.
@@ -238,9 +274,17 @@ namespace phys {
           d8 = D::dim8 / N
         };
 
-        typedef Collapse<dimensions<d1, d2, d3, d4, d5, d6, d7, d8>, T> type;
+        typedef dimensions<d1, d2, d3, d4, d5, d6, d7, d8> dim;
+
+        typedef Collapse<dim, T> type;
       };
 
+      /**
+       * convenience type for root dimension
+       */
+      template <typename D, int N>
+      using root_d = typename root<D, N, Rep>::dim;
+
       template <typename D, int N, typename T>
       using Root = typename detail::root<D, N, T>::type;