Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

User's Guide

Series Containers
Series Initialization
Zeros and Sparse Data
Series Discretization and Strong Typing
Floating-point Offsets
Populating a Series
Series Algorithms
Arithmetic
Promotion
Conversions
Algorithms
adjacent_difference()
clip()
coarse_grain()
fine_grain()
integrate()
invert_elements()
invert_heaviside()
partial_sum()
period_sums()
piecewise_sample()
piecewise_surface_sample()
rotate_left()
rotate_right()
shift()
subscript()
variable_period_sums()
Extensibility
The TimeSeries Concept
Defining New TimeSeries algorithms
Defining a New TimeSeries Type

This section describes how to use the Boost.Time_series library to represent and manipulate series of data. For detailed information regarding specific components in Boost.Time_series, check the Reference section.

Hello, World!

Below is a complete example of how to use Boost.Time_series.

#include <iostream>
#include <boost/foreach.hpp>
#include <boost/time_series/dense_series.hpp>
#include <boost/time_series/sparse_series.hpp>
#include <boost/time_series/ordered_inserter.hpp>
#include <boost/time_series/numeric/numeric.hpp>
using namespace boost::time_series;

int main()
{
    // A dense series is like a std::vector
    dense_series< double > d( start=0, stop=4, value=2.2 );
    
    // A sparse series only stores its non-zeros
    sparse_series< double > s;
    
    // Filling a sparse_series (or any series, actually) can be done
    // with an ordered insertion, as follows:
    make_ordered_inserter( s )
        (4.0, 1)  // Insert value 4.0 at offset 1
        (6.0, 3)  // Insert value 6.0 at offset 3
    .commit();  // Commit the changes to sparse_series s.

    // Multiplication is done element-wise. Multiplying a sparse
    // series by a dense series yields a sparse series.
    sparse_series< double > r = d * s;
    
    // All series are infinite, but a clipped range is a valid
    // STL container, with iterators that make the storage look
    // dense; that is, they traverse both the zeros and the 
    // non-zeros, even if the storage is sparse.
    BOOST_FOREACH( double val, clip( r, 0, 4 ) )
    {
        std::cout << val << std::endl;
    }
    
    return 0;
}

This program displays the following:

0
8.8
0
13.2

Boost.Time_series offers the following series types for representing your data:

dense_series<>
A dense series, much like std::vector<>
sparse_series<>
A sparse series where runs have unit length.
piecewise_constant_series<>
A series which has an arbitrary number of runs of arbitrary length.
delta_series<>
A series which has exactly one run of unit length.
delta_unit_series<>
A delta series for which the run has a value of 1.
constant_series<>
A series which has exactly one run from -Inf to +Inf
characteristic_series<>
A series which has exactly one run.
characteristic_unit_series<>
A characteristic series for which the run has a value of 1.
heaviside_series<>
A series which has one run from some offset to +Inf.
heaviside_unit_series<>
A heaviside series for which the run has a value of 1.
inverse_heaviside_series<>
A series which has one run from -Inf to some end offset.
inverse_heaviside_unit_series<>
An inverse heaviside series for which the run has a value of 1.

In addition, Boost.Time_series provides the following series adaptors, which can be applied to any of the above series.

clipped_series<>
Makes the adapted series appear to have zeros outside some range.
scaled_series<>
Multiplies all the elements of the adapted series by some constant factor.
shifted_series<>
Shifts all the runs in a series by some constant offset.

Series Interface

All series types present the same interface, summarized below.

class series-type
{
public:
    // Public typedefs
    typedef discretization-type discretization_type;
    typedef value-type value_type;
    typedef offset-type offset_type;
    typedef zero-type zero_type;
    typedef unspecified reference;
    typedef reference const_reference;
    
    // Constructors
    series-type()
    series-type( A0 const &, A2 const &, ... );
    
    // Accessors
    reference operator [](offset_type) const;
    discretization_type discretization() const;
    void discretization(discretization_type disc);

    // Modifiers        
    void swap( series-type & );
    
    template< class Series >
    series-type & operator=(Series const &);
};

void swap( series-type &, series-type & );

In addition, all series satisfy the InfiniteRangeRunStorage concept. Since InfiniteRangeRunStorage is a refinement of the RangeRunStorage and Sequence concepts, all series model those concept also.

Different series types require different initialization. All the series types use named parameters for their constructor arguments. The allowable named parameters are:

start
The start offset of an element or range of elements.
stop
The end offset of a range of elements.
value
The value of an element or range of elements.
discretization
The "stride" of the series. This is used to specify whether a series represents daily or monthly data, for example.
zero
The value that should be assumed by the "zeros" of sparse storage.

Not all series will use all of these constructor parameters. The table below shows which series types accept which constructor parameters.

Table 1.1. Series Construction Parameters

Series Type Named Construction Parameters
characteristic_series<> start (default = 0), stop (default = 0), value (default = 1), discretization (default = 1), zero (default = value_type())
characteristic_unit_series<> start (default = 0), stop (default = 0), value (default = 1), discretization (default = 1), zero (default = value_type())
constant_series<> value (default = 1), discretization (default = 1), zero (default = value_type())
delta_series<> start (default = 0), value (default = 1), discretization (default = 1), zero (default = value_type())
delta_unit_series<> start (default = 0), value (default = 1), discretization (default = 1), zero (default = value_type())
dense_series<> start (default = 0), stop (default = 0), value (default = 1), discretization (default = 1), zero (default = value_type())
heaviside_series<> start (default = 0), value (default = 1), discretization (default = 1), zero (default = value_type())
heaviside_unit_series<> start (default = 0), value (default = 1), discretization (default = 1), zero (default = value_type())
inverse_heaviside_series<> stop (default = 0), value (default = 1), discretization (default = 1), zero (default = value_type())
inverse_heaviside_unit_series<> stop (default = 0), value (default = 1), discretization (default = 1), zero (default = value_type())
piecewise_constant_series<> discretization (default = 1), zero (default = value_type())
sparse_series<> discretization (default = 1), zero (default = value_type())

These constructor parameters can be used either positionally or with the named parameter syntax. When used positionally, the order the parameters appear in the table above determine how the library interprets the parameters in the constructor. For example, consider the following code:

// A delta series with a 42.0 at offset 512:
delta_series< double > delta1( start = 512, value = 42.0, discretization = 30 );

// Same as above
delta_series< double > delta2( 512, 42.0, 30 );
[Note] Note

It may seem odd that even the unit series have a value parameter. This is because 1 is not always convertible to the series' value type. Consider a delta_unit_series< std::vector< int > >. You may decide that for this series, the unit value should be std::vector< int >(3, 1). For unit series with scalar value types such as int, double and std::complex<>, the value parameter is ignored.

Every series type lets you specify the value of the zero elements. By default, the zeros are default-constructed objects of the series' value type. That is not always what you want. For example, consider the case of a sparse_series< std::vector< int > >. If all the non-zeros of the series are vectors of size 3, you probably want the zero elements to also be vectors of size 3, where the integers are all zero. The code below demonstrates how to accomlish that.

std::vector< int > nil( 3, 0 ); // vector of 3 zeros

// OK, use nil as the value of the zeros in the sparse series
sparse_series< std::vector< int > > s( zero = nil );

Positionally, the zero parameter always assumes the final position in the constructor argument list.

In the examples seen so far, the discretization is specified with a plain integer. Arithmetic operations between series is only defined when the series have the same discretization. When the discretization is a plain integer, the discretizations are checked for compatibility at runtime. However, you can get compile-time checking of the discretization if you use compile-time constants for the discretization, as follows:

dense_series< double, monthly > d;
sparse_series< double, monthly > s;

As you can see, there is an extra optional template parameter for specifying the Discretization. The Boost.Time_series library defines the following types for use as the Discretization template parameter:

Table 1.2. Time Series Resolutions

Discretization Equivalent To
daily mpl::int_<1>
weekly mpl::int_<7>
montly mpl::int_<30>
quarterly mpl::int_<90>
semi_yearly mpl::int_<180>
yearly mpl::int_<360>

Most of the series types allow you to use floating-point types instead of integers for offsets and indices. For instance, you might want a characteristic_series<> where the series is non-zero from -3.14 through 2.12. For that, you would specify the third template parameter of characteristic_series<> to be double. For example:

// A characteristic_series where the values are ints,
// the discretization is daily, and offsets are doubles
characteristic_series< int, daily, double > c( start = -3.14, stop = 2.12, value = 42 );

assert( 0 == c[ -4.11 ] );
assert( 42 == c[ 1.23 ] );

When doing series arithmetic with two series, the offset types of the two series must be the same.

[Note] Note

Some of the numeric algorithms do not work with series that have floating-point offsets. For instance, partial_sum() assumes integral offsets; in fact, the discrete nature of the algorithm prohibits its use with any series with floating-point offsets.

The only series type that does not support floating-point offsets is dense_series<>.

All series types provide a generic mechanism for populating the series with data. The basic idea to to ask the series for an inserter object, push data into the inserter in order, and then commit the changes. The inserter object is called ordered_inserter<>. Below is an example of using ordered_inserter<> to populate a sparse_series<>.

// Define a sparse_series
sparse_series< double > s;

// Make an inserter object for the series
ordered_inserter< sparse_series< double > > in( s );

in( 1.1, 2 );  // Insert value 1.1 at offset 2
in( 2.2, 4 );  // Insert value 2.2 at offset 4
in( 3.3, 8 );  // Insert value 3.3 at offset 8
in( 4.4, 16 ); // Insert value 4.4 at offset 16

// Commit the changes to series s
in.commit();

The ordered_inserter<> object has three overloads of operator(), described below.

// Define a piecewise_constant_series
piecewise_constant_series< double > pwc;

// Make an inserter object for the series
ordered_inserter< piecewise_constant_series< double > > in( pwc );

in( 1.1, 2, 6 );  // Insert value 1.1 at offsets 2 through 6
in( 2.2, 8 );     // Insert value 2.2 at offset 8
in( 3.3 );        // Insert value 3.3 at the next offset (9)

// Commit the changes to series pwc
in.commit();

All of the operator() overloads return a reference to *this, so the insertions can be chained. In addition, there is a make_ordered_inserter() helper function. These can be used together to simply and efficiently populate series:

piecewise_constant_series< double > pwc( 64 );

make_ordered_inserter( pwc )
    (1.1, 2, 6)  // Insert value 1.1 at offsets 2 through 6
    (2.2, 8)     // Insert value 2.2 at offset 8
    (3.3)        // Insert value 3.3 at the next offset (9)
.commit();

The ordered_inserter<> object can also behave like an output iterator. When used in this way, the following syntactic constructs are equivalent for a given ordered_inserter<> in.

Output Iterator Syntax Equivalent Ordered Inserter Syntax
*in++ = val; in( val );
*in++ = make_pair( val, off ); in( val, off );
*in++ = make_tuple( val, off ); in( val, off );
*in++ = make_tuple( val, off, end ); in( val, off, end );

These equivalencies are designed to permit the ordered_inserter<> to work well with zip_iterator<> from the Boost.Iterator library. For example, the following code inserts the specified values into the specified offsets in a sparse series.

sparse_series< double > s;

// Use boost::array to define the values and offsets
array< double,4 > values  = {1.1, 2.2, 3.3, 4.4};
array< int,4 >    offsets = {  2,   4,   8,  16};

// Use boost::zip_iterator to iterate over both the
// values and offsets in parallel.
std::copy(
    make_zip_iterator( make_tuple( values.begin(), offsets.begin() ) )
  , make_zip_iterator( make_tuple( values.end(), offsets.end() ) )
  , make_ordered_inserter( s )
).commit();

In the above example, make_ordered_inserter( s ) returns an inserter object for the sparse series s that is used as an output iterator by std::copy. The ordered inserter is assigned each (value, offset) tuple in turn. Finally, std::copy returns the inserter, and we call commit() on it, which writes all the (value, offset) tuples into the sparse series.

Copyright © 2006 Eric Niebler

PrevUpHomeNext