/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2014 Frederik Heber. All rights reserved.
 *
 *
 *   This file is part of MoleCuilder.
 *
 *    MoleCuilder is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    MoleCuilder 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
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with MoleCuilder.  If not, see .
 */
/*
 * SaturationDistanceMaximizer.cpp
 *
 *  Created on: Jul 27, 2014
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
#include "CodePatterns/MemDebug.hpp"
#include "SaturationDistanceMaximizer.hpp"
#include 
#include 
#include 
#include "CodePatterns/Log.hpp"
double
func(const gsl_vector *x, void *adata)
{
  // get the object whose functions we call
  SaturationDistanceMaximizer::Advocate *maximizer =
      static_cast(adata);
  // set alphas
  maximizer->setAlphas(x);
  // calculate function value and return
  return maximizer->calculatePenality();
}
void
jacf(const gsl_vector *x, void *adata, gsl_vector *g)
{
  // get the object whose functions we call
  SaturationDistanceMaximizer::Advocate *maximizer =
      static_cast(adata);
  // set alphas
  maximizer->setAlphas(x);
  // calculate function gradient and return
  std::vector gradient = maximizer->calculatePenalityGradient();
  for (unsigned int i=0;i(adata);
  // set alphas
  maximizer->setAlphas(x);
  // calculate function value and return
  *f = maximizer->calculatePenality();
  std::vector gradient = maximizer->calculatePenalityGradient();
  for (unsigned int i=0;i SaturationDistanceMaximizer::getAlphas() const
{
  std::vector alphas;
  PositionContainers_t::iterator containeriter = PositionContainers.begin();
  for (unsigned int i=0; ialpha );
  return alphas;
}
void SaturationDistanceMaximizer::setAlphas(const gsl_vector *x)
{
  PositionContainers_t::iterator containeriter = PositionContainers.begin();
  for (unsigned int i=0; ialpha = gsl_vector_get(x,i);
}
void SaturationDistanceMaximizer::operator()()
{
  // some control constants
  const double tolerance = 1e-6;
  const unsigned int MAXITERATIONS = 100;
  const gsl_multimin_fdfminimizer_type *T;
  gsl_multimin_fdfminimizer *s;
  gsl_vector *x;
  gsl_multimin_function_fdf my_func;
  const unsigned int N = PositionContainers.size();
  my_func.n = N;
  my_func.f = &func;
  my_func.df = &jacf;
  my_func.fdf = &funcjacf;
  SaturationDistanceMaximizer::Advocate* const advocate = getAdvocate();
  my_func.params = advocate;
  // allocate argument and set to zero
  x = gsl_vector_alloc(N);
  for (unsigned int i=0;igradient, tolerance);
  } while ((status = GSL_CONTINUE) && (iter < MAXITERATIONS));
  // set to solution
  setAlphas(s->x);
  // print solution
  if (DoLog(4)) {
    std::stringstream sstream;
    sstream << "DEBUG: Minimal alphas are ";
    for (unsigned int i=0;ix,i) << ((i!= N-1) ? "," : "");
    LOG(4, sstream.str());
  }
  // free memory
  gsl_multimin_fdfminimizer_free(s);
  my_func.params = NULL;
  delete advocate;
  gsl_vector_free(x);
}
SaturationDistanceMaximizer::Advocate* SaturationDistanceMaximizer::getAdvocate()
{
  return new Advocate(*this);
}
SaturationDistanceMaximizer::position_bins_t
SaturationDistanceMaximizer::getAllPositionBins() const
{
  position_bins_t position_bins;
  position_bins.reserve(PositionContainers.size());
  for (PositionContainers_t::const_iterator containeriter = PositionContainers.begin();
      containeriter != PositionContainers.end(); ++containeriter)
    position_bins.push_back( (*containeriter)->getPositions() );
  return position_bins;
}
double SaturationDistanceMaximizer::calculatePenality() const
{
  double penalty = 0.;
  LOG(6, "DEBUG: Current alphas are " << getAlphas());
  // gather all positions
  position_bins_t position_bins = getAllPositionBins();
  // go through both bins (but with ibegin();
          firstpositioniter != firstbiniter->end(); ++firstpositioniter) {
        for (SaturatedBond::positions_t::const_iterator secondpositioniter = secondbiniter->begin();
            secondpositioniter != secondbiniter->end(); ++secondpositioniter) {
          // Both iters are from different bins, can never be the same.
          // We do not penalize over positions from same bin as their positions
          // are fixed.
          // We penalize by one over the squared distance
          penalty += 1./(firstpositioniter->DistanceSquared(*secondpositioniter));
        }
      }
    }
  }
  LOG(4, "DEBUG: Penalty is " << penalty);
  return penalty;
}
#ifdef HAVE_INLINE
inline
#else
static
#endif
size_t calculateHydrogenNo(
    const SaturatedBond::positions_t::const_iterator &_start,
    const SaturatedBond::positions_t::const_iterator &_current)
{
  const size_t HydrogenNo = std::distance(_start, _current);
  ASSERT( (HydrogenNo >= 0) && (HydrogenNo <= 2),
      "calculatePenalityGradient() - hydrogen no not in [0,2].");
  return HydrogenNo;
}
std::vector SaturationDistanceMaximizer::calculatePenalityGradient() const
{
  // gather all positions
  const position_bins_t position_bins = getAllPositionBins();
  LOG(6, "DEBUG: Current alphas are " << getAlphas());
  std::vector gradient(position_bins.size(), 0.);
  std::vector::iterator biniter = gradient.begin();
  PositionContainers_t::const_iterator bonditer = PositionContainers.begin();
  position_bins_t::const_iterator firstbiniter = position_bins.begin();
  // go through each bond/gradient component/alpha
  for(; biniter != gradient.end(); ++biniter, ++bonditer, ++firstbiniter) {
    LOG(5, "DEBUG: Current bond is " << **bonditer << ", current bin is #"
        << std::distance(gradient.begin(), biniter) << ", set of positions are "
        << *firstbiniter);
    // skip bin if it belongs to a degree-1 bond (no alpha dependency here)
    if ((*bonditer)->saturated_bond.getDegree() == 1) {
      LOG(6, "DEBUG: Skipping due to degree 1.");
      continue;
    }
    // in the bin go through each position
    for (SaturatedBond::positions_t::const_iterator firstpositioniter = firstbiniter->begin();
        firstpositioniter != firstbiniter->end(); ++firstpositioniter) {
      LOG(6, "DEBUG: Current position is " << *firstpositioniter);
      // count the hydrogen we are looking at: Each is placed at a different position!
      const size_t HydrogenNo =
          calculateHydrogenNo(firstbiniter->begin(), firstpositioniter);
      const double alpha = (*bonditer)->alpha
          + (double)HydrogenNo * 2.*M_PI/(double)(*bonditer)->saturated_bond.getDegree();
      LOG(6, "DEBUG: HydrogenNo is " << HydrogenNo << ", alpha is " << alpha);
      // and go through each other bin
      for (position_bins_t::const_iterator secondbiniter = position_bins.begin();
          secondbiniter != position_bins.end(); ++secondbiniter) {
        // distance between hydrogens in same bin is not affected by the angle
//        if (firstbiniter == secondbiniter)
//          continue;
        // in the other bin go through each position
        for (SaturatedBond::positions_t::const_iterator secondpositioniter = secondbiniter->begin();
            secondpositioniter != secondbiniter->end(); ++secondpositioniter) {
          if (firstpositioniter == secondpositioniter) {
            LOG(7, "DEBUG: Skipping due to same positions.");
            continue;
          }
          LOG(7, "DEBUG: Second position is " << *secondpositioniter);
          // iters are from different bins, can never be the same
          const Vector distance = *firstpositioniter - *secondpositioniter;
          const double temp = -2./pow(distance.NormSquared(), 2);
          const Vector tempVector =
              (-sin(alpha)*(*bonditer)->vector_a)
              +(cos(alpha)*(*bonditer)->vector_b);
          const double result = temp * (distance.ScalarProduct(tempVector));
          *biniter += 2.*result; //for x_i and x_j
          LOG(7, "DEBUG: Total is " << result << ", temp is " << temp << ", tempVector is " << tempVector
              << ", and bondVector is " << distance << ": bin = " << *biniter);
        }
      }
    }
  }
  LOG(4, "DEBUG: Gradient of penalty is " << gradient);
  return gradient;
}