diff --git a/src/analysis/Polynomial1D.hpp b/src/analysis/Polynomial1D.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..20d4cf90ac11c8b0ae6c264b9f3ccf632d0c493e
--- /dev/null
+++ b/src/analysis/Polynomial1D.hpp
@@ -0,0 +1,280 @@
+#ifndef POLYNOMIAL_1D_HPP
+#define POLYNOMIAL_1D_HPP
+
+#include <utils/Array.hpp>
+#include <utils/Exceptions.hpp>
+
+class [[nodiscard]] Polynomial1D
+{
+ private:
+  Array<double> m_coefficients;
+
+  PUGS_INLINE
+  size_t _getRealDegree() const
+  {
+    size_t real_degree = this->degree();
+    while (real_degree > 0 and std::abs(m_coefficients[real_degree]) < 1E-14) {
+      --real_degree;
+    }
+    return real_degree;
+  }
+
+  PUGS_INLINE
+  friend Polynomial1D _simplify(Polynomial1D && p)
+  {
+    size_t real_degree = p._getRealDegree();
+
+    if (real_degree != p.degree()) {
+      Polynomial1D q(real_degree);
+      for (size_t i = 0; i <= real_degree; ++i) {
+        q.coefficient(i) = p.coefficient(i);
+      }
+      return q;
+    } else {
+      return std::move(p);
+    }
+  }
+
+ public:
+  friend std::ostream& operator<<(std::ostream& os, const Polynomial1D& p)
+  {
+    bool has_written = false;
+    for (size_t i = 0; i < p.m_coefficients.size(); ++i) {
+      const double& coef = p.m_coefficients[i];
+      if (coef != 0) {
+        if (coef > 0) {
+          if (has_written) {
+            os << " + ";
+          }
+          os << coef;
+        } else {
+          if (has_written) {
+            os << " - " << -coef;
+          } else {
+            os << coef;
+          }
+        }
+        if (i > 0) {
+          os << "*x";
+          if (i > 1) {
+            os << '^' << i;
+          }
+        }
+        has_written = true;
+      }
+    }
+    if (not has_written) {
+      os << 0;
+    }
+    return os;
+  }
+
+  PUGS_INLINE
+  Polynomial1D operator-() const
+  {
+    Polynomial1D opposite(this->degree());
+    for (size_t i = 0; i < m_coefficients.size(); ++i) {
+      opposite.m_coefficients[i] = -m_coefficients[i];
+    }
+    return _simplify(std::move(opposite));
+  }
+
+  PUGS_INLINE
+  friend Polynomial1D operator*(const double a, const Polynomial1D& p)
+  {
+    Polynomial1D product(p.degree());
+    for (size_t i = 0; i < p.m_coefficients.size(); ++i) {
+      product.m_coefficients[i] = a * p.m_coefficients[i];
+    }
+    return _simplify(std::move(product));
+  }
+
+  PUGS_INLINE
+  Polynomial1D operator*(const Polynomial1D& p) const
+  {
+    Polynomial1D product(this->degree() + p.degree());
+    product.m_coefficients.fill(0);
+    for (size_t i = 0; i < this->m_coefficients.size(); ++i) {
+      for (size_t j = 0; j < p.m_coefficients.size(); ++j) {
+        product.m_coefficients[i + j] += this->m_coefficients[i] * p.m_coefficients[j];
+      }
+    }
+    return _simplify(std::move(product));
+  }
+
+  PUGS_INLINE
+  Polynomial1D operator+(const Polynomial1D& p) const
+  {
+    auto sum_left_is_bigger = [](const Polynomial1D& greater, const Polynomial1D& smaller) {
+      Polynomial1D result(greater.degree());
+      copy_to(greater.m_coefficients, result.m_coefficients);
+      for (size_t i = 0; i < smaller.m_coefficients.size(); ++i) {
+        result.m_coefficients[i] += smaller.m_coefficients[i];
+      }
+      return _simplify(std::move(result));
+    };
+
+    if (m_coefficients.size() >= p.m_coefficients.size()) {
+      return sum_left_is_bigger(*this, p);
+    } else {
+      return sum_left_is_bigger(p, *this);
+    }
+  }
+
+  PUGS_INLINE
+  Polynomial1D operator-(const Polynomial1D& p) const
+  {
+    if (m_coefficients.size() >= p.m_coefficients.size()) {
+      Polynomial1D result(this->degree());
+      for (size_t i = 0; i < p.m_coefficients.size(); ++i) {
+        result.m_coefficients[i] = m_coefficients[i] - p.m_coefficients[i];
+      }
+      for (size_t i = p.m_coefficients.size(); i < m_coefficients.size(); ++i) {
+        result.m_coefficients[i] = m_coefficients[i];
+      }
+      return _simplify(std::move(result));
+    } else {
+      Polynomial1D result(p.degree());
+      for (size_t i = 0; i < m_coefficients.size(); ++i) {
+        result.m_coefficients[i] = m_coefficients[i] - p.m_coefficients[i];
+      }
+      for (size_t i = m_coefficients.size(); i < p.m_coefficients.size(); ++i) {
+        result.m_coefficients[i] = -p.m_coefficients[i];
+      }
+      return _simplify(std::move(result));
+    }
+  }
+
+  PUGS_INLINE
+  Polynomial1D operator%(const Polynomial1D& q) const
+  {
+    const Polynomial1D& p = *this;
+    Polynomial1D ratio(this->degree());
+    ratio.m_coefficients.fill(0);
+    Polynomial1D remaining(this->degree());
+    copy_to(m_coefficients, remaining.m_coefficients);
+
+    const size_t p_degree = p.degree();
+    const size_t q_degree = q._getRealDegree();
+
+    for (ssize_t i = p_degree - q_degree; i >= 0; --i) {
+      ratio.m_coefficients[i] = remaining.m_coefficients[q_degree + i] / q.m_coefficients[q_degree];
+      for (ssize_t j = q_degree + i; j >= i; --j) {
+        remaining.m_coefficients[j] -= ratio.m_coefficients[i] * q.m_coefficients[j - i];
+      }
+    }
+
+    for (size_t j = q_degree; j <= remaining.degree(); ++j) {
+      remaining.m_coefficients[j] = 0;
+    }
+
+    return _simplify(std::move(remaining));
+  }
+
+  PUGS_INLINE
+  Polynomial1D operator/(const Polynomial1D& q) const
+  {
+    const Polynomial1D& p = *this;
+    Polynomial1D ratio(this->degree());
+    ratio.m_coefficients.fill(0);
+    Polynomial1D remaining(this->degree());
+    copy_to(m_coefficients, remaining.m_coefficients);
+
+    const size_t p_degree = p.degree();
+    const size_t q_degree = q._getRealDegree();
+
+    for (ssize_t i = p_degree - q_degree; i >= 0; --i) {
+      ratio.m_coefficients[i] = remaining.m_coefficients[q_degree + i] / q.m_coefficients[q_degree];
+      for (ssize_t j = q_degree + i; j >= i; --j) {
+        remaining.m_coefficients[j] -= ratio.m_coefficients[i] * q.m_coefficients[j - i];
+      }
+    }
+
+    return _simplify(std::move(ratio));
+  }
+
+  PUGS_INLINE size_t degree() const
+  {
+    Assert(m_coefficients.size() > 0);
+    return m_coefficients.size() - 1;
+  }
+
+  PUGS_INLINE
+  double& coefficient(const size_t i)
+  {
+    Assert(i < m_coefficients.size());
+    return m_coefficients[i];
+  }
+
+  PUGS_INLINE
+  const double& coefficient(const size_t i) const
+  {
+    Assert(i < m_coefficients.size());
+    return m_coefficients[i];
+  }
+
+  PUGS_INLINE
+  friend Polynomial1D derive(const Polynomial1D& p)
+  {
+    if (p.degree() > 0) {
+      Polynomial1D derivative{p.degree() - 1};
+      for (size_t i = 0; i <= derivative.degree(); ++i) {
+        derivative.m_coefficients[i] = (i + 1) * p.m_coefficients[i + 1];
+      }
+      return derivative;
+    } else {
+      return Polynomial1D(std::vector<double>{0});
+    }
+  }
+
+  PUGS_INLINE
+  friend Polynomial1D primitive(const Polynomial1D& p)
+  {
+    if (p.degree() > 0) {
+      Polynomial1D primitive{p.degree() + 1};
+      primitive.m_coefficients[0] = 0;
+      for (size_t i = 0; i <= p.degree(); ++i) {
+        primitive.m_coefficients[i + 1] = p.m_coefficients[i] / (i + 1);
+      }
+      return _simplify(std::move(primitive));
+    } else {
+      return Polynomial1D(std::vector<double>{0});
+    }
+  }
+
+  PUGS_INLINE
+  double operator()(const double x) const
+  {
+    double value = m_coefficients[this->degree()];
+    for (ssize_t i = this->degree() - 1; i >= 0; --i) {
+      value *= x;
+      value += m_coefficients[i];
+    }
+    return value;
+  }
+
+  Polynomial1D& operator=(Polynomial1D&&) = default;
+  Polynomial1D& operator=(const Polynomial1D&) = default;
+
+  PUGS_INLINE
+  explicit Polynomial1D(const std::vector<double>& coeffients) : m_coefficients(coeffients.size())
+  {
+    Assert(coeffients.size() > 0, "empty list is not allowed");
+    for (size_t i = 0; i < coeffients.size(); ++i) {
+      m_coefficients[i] = coeffients[i];
+    }
+  }
+
+  PUGS_INLINE
+  explicit Polynomial1D(const size_t degree) : m_coefficients(degree + 1)
+  {
+    m_coefficients.fill(0);
+  }
+
+  Polynomial1D(const Polynomial1D&) = default;
+  Polynomial1D(Polynomial1D &&)     = default;
+
+  ~Polynomial1D() = default;
+};
+
+#endif   // POLYNOMIAL_1D_HPP
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 4bb227aac16a433471caa45bdfb8b8bc48f4f2bb..827da322463b0b94ef9ab0ef1aa6714211c53c27 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -83,6 +83,7 @@ add_executable (unit_tests
   test_NameProcessor.cpp
   test_OStreamProcessor.cpp
   test_ParseError.cpp
+  test_Polynomial1D.cpp
   test_PugsAssert.cpp
   test_PugsFunctionAdapter.cpp
   test_PugsUtils.cpp
diff --git a/tests/test_Polynomial1D.cpp b/tests/test_Polynomial1D.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..110cfadf74bad94ce8a1e8fc3ff6dff8247cad3f
--- /dev/null
+++ b/tests/test_Polynomial1D.cpp
@@ -0,0 +1,259 @@
+#include <catch2/catch_approx.hpp>
+#include <catch2/catch_test_macros.hpp>
+
+#include <analysis/Polynomial1D.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("Polynomial1D", "[analysis]")
+{
+  SECTION("Constructor")
+  {
+    Polynomial1D p({-1, 0, 0, 1});
+
+    REQUIRE(p.coefficient(0) == -1);
+    REQUIRE(p.coefficient(1) == 0);
+    REQUIRE(p.coefficient(2) == 0);
+    REQUIRE(p.coefficient(3) == 1);
+
+    REQUIRE(p.degree() == 3);
+  }
+
+  SECTION("value")
+  {
+    Polynomial1D p({-1, -1.3, 2.7, 1});
+
+    REQUIRE(p.degree() == 3);
+
+    double x = 1.3;
+    REQUIRE(p(x) == Catch::Approx(-1 - 1.3 * x + 2.7 * x * x + x * x * x));
+
+    x = 0;
+    REQUIRE(p(x) == Catch::Approx(-1 - 1.3 * x + 2.7 * x * x + x * x * x));
+
+    x = 7;
+    REQUIRE(p(x) == Catch::Approx(-1 - 1.3 * x + 2.7 * x * x + x * x * x));
+
+    x = -3;
+    REQUIRE(p(x) == Catch::Approx(-1 - 1.3 * x + 2.7 * x * x + x * x * x));
+  }
+
+  SECTION("opposite")
+  {
+    Polynomial1D p({-1, -1.3, 2.7, 1});
+    Polynomial1D r = -p;
+
+    REQUIRE(r.degree() == 3);
+
+    REQUIRE(r.coefficient(0) == 1);
+    REQUIRE(r.coefficient(1) == 1.3);
+    REQUIRE(r.coefficient(2) == -2.7);
+    REQUIRE(r.coefficient(3) == -1);
+  }
+
+  SECTION("sum")
+  {
+    Polynomial1D p({-1, 0, 0, 1});
+    Polynomial1D q({-1, 2, 1.4, 2, -1});
+
+    Polynomial1D r = p + q;
+    REQUIRE(r.degree() == 4);
+
+    REQUIRE(r.coefficient(0) == -2);
+    REQUIRE(r.coefficient(1) == 2);
+    REQUIRE(r.coefficient(2) == 1.4);
+    REQUIRE(r.coefficient(3) == 3);
+    REQUIRE(r.coefficient(4) == -1);
+  }
+
+  SECTION("difference")
+  {
+    Polynomial1D p({-1, 0, 0, 1});
+    Polynomial1D q({-1, 2, 1.4, 2, -1});
+    {
+      Polynomial1D r = p - q;
+      REQUIRE(r.degree() == 4);
+
+      REQUIRE(r.coefficient(0) == 0);
+      REQUIRE(r.coefficient(1) == -2);
+      REQUIRE(r.coefficient(2) == -1.4);
+      REQUIRE(r.coefficient(3) == -1);
+      REQUIRE(r.coefficient(4) == 1);
+    }
+
+    {
+      Polynomial1D s = q - p;
+      REQUIRE(s.degree() == 4);
+
+      REQUIRE(s.coefficient(0) == 0);
+      REQUIRE(s.coefficient(1) == 2);
+      REQUIRE(s.coefficient(2) == 1.4);
+      REQUIRE(s.coefficient(3) == 1);
+      REQUIRE(s.coefficient(4) == -1);
+    }
+
+    {
+      Polynomial1D s = q - q;
+      REQUIRE(s.degree() == 0);
+
+      REQUIRE(s.coefficient(0) == 0);
+    }
+  }
+
+  SECTION("product")
+  {
+    Polynomial1D p({-1, 1, 0, 2});
+    Polynomial1D q({-1, 2, 1.4, 2, -1});
+
+    {
+      Polynomial1D s = 2 * p;
+      REQUIRE(s.degree() == 3);
+
+      REQUIRE(s.coefficient(0) == -2);
+      REQUIRE(s.coefficient(1) == 2);
+      REQUIRE(s.coefficient(2) == 0);
+      REQUIRE(s.coefficient(3) == 4);
+    }
+
+    {
+      Polynomial1D r = p * q;
+      REQUIRE(r.degree() == 7);
+
+      REQUIRE(r.coefficient(0) == Catch::Approx(1));
+      REQUIRE(r.coefficient(1) == Catch::Approx(-3));
+      REQUIRE(r.coefficient(2) == Catch::Approx(0.6));
+      REQUIRE(r.coefficient(3) == Catch::Approx(-2.6));
+      REQUIRE(r.coefficient(4) == Catch::Approx(7));
+      REQUIRE(r.coefficient(5) == Catch::Approx(1.8));
+      REQUIRE(r.coefficient(6) == Catch::Approx(4));
+      REQUIRE(r.coefficient(7) == Catch::Approx(-2));
+    }
+  }
+
+  SECTION("divide")
+  {
+    SECTION("exact")
+    {
+      Polynomial1D p({-1, 1, 0, 2});
+      Polynomial1D q({1, -3, 0.6, -2.6, 7, 1.8, 4, -2});
+
+      {
+        Polynomial1D s = q / p;
+
+        REQUIRE(s.degree() == 4);
+
+        REQUIRE(s.coefficient(0) == -1);
+        REQUIRE(s.coefficient(1) == 2);
+        REQUIRE(s.coefficient(2) == 1.4);
+        REQUIRE(s.coefficient(3) == 2);
+        REQUIRE(s.coefficient(4) == -1);
+      }
+    }
+
+    SECTION("with remainder")
+    {
+      Polynomial1D p({-1, 0, 1});
+      Polynomial1D q({0, 1});
+
+      {
+        Polynomial1D s = p / q;
+
+        REQUIRE(s.degree() == 1);
+
+        REQUIRE(s.coefficient(0) == 0);
+        REQUIRE(s.coefficient(1) == 1);
+      }
+    }
+  }
+
+  SECTION("mod")
+  {
+    SECTION("exact")
+    {
+      Polynomial1D p({-1, 1, 0, 2});
+      Polynomial1D q({1, -3, 0.6, -2.6, 7, 1.8, 4, -2});
+
+      {
+        Polynomial1D s = q % p;
+
+        REQUIRE(s.degree() == 0);
+        REQUIRE(s.coefficient(0) == 0);
+      }
+    }
+
+    SECTION("with remainder")
+    {
+      Polynomial1D p({-1, 0, 1});
+      Polynomial1D q({0, 1});
+
+      {
+        Polynomial1D s = p % q;
+
+        REQUIRE(s.degree() == 0);
+        REQUIRE(s.coefficient(0) == -1);
+      }
+    }
+  }
+
+  SECTION("derive")
+  {
+    SECTION("constant")
+    {
+      Polynomial1D p(std::vector<double>{-1});
+      Polynomial1D p_prime = derive(p);
+
+      REQUIRE(p_prime.degree() == 0);
+      REQUIRE(p_prime.coefficient(0) == 0);
+    }
+
+    SECTION("non constant")
+    {
+      Polynomial1D p({-1, 1, 0, 2});
+      Polynomial1D p_prime = derive(p);
+
+      REQUIRE(p_prime.degree() == 2);
+      REQUIRE(p_prime.coefficient(0) == 1);
+      REQUIRE(p_prime.coefficient(1) == 0);
+      REQUIRE(p_prime.coefficient(2) == 6);
+    }
+  }
+
+  SECTION("primitive")
+  {
+    SECTION("zero")
+    {
+      Polynomial1D p(std::vector<double>{0});
+      Polynomial1D P = primitive(p);
+
+      REQUIRE(P.degree() == 0);
+      REQUIRE(P.coefficient(0) == 0);
+    }
+
+    SECTION("non zero")
+    {
+      Polynomial1D p({-1, 1, 0, 2});
+      Polynomial1D P = primitive(p);
+
+      REQUIRE(P.degree() == 4);
+      REQUIRE(P.coefficient(0) == 0);
+      REQUIRE(P.coefficient(1) == -1);
+      REQUIRE(P.coefficient(2) == 0.5);
+      REQUIRE(P.coefficient(3) == 0);
+      REQUIRE(P.coefficient(4) == 0.5);
+    }
+  }
+
+  SECTION("Output")
+  {
+    Polynomial1D p({-1, 0, 0, 1});
+
+    std::ostringstream os;
+    os << p;
+
+    REQUIRE(os.str() == "-1 + 1*x^3");
+  }
+
+#ifndef NDEBUG
+  SECTION("checking for bounds validation") {}
+#endif   // NDEBUG
+}