/*
 *            Copyright 2009-2020 The VOTCA Development Team
 *                       (http://www.votca.org)
 *
 *      Licensed under the Apache License, Version 2.0 (the "License")
 *
 * You may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *              http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

// Third party includes
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <boost/format.hpp>

// VOTCA includes
#include <stdexcept>
#include <votca/tools/constants.h>

// Local VOTCA includes
#include "votca/xtp/basisset.h"
#include "votca/xtp/bse.h"
#include "votca/xtp/ecpbasisset.h"
#include "votca/xtp/gwbse.h"
#include "votca/xtp/logger.h"
#include "votca/xtp/openmp_cuda.h"
#include "votca/xtp/orbitals.h"
#include "votca/xtp/vxc_grid.h"
#include "votca/xtp/vxc_potential.h"

using boost::format;
using namespace boost::filesystem;
using std::flush;
namespace votca {
namespace xtp {

Index GWBSE::CountCoreLevels() {
  Index ignored_corelevels = 0;
  if (!orbitals_.hasECPName()) {
    ECPBasisSet basis;
    basis.Load("corelevels");
    Index coreElectrons = 0;
    for (const auto& atom : orbitals_.QMAtoms()) {
      coreElectrons += basis.getElement(atom.getElement()).getNcore();
    }
    ignored_corelevels = coreElectrons / 2;
  }
  return ignored_corelevels;
}

void GWBSE::Initialize(tools::Property& options) {

  // getting level ranges
  Index rpamax = 0;
  Index rpamin = 0;  // never changes
  Index qpmin = 0;
  Index qpmax = 0;
  Index bse_vmin = 0;
  Index bse_cmax = 0;

  Index homo = orbitals_.getHomo();  // indexed from 0
  Index num_of_levels = orbitals_.getBasisSetSize();
  Index num_of_occlevels = orbitals_.getNumberOfAlphaElectrons();

  std::string ranges = options.get(".ranges").as<std::string>();

  // now check validity, and get rpa, qp, and bse level ranges accordingly

  if (ranges == "factor") {

    double rpamaxfactor = options.get(".rpamax").as<double>();
    rpamax = Index(rpamaxfactor * double(num_of_levels)) -
             1;  // total number of levels

    double qpminfactor = options.get(".qpmin").as<double>();
    qpmin =
        num_of_occlevels - Index(qpminfactor * double(num_of_occlevels)) - 1;

    double qpmaxfactor = options.get(".qpmax").as<double>();
    qpmax =
        num_of_occlevels + Index(qpmaxfactor * double(num_of_occlevels)) - 1;

    double bseminfactor = options.get(".bsemin").as<double>();
    bse_vmin =
        num_of_occlevels - Index(bseminfactor * double(num_of_occlevels)) - 1;

    double bsemaxfactor = options.get(".bsemax").as<double>();
    bse_cmax =
        num_of_occlevels + Index(bsemaxfactor * double(num_of_occlevels)) - 1;

  } else if (ranges == "explicit") {
    // get explicit numbers
    rpamax = options.get(".rpamax").as<Index>();
    qpmin = options.get(".qpmin").as<Index>();
    qpmax = options.get(".qpmax").as<Index>();
    bse_vmin = options.get(".bsemin").as<Index>();
    bse_cmax = options.get(".bsemax").as<Index>();
  } else if (ranges == "default") {
    rpamax = num_of_levels - 1;
    qpmin = 0;
    qpmax = 3 * homo + 1;
    bse_vmin = 0;
    bse_cmax = 3 * homo + 1;
  } else if (ranges == "full") {
    rpamax = num_of_levels - 1;
    qpmin = 0;
    qpmax = num_of_levels - 1;
    bse_vmin = 0;
    bse_cmax = num_of_levels - 1;
  }
  std::string ignore_corelevels =
      options.get(".ignore_corelevels").as<std::string>();

  if (ignore_corelevels == "RPA" || ignore_corelevels == "GW" ||
      ignore_corelevels == "BSE") {
    Index ignored_corelevels = CountCoreLevels();
    if (ignore_corelevels == "RPA") {
      rpamin = ignored_corelevels;
    }
    if (ignore_corelevels == "GW" || ignore_corelevels == "RPA") {
      if (qpmin < ignored_corelevels) {
        qpmin = ignored_corelevels;
      }
    }
    if (ignore_corelevels == "GW" || ignore_corelevels == "RPA" ||
        ignore_corelevels == "BSE") {
      if (bse_vmin < ignored_corelevels) {
        bse_vmin = ignored_corelevels;
      }
    }

    XTP_LOG(Log::error, *pLog_)
        << TimeStamp() << " Ignoring " << ignored_corelevels
        << " core levels for " << ignore_corelevels << " and beyond." << flush;
  }

  // check maximum and minimum sizes
  if (rpamax > num_of_levels) {
    rpamax = num_of_levels - 1;
  }
  if (qpmax > num_of_levels) {
    qpmax = num_of_levels - 1;
  }
  if (bse_cmax > num_of_levels) {
    bse_cmax = num_of_levels - 1;
  }
  if (bse_vmin < 0) {
    bse_vmin = 0;
  }
  if (qpmin < 0) {
    qpmin = 0;
  }

  gwopt_.homo = homo;
  gwopt_.qpmin = qpmin;
  gwopt_.qpmax = qpmax;
  gwopt_.rpamin = rpamin;
  gwopt_.rpamax = rpamax;

  bseopt_.vmin = bse_vmin;
  bseopt_.cmax = bse_cmax;
  bseopt_.homo = homo;
  bseopt_.qpmin = qpmin;
  bseopt_.qpmax = qpmax;
  bseopt_.rpamin = rpamin;
  bseopt_.rpamax = rpamax;

  orbitals_.setRPAindices(rpamin, rpamax);
  orbitals_.setGWindices(qpmin, qpmax);
  orbitals_.setBSEindices(bse_vmin, bse_cmax);
  orbitals_.SetFlagUseHqpOffdiag(bseopt_.use_Hqp_offdiag);

  Index bse_vmax = homo;
  Index bse_cmin = homo + 1;
  Index bse_vtotal = bse_vmax - bse_vmin + 1;
  Index bse_ctotal = bse_cmax - bse_cmin + 1;
  Index bse_size = bse_vtotal * bse_ctotal;

  XTP_LOG(Log::error, *pLog_) << TimeStamp() << " RPA level range [" << rpamin
                              << ":" << rpamax << "]" << flush;
  XTP_LOG(Log::error, *pLog_) << TimeStamp() << " GW  level range [" << qpmin
                              << ":" << qpmax << "]" << flush;
  XTP_LOG(Log::error, *pLog_)
      << TimeStamp() << " BSE level range occ[" << bse_vmin << ":" << bse_vmax
      << "]  virt[" << bse_cmin << ":" << bse_cmax << "]" << flush;

  gwopt_.reset_3c = options.get(".gw.rebuild_3c_freq").as<Index>();

  bseopt_.nmax = options.get(".bse.exctotal").as<Index>();
  if (bseopt_.nmax > bse_size || bseopt_.nmax < 0) {
    bseopt_.nmax = bse_size;
  }

  bseopt_.davidson_correction =
      options.get("bse.davidson.correction").as<std::string>();

  bseopt_.davidson_tolerance =
      options.get("bse.davidson.tolerance").as<std::string>();

  bseopt_.davidson_update =
      options.get("bse.davidson.update").as<std::string>();

  bseopt_.davidson_maxiter = options.get("bse.davidson.maxiter").as<Index>();

  bseopt_.useTDA = options.get("bse.useTDA").as<bool>();
  orbitals_.setTDAApprox(bseopt_.useTDA);
  if (!bseopt_.useTDA) {
    XTP_LOG(Log::error, *pLog_) << " BSE type: full" << flush;
  } else {
    XTP_LOG(Log::error, *pLog_) << " BSE type: TDA" << flush;
  }

  Index full_bse_size = (bseopt_.useTDA) ? bse_size : 2 * bse_size;
  XTP_LOG(Log::error, *pLog_) << TimeStamp() << " BSE Hamiltonian has size "
                              << full_bse_size << "x" << full_bse_size << flush;

  bseopt_.use_Hqp_offdiag = options.get("bse.use_Hqp_offdiag").as<bool>();

  if (!bseopt_.use_Hqp_offdiag) {
    XTP_LOG(Log::error, *pLog_)
        << " BSE without Hqp offdiagonal elements" << flush;
  } else {
    XTP_LOG(Log::error, *pLog_)
        << " BSE with Hqp offdiagonal elements" << flush;
  }

  bseopt_.max_dyn_iter = options.get("bse.dyn_screen_max_iter").as<Index>();
  bseopt_.dyn_tolerance = options.get("bse.dyn_screen_tol").as<double>();
  if (bseopt_.max_dyn_iter > 0) {
    do_dynamical_screening_bse_ = true;
  }

  functional_ = orbitals_.getXCFunctionalName();
  grid_ = orbitals_.getXCGrid();

  dftbasis_name_ = orbitals_.getDFTbasisName();
  if (orbitals_.hasAuxbasisName()) {
    auxbasis_name_ = orbitals_.getAuxbasisName();
  } else if (options.exists(".auxbasisset")) {
    auxbasis_name_ = options.get(".auxbasisset").as<std::string>();
  } else {
    auxbasis_name_ = "aux-" + dftbasis_name_;
    try {
      BasisSet b;
      b.Load(auxbasis_name_);
    } catch (std::runtime_error&) {
      std::runtime_error(
          "There is no auxbasis from the dftcalculation nor did you specify an "
          "auxbasisset for the gwbse calculation. Also no auxiliary basisset "
          "for basisset " +
          dftbasis_name_ + " could be found!");
    }
    XTP_LOG(Log::error, *pLog_)
        << " Could not find an auxbasisset using " << auxbasis_name_ << flush;
  }

  std::string mode = options.get("gw.mode").as<std::string>();
  if (mode == "G0W0") {
    gwopt_.gw_sc_max_iterations = 1;
  } else if (mode == "evGW") {
    gwopt_.g_sc_limit = 0.1 * gwopt_.gw_sc_limit;
    gwopt_.eta = 0.1;
  }

  XTP_LOG(Log::error, *pLog_) << " Running GW as: " << mode << flush;
  gwopt_.ScaHFX = orbitals_.getScaHFX();

  gwopt_.shift = options.get("gw.scissor_shift").as<double>();
  gwopt_.g_sc_limit =
      options.get("gw.qp_sc_limit").as<double>();  // convergence criteria
                                                   // for qp iteration
                                                   // [Hartree]]
  gwopt_.g_sc_max_iterations =
      options.get("gw.qp_sc_max_iter").as<Index>();  // convergence
                                                     // criteria for qp
                                                     // iteration
                                                     // [Hartree]]

  if (mode == "evGW") {
    gwopt_.gw_sc_max_iterations = options.get("gw.sc_max_iter").as<Index>();
  }

  gwopt_.gw_sc_limit =
      options.get("gw.sc_limit").as<double>();  // convergence criteria
                                                // for shift it
  XTP_LOG(Log::error, *pLog_)
      << " qp_sc_limit [Hartree]: " << gwopt_.g_sc_limit << flush;
  if (gwopt_.gw_sc_max_iterations > 1) {
    XTP_LOG(Log::error, *pLog_)
        << " gw_sc_limit [Hartree]: " << gwopt_.gw_sc_limit << flush;
  }
  bseopt_.min_print_weight = options.get("bse.print_weight").as<double>();
  // print exciton WF composition weight larger than minimum

  // possible tasks
  std::string tasks_string = options.get(".tasks").as<std::string>();
  boost::algorithm::to_lower(tasks_string);
  if (tasks_string.find("all") != std::string::npos) {
    do_gw_ = true;
    do_bse_singlets_ = true;
    do_bse_triplets_ = true;
  }
  if (tasks_string.find("gw") != std::string::npos) {
    do_gw_ = true;
  }
  if (tasks_string.find("singlets") != std::string::npos) {
    do_bse_singlets_ = true;
  }
  if (tasks_string.find("triplets") != std::string::npos) {
    do_bse_triplets_ = true;
  }

  XTP_LOG(Log::error, *pLog_) << " Tasks: " << flush;
  if (do_gw_) {
    XTP_LOG(Log::error, *pLog_) << " GW " << flush;
  }
  if (do_bse_singlets_) {
    XTP_LOG(Log::error, *pLog_) << " singlets " << flush;
  }
  if (do_bse_triplets_) {
    XTP_LOG(Log::error, *pLog_) << " triplets " << flush;
  }
  XTP_LOG(Log::error, *pLog_) << " Store: " << flush;
  if (do_gw_) {
    XTP_LOG(Log::error, *pLog_) << " GW " << flush;
  }

  if (options.exists("bse.fragments")) {
    std::vector<tools::Property*> prop_region =
        options.Select("bse.fragments.fragment");
    Index index = 0;
    for (tools::Property* prop : prop_region) {
      std::string indices = prop->get("indices").as<std::string>();
      fragments_.push_back(QMFragment<BSE_Population>(index, indices));
      index++;
    }
  }

  gwopt_.sigma_integration =
      options.get("gw.sigma_integrator").as<std::string>();
  XTP_LOG(Log::error, *pLog_)
      << " Sigma integration: " << gwopt_.sigma_integration << flush;
  gwopt_.eta = options.get("gw.eta").as<double>();
  XTP_LOG(Log::error, *pLog_) << " eta: " << gwopt_.eta << flush;
  if (gwopt_.sigma_integration == "exact") {
    XTP_LOG(Log::error, *pLog_)
        << " RPA Hamiltonian size: " << (homo + 1 - rpamin) * (rpamax - homo)
        << flush;
  }
  if (gwopt_.sigma_integration == "cda") {
    gwopt_.order = options.get("gw.quadrature_order").as<Index>();
    XTP_LOG(Log::error, *pLog_)
        << " Quadrature integration order : " << gwopt_.order << flush;
    gwopt_.quadrature_scheme =
        options.get(".quadrature_scheme").as<std::string>();
    XTP_LOG(Log::error, *pLog_)
        << " Quadrature integration scheme : " << gwopt_.quadrature_scheme
        << flush;
    gwopt_.alpha = options.get("gw.alpha").as<double>();
    XTP_LOG(Log::error, *pLog_)
        << " Alpha smoothing parameter : " << gwopt_.alpha << flush;
  }
  gwopt_.qp_solver = options.get("gw.qp_solver").as<std::string>();

  XTP_LOG(Log::error, *pLog_) << " QP solver: " << gwopt_.qp_solver << flush;
  if (gwopt_.qp_solver == "grid") {
    gwopt_.qp_grid_steps = options.get("gw.qp_grid_steps").as<Index>();
    gwopt_.qp_grid_spacing = options.get("gw.qp_grid_spacing").as<double>();
    XTP_LOG(Log::error, *pLog_)
        << " QP grid steps: " << gwopt_.qp_grid_steps << flush;
    XTP_LOG(Log::error, *pLog_)
        << " QP grid spacing: " << gwopt_.qp_grid_spacing << flush;
  }
  gwopt_.gw_mixing_order =
      options.get("gw.mixing_order").as<Index>();  // max history in
                                                   // mixing (0: plain,
                                                   // 1: linear, >1
                                                   // Anderson)

  gwopt_.gw_mixing_alpha = options.get("gw.mixing_alpha").as<double>();

  if (mode == "evGW") {
    if (gwopt_.gw_mixing_order == 0) {
      XTP_LOG(Log::error, *pLog_) << " evGW with plain update " << std::flush;
    } else if (gwopt_.gw_mixing_order == 1) {
      XTP_LOG(Log::error, *pLog_) << " evGW with linear update using alpha "
                                  << gwopt_.gw_mixing_alpha << std::flush;
    } else {
      XTP_LOG(Log::error, *pLog_) << " evGW with Anderson update with history "
                                  << gwopt_.gw_mixing_order << " using alpha "
                                  << gwopt_.gw_mixing_alpha << std::flush;
    }
  }

  if (options.exists(".sigma_plot")) {
    sigma_plot_states_ = options.get(".sigma_plot.states").as<std::string>();
    sigma_plot_steps_ = options.get(".sigma_plot.steps").as<Index>();
    sigma_plot_spacing_ = options.get(".sigma_plot.spacing").as<double>();
    sigma_plot_filename_ =
        options.get(".sigma_plot.filename").as<std::string>();

    XTP_LOG(Log::error, *pLog_)
        << " Sigma plot states: " << sigma_plot_states_ << flush;
    XTP_LOG(Log::error, *pLog_)
        << " Sigma plot steps: " << sigma_plot_steps_ << flush;
    XTP_LOG(Log::error, *pLog_)
        << " Sigma plot spacing: " << sigma_plot_spacing_ << flush;
    XTP_LOG(Log::error, *pLog_)
        << " Sigma plot filename: " << sigma_plot_filename_ << flush;
  }
}

void GWBSE::addoutput(tools::Property& summary) {

  const double hrt2ev = tools::conv::hrt2ev;
  tools::Property& gwbse_summary = summary.add("GWBSE", "");
  if (do_gw_) {
    gwbse_summary.setAttribute("units", "eV");
    gwbse_summary.setAttribute(
        "DFTEnergy",
        (format("%1$+1.6f ") % (orbitals_.getDFTTotalEnergy() * hrt2ev)).str());

    tools::Property& dft_summary = gwbse_summary.add("dft", "");
    dft_summary.setAttribute("HOMO", gwopt_.homo);
    dft_summary.setAttribute("LUMO", gwopt_.homo + 1);

    for (Index state = 0; state < gwopt_.qpmax + 1 - gwopt_.qpmin; state++) {
      tools::Property& level_summary = dft_summary.add("level", "");
      level_summary.setAttribute("number", state + gwopt_.qpmin);
      level_summary.add(
          "dft_energy",
          (format("%1$+1.6f ") %
           (orbitals_.MOs().eigenvalues()(state + gwopt_.qpmin) * hrt2ev))
              .str());
      level_summary.add(
          "gw_energy",
          (format("%1$+1.6f ") % (orbitals_.QPpertEnergies()(state) * hrt2ev))
              .str());

      level_summary.add("qp_energy",
                        (format("%1$+1.6f ") %
                         (orbitals_.QPdiag().eigenvalues()(state) * hrt2ev))
                            .str());
    }
  }
  if (do_bse_singlets_) {
    tools::Property& singlet_summary = gwbse_summary.add("singlets", "");
    for (Index state = 0; state < bseopt_.nmax; ++state) {
      tools::Property& level_summary = singlet_summary.add("level", "");
      level_summary.setAttribute("number", state + 1);
      level_summary.add(
          "omega", (format("%1$+1.6f ") %
                    (orbitals_.BSESinglets().eigenvalues()(state) * hrt2ev))
                       .str());
      if (orbitals_.hasTransitionDipoles()) {

        const Eigen::Vector3d& dipoles = (orbitals_.TransitionDipoles())[state];
        double f = 2 * dipoles.squaredNorm() *
                   orbitals_.BSESinglets().eigenvalues()(state) / 3.0;

        level_summary.add("f", (format("%1$+1.6f ") % f).str());
        tools::Property& dipol_summary = level_summary.add(
            "Trdipole", (format("%1$+1.4f %2$+1.4f %3$+1.4f") % dipoles.x() %
                         dipoles.y() % dipoles.z())
                            .str());
        dipol_summary.setAttribute("unit", "e*bohr");
        dipol_summary.setAttribute("gauge", "length");
      }
    }
  }
  if (do_bse_triplets_) {
    tools::Property& triplet_summary = gwbse_summary.add("triplets", "");
    for (Index state = 0; state < bseopt_.nmax; ++state) {
      tools::Property& level_summary = triplet_summary.add("level", "");
      level_summary.setAttribute("number", state + 1);
      level_summary.add(
          "omega", (format("%1$+1.6f ") %
                    (orbitals_.BSETriplets().eigenvalues()(state) * hrt2ev))
                       .str());
    }
  }
  return;
}

/*
 *    Many-body Green's fuctions theory implementation
 *
 *  data required from orbitals file
 *  - atomic coordinates
 *  - DFT molecular orbitals (energies and coeffcients)
 *  - DFT exchange-correlation potential matrix in atomic orbitals
 *  - number of electrons, number of levels
 */

Eigen::MatrixXd GWBSE::CalculateVXC(const AOBasis& dftbasis) {
  if (orbitals_.getXCFunctionalName().empty()) {
    orbitals_.setXCFunctionalName(functional_);
  } else {
    if (!(functional_ == orbitals_.getXCFunctionalName())) {
      throw std::runtime_error("Functionals from DFT " +
                               orbitals_.getXCFunctionalName() + " GWBSE " +
                               functional_ + " differ!");
    }
  }

  Vxc_Grid grid;
  grid.GridSetup(grid_, orbitals_.QMAtoms(), dftbasis);
  XTP_LOG(Log::info, *pLog_)
      << TimeStamp() << " Setup grid for integration with gridsize: " << grid_
      << " with " << grid.getGridSize() << " points, divided into "
      << grid.getBoxesSize() << " boxes" << flush;
  Vxc_Potential<Vxc_Grid> vxcpotential(grid);
  vxcpotential.setXCfunctional(functional_);
  XTP_LOG(Log::error, *pLog_)
      << TimeStamp() << " Integrating Vxc with functional " << functional_
      << flush;
  Eigen::MatrixXd DMAT = orbitals_.DensityMatrixGroundState();
  Mat_p_Energy e_vxc_ao = vxcpotential.IntegrateVXC(DMAT);
  XTP_LOG(Log::info, *pLog_) << TimeStamp() << " Calculated Vxc" << flush;
  XTP_LOG(Log::error, *pLog_)
      << TimeStamp() << " Set hybrid exchange factor: " << orbitals_.getScaHFX()
      << flush;
  Index qptotal = gwopt_.qpmax - gwopt_.qpmin + 1;
  Eigen::MatrixXd mos =
      orbitals_.MOs().eigenvectors().middleCols(gwopt_.qpmin, qptotal);

  Eigen::MatrixXd vxc = mos.transpose() * e_vxc_ao.matrix() * mos;
  XTP_LOG(Log::error, *pLog_)
      << TimeStamp() << " Calculated exchange-correlation expectation values "
      << flush;

  return vxc;
}

bool GWBSE::Evaluate() {

  // set the parallelization
  XTP_LOG(Log::error, *pLog_) << TimeStamp() << " Using "
                              << OPENMP::getMaxThreads() << " threads" << flush;

  if (XTP_HAS_MKL_OVERLOAD()) {
    XTP_LOG(Log::error, *pLog_)
        << TimeStamp() << " Using MKL overload for Eigen " << flush;
  } else {
    XTP_LOG(Log::error, *pLog_)
        << TimeStamp()
        << " Using native Eigen implementation, no BLAS overload " << flush;
  }
  Index nogpus = OpenMP_CUDA::UsingGPUs();
  if (nogpus > 0) {
    XTP_LOG(Log::error, *pLog_)
        << TimeStamp() << " Using CUDA support for tensor multiplication with "
        << nogpus << " GPUs." << flush;
  }

  XTP_LOG(Log::error, *pLog_)
      << TimeStamp() << " Molecule Coordinates [A] " << flush;
  for (QMAtom& atom : orbitals_.QMAtoms()) {
    std::string output = (boost::format("%5d"
                                        "%5s"
                                        "   %1.4f %1.4f %1.4f") %
                          atom.getId() % atom.getElement() %
                          (atom.getPos().x() * tools::conv::bohr2ang) %
                          (atom.getPos().y() * tools::conv::bohr2ang) %
                          (atom.getPos().z() * tools::conv::bohr2ang))
                             .str();

    XTP_LOG(Log::error, *pLog_) << output << flush;
  }

  std::string dft_package = orbitals_.getQMpackage();
  XTP_LOG(Log::error, *pLog_)
      << TimeStamp() << " DFT data was created by " << dft_package << flush;

  BasisSet dftbs;
  dftbs.Load(dftbasis_name_);

  XTP_LOG(Log::error, *pLog_)
      << TimeStamp() << " Loaded DFT Basis Set " << dftbasis_name_ << flush;

  // fill DFT AO basis by going through all atoms
  AOBasis dftbasis;
  dftbasis.Fill(dftbs, orbitals_.QMAtoms());
  XTP_LOG(Log::error, *pLog_) << TimeStamp() << " Filled DFT Basis of size "
                              << dftbasis.AOBasisSize() << flush;

  // load auxiliary basis set (element-wise information) from xml file
  BasisSet auxbs;
  auxbs.Load(auxbasis_name_);
  XTP_LOG(Log::error, *pLog_)
      << TimeStamp() << " Loaded Auxbasis Set " << auxbasis_name_ << flush;

  // fill auxiliary AO basis by going through all atoms
  AOBasis auxbasis;
  auxbasis.Fill(auxbs, orbitals_.QMAtoms());
  orbitals_.setAuxbasisName(auxbasis_name_);
  XTP_LOG(Log::error, *pLog_) << TimeStamp() << " Filled Auxbasis of size "
                              << auxbasis.AOBasisSize() << flush;

  if ((do_bse_singlets_ || do_bse_triplets_) && fragments_.size() > 0) {
    for (const auto& frag : fragments_) {
      XTP_LOG(Log::error, *pLog_) << TimeStamp() << " Fragment " << frag.getId()
                                  << " size:" << frag.size() << flush;
    }
  }

  if (!do_gw_ && !orbitals_.hasQPdiag()) {
    throw std::runtime_error(
        "You want no GW calculation but the orb file has no QPcoefficients for "
        "BSE");
  }
  TCMatrix_gwbse Mmn;
  // rpamin here, because RPA needs till rpamin
  Index max_3c = std::max(bseopt_.cmax, gwopt_.qpmax);
  Mmn.Initialize(auxbasis.AOBasisSize(), gwopt_.rpamin, max_3c, gwopt_.rpamin,
                 gwopt_.rpamax);
  XTP_LOG(Log::error, *pLog_)
      << TimeStamp()
      << " Calculating Mmn_beta (3-center-repulsion x orbitals)  " << flush;
  Mmn.Fill(auxbasis, dftbasis, orbitals_.MOs().eigenvectors());
  XTP_LOG(Log::info, *pLog_)
      << TimeStamp() << " Removed " << Mmn.Removedfunctions()
      << " functions from Aux Coulomb matrix to avoid near linear dependencies"
      << flush;
  XTP_LOG(Log::error, *pLog_)
      << TimeStamp() << " Calculated Mmn_beta (3-center-repulsion x orbitals)  "
      << flush;

  Eigen::MatrixXd Hqp;
  if (do_gw_) {

    std::chrono::time_point<std::chrono::system_clock> start =
        std::chrono::system_clock::now();
    Eigen::MatrixXd vxc = CalculateVXC(dftbasis);
    GW gw = GW(*pLog_, Mmn, vxc, orbitals_.MOs().eigenvalues());
    gw.configure(gwopt_);
    gw.CalculateGWPerturbation();

    if (!sigma_plot_states_.empty()) {
      gw.PlotSigma(sigma_plot_filename_, sigma_plot_steps_, sigma_plot_spacing_,
                   sigma_plot_states_);
    }

    // store perturbative QP energy data in orbitals object (DFT, S_x,S_c, V_xc,
    // E_qp)
    orbitals_.QPpertEnergies() = gw.getGWAResults();
    orbitals_.RPAInputEnergies() = gw.RPAInputEnergies();

    XTP_LOG(Log::info, *pLog_)
        << TimeStamp() << " Calculating offdiagonal part of Sigma  " << flush;
    gw.CalculateHQP();
    XTP_LOG(Log::error, *pLog_)
        << TimeStamp() << " Calculated offdiagonal part of Sigma  " << flush;

    Hqp = gw.getHQP();

    Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es =
        gw.DiagonalizeQPHamiltonian();
    if (es.info() == Eigen::ComputationInfo::Success) {
      XTP_LOG(Log::error, *pLog_)
          << TimeStamp() << " Diagonalized QP Hamiltonian  " << flush;
    }

    orbitals_.QPdiag().eigenvectors() = es.eigenvectors();
    orbitals_.QPdiag().eigenvalues() = es.eigenvalues();
    std::chrono::duration<double> elapsed_time =
        std::chrono::system_clock::now() - start;
    XTP_LOG(Log::error, *pLog_) << TimeStamp() << " GW calculation took "
                                << elapsed_time.count() << " seconds." << flush;

  } else {
    if (orbitals_.getGWAmax() != gwopt_.qpmax ||
        orbitals_.getGWAmin() != gwopt_.qpmin ||
        orbitals_.getRPAmax() != gwopt_.rpamax ||
        orbitals_.getRPAmin() != gwopt_.rpamin) {
      throw std::runtime_error(
          "The ranges for GW and RPA do not agree with the ranges from the "
          ".orb file, rerun your GW calculation");
    }
    const Eigen::MatrixXd& qpcoeff = orbitals_.QPdiag().eigenvectors();

    Hqp = qpcoeff * orbitals_.QPdiag().eigenvalues().asDiagonal() *
          qpcoeff.transpose();
  }

  // proceed only if BSE requested
  if (do_bse_singlets_ || do_bse_triplets_) {

    std::chrono::time_point<std::chrono::system_clock> start =
        std::chrono::system_clock::now();

    BSE bse = BSE(*pLog_, Mmn);
    bse.configure(bseopt_, orbitals_.RPAInputEnergies(), Hqp);

    // store the direct contribution to the static BSE results
    Eigen::VectorXd Hd_static_contrib_triplet;
    Eigen::VectorXd Hd_static_contrib_singlet;

    if (do_bse_triplets_) {
      bse.Solve_triplets(orbitals_);
      XTP_LOG(Log::error, *pLog_)
          << TimeStamp() << " Solved BSE for triplets " << flush;
      bse.Analyze_triplets(fragments_, orbitals_);
    }

    if (do_bse_singlets_) {
      bse.Solve_singlets(orbitals_);
      XTP_LOG(Log::error, *pLog_)
          << TimeStamp() << " Solved BSE for singlets " << flush;
      bse.Analyze_singlets(fragments_, orbitals_);
    }

    // do perturbative dynamical screening in BSE
    if (do_dynamical_screening_bse_) {

      if (do_bse_triplets_) {
        bse.Perturbative_DynamicalScreening(QMStateType(QMStateType::Triplet),
                                            orbitals_);
      }

      if (do_bse_singlets_) {
        bse.Perturbative_DynamicalScreening(QMStateType(QMStateType::Singlet),
                                            orbitals_);
      }
    }

    std::chrono::duration<double> elapsed_time =
        std::chrono::system_clock::now() - start;
    XTP_LOG(Log::error, *pLog_) << TimeStamp() << " BSE calculation took "
                                << elapsed_time.count() << " seconds." << flush;
  }
  XTP_LOG(Log::error, *pLog_)
      << TimeStamp() << " GWBSE calculation finished " << flush;
  return true;
}

}  // namespace xtp
}  // namespace votca


/*
 * This file is a part of the TChecker project.
 *
 * See files AUTHORS and LICENSE for copyright details.
 *
 */

#ifndef TCHECKER_REFDBM_HH
#define TCHECKER_REFDBM_HH

#include <boost/dynamic_bitset/dynamic_bitset.hpp>

#include "tchecker/basictypes.hh"
#include "tchecker/dbm/db.hh"
#include "tchecker/dbm/dbm.hh"
#include "tchecker/variables/clocks.hh"

/*!
 \file refdbm.hh
 \brief Functions on DBMs with reference clocks
 \note A DBM with reference clocks is a DBM where the first refcount variables
 are reference variables, and the other variables are clock (or offset)
 variables. Each clock variable has a corresponding reference variable. The
 correspondance is defined according to an instance r of
 tchecker::reference_clock_variables_t. It associates to each clock variable X
 its reference clock r(X). Each reference clock is mapped to itself.

 The value of system clock x is represented as the value of X-r(X), the
 difference between the corresponding clock variable X and its reference clock
 r(X). Reference DBMs generalise standard DBMs which have a single reference
 clock, usually denoted 0.

 Reference DBMs have been introduced as "offset DBMs" in "Partial Order
 Reduction for Timed Systems", J. Bengtsson, B. Jonsson, J. Lilis and Wang Yi,
 CONCUR, 1998.
 */

namespace tchecker {

namespace refdbm {

/*!
 \brief Universal DBM with reference clocks
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \pre rdbm is a DBM over reference clocks r
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 \post dbm is the universal DBM over reference clocks r
 rdbm is tight and consistent.
 */
void universal(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Positive universal DBM with reference clocks
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 \post rdbm is the universal positive DBM over reference clocks r
 dbm is tight and consistent
 */
void universal_positive(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Empty DBM with reference clocks
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 \post rdbm represents the empty DBM over reference clocks r (is_empty_0() returns
 true on dbm)
 rdbm IS NOT TIGHT (empty DBMs cannot be tight), NOR CONSISTENT
 */
void empty(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Initialize DBM with reference clocks to zero
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 \post rdbm represents the zone where all variables are equal to each other over
 reference variables r.
 dbm is tight and consistent.
 */
void zero(tchecker::dbm::db_t * dbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Fast emptiness predicate on DBMs with reference clocks
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is either non-empty and tight, or it is empty
 rdbm is a DBM over reference clocks r
 \return true if rdbm has a negative difference bound on (0,0), false otherwise
 \note this function only checks the (0,0) entry of rdbm. Hence, this function
 returns false for empty DBMs which have no negative bound on (0,0).
 However, all other functions on DBMs with reference clocks set (0,0) to a value
 less-than <=0 when they generate an empty zone. So this function can be used as
 a safe and efficient emptiness check if rdbm has been generated by calls to
 functions over DBMs with reference variables defined in this file.
 */
bool is_empty_0(tchecker::dbm::db_t const * rdbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Universality predicate on DBMs with reference clocks
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \return true if rdbm is universal, false otherwise
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is tight (checked by assertion)
 rdbm is a DBM over reference clocks r
 */
bool is_universal(tchecker::dbm::db_t const * rdbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Positivity predicate on DBMs with reference clocks
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \return true if all clocks in rdbm have a value greater-than or equal-to their
 reference clock, false otherwise
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is tight (checked by assertion)
 rdbm is a DBM over reference clocks r
 */
bool is_positive(tchecker::dbm::db_t const * rdbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Positive universality predicate on DBMs with reference clocks
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \return true if rdbm is universal positive (the only constraints in rdbm are
 positivity constraints: each clock is greater-than or equal-to its reference
 clock), false otherwise.
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is tight (checked by assertion)
 rdbm is a DBM over reference clocks r
 */
bool is_universal_positive(tchecker::dbm::db_t const * rdbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Checks if a DBM with reference clocks is open up (time-elapsed)
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \return true if rdbm is open up (it is closed under time successors), false
 otherwise
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is tight (checked by assertion)
 rdbm is consistent (checked by assertion)
 rdbm is a DBM over reference clocks r
 */
bool is_open_up(tchecker::dbm::db_t const * rdbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Tightness predicate on DBMs with reference clocks
 \param rdbm : a DBM
 \param r : reference clocks fr rdbm
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is a DBM over reference clocks r
 \return true if dbm is tight, false otherwise
 */
bool is_tight(tchecker::dbm::db_t const * rdbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Tighten a  DBM with reference clocks
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is a DBM over reference clocks r
 rdbm is consistent (checked by assertion)
 \post rdbm is tight if it is not empty.
 if rdbm is empty, then the difference bound in (0,0) is less-than <=0
 (tchecker::refdbm::is_empty_0() returns true)
 \return EMPTY if rdbm is empty, NON_EMPTY otherwise
 \note Applies Floyd-Warshall algorithm on rdbm seen as a weighted graph.
 */
enum tchecker::dbm::status_t tighten(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Consistency of DBMs with reference clocks
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is a DBM over reference clocks r
 \return true if rdbm is consistent (no negative value on the diagonal), false
 otherwise
*/
bool is_consistent(tchecker::dbm::db_t const * rdbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Synchronized predicate on DBMs with reference clocks
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is consistent (checked by assertion)
 rdbm is tight (checked by assertion)
 rdbm is a DBM over reference clocks r
 \return true if reference clocks are equal to each other in rdbm, false otherwise
 */
bool is_synchronized(tchecker::dbm::db_t const * rdbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Synchronized predicate on DBMs with reference clocks
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \param sync_ref_clocks : synchronized reference clocks
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is consistent (checked by assertion)
 rdbm is tight (checked by assertion)
 rdbm is a DBM over reference clocks r
 the size of sync_ref_clocks is the number of reference clocks in r (checked by
 assertion)
 \return true if all reference clocks in sync_ref_clocks are equal in rdbm,
 false otherwise
 */
bool is_synchronized(tchecker::dbm::db_t const * rdbm, tchecker::reference_clock_variables_t const & r,
                     boost::dynamic_bitset<> const & sync_ref_clocks);

/*!
 \brief Check if a DBM with reference clocks contains a synchronized valuation
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is consistent (checked by assertion)
 rdbm is tight (checked by assertion)
 rdbm is a DBM over reference clocks r
 \return true dbm contains a synchronized valuation, false otherwise
 */
bool is_synchronizable(tchecker::dbm::db_t const * rdbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Equality predicate on DBMs with reference clocks
 \param rdbm1 : a first DBM
 \param rdbm2 : a second DBM
 \param r : reference clocks for rdbm1 and rdbm2
 \pre rdbm1 and rdbm2 are not nullptr (checked by assertion)
 rdbm1 and rdbm2 are r.size()*r.size() arrays of difference bounds
 rdbm1 and rdbm2 are consistent (checked by assertion)
 rdbm1 and rdbm2 are tight (checked by assertion)
 rdbm1 and rdbm2 are DBMs over reference clocks r
 \return true if rdbm1 and rdbm2 are equal, false otherwise
 */
bool is_equal(tchecker::dbm::db_t const * rdbm1, tchecker::dbm::db_t const * rdbm2,
              tchecker::reference_clock_variables_t const & r);

/*!
 \brief Inclusion predicate on DBMs with reference clocks
 \param rdbm1 : a first DBM
 \param rdbm2 : a second DBM
 \param r : reference clocks for rdbm1 and rdbm2
 \pre rdbm1 and rdbm2 are not nullptr (checked by assertion)
 rdbm1 and rdbm2 are r.size()*r.size() arrays of difference bounds
 rdbm1 and rdbm2 are consistent (checked by assertion)
 rdbm1 and rdbm2 are tight (checked by assertion)
 rdbm1 and rdbm2 are DBMs over reference clocks r
 \return true if rdbm1 is included into rdbm2, false otherwise
 */
bool is_le(tchecker::dbm::db_t const * rdbm1, tchecker::dbm::db_t const * rdbm2,
           tchecker::reference_clock_variables_t const & r);

/*!
 \brief Checks inclusion w.r.t. abstraction aLU* (i.e. aLU over reference DBMs)
 \param rdbm1 : a first dbm
 \param rdbm2 : a second dbm
 \param r : reference clocks for rdbm1 and rdbm2
 \param l : clock lower bounds for offset clocks, l[0] is the bound for offset
 clock 1 and so on
 \param u : clock upper bounds for offset clocks, u[0] is the bound for offset
 clock 1 and so on
 \pre rdbm1 and rdbm2 are not nullptr (checked by assertion)
 rdbm1 and rdbm2 are r.size()*r.size() arrays of difference bounds
 rdbm1 and rdbm2 are consistent (checked by assertion)
 rdbm1 and rdbm2 are positive (checked by assertion)
 rdbm1 and rdbm2 are tight (checked by assertion)
 l and u are arrays of size r.size()-r.refcount()
 l[i], u[i] < tchecker::dbm::INF_VALUE for all offset clock i>=0 (checked by
 assertion)
 \return true if rdbm1 <= aLU*(rdbm2), false otherwise
 \note set l[i]/u[i] to tchecker::clockbounds::NO_BOUND if clock i has no
 lower/upper bound
 */
bool is_alu_star_le(tchecker::dbm::db_t const * rdbm1, tchecker::dbm::db_t const * rdbm2,
                    tchecker::reference_clock_variables_t const & r, tchecker::integer_t const * l,
                    tchecker::integer_t const * u);

/*!
 \brief Checks inclusion w.r.t. abstraction aM* (i.e. aM over reference DBMs)
 \param rdbm1 : a first dbm
 \param rdbm2 : a second dbm
 \param r : reference clocks for rdbm1 and rdbm2
 \param m : clock bounds for offset clocks, m[0] is the bound for offset clock 1
 and so on
 \pre rdbm1 and rdbm2 are not nullptr (checked by assertion)
 rdbm1 and rdbm2 are r.size()*r.size() arrays of difference bounds
 rdbm1 and rdbm2 are consistent (checked by assertion)
 rdbm1 and rdbm2 are positive (checked by assertion)
 rdbm1 and rdbm2 are tight (checked by assertion)
 m is an array of size r.size()-r.refcount()
 m[i] < tchecker::dbm::INF_VALUE for all offset clock i>=0 (checked by
 assertion)
 \return true if rdbm1 <= aM*(rdbm2), false otherwise
 \note set m[i] to tchecker::clockbounds::NO_BOUND if clock i has no lower/upper
 bound
 */
bool is_am_star_le(tchecker::dbm::db_t const * rdbm1, tchecker::dbm::db_t const * rdbm2,
                   tchecker::reference_clock_variables_t const & r, tchecker::integer_t const * m);

/*!
 \brief Checks inclusion w.r.t. abstraction aLU* (i.e. aLU over reference DBMs)
 combined with time elapse
 \param rdbm1 : a first dbm
 \param rdbm2 : a second dbm
 \param r : reference clocks for rdbm1 and rdbm2
 \param l : clock lower bounds for offset clocks, l[0] is the bound for offset
 clock 1 and so on
 \param u : clock upper bounds for offset clocks, u[0] is the bound for offset
 clock 1 and so on
 \pre rdbm1 and rdbm2 are not nullptr (checked by assertion)
 rdbm1 and rdbm2 are r.size()*r.size() arrays of difference bounds
 rdbm1 and rdbm2 are consistent (checked by assertion)
 rdbm1 and rdbm2 are positive (checked by assertion)
 rdbm1 and rdbm2 are tight (checked by assertion)
 l and u are arrays of size r.size()-r.refcount()
 l[i], u[i] < tchecker::dbm::INF_VALUE for all offset clock i>=0 (checked by
 assertion)
 \return true if time-elapse(rdbm1) <= aLU*(time-elapse(rdbm2)), false otherwise
 \note set l[i]/u[i] to tchecker::clockbounds::NO_BOUND if clock i has no
 lower/upper bound
 \note time-elapsed(dbm1) and time-elapsed(dbm2) are not computed but the
 algorithm for aLU* is adapted to behave as if rdbm1 and rdbm2 were time-elapsed
 */
bool is_time_elapse_alu_star_le(tchecker::dbm::db_t const * rdbm1, tchecker::dbm::db_t const * rdbm2,
                                tchecker::reference_clock_variables_t const & r, tchecker::integer_t const * l,
                                tchecker::integer_t const * u);

/*!
 \brief Checks inclusion w.r.t. abstraction aM* (i.e. aM over reference DBMs)
 combined with time elapse
 \param rdbm1 : a first dbm
 \param rdbm2 : a second dbm
 \param r : reference clocks for rdbm1 and rdbm2
 \param m : clock bounds for offset clocks, m[0] is the bound for offset clock 1
 and so on
 \pre rdbm1 and rdbm2 are not nullptr (checked by assertion)
 rdbm1 and rdbm2 are r.size()*r.size() arrays of difference bounds
 rdbm1 and rdbm2 are consistent (checked by assertion)
 rdbm1 and rdbm2 are positive (checked by assertion)
 rdbm1 and rdbm2 are tight (checked by assertion)
 m is an array of size r.size()-r.refcount()
 m[i] < tchecker::dbm::INF_VALUE for all offset clock i>=0 (checked by
 assertion)
 \return true if time-elapse(rdbm1) <= aM*(time-elapse(rdbm2)), false otherwise
 \note set m[i] to tchecker::clockbounds::NO_BOUND if clock i has no lower/upper
 bound
 \note time-elapsed(dbm1) and time-elapsed(dbm2) are not computed but the
 algorithm for aLU* is adapted to behave as if rdbm1 and rdbm2 were time-elapsed
 */
bool is_time_elapse_am_star_le(tchecker::dbm::db_t const * rdbm1, tchecker::dbm::db_t const * rdbm2,
                               tchecker::reference_clock_variables_t const & r, tchecker::integer_t const * m);

/*!
 \brief Checks inclusion w.r.t. abstraction aLU over synchronized valuations
 \param rdbm1 : a first dbm
 \param rdbm2 : a second dbm
 \param r : reference clocks for rdbm1 and rdbm2
 \param l : clock lower bounds for offset clocks, l[0] is the bound for offset
 clock 1 and so on
 \param u : clock upper bounds for offset clocks, u[0] is the bound for offset
 clock 1 and so on
 \pre rdbm1 and rdbm2 are not nullptr (checked by assertion)
 rdbm1 and rdbm2 are r.size()*r.size() arrays of difference bounds
 rdbm1 and rdbm2 are consistent (checked by assertion)
 rdbm1 and rdbm2 are positive (checked by assertion)
 rdbm1 and rdbm2 are tight (checked by assertion)
 l and u are arrays of size r.size()-r.refcount()
 l[i], u[i] < tchecker::dbm::INF_VALUE for all offset clock i>=0 (checked by
 assertion)
 \return true if sync(local_time_elapse(rdbm1)) <=
 aLU(sync(local_time_elapse(rdbm2))), false otherwise
 \note set l[i]/u[i] to tchecker::clockbounds::NO_BOUND if clock i has no
 lower/upper bound
 */
bool is_sync_alu_le(tchecker::dbm::db_t const * rdbm1, tchecker::dbm::db_t const * rdbm2,
                    tchecker::reference_clock_variables_t const & r, tchecker::integer_t const * l,
                    tchecker::integer_t const * u);

/*!
 \brief Checks inclusion w.r.t. abstraction aM over synchronized valuations
 \param rdbm1 : a first dbm
 \param rdbm2 : a second dbm
 \param r : reference clocks for rdbm1 and rdbm2
 \param m : clock bounds for offset clocks, m[0] is the bound for offset clock 1
 and so on
 \pre rdbm1 and rdbm2 are not nullptr (checked by assertion)
 rdbm1 and rdbm2 are r.size()*r.size() arrays of difference bounds
 rdbm1 and rdbm2 are consistent (checked by assertion)
 rdbm1 and rdbm2 are positive (checked by assertion)
 rdbm1 and rdbm2 are tight (checked by assertion)
 m is an array of size r.size()-r.refcount()
 m[i] < tchecker::dbm::INF_VALUE for all offset clock i>=0 (checked by
 assertion)
 \return true if sync(rdbm1) <= aM(sync(rdbm2)), false otherwise
 \note set m[i] to tchecker::clockbounds::NO_BOUND if clock i has no bound
 */
bool is_sync_am_le(tchecker::dbm::db_t const * rdbm1, tchecker::dbm::db_t const * rdbm2,
                   tchecker::reference_clock_variables_t const & r, tchecker::integer_t const * m);

/*!
 \brief Hash function on DBMs with reference clocks
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \return hash value for rdbm
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is a DBM over reference clocks r
 \note if rdbm is not tight, the returned hash code may differ from the hash
 code of its corresponding tight DBM
 */
std::size_t hash(tchecker::dbm::db_t const * rdbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Constrain a DBM with reference clocks
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \param x : index of first clock
 \param y : index of second clock
 \param cmp : constraint comparator
 \param value : constraint value
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is tight (checked by assertion)
 rdbm is a DBM over reference clocks r
 0 <= x < r.size() (checked by assertion)
 0 <= y < r.size() (checked by assertion)
 \post rdbm has been intersected with constraint `x - y # value` where # is < if
 cmp is LT, and # is <= if cmp is LE
 rdbm is tight if it is not empty.
 if rdbm is empty, then its difference bound in (0,0) is less-than <=0
 (tchecker::refdbm::is_empty_0() returns true)
 \return EMPTY if rdbm is empty, NON_EMPTY otherwise
 \throw std::invalid_argument : if `cmp value` cannot be represented as a
 tchecker::dbm::db_t (only if compilation flag DBM_UNSAFE is not set)
 */
enum tchecker::dbm::status_t constrain(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r,
                                       tchecker::clock_id_t x, tchecker::clock_id_t y, tchecker::dbm::comparator_t cmp,
                                       tchecker::integer_t value);

/*!
 \brief Constrain a DBM with reference clocks with a clock constraint
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \param c : clock constraint
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is tight (checked by assertion)
 rdbm is a DBM over reference clocks r
 clocks IDs in c are systems clocks, i.e. the first clock has index 0, and the
 last clock has index r.size() - r.refcount() - 1
 \post rdbm has been intersected with constraint c (translated w.r.t. r clocks)
 rdbm is tight if it is not empty.
 if rdbm is empty, then its difference bound in (0,0) is less-than <=0
 (tchecker::refdbm::is_empty_0() returns true)
 \return EMPTY if rdbm is empty, NON_EMPTY otherwise
 */
enum tchecker::dbm::status_t constrain(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r,
                                       tchecker::clock_constraint_t const & c);
/*!
 \brief Constrain a DBM with reference clocks with a collection of clock constraints
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \param cc : collection of clock constraints
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is tight (checked by assertion)
 rdbm is a DBM over reference clocks r
 clocks IDs in c are systems clocks, i.e. the first clock has index 0, and the
 last clock has index r.size() - r.refcount() - 1
 \post rdbm has been intersected with clock constraints in cc (translated w.r.t.
 r clocks))
 rdbm is tight if it is not empty.
 if rdbm is empty, then its difference bound in (0,0) is less-than <=0
 (tchecker::refdbm::is_empty_0() returns true)
 \return EMPTY if rdbm is empty, NON_EMPTY otherwise
 */
enum tchecker::dbm::status_t constrain(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r,
                                       tchecker::clock_constraint_container_t const & cc);

/*!
 \brief Restriction to synchronized valuations
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is consistent (checked by assertion)
 rdbm is tight (checked by assertion)
 \post rdbm has been restricted to its subset of synchronized valuations
 \return tchecker::dbm::EMPTY if synchronized dbm is empty,
 tchecker::dbm::NON_EMPTY otherwise
 */
enum tchecker::dbm::status_t synchronize(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Restriction to synchronized valuations over specified set of reference
 clocks
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \param sync_ref_clocks : set of reference clocks to synchronize
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is consistent (checked by assertion)
 rdbm is tight (checked by assertion)
 the size of sync_ref_clocks is the number of reference clocks in r (checked by
 assertion)
 \post rdbm has been restricted to its subset of valuations that are
 synchronized over all reference clocks in sync_ref_clocks
 \return tchecker::dbm::EMPTY if synchronized dbm is empty,
 tchecker::dbm::NON_EMPTY otherwise
 */
enum tchecker::dbm::status_t synchronize(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r,
                                         boost::dynamic_bitset<> const & sync_ref_clocks);

/*!
 \brief Unbounded spread
*/
extern tchecker::integer_t const UNBOUNDED_SPREAD;

/*!
 \brief Bound the spread between reference clocks
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \param spread : expected spread between reference clocks
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is consistent (checked by assertion)
 rdbm is tight (checked by assertion)
 \post rdbm is unchanged if spread is tchecker::refdbm::UNBOUNDED_SPREAD.
 rdbm has been restricted to its subset of valuations such that the spread
 between all reference clocks is bounded by spread otherwise, if not empty.
 if rdbm is empty, then its difference bound in (0,0) is less-than <=0
 (tchecker::refdbm::is_empty_0() returns true)
 \return tchecker::dbm::EMPTY is the spread-bounded dbm is empty,
 tchecker::dbm::NON_EMPTY otherwise
 */
enum tchecker::dbm::status_t bound_spread(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r,
                                          tchecker::integer_t spread);

/*!
 \brief Bound the spread between reference clocks
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \param spread : expected spread between reference clocks
 \param ref_clocks : reference clocks to bound
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is consistent (checked by assertion)
 rdbm is tight (checked by assertion)
 the size of ref_clocks is the number of reference clocks in r (checked by
 assertion)
 \post rdbm is unchanged if spread is tchecker::refdbm::UNBOUNDED_SPREAD.
 rdbm has been restricted to its subset of valuations such that the spread
 between all reference clocks in ref_clocks is bounded by spread otherwise, if
 not empty.
 if rdbm is empty, then its difference bound in (0,0) is less-than <=0
 (tchecker::refdbm::is_empty_0() returns true)
 \return tchecker::dbm::EMPTY is the spread-bounded dbm is empty,
 tchecker::dbm::NON_EMPTY otherwise
 */
enum tchecker::dbm::status_t bound_spread(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r,
                                          tchecker::integer_t spread, boost::dynamic_bitset<> const & ref_clocks);

/*!
 \brief Reset a clock to its reference clock
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \param x : clock identifier
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is consistent (checked by assertion)
 rdbm is tight (checked by assertion)
 x < r.size() (checked by assertion)
 \post clock x has been updated to value r.refmap()[x] in dbm. All other
 variables are unchanged
 rdbm is tight
 */
void reset_to_reference_clock(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r,
                              tchecker::clock_id_t x);

/*!
 \brief Reset a DBM with reference clocks w.r.t. a clock reset
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \param reset : clock reset
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is consistent (checked by assertion)
 rdbm is tight (checked by assertion)
 all clock IDs in reset are system clock IDs
 reset should be a reset to reference clock: the right id of reset is
 tchecker::REFCLOCK_ID, and the value in reset is 0 (checked by assertion)
 \post the left clock in reset is equal to its reference clock. All other
 variables are unchanged.
 rdbm is tight
 */
void reset(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r, tchecker::clock_reset_t const & reset);

/*!
 \brief Reset a DBM with reference clocks w.r.t. a clock reset
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \param rc : clock reset collection
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is consistent (checked by assertion)
 rdbm is tight (checked by assertion)
 all clock IDs in rc are system clock IDs
 all resets in rc must be resets to reference clock: the right id is
 tchecker::REFCLOCK_ID, and the value  is 0 (checked by assertion)
 \post the left clock in each reset in cr is equal to its reference clock. All
 other variables are unchanged.
 rdbm is tight
 */
void reset(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r,
           tchecker::clock_reset_container_t const & rc);

/*!
 \brief Asynchronous open-up (delay)
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is consistent (checked by assertion)
 rdbm is tight (checked by assertion)
 rdbm is a DBM over reference clocks r
 \post upper bounds on reference clocks and constraints relating reference
 clocks have been removed from rdbm (i.e. they have been set to (<,inf)).
 rdbm is tight and consistent.
 */
void asynchronous_open_up(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Asynchronous open-up (delay) with clock stopping
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \param delay_allowed : reference clocks allowed to delay
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is consistent (checked by assertion)
 rdbm is tight (checked by assertion)
 rdbm is a DBM over reference clocks r
 delay_allowed has size r.refcount() (checked by assertion)
 \post reference clocks in rdbm with delay_allowed are unbounded (i.e. x-t<inf
 for every reference clock t and any variable x, including x being another
 reference clock).
 reference clocks in rdbm without delay_allowed are unchanged
 rdbm is tight and consistent.
 */
void asynchronous_open_up(tchecker::dbm::db_t * rdbm, tchecker::reference_clock_variables_t const & r,
                          boost::dynamic_bitset<> const & delay_allowed);

/*!
 \brief Extract a standard DBM from a DBM with reference clocks
 \param rdbm : a DBM with reference clocks
 \param r : reference clocks for rdbm
 \param dbm : a DBM
 \param dim : dimension of dbm
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is an r.size()*r.size() array of difference bounds (checked by assertion)
 rdbm is not empty.
 rdbm is consistent (checked by assertion)
 rdbm is tight (checked by assertion)
 rdbm is synchronized (checked by assertion)
 rdbm is a DBM over reference clocks r
 dbm is not nullptr (checked by assertion)
 dbm is a dim*dim array of difference bounds
 dim is equal to r.size() - r.refcount() + 1 (checked by assertion)
 \post dbm is the zone extracted from rdbm by identifying the reference clocks
 in rdbm to the zero clock in dbm.
 dbm is tight and consistent.
 */
void to_dbm(tchecker::dbm::db_t const * rdbm, tchecker::reference_clock_variables_t const & r, tchecker::dbm::db_t * dbm,
            tchecker::clock_id_t dim);

/*!
 \brief Output a DBM with reference clocks as a matrix
 \param os : output stream
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is a DBM over reference clocks r
 \post rdbm has been output to os as a matrix
 \return os after output
 */
std::ostream & output_matrix(std::ostream & os, tchecker::dbm::db_t const * rdbm,
                             tchecker::reference_clock_variables_t const & r);

/*!
 \brief Output a DBM with reference clocks as a conjunction of constraints
 \param os : output stream
 \param rdbm : a DBM
 \param r : reference clocks for rdbm
 \pre rdbm is not nullptr (checked by assertion)
 rdbm is a r.size()*r.size() array of difference bounds
 rdbm is a DBM over reference clocks r
 \post the relevant constraints in rdbm have been output to os. Relevant
 constraints are those that differ from the universal DBM.
 \return os after output
 */
std::ostream & output(std::ostream & os, tchecker::dbm::db_t const * rdbm, tchecker::reference_clock_variables_t const & r);

/*!
 \brief Lexical ordering over DBMs with reference clocks
 \param rdbm1 : first DBM
 \param r1 : reference clocks for rdbm1
 \param rdbm2 : second DBM
 \param r2 : reference clocks for rdbm2
 \pre rdbm1 and rdbm2 are not nullptr (checked by assertion)
 rdbm1 is a r1.size()*r1.size() array of difference bounds
 rdbm2 is a r2.size()*r2.size() array of difference bounds
 rdbm1 is a DBM over reference clocks r1
 rdbm2 is a DBM over reference clocks r2
 \return 0 if rdbm1 and rdbm2 are equal, a negative value if rdbm1 is smaller
 than rdbm2 w.r.t. lexical ordering, and a positive value otherwise
 */
int lexical_cmp(tchecker::dbm::db_t const * rdbm1, tchecker::reference_clock_variables_t const & r1,
                tchecker::dbm::db_t const * rdbm2, tchecker::reference_clock_variables_t const & r2);

} // end of namespace refdbm

} // end of namespace tchecker

#endif // TCHECKER_REFDBM_HH


#pragma once

// deal.II includes ----------------------------------------
#include <deal.II/dofs/dof_accessor.h>
#include <deal.II/dofs/dof_handler.h>
#include <deal.II/dofs/dof_tools.h>
#include <deal.II/grid/tria.h>

// system  includes ----------------------------------------
#include <fstream>
#include <iostream>
#include <map>
#include <vector>

namespace boltzmann {

/**y
 * @brief Provides mapping between periodic and non periodic indexing on a
 *        given grid in the *physical* domain.
 *
 * @param dh
 */
class DoFMapperPeriodic
{
 private:
  typedef unsigned int index_t;
  typedef std::set<index_t> index_set_t;
  typedef std::map<index_t, index_t> map_t;

 public:
  void init(const dealii::DoFHandler<2>& dh);

  /**
   * @brief full grid -> periodic enumeration
   *
   * @param unrestricted_idx (full grid id)
   *
   * @return restricted grid id
   */
  index_t operator[](const index_t unrestricted_idx) const;

  /**
   * @brief restriced -> full
   *
   * @param restricted_idx
   *
   * @return full_idx
   */
  index_t lookup(const index_t restricted_idx) const;

  const std::vector<index_t>& get_mapping() const { return mapping; }

  unsigned int size() const { return size_; }

  const map_t& dof_map() const { return dof_map_; }

 private:
  unsigned int size_;
  /// full -> periodic renumbering
  std::vector<index_t> mapping;
  ///
  std::vector<index_t> inverse;
  /// maps boundary DoFs to their master DoF
  /// no entry means this DoF is original
  map_t dof_map_;
};

// ------------------------------------------------------------
/**
 * @brief periodic -> non-periodic
 *
 * @param restricted_idx
 *
 * @return
 */
DoFMapperPeriodic::index_t
DoFMapperPeriodic::lookup(const index_t restricted_idx) const
{
  return this->inverse[restricted_idx];
}

// ------------------------------------------------------------
/**
 * @brief go from non-periodic dof-idx to periodic indexing
 *
 * @param unrestricted_idx  non-periodic dof-idx
 *
 * @return
 */
DoFMapperPeriodic::index_t DoFMapperPeriodic::operator[](const index_t unrestricted_idx) const
{
  return mapping[unrestricted_idx];
}

// ------------------------------------------------------------
void
DoFMapperPeriodic::init(const dealii::DoFHandler<2>& dh)
{
  typedef std::map<index_t, double> map_t;
  map_t dof_locations_y;
  map_t dof_locations_x;

  for (dealii::DoFHandler<2>::active_cell_iterator cell = dh.begin_active(); cell != dh.end();
       ++cell) {
    auto collect_vertices = [&](map_t& dof_locations, const int face_id, const int c_id) {
      if (cell->at_boundary() && cell->face(face_id)->at_boundary()) {
        dof_locations[cell->face(face_id)->vertex_dof_index(0, 0)] =
            cell->face(face_id)->vertex(0)[c_id];
        dof_locations[cell->face(face_id)->vertex_dof_index(1, 0)] =
            cell->face(face_id)->vertex(1)[c_id];
      }
    };
    collect_vertices(dof_locations_y, 1, 1);
    collect_vertices(dof_locations_x, 3, 0);
  }

  typedef std::pair<index_t, index_t> index_pair_t;
  typedef std::set<index_pair_t> index_pair_set_t;

  index_pair_set_t edges;
  // contains all dof indices located on the boundary
  std::set<index_t> indices;
  // build edges
  for (dealii::DoFHandler<2>::active_cell_iterator cell = dh.begin_active(); cell != dh.end();
       ++cell) {
    // left and right boundary
    int face_id = 0;
    // coordinate id
    int c_id = 1;
    if (cell->at_boundary() && cell->face(face_id)->at_boundary()) {
      for (index_t face_vertex = 0; face_vertex < 2; ++face_vertex) {
        map_t::const_iterator p = dof_locations_y.begin();
        for (; p != dof_locations_y.end(); ++p) {
          if (std::fabs(p->second - cell->face(face_id)->vertex(face_vertex)[c_id]) < 1e-8) {
            const index_t ix1 = cell->face(face_id)->vertex_dof_index(face_vertex, 0);
            const index_t ix2 = p->first;
            if (ix1 < ix2) {
              edges.insert(std::make_pair(ix1, ix2));
              indices.insert(ix1);
            } else {
              edges.insert(std::make_pair(ix2, ix1));
              indices.insert(ix2);
            }
            break;
          }
        }
        if (p == dof_locations_y.end()) {
          std::cerr << "No corresponding degree of freedom was found!"
                    << "At coordinate y = " << p->second << std::endl;
          exit(-1);
        }
        // Assert( p != dof_locations_y.end(),
        //         dealii::ExcMessage( "No corresponding degree of freedom was found!"));
      }
    }
    face_id = 2;
    c_id = 0;
    if (cell->at_boundary() && cell->face(face_id)->at_boundary()) {
      for (index_t face_vertex = 0; face_vertex < 2; ++face_vertex) {
        map_t::const_iterator p = dof_locations_x.begin();
        for (; p != dof_locations_x.end(); ++p) {
          if (std::fabs(p->second - cell->face(face_id)->vertex(face_vertex)[c_id]) < 1e-8) {
            const index_t ix1 = cell->face(face_id)->vertex_dof_index(face_vertex, 0);
            const index_t ix2 = p->first;
            if (ix1 < ix2) {
              edges.insert(std::make_pair(ix1, ix2));
              indices.insert(ix1);
            } else {
              edges.insert(std::make_pair(ix2, ix1));
              indices.insert(ix2);
            }
            break;
          }
        }
        if (p == dof_locations_x.end()) {
          std::cerr << "No corresponding degree of freedom was found!"
                    << "At coordinate x = " << p->second << std::endl;
          exit(-1);
        }
        // Assert( p != dof_locations_x.end(),
        //         dealii::ExcMessage( "No corresponding degree of freedom was found!"));
      }
    }
  }
  // build cluster
  typedef std::set<index_t> index_set_t;
  std::function<void(index_t, index_set_t & neighbors)> find_clones = [&](index_t ix,
                                                                          index_set_t& clones) {
    index_pair_set_t::iterator it = edges.begin();
    while (true) {
      // finished?
      if (!(it != edges.end())) break;
      if (it->first == ix) {
        // add this edge to
        index_t next = it->second;
        edges.erase(it++);
        // recursion
        find_clones(next, clones);
        clones.insert(next);
        // remove this edge from the set and go to the next element
      } else {
        ++it;
      }
    }
  };

  std::map<index_t, index_set_t> clones_map;
  // iterate over boundary vertices and cluster them
  for (index_t ix : indices) {
    index_set_t& clones = clones_map[ix];
    // recursively traverse the graph add to
    // neighbors and delete the edges after they have been visited
    find_clones(ix, clones);
    // create a map
    for (index_t i : clones) {
      dof_map_[i] = ix;
    }
  }

  // mapping for periodic boundary condition has been constructed,
  // now renumber the dofs
  struct DOF
  {
    // helper class
    DOF(const index_t ix_)
        : ix(ix_)
    {
    }
    index_t ix;
  };

  std::nullptr_t nullp;

  typedef std::shared_ptr<DOF> ptr_DOF;
  std::vector<ptr_DOF> mapping_ptr(dh.n_dofs(), NULL);

  unsigned int index_counter = 0;
  for (unsigned int i = 0; i < dh.n_dofs(); ++i) {
    // slave node? (one that is remapped to another?)
    auto it = dof_map_.find(i);
    if (it != dof_map_.end()) {
      index_t master = it->second;
      // does it point to another DOF which is already initialized?
      if (mapping_ptr[master] != nullp) {
        mapping_ptr[i] = mapping_ptr[master];
      } else {
        // create this dof
        mapping_ptr[master] = ptr_DOF(new DOF(index_counter++));
      }
    } else {
      // it is a master node
      // initialize it if required
      if (mapping_ptr[i] == nullp) {
        mapping_ptr[i] = ptr_DOF(new DOF(index_counter++));
      }
    }
  }
  mapping.resize(dh.n_dofs());
  // store renumber into mapping
  for (unsigned int i = 0; i < dh.n_dofs(); ++i) {
    mapping[i] = mapping_ptr[i]->ix;
  }

  this->size_ = index_counter;

  // inverse mapping
  inverse.resize(this->size_);
  for (unsigned int i = 0; i < dh.n_dofs(); ++i) {
    inverse[mapping[i]] = i;
  }
}
} // end namespace boltzmann