diff --git a/src/mo_polynomial.f90 b/src/mo_polynomial.f90
new file mode 100644
index 0000000000000000000000000000000000000000..e6aee44f881bb4b148cbb04dec162eea7db489ad
--- /dev/null
+++ b/src/mo_polynomial.f90
@@ -0,0 +1,329 @@
+!> \file    mo_polynomial.f90
+!> \brief   \copybrief mo_polynomial
+!> \details \copydetails mo_polynomial
+
+!> \brief   Module to handle polynomials.
+!> \details This module provides routines to deal with polynomials like evaluation, root finding or derivation.
+!> \version 0.1
+!> \authors Sebastian Mueller
+!> \date    Oct 2024
+!> \copyright Copyright 2005-\today, the CHS Developers, Sabine Attinger: All rights reserved.
+!! FORCES is released under the LGPLv3+ license \license_note
+module mo_polynomial
+
+  use mo_kind, only : i4, dp
+  use mo_utils, only : equal
+  use mo_message, only : error_message
+
+  implicit none
+  private
+
+  public :: poly_eval
+  public :: poly_deriv
+  public :: poly_order
+  public :: poly_root
+  public :: poly_root_newton
+  public :: poly_root_halley
+  public :: poly_root_cubic
+
+contains
+
+  !> \brief   Evaluate polynomial from given coefficients p(1) * x^(n-1) + ... + p(n).
+  !> \return  Function value for given point.
+  real(dp) function poly_eval(p, x)
+
+    !> array with coefficients
+    real(dp), intent(in)  :: p(:)
+    !> point for evaluation
+    real(dp), intent(in) :: x
+
+    integer(i4) :: n, i
+
+    n = size(p) ! should be > 0
+    if (n == 0_i4) call error_message("mo_polynomial : p is an empty array.")
+
+    ! Horner's scheme
+    poly_eval = p(1)
+    do i = 2_i4, n
+      poly_eval = poly_eval * x + p(i)
+    end do
+
+  end function poly_eval
+
+  !> \brief   Coefficients of the derivative of a polynomial from given coefficients p(1) * x^(n-1) + ... + p(n).
+  !> \return  Coefficient array for the derivative d_p(1) * x^(n-2) + ... + d_p(n-1) with d_p(i) = (n - i) * p(i)
+  function poly_deriv(p) result(d_p)
+
+    !> array with coefficients
+    real(dp), intent(in)  :: p(:)
+
+    real(dp) :: d_p(max(size(p) - 1_i4, 1_i4))
+    integer(i4) :: n, i
+
+    n = size(p)
+    if (n == 0_i4) call error_message("mo_polynomial : p is an empty array.")
+    if (n == 1_i4) d_p(1) = 0.0
+
+    do i = 1_i4, n - 1_i4
+        d_p(i) = (n - i) * p(i)
+    end do
+
+  end function poly_deriv
+
+  !> \brief   Derive polynomial order from given coefficients p(1) * x^(n-1) + ... + p(n).
+  !> \return  Order of the polynomial (determined by non zero entry in coefficients). order < n.
+  integer(i4) function poly_order(p)
+
+    !> array with coefficients
+    real(dp), intent(in)  :: p(:)
+
+    integer(i4) :: n, i
+
+    n = size(p)
+    if (n == 0_i4) call error_message("mo_polynomial : p is an empty array.")
+
+    do i = 1_i4, n
+      if (abs(p(i)) > 0.0_dp) then
+        poly_order = (n - i)
+        return
+      end if
+    end do
+
+  end function poly_order
+
+  !> \brief   Find root of a polynomial from given coefficients p(1) * x^(n-1) + ... + p(n) with Householder methods.
+  !> \return  Coefficient array for the derivative.
+  function poly_root(p, x0, delta, max_n, method, raise, status) result(root)
+
+    !> array with coefficients
+    real(dp), intent(in)               :: p(:)
+    !> initial guess
+    real(dp), intent(in)               :: x0
+    !> convergence criteria (default: 1e-2)
+    real(dp), intent(in), optional     :: delta
+    !> maximum number of iterations (default: 50)
+    integer(i4), intent(in), optional  :: max_n
+    !> method to use: 0 - will be selected by order, 1 - Newton, 2 - Halley (default ord > 1), 3 - cubic Householder
+    integer(i4), intent(in), optional :: method
+    !> whether to raise an error if status is not 0 (default: .true.)
+    logical, intent(in), optional  :: raise
+    !> status of root finding: 0 - found root, 1 - max number of iterations reached, 2 - not converging, 3 - polynomial is constant
+    integer(i4), intent(out), optional :: status
+
+    real(dp) :: root
+    integer(i4) :: method_, status_
+    logical :: raise_
+
+    method_ = 0_i4
+    raise_ = .true.
+    if ( present(method) ) method_ = method
+    if ( present(raise) ) raise_ = raise
+
+    if ( method_ == 0_i4 ) then
+      if ( poly_order(p) < 2 ) then
+        method_ = 1_i4
+      else
+        method_ = 2_i4 ! Halley is optimal for polynomials
+      end if
+    end if
+
+    select case(method_)
+      case(1_i4) ! Newton
+        root = poly_root_newton(p, x0, delta, max_n, status_)
+      case(2_i4) ! Halley
+        root = poly_root_halley(p, x0, delta, max_n, status_)
+      case(3_i4) ! cubic Householder
+        root = poly_root_cubic(p, x0, delta, max_n, status_)
+      case default
+        call error_message("mo_polynomial::poly_root : 'method' needs to be in {1,2,3}.")
+    end select
+
+    if ( present(status) ) status = status_
+    if ( status_ /= 0_i4 .and. raise_ ) then
+      select case(status_)
+        case(1_i4)
+          call error_message("mo_polynomial::poly_root : max number of iterations reached.")
+        case(2_i4)
+          call error_message("mo_polynomial::poly_root : stuck on plateau.")
+        case(3_i4)
+          call error_message("mo_polynomial::poly_root : given polynomial is constant.")
+      end select
+    end if
+
+  end function poly_root
+
+  !> \brief   Find root of a polynomial from given coefficients p(1) * x^(n-1) + ... + p(n) with Newtons method.
+  !> \return  Coefficient array for the derivative.
+  function poly_root_newton(p, x0, delta, max_n, status) result(root)
+
+    !> array with coefficients
+    real(dp), intent(in)               :: p(:)
+    !> initial guess
+    real(dp), intent(in)               :: x0
+    !> convergence criteria (default: 1e-2)
+    real(dp), intent(in), optional     :: delta
+    !> maximum number of iterations (default: 50)
+    integer(i4), intent(in), optional  :: max_n
+    !> status of root finding: 0 - found root, 1 - max number of iterations reached, 2 - not converging, 3 - polynomial is constant
+    integer(i4), intent(out), optional :: status
+
+    real(dp) :: root
+    real(dp) :: d_p(size(p) - 1)
+    integer(i4) :: n, i, status_, max_n_
+    real(dp) :: delta_, df
+
+    delta_ = 1.0e-02
+    max_n_ = 50_i4
+    if ( present(delta) ) delta_ = delta
+    if ( present(max_n) ) max_n_ = max_n
+
+    root = x0
+    if ( poly_order(p) < 1 ) then
+      if ( present(status) ) status = 3_i4
+      return
+    end if
+
+    status_ = 1_i4
+    d_p = poly_deriv(p)
+
+    ! newton iteration
+    do i = 1_i4, max_n_
+      ! check if current iteration is already fine
+      if ( abs(poly_eval(p, root)) < delta_ ) then
+        status_ = 0_i4
+        exit
+      end if
+      ! if derivative is 0, return and set status to 2
+      df = poly_eval(d_p, root)
+      if (equal(df, 0.0_dp)) then
+        status_ = 2_i4
+        exit
+      end if
+      ! next value by newton method
+      root = root - poly_eval(p, root) / df
+    end do
+
+    if ( present(status) ) status = status_
+
+  end function poly_root_newton
+
+  !> \brief   Find root of a polynomial from given coefficients p(1) * x^(n-1) + ... + p(n) with Halleys method.
+  !> \return  Coefficient array for the derivative.
+  function poly_root_halley(p, x0, delta, max_n, status) result(root)
+
+    !> array with coefficients
+    real(dp), intent(in)               :: p(:)
+    !> initial guess
+    real(dp), intent(in)               :: x0
+    !> convergence criteria (default: 1e-2)
+    real(dp), intent(in), optional     :: delta
+    !> maximum number of iterations (default: 50)
+    integer(i4), intent(in), optional  :: max_n
+    !> status of root finding: 0 - found root, 1 - max number of iterations reached, 2 - not converging, 3 - polynomial is constant
+    integer(i4), intent(out), optional :: status
+
+    real(dp) :: root
+    real(dp) :: d_p1(max(size(p) - 1_i4, 1_i4)), d_p2(max(size(p) - 2_i4, 1_i4))
+    integer(i4) :: n, i, status_, max_n_
+    real(dp) :: delta_, df1, df2, f_df
+
+    delta_ = 1.0e-02
+    max_n_ = 50_i4
+    if ( present(delta) ) delta_ = delta
+    if ( present(max_n) ) max_n_ = max_n
+
+    root = x0
+    if ( poly_order(p) < 1 ) then
+      if ( present(status) ) status = 3_i4
+      return
+    end if
+
+    status_ = 1_i4
+    d_p1 = poly_deriv(p)
+    d_p2 = poly_deriv(d_p1)
+
+    ! halley iteration
+    do i = 1_i4, max_n_
+      ! check if current iteration is already fine
+      if ( abs(poly_eval(p, root)) < delta_ ) then
+        status_ = 0_i4
+        exit
+      end if
+      ! if derivative is 0, return and set status to 2
+      df1 = poly_eval(d_p1, root)
+      if (equal(df1, 0.0_dp)) then
+        status_ = 2_i4
+        exit
+      end if
+      df2 = poly_eval(d_p2, root)
+      f_df = poly_eval(p, root) / df1
+      ! next value by halley method
+      root = root - f_df / (1.0_dp - f_df * df2 / df1 * 0.5_dp)
+    end do
+
+    if ( present(status) ) status = status_
+
+  end function poly_root_halley
+
+  !> \brief   Find root of a polynomial from given coefficients p(1) * x^(n-1) + ... + p(n) with cubic Householder method.
+  !> \return  Coefficient array for the derivative.
+  function poly_root_cubic(p, x0, delta, max_n, status) result(root)
+
+    !> array with coefficients
+    real(dp), intent(in)               :: p(:)
+    !> initial guess
+    real(dp), intent(in)               :: x0
+    !> convergence criteria (default: 1e-2)
+    real(dp), intent(in), optional     :: delta
+    !> maximum number of iterations (default: 50)
+    integer(i4), intent(in), optional  :: max_n
+    !> status of root finding: 0 - found root, 1 - max number of iterations reached, 2 - not converging, 3 - polynomial is constant
+    integer(i4), intent(out), optional :: status
+
+    real(dp) :: root
+    real(dp) :: d_p1(max(size(p) - 1_i4, 1_i4)), d_p2(max(size(p) - 2_i4, 1_i4)), d_p3(max(size(p) - 3_i4, 1_i4))
+    integer(i4) :: n, i, status_, max_n_
+    real(dp) :: delta_, df1, df2, df3, f_df1, df2_df1
+
+    delta_ = 1.0e-02
+    max_n_ = 50_i4
+    if ( present(delta) ) delta_ = delta
+    if ( present(max_n) ) max_n_ = max_n
+
+    root = x0
+    if ( poly_order(p) < 1 ) then
+      if ( present(status) ) status = 3_i4
+      return
+    end if
+
+    status_ = 1_i4
+    d_p1 = poly_deriv(p)
+    d_p2 = poly_deriv(d_p1)
+    d_p3 = poly_deriv(d_p2)
+
+    ! halley iteration
+    do i = 1_i4, max_n_
+      ! check if current iteration is already fine
+      if ( abs(poly_eval(p, root)) < delta_ ) then
+        status_ = 0_i4
+        exit
+      end if
+      ! if derivative is 0, return and set status to 2
+      df1 = poly_eval(d_p1, root)
+      if (equal(df1, 0.0_dp)) then
+        status_ = 2_i4
+        exit
+      end if
+      df2 = poly_eval(d_p2, root)
+      df3 = poly_eval(d_p3, root)
+      f_df1 = poly_eval(p, root) / df1
+      df2_df1 = df2 / df1
+      ! next value by cubic householder method
+      root = root - f_df1 * (1.0_dp - f_df1 * df2_df1 * 0.5_dp) / (1.0_dp - df2_df1 * f_df1 + df3 / df1 * f_df1 ** 2_i4 / 6.0_dp)
+    end do
+
+    if ( present(status) ) status = status_
+
+  end function poly_root_cubic
+
+end module mo_polynomial
diff --git a/src/pf_tests/test_mo_polynomial.pf b/src/pf_tests/test_mo_polynomial.pf
new file mode 100644
index 0000000000000000000000000000000000000000..e4c1e065992d592fc3d8c4cfd9345eb18da78832
--- /dev/null
+++ b/src/pf_tests/test_mo_polynomial.pf
@@ -0,0 +1,54 @@
+module test_mo_polynomial
+
+  use funit
+  use mo_kind,   only: dp, i4
+  use mo_polynomial, only: poly_eval, poly_root, poly_order, poly_deriv
+
+  implicit none
+
+  real(dp), parameter, dimension(5) :: p = [1.0_dp, 0.0_dp, 0.0_dp, 1.0_dp, -1.0_dp]
+  real(dp), parameter :: x0 = -10.0_dp
+  real(dp), parameter :: delta = 1e-14_dp
+  real(dp), parameter :: tol=1e-4_dp
+  integer(i4), parameter :: max_n=2000_i4
+
+  contains
+
+! Test coordinate of a point compared to polygon
+  @test
+  subroutine test_polynomial()
+    real(dp), dimension(1) :: p1
+    real(dp), dimension(4) :: p4
+    real(dp) :: root
+    integer(i4) :: status
+
+    @assertEqual(poly_order(p), 4_i4)
+    @assertEqual(poly_order([1.0_dp]), 1_i4)
+
+    p1 = poly_deriv([1.0_dp])
+    @assertEqual(p1(1), 0_i4)
+
+    @assertEqual(poly_eval(p, 1.5_dp), 5.5625_dp, tolerance=tol)
+
+    p4 = poly_deriv(p)
+    @assertEqual(poly_eval(p4, 1.5_dp), 14.5_dp, tolerance=tol)
+
+    root = poly_root(p, x0, delta=delta, max_n=max_n, status=status, method=0)
+    @assertEqual(status, 0_i4)
+    @assertEqual(root, -1.2207440846057596_dp, tolerance=tol)
+
+    root = poly_root(p, x0, delta=delta, max_n=max_n, status=status, method=1)
+    @assertEqual(status, 0_i4)
+    @assertEqual(root, -1.2207440846057596_dp, tolerance=tol)
+
+    root = poly_root(p, x0, delta=delta, max_n=max_n, status=status, method=2)
+    @assertEqual(status, 0_i4)
+    @assertEqual(root, -1.2207440846057596_dp, tolerance=tol)
+
+    root = poly_root(p, x0, delta=delta, max_n=max_n, status=status, method=3)
+    @assertEqual(status, 0_i4)
+    @assertEqual(root, -1.2207440846057596_dp, tolerance=tol)
+
+  end subroutine test_polynomial
+
+end module test_mo_polynomial
\ No newline at end of file