/*! file one-solver-anneal.cpp
 *
 * @copyright Licensed under MIT license by hyperQ – Ewa Hendzel
 *
 */

#include <boost/program_options.hpp>
#include <fstream>
#include <iostream>
#include <ctime>
#include <random>

#include <mpi.h>
#include "CL/sycl.hpp"
#include "helpers/devices.hpp"
#include "helpers/qubo_helpers.hpp"
#include "model/qubo.hpp"
#include "model/solution.hpp"
#include "simulated_annealing/annealing.hpp"

#define SEED 1234

namespace po = boost::program_options;
namespace sycl = cl::sycl;

using queue_ptr = std::unique_ptr<sycl::queue>;

/*!
 * @brief Constructs a linear beta schedule.
 *
 * @param a reference to a vector for storing the schedule
 * @param minimum value of beta in the annealing schedule.
 * @param maximum value of beta in the annealing schedule.
 * @param number of iterations of the algorithm.
 *
 * @returns returns schedule.
 */
void construct_linear_beta_schedule(std::vector<double> &schedule,
                                    double beta_min, double beta_max,
                                    uint num_iter) {
  for (auto i = 0; i < num_iter; i++) {
    schedule[i] = beta_min + beta_max * i / static_cast<double>(num_iter - 1);
  }
}

/*!
 * @brief Constructs a geometric beta schedule.
 *
 * @param a reference to a vector for storing the schedule
 * @param minimum value of beta in the annealing schedule.
 * @param maximum value of beta in the annealing schedule.
 * @param number of iterations of the algorithm.
 *
 */
void construct_geometric_beta_schedule(std::vector<double> &schedule,
                                       double beta_min, double beta_max,
                                       uint num_iter) {
  schedule[0] = beta_min;
  auto alpha = pow(beta_max / beta_min, 1.0 / (num_iter - 1));
  for (auto i = 1; i < num_iter; i++) {
    schedule[i] = (schedule[i - 1]) * alpha;
  }
}

/*!
 * @brief Compute QUBO solution in a give solution space on a given device.
 *
 * @param Instance of the proble to solve.
 * @param Accelerator device type.
 * @param beta schedule type linear or geometric.
 * @param seed for random number generator.
 * @param minimum value of beta in the annealing schedule.
 * @param maximum value of beta in the annealing schedule.
 * @param number of iterations of the algorithm.
 * @param number of trajectories to try.
 *
 * @returns solution to the given QUBO problem and acclerator device name.
 */
std::tuple<qubo::Solution, std::string> sycl_native_anneal(qubo::QUBOModel<int, double> instance, std::string device_type, std::string schedule_type, std::uint64_t seed, double beta_min, double beta_max, uint num_iter, uint num_tries)
{
  queue_ptr q_ptr;
  std::string accl_device_name;

  try {
    q_ptr = queue_ptr(
        new sycl::queue(*devices::construct_device_selector(device_type)));
  } catch (std::runtime_error) {
    std::cerr << "No devices of given type could be initialized."
              << std::endl;
  }

  accl_device_name = q_ptr->get_device().get_info<sycl::info::device::name>();
  // std::cout << "Node ID [" << machine_name << "], " << "Rank [" << rank << "], " << "Using device: "
  //           << q_ptr->get_device().get_info<sycl::info::device::name>()
  //           << std::endl;

  std::vector<double> beta_schedule(num_iter);

  if (schedule_type == "linear") {
    construct_linear_beta_schedule(beta_schedule, beta_min, beta_max,
                                   num_iter);
  } else {
    construct_geometric_beta_schedule(beta_schedule, beta_min, beta_max,
                                      num_iter);
  }

  auto solution =
      sa::anneal(instance, *q_ptr, beta_schedule, seed, num_iter, num_tries);

  return {solution, accl_device_name};
}

/*!
 * @brief Parse command line.
 * @param argument count.
 * @param arguments array.
 *
 * @returns returns input file name, output file name, device type, beta_min, beta_max,
 *                  number of iteration and number of tries.
 */
std::tuple<std::string, std::string, std::string, std::string, double, double, uint, uint> parse_cmd_line_anneal(int argc, char *argv[])
{
  std::string input_file;
  std::string output_file;
  std::string schedule_type;
  std::string device_type;

  uint num_iter;
  uint num_tries;

  double beta_min, beta_max;

  po::options_description options("Allowed options");
  options.add_options()("help", "produce help message")(
     "input", po::value<std::string>(&input_file), "input file")(
     "output", po::value<std::string>(&output_file), "output file")(
     "num-iter", po::value<uint>(&num_iter)->default_value(100),
     "number of iterations of the algorithm")(
     "num-tries", po::value<uint>(&num_tries)->default_value(100),
     "number of trajectories to try")(
     "schedule-type",
     po::value<std::string>(&schedule_type)->default_value("geometric"),
     "type of beta schedule to use, either linear or geometric")(
     "beta-min", po::value<double>(&beta_min)->default_value(0.1),
     "minimum value of beta in the annealing schedule (default 0.1)")(
     "beta-max", po::value<double>(&beta_max)->default_value(1.0),
     "maximum value of beta in the annealing schedule (default 1.0)")(
     "device-type",
     po::value<std::string>(&device_type)->default_value("host"),
     "device type to use (cpu, gpu or host)");

  po::variables_map vm;
  po::store(po::parse_command_line(argc, argv, options), vm);
  po::notify(vm);

  if (vm.count("help")) {
    std::cout << options << std::endl;
    MPI_Abort(MPI_COMM_WORLD, MPI_SUCCESS);
  }

  if (!vm.count("input")) {
    throw std::runtime_error("No input file provided.");
  }

  if (!vm.count("output")) {
    throw std::runtime_error("No output file provided.");
  }

  if (device_type != "cpu" && device_type != "gpu" && device_type != "host") {
    throw std::runtime_error("Unknown device type: " + device_type);
  }

  if (schedule_type != "linear" && schedule_type != "geometric") {
    throw std::runtime_error("Unknown beta schedule: " + schedule_type);
  }

  if (beta_max < 0 || beta_min < 0) {
    throw std::runtime_error("Invalid schedule, both ends of beta range need to be positive.");
  }

  if (beta_min >= beta_max) {
    throw std::runtime_error("Invalid schedule, initial beta is not lesser than final beta.");
  }

  return {input_file, output_file, device_type, schedule_type, beta_min, beta_max, num_iter, num_tries};
}

int main(int argc, char *argv[]) {
  const clock_t begin_time = clock();

  char machine_name[MPI_MAX_PROCESSOR_NAME];
  int name_len = 0;
  int rank = 0;
  const int root_rank = 0; // Rank zero process.
  int min_energy_process_rank = 0; // Rank of process with minimum energy.
  int num_procs = 0;
  int size = 0;

  long long int msg_size = 0;

  std::string input_file;
  std::string output_file;
  std::string schedule_type;
  std::string device_type;
  std::vector<char> msg_buff;
  std::uint64_t *seed_buff = NULL;
  std::uint64_t seed;

  uint num_iter;
  uint num_tries;
  uint param_buff[2];

  double beta_min, beta_max;
  double beta_buff[2];

  qubo::QUBOModel<int, double> instance;

  try {
    // Start MPI.
    if (MPI_Init(&argc, &argv) != MPI_SUCCESS) {
      throw std::runtime_error("Failed to initialize MPI\n");
    }

    // Create the communicator, and retrieve the number of MPI ranks.
    MPI_Comm_size(MPI_COMM_WORLD, &num_procs);

    // Determine the rank number.
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);

    // Get the machine name.
    MPI_Get_processor_name(machine_name, &name_len);

    if (rank == root_rank) {
      // Parse command line to get the parameters.
      std::tie(input_file, output_file, device_type, schedule_type, beta_min, beta_max, num_iter, num_tries) = parse_cmd_line_anneal(argc, argv);

      std::cout << "Reading input from: " << input_file << std::endl;

      std::cout << "Output will be saved to: " << output_file << std::endl;

      std::cout << "Schedule type: " << schedule_type << std::endl;

      std::cout << "Beta range: [" << beta_min << ", " << beta_max << "]"
                << std::endl;

      std::cout << "Number of iterations: " << num_iter << std::endl;
      std::cout << "Number of tries: " << num_tries << std::endl;

      std::ifstream qubo_file(input_file);

      if (!qubo_file) {
        std::cerr << "can not open input file: " << input_file << std::endl;
        return -1;
      }

      qubo_file.unsetf(std::ios::skipws);
      instance = qubo::QUBOModel<int, double>::load(qubo_file);

      std::ostringstream oss;
      // save data to archive
      {
         boost::archive::text_oarchive ar(oss);
         // write class instance to archive
         ar << instance;
         // archive and stream closed when destructors are called.
      }
#ifdef DEBUG
      std::cout << "sSize: " << oss.str().size() << std::endl;
      std::cout << "sData: " << oss.str().c_str() << std::endl;
#endif
      msg_buff.assign(oss.str().c_str(), oss.str().c_str() + oss.str().size() +1);
      msg_size = oss.str().size();
    }

    // Send QUBOModel instance to all the proecesses.
    if (MPI_Bcast(&msg_size, 1, MPI_LONG_LONG_INT, root_rank, MPI_COMM_WORLD) != MPI_SUCCESS) {
      throw std::runtime_error("MPI broadcast failed\n");
    }

    if (rank != root_rank) {
      msg_buff.resize(msg_size);
    }

    if (MPI_Bcast(msg_buff.data(), msg_size, MPI_CHAR, root_rank, MPI_COMM_WORLD) != MPI_SUCCESS) {
      throw std::runtime_error("MPI broadcast failed\n");
    }

    if (rank != root_rank) {
      if (msg_buff.size() > 0) {
        std::istringstream iss(std::string(msg_buff.data(), msg_buff.size()));
#ifdef DEBUG
        std::cout << "rSize: " << iss.str().size() << std::endl;
        std::cout << "rData: " << iss.str().c_str() << std::endl;
#endif
        {
          boost::archive::text_iarchive ar(iss);
          ar >> instance;
        }
      }
    }

    // Send device_type to all the processes.
    if (rank == root_rank) {
      msg_size = device_type.size();
      msg_buff.assign(device_type.c_str(),device_type.c_str() + device_type.size() +1);
    }

    if (MPI_Bcast(&msg_size, 1, MPI_LONG_LONG_INT, root_rank, MPI_COMM_WORLD) != MPI_SUCCESS) {
      throw std::runtime_error("MPI broadcast failed\n");
    }

    if (rank != root_rank) {
      msg_buff.resize(msg_size);
    }

    if (MPI_Bcast(msg_buff.data(), msg_size, MPI_CHAR, root_rank, MPI_COMM_WORLD) != MPI_SUCCESS) {
      throw std::runtime_error("MPI broadcast failed\n");
    }

    if (rank != root_rank) {
      device_type = std::string(&msg_buff[0], msg_size);
#ifdef DEBUG
      std::cout << "device_type: " << device_type << std::endl;
#endif
    }

    // Send schedule_type to all the processes.
    if (rank == root_rank) {
      msg_size = schedule_type.size();
      msg_buff.assign(schedule_type.c_str(),schedule_type.c_str() + schedule_type.size() +1);
    }

    if (MPI_Bcast(&msg_size, 1, MPI_LONG_LONG_INT, root_rank, MPI_COMM_WORLD) != MPI_SUCCESS) {
      throw std::runtime_error("MPI broadcast failed\n");
    }

    if (rank != root_rank) {
      msg_buff.resize(msg_size);
    }

    if (MPI_Bcast(msg_buff.data(), msg_size, MPI_CHAR, root_rank, MPI_COMM_WORLD) != MPI_SUCCESS) {
      throw std::runtime_error("MPI broadcast failed\n");
    }

    if (rank != root_rank) {
      schedule_type = std::string(&msg_buff[0], msg_size);
#ifdef DEBUG
      std::cout << "schedule_type: " << schedule_type << std::endl;
#endif
    }

    // Send beta_min and beta_max to all the processes.
    if(rank == root_rank){
      beta_buff[0] = beta_min;
      beta_buff[1] = beta_max;
    }

    if (MPI_Bcast(&beta_buff, 2, MPI::DOUBLE, root_rank, MPI_COMM_WORLD) != MPI_SUCCESS) {
      throw std::runtime_error("MPI broadcast failed\n");
    }

    if (rank != root_rank) {
      beta_min = beta_buff[0];
      beta_max = beta_buff[1];
#ifdef DEBUG
      std::cout << "Beta range: [" << beta_min << ", " << beta_max << "]"
                << std::endl;
#endif
    }

    // Send num_iter and num_tries to all the processes.
    if (rank == root_rank) {
      param_buff[0] = num_iter;
      param_buff[1] = num_tries;
    }

    if (MPI_Bcast(&param_buff, 2, MPI_UNSIGNED, root_rank, MPI_COMM_WORLD) != MPI_SUCCESS) {
      throw std::runtime_error("MPI broadcast failed\n");
    }

    if (rank != root_rank) {
      num_iter = param_buff[0];
      num_tries = param_buff[1];
#ifdef DEBUG
      std::cout << "Number of iterations: " << num_iter << std::endl;
      std::cout << "Number of tries: " << num_tries << std::endl;
#endif
    }

    // Send the seed for random number generator to all the processes.
    if (rank == root_rank) {
      seed_buff = new std::uint64_t[num_procs];
      // Seed generation using mt19937_64 i.e 64 bit Mersenne Twister by Matsumoto, 2000.
      //std::random_device rd; // for non-deterministic generator uncomment this line and
      std::mt19937_64 gen(SEED); // replace SEED with rd() in mersenne twister.

      for (auto i=0; i < num_procs; ++i) {
        seed_buff[i] = gen();
#ifdef DEBUG
        std::cout << "seed: " << seed_buff[i] << std::endl;
#endif
      }
    }

    if (MPI_Scatter(seed_buff, 1, MPI_INT64_T, &seed, 1, MPI_INT64_T, root_rank, MPI_COMM_WORLD) != MPI_SUCCESS) {
      throw std::runtime_error("MPI scatter failed\n");
    }

    if (rank == root_rank) {
      delete [] seed_buff;
    }

#ifdef DEBUG
    std::cout << "seed: " << seed << std::endl;
#endif

    auto [solution, accl_device_name] = sycl_native_anneal(instance, device_type, schedule_type, seed, beta_min, beta_max, num_iter, num_tries);

    if(rank == root_rank){
      //std::unique_ptr<char> log_buff_ptr(new char[100]);
      MPI_Status status;
      int msg_length;

      //char log_buff_ptr[100];
      std::cout << "Node ID [" << machine_name << "], " << "Rank [" << rank << "], " << "Using device: " << accl_device_name << std::endl;
      for(auto rank_indx = 1; rank_indx < num_procs; ++rank_indx){
        MPI_Probe(rank_indx, 1, MPI_COMM_WORLD, &status);
        MPI_Get_count(&status, MPI_CHAR, &msg_length);

        std::unique_ptr<char> log_buff_ptr(new char[msg_length]{});

        MPI_Recv(log_buff_ptr.get(), msg_length, MPI_CHAR, rank_indx, 1 , MPI_COMM_WORLD, MPI_STATUS_IGNORE);
        std::string log_msg(log_buff_ptr.get());
        //std::cout << log_buff_ptr.get() << std::endl;
        std::cout << log_msg;
      }
    }else{
      std::ostringstream log_stream;
      log_stream << "Node ID [" << machine_name << "], " << "Rank [" << rank << "], " << "Using device: " << accl_device_name << std::endl;
      MPI_Send(log_stream.str().c_str(), log_stream.str().size(), MPI_CHAR, root_rank, 1, MPI_COMM_WORLD);
    }

    std::unique_ptr<double> energy_buff_ptr(new double[num_procs]);

    if (MPI_Gather(&solution.energy, 1, MPI_DOUBLE, energy_buff_ptr.get(), 1, MPI_DOUBLE, root_rank, MPI_COMM_WORLD) != MPI_SUCCESS) {
      throw std::runtime_error("MPI gather failed\n");
    }

    if (rank == root_rank) {
       std::vector<double> energies(&energy_buff_ptr.get()[0], &energy_buff_ptr.get()[num_procs]);
       auto min_energy = std::min_element(energies.begin(), energies.end());
       auto min_idx_energy = std::distance(energies.begin(), min_energy);
#ifdef DEBUG
       std::cout << "min_energy: " << energies[min_idx_energy] << std::endl;
#endif
       solution.energy = energies[min_idx_energy];

       min_energy_process_rank = min_idx_energy;
    }

    if (MPI_Bcast(&min_energy_process_rank, 1, MPI_INT, root_rank, MPI_COMM_WORLD) != MPI_SUCCESS) {
      throw std::runtime_error("MPI broadcast failed\n");
    }

    if ((min_energy_process_rank != root_rank) && (rank == min_energy_process_rank)) {
       auto state = solution.state;
       if (MPI_Send(state.data(), state.size(), MPI_CHAR, root_rank, 0, MPI_COMM_WORLD) != MPI_SUCCESS) {
         throw std::runtime_error("MPI send failed\n");
        }
    }

    if (rank == root_rank) {
      if (min_energy_process_rank != 0) {
        MPI_Status status;
        int msg_length;

        // Probe msg size
        MPI_Probe(min_energy_process_rank, 0, MPI_COMM_WORLD, &status);
        MPI_Get_count(&status, MPI_CHAR, &msg_length);

        std::unique_ptr<char> buff_ptr(new char[msg_length]{});

        if (MPI_Recv(buff_ptr.get(), msg_length, MPI_CHAR, min_energy_process_rank, root_rank, MPI_COMM_WORLD, MPI_STATUS_IGNORE) != MPI_SUCCESS) {
         throw std::runtime_error("MPI receive failed\n");
        }
        //std::cout << buff_ptr.get();
#ifdef DEBUG
        std::cout << "Min Energy State: ";
        for (auto i = 0; i < solution.state.size(); ++i) {
        std::cout << (int)buff_ptr.get()[i] << " ";
        }
        std::cout << std::endl;
#endif
        for (auto i=0; i < solution.state.size(); ++i ) {
          solution.state[i] = buff_ptr.get()[i];
        }
      }

#ifdef DEBUG
      std::cout << "Min Energy State: "
      for (auto i = 0; i < solution.state.size(); ++i) {
        std::cout << (int)solution.state[i] << " ";
      }
      std::cout << std::endl;
#endif
      std::ofstream results_file(output_file);
      solution.save(results_file);
      results_file.close();
    }

  } catch (std::exception &e) {
    std::cerr << "error: " << e.what() << "\n";
    MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE);
  } catch (...) {
    std::cerr << "Exception of unknown type!\n";
  }

  if (rank == root_rank) {
    std::cout << "Calculation time [s]: "<< float( clock () - begin_time ) /  CLOCKS_PER_SEC << "\n";
  }

  MPI_Finalize();
  return 0;
}


#include <list>
#include <algorithm>
#include <math.h>

#include <boost/numeric/mtl/mtl.hpp>
#include <boost/numeric/mtl/utility/tag.hpp>
#include <boost/numeric/mtl/utility/category.hpp>
#include <boost/numeric/linear_algebra/identity.hpp>

#include "FixVec.hpp"
#include "Boundary.hpp"
#include "DOFAdmin.hpp"
#include "ElInfo.hpp"
// #include "Expressions.hpp"
#include "FiniteElemSpace.hpp"
#include "Global.hpp"
#include "Mesh.hpp"
#include "Quadrature.hpp"
#include "BoundaryManager.hpp"
#include "Assembler.hpp"
#include "Operator.hpp"
#include "Initfile.hpp"
#include "Traverse.hpp"
// #include "DualTraverse.hpp"
#include "MatrixVectorOperations.hpp"

#ifdef HAVE_PARALLEL_DOMAIN_AMDIS
#include "parallel/MpiHelper.hpp"
#include "parallel/MeshDistributor.hpp"
#endif

#include <expressions/Expressions.hpp>

// Defining the interface for MTL4
namespace mtl
{
  // Let MTL4 know that DOFVector it is a column vector
  namespace traits
  {
    template <class T>
    struct category<AMDiS::DOFVector<T>>
    {
      typedef tag::dense_col_vector type;
    };
  } // end namepsace traits

  namespace ashape
  {
    template <class T>
    struct ashape<AMDiS::DOFVector<T>>
    {
      typedef cvec<typename ashape<T>::type> type;
    };
  } // end namespace ashape

  // Modelling Collection and MutableCollection
  template <class T>
  struct Collection<AMDiS::DOFVector<T>>
  {
    typedef T               value_type;
    typedef const T&        const_reference;
    typedef std::size_t     size_type;
  };

  template <typename T>
  struct MutableCollection<AMDiS::DOFVector<T>>
        : public Collection<AMDiS::DOFVector<T>>
  {
    typedef T&              reference;
  };

} // namespace mtl

namespace AMDiS
{
  template <class T>
  DOFVector<T>::DOFVector(const FiniteElemSpace* f, std::string n, bool addToSynch)
    : DOFVectorBase<T>(f, n)
  {
    vec.resize(0);
    init(f, n, addToSynch);
  }

  template <class T>
  void DOFVector<T>::init(const FiniteElemSpace* f, std::string n, bool /*addToSynch*/) // NOTE: parameter addToSynch only for PARALLEL_DOMAIN
  {
    this->name = n;
    this->feSpace = f;
    if (this->feSpace && this->feSpace->getAdmin())
      (this->feSpace->getAdmin())->addDOFIndexed(this);
    this->boundaryManager = new BoundaryManager(f);
#ifdef HAVE_PARALLEL_DOMAIN_AMDIS
    if (addToSynch && Parallel::MeshDistributor::globalMeshDistributor != NULL)
      Parallel::MeshDistributor::globalMeshDistributor->addInterchangeVector(this);
#endif
  }

  template <class T>
  DOFVector<T>::~DOFVector()
  {
#ifdef HAVE_PARALLEL_DOMAIN_AMDIS
    if ( Parallel::MeshDistributor::globalMeshDistributor != NULL)
      Parallel::MeshDistributor::globalMeshDistributor->removeInterchangeVector(this);
#endif
    if (this->feSpace && this->feSpace->getAdmin())
      (this->feSpace->getAdmin())->removeDOFIndexed(this);

    if (this->boundaryManager)
      delete this->boundaryManager;

    vec.clear();
  }

  template <class T>
  double DOFVector<T>::squareNrm2() const
  {
    checkFeSpace(this->feSpace, vec);

    double nrm = 0.0;
    Iterator vecIterator(dynamic_cast<DOFIndexed<T>*>(const_cast<DOFVector<T>*>(this)),
                         USED_DOFS);
    for (vecIterator.reset(); !vecIterator.end(); ++vecIterator)
      nrm += (*vecIterator) * (*vecIterator);

#ifdef HAVE_PARALLEL_DOMAIN_AMDIS
    Parallel::mpi::globalAdd(nrm);
#endif

    return nrm;
  }

  template <class T>
  double DOFVector<T>::nrm2() const
  {
    return std::sqrt(squareNrm2());
  }

  template <class T>
  T DOFVector<T>::asum() const
  {
    checkFeSpace(this->feSpace, vec);

    double nrm = 0.0;
    Iterator vecIterator(dynamic_cast<DOFIndexed<T>*>(const_cast<DOFVector<T>*>(this)),
                         USED_DOFS);
    for (vecIterator.reset(); !vecIterator.end(); ++vecIterator)
      nrm += std::abs(*vecIterator);

#ifdef HAVE_PARALLEL_DOMAIN_AMDIS
    Parallel::mpi::globalAdd(nrm);
#endif

    return nrm;
  }

  template <class T>
  T DOFVector<T>::sum() const
  {
    checkFeSpace(this->feSpace, vec);

    double nrm = 0.0;
    Iterator vecIterator(dynamic_cast<DOFIndexed<T>*>(const_cast<DOFVector<T>*>(this)),
                         USED_DOFS);
    for (vecIterator.reset(); !vecIterator.end(); ++vecIterator)
      nrm += *vecIterator;

#ifdef HAVE_PARALLEL_DOMAIN_AMDIS
    Parallel::mpi::globalAdd(nrm);
#endif

    return nrm;
  }

  template <class T>
  void DOFVector<T>::set(T alpha)
  {
    checkFeSpace(this->feSpace, vec);

    Iterator vecIterator(dynamic_cast<DOFIndexed<T>*>(this), USED_DOFS);
    for (vecIterator.reset(); !vecIterator.end(); ++vecIterator)
      *vecIterator = alpha ;
  }

  template <class T>
  void DOFVector<T>::copy(DOFVector<T> const& x)
  {
    FUNCNAME_DBG("DOFVector<T>::copy()");

    checkFeSpace(this->feSpace, vec);

    TEST_EXIT_DBG(static_cast<int>(x.vec.size()) >=
                  this->feSpace->getAdmin()->getUsedSize())
    ("x.size = %d too small: admin->sizeUsed = %d\n", x.vec.size(),
     this->feSpace->getAdmin()->getUsedSize());

    Iterator vecIterator(dynamic_cast<DOFIndexed<T>*>(this), USED_DOFS);
    Iterator xIterator(dynamic_cast<DOFVector<T>*>(const_cast<DOFVector<T>*>(&x)),
                       USED_DOFS);
    for (vecIterator.reset(), xIterator.reset(); !vecIterator.end();
         ++vecIterator, ++xIterator)
      *vecIterator = *xIterator;
  }

  template <class T>
  T DOFVector<T>::min() const
  {
    checkFeSpace(this->feSpace, vec);

    T m;
    Iterator vecIterator(const_cast<DOFIndexed<T>*>(dynamic_cast<const DOFIndexed<T>*>(this)), USED_DOFS);
    for (vecIterator.reset(), m = *vecIterator; !vecIterator.end(); ++vecIterator)
      m = std::min(m, *vecIterator);

#ifdef HAVE_PARALLEL_DOMAIN_AMDIS
    Parallel::mpi::globalMin(m);
#endif

    return m;
  }

  template <class T>
  T DOFVector<T>::max() const
  {
    checkFeSpace(this->feSpace, vec);

    T m;
    Iterator vecIterator(const_cast<DOFIndexed<T>*>(dynamic_cast<const DOFIndexed<T>*>(this)), USED_DOFS);
    for (vecIterator.reset(), m = *vecIterator; !vecIterator.end(); ++vecIterator)
      m = std::max(m, *vecIterator);

#ifdef HAVE_PARALLEL_DOMAIN_AMDIS
    Parallel::mpi::globalMax(m);
#endif

    return m;
  }

  template <class T>
  T DOFVector<T>::absMax() const
  {
    return std::max(std::abs(max()), std::abs(min()));
  }

  template <class T>
  T DOFVector<T>::average() const
  {
    checkFeSpace(this->feSpace, vec);

    int count = 0;
    T m = 0;
    Iterator vecIterator(const_cast<DOFIndexed<T>*>(dynamic_cast<const DOFIndexed<T>*>(this)), USED_DOFS);
    for (vecIterator.reset(); !vecIterator.end(); ++vecIterator)
    {
      m += *vecIterator;
      count++;
    }

#ifdef HAVE_PARALLEL_DOMAIN_AMDIS
    Parallel::mpi::globalAdd(m);
    Parallel::mpi::globalAdd(count);
#endif

    return m / count;
  }

  template <class T>
  size_t DOFVector<T>::calcMemoryUsage() const
  {
    size_t result = 0;
    result += sizeof(DOFVector<T>);
    result += vec.size() * sizeof(T);

    return result;
  }

  template <class T>
  T DOFVector<T>::Int(int meshLevel, Quadrature* q) const
  {
    Mesh* mesh = this->feSpace->getMesh();

    if (!q)
    {
      int deg = 2 * this->feSpace->getBasisFcts()->getDegree();
      q = Quadrature::provideQuadrature(this->dim, deg);
    }

    FastQuadrature* quadFast =
      FastQuadrature::provideFastQuadrature(this->feSpace->getBasisFcts(), *q, INIT_PHI);

    Flag flag = Mesh::FILL_COORDS | Mesh::FILL_DET;
    if (meshLevel == -1)
      flag |= Mesh::CALL_LEAF_EL;
    else
      flag |= Mesh::CALL_EL_LEVEL;

    T result;
    nullify(result);
    int nPoints = quadFast->getNumPoints();
    DenseVector<T> uh_vec(nPoints);
    TraverseStack stack;
    ElInfo* elInfo =  stack.traverseFirst(mesh, meshLevel, flag);
    while (elInfo)
    {
      double det = elInfo->getDet();
      T normT;
      nullify(normT);
      this->getVecAtQPs(elInfo, NULL, quadFast, uh_vec);
      for (int iq = 0; iq < nPoints; iq++)
        normT += quadFast->getWeight(iq) * (uh_vec[iq]);
      result += det * normT;

      elInfo = stack.traverseNext(elInfo);
    }

#ifdef HAVE_PARALLEL_DOMAIN_AMDIS
    Parallel::mpi::globalAdd(result);
#endif

    return result;
  }

  template <class T>
  double DOFVector<T>::L1Norm(Quadrature* q) const
  {
    Mesh* mesh = this->feSpace->getMesh();

    if (!q)
    {
      int deg = 2 * this->feSpace->getBasisFcts()->getDegree();
      q = Quadrature::provideQuadrature(this->dim, deg);
    }

    FastQuadrature* quadFast =
      FastQuadrature::provideFastQuadrature(this->feSpace->getBasisFcts(), *q, INIT_PHI);

    double result = 0.0;
    int nPoints = quadFast->getNumPoints();
    DenseVector<T> uh_vec(nPoints);
    TraverseStack stack;
    ElInfo* elInfo =
      stack.traverseFirst(mesh, -1,
                          Mesh::CALL_LEAF_EL | Mesh::FILL_COORDS | Mesh::FILL_DET);
    while (elInfo)
    {
      double det = elInfo->getDet();
      double normT = 0.0;
      this->getVecAtQPs(elInfo, NULL, quadFast, uh_vec);
      for (int iq = 0; iq < nPoints; iq++)
        normT += quadFast->getWeight(iq) * std::abs(uh_vec[iq]);
      result += det * normT;

      elInfo = stack.traverseNext(elInfo);
    }

#ifdef HAVE_PARALLEL_DOMAIN_AMDIS
    Parallel::mpi::globalAdd(result);
#endif

    return result;
  }

  template <class T>
  double DOFVector<T>::L2NormSquare(Quadrature* q) const
  {
    using math::sqr;
    Mesh* mesh = this->feSpace->getMesh();

    if (!q)
    {
      int deg = 2 * this->feSpace->getBasisFcts()->getDegree();
      q = Quadrature::provideQuadrature(this->dim, deg);
    }

    FastQuadrature* quadFast =
      FastQuadrature::provideFastQuadrature(this->feSpace->getBasisFcts(), *q, INIT_PHI);

    double result = 0.0;
    int nPoints = quadFast->getNumPoints();
    DenseVector<T> uh_vec(nPoints);
    TraverseStack stack;
    ElInfo* elInfo =
      stack.traverseFirst(mesh, -1,
                          Mesh::CALL_LEAF_EL | Mesh::FILL_COORDS | Mesh::FILL_DET);
    while (elInfo)
    {
      double det = elInfo->getDet();
      double normT = 0.0;
      this->getVecAtQPs(elInfo, NULL, quadFast, uh_vec);
      for (int iq = 0; iq < nPoints; iq++)
        normT += quadFast->getWeight(iq) * sqr(uh_vec[iq]);
      result += det * normT;

      elInfo = stack.traverseNext(elInfo);
    }

#ifdef HAVE_PARALLEL_DOMAIN_AMDIS
    Parallel::mpi::globalAdd(result);
#endif

    return result;
  }

  template <class T>
  double DOFVector<T>::H1NormSquare(Quadrature* q) const
  {
    using math::sqr;
    Mesh* mesh = this->feSpace->getMesh();

    if (!q)
    {
      int deg = 2 * this->feSpace->getBasisFcts()->getDegree() - 2;
      q = Quadrature::provideQuadrature(this->dim, deg);
    }

    FastQuadrature* quadFast =
      FastQuadrature::provideFastQuadrature(this->feSpace->getBasisFcts(), *q, INIT_GRD_PHI);

    double result = 0.0;
    int nPoints = quadFast->getNumPoints();
    int dimOfWorld = Global::getGeo(WORLD);
    DenseVector<WorldVector<T>> grduh_vec(nPoints);
    TraverseStack stack;
    ElInfo* elInfo =
      stack.traverseFirst(mesh, -1,
                          Mesh::CALL_LEAF_EL | Mesh::FILL_COORDS |
                          Mesh::FILL_DET | Mesh::FILL_GRD_LAMBDA);
    while (elInfo)
    {
      double det = elInfo->getDet();
      double normT = 0.0;
      this->getGrdAtQPs(elInfo, NULL, quadFast, grduh_vec);

      for (int iq = 0; iq < nPoints; iq++)
      {
        double norm2 = 0.0;
        for (int j = 0; j < dimOfWorld; j++)
          norm2 += sqr(grduh_vec[iq][j]);
        normT += quadFast->getWeight(iq) * norm2;
      }

      result += det * normT;

      elInfo = stack.traverseNext(elInfo);
    }

#ifdef HAVE_PARALLEL_DOMAIN_AMDIS
    Parallel::mpi::globalAdd(result);
#endif

    return result;
  }

  template <class T>
  bool DOFVector<T>::getDofIdxAtPoint(WorldVector<double>& p,
                                      DegreeOfFreedom& idx,
                                      ElInfo* oldElInfo,
                                      bool useOldElInfo) const
  {
    Mesh* mesh = this->feSpace->getMesh();
    const BasisFunction* basFcts = this->feSpace->getBasisFcts();

    int dim = mesh->getDim();
    int numBasFcts = basFcts->getNumber();

    std::vector<DegreeOfFreedom> localIndices(numBasFcts);
    DimVec<double> lambda(dim);

    ElInfo* elInfo = mesh->createNewElInfo();
    idx = 0;
    int inside = 0;

    if (oldElInfo && useOldElInfo && oldElInfo->getMacroElement())
    {
      inside = mesh->findElInfoAtPoint(p, elInfo, lambda, oldElInfo->getMacroElement(), NULL, NULL);
      delete oldElInfo;
    }
    else
    {
      inside = mesh->findElInfoAtPoint(p, elInfo, lambda, NULL, NULL, NULL);
    }

    if (oldElInfo)
      oldElInfo = elInfo;

    if (inside == 0)
    {
      delete elInfo;
      return false;
    }

    basFcts->getLocalIndices(elInfo->getElement(), this->feSpace->getAdmin(), localIndices);

    WorldVector<double> coord;
    int minIdx = -1;
    double minDist = 1.e15;

    for (int i = 0; i < numBasFcts; i++)
    {
      elInfo->coordToWorld(*(basFcts->getCoords(i)), coord);
      double newDist = norm(coord - p);
      if (newDist < minDist)
      {
        minIdx = i;
        minDist = newDist;
        if (minDist < 1.e-10)
          break;
      }
    }

    if (minIdx >= 0)
      idx = localIndices[minIdx];

    if(!oldElInfo) delete elInfo;
    return inside != 0;
  }

  template <class T>
  void DOFVector<T>::compressDOFIndexed(int first, int last,
                                        std::vector<DegreeOfFreedom>& newDOF)
  {
    for (int i = first; i <= last; i++)
      if (newDOF[i] >= 0)
        vec[newDOF[i]] = vec[i];
  }

  template <class T>
  DOFVector<T>& DOFVector<T>::operator=(const DOFVector<T>& rhs)
  {
    this->feSpace = rhs.feSpace;
    this->dim = rhs.dim;
    this->nBasFcts = rhs.nBasFcts;
    vec = rhs.vec;
    this->elementVector.change_dim(this->nBasFcts);
    this->operators = rhs.operators;
    this->operatorFactor = rhs.operatorFactor;

    if (rhs.boundaryManager)
    {
      if (this->boundaryManager)
        delete this->boundaryManager;

      this->boundaryManager = new BoundaryManager(*rhs.boundaryManager);
    }
    else
    {
      this->boundaryManager = NULL;
    }

    return *this;
  }

  template <class T>
  double DOFVector<T>::DoubleWell(Quadrature* q) const
  {
    using math::sqr;
    Mesh* mesh = this->feSpace->getMesh();

    if (!q)
    {
      int deg = 2 * this->feSpace->getBasisFcts()->getDegree();
      q = Quadrature::provideQuadrature(this->dim, deg);
    }

    FastQuadrature* quadFast =
      FastQuadrature::provideFastQuadrature(this->feSpace->getBasisFcts(), *q, INIT_PHI);

    double result = 0.0;
    int nPoints = quadFast->getNumPoints();
    DenseVector<T> uh_vec(nPoints);
    TraverseStack stack;
    ElInfo* elInfo =
      stack.traverseFirst(mesh, -1,
                          Mesh::CALL_LEAF_EL | Mesh::FILL_COORDS | Mesh::FILL_DET);
    while (elInfo)
    {
      double det = elInfo->getDet();
      double normT = 0.0;
      this->getVecAtQPs(elInfo, NULL, quadFast, uh_vec);
      for (int iq = 0; iq < nPoints; iq++)
        normT += quadFast->getWeight(iq) * sqr(uh_vec[iq]) * sqr(1.0 - uh_vec[iq]);
      result += det * normT;

      elInfo = stack.traverseNext(elInfo);
    }

#ifdef HAVE_PARALLEL_DOMAIN_AMDIS
    Parallel::mpi::globalAdd(result);
#endif

    return result;
  }

  template <class T>
  DOFVector<Gradient_t<T>>*
                        DOFVector<T>::getGradient(DOFVector<Gradient_t<T>>* grad) const
  {
    FUNCNAME_DBG("DOFVector<T>::getGradient()");
    const FiniteElemSpace* feSpace = DOFVector<T>::feSpace;

    // define result vector
    static DOFVector<Gradient_t<T>>* result = NULL; // TODO: REMOVE STATIC

    if (grad)
    {
      result = grad;
    }
    else
    {
      if(result && result->getFeSpace() != feSpace)
      {
        delete result;
        result = new DOFVector<Gradient_t<T>>(feSpace, "gradient");
      }
    }

    Mesh* mesh = feSpace->getMesh();
    int dim = mesh->getDim();
    const BasisFunction* basFcts = feSpace->getBasisFcts();
    DOFAdmin* admin = feSpace->getAdmin();

    // count number of nodes and dofs per node
    std::vector<int> nNodeDOFs;
    std::vector<int> nNodePreDofs;
    std::vector<DimVec<double>*> bary;

    int nNodes = 0;
    int nDofs = 0;

    for (int i = 0; i < dim + 1; i++)
    {
      GeoIndex geoIndex = INDEX_OF_DIM(i, dim);
      int nPositions = mesh->getGeo(geoIndex);
      int numPreDofs = admin->getNumberOfPreDofs(i);
      for (int j = 0; j < nPositions; j++)
      {
        int dofs = basFcts->getNumberOfDofs(geoIndex);
        nNodeDOFs.push_back(dofs);
        nDofs += dofs;
        nNodePreDofs.push_back(numPreDofs);
      }
      nNodes += nPositions;
    }

    TEST_EXIT_DBG(nDofs == basFcts->getNumber())
    ("number of dofs != number of basis functions\n");

    for (int i = 0; i < nDofs; i++)
      bary.push_back(basFcts->getCoords(i));

    DenseVector<T> localUh(basFcts->getNumber());

    // traverse mesh
    std::vector<bool> visited(getUsedSize(), false);
    TraverseStack stack;
    Flag fillFlag = Mesh::CALL_LEAF_EL | Mesh::FILL_GRD_LAMBDA | Mesh::FILL_COORDS;
    ElInfo* elInfo = stack.traverseFirst(mesh, -1, fillFlag);

    while (elInfo)
    {
      const DegreeOfFreedom** dof = elInfo->getElement()->getDof();
      auto& grdLambda = elInfo->getGrdLambda();
      this->getLocalVector(elInfo->getElement(), localUh);

      int localDOFNr = 0;
      for (int i = 0; i < nNodes; i++)   // for all nodes
      {
        for (int j = 0; j < nNodeDOFs[i]; j++)   // for all dofs at this node
        {
          DegreeOfFreedom dofIndex = dof[i][nNodePreDofs[i] + j];
          if (!visited[dofIndex])
          {
            basFcts->evalGrdUh(*(bary[localDOFNr]), grdLambda,
                               localUh, ((*result)[dofIndex]));

            visited[dofIndex] = true;
          }
          localDOFNr++;
        }
      }

      elInfo = stack.traverseNext(elInfo);
    }

    return result;
  }

  template <class T>
  DOFVector<Gradient_t<T>>*
                        DOFVector<T>::getRecoveryGradient(DOFVector<Gradient_t<T>>* grad) const
  {
    const FiniteElemSpace* feSpace = DOFVector<T>::feSpace;
    int dim = DOFVector<T>::dim;

    // define result vector
    static DOFVector<Gradient_t<T>>* vec = NULL; // TODO: REMOVE STATIC

    DOFVector<Gradient_t<T>>* result = grad;

    if (!result)
    {
      if (vec && vec->getFeSpace() != feSpace)
      {
        delete vec;
        vec = NULL;
      }
      if (!vec)
        vec = new DOFVector<Gradient_t<T>>(feSpace, "gradient");

      result = vec;
    }

    Gradient_t<T> grd;
    nullify(grd);

    result->set(grd);

    DOFVector<double> volume(feSpace, "volume");
    volume.set(0.0);

    const BasisFunction* basFcts = feSpace->getBasisFcts();
    int nBasisFcts = basFcts->getNumber();
    DimVec<double> bary(dim, (1.0 / (dim + 1.0)));

    // traverse mesh
    Mesh* mesh = feSpace->getMesh();
    TraverseStack stack;
    ElInfo* elInfo = stack.traverseFirst(mesh, -1,
                                         Mesh::CALL_LEAF_EL | Mesh::FILL_DET |
                                         Mesh::FILL_GRD_LAMBDA | Mesh::FILL_COORDS);

    DenseVector<T> localUh(nBasisFcts);
    std::vector<DegreeOfFreedom> localIndices(nBasisFcts);

    while (elInfo)
    {
      double det = elInfo->getDet();
      auto& grdLambda = elInfo->getGrdLambda();
      this->getLocalVector(elInfo->getElement(), localUh);
      basFcts->evalGrdUh(bary, grdLambda, localUh, grd);
      basFcts->getLocalIndices(elInfo->getElement(), feSpace->getAdmin(), localIndices);

      for (int i = 0; i < nBasisFcts; i++)
      {
        (*result)[localIndices[i]] += grd * det;
        volume[localIndices[i]] += det;
      }

      elInfo = stack.traverseNext(elInfo);

    }

#ifdef HAVE_PARALLEL_DOMAIN_AMDIS
    Parallel::MeshDistributor::globalMeshDistributor->checkMeshChange(false);
    Parallel::MeshDistributor::globalMeshDistributor->synchAddVector(*result);
    Parallel::MeshDistributor::globalMeshDistributor->synchAddVector(volume);
#endif

    DOFVector<double>::Iterator volIt(&volume, USED_DOFS);
    typename DOFVector<Gradient_t<T>>::Iterator grdIt(result, USED_DOFS);

    for (volIt.reset(), grdIt.reset(); !volIt.end(); ++volIt, ++grdIt)
      if (*volIt != 0.0)
        *grdIt *= 1.0 / (*volIt);

    return result;
  }

  template <class T>
  void DOFVector<T>::coarseRestrictImpl(Id<double>, RCNeighbourList& list, int n)
  {
    FUNCNAME("DOFVector<double>::coarseRestrict()");

    switch (this->coarsenOperation)
    {
    case NO_OPERATION:
      return;
      break;
    case COARSE_RESTRICT:
      (const_cast<BasisFunction*>(this->feSpace->getBasisFcts()))->coarseRestr(this, &list, n);
      break;
    case COARSE_INTERPOL:
      TEST_EXIT_DBG(this->feSpace)("Should not happen!\n");
      TEST_EXIT_DBG(this->feSpace->getBasisFcts())("Shoud not happen!\n");

      (const_cast<BasisFunction*>(this->feSpace->getBasisFcts()))->coarseInter(this, &list, n);
      break;
    default:
      WARNING("Invalid coarsen operation \"%d\" in vector \"%s\"\n",
              this->coarsenOperation, this->name.c_str());
    }
  }

  template <class T>
  void DOFVector<T>::refineInterpolImpl(Id<double>, RCNeighbourList& list, int n)
  {
    switch (this->refineOperation)
    {
    case NO_OPERATION:
      return;
      break;
    case REFINE_INTERPOL:
    default:
      (const_cast<BasisFunction*>(this->feSpace->getBasisFcts()))->refineInter(this, &list, n);
      break;
    }
  }

  template <class T>
  void DOFVector<T>::refineInterpolImpl(Id<WorldVector<double>>, RCNeighbourList& list, int n)
  {
    if (this->refineOperation == NO_OPERATION)
      return;

    if (n < 1)
      return;

    Element* el = list.getElement(0);
    int n0 = this->feSpace->getAdmin()->getNumberOfPreDofs(VERTEX);
    DegreeOfFreedom dof0 = el->getDof(0, n0);
    DegreeOfFreedom dof1 = el->getDof(1, n0);
    DegreeOfFreedom dof_new = el->getChild(0)->getDof(this->feSpace->getMesh()->getDim(), n0);
    vec[dof_new] = vec[dof0];
    vec[dof_new] += vec[dof1];
    vec[dof_new] *= 0.5;
  }

  template <class T>
  double DOFVector<T>::evalAtPointImpl(Id<double>,
      WorldVector<double> const& p,
      ElInfo* oldElInfo) const
  {
    Mesh* mesh = this->feSpace->getMesh();
    const BasisFunction* basFcts = this->feSpace->getBasisFcts();

    int dim = mesh->getDim();
    int nBasFcts = basFcts->getNumber();

    std::vector<DegreeOfFreedom> localIndices(nBasFcts);
    DimVec<double> lambda(dim);

    ElInfo* elInfo = mesh->createNewElInfo();
    double value = 0.0;
    int inside = 0;

    if (oldElInfo && oldElInfo->getMacroElement())
    {
      inside = mesh->findElInfoAtPoint(p, elInfo, lambda, oldElInfo->getMacroElement(), NULL, NULL);
      delete oldElInfo;
    }
    else
      inside = mesh->findElInfoAtPoint(p, elInfo, lambda, NULL, NULL, NULL);

    if (oldElInfo)
      oldElInfo = elInfo;

    if (inside > 0)
    {
      basFcts->getLocalIndices(elInfo->getElement(), this->feSpace->getAdmin(), localIndices);
      DenseVector<double> uh(nBasFcts);
      for (int i = 0; i < nBasFcts; i++)
        uh[i] = operator[](localIndices[i]);
      value = basFcts->evalUh(lambda, uh);
    }
    else
    {
#ifdef HAVE_PARALLEL_DOMAIN_AMDIS
      value = 0.0;
#else
      ERROR_EXIT("Can not eval DOFVector at point p, because point is outside geometry.");
#endif
    }

    if (oldElInfo == NULL)
      delete elInfo;

#ifdef HAVE_PARALLEL_DOMAIN_AMDIS
    Parallel::mpi::globalAdd(value);
#endif
    return value;
  }

  template <class T>
  WorldVector<double> DOFVector<T>::evalAtPointImpl(Id<WorldVector<double>>,
        WorldVector<double> const& p,
        ElInfo* oldElInfo) const
  {
    Mesh* mesh = this->feSpace->getMesh();
    const BasisFunction* basFcts = this->feSpace->getBasisFcts();

    int dim = mesh->getDim();
    int nBasFcts = basFcts->getNumber();

    std::vector<DegreeOfFreedom> localIndices(nBasFcts);
    DimVec<double> lambda(dim);
    ElInfo* elInfo = mesh->createNewElInfo();
    WorldVector<double> value(DEFAULT_SIZE, 0.0);
    int inside = 0;

    if (oldElInfo && oldElInfo->getMacroElement())
    {
      inside = mesh->findElInfoAtPoint(p, elInfo, lambda, oldElInfo->getMacroElement(), NULL, NULL);
      delete oldElInfo;
    }
    else
      inside = mesh->findElInfoAtPoint(p, elInfo, lambda, NULL, NULL, NULL);

    if (oldElInfo)
      oldElInfo = elInfo;

    if (inside > 0)
    {
      basFcts->getLocalIndices(elInfo->getElement(), this->feSpace->getAdmin(), localIndices);
      DenseVector<WorldVector<double>> uh(nBasFcts);
      for (int i = 0; i < nBasFcts; i++)
        uh[i] = operator[](localIndices[i]);
      value = basFcts->evalUh(lambda, uh);
    }
    else
    {
      ERROR_EXIT("Can not eval DOFVector at point p, because point is outside geometry.");
    }

    if (oldElInfo == NULL)
      delete elInfo;

    return value;
  }

  template <class T>
  std::vector<DOFVector<double>*>* transform(DOFVector<Gradient_t<T>>* vec,
      std::vector<DOFVector<double>*>* res)
  {
    FUNCNAME_DBG("DOFVector<T>::transform()");

    TEST_EXIT_DBG(vec)("no vector\n");

    static std::vector<DOFVector<double>*>* result = NULL; // TODO: REMOVE STATIC

    int len = num_rows(GradientType<T>::getValues((*vec)[0]));

    if (!res && !result)
    {
      result = new std::vector<DOFVector<double>*>(len);
      for (int i = 0; i < len; i++)
        (*result)[i] = new DOFVector<double>(vec->getFeSpace(), "transform");
    }
    else if (res->size() == 0 || (*res)[0] == NULL)
    {
      res->resize(len, NULL);
      for (int i = 0; i < len; i++)
        (*res)[i] = new DOFVector<double>(vec->getFeSpace(), "transform");
    }

    std::vector<DOFVector<double>*>* r = res ? res : result;

    int vecSize = vec->getSize();
    for (int i = 0; i < vecSize; i++)
      for (int j = 0; j < len; j++)
        (*((*r)[j]))[i] = (GradientType<T>::getValues((*vec)[i]))[j];

    return r;
  }

  template<typename T>
  DOFVector<T>& DOFVector<T>::operator*=(T scal)
  {
    FUNCNAME_DBG("DOFVector<T>::operator*=(T scal)");

    TEST_EXIT_DBG(this->getFeSpace() && this->getFeSpace()->getAdmin())
    ("pointer is NULL: %8X, %8X\n", this->getFeSpace(), this->getFeSpace()->getAdmin());

    DOFIterator<T> xIterator(this, USED_DOFS);
    for (xIterator.reset(); !xIterator.end(); ++xIterator)
      (*xIterator) *= scal;

    return *this;
  }

  template<typename T>
  DOFVector<T>& DOFVector<T>::operator+=(DOFVector<T> const& y)
  {
    FUNCNAME_DBG("DOFVector<T>::operator+=(const DOFVector<T>& y)");

    TEST_EXIT_DBG(this->getFeSpace() && y.getFeSpace())
    ("feSpace is NULL: %8X, %8X\n", this->getFeSpace(), y.getFeSpace());
    TEST_EXIT_DBG(this->getFeSpace()->getAdmin() &&
                  (this->getFeSpace()->getAdmin() == y.getFeSpace()->getAdmin()))
    ("no admin or different admins: %8X, %8X\n",
     this->getFeSpace()->getAdmin(), y.getFeSpace()->getAdmin());
    TEST_EXIT_DBG(this->getSize() == y.getSize())("different sizes\n");

    DOFIterator<T> xIterator(this, USED_DOFS);
    DOFConstIterator<T> yIterator(&y, USED_DOFS);
    for (xIterator.reset(), yIterator.reset(); !xIterator.end(); ++xIterator, ++yIterator)
      *xIterator += *yIterator;

    return *this;
  }

  template<typename T>
  DOFVector<T>& DOFVector<T>::operator-=(DOFVector<T> const& y)
  {
    FUNCNAME_DBG("DOFVector<T>::operator-=(const DOFVector<T>& y)");

    TEST_EXIT_DBG(this->getFeSpace() && y.getFeSpace())
    ("feSpace is NULL: %8X, %8X\n", this->getFeSpace(), y.getFeSpace());
    TEST_EXIT_DBG(this->getFeSpace()->getAdmin() &&
                  (this->getFeSpace()->getAdmin() == y.getFeSpace()->getAdmin()))
    ("no admin or different admins: %8X, %8X\n",
     this->getFeSpace()->getAdmin(), y.getFeSpace()->getAdmin());
    TEST_EXIT_DBG(this->getSize() == y.getSize())("different sizes\n");

    DOFIterator<T> xIterator(this, USED_DOFS);
    DOFConstIterator<T> yIterator(&y, USED_DOFS);
    for (xIterator.reset(), yIterator.reset(); !xIterator.end(); ++xIterator, ++yIterator)
      *xIterator -= *yIterator;

    return *this;
  }

  template<typename T>
  DOFVector<T>& DOFVector<T>::operator*=(DOFVector<T> const& y)
  {
    FUNCNAME_DBG("DOFVector<T>::operator*=(const DOFVector<T>& y)");

    TEST_EXIT_DBG(this->getFeSpace() && y.getFeSpace())
    ("feSpace is NULL: %8X, %8X\n", this->getFeSpace(), y.getFeSpace());
    TEST_EXIT_DBG(this->getFeSpace()->getAdmin() &&
                  (this->getFeSpace()->getAdmin() == y.getFeSpace()->getAdmin()))
    ("no admin or different admins: %8X, %8X\n",
     this->getFeSpace()->getAdmin(), y.getFeSpace()->getAdmin());
    TEST_EXIT_DBG(this->getSize() == y.getSize())("different sizes\n");

    DOFIterator<T> xIterator(this, USED_DOFS);
    DOFConstIterator<T> yIterator(&y, USED_DOFS);
    for (xIterator.reset(), yIterator.reset(); !xIterator.end(); ++xIterator, ++yIterator)
      *xIterator *= *yIterator;

    return *this;
  }

} // end namespace AMDiS


/*
Copyright (C) 2015 CompatibL

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.
*/

#ifndef cl_utilities_adjoint_hpp
#define cl_utilities_adjoint_hpp
#pragma once

#include <ql/math/matrix.hpp>
#include <boost/timer.hpp>
#include <boost/date_time.hpp>

namespace QuantLib
{
    struct PerformanceTime
    {
        static std::deque<std::string > get_columns()
        {
            static std::deque<std::string > columns =
            {
                "Number of independent variables", "Tape recording time", "Adjoint calculations time", "No Adjoint calculations time"
            };

            return columns;
        }

        template <typename stream_type>
        friend inline stream_type&
            operator << (stream_type& stm, PerformanceTime& v)
        {
                stm << v.indepVarNumber_
                    << ";" << v.timeTapeRecording_
                    << ";" << v.timeAdjoint_
                    << ";" << v.timeAnalytical_ << std::endl;

                return stm;
            }

        double timeTapeRecording_;
        double timeAdjoint_;
        double timeAnalytical_;
        Size indepVarNumber_;
    };

    struct AdjointTime
    {
        static std::deque<std::string > get_columns()
        {
            static std::deque<std::string > columns =
            {
                "Number of independent variables", ""
            };

            return columns;
        }

        template <typename stream_type>
        friend inline stream_type&
            operator << (stream_type& stm, AdjointTime& v)
        {
                stm << v.indepVarNumber_
                    << ";" << v.timeAdjoint_
                    << std::endl;
                return stm;
            }

        double timeAdjoint_;
        Size indepVarNumber_;
    };

    struct TapeSize
    {
        static std::deque<std::string > get_columns()
        {
            static std::deque<std::string > columns =
            {
                "Number of independent variables", ""
            };

            return columns;
        }

        template <typename stream_type>
        friend inline stream_type&
            operator << (stream_type& stm, TapeSize& v)
        {
                stm << v.indepVarNumber_
                    << ";" << v.memory_ / 1048576.0
                    << std::endl;
                return stm;
            }

        Size indepVarNumber_;
        Size memory_;
    };

    inline std::string currentTime()
    {
        boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
        std::stringstream ss;
        ss << now.time_of_day().hours() << "h  " << now.time_of_day().minutes() << "m  "
            << now.time_of_day().seconds() << "s  " << now.time_of_day().fractional_seconds() << "mcs";
        return ss.str();
    }

    // checking consistency of elements of three vectors
    inline bool checkConsistencyWithBC(
        const std::vector<double>& forwardMode,
        const std::vector<double>& reverseMode,
        const std::vector<Real>& theoretical,
        double relativeTol, double absTol = 1e-10)
    {
        bool result = true;
        int n = theoretical.size();
        if (theoretical.size() != forwardMode.size())
        {
            std::cout << "Forward mode and Theoretical results vectors size mismatch.\n";
            return false;
        }
        if (theoretical.size() != reverseMode.size())
        {
            std::cout << "Reverse mode and Theoretical results vectors size mismatch.\n";
            return false;
        }
        std::vector<double>::const_iterator fwd = forwardMode.begin();
        std::vector<double>::const_iterator rev = reverseMode.begin();
        std::vector<Real  >::const_iterator thr = theoretical.begin();
        for (int i = 0; i < n; ++i, ++thr, ++fwd, ++rev)
        {
            Real maxabs = std::max(std::abs(*thr), std::max(std::abs(*fwd), std::abs(*rev)));
            Real tol = relativeTol * maxabs + absTol;
            if (std::abs(*fwd - *thr) > tol)
            {
                result = false;
                std::cout << "Forward mode derivative[" << i << "] and Theoretical results mismatch."
                    << "\n    forward mode:      " << *fwd
                    << "\n    theoretical:       " << *thr
                    << "\n    tolerance:  " << tol << std::endl;
            }
            if (std::abs(*rev - *thr) > tol)
            {
                result = false;
                std::cout << "Reverse mode derivative[" << i << "] and Theoretical results mismatch."
                    << "\n    reverse mode:      " << *rev
                    << "\n    theoretical:       " << *thr
                    << "\n    tolerance:  " << tol << std::endl;
            }
        }
        return result;
    }

    // checking consistency of elements of three vectors
    inline bool checkConsistencyWithFD(
        const std::vector<double>& forwardMode,
        const std::vector<double>& reverseMode,
        const std::vector<Real>& finiteDiff,
        double relativeTol, double absTol = 1e-10)
    {
        bool result = true;
        int n = finiteDiff.size();
        if (finiteDiff.size() != forwardMode.size())
        {
            std::cout << "Forward mode and Finite difference vectors size mismatch.\n";
            return false;
        }
        if (finiteDiff.size() != reverseMode.size())
        {
            std::cout << "Reverse mode and Finite difference vectors size mismatch.\n";
            return false;
        }
        std::vector<double>::const_iterator fwd = forwardMode.begin();
        std::vector<double>::const_iterator rev = reverseMode.begin();
        std::vector<Real  >::const_iterator fin = finiteDiff.begin();
        for (int i = 0; i < n; ++i, ++fin, ++fwd, ++rev)
        {
            Real maxabs = std::max(std::abs(*fin), std::max(std::abs(*fwd), std::abs(*rev)));
            Real tol = relativeTol * maxabs + absTol;
            if (std::abs(*fwd - *fin) > tol)
            {
                result = false;
                std::cout << "Forward mode and Finite difference derivative[" << i << "] mismatch."
                    << "\n    forward mode:      " << *fwd
                    << "\n    finite difference: " << *fin
                    << "\n    tolerance:  " << tol << std::endl;
            }
            if (std::abs(*rev - *fin) > tol)
            {
                result = false;
                std::cout << "Reverse mode and Finite difference derivative[" << i << "] mismatch."
                    << "\n    reverse mode:      " << *rev
                    << "\n    finite difference: " << *fin
                    << "\n    tolerance:  " << tol << std::endl;
            }
            if (std::abs(*rev - *fwd) > tol)
            {
                result = false;
                std::cout << "Forward mode and Reverse mode derivative[" << i << "] mismatch."
                    << "\n    forward mode:      " << *fwd
                    << "\n    reverse mode:      " << *rev
                    << "\n    tolerance:  " << tol << std::endl;
            }
        }
        return result;
    }

    // computes gradient in Forward AD mode and outputs results
    inline double gradForward(cl::tape_function<double>& f,
        std::vector<double>& grad,
        bool timeOutput,
        bool gradOutput)
    {
        using namespace std;
        size_t n = f.Domain();
        size_t m = f.Range();
        grad.resize(m*n);
        vector<double> dx(n), dy(m);

        const size_t iterNum = 10;
        boost::timer timer;
        timer.restart();
        for (size_t k = 0; k < iterNum; k++)
        {
            for (size_t i = 0; i < n; i++)
            {
                dx[i] = 1;
                dy = f.Forward(1, dx);
                dx[i] = 0;
                for (size_t j = 0; j < m; j++)
                    grad[j*n + i] = dy[j];
            }
        }
        double timeForward = timer.elapsed() / iterNum;
        if (timeOutput)
            cout << "Time for Forward AD mode : " << timeForward << endl;

        if (gradOutput)
        {
            cout << "Forward AD mode : " << endl;
            for (size_t i = 0; i < grad.size(); i++)
                cout << "dy" << i / n << "/dx" << i % n << "= " << grad[i] << endl;
        }
        return timeForward;
    }

    // computes gradient in Reverse AD mode and outputs results
    inline double gradReverse(cl::tape_function<double>& f,
        std::vector<double>& grad,
        bool timeOutput,
        bool gradOutput)
    {
        using namespace std;
        size_t n = f.Domain();
        size_t m = f.Range();
        grad.resize(m*n);
        vector<double> dw(m), dy(n);

        const size_t iterNum = 10;
        boost::timer timer;
        timer.restart();
        for (size_t k = 0; k < iterNum; k++)
        for (size_t i = 0; i < m; i++)
        {
            dw[i] = 1;
            dy = f.Reverse(1, dw);
            dw[i] = 0;
            for (size_t j = 0; j < n; j++)
                grad[i*n + j] = dy[j];
        }
        double timeReverse = timer.elapsed() / iterNum;
        if (timeOutput)
            cout << "Time for Reverse AD mode : " << timer.elapsed() / iterNum << endl;

        if (gradOutput)
        {
            cout << "Reverse AD mode : " << endl;
            for (size_t i = 0; i < grad.size(); i++)
                cout << "dy" << i / n << "/dx" << i % n << "= " << grad[i] << endl;
        }
        return timeReverse;
    }

    // computes gradient in Forward AD mode and outputs results
    inline double gradForward(cl::tape_function<double>& f,
        std::vector<double>& grad,
        cl::tape_empty_test_output& out,
        bool timeOutput,
        bool gradOutput,
        size_t iterNum = 10)
    {
#ifndef CL_GRAPH_GEN
        iterNum = 1;
#endif
        using namespace std;
        out.log() << "Start of differentiation in Forward mode:  " << currentTime() << endl;
        size_t n = f.Domain();
        size_t m = f.Range();
        grad.resize(m*n);
        vector<double> dx(n), dy(m);

        try{
            boost::timer timer;
            timer.restart();
            for (size_t k = 0; k < iterNum; k++)
            {
                for (size_t i = 0; i < n; i++)
                {
                    dx[i] = 1;
                    dy = f.Forward(1, dx);
                    dx[i] = 0;
                    for (size_t j = 0; j < m; j++)
                        grad[j*n + i] = dy[j];
                }
            }
            double timeForward = timer.elapsed() / iterNum;
            if (timeOutput)
                cout << "Time for Forward AD mode : " << timeForward << endl;

            if (gradOutput)
            {
                cout << "Forward AD mode : " << endl;
                for (size_t i = 0; i < grad.size(); i++)
                    cout << "dy" << i / n << "/dx" << i % n << "= " << grad[i] << endl;
            }
            out.log() << "Forward Mode Derivatives calculated successfully" << std::endl;
            out.log() << "Time in Forward mode:   " << timeForward << "s" << endl;

            return timeForward;
        }
        catch (std::exception& ex)
        {
            out.log() << ex.what() << std::endl;
            return 0;
        }
    }

    // computes gradient in Reverse AD mode and outputs results
    inline double gradReverse(cl::tape_function<double>& f,
        std::vector<double>& grad,
        cl::tape_empty_test_output& out,
        bool timeOutput,
        bool gradOutput,
        size_t iterNum = 10)
    {
#ifndef CL_GRAPH_GEN
        iterNum = 1;
#endif
        using namespace std;
        out.log() << "Start of differentiation in Reverse mode:   " << currentTime() << endl;
        size_t n = f.Domain();
        size_t m = f.Range();
        grad.resize(m*n);
        vector<double> dw(m), dy(n);

        try{
            boost::timer timer;
            timer.restart();
            for (size_t k = 0; k < iterNum; k++)
            for (size_t i = 0; i < m; i++)
            {
                dw[i] = 1;
                dy = f.Reverse(1, dw);
                dw[i] = 0;
                for (size_t j = 0; j < n; j++)
                    grad[i*n + j] = dy[j];
            }
            double timeReverse = timer.elapsed() / iterNum;
            if (timeOutput)
                cout << "Time for Reverse AD mode : " << timer.elapsed() / iterNum << endl;

            if (gradOutput)
            {
                cout << "Reverse AD mode : " << endl;
                for (size_t i = 0; i < grad.size(); i++)
                    cout << "dy" << i / n << "/dx" << i % n << "= " << grad[i] << endl;
            }

            out.log() << "Reverse Mode Derivatives calculated successfully" << std::endl;
            out.log() << "Time in Reverse mode:   " << timeReverse << " s" << endl;

            return timeReverse;
        }
        catch (std::exception& ex)
        {
            out.log() << ex.what() << std::endl;
            return 0;
        }
    }

    // vector-matrix converters

    template<class C>
    inline Matrix vector2matrix(const C& vec, int rows, int columns)
    {
        Matrix A(rows, columns);
        copy(vec.begin(), vec.end(), A.begin());
        return A;
    }

    inline std::vector<cl::tape_double>  matrix2vector(const Matrix& A)
    {
        return std::vector<cl::tape_double>(A.begin(), A.end());
    }

    inline PerformanceTime& operator+=(PerformanceTime& left, PerformanceTime const& right)
    {
        left.timeAdjoint_ += right.timeAdjoint_;
        left.timeAnalytical_ += right.timeAnalytical_;
        left.timeTapeRecording_ += right.timeTapeRecording_;
        return left;
    }
    inline PerformanceTime& operator-=(PerformanceTime& left, PerformanceTime const& right)
    {
        left.timeAdjoint_ -= right.timeAdjoint_;
        left.timeAnalytical_ -= right.timeAnalytical_;
        left.timeTapeRecording_ -= right.timeTapeRecording_;
        return left;
    }
    inline PerformanceTime& operator*=(PerformanceTime& left, double right)
    {
        left.timeAdjoint_ *= right;
        left.timeAnalytical_ *= right;
        left.timeTapeRecording_ *= right;
        return left;
    }
    inline PerformanceTime operator+(PerformanceTime const& left, PerformanceTime const& right)
    {
        PerformanceTime temp = left;
        return temp += right;
    }
    inline PerformanceTime operator-(PerformanceTime const& left, PerformanceTime const& right)
    {
        PerformanceTime temp = left;
        return temp -= right;
    }
    inline PerformanceTime operator*(double left, PerformanceTime const& right)
    {
        PerformanceTime temp = right;
        return temp *= left;
    }
    // what = max(what, where);
    inline void upTo(PerformanceTime & what, PerformanceTime const& where)
    {
        what.timeAdjoint_ = std::max(what.timeAdjoint_, where.timeAdjoint_);
        what.timeAnalytical_ = std::max(what.timeAnalytical_, where.timeAnalytical_);
        what.timeTapeRecording_ = std::max(what.timeTapeRecording_, where.timeTapeRecording_);
    }
    // what = max(what, where);
    inline void upTo(PerformanceTime & what, double where)
    {
        upTo(what, PerformanceTime{ where, where, where, 0 });
    }

    inline void upTo(double & what, double const& where)
    {
        what = std::max(what, where);
    }

    // Dummy realization of heat equation.
    template <class RanIt>
    void heatbackward(RanIt first, RanIt last, double a, size_t n = 1)
    {
        if (last - first < 3)
            return;

        for (size_t i = 0; i < n; i++)
        {
            std::reverse_iterator<RanIt> prev(last);
            auto curr = prev + 1;
            auto next = curr + 1;
            for (; next != std::reverse_iterator<RanIt>(first); ++curr, ++next, ++prev)
            {
                typename RanIt::value_type deriv = *next + *prev - 2 * (*curr);
                *curr += 0.5 * a * deriv;
                *prev -= 0.25 * a * deriv;
                *next -= 0.25 * a * deriv;
                upTo(*next, 0);
            }
            upTo(*curr, 0.5 * (*prev));
        }
    }

    template <class RanIt>
    void heat2(RanIt first, RanIt last, double a, size_t n = 1)
    {
        size_t size = last - first;
        if (size < 3)
            return;

        typedef typename RanIt::value_type value_type;
        value_type left = *first;
        value_type right = *(last - 1);
        value_type sum = std::accumulate(first + 1, last, *first);
        for (size_t i = 0; i < n; i++)
        {
            std::vector<value_type> derivatives(size);
            RanIt prev = first;
            RanIt curr = prev + 1;
            RanIt next = curr + 1;
            derivatives[0] = *curr + left - 2 * (*first);
            for (auto deriv = derivatives.begin() + 1; deriv != derivatives.end() - 1; ++deriv, ++curr, ++next, ++prev)
            {
                *deriv = *next + *prev - 2 * (*curr);
            }
            derivatives.back() = right + *prev - 2 * (*curr);
            auto deriv = derivatives.begin();
            std::for_each(first, last, [&deriv, a](value_type& v)
            {
                v += 0.5 * a * (*deriv++);
            });
        }
    }

    template <class RanIt>
    inline void makeSmooth(RanIt first, RanIt last, double sigma = 10.0)
    {
        if (sigma <= 0)
            return;

        size_t n = size_t(sigma*sigma * 10 + sigma * 50) + 1;
        double a = sigma*sigma / n;
        heatbackward(first, last, a, n);
        //heat2(first, last, a, n);
    }

    // Makes data in c smooth.
    template <class Cont>
    inline void makeSmooth(Cont& c, double sigma = 10.0)
    {
        makeSmooth(c.begin(), c.end(), sigma);
    }

    template <template<typename T, typename A = std::allocator<T> > class Cont>
    inline void makeSmooth(Cont<std::string>& c, const std::string& sigma)
    {
        std::vector<double> v(c.size());
        std::transform(c.begin(), c.end(), v.begin(),
            [](std::string const& s)
        {
            return std::stod(s);
        });

        if (sigma == "default")
        {
            makeSmooth(v, c.size() * 0.05 + 5);
        }
        else
        {
            makeSmooth(v, std::stod(sigma));
        }

        std::transform(v.begin(), v.end(), c.begin(), [](double v)
        {
            std::ostringstream strs;
            strs << v;
            return strs.str();
        });
    }

    template <class Key, class Cont, class Pr>
    inline void makeSmooth(std::map<Key, Cont>& graphics, const std::string& sigma, Pr pred)
    {
        std::for_each(graphics.begin(), graphics.end(),
            [&pred, &sigma](std::pair<const Key, Cont>& v)
        {
            if (pred(v.first))
                makeSmooth(v.second, sigma);
        });
    }

    template <class Key, class Cont>
    inline void makeSmooth(std::map<Key, Cont>& graphics, const std::string& sigma)
    {
        makeSmooth(graphics, sigma, [](const std::string&){ return true; });
    }
}

#endif


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

/*
 Copyright (C) 2006, 2007, 2011 Ferdinando Ametrano
 Copyright (C) 2000, 2001, 2002, 2003 RiskMap srl
 Copyright (C) 2003, 2004, 2005, 2006, 2009 StatPro Italia srl

 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 schedule.hpp
    \brief date schedule
*/

#ifndef quantlib_schedule_hpp
#define quantlib_schedule_hpp

#include <ql/time/calendars/nullcalendar.hpp>
#include <ql/utilities/null.hpp>
#include <ql/time/period.hpp>
#include <ql/time/dategenerationrule.hpp>
#include <ql/errors.hpp>
#include <boost/optional.hpp>

namespace QuantLib {

    //! Payment schedule
    /*! \ingroup datetime */
    class Schedule {
      public:
        /*! constructor that takes any list of dates, and optionally
            meta information that can be used by client classes. Note
            that neither the list of dates nor the meta information is
            checked for plausibility in any sense. */
        Schedule(const std::vector<Date>&,
                 const Calendar& calendar = NullCalendar(),
                 const BusinessDayConvention
                                    convention = Unadjusted,
                 boost::optional<BusinessDayConvention>
                     terminationDateConvention = boost::none,
                 const boost::optional<Period> tenor = boost::none,
                 boost::optional<DateGeneration::Rule> rule = boost::none,
                 boost::optional<bool> endOfMonth = boost::none,
                 const std::vector<bool>& isRegular = std::vector<bool>(0));
        /*! rule based constructor */
        Schedule(Date effectiveDate,
                 const Date& terminationDate,
                 const Period& tenor,
                 const Calendar& calendar,
                 BusinessDayConvention convention,
                 BusinessDayConvention terminationDateConvention,
                 DateGeneration::Rule rule,
                 bool endOfMonth,
                 const Date& firstDate = Date(),
                 const Date& nextToLastDate = Date());
        Schedule() {}
        //! \name Date access
        //@{
        Size size() const { return dates_.size(); }
        const Date& operator[](Size i) const;
        const Date& at(Size i) const;
        const Date& date(Size i) const;
        Date previousDate(const Date& refDate) const;
        Date nextDate(const Date& refDate) const;
        const std::vector<Date>& dates() const { return dates_; }
        bool hasIsRegular() const;
        bool isRegular(Size i) const;
        const std::vector<bool>& isRegular() const;
        //@}
        //! \name Other inspectors
        //@{
        bool empty() const { return dates_.empty(); }
        const Calendar& calendar() const;
        const Date& startDate() const;
        const Date& endDate() const;
        bool hasTenor() const;
        const Period& tenor() const;
        BusinessDayConvention businessDayConvention() const;
        bool hasTerminationDateBusinessDayConvention() const;
        BusinessDayConvention terminationDateBusinessDayConvention() const;
        bool hasRule() const;
        DateGeneration::Rule rule() const;
        bool hasEndOfMonth() const;
        bool endOfMonth() const;
        //@}
        //! \name Iterators
        //@{
        typedef std::vector<Date>::const_iterator const_iterator;
        const_iterator begin() const { return dates_.begin(); }
        const_iterator end() const { return dates_.end(); }
        const_iterator lower_bound(const Date& d = Date()) const;
        //@}
        //! \name Utilities
        //@{
        //! truncated schedule
        Schedule until(const Date& truncationDate) const;
        //@}
      private:
        boost::optional<Period> tenor_;
        Calendar calendar_;
        BusinessDayConvention convention_;
        boost::optional<BusinessDayConvention> terminationDateConvention_;
        boost::optional<DateGeneration::Rule> rule_;
        boost::optional<bool> endOfMonth_;
        Date firstDate_, nextToLastDate_;
        std::vector<Date> dates_;
        std::vector<bool> isRegular_;
    };

    //! helper class
    /*! This class provides a more comfortable interface to the
        argument list of Schedule's constructor.
    */
    class MakeSchedule {
      public:
        MakeSchedule();
        MakeSchedule& from(const Date& effectiveDate);
        MakeSchedule& to(const Date& terminationDate);
        MakeSchedule& withTenor(const Period&);
        MakeSchedule& withFrequency(Frequency);
        MakeSchedule& withCalendar(const Calendar&);
        MakeSchedule& withConvention(BusinessDayConvention);
        MakeSchedule& withTerminationDateConvention(BusinessDayConvention);
        MakeSchedule& withRule(DateGeneration::Rule);
        MakeSchedule& forwards();
        MakeSchedule& backwards();
        MakeSchedule& endOfMonth(bool flag=true);
        MakeSchedule& withFirstDate(const Date& d);
        MakeSchedule& withNextToLastDate(const Date& d);
        operator Schedule() const;
      private:
        Calendar calendar_;
        Date effectiveDate_, terminationDate_;
        boost::optional<Period> tenor_;
        boost::optional<BusinessDayConvention> convention_;
        boost::optional<BusinessDayConvention> terminationDateConvention_;
        DateGeneration::Rule rule_;
        bool endOfMonth_;
        Date firstDate_, nextToLastDate_;
    };

    // inline definitions

    inline const Date& Schedule::date(Size i) const {
        return dates_.at(i);
    }

    inline const Date& Schedule::operator[](Size i) const {
        #if defined(QL_EXTRA_SAFETY_CHECKS)
        return dates_.at(i);
        #else
        return dates_[i];
        #endif
    }

    inline const Date& Schedule::at(Size i) const {
        return dates_.at(i);
    }

    inline const Calendar& Schedule::calendar() const {
        return calendar_;
    }

    inline const Date& Schedule::startDate() const {
        return dates_.front();
    }

    inline const Date &Schedule::endDate() const { return dates_.back(); }

    inline bool Schedule::hasTenor() const {
        return tenor_ != boost::none;
    }

    inline const Period& Schedule::tenor() const {
        QL_REQUIRE(hasTenor(),
                   "full interface (tenor) not available");
        return *tenor_;
    }

    inline BusinessDayConvention Schedule::businessDayConvention() const {
        return convention_;
    }

    inline bool
    Schedule::hasTerminationDateBusinessDayConvention() const {
        return terminationDateConvention_ != boost::none;
    }

    inline BusinessDayConvention
    Schedule::terminationDateBusinessDayConvention() const {
        QL_REQUIRE(hasTerminationDateBusinessDayConvention(),
                   "full interface (termination date bdc) not available");
        return *terminationDateConvention_;
    }

    inline bool Schedule::hasRule() const {
        return rule_ != boost::none;
    }

    inline DateGeneration::Rule Schedule::rule() const {
        QL_REQUIRE(hasRule(), "full interface (rule) not available");
        return *rule_;
    }

    inline bool Schedule::hasEndOfMonth() const {
        return endOfMonth_ != boost::none;
    }

    inline bool Schedule::endOfMonth() const {
        QL_REQUIRE(hasEndOfMonth(),
                   "full interface (end of month) not available");
        return *endOfMonth_;
    }

}

#endif