differential_entropy_sp.h

Back to Stowell-Plumbley estimator

tim/core/

// Description: Differential entropy estimation
// Detail: Stowell-Plumbley recursive partition estimator

#ifndef TIM_DIFFERENTIAL_ENTROPY_SP_H
#define TIM_DIFFERENTIAL_ENTROPY_SP_H

#include "tim/core/mytypes.h"
#include "tim/core/signal.h"
#include "tim/core/signal_tools.h"

#include <pastel/sys/range.h>
#include <pastel/sys/vector/vector_tools.h>
#include <pastel/sys/math_functions.h>
#include <pastel/sys/sequence/copy_n.h>

#include <algorithm>

namespace Tim
{

    namespace Detail_DifferentialEntropySp
    {

        class Computation
        {
        public:
            Computation(
                integer samples,
                integer dimension,
                integer minLevel)
                : samples_(samples)
                , dimension_(dimension)
                , minLevel_(minLevel)
            {
                unused(dimension_);
            }

            template <typename Iterator>
            dreal work(
                const Iterator& begin,
                const Iterator& end,
                const VectorD& min,
                const VectorD& max,
                integer level) const
            {
                // "Fast Multidimensional Entropy Estimation
                // by k-d Partitioning", 
                // Dan Stowell, Mark D. Plumbley,
                // IEEE Signal Processing Letters, Vol. 16, No. 6,
                // June 2009.

                if (begin == end)
                {
                    return 0;
                }

                integer n = end - begin;
                Iterator medianIter = begin + n / 2;
                integer d = maxIndex(max - min);

                Compare compare(d);
                std::nth_element(begin, medianIter, end, compare);

                const dreal median = (*medianIter)[d];

                const dreal z = 2 * std::sqrt((dreal)n) * 
                    (median - linear(min[d], max[d], 0.5)) /
                    (max[d] - min[d]);

                if (n <= 3 || (level >= minLevel_ && std::abs(z) >= 1.96))
                {
                    dreal p = (dreal)n / samples_;

                    return p * std::log(product(max - min) / p);
                }
                else
                {
                    VectorD leftMax = max;
                    leftMax[d] = median;

                    VectorD rightMin = min;
                    rightMin[d] = median;

                    return work(begin, medianIter, min, leftMax, level + 1) +
                        work(medianIter, end, rightMin, max, level + 1);
                }
            }

        private:
            class Compare
            {
            public:
                explicit Compare(integer dimension)
                    : dimension_(dimension)
                {
                }

                bool operator()(
                    const dreal* left,
                    const dreal* right) const
                {
                    if (left[dimension_] < right[dimension_])
                    {
                        return true;
                    }

                    if (right[dimension_] < left[dimension_])
                    {
                        return false;
                    }

                    return left < right;
                }

            private:
                integer dimension_;
            };

            integer samples_;
            integer dimension_;
            integer minLevel_;
        };

    }

    template <ranges::forward_range Signal_Range>
    dreal differentialEntropySp(
        Signal_Range&& signalSet)
    {
        if (ranges::empty(signalSet))
        {
            return 0;
        }

        integer signals = ranges::size(signalSet);
        integer samples = minSamples(signalSet);

        const integer n = samples * signals;

        // Gather the point set.

        std::vector<const dreal*> pointSet;
        pointSet.reserve(n);
        auto iter = ranges::begin(signalSet);
        auto iterEnd = signalSet.end();
        while(iter != iterEnd)
        {
            const Signal& signal = *iter;
            std::copy_n(
                std::begin(signal.pointRange()), samples,
                std::back_inserter(pointSet));

            ++iter;
        }

        // Compute bounds.

        integer dimension = std::begin(signalSet)->dimension();
        VectorD min(ofDimension(dimension), infinity<dreal>());
        VectorD max(ofDimension(dimension), -infinity<dreal>());
        for (integer i = 0;i < n;++i)
        {

            const dreal* point = pointSet[i];
            for (integer d = 0;d < dimension;++d)
            {
                dreal position = point[d];
                if (position < min[d])
                {
                    min[d] = position;
                }
                if (position > max[d])
                {
                    max[d] = position;
                }
            }
        }

        // Compute differential entropy.

        integer minLevel = std::ceil(log2<dreal>(n) / 2);
        Detail_DifferentialEntropySp::Computation
            computation(n, dimension, minLevel);

        return computation.work(
            pointSet.begin(), pointSet.end(),
            min, max, 0);
    }

}

#endif