signal_generate.h

Back to Signal class

tim/core/

// Description: Generation of common signals

#ifndef TIM_SIGNAL_GENERATE_H
#define TIM_SIGNAL_GENERATE_H

#include "tim/core/signal.h"

#include <pastel/math/matrix/cholesky_decomposition.h>
#include <pastel/math/matrix/matrix.h>

#include <pastel/sys/random.h>

namespace Tim
{

    //! Generates uniform random variables in [0, 1]^n.
    /*!
   Preconditions:
   dimension > 0
   size >= 0
   */
    inline TIM void generateUniform(Signal signal)
    {
        ranges::generate(signal.data().range(), [](){return 2 * random<dreal>() - 1;});
    }

    inline TIM SignalData generateUniform(integer dimension, integer samples)
    {
        SignalData signal(dimension, samples);
        generateUniform((Signal)signal);
        return signal;
    }

    //! Generates standard gaussian random variables in R^n.
    /*!
   Preconditions:
   dimension > 0
   samples >= 0
   */
    inline TIM void generateGaussian(Signal signal)
    {
        ranges::generate(signal.data().range(), [](){return randomGaussian<dreal>();});
    }

    inline TIM SignalData generateGaussian(integer dimension, integer samples)
    {
        SignalData signal(dimension, samples);
        generateGaussian((Signal)signal);
        return signal;
    }

    //! Generates correlated gaussian random variables in R^n.
    /*!
   Preconditions:
   dimension > 0
   samples >= 0

   The correlated gaussian random variable is given by
   multiplying a standard gaussian random variable
   with the lower triangular part of the cholesky decomposition 
   of the correlation matrix.

   If the given correlation matrix turns out not to
   be numerically positive definite then
   the function call is equivalent to calling
   generateGaussian() (resulting in the 
   correlation matrix being identity).
   */
    inline TIM void generateCorrelatedGaussian(
        Signal signal,
        const CholeskyDecompositionInplace<dreal>& covarianceCholesky)
    {
        ENSURE_OP(covarianceCholesky.lower().cols(), ==, signal.dimension());
        ENSURE(covarianceCholesky.succeeded());

        generateGaussian(signal);

        asMatrix(signal.data()) *= asMatrix(covarianceCholesky.lower().transpose());
    }

    inline TIM SignalData generateCorrelatedGaussian(
        integer dimension, integer samples, 
        const CholeskyDecompositionInplace<dreal>& covarianceCholesky) {
        SignalData signal(dimension, samples);
        generateCorrelatedGaussian((Signal)signal, covarianceCholesky);
        return signal;
    }

    inline TIM void generateGeneralizedGaussian(
        Signal signal, dreal shape,   dreal scale)
    {
        ranges::generate(signal.data().range(), 
            [shape, scale](){return randomGeneralizedGaussian<dreal>(shape, scale);});
    }

    inline TIM SignalData generateGeneralizedGaussian(
        integer dimension, integer samples, dreal shape, dreal scale)
    {
        SignalData signal(dimension, samples);
        generateGeneralizedGaussian((Signal)signal, shape, scale);
        return signal;
    }

    //! Generates a signal with time-varying coupling.
    /*!
   Preconditions:
   samples >= 0
   yzShift >= 0
   zyShift >= 0

   The signals are divide into three time regions.
   In the first and the third time regions, there is
   no coupling between x, y, and z. However, in
   the second time region x drives y, and y drives z.
   The amplitudes of these drives are given by a sine
   wave for the x->y, and by a cosine wave for the
   y->z. Thus, those estimators which are sensitive
   to temporal changes in coupling (e.g. partial 
   transfer entropy) should give similar coupling curves.  
   */
    inline TIM void generateTimeVaryingCoupling(
        integer samples,
        integer yxShift,
        integer zyShift,
        Signal& xSignal,
        Signal& ySignal,
        Signal& zSignal)
    {
        ENSURE_OP(samples, >=, 0);
        ENSURE_OP(yxShift, >=, 0);
        ENSURE_OP(zyShift, >=, 0);

        ENSURE_OP(xSignal.samples(), ==, ySignal.samples());
        ENSURE_OP(xSignal.samples(), ==, zSignal.samples());
        ENSURE_OP(xSignal.dimension(), ==, 1);
        ENSURE_OP(ySignal.dimension(), ==, 1);
        ENSURE_OP(zSignal.dimension(), ==, 1);

        if (xSignal.samples() == 0)
        {
            return;
        }

        integer couplingStart = samples / 3;

        const integer couplingEnd = (samples * 2) / 3;
        integer couplingSamples = couplingEnd - couplingStart;
        dreal cyclesPerSample = 

            (2 * constantPi<dreal>()) / couplingSamples;

        auto xi = std::begin(xSignal.data().range());
        auto yi = std::begin(ySignal.data().range());
        auto zi = std::begin(zSignal.data().range());

        for (integer i = 0;i < samples;++i)
        {
            dreal couplingYx = 0;
            dreal couplingZy = 0;
            if (i >= couplingStart && i < couplingEnd)
            {
                const dreal t = cyclesPerSample * (i - couplingStart);
                couplingYx = std::sin(t);
                couplingZy = std::cos(t);
            }

            dreal xPrevious = 0;
            dreal yPrevious = 0;
            dreal zPrevious = 0;
            if (i >= 1)
            {
                xPrevious = *(xi - 1);
                yPrevious = *(yi - 1);
                zPrevious = *(zi - 1);
            }

            dreal xHistory = 0;
            if (i >= yxShift)
            {
                xHistory = *(xi - yxShift);
            }

            dreal yHistory = 0;
            if (i >= zyShift)
            {
                yHistory = *(yi - zyShift);
            }

            *xi = 0.4 * xPrevious + 
                randomGaussian<dreal>();
            *yi = 0.5 * yPrevious + 
                couplingYx * std::sin(xHistory) + 
                randomGaussian<dreal>();
            *zi = 0.5 * zPrevious + 
                couplingZy * std::sin(yHistory) + 
                randomGaussian<dreal>();

            ++xi;
            ++yi;
            ++zi;
        }
    }

}

#endif