Skip to content

datatypes/time_series.hpp

Namespaces

Name
datatypes
datatypes::timeseries
datatypes::exceptions
Helper function to build consistent informative error messages in exceptions with commonalities.

Classes

Name
struct datatypes::timeseries::DefaultMissingValuePolicyTypeFactory
class datatypes::timeseries::TTimeSeries
A template for univariate, single realisasion time series.
class datatypes::timeseries::MultiTimeSeries
Template for time series with multiple values at time point; e.g. to hold multiple realizations of time series in an ensemble.
struct datatypes::timeseries::time_series_of
struct datatypes::timeseries::ensemble_of
struct datatypes::timeseries::item_type_of
struct datatypes::timeseries::CommonTypes
Typical ensemble and time series data types derived from a fundamental data type for each data item.
class datatypes::timeseries::TimeSeriesOperations
class datatypes::timeseries::TimeWindow
An object that represents a time window, defining subset/trim operations on time series.
class datatypes::exceptions::TimeSeriesChecks

Source code

#pragma once


#include <iterator>
#include "boost/date_time/posix_time/posix_time.hpp"
#include <boost/function.hpp>
#include "datatypes/common.h"
#include "datatypes/time_step.h"
#include "datatypes/time_series_strategies.hpp"
#include "datatypes/exception_utilities.h"
#include "cinterop/common_c_interop.h"


using namespace boost::posix_time;
using namespace boost::gregorian;

using namespace datatypes::utils;

namespace datatypes
{
    namespace timeseries
    {

        template <typename T>
        struct DefaultMissingValuePolicyTypeFactory {
            typedef typename IfThenElse<
                std::is_pointer<T>::value, 
                NullPointerIsMissingPolicy<T>, 
                DefaultMissingFloatingPointPolicy<T>>::ResultT type;
        };

        template <typename T>
        MissingValuePolicy<T>* DefaultMissingValuePolicy() 
        {
            using U = typename DefaultMissingValuePolicyTypeFactory<T>::type;
            return new U();
        }
/*
        template <typename T>
        std::enable_if_t<std::is_pointer<T>::value, MissingValuePolicy<T>*>
            DefaultMissingValuePolicy() { return new NullPointerIsMissingPolicy<T>(); }
        template <typename T>
        std::enable_if_t<!std::is_pointer<T>::value, MissingValuePolicy<T>*>
            DefaultMissingValuePolicy() { return new DefaultMissingFloatingPointPolicy<T>(); }
*/

        template <typename T>
        StoragePolicy<T>* DefaultStoragePolicy() { return new StlVectorStorage<T>(); }

        template <typename T = double>
        class TTimeSeries
        {
        public:
            using ElementType = T;
        private:
            StoragePolicy<T>* storage = nullptr;
            MissingValuePolicy<T>* mvp = nullptr;

            void CheckConstructorReadonly()
            {
                if (this->storage->ReadOnly())
                    datatypes::exceptions::ExceptionUtilities::ThrowInvalidOperation("The storage for this time series is marked as read-only; there may be inconsistencies");
            }

        protected:

            inline T GetNACode() const { return this->mvp->GetMissingValue(); }

        public:

            inline bool IsMissingValue(const T& value) const { return this->mvp->IsMissingValue(value); }

            inline T GetMissingValue() const { return this->mvp->GetMissingValue(); }

            TTimeSeries(StoragePolicy<T>* storage = nullptr, MissingValuePolicy<T>* mvp = nullptr)
            {
                SetDefaults(storage, mvp);
                if (!this->storage->ReadOnly())
                    Reset(0, not_a_date_time);
                instances++;
            }

            TTimeSeries(T default_value, size_t length, const ptime& startDate, const TimeStep& timeStep = TimeStep::GetHourly(), StoragePolicy<T>* storage = nullptr, MissingValuePolicy<T>* mvp = nullptr)
            {
                SetDefaults(storage, mvp);
                CheckConstructorReadonly();
                this->storage->SetTimeStep(timeStep);
                Reset(length, startDate, default_value);
                instances++;
            }

            TTimeSeries(size_t length, const ptime& startDate, const TimeStep& timeStep = TimeStep::GetHourly(), StoragePolicy<T>* storage = nullptr, MissingValuePolicy<T>* mvp = nullptr)
            {
                SetDefaults(storage, mvp);
                CheckConstructorReadonly();
                this->storage->SetTimeStep(timeStep);
                Reset(length, startDate, this->mvp->GetMissingValue());
                instances++;
            }

            TTimeSeries(const vector<T>& values, const ptime& startDate, const TimeStep& timeStep = TimeStep::GetHourly(), StoragePolicy<T>* storage = nullptr, MissingValuePolicy<T>* mvp = nullptr)
            {
                SetDefaults(storage, mvp);
                CheckConstructorReadonly();
                this->storage->SetTimeStep(timeStep);
                Reset(values, startDate);
                instances++;
            }

            TTimeSeries(const T * values, size_t length, const ptime& startDate, const TimeStep& timeStep = TimeStep::GetHourly(), StoragePolicy<T>* storage = nullptr, MissingValuePolicy<T>* mvp = nullptr)
            {
                SetDefaults(storage, mvp);
                CheckConstructorReadonly();
                this->storage->SetTimeStep(timeStep);
                Reset(length, startDate, values);
                instances++;
            }

            // This constructor is initially intended only as a convenience for testing purposes.
            TTimeSeries(std::function<T(size_t)>& valueGen, size_t length, const ptime& startDate, const TimeStep& timeStep = TimeStep::GetHourly(), StoragePolicy<T>* storage = nullptr, MissingValuePolicy<T>* mvp = nullptr)
            {
                SetDefaults(storage, mvp);
                CheckConstructorReadonly();
                this->storage->SetTimeStep(timeStep);
                vector<T> values(length);
                for (size_t i = 0; i < length; i++)
                    values[i] = valueGen(i);
                Reset(values, startDate);
                instances++;
            }

            TTimeSeries(const TTimeSeries<T>& src) {   // (Deep) Copy constructor.
                *this = src;
                instances++;
            }

            TTimeSeries(const TTimeSeries<T>& src, StoragePolicy<T>* storage) {   // (Deep) Copy constructor.
                Tag = src.Tag;
                mvp = src.mvp->Clone();
                this->storage = (storage == nullptr) ? DefaultStoragePolicy<T>() : storage;
                src.storage->CopyTo(*(this->storage));
                instances++;
            }

            TTimeSeries(const TTimeSeries<T>* src) {   // (Deep) Copy constructor.
                *this = *src;
                instances++;
            }

            TTimeSeries(TTimeSeries<T>&& src) {   // Move constructor.
                *this = std::move(src);
                instances++;
            }

            TTimeSeries<T>& operator=(TTimeSeries<T>&& src){
                // Avoid self assignment
                if (&src == this){
                    return *this;
                }
                std::swap(Tag, src.Tag);
                std::swap(storage, src.storage);
                std::swap(mvp, src.mvp);
                return *this;
            }

            TTimeSeries<T>& operator=(const TTimeSeries<T>& src)
            {
                if (&src == this){
                    return *this;
                }
                DeepCopyFrom(src);
                return *this;
            }

            virtual ~TTimeSeries()
            {
                if (storage != nullptr)
                    delete storage;
                if (mvp != nullptr)
                    delete mvp;
                instances--;
            }

            T GetValueNoConst(const size_t& index)
            {
                if (index < 0 || index >= GetLength())
                    datatypes::exceptions::ExceptionUtilities::ThrowOutOfRange("Trying to access a time series value with an index outside of its bounds");
                return storage->operator[](index);
            }

            const T& GetValueConst(const size_t& index) const {
                if (index < 0 || index >= GetLength())
                    datatypes::exceptions::ExceptionUtilities::ThrowOutOfRange("Trying to access a time series value with an index outside of its bounds");
                return storage->operator[](index);
            }

            T GetValue(const size_t& index)
            {
                return GetValueNoConst(index);
            }

            const T& GetValue(const size_t& index) const {
                return GetValueConst(index);
            }

            T GetValueNoConst(const ptime& instant)
            {
                return GetValueNoConst(IndexForTime(instant));
            }

            T GetValue(const ptime& instant)
            {
                return GetValueNoConst(IndexForTime(instant));
            }

            const T& GetValue(const ptime& instant) const
            {
                const size_t index = IndexForTime(instant);
                return GetValue(index);
            }

            T GetLastValue()
            {
                size_t length = GetLength();
                if (length == 0)
                    datatypes::exceptions::ExceptionUtilities::ThrowOutOfRange("TimeSeries::GetLastValue cannot be called on an empty time series");
                size_t index = length - 1;
                return GetValue(index);
            }

            void CopyTo(T * dest, size_t from = 0, size_t to = -1) const
            {
                CheckIntervalBounds(from, to);
                UncheckedCopyTo(dest, from, to);
            }

            void CopyWithMissingValueTo(T * dest, const T& missingValueValue, size_t from = 0, size_t to = -1) const
            {
                CheckIntervalBounds(from, to);
                UncheckedCopyTo(dest, from, to, missingValueValue);
            }

            void CopyValues(const TTimeSeries<T>& src)
            {
                if (GetTimeStep() != src.GetTimeStep())
                    datatypes::exceptions::ExceptionUtilities::ThrowInvalidOperation("time series copy values is only for identical time steps");
                size_t from = IndexForTime(src.GetStartDate());
                size_t to = IndexForTime(src.GetEndDate());
                CheckIntervalBounds(from, to);
                for (size_t i = from; i <= to; i++)
                {
                    T val = src.GetValue(i - from);
                    this->SetValue(i, (src.IsMissingValue(val) ? GetMissingValue() : val));
                }
            }

            void CopyTo(vector<T>& dest, size_t from = 0, size_t to = -1) const
            {
                CheckIntervalBounds(from, to);
                storage->CopyTo(dest, from, to);
            }

            T * GetValues(size_t from = 0, size_t to = std::numeric_limits<size_t>::max()) const
            {
                CheckIntervalBounds(from, to);
                size_t len = (to - from) + 1;
                T * values = new T[len];
                this->UncheckedCopyTo(values, from, to);
                return values;
            }

            vector<T> GetValuesVector(size_t from = 0, size_t to = std::numeric_limits<size_t>::max()) const
            {
                CheckIntervalBounds(from, to);
                vector<T> values;
                this->CopyTo(values, from, to);
                return values;
            }

            TTimeSeries<T> Subset(size_t from = 0, size_t to = std::numeric_limits<size_t>::max()) const
            {
                CheckIntervalBounds(from, to);
                size_t len = (to - from) + 1;
                T * values = GetValues(from, to);
                // TODO: consider what to do with storage policy. MVP makes sense to keep the same.
                auto res = TTimeSeries<T>(values, len, TimeForIndex(from), GetTimeStep(), nullptr, mvp->Clone());
                delete[] values;
                return res;
            }

            void SetValue(size_t index, T value)
            {
                storage->operator[](index) = value;
            }

            void SetValue(const ptime& instant, T value)
            {
                SetValue(IndexForTime(instant), value);
            }

            void Reset(size_t length, const ptime& startDate, const TimeStep& timeStep)
            {
                this->storage->SetStart(startDate);
                this->storage->SetTimeStep(timeStep);
                this->storage->Allocate(length, mvp->GetMissingValue());
            }

            void Reset(size_t length, const ptime& startDate, const T * values = nullptr)
            {
                if (values == nullptr)
                    this->storage->Allocate(length, mvp->GetMissingValue());
                else
                    this->storage->AllocateValues(length, values);
                this->storage->SetStart(startDate);
            }

            void Reset(size_t length, const ptime& startDate, T value)
            {
                this->storage->Allocate(length, value);
                this->storage->SetStart(startDate);
            }

            void Reset(const vector<T>& values, const ptime& startDate)
            {
                this->storage->AllocateValues(values);
                this->storage->SetStart(startDate);
            }

            void Reset(const vector<T>& values, const ptime& startDate, const TimeStep& timeStep)
            {
                this->storage->AllocateValues(values);
                this->storage->SetStart(startDate);
                this->storage->SetTimeStep(timeStep);
            }

            size_t GetLength() const
            {
                return storage->Size();
            }

            ptime GetStartDate() const
            {
                return storage->GetStart();
            }

            ptime GetEndDate() const
            {
                size_t n = GetLength();
                if (n == 0) return GetStartDate(); // what else?
                else
                    return GetTimeStep().AddSteps(GetStartDate(), n-1);
            }

            TimeStep GetTimeStep() const
            {
                return storage->GetTimeStep();
            }

            void SetTimeStep(const TimeStep& timeStep)
            {
                this->storage->SetTimeStep(timeStep);
            }

            void SetStartDate(const ptime& start)
            {
                this->storage->SetStart(start);
            }

            ptime TimeForIndex(size_t timeIndex) const
            {
                return GetTimeStep().AddSteps(this->GetStartDate(), timeIndex);
            }

            vector<ptime> TimeIndices() const
            {
                auto tstep = GetTimeStep();
                return tstep.AddSteps(this->GetStartDate(), SeqVec<double>(0, 1, GetLength()));
            }

            size_t IndexForTime(const ptime& instant) const {
                return LowerIndexForTime(instant);
            }

            size_t UpperIndexForTime(const ptime& instant) const
            {
                auto uIndex = GetTimeStep().GetUpperNumSteps(GetStartDate(), instant) - 1;
                datatypes::exceptions::ExceptionUtilities::CheckInRange<ptrdiff_t>(uIndex, 0, GetLength() - 1, "UpperIndexForTime");
                return (size_t)uIndex;
            }

            size_t LowerIndexForTime(const ptime& instant) const
            {
                auto uIndex = GetTimeStep().GetNumSteps(GetStartDate(), instant) - 1;
                datatypes::exceptions::ExceptionUtilities::CheckInRange<ptrdiff_t>(uIndex, 0, GetLength() - 1, "LowerIndexForTime");
                return (size_t)uIndex;
            }

            string GetSummary() const
            {
                string result = "[" + to_iso_extended_string(GetStartDate()) + "/" + to_iso_extended_string(GetEndDate()) + "];length=" +
                    std::to_string(GetLength()) + ";time step=" + GetTimeStep().GetName();
                return result;
            }

            T& operator[](const ptime& instant) {
                return storage->operator[](IndexForTime(instant));
            }

            T& operator[](const size_t i) {
                return storage->operator[](i);
            }

            const T& operator[](const ptime& instant) const {
                return this->operator[](IndexForTime(instant));
            }

            const T& operator[](const size_t i) const {
                return storage->operator[](i);
            }

            TTimeSeries<T> operator+(const TTimeSeries<T>& rhs) const
            {
                // TODO check geometry compatibility
                T a, b;
                T na = this->mvp->GetMissingValue();
                size_t length = rhs.GetLength();
                TTimeSeries<T> ts(na, length, GetStartDate(), GetTimeStep(), nullptr, mvp->Clone());
                //ts.naCode = this->naCode;
                for (size_t i = 0; i < length; i++)
                {
                    a = this->operator[](i);
                    b = rhs.operator[](i);
                    if (IsMissingValue(a) || rhs.IsMissingValue(b))
                        ts.operator[](i) = ts.GetNACode();
                    else
                        ts.operator[](i) = a + b;
                }
                return ts;
            }

            TTimeSeries<T> operator+(T value) const
            {
                T a;
                T na = this->mvp->GetMissingValue();
                size_t length = this->GetLength();
                TTimeSeries<T> ts(na, length, GetStartDate(), GetTimeStep(), nullptr, mvp->Clone());
                for (size_t i = 0; i < length; i++)
                {
                    a = this->operator[](i);
                    if (IsMissingValue(a))
                        ts.operator[](i) = ts.GetNACode();
                    else
                        ts.operator[](i) = a + value;
                }
                return ts;
            }

            template<typename M>
            TTimeSeries<T> operator*(M multiplicator) const
            {
                T a;
                TTimeSeries<T> ts(*this);
                auto length = GetLength();
                for (size_t i = 0; i < length; i++)
                {
                    a = this->operator[](i);
                    if (IsMissingValue(a))
                        ts.operator[](i) = ts.GetNACode();
                    else
                        ts.operator[](i) = a * multiplicator;
                }
                return ts;
            }

            string Tag;

        private:

            void CheckIntervalBounds(const size_t& from, size_t& to) const
            {
                size_t tsLen = this->GetLength();
                datatypes::exceptions::RangeCheck<size_t>::CheckTimeSeriesInterval(from, to, tsLen);
            }

            void UncheckedCopyTo(T * dest, const size_t& from, const size_t& to) const
            {
                // The following is neat and safe but not portable...
                //std::copy(data.begin() + from, data.begin() + to + 1, stdext::checked_array_iterator<T*>(dest, to - from + 1));
                // so, looking potentially unsafe:
                for (size_t i = from; i <= to; i++)
                {
                    dest[i - from] = storage->operator[](i);
                }
            }

            void UncheckedCopyTo(T * dest, const size_t& from, const size_t& to, const T& missingValue) const
            {
                for (size_t i = from; i <= to; i++)
                {
                    T value = storage->operator[](i);
                    dest[i - from] = IsMissingValue(value) ? missingValue : value;
                }
            }

            void DeepCopyFrom(const TTimeSeries<T>& src) {
                Tag = src.Tag;
                mvp = src.mvp->Clone();
                if (storage == nullptr)
                    storage = src.storage->Clone();
                else
                    src.storage->CopyTo(*(this->storage));
            }

            //ptime startDate;
            //TimeStep timeStep;
            static std::atomic<int> instances;

            void SetDefaults(StoragePolicy<T>* storage, MissingValuePolicy<T>* mvp)
            {
                this->mvp = (mvp != nullptr) ? mvp : DefaultMissingValuePolicy<T>();
                this->storage = (storage != nullptr) ? storage : DefaultStoragePolicy<T>();
                if(!this->storage->ReadOnly())
                    this->SetTimeStep(TimeStep::GetHourly());
            }

        public:
            static int NumInstances() { return instances; };
        };

        //template <typename T, template <typename> class StP, template <typename> class MvP>
        template <typename T>
        std::atomic<int> TTimeSeries<T>::instances(0);

        typedef TTimeSeries < double > TimeSeries;

        template <typename TsType = TimeSeries>
        class MultiTimeSeries // This may become an abstract class with specializations for lazy loading time series from the data store.
        {
        private:
            EnsembleStoragePolicy<TsType>* store = nullptr;
        protected:
            virtual void InitializeStorage()
            {
                if (store == nullptr)
                    store = new StdVectorEnsembleStoragePolicy<TsType>();
            }
        public:

            typedef typename std::remove_pointer<TsType>::type Type;
            typedef typename std::add_pointer<Type>::type PtrType;
            typedef TsType ItemType;
            typedef typename Type::ElementType ElementType;

            MultiTimeSeries(const vector<ElementType*>& values, size_t length, const ptime& startDate, const TimeStep& timeStep)
            {
                Clear();
                this->startDate = startDate;
                this->timeStep = timeStep;
                vector<PtrType> series;
                for (auto& d : values)
                    series.push_back(new Type(d, length, startDate, timeStep));
                store->Reset(series, startDate, timeStep);
            }

            MultiTimeSeries(const vector<vector<ElementType>>& values, const ptime& startDate, const TimeStep& timeStep)
            {
                Clear();
                this->startDate = startDate;
                this->timeStep = timeStep;
                vector<PtrType> series;
                for (auto& d : values)
                    series.push_back(new Type(d, startDate, timeStep));
                store->Reset(series, startDate, timeStep);
            }

            MultiTimeSeries(ElementType** const values, size_t ensSize, size_t length, const ptime& startDate, const TimeStep& timeStep)
            {
                Clear(); 
                this->startDate = startDate;
                this->timeStep = timeStep;
                vector<PtrType> series;
                for (int i = 0; i < ensSize; i++)
                    series.push_back(new Type(values[i], length, startDate, timeStep));
                store->Reset(series, startDate, timeStep);
            }

            MultiTimeSeries(const vector<PtrType>& values, const ptime& startDate, const TimeStep& timeStep)
            {
                Clear();
                this->startDate = startDate;
                this->timeStep = timeStep;
                store->Reset(values, startDate, timeStep);
            }

            MultiTimeSeries(const Type& series)
            {
                Clear();
                this->startDate = series.GetStartDate();
                this->timeStep = series.GetTimeStep();
                vector<PtrType> s;
                s.push_back(new Type(series));
                store->Reset(s, startDate, timeStep);
            }

            MultiTimeSeries(const vector<Type>& values, const ptime& startDate, const TimeStep& timeStep)
            {
                Clear();
                this->startDate = startDate;
                this->timeStep = timeStep;
                vector<PtrType> series;
                for (const Type& d : values)
                    series.push_back(new Type(d));
                store->Reset(series, startDate, timeStep);
            }

            // This constructor is initially intended only as a convenience for testing purposes.
            MultiTimeSeries(std::function<Type(size_t)>& valueGen, size_t ensSize, const ptime& startDate, const TimeStep& timeStep)
            {
                Clear();
                this->startDate = startDate;
                this->timeStep = timeStep;
                vector<PtrType> series;
                for (size_t i = 0; i < ensSize; i++)
                    series.push_back(new Type(valueGen(i)));
                store->Reset(series, startDate, timeStep);
            }

            MultiTimeSeries(const MultiTimeSeries<TsType>& src)
            {
                *this = src;
            }

            MultiTimeSeries(EnsembleStoragePolicy<TsType>* store)
            {
                if (store == nullptr) datatypes::exceptions::ExceptionUtilities::ThrowInvalidArgument("store must not be nullptr");
                this->store = store;
            }

            MultiTimeSeries()
            {
                this->startDate = ptime(not_a_date_time);
                this->timeStep = TimeStep::GetHourly();
            }

            MultiTimeSeries<PtrType>* AsPointerSeries()
            {
                return new MultiTimeSeries<PtrType>(store->AsReadonlyVector(), startDate, timeStep);
            }

            MultiTimeSeries<Type> AsValueSeries()
            {
                return MultiTimeSeries<Type>(store->AsReadonlyVector(), startDate, timeStep);
            }

            ~MultiTimeSeries()
            {
                Clear();
            }

            MultiTimeSeries& operator=(const MultiTimeSeries& src) {
                if (&src == this) {
                    return *this;
                }
                OperatorEqualImpl(src);
                return *this;
            }

            MultiTimeSeries& operator=(MultiTimeSeries&& src) {
                if (&src == this) {
                    return *this;
                }
                std::swap(timeStep, src.timeStep);
                std::swap(startDate, src.startDate);
                std::swap(store, src.store);
                return *this;
            }

            void ResetSeries(const size_t& numSeries, const size_t& lengthSeries, const ptime& startDate, const TimeStep& timeStep)
            {
                Clear();
                store->ResetSeries(numSeries, lengthSeries, startDate, timeStep);
                this->startDate = startDate;
                this->timeStep = timeStep;
            }

            TsType Get(size_t i)
            {
                return store->Get(i);
            }

            Type Get(size_t i) const
            {
                return store->Get(i);
            }

            ElementType Get(size_t i, size_t tsIndex)
            {
                return store->Get(i, tsIndex);
            }

            void Set(size_t i, size_t tsIndex, ElementType val)
            {
                store->Set(i, tsIndex, val);
            }

            void Set(size_t i, const Type& val)
            {
                store->Set(i, val);
            }

            vector<ElementType*>* GetValues() const
            {
                return store->GetValues();
            }

            void CopyTo(ElementType ** dest) const
            {
                store->CopyTo(dest);
            }

            size_t Size() const
            {
                return store->Size();
            }

            size_t GetLength(size_t i) const
            {
                return store->GetLength(i);
            }

            ptime GetStartDate() const
            {
                return startDate;
            }

            void SetStartDate(const ptime& start)
            {
                startDate = start;
            }

            TimeStep GetTimeStep() const
            {
                return timeStep;
            }

            void Clear()
            {
                if (store == nullptr)
                    InitializeStorage();
                else
                    store->Clear();
            }

        private:

            ptime startDate;
            TimeStep timeStep;

            void DeepCopyFrom(const MultiTimeSeries<TsType>& src)
            {
                this->startDate = src.startDate;
                this->timeStep = src.timeStep;
                if (store == nullptr)
                    store = src.store->Clone();
                else
                    *(src.store) = (*(this->store));
            }

        protected:
            virtual void OperatorEqualImpl(const MultiTimeSeries<TsType>& src)
            {
                DeepCopyFrom(src);
            }
        };


        template<typename T>
        struct time_series_of
        {
            typedef TTimeSeries<T> type;
        };

        template<typename T>
        struct ensemble_of
        {
            typedef MultiTimeSeries<T> type;
        };

        template<typename T>
        struct item_type_of
        {
            typedef typename T::ElementType type;
        };

        template<typename ElementType=double>
        struct CommonTypes
        {
            using SeriesType = typename time_series_of<ElementType>::type;
            using PtrSeriesType = typename std::add_pointer<SeriesType>::type;

            using EnsembleType = typename ensemble_of<SeriesType>::type;
            using EnsemblePtrType = typename ensemble_of<PtrSeriesType>::type;
            using PtrEnsemblePtrType = typename std::add_pointer<EnsemblePtrType>::type;

            using TSeriesEnsemblePtrType = typename time_series_of<PtrEnsemblePtrType>::type;
            using PtrTSeriesEnsemblePtrType = typename std::add_pointer<TSeriesEnsemblePtrType>::type;
        };

        template <typename ItemType>
        using PointerTypeTimeSeries = TTimeSeries < ItemType* > ;

        template <typename Tts = TimeSeries>
        using MultiTimeSeriesPtr = MultiTimeSeries < Tts* > ;

        template <typename Tts = TimeSeries>
        using ForecastTimeSeries = PointerTypeTimeSeries < Tts >;

        template <typename Tts = TimeSeries>
        using TimeSeriesEnsemble = MultiTimeSeriesPtr < Tts >;

        template <typename Tts = TimeSeries>
        using EnsembleForecastTimeSeries = PointerTypeTimeSeries < MultiTimeSeriesPtr<Tts> > ;

        template <typename Tts = TimeSeries>
        class TimeSeriesOperations
        {
        public:

            using ElementType = typename Tts::ElementType;
            using SeriesType = typename CommonTypes<ElementType>::SeriesType;
            using PtrSeriesType = typename CommonTypes<ElementType>::PtrSeriesType;
            using EnsembleType = typename CommonTypes<ElementType>::EnsembleType;
            using EnsemblePtrType = typename CommonTypes<ElementType>::EnsemblePtrType;
            using PtrEnsemblePtrType = typename CommonTypes<ElementType>::PtrEnsemblePtrType;
            using TSeriesEnsemblePtrType = typename CommonTypes<ElementType>::TSeriesEnsemblePtrType;
            using PtrTSeriesEnsemblePtrType = typename CommonTypes<ElementType>::PtrTSeriesEnsemblePtrType;

            static PtrSeriesType TrimTimeSeries(const SeriesType& timeSeries, const ptime& startDate, const ptime& endDate)
            {
                ptime sd = timeSeries.GetStartDate();
                ptime ed = timeSeries.GetEndDate();

                size_t sIndex = timeSeries.UpperIndexForTime(startDate);
                size_t eIndex = timeSeries.LowerIndexForTime(endDate);

                return new SeriesType(timeSeries.Subset(sIndex, eIndex));
            }

            static PtrSeriesType Resample(const SeriesType& timeSeries, const string& method)
            {
                using namespace boost::algorithm;
                string m = method;
                to_lower(m);
                if (m == "")
                    return new TimeSeries(timeSeries);
                else if (m == "daily_to_hourly")
                    return DailyToHourly(timeSeries);
                else
                {
                    datatypes::exceptions::ExceptionUtilities::ThrowInvalidArgument("Time series resampling method not known: " + method);
                    return nullptr;
                }
            }

            static PtrSeriesType DailyToHourly(const SeriesType& dailyTimeSeries)
            {
                using T = typename SeriesType::ElementType;
                size_t length = dailyTimeSeries.GetLength();

                T * data = new T[length * 24];

                for (size_t i = 0; i < length; i++)
                    for (size_t j = 0; j < 24; j++)
                        data[(i * 24) + j] = (dailyTimeSeries[i] / 24);

                PtrSeriesType result = new SeriesType(data, length * 24, dailyTimeSeries.GetStartDate());

                delete[] data;

                return result;
            }

            static PtrSeriesType JoinTimeSeries(const SeriesType& head, const SeriesType& tail)
            {
                using T = typename SeriesType::ElementType;
                ptime startDate = head.GetStartDate();

                size_t headLength = head.GetLength();
                size_t tailLength = tail.GetLength();

                size_t length = headLength + tailLength;

                T * data = new T[length];

                for (size_t i = 0; i < length; i++)
                {
                    if (i < headLength)
                        data[i] = head[i];
                    else
                        data[i] = tail[i - headLength];
                }

                PtrSeriesType result = new SeriesType(data, length, startDate);

                delete[] data;

                return result;
            }

            static SeriesType AggregateTimeStep(const SeriesType& series, const string& argument) {

                using sec_type = boost::posix_time::time_duration::sec_type;
                auto newTimeStep = TimeStep::Parse(argument);
                sec_type deltaNew = newTimeStep.GetRegularStepDuration().total_seconds();
                sec_type delta = series.GetTimeStep().GetRegularStepDuration().total_seconds();
                if (deltaNew < delta)
                    throw std::logic_error("Cannot aggregate to a target time step finer than data source");
                sec_type remainder = deltaNew %delta;
                if (remainder != 0)
                    throw std::logic_error("Aggregate: target time step must be a multiple of the starting time step");
                sec_type multiple = deltaNew / delta;

                size_t targetSize = series.GetLength() / multiple;
                if (targetSize < 1)
                    throw std::logic_error("Aggregate: source lenght too short to aggregate");
                SeriesType newTs(0.0, targetSize, series.TimeForIndex(multiple-1), newTimeStep);

                for (size_t i = 0; i < targetSize; i++)
                {
                    double accumulated = 0;
                    for (size_t j = 0; j < multiple; j++)
                    {
                        auto val = series.GetValue(i * multiple + j);
                        if (series.IsMissingValue(val))
                        {
                            newTs.SetValue(i, newTs.GetMissingValue());
                            accumulated = .0;
                            break;
                        }
                        else
                            accumulated += val;
                    }
                    newTs.SetValue(i, accumulated);
                    accumulated = .0;
                }
                return newTs;
            }

            static SeriesType DisaggregateTimeStep(const SeriesType& series, const string& argument) {

                using sec_type = boost::posix_time::time_duration::sec_type;
                TimeStep newTimeStep = TimeStep::Parse(argument);
                sec_type deltaNew = newTimeStep.GetRegularStepDuration().total_seconds();
                sec_type delta = series.GetTimeStep().GetRegularStepDuration().total_seconds();
                if (deltaNew > delta)
                    throw std::logic_error("Cannot disaggregate to a target time step coarser than data source");
                sec_type remainder = delta % deltaNew;
                if (remainder != 0)
                    throw std::logic_error("Disaggregate: the starting time step must be a multiple of the target time step");

                sec_type multiple_sc = delta / deltaNew;
                // NOTE: I had changed from sec_type to size_t because of a compilation issue with gcc 8.3.0 (??), but this breaks the UT on calling AddSteps
                size_t multiple = (size_t)multiple_sc;

                size_t srcSize = series.GetLength();
                size_t targetSize = series.GetLength() * multiple;
                if (targetSize < 1)
                    throw std::logic_error("Disaggregate: source lengh is too short to aggregate");
                ptime newStartDate = newTimeStep.AddSteps(series.GetStartDate(), -( ((double)multiple) - 1));
                SeriesType newTs(0.0, targetSize, newStartDate, newTimeStep);

                for (size_t i = 0; i < srcSize; i++)
                {
                    auto val = series.GetValue(i);
                    for (size_t j = 0; j < multiple; j++)
                    {
                        if (series.IsMissingValue(val))
                            newTs.SetValue(i * multiple + j, newTs.GetMissingValue());
                        else
                            newTs.SetValue(i * multiple + j, val / multiple);
                    }
                }
                return newTs;
            }

            static void AggregateTimeStep(SeriesType& series, const string& argument) {
                series = AggregateTimeStep((const SeriesType&)series, argument);
            }

            static void DisaggregateTimeStep(SeriesType& series, const string& argument) {
                series = DisaggregateTimeStep((const SeriesType&)series, argument);
            }

            static void TransformEachItem(TSeriesEnsemblePtrType& efts, const string& method, const string& methodArgument)
            {
                if (method == string("accumulate"))
                {
                    auto n = efts.GetLength();
                    for (size_t i = 0; i < n; i++)
                    {
                        PtrEnsemblePtrType item = efts.GetValue(i);
                        EnsemblePtrType& y = *item;
                        for (size_t j = 0; j < y.Size(); j++)
                        {
                            PtrSeriesType t = y.Get(j);
                            AggregateTimeStep(*t, methodArgument);
                            if (j == 0) y.SetStartDate(t->GetStartDate());
                        }
                    }
                }
                else if (method == string("disaggregate"))
                {
                    auto n = efts.GetLength();
                    for (size_t i = 0; i < n; i++)
                    {
                        PtrEnsemblePtrType item = efts.GetValue(i);
                        EnsemblePtrType& y = *item;
                        for (size_t j = 0; j < y.Size(); j++)
                        {
                            PtrSeriesType t = y.Get(j);
                            DisaggregateTimeStep(*t, methodArgument);
                            if (j == 0) y.SetStartDate(t->GetStartDate());
                        }
                    }
                }
                else
                    throw std::logic_error("TransformEachItem is limited to 'accumulate' or 'disaggregate'");
            }

        private:
            static const PtrEnsemblePtrType& GetEnsemble(const TSeriesEnsemblePtrType& ensTs, size_t index = 0)
            {
                using datatypes::exceptions::ExceptionUtilities;
                if (ensTs.GetLength() == 0) ExceptionUtilities::ThrowInvalidArgument("ensemble time series is of length zero");
                ExceptionUtilities::CheckInRange<size_t>(index, 0, ensTs.GetLength(), "index");
                const PtrEnsemblePtrType& ens = ensTs.GetValue(index);
                //if (ens->Size() == 0) ExceptionUtilities::ThrowInvalidArgument("ensemble is of size zero");
                return ens;
            }
        public:

            static ptime GetStart(const TSeriesEnsemblePtrType& ensTs, size_t index = 0)
            {
                auto ens = GetEnsemble(ensTs, index);
                return ens->GetStartDate();
            }

            static ptime GetEnd(const TSeriesEnsemblePtrType& ensTs, size_t index = 0)
            {
                auto ens = GetEnsemble(ensTs, index);
                return ens->Get(0)->GetEndDate();
            }

            static void CreateForecast(TSeriesEnsemblePtrType& forecast, const SeriesType& observations, const ptime& start, size_t length, const TimeStep& issueTimeStep, size_t leadTime, size_t offsetForecasts)
            {
                forecast.Reset(length, start, issueTimeStep);
                TimeStep obsTstep = observations.GetTimeStep();
                for (size_t i = 0; i < length; i++)
                {
                    // Index in the observation time series corresponding to the forecast issue time 'i'
                    size_t obsIndex = observations.IndexForTime(issueTimeStep.AddSteps(start, i));
                    size_t from = obsIndex + offsetForecasts;
                    size_t to = from + (leadTime-1);
                    SeriesType fcast = observations.Subset(from, to);
                    EnsemblePtrType* mts = new EnsemblePtrType(fcast);
                    forecast.SetValue(i, mts);
                }
            }

            static TSeriesEnsemblePtrType* CreateForecastPtr(const SeriesType& observations, const ptime& start, size_t length, const TimeStep& issueTimeStep, size_t leadTime, size_t offsetForecasts)
            {
                TSeriesEnsemblePtrType* forecast = new TSeriesEnsemblePtrType(length, start, issueTimeStep);
                CreateForecast(*forecast, observations, start, length, issueTimeStep, leadTime, offsetForecasts);
                return forecast;
            }

            static TSeriesEnsemblePtrType CreateForecast(const SeriesType& observations, const ptime& start, size_t length, const TimeStep& issueTimeStep, size_t leadTime, size_t offsetForecasts)
            {
                TSeriesEnsemblePtrType forecast;
                CreateForecast(forecast, observations, start, length, issueTimeStep, leadTime, offsetForecasts);
                return forecast;
            }

            template<typename U>
            static bool AreEqual(const SeriesType& a, const U& b, bool strict = false, double tolerance = 1e-12)
            {
                size_t lengthA = a.GetLength();
                for (size_t i = 0; i < lengthA; i++)
                {
                    auto valA = a[i];

                    if (strict && (valA != b))
                        return false;
                    else if (std::abs(valA - b) > tolerance)
                        return false;
                }
                return true;
            }

            static bool AreTimeSeriesEqual(const SeriesType& a, const SeriesType& b, bool strict = false, double tolerance = 1e-12)
            {
                ptime startA = a.GetStartDate();
                ptime startB = b.GetStartDate();

                if (startA != startB)
                    return false;

                ptime endA = a.GetEndDate();
                ptime endB = b.GetEndDate();

                if (endA != endB)
                    return false;

                size_t lengthA = a.GetLength();
                size_t lengthB = b.GetLength();

                if (lengthA != lengthB)
                    return false;

                for (size_t i = 0; i < lengthA; i++)
                {
                    auto valA = a[i];
                    auto valB = b[i];

                    if (strict && (valA != valB))
                        return false;
                    else if (std::abs(valA - valB) > tolerance)
                        return false;
                }

                return true;
            }

            static bool AreTimeSeriesEqual(const SeriesType& a, const SeriesType& b, const ptime& from, const ptime& to, bool strict = false, double tolerance = 1e-12)
            {
                TimeStep ta = a.GetTimeStep();
                TimeStep tb = b.GetTimeStep();
                if (ta != tb)
                    return false;

                auto n = ta.GetNumSteps(from, to);
                if (n < 0)
                    return false;
                size_t s = (size_t)n;
                auto offsetA = a.IndexForTime(from);
                auto offsetB = b.IndexForTime(from);
                for (size_t i = 0; i < s; i++)
                {
                    auto valA = a[i + offsetA];
                    auto valB = b[i + offsetB];
                    if (strict && (valA != valB))
                        return false;
                    else if (std::abs(valA - valB) > tolerance)
                        return false;
                }

                return true;
            }

            static bool AreValueEqual(const SeriesType& a, const vector<ElementType>& b, size_t from = 0, size_t to = std::numeric_limits<size_t>::max(), bool strict = false, double tolerance = 1e-12)
            {
                vector<ElementType> v = a.GetValuesVector(from, to);
                if (v.size() != b.size())
                    return false;
                for (size_t i = 0; i < v.size(); i++)
                {
                    auto valA = v[i];
                    auto valB = b[i];
                    if (strict && (valA != valB))
                        return false;
                    else if (std::abs(valA - valB) > tolerance)
                        return false;
                }
                return true;
            }


            static bool AreEnsembleTimeSeriesEqual(EnsemblePtrType& a, EnsemblePtrType& b)
            {
                size_t lengthA = a.Size();
                size_t lengthB = b.Size();

                if (lengthA != lengthB)
                    return false;

                for (size_t i = 0; i < lengthA; i++)
                {
                    auto valA = a.Get(i);
                    auto valB = b.Get(i);

                    if (!AreTimeSeriesEqual(*valA, *valB))
                        return false;
                }
                return true;
            }

            static bool AreEnsembleTimeSeriesEqual(EnsembleType& a, EnsembleType& b)
            {
                size_t lengthA = a.Size();
                size_t lengthB = b.Size();

                if (lengthA != lengthB)
                    return false;

                for (size_t i = 0; i < lengthA; i++)
                {
                    auto valA = a.Get(i);
                    auto valB = b.Get(i);

                    if (!AreTimeSeriesEqual(valA, valB))
                        return false;
                }
                return true;
            }

            static bool AreTimeSeriesEnsembleTimeSeriesEqual(TSeriesEnsemblePtrType& a, TSeriesEnsemblePtrType& b)
            {
                size_t lengthA = a.GetLength();
                size_t lengthB = b.GetLength();

                if (lengthA != lengthB)
                    return false;

                ptime startA = a.GetStartDate();
                ptime startB = b.GetStartDate();

                if (startA != startB)
                    return false;

                for (size_t i = 0; i < lengthA; i++)
                {
                    auto valA = a.GetValue(i);
                    auto valB = b.GetValue(i);

                    if (!AreEnsembleTimeSeriesEqual(*valA, *valB))
                        return false;
                }
                return true;
            }

            template <typename T = typename Tts::ElementType>
            static PtrSeriesType MaskTimeSeries(const SeriesType& timeSeries, const ptime& start, const ptime& end, T maskValue)
            {
                if (start < timeSeries.GetStartDate())
                    datatypes::exceptions::ExceptionUtilities::ThrowInvalidArgument("start needs to be greater than time series start");

                if (end > timeSeries.GetEndDate())
                    datatypes::exceptions::ExceptionUtilities::ThrowInvalidArgument("end needs to be less than time series end");

                size_t sIndex = timeSeries.UpperIndexForTime(start);
                size_t eIndex = timeSeries.LowerIndexForTime(end);

                size_t len = timeSeries.GetLength();
                double* d = new double[len];
                timeSeries.CopyTo(d, 0, len - 1);

                for (size_t i = sIndex; i <= eIndex; i++)
                    d[i] = maskValue;

                PtrSeriesType result = new SeriesType(d, len, timeSeries.GetStartDate(), timeSeries.GetTimeStep());

                delete[] d;

                return result;
            }
        };

        template <typename T>
        TTimeSeries<T> operator<<(const TTimeSeries<T>& a, const TTimeSeries<T>& b) {
            using TsOps = TimeSeriesOperations<TTimeSeries<T>>;
            if (a.GetTimeStep() != b.GetTimeStep()) // TODO: this is a minimum requirement
                datatypes::exceptions::ExceptionUtilities::ThrowNotSupported("operator<< requires identical time steps");
            auto s = std::min(a.GetStartDate(), b.GetStartDate());
            auto e = std::max(a.GetEndDate(), b.GetEndDate());
            auto tstep = a.GetTimeStep();
            auto length = tstep.GetNumSteps(s, e);
            TTimeSeries<T> result(length, s, tstep);
            result.CopyValues(a);
            result.CopyValues(b);
            return result;
        }

        template <typename Tts = TimeSeries>
        class TimeWindow
        {
        public:
            TimeWindow(const ptime& startDate, const ptime& endDate)
            {
                this->startDate = startDate;
                this->endDate = endDate;
            }

            Tts* Trim(const Tts& timeSeries) const
            {
                return TimeSeriesOperations<Tts>::TrimTimeSeries(timeSeries, startDate, endDate);
            }

        private:
            ptime startDate;
            ptime endDate;

        };

        /*******************
        Below are implementations of the template code; they would normally be found in a .cpp file, but as
        templates putting them here makes it more reusable from other programs.
        ******************/
    }
    namespace exceptions
    {
        class DATATYPES_DLL_LIB TimeSeriesChecks
        {
        public:
            static void CheckOutOfRange(const string& msg, const datatypes::timeseries::TimeSeries& ts, const ptime& d);
        };
    }
}

Updated on 2022-08-21 at 18:10:33 +1000