Skip to content

File linear_interp.hpp

File List > include > linear_interp.hpp

Go to the documentation of this file

#pragma once

#include <cassert>
#include <cmath>
#include <cstddef>
#include <vector>
#include <mdspan/mdspan.hpp>
#include <utility>
#include <numeric>
#include <tuple>
#include "vectorn.hpp"
#include "utils.hpp"


namespace cip {


template <typename T, std::size_t N>
class LinearCellND {
    using Span = std::span<const T>;
    using Spans = std::array<Span, N>;
    using Mdspan = std::mdspan<const T, std::dextents<std::size_t, N>, std::layout_stride>;
    using NomArray = std::array<T, (1 << N)>;
    static constexpr std::size_t numCorners = 1 << N;
public:
    explicit LinearCellND(const Mdspan &_f, const Spans &_x)
    : x(_x),
      f(_f),
      H(std::transform_reduce(x.begin(), x.end(), T{1}, std::multiplies<>{},
        [](const Span& xi) { return xi[1] - xi[0]; })),
      c(compute_coefficients())
    {
    }

    template <typename... Args>
    requires (sizeof...(Args) == N)
    T eval(Args&&... args) const
    {
        return gather_corners(
            {std::forward<Args>(args)...},
            std::make_index_sequence<numCorners>{}
        );
    }


private:
    const Spans x;
    const Mdspan f;
    const T H;
    const NomArray c;

    NomArray compute_coefficients() const {
        NomArray c;
        for (std::size_t J = 0; J < numCorners; ++J) {
            c[J] = compute_c_J(J);
        }
        return c;
    }

    T compute_c_J(std::size_t J) const {
        T c_J = T{0};
        for (std::size_t mask = 0; mask < numCorners; ++mask) {
            T prod = T{1};
            std::array<std::size_t, N> indices{};
            for (std::size_t k = 0; k < N; ++k) {
                indices[k] = (mask & (1u << k)) ? 1 : 0;
                prod *= gamma(J, indices[k], k);
            }
            c_J += f(indices) * prod;
        }
        return c_J / H;
    }

    T gamma(std::size_t J, std::size_t i, std::size_t k) const {
        return (J & (1u << k))
            ? (i == 0 ? -T{1} : T{1})
            : (i == 0 ? x[k][1] : -x[k][0]);
    }

    template <std::size_t... Js>
    T gather_corners(const std::array<T, N>& xs, std::index_sequence<Js...>) const {
        return ( ... + compute_corner<Js>(xs, std::make_index_sequence<N>{}) );
    }

    template <std::size_t J, std::size_t... I>
    T compute_corner(const std::array<T, N>& xs, std::index_sequence<I...>) const {
        return c[J] * (T{1} * ... * ((J & (1u << I)) ? xs[I] : T{1}));
    }

}; // class LinearCellND


template <typename T, std::size_t N>
class LinearInterpND {
    using Vector = std::vector<T>;
    using Array = std::array<Vector, N>;
    using F = cip::VectorN<T, N>;
    using Cell = LinearCellND<T, N>;
    using Cells = cip::VectorN<Cell, N>;
    using Span = std::span<const T>;
    using Pr = std::pair<std::size_t, std::size_t>;
    using Indexers = std::array<cip::Indexer<T>, N>;
public:
    template <typename... Args>
    LinearInterpND(const F &_f, const Args & ... _xi)
    : xi{_xi...}, 
      f(_f), 
      indexers{cip::Indexer<T>(_xi)...}, 
      cells(build(_xi...))
    {
    }

    ~LinearInterpND() { }

    template <typename... Args>
    T eval(const Args&... args) const
    {
        std::size_t dim = 0;
        std::array<size_t, N> indices = { indexers[dim++].sort_index(args)... };
        return cells(indices).eval(args...);
    }

    template <typename... Vectors>
    Vector evaln(const Vectors & ... inputs) const
    {
        static_assert(sizeof...(inputs) > 0, "At least one input vector is required");

        const std::size_t size = std::get<0>(std::tuple<const Vectors&...>(inputs...)).size();
        if (!((inputs.size() == size) && ...)) {
            throw std::invalid_argument("All input vectors must have the same size");
        }

        Vector z(size); // Output vector
        for (std::size_t i = 0; i < size; ++i) {
            z[i] = eval(inputs[i]...);
        }
        return z;
    }


private:
    const Array xi;
    const F f;
    const Indexers indexers;
    const Cells cells;

    template <typename... Args>
    const Cells build(const Args & ... _xi) const
    {
        Cells _cells({(_xi.size()-1)...});
        build_cell(_cells);
        return _cells;
    }

    template <typename... Indices>
    void build_cell(Cells &_cells, Indices... indices) const {
        if constexpr (sizeof...(Indices) == N) {
            std::size_t index = 0;
            std::array<Span, N> spans = {Span(&xi[index++][indices], 2)...};
            _cells.emplace_back(
                f.submdspan(Pr{indices, indices+1}...),
                spans
            );
        } else {
            for (std::size_t i = 0; i < xi[sizeof...(indices)].size()-1; ++i) {
                build_cell(_cells, indices..., i);
            }
        }
    }

}; // class LinearInterpND



template <typename T>
class LinearCellND<T, 1> {
    using Span = std::span<const T>;
public:
    explicit LinearCellND(Span x, Span f)
    : x0(x[0]),
      b0((x[1]*f[0]-x[0]*f[1])/(x[1]-x[0])),
      a0((f[1]-f[0])/(x[1]-x[0]))
    {
    }
    ~LinearCellND() { }

    T eval(const T &xi) const
    {
        return b0 + a0*xi;
    }

private:
    const T x0;
    const T a0;
    const T b0;
};


template <typename T>
class LinearInterpND<T, 1> {
    using Cell = LinearCellND<T, 1>;
    using Vector = std::vector<T>;
    using Span = std::span<const T>;
public:
    LinearInterpND(const Vector &_f, const Vector &_x)
      : indexer(_x)
    {
        assert(_x.size() == _f.size());
        build(_x, _f);
    }
    ~LinearInterpND() { }

    void build(const Vector &x, const Vector &f)
    {
        cells.reserve(x.size()-1);
        for (int i = 0; i < x.size()-1; ++i)
        {
            cells.push_back(Cell(Span(&x[i], 2), Span(&f[i], 2)));
        }
    }

    T eval(const T xi) const
    {
        return cells[indexer.sort_index(xi)].eval(xi);
    }

    Vector evaln(const Vector &xi) const
    {
        auto xi_iter = xi.begin();
        Vector yi(xi.size());
        for (auto &yi_i : yi)
        {
            yi_i = eval(*xi_iter++);
        }
        return yi;
    }

private:
    const cip::Indexer<T> indexer;
    std::vector<Cell> cells;
}; // class LinearInterpND case N=1


template <typename T>
class LinearInterp1D : public LinearInterpND<T, 1> {
    using Vector = std::vector<T>;
    using Vector2 = cip::VectorN<T, 1>;
public:
    explicit LinearInterp1D(const Vector &x, const Vector &f)
    : LinearInterpND<T, 1>(f, x)
    {}

    ~LinearInterp1D() { }
};



template <typename T>
class LinearInterp2D : public LinearInterpND<T, 2> {
    using Vector = std::vector<T>;
    using Vector2 = cip::VectorN<T, 2>;
public:
    explicit LinearInterp2D(const Vector &x, const Vector &y, const Vector2 &f)
    : LinearInterpND<T, 2>(f, x, y)
    {}

    ~LinearInterp2D() { }
};


template <typename T>
class LinearInterp3D : public LinearInterpND<T, 3> {
    using Vector = std::vector<T>;
    using Vector3 = cip::VectorN<T, 3>;
public:
    explicit LinearInterp3D(const Vector &x, const Vector &y, const Vector &z, const Vector3 &f)
    : LinearInterpND<T, 3>(f, x, y, z)
    {}

    ~LinearInterp3D() { }
};



} // namespace cip