/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*                                                                           */
/*  This file is part of the library KASKADE 7                               */
/*    see http://www.zib.de/projects/kaskade7-finite-element-toolbox         */
/*                                                                           */
/*  Copyright (C) 2002-2011 Zuse Institute Berlin                            */
/*                                                                           */
/*  KASKADE 7 is distributed under the terms of the ZIB Academic License.    */
/*    see $KASKADE/academic.txt                                              */
/*                                                                           */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/**
 *
 * Use this file for lossy compression with adaptive grids. No temporal prediction/differential
 * encoding is performed.
 *
 * TODO: Implement temporal compression for adaptive grids.
 *
 */

#ifndef LOSSYSTORAGE_HH
#define LOSSYSTORAGE_HH

#include <limits>

#include "io/vtk.hh"
#include "io/rangecoder.hh"
#include "fem/mgtools.hh"

#include <boost/unordered_map.hpp>

bool abscompare( double a, double b ) { return fabs( a ) < fabs( b ) ; }
template< class T > bool greaterZero( T t ) { return t > 0 ; }

typedef unsigned long ULong ;

// Use policies for determining the quantization intervals
template <class VariableSetSens, class Grid, int D=2>
struct UniformQuantizationPolicy
{
   typedef Dune::FieldVector<double,D> Coor ;

   void update( typename VariableSetSens::VariableSet const&, GridManager<Grid> &  ){}

   void resetCount() {}

   long int getIntervals(double range, double aTol, Coor const& coor = Coor(0),
			 bool uniform = true, unsigned int vidx=0  )
   {
//      return (long int) ceil( range / aTol / 2.0 ) ;

//       symmetric mid-tread quantization : need odd number of intervals
      long int nIntervals = (long int) ceil( range / aTol / 2.0 ) ;
      if( nIntervals % 2 == 0 ) return nIntervals + 1 ;
      return nIntervals ;
   }
} ;

template <class Grid, class VariableSet, class Space, class QuantizationPolicy=UniformQuantizationPolicy<VariableSet,Grid> >
class LossyStorage
{
  public:
    static const int dim = Grid::dimension ;

    // some typedefs used throughout the class
    typedef Dune::FieldVector<double,1> StorageValueType;
    typedef boost::fusion::vector<Space const*> Spaces ;
    typedef boost::fusion::vector<VariableDescription<0,1,0> > VariableDescriptions;
    typedef VariableSetDescription<Spaces,VariableDescriptions> PredVariableSet;
    typedef typename Grid::template Codim<dim>::LevelIterator VertexLevelIterator ;
    typedef typename Grid::template Codim<dim>::LeafIterator  VertexLeafIterator ;
    typedef typename Grid::template Codim<dim>::LeafIndexSet IndexSet ;
    typedef typename Grid::LevelGridView LevelView ;

  public:

    LossyStorage( GridManager<Grid>& gridManager_, VariableSet const& varSet_, int coarseLevel_, double aTol_ , bool uniform_ = false,
		  QuantizationPolicy& quantizationPolicy_ = QuantizationPolicy()  )
      : gridManager(gridManager_), varSet(varSet_), coarseLevel(coarseLevel_), aTol(aTol_),
        count(0), firstCall(true), uniform(uniform_), order(1), quantizationPolicy(quantizationPolicy_)
    {
      mlTransfer = NULL ;
      ioOptions.outputType = IoOptions::ascii ;
    }

    ~LossyStorage()
    {
      if( mlTransfer != NULL ) delete mlTransfer ;
    }

    /**
     *  Encode a given state.
     *  Quantized values are written to a file specified by the member variables
     *  filePrefix and count, keeping track of the already encoded timesteps.
     */
    typename VariableSet::VariableSet encode( typename VariableSet::VariableSet const& sol, std::string fn )
    {
      fn += ".dat" ;

      // for adaptivity in space, mlTransfer has to be recomputed
      if( mlTransfer != NULL ) delete mlTransfer ;
      mlTransfer = new MultilevelTransfer<Space,Grid>( gridManager, order, coarseLevel ) ;

      // build map: vertex id - level the vertex is created
      unsigned long int ID ;
      levelInfo.clear();
      for( int level = 0 ; level <= gridManager.grid().maxLevel() ; level++ )
      {
	for ( VertexLevelIterator it = gridManager.grid().template lbegin <dim>( level );
	      it != gridManager.grid().template lend <dim>( level ); ++ it)
	{
	  ID = gridManager.grid().globalIdSet().id( *it ) ;
	  if( levelInfo.find(ID) != levelInfo.end() ) continue ;
	  levelInfo[ID] = level ;
	}
      }

      IndexSet const& indexSet = varSet.indexSet ;
      int maxLevel = gridManager.grid().maxLevel() ;

      // for some debug output
      std::ostringstream debugfn ;
      typename VariableSet::VariableSet predictionError(varSet) ;
      predictionError *= 0 ;

      // create variable set over hierarchic function space for multilevel-prediction
      LevelView gv = gridManager.grid().levelView( coarseLevel ) ;
      Space space( gridManager, gv, order ) ;
      Spaces spaces( &space ) ;
      std::string varNames[1] = { "pred" };
      PredVariableSet predVarSet( spaces, varNames ) ;

      typename PredVariableSet::VariableSet x( predVarSet ) ;
      x *= 0 ;

      // calculate quantization (for coarseLevel)
      std::vector<std::vector<ULong> > allIndices ;
      std::vector<ULong> indices ;
      std::vector<double> nodalValues ;
      for ( VertexLevelIterator it = gridManager.grid().template lbegin<dim>( coarseLevel ) ;
	  it != gridManager.grid().template lend<dim>( coarseLevel ); ++it )
      {
	nodalValues.push_back( -(*boost::fusion::at_c<0>(sol.data))[indexSet.index(*it)] ) ;
      }

      double ub = *std::max_element( nodalValues.begin(), nodalValues.end() ) ;
      double lb = *std::min_element( nodalValues.begin(), nodalValues.end() ) ;

      // symmetric quantization
      ub = *std::max_element( nodalValues.begin(), nodalValues.end(), abscompare ) ;
      if( ub < 0 ) { lb = ub ; ub = -lb  ; }
      else         { lb = -ub ; }
      // --

      double range = ub - lb ;

      // only needed if no pre-computed frequencies are used for range coding
      double maxBits ;
      if( range > 0 ) maxBits = (-ld(aTol) ) ;
      else            maxBits = 0 ;
      if( maxBits < 0 ) maxBits = 0 ;
      maxIntervals = (long int)ceil(pow( 2, maxBits ));

      std::ofstream out( fn.c_str(), std::ios::binary ) ;

      // write interval bounds for coarse level
      out.write(reinterpret_cast<char*>( &lb ), sizeof(double) ) ;
      out.write(reinterpret_cast<char*>( &ub ), sizeof(double) ) ;

      // for iterating over grid level, used throughout this method
      VertexLevelIterator itEnd = gridManager.grid().template lend<dim>( coarseLevel );

      unsigned long vertexCount = 0 ;
      if( uniform )
      {
	quantize( nodalValues, indices, coarseLevel, lb, ub ) ;

	// collect indices on each level in a common vector, and after finishing the
	// multilevel prediction, calculate occuring frequencies of interval numbers, and encode
	// using range coder
	allIndices.push_back( indices ) ;

	reconstruct( nodalValues, indices, coarseLevel, lb, ub ) ;
      }
      else
      {
	// perform quantization adaptively, possibly different for each node
	indices.clear() ;
	indices.resize(nodalValues.size(), 0) ;
	for ( VertexLevelIterator it = gridManager.grid().template lbegin<dim>( coarseLevel ) ;
	      it != itEnd; ++it )
	{
	  unsigned int vidx = gridManager.grid().leafIndexSet().index(*it) ;

	  indices[vertexCount] = quantize( nodalValues[vertexCount], lb, ub, it->geometry().corner(0),vidx );

	  nodalValues[vertexCount] = reconstruct( indices[vertexCount], lb, ub, it->geometry().corner(0),vidx);

	  vertexCount++ ;
	}
	allIndices.push_back( indices ) ;
      }

      // assign reconstructed coarse grid values of solution to hierarchic variable
      vertexCount = 0 ;
      for ( VertexLevelIterator it = gridManager.grid().template lbegin<dim>( coarseLevel );
	      it != itEnd ; ++it)
      {
	(*boost::fusion::at_c<0>(x.data))[gv.indexSet().index(*it)] -= nodalValues[vertexCount] ;
	vertexCount++ ;
      }

      typename VariableSet::VariableSet pred(varSet) ;
      for ( VertexLevelIterator it = gridManager.grid().template lbegin<dim>(coarseLevel);
	      it != itEnd; ++it)
	{
	  (*boost::fusion::at_c<0>(pred.data))[ indexSet.index(*it) ] = (*boost::fusion::at_c<0>(x.data))[ gv.indexSet().index(*it) ] ;
	}

      // now iterate over all grid levels
      for( int l = coarseLevel ; l < maxLevel ; l++ )
      {
	gv = gridManager.grid().levelView(l+1) ;
	space.setGridView(gv);

	*boost::fusion::at_c<0>(x.data) = *(mlTransfer->apply(l, *boost::fusion::at_c<0>(x.data))) ;

	nodalValues.clear() ;
	itEnd = gridManager.grid().template lend<dim>( l+1 );
	for ( VertexLevelIterator it = gridManager.grid().template lbegin<dim>( l+1 );
		it != itEnd;  ++it)
	{
	  double val = (*boost::fusion::at_c<0>(x.data))[gv.indexSet().index(*it)]
		      -(*boost::fusion::at_c<0>(sol.data))[indexSet.index(*it)] ;

	  nodalValues.push_back(val);
	}

	ub = *std::max_element( nodalValues.begin(), nodalValues.end() ) ;
	lb = *std::min_element( nodalValues.begin(), nodalValues.end() ) ;

	// symmetric mid tread quant
	ub = *std::max_element( nodalValues.begin(), nodalValues.end(), abscompare ) ;
	if( ub < 0 ) { lb = ub ; ub = -lb  ; }
	else         { lb = -ub ; }
	// --

	out.write(reinterpret_cast<char*>( &lb ), sizeof(double) ) ;
	out.write(reinterpret_cast<char*>( &ub ), sizeof(double) ) ;

	if( uniform )
	{
	  quantize( nodalValues, indices, l+1, lb, ub) ;
	  allIndices.push_back( indices ) ;
	  reconstruct( nodalValues, indices, l+1, lb, ub ) ;
	}
	else
	{
	  vertexCount = 0 ;
	  indices.clear() ; indices.resize(nodalValues.size(), 0);

	  for ( VertexLevelIterator it = gridManager.grid().template lbegin<dim>( l+1 ) ;
	    it != itEnd; ++it )
	  {
	    unsigned int vidx = gridManager.grid().leafIndexSet().index(*it) ;

	    indices[vertexCount] = quantize( nodalValues[vertexCount], lb, ub, it->geometry().corner(0), vidx );
	    nodalValues[vertexCount] = reconstruct( indices[vertexCount], lb, ub, it->geometry().corner(0), vidx );
	    vertexCount++ ;
	  }
	  allIndices.push_back( indices ) ;
	}

	// prepare prediction on next level -- use reconstructed values
	vertexCount = 0 ;
	unsigned long skipped = 0 ;
	for ( VertexLevelIterator it = gridManager.grid().template lbegin<dim>( l+1 );
		it != itEnd; ++it)
	{
	  // correction only for the nodes on level l+1
	  unsigned long ID = gridManager.grid().globalIdSet().id( *it ) ;
	  unsigned char vertexLevel = levelInfo[ID] ;
	  if( vertexLevel < l+1 ) { vertexCount++ ; skipped++ ; continue ; }

	  (*boost::fusion::at_c<0>(x.data))[gv.indexSet().index(*it)] -= nodalValues[vertexCount] ;
	  vertexCount++ ;
	}
// 	std::cout << "vertices on level " << l+1 << ": " << vertexCount - skipped << std::endl ;

	// now prediction to level l+1 is finished -- store in prediction variable set
	for ( VertexLevelIterator it = gridManager.grid().template lbegin<dim>(l+1);
	      it != itEnd; ++it)
	{
	  (*boost::fusion::at_c<0>(pred.data))[ indexSet.index(*it) ] = (*boost::fusion::at_c<0>(x.data))[ gv.indexSet().index(*it) ] ;
	}
      }

      // evaluation of quantization error
      pred -= sol ;
//       std::vector<double> foo( gridManager.grid().size(dim) ) ;
//       pred.write( foo.begin() ) ;
//       double absQuantErr = fabs(*std::max_element(foo.begin(),foo.end(),abscompare)) ;
//       sol.write( foo.begin() ) ;
//       double relQuantErr = absQuantErr / fabs(*std::max_element(foo.begin(),foo.end(),abscompare)) ;
//       std::cout << "Linf quant err: abs = " << absQuantErr << "\trel = " << relQuantErr << "\n" ;
//
//       L2Norm l2 ;
//       double l2q2 = l2.square( boost::fusion::at_c<0>(pred.vars) ) ;
//       double l2y2 = l2.square( boost::fusion::at_c<0>(sol.vars) ) ;
//       relQuantErr = sqrt(l2q2) / sqrt(l2y2) ;
//       absQuantErr = sqrt(l2q2) ;
//       std::cout << "  L2 quant err: abs = " << absQuantErr << "\trel = " << relQuantErr << "\n" ;

      // debug output
//       debugfn.str("") ; debugfn << "predErr" << count ; debugfn.flush() ;
//       writeVTKFile( gridManager.grid().leafView(), varSet, pred, debugfn.str(), ioOptions ) ;

      // Now allIndices contains the collected interval numbers of all levels.
      // In order to maximize efficiency of the range coder, precalculate the
      // frequency of each interval, befor encoding.
      typename VariableSet::VariableSet intervals(varSet) ; intervals *= 0 ;
      for( int l = maxLevel ; l >= coarseLevel ; l-- )
      {
	vertexCount = 0 ;
	itEnd = gridManager.grid().template lend<dim>( l );
	for ( VertexLevelIterator it = gridManager.grid().template lbegin<dim>( l );
		it != itEnd;  ++it)
	{
	  (*boost::fusion::at_c<0>(intervals.data))[indexSet.index(*it)] = allIndices[l-coarseLevel][vertexCount] ;
	  vertexCount++ ;
	}
      }

     count++ ;

      // and output the indices
      std::vector<long int> intervalIndicesTmp( gridManager.grid().size(dim) ) ;
      intervals.write( intervalIndicesTmp.begin() ) ;

      // shift indices, only positive indices for range encoding
      // TODO: could be skipped when not using differential encoding in time, indices are always >=0.
      long int minIndex = *std::min_element( intervalIndicesTmp.begin(), intervalIndicesTmp.end() ) ;
      std::vector<ULong> intervalIndices( gridManager.grid().size(dim) ) ;

      for( int i = 0 ; i < intervalIndicesTmp.size() ; i++ )
	intervalIndices[i] = intervalIndicesTmp[i] - minIndex ;

      out.write(reinterpret_cast<char*>( &minIndex ), sizeof(long int) ) ;

      long int maxIndex = *std::max_element( intervalIndices.begin(), intervalIndices.end() ) + 1 ;

      std::vector<ULong> frequencies( maxIndex, 0 ) ;
      for( int i = 0 ; i < intervalIndices.size() ; i++ ) frequencies[ intervalIndices[i] ]++ ;

      // only need to store symbols with nonzero frequency
      unsigned int nnz = (unsigned int) std::count_if( frequencies.begin(), frequencies.end(), greaterZero<ULong> ) ;

      std::vector<int> dictionary( frequencies.size(), -1 ) ;
      int cur_no = 0 ;
      for( int i = 0 ; i < frequencies.size() ; i++ )
      {
	if( frequencies[i] > 0 )
	{
	  dictionary[i] = cur_no ; cur_no++ ;
	}
      }
      out.write(reinterpret_cast<char*>( &nnz ), sizeof(unsigned int) ) ;
      for( int i = 0 ; i < dictionary.size() ; i++ )
      {
	if( dictionary[i] >= 0 ) out.write(reinterpret_cast<char*>( &i ), sizeof(int) ) ;
      }

      frequencies.erase( std::remove(frequencies.begin(), frequencies.end(), 0), frequencies.end() ) ;
      for( int i = 0 ; i < frequencies.size() ; i++ )
	out.write(reinterpret_cast<char*>( &frequencies[i] ), sizeof(ULong) ) ;

      Alphabet<ULong> alphabet( frequencies.begin(), frequencies.end() ) ;
      RangeEncoder<ULong> encoder( out ) ;

      for( int i = 0 ; i < intervalIndices.size(); i++ )
      {
	encodeSymbol( encoder, alphabet, dictionary[intervalIndices[i]] );
      }

      // encoding without using precomputed frequencies
//       int symbolCounter = 0 ;
//       std::vector<ULong> count(maxIntervals, 1) ;
//       Alphabet<ULong> alphabet( count.begin(), count.end() ) ;
//       for( int i = 0 ; i < intervalIndices.size(); i++ )
//       {
// 	encodeSymbol( encoder, alphabet, intervalIndices[i] );
// 	++count[intervalIndices[i]];
// 	++symbolCounter ;
// 	if (symbolCounter>2*alphabet.size())
// 	{
// 	  alphabet.update(count.begin(),count.end());
// 	  symbolCounter=0;
// 	}
//       }

      return pred ; // return quantization error
    }

    /** In the end, write remaining indices to disc. */
    void flush()  { } // not needed for version without temporal correlations

    void finish() { firstCall = true ; quantizationPolicy.resetCount() ; } ;

    /**
     *  Decode quantized values and store in a VariableSet::VariableSet.
     *  The file to be read is specified by the member variables
     *  filePrefix and count, keeping track of the already decoded timesteps.
     */
    void decode( GridManager<Grid>& gridManagerState, typename VariableSet::VariableSet& sol, std::string fn )
    {
      fn += ".dat" ;

      // for adaptivity in space, mlTransfer has to be recomputed
      if( mlTransfer != NULL ) delete mlTransfer ;
      mlTransfer = new MultilevelTransfer<Space,Grid>( gridManagerState, order, coarseLevel ) ;

      // build map: vertex id - level the vertex is created
      unsigned long int ID ;
      levelInfo.clear() ;
      for( int level = 0 ; level <= gridManagerState.grid().maxLevel() ; level++ )
      {
	for ( VertexLevelIterator it = gridManagerState.grid().template lbegin <dim>( level );
	      it != gridManagerState.grid().template lend <dim>( level ); ++ it)
	{
	  ID = gridManagerState.grid().globalIdSet().id( *it ) ;
	  if( levelInfo.find(ID) != levelInfo.end() ) continue ;
	  levelInfo[ID] = level ;
	}
      }

      // build hierarchic variable set -- could probably be moved to constructor
      LevelView gv = gridManagerState.grid().levelView( coarseLevel ) ;
      Space space( gridManagerState, gv, order ) ;
      Spaces spaces( &space ) ;
      std::string varNames[1] = { "pred" };
      PredVariableSet predVarSet( spaces, varNames ) ;
      typename PredVariableSet::VariableSet x( predVarSet ) ;
      x *= 0 ;

      // prepare prediction
      int maxLevel = gridManagerState.grid().maxLevel() ;
      IndexSet const& indexSet = gridManagerState.grid().leafIndexSet();

      std::vector<double> values ;

      std::vector<double> lb(maxLevel-coarseLevel+1), ub(maxLevel-coarseLevel+1) ;

      // read lb, ub from file
      std::ifstream in( fn.c_str(), std::ios::binary ) ;
      for( int i = 0 ; i <= maxLevel-coarseLevel ; i++ )
      {
	in.read( reinterpret_cast<char*>( &lb[i] ), sizeof(double) ) ;
	in.read( reinterpret_cast<char*>( &ub[i] ), sizeof(double) ) ;
      }

      // read in indices from file
      std::vector<long int> intervalIndices( gridManagerState.grid().size(dim) ) ;

      long int minIndex ;
      in.read(reinterpret_cast<char*>( &minIndex ), sizeof(long int) ) ;

      // # non-empty intervals
      unsigned int nnz ;
      in.read(reinterpret_cast<char*>( &nnz ), sizeof(unsigned int) ) ;

      // existing intervals (dictionary)
      std::vector<int> dictionary( nnz ) ;
      for( int i = 0 ; i < nnz ; i++ )
      {
	in.read(reinterpret_cast<char*>( &dictionary[i] ), sizeof(int) ) ;
      }
      std::vector<ULong> frequencies( nnz, 0 ) ;
      for( int i = 0 ; i < nnz ; i++ )
	in.read(reinterpret_cast<char*>( &frequencies[i] ), sizeof(ULong) ) ; // frequencies

      Alphabet<ULong> alphabet( frequencies.begin(), frequencies.end() ) ;
      try
      {
	RangeDecoder<ULong> decoder( in ) ;
	for( int i = 0 ; i < intervalIndices.size() ; i++ )
	{
	  ULong s = decodeSymbol(decoder,alphabet) ;
	  intervalIndices[i] = dictionary[s] + minIndex ;
	}
      }
      catch( std::ios_base::failure& e ) { std::cout << e.what() << " Decoding error\n" ; }

      // version without using precomputed frequencies
//       int symbolCounter = 0 ;                 // 2010-02-19
//       std::vector<ULong> symcount(maxIntervals, 1) ; // 2010-02-19
//       Alphabet<ULong> alphabet( symcount.begin(), symcount.end() ) ;//2010-02-19
//       try
//       {
// 	RangeDecoder<ULong> decoder( in ) ;
// 	for( int i = 0 ; i < intervalIndices.size() ; i++ )
// 	{
// 	  ULong s = decodeSymbol(decoder,alphabet) ;
// 	  intervalIndices[i] = s + minIndex ;
// 	  ++symcount[s];
// 	  ++symbolCounter ;
// 	  if (symbolCounter>2*alphabet.size())
// 	  {
// 	    alphabet.update(symcount.begin(),symcount.end());
// 	    symbolCounter=0;
// 	  }
// 	}
//       }
//       catch( std::ios_base::failure& e ) { std::cout << e.what() << " Decoding error\n" ; }

      // assign corrections to variableSet
      typename VariableSet::VariableSet corr(sol) ; corr *= 0 ;
      corr.read( intervalIndices.begin() ) ;

      // assign the overall vector to the single levels
      std::vector<std::vector<ULong> > allIndices( maxLevel+1-coarseLevel ) ;
      for( int l = coarseLevel ; l <= maxLevel ; l++ )
      {
	for ( VertexLevelIterator it = gridManagerState.grid().template lbegin<dim>( l );
	      it != gridManagerState.grid().template lend<dim>( l ); ++it)
	{
	  allIndices[l-coarseLevel].push_back( (*boost::fusion::at_c<0>(corr.data))[indexSet.index(*it)] ) ;
	}
      }

      // start reconstruction
      VertexLevelIterator itEnd = gridManagerState.grid().template lend<dim>( coarseLevel );

      int vertexCount = 0;
      if( uniform )
      {
	reconstruct( values, allIndices[0], coarseLevel, lb[0], ub[0] );
      }
      else
      {
	values.clear()  ; values.resize( allIndices[0].size() ) ;
	for ( VertexLevelIterator it = gridManagerState.grid().template lbegin<dim>( coarseLevel ) ;
	  it != itEnd; ++it )
	{
	  unsigned int vidx = gridManagerState.grid().leafIndexSet().index(*it) ;

	  values[vertexCount] = reconstruct( allIndices[0][vertexCount], lb[0], ub[0], it->geometry().corner(0),vidx );
	  vertexCount++ ;
	}
      }

      vertexCount = 0 ;
      for ( VertexLevelIterator it = gridManagerState.grid().template lbegin<dim>( coarseLevel );
	      it != itEnd; ++it)
      {
	(*boost::fusion::at_c<0>(x.data))[gv.indexSet().index(*it)] = -values[vertexCount] ;
	vertexCount++ ;
      }

      // store predicted values for the coarse grid in solution vector
      for ( VertexLevelIterator it = gridManagerState.grid().template lbegin<dim>( coarseLevel );
	      it != itEnd; ++it)
      {
	(*boost::fusion::at_c<0>(sol.data))[ indexSet.index(*it) ] = (*boost::fusion::at_c<0>(x.data))[ gv.indexSet().index(*it) ] ;
	vertexCount++ ;
      }

      // perform prediction and correction
      for( int l = coarseLevel ; l < maxLevel ; l++ )
      {
	itEnd = gridManagerState.grid().template lend<dim>( l+1 );

	gv = gridManagerState.grid().levelView(l+1) ;
	space.setGridView(gv);

	*boost::fusion::at_c<0>(x.data) = *(mlTransfer->apply(l, *boost::fusion::at_c<0>(x.data))) ;

	if( uniform )
	{
	  reconstruct( values, allIndices[l-coarseLevel+1], l+1, lb[l+1-coarseLevel], ub[l+1-coarseLevel] );
	}
	else
	{
	  vertexCount = 0;
	  values.clear();
	  values.resize( allIndices[l-coarseLevel+1].size() ) ;

	  for ( VertexLevelIterator it = gridManagerState.grid().template lbegin<dim>( l+1 ) ;
	    it != itEnd; ++it )
	  {
	    unsigned int vidx = gridManagerState.grid().leafIndexSet().index(*it) ;

	    values[vertexCount] = reconstruct( allIndices[l-coarseLevel+1][vertexCount], lb[l-coarseLevel+1],
	    ub[l-coarseLevel+1], it->geometry().corner(0), vidx );
	    vertexCount++ ;
	  }
	}

	vertexCount = 0 ;
	for ( VertexLevelIterator it = gridManagerState.grid().template lbegin<dim>( l+1 );
		it != itEnd; ++it)
	{
	  //correct only vertices new on level l+1
	  if( levelInfo[gridManagerState.grid().globalIdSet().id(*it)] == l+1 )
	  {
	    (*boost::fusion::at_c<0>(x.data))[gv.indexSet().index(*it)] -= values[vertexCount] ;
	  }
	  vertexCount++ ;
	}

	// store predicted values for the coarse grid in solution vector
	for ( VertexLevelIterator it = gridManagerState.grid().template lbegin<dim>( l+1 );
	      it != itEnd; ++it)
	{
	  (*boost::fusion::at_c<0>(sol.data))[ indexSet.index(*it) ] = (*boost::fusion::at_c<0>(x.data))[ gv.indexSet().index(*it) ] ;
	  vertexCount++ ;
	}
      }

      count-- ;

      // debug output
//       std::ostringstream debugfn ;
//       debugfn << "reconstruction" << count ; debugfn.flush() ;
//       writeVTKFile( varSet, sol, debugfn.str(), ioOptions ) ;

      in.close() ;
    }

  private:
    LossyStorage( LossyStorage const& );
    LossyStorage& operator=( LossyStorage const& ) ;

    /** Helper method to perform the actual quantization for a whole level. */
    void quantize( std::vector<double> const& values, std::vector<ULong>& indices, int level, double lb, double ub )
    {
      double range = ub - lb ;

      long int nIntervals = quantizationPolicy.getIntervals( range, aTol );

      // needed for range encoding without precomputed frequencies
      if( nIntervals > maxIntervals ) maxIntervals = nIntervals ;

      // debug output
//       std::cout << nIntervals << "="
// 		<< ( (nIntervals > 0) ? (int(ld(nIntervals)*100))/100.0 : 0 ) << " / " ;

//      std::cout << "  Level:  " << level << "  Intervals: " << nIntervals
//                 << "   Bits:  "  << ( (nIntervals > 0) ? (int(ld(nIntervals)*100))/100.0 : 0 )
// 		<< "\n" ;

      indices.clear() ;
      indices.resize( values.size(), 0 ) ;
      if( range > 0 ) // if range is empty, indices[i]=0 for all values
      {
	ULong idx ;
	for( int i = 0 ; i < values.size() ; i++ )
	{
	  idx = (ULong)( (values[i] - lb) * nIntervals / range ) + 1 ;
	  indices[i] = idx ;
	}
      }
    }

    /** Helper method to perform the actual quantization for a single node. */
    ULong quantize( double value, double lb, double ub, Dune::FieldVector<double,dim> const& coor,
		    bool uniform = false, unsigned int vidx = 0 )
    {
      double range = ub - lb ;

      if( range == 0 ) return 0 ;

      long int nIntervals = quantizationPolicy.getIntervals( range, aTol, coor, uniform, vidx ) ;

      // needed for range encoding without precomputed frequencies
      if( nIntervals > maxIntervals ) maxIntervals = nIntervals ;

      return (ULong)( (value - lb) * nIntervals / range ) + 1 ;
    }

    /** Helper method to perform the actual reconstruction of quantized values. */
    void reconstruct( std::vector<double>& values, std::vector<ULong> const& indices, int level, double lb, double ub )
    {
      double range = ub - lb ;

      long int nIntervals = quantizationPolicy.getIntervals( range, aTol );

      double delta ;
      if( nIntervals <= 0 ) delta = 0 ;
      else                  delta = range / nIntervals ;

      values.clear() ;
      values.resize( indices.size() ) ;
      for( int i = 0 ; i < indices.size() ; i++ )
      {
	if( indices[i] == 0 ) values[i] = lb ;
	else	              values[i] = lb + (2*indices[i]-1) * delta/2 ;
      }
    }

    /** Helper method to perform the actual reconstruction for a single node. */
    double reconstruct( ULong index, double lb, double ub, Dune::FieldVector<double,dim> const& coor,
			bool uniform = false, unsigned int vidx = 0 )
    {
      double range = ub - lb ;
      long int nIntervals = quantizationPolicy.getIntervals( range, aTol, coor, uniform, vidx ) ;
      double delta ;
      if( nIntervals <= 0 ) delta = 0 ;
      else                  delta = range / nIntervals ;
      if( index == 0 ) return lb ;
      else             return lb + (2*index-1) * delta/2 ;
    }

    /** Helper method returning the base 2-logarithm. */
    double ld( double val ) { return log(val)/log(2.0) ; }

    void setTolerance( double tol ) { aTol = tol ; }

  private:
    GridManager<Grid>& gridManager ;
    VariableSet varSet ;
    MultilevelTransfer<Space,Grid>* mlTransfer ;

    int coarseLevel ;
    double aTol ;
    std::string filePrefix ;

    int count ; // count timesteps
    IoOptions ioOptions;

    bool firstCall ;
    long int maxIntervals ;

    bool uniform ;
    boost::unordered_map<unsigned long int, unsigned char> levelInfo ;

    int order; // degree of interpolation polynomials; currently only linear interpolation is supported

  public:
    QuantizationPolicy quantizationPolicy ;

} ;

#endif


//  (C) Copyright John Maddock 2006.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_MATH_TOOLS_NEWTON_SOLVER_HPP
#define BOOST_MATH_TOOLS_NEWTON_SOLVER_HPP

#ifdef _MSC_VER
#pragma once
#endif

#include <utility>
#include <boost/config/no_tr1/cmath.hpp>
#include <stdexcept>

#include <boost/math/tools/config.hpp>
#include <boost/cstdint.hpp>
#include <boost/assert.hpp>
#include <boost/throw_exception.hpp>

#ifdef BOOST_MSVC
#pragma warning(push)
#pragma warning(disable: 4512)
#endif
#include <boost/math/tools/tuple.hpp>
#ifdef BOOST_MSVC
#pragma warning(pop)
#endif

#include <boost/math/special_functions/sign.hpp>
#include <boost/math/tools/toms748_solve.hpp>
#include <boost/math/policies/error_handling.hpp>

namespace boost{ namespace math{ namespace tools{

namespace detail{

template <class Tuple, class T>
inline void unpack_0(const Tuple& t, T& val)
{ val = boost::math::get<0>(t); }

template <class F, class T>
void handle_zero_derivative(F f,
                            T& last_f0,
                            const T& f0,
                            T& delta,
                            T& result,
                            T& guess,
                            const T& min,
                            const T& max)
{
   if(last_f0 == 0)
   {
      // this must be the first iteration, pretend that we had a
      // previous one at either min or max:
      if(result == min)
      {
         guess = max;
      }
      else
      {
         guess = min;
      }
      unpack_0(f(guess), last_f0);
      delta = guess - result;
   }
   if(sign(last_f0) * sign(f0) < 0)
   {
      // we've crossed over so move in opposite direction to last step:
      if(delta < 0)
      {
         delta = (result - min) / 2;
      }
      else
      {
         delta = (result - max) / 2;
      }
   }
   else
   {
      // move in same direction as last step:
      if(delta < 0)
      {
         delta = (result - max) / 2;
      }
      else
      {
         delta = (result - min) / 2;
      }
   }
}

} // namespace

template <class F, class T, class Tol, class Policy>
std::pair<T, T> bisect(F f, T min, T max, Tol tol, boost::uintmax_t& max_iter, const Policy& pol)
{
   T fmin = f(min);
   T fmax = f(max);
   if(fmin == 0)
      return std::make_pair(min, min);
   if(fmax == 0)
      return std::make_pair(max, max);

   //
   // Error checking:
   //
   static const char* function = "boost::math::tools::bisect<%1%>";
   if(min >= max)
   {
      return boost::math::detail::pair_from_single(policies::raise_evaluation_error(function,
         "Arguments in wrong order in boost::math::tools::bisect (first arg=%1%)", min, pol));
   }
   if(fmin * fmax >= 0)
   {
      return boost::math::detail::pair_from_single(policies::raise_evaluation_error(function,
         "No change of sign in boost::math::tools::bisect, either there is no root to find, or there are multiple roots in the interval (f(min) = %1%).", fmin, pol));
   }

   //
   // Three function invocations so far:
   //
   boost::uintmax_t count = max_iter;
   if(count < 3)
      count = 0;
   else
      count -= 3;

   while(count && (0 == tol(min, max)))
   {
      T mid = (min + max) / 2;
      T fmid = f(mid);
      if((mid == max) || (mid == min))
         break;
      if(fmid == 0)
      {
         min = max = mid;
         break;
      }
      else if(sign(fmid) * sign(fmin) < 0)
      {
         max = mid;
         fmax = fmid;
      }
      else
      {
         min = mid;
         fmin = fmid;
      }
      --count;
   }

   max_iter -= count;

#ifdef BOOST_MATH_INSTRUMENT
   std::cout << "Bisection iteration, final count = " << max_iter << std::endl;

   static boost::uintmax_t max_count = 0;
   if(max_iter > max_count)
   {
      max_count = max_iter;
      std::cout << "Maximum iterations: " << max_iter << std::endl;
   }
#endif

   return std::make_pair(min, max);
}

template <class F, class T, class Tol>
inline std::pair<T, T> bisect(F f, T min, T max, Tol tol, boost::uintmax_t& max_iter)
{
   return bisect(f, min, max, tol, max_iter, policies::policy<>());
}

template <class F, class T, class Tol>
inline std::pair<T, T> bisect(F f, T min, T max, Tol tol)
{
   boost::uintmax_t m = (std::numeric_limits<boost::uintmax_t>::max)();
   return bisect(f, min, max, tol, m, policies::policy<>());
}

template <class F, class T>
T newton_raphson_iterate(F f, T guess, T min, T max, int digits, boost::uintmax_t& max_iter)
{
   BOOST_MATH_STD_USING

   T f0(0), f1, last_f0(0);
   T result = guess;

   T factor = static_cast<T>(ldexp(1.0, 1 - digits));
   T delta = 1;
   T delta1 = tools::max_value<T>();
   T delta2 = tools::max_value<T>();

   boost::uintmax_t count(max_iter);

   do{
      last_f0 = f0;
      delta2 = delta1;
      delta1 = delta;
      boost::math::tie(f0, f1) = f(result);
      if(0 == f0)
         break;
      if(f1 == 0)
      {
         // Oops zero derivative!!!
#ifdef BOOST_MATH_INSTRUMENT
         std::cout << "Newton iteration, zero derivative found" << std::endl;
#endif
         detail::handle_zero_derivative(f, last_f0, f0, delta, result, guess, min, max);
      }
      else
      {
         delta = f0 / f1;
      }
#ifdef BOOST_MATH_INSTRUMENT
      std::cout << "Newton iteration, delta = " << delta << std::endl;
#endif
      if(fabs(delta * 2) > fabs(delta2))
      {
         // last two steps haven't converged, try bisection:
         delta = (delta > 0) ? (result - min) / 2 : (result - max) / 2;
      }
      guess = result;
      result -= delta;
      if(result <= min)
      {
         delta = 0.5F * (guess - min);
         result = guess - delta;
         if((result == min) || (result == max))
            break;
      }
      else if(result >= max)
      {
         delta = 0.5F * (guess - max);
         result = guess - delta;
         if((result == min) || (result == max))
            break;
      }
      // update brackets:
      if(delta > 0)
         max = guess;
      else
         min = guess;
   }while(--count && (fabs(result * factor) < fabs(delta)));

   max_iter -= count;

#ifdef BOOST_MATH_INSTRUMENT
   std::cout << "Newton Raphson iteration, final count = " << max_iter << std::endl;

   static boost::uintmax_t max_count = 0;
   if(max_iter > max_count)
   {
      max_count = max_iter;
      std::cout << "Maximum iterations: " << max_iter << std::endl;
   }
#endif

   return result;
}

template <class F, class T>
inline T newton_raphson_iterate(F f, T guess, T min, T max, int digits)
{
   boost::uintmax_t m = (std::numeric_limits<boost::uintmax_t>::max)();
   return newton_raphson_iterate(f, guess, min, max, digits, m);
}

template <class F, class T>
T halley_iterate(F f, T guess, T min, T max, int digits, boost::uintmax_t& max_iter)
{
   BOOST_MATH_STD_USING

   T f0(0), f1, f2;
   T result = guess;

   T factor = static_cast<T>(ldexp(1.0, 1 - digits));
   T delta = (std::max)(T(10000000 * guess), T(10000000));  // arbitarily large delta
   T last_f0 = 0;
   T delta1 = delta;
   T delta2 = delta;

   bool out_of_bounds_sentry = false;

#ifdef BOOST_MATH_INSTRUMENT
   std::cout << "Halley iteration, limit = " << factor << std::endl;
#endif

   boost::uintmax_t count(max_iter);

   do{
      last_f0 = f0;
      delta2 = delta1;
      delta1 = delta;
      boost::math::tie(f0, f1, f2) = f(result);

      BOOST_MATH_INSTRUMENT_VARIABLE(f0);
      BOOST_MATH_INSTRUMENT_VARIABLE(f1);
      BOOST_MATH_INSTRUMENT_VARIABLE(f2);

      if(0 == f0)
         break;
      if(f1 == 0)
      {
         // Oops zero derivative!!!
#ifdef BOOST_MATH_INSTRUMENT
         std::cout << "Halley iteration, zero derivative found" << std::endl;
#endif
         detail::handle_zero_derivative(f, last_f0, f0, delta, result, guess, min, max);
      }
      else
      {
         if(f2 != 0)
         {
            T denom = 2 * f0;
            T num = 2 * f1 - f0 * (f2 / f1);

            BOOST_MATH_INSTRUMENT_VARIABLE(denom);
            BOOST_MATH_INSTRUMENT_VARIABLE(num);

            if((fabs(num) < 1) && (fabs(denom) >= fabs(num) * tools::max_value<T>()))
            {
               // possible overflow, use Newton step:
               delta = f0 / f1;
            }
            else
               delta = denom / num;
            if(delta * f1 / f0 < 0)
            {
               // Oh dear, we have a problem as Newton and Halley steps
               // disagree about which way we should move.  Probably
               // there is cancelation error in the calculation of the
               // Halley step, or else the derivatives are so small
               // that their values are basically trash.  We will move
               // in the direction indicated by a Newton step, but
               // by no more than twice the current guess value, otherwise
               // we can jump way out of bounds if we're not careful.
               // See https://svn.boost.org/trac/boost/ticket/8314.
               delta = f0 / f1;
               if(fabs(delta) > 2 * fabs(guess))
                  delta = (delta < 0 ? -1 : 1) * 2 * fabs(guess);
            }
         }
         else
            delta = f0 / f1;
      }
#ifdef BOOST_MATH_INSTRUMENT
      std::cout << "Halley iteration, delta = " << delta << std::endl;
#endif
      T convergence = fabs(delta / delta2);
      if((convergence > 0.8) && (convergence < 2))
      {
         // last two steps haven't converged, try bisection:
         delta = (delta > 0) ? (result - min) / 2 : (result - max) / 2;
         if(fabs(delta) > result)
            delta = sign(delta) * result; // protect against huge jumps!
         // reset delta2 so that this branch will *not* be taken on the
         // next iteration:
         delta2 = delta * 3;
         BOOST_MATH_INSTRUMENT_VARIABLE(delta);
      }
      guess = result;
      result -= delta;
      BOOST_MATH_INSTRUMENT_VARIABLE(result);

      // check for out of bounds step:
      if(result < min)
      {
         T diff = ((fabs(min) < 1) && (fabs(result) > 1) && (tools::max_value<T>() / fabs(result) < fabs(min))) ? T(1000)  : T(result / min);
         if(fabs(diff) < 1)
            diff = 1 / diff;
         if(!out_of_bounds_sentry && (diff > 0) && (diff < 3))
         {
            // Only a small out of bounds step, lets assume that the result
            // is probably approximately at min:
            delta = 0.99f * (guess  - min);
            result = guess - delta;
            out_of_bounds_sentry = true; // only take this branch once!
         }
         else
         {
            delta = (guess - min) / 2;
            result = guess - delta;
            if((result == min) || (result == max))
               break;
         }
      }
      else if(result > max)
      {
         T diff = ((fabs(max) < 1) && (fabs(result) > 1) && (tools::max_value<T>() / fabs(result) < fabs(max))) ? T(1000) : T(result / max);
         if(fabs(diff) < 1)
            diff = 1 / diff;
         if(!out_of_bounds_sentry && (diff > 0) && (diff < 3))
         {
            // Only a small out of bounds step, lets assume that the result
            // is probably approximately at min:
            delta = 0.99f * (guess  - max);
            result = guess - delta;
            out_of_bounds_sentry = true; // only take this branch once!
         }
         else
         {
            delta = (guess - max) / 2;
            result = guess - delta;
            if((result == min) || (result == max))
               break;
         }
      }
      // update brackets:
      if(delta > 0)
         max = guess;
      else
         min = guess;
   }while(--count && (fabs(result * factor) < fabs(delta)));

   max_iter -= count;

#ifdef BOOST_MATH_INSTRUMENT
   std::cout << "Halley iteration, final count = " << max_iter << std::endl;
#endif

   return result;
}

template <class F, class T>
inline T halley_iterate(F f, T guess, T min, T max, int digits)
{
   boost::uintmax_t m = (std::numeric_limits<boost::uintmax_t>::max)();
   return halley_iterate(f, guess, min, max, digits, m);
}

template <class F, class T>
T schroeder_iterate(F f, T guess, T min, T max, int digits, boost::uintmax_t& max_iter)
{
   BOOST_MATH_STD_USING

   T f0(0), f1, f2, last_f0(0);
   T result = guess;

   T factor = static_cast<T>(ldexp(1.0, 1 - digits));
   T delta = 0;
   T delta1 = tools::max_value<T>();
   T delta2 = tools::max_value<T>();

#ifdef BOOST_MATH_INSTRUMENT
   std::cout << "Schroeder iteration, limit = " << factor << std::endl;
#endif

   boost::uintmax_t count(max_iter);

   do{
      last_f0 = f0;
      delta2 = delta1;
      delta1 = delta;
      boost::math::tie(f0, f1, f2) = f(result);
      if(0 == f0)
         break;
      if((f1 == 0) && (f2 == 0))
      {
         // Oops zero derivative!!!
#ifdef BOOST_MATH_INSTRUMENT
         std::cout << "Halley iteration, zero derivative found" << std::endl;
#endif
         detail::handle_zero_derivative(f, last_f0, f0, delta, result, guess, min, max);
      }
      else
      {
         T ratio = f0 / f1;
         if(ratio / result < 0.1)
         {
            delta = ratio + (f2 / (2 * f1)) * ratio * ratio;
            // check second derivative doesn't over compensate:
            if(delta * ratio < 0)
               delta = ratio;
         }
         else
            delta = ratio;  // fall back to Newton iteration.
      }
      if(fabs(delta * 2) > fabs(delta2))
      {
         // last two steps haven't converged, try bisection:
         delta = (delta > 0) ? (result - min) / 2 : (result - max) / 2;
      }
      guess = result;
      result -= delta;
#ifdef BOOST_MATH_INSTRUMENT
      std::cout << "Halley iteration, delta = " << delta << std::endl;
#endif
      if(result <= min)
      {
         delta = 0.5F * (guess - min);
         result = guess - delta;
         if((result == min) || (result == max))
            break;
      }
      else if(result >= max)
      {
         delta = 0.5F * (guess - max);
         result = guess - delta;
         if((result == min) || (result == max))
            break;
      }
      // update brackets:
      if(delta > 0)
         max = guess;
      else
         min = guess;
   }while(--count && (fabs(result * factor) < fabs(delta)));

   max_iter -= count;

#ifdef BOOST_MATH_INSTRUMENT
   std::cout << "Schroeder iteration, final count = " << max_iter << std::endl;

   static boost::uintmax_t max_count = 0;
   if(max_iter > max_count)
   {
      max_count = max_iter;
      std::cout << "Maximum iterations: " << max_iter << std::endl;
   }
#endif

   return result;
}

template <class F, class T>
inline T schroeder_iterate(F f, T guess, T min, T max, int digits)
{
   boost::uintmax_t m = (std::numeric_limits<boost::uintmax_t>::max)();
   return schroeder_iterate(f, guess, min, max, digits, m);
}

} // namespace tools
} // namespace math
} // namespace boost

#endif // BOOST_MATH_TOOLS_NEWTON_SOLVER_HPP


/*    Copyright (c) 2010-2015, Delft University of Technology
 *    All rights reserved.
 *
 *    Redistribution and use in source and binary forms, with or without modification, are
 *    permitted provided that the following conditions are met:
 *      - Redistributions of source code must retain the above copyright notice, this list of
 *        conditions and the following disclaimer.
 *      - Redistributions in binary form must reproduce the above copyright notice, this list of
 *        conditions and the following disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *      - Neither the name of the Delft University of Technology nor the names of its contributors
 *        may be used to endorse or promote products derived from this software without specific
 *        prior written permission.
 *
 *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 *    OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *    MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *    COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 *    GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 *    AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 *    OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *    Changelog
 *      YYMMDD    Author            Comment
 *      130218    D. Dirkx          File created from personal application.
 *
 *    References
 *
 *    Notes
 *
 */

#include <boost/date_time/gregorian/gregorian.hpp>

#include "Tudat/Astrodynamics/BasicAstrodynamics/physicalConstants.h"

#include "Tudat/Astrodynamics/BasicAstrodynamics/timeConversions.h"

namespace tudat
{
namespace basic_astrodynamics
{

//! Compute number of seconds since a reference Julian day.
double convertJulianDayToSecondsSinceEpoch( const double julianDay,
                                            const double epochSinceJulianDayZero )
{
    return ( julianDay - epochSinceJulianDayZero ) * physical_constants::JULIAN_DAY;
}

//! Compute Julian day from seconds since reference Julian day epoch.
double convertSecondsSinceEpochToJulianDay( const double secondsSinceEpoch,
                                            const double epochSinceJulianDayZero )
{
    return ( secondsSinceEpoch / physical_constants::JULIAN_DAY + epochSinceJulianDayZero );
}

//! Compute the Julian day from the calendar date and time.
double convertCalendarDateToJulianDay( const int calendarYear,
                                       const int calendarMonth,
                                       const int calendarDay,
                                       const int calendarHour,
                                       const int calendarMinutes,
                                       const double calendarSeconds )
{
    // Calculate julian day of calendar date.
    double julianDay = boost::gregorian::date( calendarYear, calendarMonth, calendarDay ).julian_day( );

    //Compute day fraction
    const double dayFraction = static_cast< double >( calendarHour ) / 24.0 +
                               static_cast< double >( calendarMinutes ) / ( 24.0 * 60.0 ) +
                               calendarSeconds / ( 24.0 * 3600.0 );

    // Compute Julian day by adding day fraction and subtracting 0.5 to reference to midnight
    // instead of noon..
    return julianDay + dayFraction - 0.5;
}

} // namespace basic_astrodynamics
} // namespace tudat


// This file is part of the dune-hdd project:
//   http://users.dune-project.org/projects/dune-hdd
// Copyright holders: Felix Schindler
// License: BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)

#ifndef DUNE_HDD_LINEARELLIPTIC_DISCRETIZATIONS_SWIPDG_HH
#define DUNE_HDD_LINEARELLIPTIC_DISCRETIZATIONS_SWIPDG_HH

#include <memory>
#include <vector>

#include <boost/numeric/conversion/cast.hpp>

#include <dune/common/timer.hh>
#include <dune/common/static_assert.hh>

#include <dune/stuff/common/disable_warnings.hh>
# if HAVE_ALUGRID
#   include <dune/grid/alugrid.hh>
# endif
#include <dune/stuff/common/reenable_warnings.hh>

#if HAVE_DUNE_GRID_MULTISCALE
# include <dune/grid/multiscale/provider/interface.hh>
#endif

#include <dune/stuff/common/configuration.hh>
#include <dune/stuff/grid/layers.hh>
#include <dune/stuff/grid/provider.hh>
#include <dune/stuff/la/container.hh>
#include <dune/stuff/la/solver.hh>
#include <dune/stuff/grid/walker/functors.hh>

#include <dune/gdt/assembler/system.hh>
#include <dune/gdt/assembler/tmp-storage.hh>
#include <dune/gdt/discretefunction/default.hh>
#include <dune/gdt/functionals/l2.hh>
#include <dune/gdt/operators/oswaldinterpolation.hh>
#include <dune/gdt/operators/projections.hh>
#include <dune/gdt/playground/functionals/swipdg.hh>
#include <dune/gdt/playground/operators/elliptic-swipdg.hh>
#include <dune/gdt/playground/operators/fluxreconstruction.hh>
#include <dune/gdt/playground/products/swipdgpenalty.hh>
#include <dune/gdt/playground/spaces/finitevolume/default.hh>
#include <dune/gdt/playground/spaces/raviartthomas/pdelab.hh>
#include <dune/gdt/products/boundaryl2.hh>
#include <dune/gdt/products/elliptic.hh>
#include <dune/gdt/products/h1.hh>
#include <dune/gdt/products/l2.hh>
#include <dune/gdt/spaces/discontinuouslagrange.hh>

#include "base.hh"

namespace Dune {
namespace HDD {
namespace LinearElliptic {
namespace Discretizations {

// forward, for friendlyness
template< class GridImp, class RangeFieldImp, int rangeDim, int polynomialOrder, Stuff::LA::ChooseBackend la_backend >
class BlockSWIPDG;

// forward, needed in the Traits
template< class GridImp, Stuff::Grid::ChooseLayer layer, class RangeFieldImp, int rangeDim, int polynomialOrder = 1,
#if HAVE_DUNE_FEM
          GDT::ChooseSpaceBackend space_backend = GDT::ChooseSpaceBackend::fem,
#else
# error No suitable space backend available!
#endif
          Stuff::LA::ChooseBackend la_backend  = Stuff::LA::default_sparse_backend >
class SWIPDG;

namespace internal {

template< class GridImp, Stuff::Grid::ChooseLayer layer, class RangeFieldImp, int rangeDim, int polynomialOrder,
          GDT::ChooseSpaceBackend space_backend,
          Stuff::LA::ChooseBackend la_backend >
class SWIPDGTraits
  : public internal::ContainerBasedDefaultTraits< typename Stuff::LA::Container< RangeFieldImp, la_backend >::MatrixType,
                                                  typename Stuff::LA::Container< RangeFieldImp, la_backend >::VectorType>
{
public:
  typedef SWIPDG< GridImp, layer, RangeFieldImp, rangeDim, polynomialOrder, space_backend, la_backend > derived_type;
  typedef GridImp GridType;
  typedef RangeFieldImp RangeFieldType;
  static const unsigned int dimRange = rangeDim;
  static const unsigned int polOrder = polynomialOrder;

private:
  typedef GDT::Spaces::DiscontinuousLagrangeProvider< GridType, layer, space_backend,
                                                      polOrder, RangeFieldType, dimRange > SpaceProvider;

  friend class SWIPDG< GridImp, layer, RangeFieldImp, rangeDim, polynomialOrder, space_backend, la_backend >;

public:
  typedef typename SpaceProvider::Type TestSpaceType;
  typedef TestSpaceType AnsatzSpaceType;
  typedef typename TestSpaceType::GridViewType GridViewType;
}; // class SWIPDGTraits

} // namespace internal

template< class GridImp, Stuff::Grid::ChooseLayer layer, class RangeFieldImp, int rangeDim, int polynomialOrder,
          GDT::ChooseSpaceBackend space_backend,
          Stuff::LA::ChooseBackend la_backend >
class SWIPDG
  : public ContainerBasedDefault< internal::SWIPDGTraits< GridImp, layer, RangeFieldImp, rangeDim, polynomialOrder,
                                                          space_backend, la_backend > >
{
  typedef ContainerBasedDefault< internal::SWIPDGTraits< GridImp, layer, RangeFieldImp, rangeDim, polynomialOrder,
                                                         space_backend, la_backend > > BaseType;
  typedef SWIPDG< GridImp, layer, RangeFieldImp, rangeDim, polynomialOrder, space_backend, la_backend > ThisType;
public:
  typedef internal::SWIPDGTraits< GridImp, layer, RangeFieldImp, rangeDim, polynomialOrder, space_backend, la_backend >
      Traits;
  typedef GridImp GridType;
  using typename BaseType::ProblemType;
  using typename BaseType::BoundaryInfoType;
  using typename BaseType::TestSpaceType;
  using typename BaseType::AnsatzSpaceType;
  using typename BaseType::MatrixType;
  using typename BaseType::VectorType;
  using typename BaseType::GridViewType;
  using typename BaseType::RangeFieldType;

  typedef typename TestSpaceType::PatternType PatternType;

  static const unsigned int dimDomain = BaseType::dimDomain;
  static const unsigned int dimRange = BaseType::dimRange;

private:
  typedef typename Traits::SpaceProvider SpaceProvider;

  typedef Stuff::Grid::ConstProviderInterface< GridType > GridProviderType;
#if HAVE_DUNE_GRID_MULTISCALE
  typedef grid::Multiscale::ProviderInterface< GridType > MsGridProviderType;
#endif
  using typename BaseType::AffinelyDecomposedMatrixType;
  using typename BaseType::AffinelyDecomposedVectorType;

  typedef typename ProblemType::DiffusionFactorType::NonparametricType DiffusionFactorType;
  typedef typename ProblemType::DiffusionTensorType::NonparametricType DiffusionTensorType;
  typedef GDT::Operators::EllipticSWIPDG< DiffusionFactorType, MatrixType
                                        , TestSpaceType, AnsatzSpaceType
                                        , GridViewType, DiffusionTensorType > EllipticOperatorType;

public:
  static std::string static_id()
  {
    return DiscretizationInterface< Traits >::static_id() + ".swipdg";
  }

  SWIPDG(const GridProviderType& grid_provider,
         const Stuff::Common::Configuration& bound_inf_cfg,
         const ProblemType& prob,
         const int level_or_subdomain = 0,
         const std::vector< std::string >& only_these_products = {})
    : BaseType(std::make_shared< TestSpaceType >(SpaceProvider::create(grid_provider, level_or_subdomain)),
               std::make_shared< AnsatzSpaceType >(SpaceProvider::create(grid_provider, level_or_subdomain)),
               bound_inf_cfg,
               prob)
    , beta_(GDT::LocalEvaluation::SWIPDG::internal::default_beta(dimDomain))
    , pattern_(EllipticOperatorType::pattern(*(BaseType::test_space()), *(BaseType::ansatz_space())))
    , only_these_products_(only_these_products)
  {
    // in case of parametric diffusion tensor this discretization is not affinely decomposable any more
    if (this->problem_.diffusion_tensor()->parametric())
      DUNE_THROW(NotImplemented, "The diffusion tensor must not be parametric!");
    if (!this->problem_.diffusion_tensor()->has_affine_part())
      DUNE_THROW(Stuff::Exceptions::wrong_input_given, "The diffusion tensor must not be empty!");
  } // SWIPDG(...)

#if HAVE_DUNE_GRID_MULTISCALE
  SWIPDG(const MsGridProviderType& grid_provider,
         const Stuff::Common::Configuration& bound_inf_cfg,
         const ProblemType& prob,
         const int level_or_subdomain = 0,
         const std::vector< std::string >& only_these_products = {})
    : BaseType(std::make_shared< TestSpaceType >(SpaceProvider::create(grid_provider, level_or_subdomain)),
               std::make_shared< AnsatzSpaceType >(SpaceProvider::create(grid_provider, level_or_subdomain)),
               bound_inf_cfg,
               prob)
    , beta_(GDT::LocalEvaluation::SWIPDG::internal::default_beta(dimDomain))
    , pattern_(EllipticOperatorType::pattern(*(BaseType::test_space()), *(BaseType::ansatz_space())))
    , only_these_products_(only_these_products)
  {
    // in case of parametric diffusion tensor this discretization is not affinely decomposable any more
    if (this->problem_.diffusion_tensor()->parametric())
      DUNE_THROW(NotImplemented, "The diffusion tensor must not be parametric!");
    if (!this->problem_.diffusion_tensor()->has_affine_part())
      DUNE_THROW(Stuff::Exceptions::wrong_input_given, "The diffusion tensor must not be empty!");
  } // SWIPDG(...)
#endif // HAVE_DUNE_GRID_MULTISCALE

  const PatternType& pattern() const
  {
    return pattern_;
  }

  void init(std::ostream& out = Stuff::Common::Logger().devnull(), const std::string prefix = "")
  {
    if (!this->container_based_initialized_) {
      using namespace GDT;

      auto& matrix = *(this->matrix_);
      auto& rhs = *(this->rhs_);
      const auto& space = *(this->test_space_);
      const auto& boundary_info = *(this->boundary_info_);

      out << prefix << "assembling... " << std::flush;
      Dune::Timer timer;
      SystemAssembler< TestSpaceType > system_assembler(space);
      Stuff::Grid::Functor::DirichletDetector< GridViewType > dirichlet_detector(this->boundary_info());
      system_assembler.add(dirichlet_detector);

      // lhs operator
      const auto& diffusion_factor = *(this->problem_.diffusion_factor());
      const auto& diffusion_tensor = *(this->problem_.diffusion_tensor());
      assert(!diffusion_tensor.parametric());
      assert(diffusion_tensor.has_affine_part());
      std::vector< std::unique_ptr< EllipticOperatorType > > elliptic_operators;
      for (size_t qq = 0; qq < boost::numeric_cast< size_t >(diffusion_factor.num_components()); ++qq) {
        const size_t id = matrix.register_component(diffusion_factor.coefficient(qq),
                                                    space.mapper().size(), space.mapper().size(), pattern_);
        elliptic_operators.emplace_back(new EllipticOperatorType(
            *(diffusion_factor.component(qq)),
            *(diffusion_tensor.affine_part()),
            boundary_info,
            *(matrix.component(id)),
            space));
      }
      if (diffusion_factor.has_affine_part()) {
        if (!matrix.has_affine_part())
          matrix.register_affine_part(space.mapper().size(), space.mapper().size(), pattern_);
        elliptic_operators.emplace_back(new EllipticOperatorType(
            *(diffusion_factor.affine_part()),
            *(diffusion_tensor.affine_part()),
            boundary_info,
            *(matrix.affine_part()),
            space));
      }
      for (auto& elliptic_operator : elliptic_operators)
        system_assembler.add(*elliptic_operator);

      // rhs functional
      // * volume
      typedef typename ProblemType::FunctionType::NonparametricType FunctionType;
      const auto& force = *(this->problem_.force());
      typedef Functionals::L2Volume< FunctionType, VectorType, TestSpaceType > L2VolumeFunctionalType;
      std::vector< std::unique_ptr< L2VolumeFunctionalType > > force_functionals;
      for (size_t qq = 0; qq < boost::numeric_cast< size_t >(force.num_components()); ++qq) {
        const size_t id = rhs.register_component(force.coefficient(qq), space.mapper().size());
        force_functionals.emplace_back(new L2VolumeFunctionalType(*(force.component(qq)),
                                                                  *(rhs.component(id)),
                                                                  space));
      }
      if (force.has_affine_part()) {
        if (!rhs.has_affine_part())
          rhs.register_affine_part(space.mapper().size());
        force_functionals.emplace_back(new L2VolumeFunctionalType(*(force.affine_part()),
                                                                  *(rhs.affine_part()),
                                                                  space));
      }
      for (auto& force_functional : force_functionals)
        system_assembler.add(*force_functional);
      // * dirichlet boundary
      const auto& dirichlet = *(this->problem_.dirichlet());
      typedef Functionals::DirichletBoundarySWIPDG< DiffusionFactorType, FunctionType, VectorType, TestSpaceType,
                                                    GridViewType, DiffusionTensorType > DirichletBoundaryFunctionalType;
      std::vector< std::unique_ptr< DirichletBoundaryFunctionalType > > dirichlet_boundary_functionals;
      if (diffusion_factor.has_affine_part() && dirichlet.has_affine_part()) {
        if (!rhs.has_affine_part())
          rhs.register_affine_part(space.mapper().size());
        dirichlet_boundary_functionals.emplace_back(new DirichletBoundaryFunctionalType(
            *(diffusion_factor.affine_part()),
            *(diffusion_tensor.affine_part()),
            *(dirichlet.affine_part()),
            boundary_info,
            *(rhs.affine_part()),
            space));
      }
      if (diffusion_factor.has_affine_part()) {
        for (size_t qq = 0; qq < boost::numeric_cast< size_t >(dirichlet.num_components()); ++qq) {
          const size_t id = rhs.register_component(dirichlet.coefficient(qq), space.mapper().size());
          dirichlet_boundary_functionals.emplace_back(new DirichletBoundaryFunctionalType(
              *(diffusion_factor.affine_part()),
              *(diffusion_tensor.affine_part()),
              *(dirichlet.component(qq)),
              boundary_info,
              *(rhs.component(id)),
              space));
        }
      }
      if (dirichlet.has_affine_part()) {
        for (size_t qq = 0; qq < boost::numeric_cast< size_t >(diffusion_factor.num_components()); ++qq) {
          const size_t id = rhs.register_component(diffusion_factor.coefficient(qq), space.mapper().size());
          dirichlet_boundary_functionals.emplace_back(new DirichletBoundaryFunctionalType(
              *(diffusion_factor.component(qq)),
              *(diffusion_tensor.affine_part()),
              *(dirichlet.affine_part()),
              boundary_info,
              *(rhs.component(id)),
              space));
        }
      }
      Pymor::ParameterType param;
      for (const auto& key : diffusion_factor.parameter_type().keys())
        param.set(key, diffusion_factor.parameter_type().get(key));
      for (const auto& key : dirichlet.parameter_type().keys())
        param.set(key, dirichlet.parameter_type().get(key));
      for (size_t pp = 0; pp < boost::numeric_cast< size_t >(diffusion_factor.num_components()); ++ pp) {
        for (size_t qq = 0; qq < boost::numeric_cast< size_t >(dirichlet.num_components()); ++qq) {
          const std::string expression = "(" + diffusion_factor.coefficient(pp)->expression()
                                             + ")*(" + dirichlet.coefficient(qq)->expression() + ")";
          const size_t id = rhs.register_component(param, expression, space.mapper().size());
          dirichlet_boundary_functionals.emplace_back(new DirichletBoundaryFunctionalType(
              *(diffusion_factor.component(pp)),
              *(diffusion_tensor.affine_part()),
              *(dirichlet.component(qq)),
              boundary_info,
              *(rhs.component(id)),
              space));
        }
      }
      for (auto& dirichlet_boundary_functional : dirichlet_boundary_functionals)
        system_assembler.add(*dirichlet_boundary_functional);

      // * neumann boundary
      const auto& neumann = *(this->problem_.neumann());
      typedef Functionals::L2Face< FunctionType, VectorType, TestSpaceType > L2FaceFunctionalType;
      std::vector< std::unique_ptr< L2FaceFunctionalType > > neumann_boundary_functionals;
      for (size_t qq = 0; qq < boost::numeric_cast< size_t >(neumann.num_components()); ++qq) {
        const size_t id = rhs.register_component(neumann.coefficient(qq), space.mapper().size());
        neumann_boundary_functionals.emplace_back(new L2FaceFunctionalType(
            *(neumann.component(qq)),
            *(rhs.component(id)),
            space,
            new Stuff::Grid::ApplyOn::NeumannIntersections< GridViewType >(boundary_info)));
      }
      if (neumann.has_affine_part()) {
        if (!rhs.has_affine_part())
          rhs.register_affine_part(space.mapper().size());
        neumann_boundary_functionals.emplace_back(new L2FaceFunctionalType(
            *(neumann.affine_part()),
            *(rhs.affine_part()),
            space,
            new Stuff::Grid::ApplyOn::NeumannIntersections< GridViewType >(boundary_info)));
      }
      for (auto& neumann_boundary_functional : neumann_boundary_functionals)
        system_assembler.add(*neumann_boundary_functional);

      // products
      const size_t over_integrate = 2;
      // * L2
      typedef Products::L2Assemblable< MatrixType, TestSpaceType, GridViewType, AnsatzSpaceType > L2ProductType;
      std::unique_ptr< L2ProductType > l2_product;
      auto l2_product_matrix = std::make_shared< AffinelyDecomposedMatrixType >();
      if (std::find(only_these_products_.begin(), only_these_products_.end(), "l2") != only_these_products_.end()) {
        l2_product_matrix->register_affine_part(this->test_space()->mapper().size(),
                                                this->ansatz_space()->mapper().size(),
                                                L2ProductType::pattern(*this->test_space(),
                                                                       *this->ansatz_space()));
        l2_product = DSC::make_unique< L2ProductType >(*(l2_product_matrix->affine_part()),
                                                       *this->test_space(),
                                                       this->grid_view(),
                                                       *this->ansatz_space(),
                                                       over_integrate);
        system_assembler.add(*l2_product);
      }
      // * H1 semi
      typedef Products::H1SemiAssemblable< MatrixType, TestSpaceType, GridViewType, AnsatzSpaceType >
          H1ProductType;
      std::unique_ptr< H1ProductType > h1_product;
      auto h1_product_matrix = std::make_shared< AffinelyDecomposedMatrixType >();
      if (std::find(only_these_products_.begin(), only_these_products_.end(), "h1_semi") != only_these_products_.end()) {
        h1_product_matrix->register_affine_part(this->test_space()->mapper().size(),
                                                this->ansatz_space()->mapper().size(),
                                                H1ProductType::pattern(*this->test_space(),
                                                                       *this->ansatz_space()));
        h1_product = DSC::make_unique< H1ProductType >(*(h1_product_matrix->affine_part()),
                                                       *this->test_space(),
                                                       this->grid_view(),
                                                       *this->ansatz_space(),
                                                       over_integrate);
        system_assembler.add(*h1_product);
      }
      // * elliptic
      assert(!diffusion_tensor.parametric());
      typedef Products::EllipticAssemblable< MatrixType, DiffusionFactorType, TestSpaceType, GridViewType,
                                                  AnsatzSpaceType, RangeFieldType, DiffusionTensorType >
          EllipticProductType;
      std::vector< std::unique_ptr< EllipticProductType > > elliptic_products;
      auto elliptic_product_matrix = std::make_shared< AffinelyDecomposedMatrixType >();
      if (std::find(only_these_products_.begin(), only_these_products_.end(), "elliptic") != only_these_products_.end()) {
        for (DUNE_STUFF_SSIZE_T qq = 0; qq < diffusion_factor.num_components(); ++qq) {
          const auto id = elliptic_product_matrix->register_component(diffusion_factor.coefficient(qq),
                                                                      this->test_space()->mapper().size(),
                                                                      this->ansatz_space()->mapper().size(),
                                                                      EllipticProductType::pattern(*this->test_space(),
                                                                                *this->ansatz_space()));
          elliptic_products.emplace_back(new EllipticProductType(*elliptic_product_matrix->component(id),
                                                                 *this->test_space(),
                                                                 this->grid_view(),
                                                                 *this->ansatz_space(),
                                                                 *diffusion_factor.component(qq),
                                                                 *diffusion_tensor.affine_part(),
                                                                 over_integrate));
        }
        if (diffusion_factor.has_affine_part()) {
          elliptic_product_matrix->register_affine_part(this->test_space()->mapper().size(),
                                                        this->ansatz_space()->mapper().size(),
                                                        EllipticProductType::pattern(*this->test_space(),
                                                                                *this->ansatz_space()));
          elliptic_products.emplace_back(new EllipticProductType(*elliptic_product_matrix->affine_part(),
                                                                 *this->test_space(),
                                                                 this->grid_view(),
                                                                 *this->ansatz_space(),
                                                                 *diffusion_factor.affine_part(),
                                                                 *diffusion_tensor.affine_part(),
                                                                 over_integrate));
        }
        for (auto& product : elliptic_products)
          system_assembler.add(*product);
      }
      // * boundary L2
      typedef Products::BoundaryL2Assemblable< MatrixType, TestSpaceType, GridViewType, AnsatzSpaceType >
          BoundaryL2ProductType;
      std::unique_ptr< BoundaryL2ProductType > boundary_l2_product;
      auto boundary_l2_product_matrix = std::make_shared< AffinelyDecomposedMatrixType >();
      if (std::find(only_these_products_.begin(), only_these_products_.end(), "boundary_l2") != only_these_products_.end()) {
        boundary_l2_product_matrix->register_affine_part(this->test_space()->mapper().size(),
                                                         this->ansatz_space()->mapper().size(),
                                                         BoundaryL2ProductType::pattern(*this->test_space(),
                                                                                *this->ansatz_space()));
        boundary_l2_product = DSC::make_unique< BoundaryL2ProductType >(*(boundary_l2_product_matrix->affine_part()),
                                                                        *this->test_space(),
                                                                        this->grid_view(),
                                                                        *this->ansatz_space(),
                                                                        over_integrate);
        system_assembler.add(*boundary_l2_product);
      }
      // * penalty term
      typedef Products::SwipdgPenaltyAssemblable
          < MatrixType, DiffusionFactorType, DiffusionTensorType, TestSpaceType > PenaltyProductType;
      std::vector< std::unique_ptr< PenaltyProductType > > penalty_products;
      auto penalty_product_matrix = std::make_shared< AffinelyDecomposedMatrixType >();
      if (std::find(only_these_products_.begin(), only_these_products_.end(), "penalty") != only_these_products_.end()) {
        for (DUNE_STUFF_SSIZE_T qq = 0; qq < diffusion_factor.num_components(); ++qq) {
          const auto id = penalty_product_matrix->register_component(diffusion_factor.coefficient(qq),
                                                                     this->test_space()->mapper().size(),
                                                                     this->ansatz_space()->mapper().size(),
                                                                     PenaltyProductType::pattern(*this->test_space(),
                                                                                *this->ansatz_space()));
          penalty_products.emplace_back(new PenaltyProductType(*penalty_product_matrix->component(id),
                                                               *this->test_space(),
                                                               this->grid_view(),
                                                               *this->ansatz_space(),
                                                               *diffusion_factor.component(qq),
                                                               *diffusion_tensor.affine_part(),
                                                               over_integrate));
        }
        if (diffusion_factor.has_affine_part()) {
          penalty_product_matrix->register_affine_part(this->test_space()->mapper().size(),
                                                       this->ansatz_space()->mapper().size(),
                                                       PenaltyProductType::pattern(*this->test_space(),
                                                                                *this->ansatz_space()));
          penalty_products.emplace_back(new PenaltyProductType(*penalty_product_matrix->affine_part(),
                                                               *this->test_space(),
                                                               this->grid_view(),
                                                               *this->ansatz_space(),
                                                               *diffusion_factor.affine_part(),
                                                               *diffusion_tensor.affine_part(),
                                                               over_integrate));
        }
        for (auto& product : penalty_products)
          system_assembler.add(*product);
      }
      // do the actual assembling
      system_assembler.walk();
      out << "done (took " << timer.elapsed() << "s)" << std::endl;

      if (!dirichlet_detector.found())
        this->purely_neumann_ = true;

      // build parameter type
      this->inherit_parameter_type(matrix.parameter_type(), "lhs");
      this->inherit_parameter_type(rhs.parameter_type(),    "rhs");

      // finalize
      if (std::find(only_these_products_.begin(), only_these_products_.end(), "l2") != only_these_products_.end())
        this->products_.insert(std::make_pair("l2", l2_product_matrix));
      if (std::find(only_these_products_.begin(), only_these_products_.end(), "h1_semi") != only_these_products_.end())
        this->products_.insert(std::make_pair("h1_semi", h1_product_matrix));
      if (std::find(only_these_products_.begin(), only_these_products_.end(), "elliptic") != only_these_products_.end())
        this->products_.insert(std::make_pair("elliptic", elliptic_product_matrix));
      if (std::find(only_these_products_.begin(), only_these_products_.end(), "boundary_l2") != only_these_products_.end())
        this->products_.insert(std::make_pair("boundary_l2", boundary_l2_product_matrix));
      if (std::find(only_these_products_.begin(), only_these_products_.end(), "penalty") != only_these_products_.end())
        this->products_.insert(std::make_pair("penalty", penalty_product_matrix));
      if (std::find(only_these_products_.begin(), only_these_products_.end(), "energy") != only_these_products_.end())
        this->products_.insert(std::make_pair("energy",
                                              std::make_shared< AffinelyDecomposedMatrixType >(this->matrix_->copy())));

      this->container_based_initialized_ = true;
    } // if (!this->container_based_initialized_)
  } // ... init(...)

private:
  friend class BlockSWIPDG< GridImp, RangeFieldImp, rangeDim, polynomialOrder, la_backend >;

  const RangeFieldType beta_;
  PatternType pattern_;
  const std::vector< std::string > only_these_products_;
}; // class SWIPDG

} // namespace Discretizations
} // namespace LinearElliptic
} // namespace HDD
} // namespace Dune

#endif // DUNE_HDD_LINEARELLIPTIC_DISCRETIZATIONS_SWIPDG_HH


/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2000, 2001, 2002, 2003 RiskMap srl
 Copyright (C) 2003, 2004, 2005, 2006 StatPro Italia srl
 Copyright (C) 2004, 2005, 2006 Ferdinando Ametrano
 Copyright (C) 2006 Katiuscia Manzoni
 Copyright (C) 2006 Toyin Akin
 Copyright (C) 2015 Klaus Spanderen

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

/*! \file date.hpp
    \brief date- and time-related classes, typedefs and enumerations
*/

#ifndef quantlib_date_hpp
#define quantlib_date_hpp

#include <ql/time/period.hpp>
#include <ql/time/weekday.hpp>
#include <ql/utilities/null.hpp>
#include <boost/cstdint.hpp>

#ifdef QL_HIGH_RESOLUTION_DATE
#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/posix_time/posix_time_duration.hpp>
#endif

#include <utility>
#include <functional>

namespace QuantLib {

    //! Day number
    /*! \ingroup datetime */
    typedef Integer Day;

    //! Month names
    /*! \ingroup datetime */
    enum Month { January   = 1,
                 February  = 2,
                 March     = 3,
                 April     = 4,
                 May       = 5,
                 June      = 6,
                 July      = 7,
                 August    = 8,
                 September = 9,
                 October   = 10,
                 November  = 11,
                 December  = 12,
                 Jan = 1,
                 Feb = 2,
                 Mar = 3,
                 Apr = 4,
                 Jun = 6,
                 Jul = 7,
                 Aug = 8,
                 Sep = 9,
                 Oct = 10,
                 Nov = 11,
                 Dec = 12
    };

    /*! \relates Month */
    std::ostream& operator<<(std::ostream&, Month);

    //! Year number
    /*! \ingroup datetime */
    typedef Integer Year;

#ifdef QL_HIGH_RESOLUTION_DATE
    //! Hour number
    /*! \ingroup datetime */
    typedef boost::posix_time::hours::hour_type Hour;

    //! Minute number
    /*! \ingroup datetime */
    typedef boost::posix_time::minutes::min_type Minute;

    //! Second number
    /*! \ingroup datetime */
    typedef boost::posix_time::minutes::sec_type Second;

    //! Millisecond number
    /*! \ingroup datetime */
    typedef boost::posix_time::time_duration::fractional_seconds_type
        Millisecond;

    //! Millisecond number
    /*! \ingroup datetime */
    typedef boost::posix_time::time_duration::fractional_seconds_type
        Microsecond;
#endif

    //! Concrete date class
    /*! This class provides methods to inspect dates as well as methods and
        operators which implement a limited date algebra (increasing and
        decreasing dates, and calculating their difference).

        \ingroup datetime

        \test self-consistency of dates, serial numbers, days of
              month, months, and weekdays is checked over the whole
              date range.
    */

    class Date {
      public:
        //! serial number type
        typedef boost::int_fast32_t serial_type;
        //! \name constructors
        //@{
        //! Default constructor returning a null date.
        Date();
        //! Constructor taking a serial number as given by Applix or Excel.
        explicit Date(Date::serial_type serialNumber);
        //! More traditional constructor.
        Date(Day d, Month m, Year y);

#ifdef QL_HIGH_RESOLUTION_DATE
        //! Constructor taking boost posix date time object
        explicit Date(const boost::posix_time::ptime& localTime);
        //! More traditional constructor.
        Date(Day d, Month m, Year y,
             Hour hours, Minute minutes, Second seconds,
             Millisecond millisec = 0, Microsecond microsec = 0);
#endif
        //@}

        //! \name inspectors
        //@{
        Weekday weekday() const;
        Day dayOfMonth() const;
        //! One-based (Jan 1st = 1)
        Day dayOfYear() const;
        Month month() const;
        Year year() const;
        Date::serial_type serialNumber() const;

#ifdef QL_HIGH_RESOLUTION_DATE
        Hour hours() const;
        Minute minutes() const;
        Second seconds() const;
        Millisecond milliseconds() const;
        Microsecond microseconds() const;

        Time fractionOfDay() const;
        Time fractionOfSecond() const;

        const boost::posix_time::ptime& dateTime() const;
#endif
        //@}

        //! \name date algebra
        //@{
        //! increments date by the given number of days
        Date& operator+=(Date::serial_type days);
        //! increments date by the given period
        Date& operator+=(const Period&);
        //! decrement date by the given number of days
        Date& operator-=(Date::serial_type days);
        //! decrements date by the given period
        Date& operator-=(const Period&);
        //! 1-day pre-increment
        Date& operator++();
        //! 1-day post-increment
        Date operator++(int );
        //! 1-day pre-decrement
        Date& operator--();
        //! 1-day post-decrement
        Date operator--(int );
        //! returns a new date incremented by the given number of days
        Date operator+(Date::serial_type days) const;
        //! returns a new date incremented by the given period
        Date operator+(const Period&) const;
        //! returns a new date decremented by the given number of days
        Date operator-(Date::serial_type days) const;
        //! returns a new date decremented by the given period
        Date operator-(const Period&) const;
        //@}

        //! \name static methods
        //@{
        //! today's date.
        static Date todaysDate();
        //! earliest allowed date
        static Date minDate();
        //! latest allowed date
        static Date maxDate();
        //! whether the given year is a leap one
        static bool isLeap(Year y);
        //! last day of the month to which the given date belongs
        static Date endOfMonth(const Date& d);
        //! whether a date is the last day of its month
        static bool isEndOfMonth(const Date& d);
        //! next given weekday following or equal to the given date
        /*! E.g., the Friday following Tuesday, January 15th, 2002
            was January 18th, 2002.

            see http://www.cpearson.com/excel/DateTimeWS.htm
        */
        static Date nextWeekday(const Date& d,
                                Weekday w);
        //! n-th given weekday in the given month and year
        /*! E.g., the 4th Thursday of March, 1998 was March 26th,
            1998.

            see http://www.cpearson.com/excel/DateTimeWS.htm
        */
        static Date nthWeekday(Size n,
                               Weekday w,
                               Month m,
                               Year y);

#ifdef QL_HIGH_RESOLUTION_DATE
        //! local date time, based on the time zone settings of the computer
        static Date localDateTime();
        //! UTC date time
        static Date universalDateTime();

        //! underlying resolution of the  posix date time object
        static Size ticksPerSecond();
#endif

        //@}

      private:
        static Date::serial_type minimumSerialNumber();
        static Date::serial_type maximumSerialNumber();
        static void checkSerialNumber(Date::serial_type serialNumber);

#ifdef QL_HIGH_RESOLUTION_DATE
        boost::posix_time::ptime dateTime_;
#else
        Date::serial_type serialNumber_;
        static Date advance(const Date& d, Integer units, TimeUnit);
        static Integer monthLength(Month m, bool leapYear);
        static Integer monthOffset(Month m, bool leapYear);
        static Date::serial_type yearOffset(Year y);
#endif
    };

    /*! \relates Date
        \brief Difference in days between dates
    */
    Date::serial_type operator-(const Date&, const Date&);
    /*! \relates Date
        \brief Difference in days (including fraction of days) between dates
    */
    Time daysBetween(const Date&, const Date&);

    /*! \relates Date */
    bool operator==(const Date&, const Date&);
    /*! \relates Date */
    bool operator!=(const Date&, const Date&);
    /*! \relates Date */
    bool operator<(const Date&, const Date&);
    /*! \relates Date */
    bool operator<=(const Date&, const Date&);
    /*! \relates Date */
    bool operator>(const Date&, const Date&);
    /*! \relates Date */
    bool operator>=(const Date&, const Date&);

    /*! \relates Date */
    std::ostream& operator<<(std::ostream&, const Date&);

    namespace detail {

        struct short_date_holder {
            short_date_holder(const Date d) : d(d) {}
            Date d;
        };
        std::ostream& operator<<(std::ostream&, const short_date_holder&);

        struct long_date_holder {
            long_date_holder(const Date& d) : d(d) {}
            Date d;
        };
        std::ostream& operator<<(std::ostream&, const long_date_holder&);

        struct iso_date_holder {
            iso_date_holder(const Date& d) : d(d) {}
            Date d;
        };
        std::ostream& operator<<(std::ostream&, const iso_date_holder&);

        struct formatted_date_holder {
            formatted_date_holder(const Date& d, const std::string& f)
            : d(d), f(f) {}
            Date d;
            std::string f;
        };
        std::ostream& operator<<(std::ostream&,
                                 const formatted_date_holder&);

#ifdef QL_HIGH_RESOLUTION_DATE
        struct iso_datetime_holder {
            iso_datetime_holder(const Date& d) : d(d) {}
            Date d;
        };
        std::ostream& operator<<(std::ostream&, const iso_datetime_holder&);
#endif
    }

    namespace io {

        //! output dates in short format (mm/dd/yyyy)
        /*! \ingroup manips */
        detail::short_date_holder short_date(const Date&);

        //! output dates in long format (Month ddth, yyyy)
        /*! \ingroup manips */
        detail::long_date_holder long_date(const Date&);

        //! output dates in ISO format (yyyy-mm-dd)
        /*! \ingroup manips */
        detail::iso_date_holder iso_date(const Date&);

        //! output dates in user defined format using boost date functionality
        /*! \ingroup manips */
        detail::formatted_date_holder formatted_date(const Date&,
                                                     const std::string& fmt);

#ifdef QL_HIGH_RESOLUTION_DATE
        //! output datetimes in ISO format (YYYY-MM-DDThh:mm:ss,SSSSSS)
        /*! \ingroup manips */
        detail::iso_datetime_holder iso_datetime(const Date&);
#endif

    }

    //! specialization of Null template for the Date class
    template <>
    class Null<Date> {
      public:
        Null() {}
        operator Date() const { return Date(); }
    };

#ifndef QL_HIGH_RESOLUTION_DATE
    // inline definitions

    inline Weekday Date::weekday() const {
        Integer w = serialNumber_ % 7;
        return Weekday(w == 0 ? 7 : w);
    }

    inline Day Date::dayOfMonth() const {
        return dayOfYear() - monthOffset(month(),isLeap(year()));
    }

    inline Day Date::dayOfYear() const {
        return serialNumber_ - yearOffset(year());
    }

    inline Date::serial_type Date::serialNumber() const {
        return serialNumber_;
    }

    inline Date Date::operator+(Date::serial_type days) const {
        return Date(serialNumber_+days);
    }

    inline Date Date::operator-(Date::serial_type days) const {
        return Date(serialNumber_-days);
    }

    inline Date Date::operator+(const Period& p) const {
        return advance(*this,p.length(),p.units());
    }

    inline Date Date::operator-(const Period& p) const {
        return advance(*this,-p.length(),p.units());
    }

    inline Date Date::endOfMonth(const Date& d) {
        Month m = d.month();
        Year y = d.year();
        return Date(monthLength(m, isLeap(y)), m, y);
    }

    inline bool Date::isEndOfMonth(const Date& d) {
       return (d.dayOfMonth() == monthLength(d.month(), isLeap(d.year())));
    }

    inline Date::serial_type operator-(const Date& d1, const Date& d2) {
        return d1.serialNumber()-d2.serialNumber();
    }

    inline Time daysBetween(const Date& d1, const Date& d2) {
        return Time(d2-d1);
    }

    inline bool operator==(const Date& d1, const Date& d2) {
        return (d1.serialNumber() == d2.serialNumber());
    }

    inline bool operator!=(const Date& d1, const Date& d2) {
        return (d1.serialNumber() != d2.serialNumber());
    }

    inline bool operator<(const Date& d1, const Date& d2) {
        return (d1.serialNumber() < d2.serialNumber());
    }

    inline bool operator<=(const Date& d1, const Date& d2) {
        return (d1.serialNumber() <= d2.serialNumber());
    }

    inline bool operator>(const Date& d1, const Date& d2) {
        return (d1.serialNumber() > d2.serialNumber());
    }

    inline bool operator>=(const Date& d1, const Date& d2) {
        return (d1.serialNumber() >= d2.serialNumber());
    }
#endif
}

#endif