From 5a82bd3923571fa50e4cee2292455b2d5877426c Mon Sep 17 00:00:00 2001
From: Timo Koch <timo.koch@iws.uni-stuttgart.de>
Date: Sat, 18 May 2019 02:45:58 +0200
Subject: [PATCH] [common] Implement simple natural cubic spline

---
 dumux/common/CMakeLists.txt |   1 +
 dumux/common/cubicspline.hh | 181 ++++++++++++++++++++++++++++++++++++
 2 files changed, 182 insertions(+)
 create mode 100644 dumux/common/cubicspline.hh

diff --git a/dumux/common/CMakeLists.txt b/dumux/common/CMakeLists.txt
index 4ec3919ec0..c743fc2994 100644
--- a/dumux/common/CMakeLists.txt
+++ b/dumux/common/CMakeLists.txt
@@ -8,6 +8,7 @@ boundaryconditions.hh
 boundaryflag.hh
 boundarytypes.hh
 boundingboxtree.hh
+cubicspline.hh
 defaultmappertraits.hh
 defaultusagemessage.hh
 dimensionlessnumbers.hh
diff --git a/dumux/common/cubicspline.hh b/dumux/common/cubicspline.hh
new file mode 100644
index 0000000000..ed6739194d
--- /dev/null
+++ b/dumux/common/cubicspline.hh
@@ -0,0 +1,181 @@
+// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+// vi: set et ts=4 sw=4 sts=4:
+/*****************************************************************************
+ *   See the file COPYING for full copying permissions.                      *
+ *                                                                           *
+ *   This program is free software: you can redistribute it and/or modify    *
+ *   it under the terms of the GNU General Public License as published by    *
+ *   the Free Software Foundation, either version 3 of the License, or       *
+ *   (at your option) any later version.                                     *
+ *                                                                           *
+ *   This program is distributed in the hope that it will be useful,         *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of          *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the            *
+ *   GNU General Public License for more details.                            *
+ *                                                                           *
+ *   You should have received a copy of the GNU General Public License       *
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.   *
+ *****************************************************************************/
+/*!
+ * \file
+ * \ingroup Common
+ * \brief A simple implementation of a cubic spline
+ */
+#ifndef DUMUX_COMMON_CUBIC_SPLINE_HH
+#define DUMUX_COMMON_CUBIC_SPLINE_HH
+
+#include <iterator>
+#include <vector>
+#include <algorithm>
+
+#include <dune/common/fvector.hh>
+#include <dune/common/fmatrix.hh>
+#include <dune/istl/btdmatrix.hh>
+#include <dune/istl/bvector.hh>
+
+namespace Dumux {
+
+/*!
+ * \ingroup Common
+ * \brief A simple implementation of a natural cubic spline
+ * \note We follow the notation at http://mathworld.wolfram.com/CubicSpline.html
+ */
+template<class Scalar = double>
+class CubicSpline
+{
+public:
+    /*!
+     * \brief Default constructor
+     */
+    CubicSpline() = default;
+
+    /*!
+     * \brief Contruct a natural cubic spline from the control points (x[i], y[i])
+     * \param x a vector of x-coordinates
+     * \param y a vector of y-coordinates
+     */
+    CubicSpline(const std::vector<Scalar>& x, const std::vector<Scalar> y)
+    {
+        // check some requirements
+        assert (x.size() == y.size());
+        assert (x.size() >=2);
+        assert (std::is_sorted(x.begin(), x.end()));
+
+        updatePoints(x, y);
+    }
+
+    /*!
+     * \brief Create a natural cubic spline from the control points (x[i], y[i])
+     * \note we enforce continuous second derivatives in the inside and zero second derivatives at the boundary
+     * \param x a vector of x-coordinates
+     * \param y a vector of y-coordinates
+     */
+    void updatePoints(const std::vector<Scalar>& x, const std::vector<Scalar>& y)
+    {
+        // save a copy of the control points
+        x_ = x;
+
+        // the number of control points
+        numPoints_ = x.size();
+
+        const auto numSegments = numPoints_-1;
+        coeff_.resize(numSegments*4+2); // 4 coefficients for each segment + last y-value and derivative
+
+        // construct a block-tridiagonal matrix system solving for the derivatives
+        Dune::BTDMatrix<Dune::FieldMatrix<double, 1, 1>> matrix(numPoints_);
+        Dune::BlockVector<Dune::FieldVector<double, 1>> rhs(numPoints_);
+        Dune::BlockVector<Dune::FieldVector<double, 1>> d(numPoints_);
+
+        // assemble matrix and rhs row-wise
+        matrix[0][0] = 2.0;
+        matrix[0][1] = 1.0;
+        rhs[0] = 3.0*(y[1] - y[0]);
+        for (int i = 1; i < numPoints_-1; ++i)
+        {
+            matrix[i][i-1] = 1.0;
+            matrix[i][i] = 4.0;
+            matrix[i][i+1] = 1.0;
+            rhs[i] = 3.0*(y[i+1] - y[i-1]);
+        }
+        matrix[numPoints_-1][numPoints_-1] = 2.0;
+        matrix[numPoints_-1][numPoints_-2] = 1.0;
+        rhs[numPoints_-1] = 3.0*(y[numPoints_-1] - y[numPoints_-2]);
+
+        // solve for derivatives
+        matrix.solve(d, rhs);
+
+        // compute coefficients
+        std::size_t offset = 0;
+        for (int i = 0; i < numSegments; ++i, offset += 4)
+        {
+            coeff_[offset+0] = y[i];
+            coeff_[offset+1] = d[i];
+            coeff_[offset+2] = 3.0*(y[i+1]-y[i]) - 2.0*d[i] - d[i+1];
+            coeff_[offset+3] = 2.0*(y[i]-y[i+1]) + d[i] + d[i+1];
+        }
+        coeff_[offset+0] = y[numPoints_-1];
+        coeff_[offset+1] = d[numPoints_-1];
+    }
+
+    /*!
+     * \brief Evaluate the y value at a given x value
+     * \param x the x-coordinate
+     * \note We extrapolate linearly if out of bounds
+     */
+    Scalar eval(const Scalar x) const
+    {
+        if (x < x_[0])
+            return coeff_[0] + evalDerivative(x_[0])*(x - x_[0]);
+        else if (x > x_[numPoints_-1])
+            return coeff_[(numPoints_-1)*4] + evalDerivative(x_[numPoints_-1])*(x - x_[numPoints_-1]);
+        else
+        {
+            const auto lookUpIndex = std::distance(x_.begin(), std::lower_bound(x_.begin(), x_.end(), x));
+            if (lookUpIndex == 0)
+                return coeff_[0];
+
+            // get coefficients
+            const auto* coeff = coeff_.data() + (lookUpIndex-1)*4;
+
+            // interpolate parametrization parameter t in [0,1]
+            const auto t = (x - x_[lookUpIndex-1])/(x_[lookUpIndex] - x_[lookUpIndex-1]);
+            return coeff[0] + t*(coeff[1] + t*coeff[2] + t*t*coeff[3]);
+        }
+    }
+
+    /*!
+     * \brief Evaluate the first derivative dy/dx at a given x value
+     * \param x the x-coordinate
+     * \note We extrapolate linearly if out of bounds
+     */
+    Scalar evalDerivative(const Scalar x) const
+    {
+        if (x < x_[0])
+            return coeff_[1]/(x_[1] - x_[0]);
+        else if (x > x_[numPoints_-1])
+            return coeff_[(numPoints_-1)*4 + 1]/(x_[numPoints_-1] - x_[numPoints_-2]);
+        else
+        {
+            const auto lookUpIndex = std::distance(x_.begin(), std::lower_bound(x_.begin(), x_.end(), x));
+            if (lookUpIndex == 0)
+                return coeff_[1]/(x_[1] - x_[0]);
+
+            // get coefficients
+            const auto* coeff = coeff_.data() + (lookUpIndex-1)*4;
+
+            // interpolate parametrization parameter t in [0,1]
+            const auto t = (x - x_[lookUpIndex-1])/(x_[lookUpIndex] - x_[lookUpIndex-1]);
+            const auto dtdx = 1.0/(x_[lookUpIndex] - x_[lookUpIndex-1]);
+            return dtdx*(coeff[1] + t*(2.0*coeff[2] + t*3.0*coeff[3]));
+        }
+    }
+
+private:
+    std::vector<Scalar> x_; //!< the x-coordinates
+    std::size_t numPoints_; //!< the number of control points
+    std::vector<Scalar> coeff_; //!< the spline coefficients
+};
+
+} // end namespace Dumux
+
+#endif
-- 
GitLab