/*
 * Project: MoleCuilder
 * Description: creates and alters molecular systems
 * Copyright (C)  2013 Frederik Heber. All rights reserved.
 * Please see the LICENSE file or "Copyright notice" in builder.cpp for details.
 *
 *
 *   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 .
 */
/*
 * PartialNucleiChargeFitter.cpp
 *
 *  Created on: 12.05.2013
 *      Author: heber
 */
// include config.h
#ifdef HAVE_CONFIG_H
#include 
#endif
#include "CodePatterns/MemDebug.hpp"
#include "PartialNucleiChargeFitter.hpp"
#include 
#include 
#include 
#include 
#include "LinearAlgebra/MatrixContent.hpp"
#include "LinearAlgebra/VectorContent.hpp"
#include "CodePatterns/Assert.hpp"
#include "CodePatterns/Log.hpp"
#include "Fragmentation/Summation/SetValues/SamplingGrid.hpp"
PartialNucleiChargeFitter::dimensions_t
PartialNucleiChargeFitter::getGridDimensions(const SamplingGrid &grid) const
{
  // convert sampled potential into a vector
  const double round_offset =
      (std::numeric_limits::round_style == std::round_toward_zero) ?
          0.5 : 0.; // need offset to get to round_toward_nearest behavior
  dimensions_t total(3,0);
  for(size_t index=0;index<3;++index) {
    const double delta = grid.getDeltaPerAxis(index);
    // delta is conversion factor from box length to discrete length, i.e. number of points
    total[index] = (grid.end[index] - grid.begin[index])/delta+round_offset;
  }
  return total;
}
PartialNucleiChargeFitter::PartialNucleiChargeFitter(
    const SamplingGrid &grid,
    const positions_t &_positions,
    const double _threshold) :
      total(getGridDimensions(grid)),
      SampledPotential(std::accumulate(total.begin(), total.end(), 1, std::multiplies())),
      grid_properties(static_cast(grid)),
      positions(_positions),
      PotentialFromCharges(NULL),
      PartialCharges(NULL),
      threshold(_threshold)
{
  // we must take care of the "window", i.e. there may be less entries in sampled_grid
  // vector as we would expect from size of grid ((2^level)^3) as 0-entries have been
  // omitted.
  size_t pre_offset[3];
  size_t post_offset[3];
  size_t length[3];
  size_t total[3];
  grid.getDiscreteWindowIndices(
      grid.begin, grid.end,
      grid.begin_window, grid.end_window,
      pre_offset,
      post_offset,
      length,
      total
      );
  const size_t calculated_size = length[0]*length[1]*length[2];
  ASSERT( calculated_size == grid.sampled_grid.size(),
      "PartialNucleiChargeFitter::PartialNucleiChargeFitter() - grid does not match size indicated by its window.");
  const double potential_sum = std::accumulate(grid.sampled_grid.begin(), grid.sampled_grid.end(), 0.);
  if ( fabs(potential_sum) > std::numeric_limits::epsilon()*1e4 ) {
    ELOG(2, "Potential sum is not less then " 
        << std::numeric_limits::epsilon()*1e4 << " but " 
        << potential_sum << ".");
  }
  SamplingGrid::sampledvalues_t::const_iterator griditer = grid.sampled_grid.begin();
  size_t index=0;
  size_t N[3];
  Vector grid_position; // position of grid point in real domain
  size_t masked_points = 0;
  // store step length per axis
  double delta[3];
  for (size_t i=0;i<3;++i)
    delta[i] = grid_properties.getDeltaPerAxis(i);
  /// convert sampled potential into a vector
  grid_position[0] = grid_properties.begin[0];
  for(N[0]=0; N[0] < pre_offset[0]; ++N[0]) {
    grid_position[1] = grid_properties.begin[1];
    for(N[1]=0; N[1] < total[1]; ++N[1]) {
      grid_position[2] = grid_properties.begin[2];
      for(N[2]=0; N[2] < total[2]; ++N[2]) {
        const_cast(SampledPotential)[index++] = 0.;
        grid_position[2] += delta[2];
      }
      grid_position[1] += delta[1];
    }
    grid_position[0] += delta[0];
  }
  for(N[0]=0; N[0] < length[0]; ++N[0]) {
    grid_position[1] = grid_properties.begin[1];
    for(N[1]=0; N[1] < pre_offset[1]; ++N[1]) {
      grid_position[2] = grid_properties.begin[2];
      for(N[2]=0; N[2] < total[2]; ++N[2]) {
        const_cast(SampledPotential)[index++] = 0.;
        grid_position[2] += delta[2];
      }
      grid_position[1] += delta[1];
    }
    for(N[1]=0; N[1] < length[1]; ++N[1]) {
      grid_position[2] = grid_properties.begin[2];
      for(N[2]=0; N[2] < pre_offset[2]; ++N[2]) {
        const_cast(SampledPotential)[index++] = 0.;
        grid_position[2] += delta[2];
      }
      for(N[2]=0; N[2] < length[2]; ++N[2]) {
        if (isGridPointSettable(positions, grid_position))
          const_cast(SampledPotential)[index++] = *griditer++;
        else {
          // skip point
          ++griditer;
          ++masked_points;
          const_cast(SampledPotential)[index++] = 0.;
        }
        grid_position[2] += delta[2];
      }
      for(N[2]=0; N[2] < post_offset[2]; ++N[2]) {
        const_cast(SampledPotential)[index++] = 0.;
        grid_position[2] += delta[2];
      }
      grid_position[1] += delta[1];
    }
    for(N[1]=0; N[1] < post_offset[1]; ++N[1]) {
      grid_position[2] = grid_properties.begin[2];
      for(N[2]=0; N[2] < total[2]; ++N[2]) {
        const_cast(SampledPotential)[index++] = 0.;
        grid_position[2] += delta[2];
      }
      grid_position[1] += delta[1];
    }
    grid_position[0] += delta[0];
  }
  for(N[0]=0; N[0] < post_offset[0]; ++N[0]) {
    grid_position[1] = grid_properties.begin[1];
    for(N[1]=0; N[1] < total[1]; ++N[1]) {
      grid_position[2] = grid_properties.begin[2];
      for(N[2]=0; N[2] < total[2]; ++N[2]) {
        const_cast(SampledPotential)[index++] = 0.;
        grid_position[2] += delta[2];
      }
      grid_position[1] += delta[1];
    }
    grid_position[0] += delta[0];
  }
  // set remainder of points to zero
  ASSERT( index == SampledPotential.getDimension(),
      "PartialNucleiChargeFitter::PartialNucleiChargeFitter() - not enough or more than calculated sample points.");
#ifndef NDEBUG
  // write vector as paraview csv file file
  {
    size_t N[3];
    size_t index = 0;
    std::ofstream paraview_output("solution.csv");
    paraview_output << "x coord,y coord,z coord,scalar" << std::endl;
    for(N[0]=0; N[0] < total[0]; ++N[0]) {
      for(N[1]=0; N[1] < total[1]; ++N[1]) {
        for(N[2]=0; N[2] < total[2]; ++N[2]) {
          paraview_output
              << (double)N[0]/(double)total[0] << ","
              << (double)N[1]/(double)total[1] << ","
              << (double)N[2]/(double)total[2] << ","
              << SampledPotential.at(index++) << std::endl;
        }
      }
    }
    paraview_output.close();
  }
#endif
  LOG(1, "INFO: I masked " << masked_points << " points in right-hand-side.");
//  LOG(4, "DEBUG: Right-hand side vector is " << SampledPotential << ".");
}
bool PartialNucleiChargeFitter::isGridPointSettable(
    const positions_t &_positions,
    const Vector &grid_position) const
{
  bool status = true;
  for (positions_t::const_iterator iter = _positions.begin();
      iter != _positions.end(); ++iter) {
    status &= grid_position.DistanceSquared(*iter) > threshold*threshold;
  }
  return status;
}
PartialNucleiChargeFitter::~PartialNucleiChargeFitter()
{
  delete PotentialFromCharges;
}
void PartialNucleiChargeFitter::constructMatrix()
{
  const size_t rows = SampledPotential.getDimension();
  const size_t cols = positions.size();
  PotentialFromCharges = new MatrixContent( rows, cols );
  // store step length per axis
  double delta[3];
  for (size_t i=0;i<3;++i)
    delta[i] = grid_properties.getDeltaPerAxis(i);
  // then for each charge ...
  size_t masked_points = 0;
  for (size_t nuclei_index = 0; nuclei_index < cols; ++nuclei_index) {
    // ... calculate potential at each grid position,
    // i.e. step through grid and calculate distance to charge position
    Vector grid_position; // position of grid point in real domain
    grid_position[0] = grid_properties.begin[0];
    size_t N[3];      // discrete grid position
    size_t index = 0; // component of column vector
    for(N[0]=0; N[0] < total[0]; ++N[0]) {
      grid_position[1] = grid_properties.begin[1];
      for(N[1]=0; N[1] < total[1]; ++N[1]) {
        grid_position[2] = grid_properties.begin[2];
        for(N[2]=0; N[2] < total[2]; ++N[2]) {
          if (isGridPointSettable(positions, grid_position)) {
            const double distance = positions[nuclei_index].distance(grid_position);
            ASSERT( distance >= 0,
                "PartialNucleiChargeFitter::constructMatrix() - distance is negative?");
            // Coulomb's constant is 1 in atomic units, see http://en.wikipedia.org/wiki/Atomic_units
            const double epsilon0_au = 1.; //4.*M_PI*0.007957747154594767;
            // ... with epsilon_0 in atom units from http://folk.uio.no/michalj/node72.html
            const double value = 1./(epsilon0_au*distance);
            PotentialFromCharges->at(index++, nuclei_index) = value;
          } else {
            ++masked_points;
            PotentialFromCharges->at(index++, nuclei_index) = 0.;
          }
          grid_position[2] += delta[2];
        }
        grid_position[1] += delta[1];
      }
      grid_position[0] += delta[0];
    }
    ASSERT( index == PotentialFromCharges->getRows(),
        "PartialNucleiChargeFitter::operator() - number of sampled positions "
        +toString(index)+" unequal to set rows "
        +toString(PotentialFromCharges->getRows())+".");
  }
  LOG(1, "INFO: I masked " << masked_points/cols << " points in matrix.");
}
double PartialNucleiChargeFitter::operator()()
{
  // prepare PartialCharges
  PartialCharges = new VectorContent(positions.size());
  // set up over-determined system's problem matrix A for Ax=b
  // i.e. columns represent potential of a single charge at grid positions
  constructMatrix();
  // solve for x
  *PartialCharges =
      PotentialFromCharges->solveOverdeterminedLinearEquation(
          SampledPotential);
//  LOG(4, "DEBUG: Solution vector is " << (*PotentialFromCharges) * (*PartialCharges) << ".");
  // calculate residual vector
  VectorContent residuum = (*PotentialFromCharges) * (*PartialCharges) - SampledPotential;
#ifndef NDEBUG
  // write solution to file
  writeMatrix();
  // write vector as paraview csv file file
  {
    size_t N[3];
    size_t index = 0;
    std::ofstream paraview_output("residuum.csv");
    paraview_output << "x coord,y coord,z coord,scalar" << std::endl;
    for(N[0]=0; N[0] < total[0]; ++N[0]) {
      for(N[1]=0; N[1] < total[1]; ++N[1]) {
        for(N[2]=0; N[2] < total[2]; ++N[2]) {
          paraview_output
              << (double)N[0]/(double)total[0] << ","
              << (double)N[1]/(double)total[1] << ","
              << (double)N[2]/(double)total[2] << ","
              << residuum.at(index++) << std::endl;
        }
      }
    }
    paraview_output.close();
  }
#endif
  // calculate L1 and L2 errors
  double residuum_l1 = 0.;
  for (size_t i=0; i< residuum.getDimension(); ++i)
    if (residuum_l1 < residuum[i])
      residuum_l1 = residuum[i];
  LOG(1, "INFO: L2-Norm of residuum is " << residuum.Norm() << ".");
  LOG(1, "INFO: L1-Norm of residuum is " << residuum_l1 << ".");
  return residuum.Norm();
}
void PartialNucleiChargeFitter::writeMatrix()
{
  constructMatrix();
  // write matrix as paraview csv file file
    size_t N[3];
    size_t index=0;
    std::string filename = std::string("potential.csv");
    std::ofstream paraview_output(filename.c_str());
    paraview_output << "x coord,y coord,z coord,scalar" << std::endl;
    for(N[0]=0; N[0] < total[0]; ++N[0]) {
      for(N[1]=0; N[1] < total[1]; ++N[1]) {
        for(N[2]=0; N[2] < total[2]; ++N[2]) {
          double sum = 0.;
          for (size_t nuclei_index = 0; nuclei_index < positions.size(); ++nuclei_index) {
            sum+= PotentialFromCharges->at(index, nuclei_index)*PartialCharges->at(nuclei_index);
          }
          paraview_output
              << (double)N[0]/(double)total[0] << ","
              << (double)N[1]/(double)total[1] << ","
              << (double)N[2]/(double)total[2] << ","
              << sum << std::endl;
          index++;
        }
      }
    }
    paraview_output.close();
}
PartialNucleiChargeFitter::charges_t
PartialNucleiChargeFitter::getSolutionAsCharges_t() const
{
  ASSERT( PartialCharges != NULL,
      "PartialNucleiChargeFitter::getSolutionAsCharges_t() - PartialCharges requested prior to calculation.");
  charges_t return_charges(positions.size(), 0.);
  for (size_t i = 0; i < return_charges.size(); ++i)
    return_charges[i] = PartialCharges->at(i);
  return return_charges;
}