// Copyright (C) 2018 Davis E. King (davis@dlib.net)
// License: Boost Software License See LICENSE.txt for the full license.
#ifndef DLIB_ISOTONIC_ReGRESSION_H_
#define DLIB_ISOTONIC_ReGRESSION_H_
#include "isotonic_regression_abstract.h"
#include <vector>
#include <utility>
#include <cstddef>
namespace dlib
{
class isotonic_regression
{
public:
template <
typename const_iterator,
typename iterator
>
void operator() (
const_iterator begin,
const_iterator end,
iterator obegin
)
{
do_isotonic_regression(begin, end);
// unpack blocks to output
for (auto& block : blocks)
{
for (size_t k = 0; k < block.num; ++k)
set_val(*obegin++, block.avg);
}
blocks.clear();
}
void operator() (
std::vector<double>& vect
) { (*this)(vect.begin(), vect.end(), vect.begin()); }
template <typename T, typename U>
void operator() (
std::vector<std::pair<T,U>>& vect
) { (*this)(vect.begin(), vect.end(), vect.begin()); }
template <
typename const_iterator,
typename iterator
>
void fit_with_linear_output_interpolation (
const_iterator begin,
const_iterator end,
iterator obegin
)
{
do_isotonic_regression(begin, end);
// Unpack blocks to output, but here instead of producing the step function
// output we linearly interpolate. Note that this actually fits the data less
// than the step-function, but in many applications might be closer to what you
// really want when using isotonic_regression than the step function.
for (size_t i = 0; i < blocks.size(); ++i)
{
auto& block = blocks[i];
double prev = (blocks.front().avg + block.avg)/2;
if (i > 0)
prev = (blocks[i-1].avg+block.avg)/2;
double next = (blocks.back().avg + block.avg)/2;
if (i+1 < blocks.size())
next = (blocks[i+1].avg+block.avg)/2;
for (size_t k = 0; k < block.num; ++k)
{
const auto mid = block.num/2.0;
if (k < mid)
{
const double alpha = k/mid;
set_val(*obegin++, (1-alpha)*prev + alpha*block.avg);
}
else
{
const double alpha = k/mid-1;
set_val(*obegin++, alpha*next + (1-alpha)*block.avg);
}
}
}
blocks.clear();
}
void fit_with_linear_output_interpolation (
std::vector<double>& vect
) { fit_with_linear_output_interpolation(vect.begin(), vect.end(), vect.begin()); }
template <typename T, typename U>
void fit_with_linear_output_interpolation (
std::vector<std::pair<T,U>>& vect
) { fit_with_linear_output_interpolation(vect.begin(), vect.end(), vect.begin()); }
private:
template <
typename const_iterator
>
void do_isotonic_regression (
const_iterator begin,
const_iterator end
)
{
blocks.clear();
// Do the actual isotonic regression. The output is a step-function and is
// stored in the vector of blocks.
for (auto i = begin; i != end; ++i)
{
blocks.emplace_back(get_val(*i));
while (blocks.size() > 1 && prev_block().avg > current_block().avg)
{
// merge the last two blocks.
prev_block() = prev_block() + current_block();
blocks.pop_back();
}
}
}
template <typename T>
static double get_val(const T& v) { return v;}
template <typename T, typename U>
static double get_val(const std::pair<T,U>& v) { return v.second;}
template <typename T>
static void set_val(T& v, double val) { v = val;}
template <typename T, typename U>
static void set_val(std::pair<T,U>& v, double val) { v.second = val;}
struct block_t
{
block_t(double val) : num(1), avg(val) {}
block_t(size_t n, double val) : num(n), avg(val) {}
size_t num;
double avg;
inline block_t operator+(const block_t& rhs) const
{
return block_t(num+rhs.num,
(num*avg + rhs.num*rhs.avg)/(num+rhs.num));
}
};
inline block_t& prev_block() { return blocks[blocks.size()-2]; }
inline block_t& current_block() { return blocks.back(); }
std::vector<block_t> blocks;
};
}
#endif // DLIB_ISOTONIC_ReGRESSION_H_