// Copyright (C) 2011-2012 by the BEM++ Authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "modified_helmholtz_3d_calderon_projector.hpp"
#include "../common/shared_ptr.hpp"

#include "modified_helmholtz_3d_single_layer_boundary_operator.hpp"
#include "modified_helmholtz_3d_double_layer_boundary_operator.hpp"
#include "modified_helmholtz_3d_hypersingular_boundary_operator.hpp"
#include "identity_operator.hpp"
#include "blocked_operator_structure.hpp"
#include "../fiber/explicit_instantiation.hpp"

#include <boost/make_shared.hpp>

namespace Bempp {

template <typename BasisFunctionType, typename KernelType, typename ResultType>
BlockedBoundaryOperator<BasisFunctionType, ResultType>
modifiedHelmholtz3dExteriorCalderonProjector(
    const shared_ptr<const Context<BasisFunctionType, ResultType>> &context,
    const shared_ptr<const Space<BasisFunctionType>> &hminusSpace,
    const shared_ptr<const Space<BasisFunctionType>> &hplusSpace,
    KernelType waveNumber, const std::string &label, bool useInterpolation,
    int interpPtsPerWavelength) {

  typedef BoundaryOperator<BasisFunctionType, ResultType> BdOp;

  shared_ptr<const Space<BasisFunctionType>> internalHplusSpace =
      hplusSpace->discontinuousSpace(hplusSpace);

  BdOp internalSlp = modifiedHelmholtz3dSingleLayerBoundaryOperator(
      context, internalHplusSpace, internalHplusSpace, internalHplusSpace,
      waveNumber, label + "_slp", SYMMETRIC, useInterpolation,
      interpPtsPerWavelength);

  BdOp dlp = modifiedHelmholtz3dDoubleLayerBoundaryOperator(
      context, hplusSpace, hplusSpace, hminusSpace, waveNumber, label + "_dlp",
      NO_SYMMETRY, useInterpolation, interpPtsPerWavelength);

  BdOp adjDlp = adjoint(dlp);

  BdOp hyp = modifiedHelmholtz3dHypersingularBoundaryOperator(
      context, hplusSpace, hminusSpace, hplusSpace, waveNumber, label + "_hyp",
      SYMMETRIC, useInterpolation, interpPtsPerWavelength, internalSlp);

  BdOp idSpaceTransformation1 = identityOperator(
      context, hminusSpace, internalHplusSpace, internalHplusSpace);
  BdOp idSpaceTransformation2 =
      identityOperator(context, internalHplusSpace, hplusSpace, hminusSpace);
  BdOp idDouble =
      identityOperator(context, hplusSpace, hplusSpace, hminusSpace);
  BdOp idAdjDouble =
      identityOperator(context, hminusSpace, hminusSpace, hplusSpace);

  // Now Assemble the entries of the Calderon Projector

  BlockedOperatorStructure<BasisFunctionType, ResultType> structure;

  structure.setBlock(0, 0, .5 * idDouble + dlp);
  structure.setBlock(0, 1, -1. * idSpaceTransformation2 * internalSlp *
                               idSpaceTransformation1);
  structure.setBlock(1, 0, -1. * hyp);
  structure.setBlock(1, 1, .5 * idAdjDouble - adjDlp);

  return BlockedBoundaryOperator<BasisFunctionType, ResultType>(structure);
}

template <typename BasisFunctionType, typename KernelType, typename ResultType>
BlockedBoundaryOperator<BasisFunctionType, ResultType>
modifiedHelmholtz3dInteriorCalderonProjector(
    const shared_ptr<const Context<BasisFunctionType, ResultType>> &context,
    const shared_ptr<const Space<BasisFunctionType>> &hminusSpace,
    const shared_ptr<const Space<BasisFunctionType>> &hplusSpace,
    KernelType waveNumber, const std::string &label, bool useInterpolation,
    int interpPtsPerWavelength) {

  typedef BoundaryOperator<BasisFunctionType, ResultType> BdOp;

  shared_ptr<const Space<BasisFunctionType>> internalHplusSpace =
      hplusSpace->discontinuousSpace(hplusSpace);

  BdOp internalSlp = modifiedHelmholtz3dSingleLayerBoundaryOperator(
      context, internalHplusSpace, internalHplusSpace, internalHplusSpace,
      waveNumber, label + "_slp", SYMMETRIC, useInterpolation,
      interpPtsPerWavelength);

  BdOp dlp = modifiedHelmholtz3dDoubleLayerBoundaryOperator(
      context, hplusSpace, hplusSpace, hminusSpace, waveNumber, label + "_dlp",
      NO_SYMMETRY, useInterpolation, interpPtsPerWavelength);

  BdOp adjDlp = adjoint(dlp);

  BdOp hyp = modifiedHelmholtz3dHypersingularBoundaryOperator(
      context, hplusSpace, hminusSpace, hplusSpace, waveNumber, label + "_hyp",
      SYMMETRIC, useInterpolation, interpPtsPerWavelength, internalSlp);

  BdOp idSpaceTransformation1 = identityOperator(
      context, hminusSpace, internalHplusSpace, internalHplusSpace);
  BdOp idSpaceTransformation2 =
      identityOperator(context, internalHplusSpace, hplusSpace, hminusSpace);
  BdOp idDouble =
      identityOperator(context, hplusSpace, hplusSpace, hminusSpace);
  BdOp idAdjDouble =
      identityOperator(context, hminusSpace, hminusSpace, hplusSpace);

  // Now Assemble the entries of the Calderon Projector

  BlockedOperatorStructure<BasisFunctionType, ResultType> structure;

  structure.setBlock(0, 0, .5 * idDouble - dlp);
  structure.setBlock(0, 1, idSpaceTransformation2 * internalSlp *
                               idSpaceTransformation1);
  structure.setBlock(1, 0, hyp);
  structure.setBlock(1, 1, .5 * idAdjDouble + adjDlp);

  return BlockedBoundaryOperator<BasisFunctionType, ResultType>(structure);
}

#define INSTANTIATE_NONMEMBER_CONSTRUCTOR(BASIS, KERNEL, RESULT)               \
  template BlockedBoundaryOperator<BASIS, RESULT>                              \
  modifiedHelmholtz3dExteriorCalderonProjector(                                \
      const shared_ptr<const Context<BASIS, RESULT>> &,                        \
      const shared_ptr<const Space<BASIS>> &,                                  \
      const shared_ptr<const Space<BASIS>> &, KERNEL, const std::string &,     \
      bool, int);                                                              \
  template BlockedBoundaryOperator<BASIS, RESULT>                              \
  modifiedHelmholtz3dInteriorCalderonProjector(                                \
      const shared_ptr<const Context<BASIS, RESULT>> &,                        \
      const shared_ptr<const Space<BASIS>> &,                                  \
      const shared_ptr<const Space<BASIS>> &, KERNEL, const std::string &,     \
      bool, int)

FIBER_ITERATE_OVER_BASIS_KERNEL_AND_RESULT_TYPES(
    INSTANTIATE_NONMEMBER_CONSTRUCTOR);

} // Bempp


/*==========================================================
 * tCGGPsumrnd.cpp
 *
 * The calling syntax is:
 *
 *		S_w = tCGGPsumrnd(p, logalpha, sigma, tau, a, b, gamma, T)
 *		S_w = tCGGPsumrnd(p, logalpha, sigma, tau, a, b, gamma, T, seed)
 *
 * Compilation command (from MATLAB console)
 * -----------------------------------------
 * Using gcc (Linux, Mac):
 * - using a C++11 compiler (gcc>=4.3):
 *
 *      mex tCGGPsumrnd.cpp CXXFLAGS='$CXXFLAGS -std=c++0x -fopenmp' LDFLAGS='$LDFLAGS -fopenmp'
 *
 * - using Boost installed in a standard path:
 *
 *      mex tCGGPsumrnd.cpp CXXFLAGS='$CXXFLAGS -fopenmp' LDFLAGS='$LDFLAGS -fopenmp'
 *
 * - using custom Boost path:
 *
 *      mex tCGGPsumrnd.cpp -Ipath/to/boost CXXFLAGS='$CXXFLAGS -fopenmp' LDFLAGS='$LDFLAGS -fopenmp'
 *
 * Using Microsoft Visual C++ (Windows) and Boost:
 *
 *      mex tCGGPsumrnd.cpp -Ipath/to/boost COMPFLAGS="$COMPFLAGS /openmp" LINKFLAGS="$LINKFLAGS /openmp"
 *
 * e.g: mex tCGGPsumrnd.cpp -I"C:/Program Files/boost/boost_1_58_0" COMPFLAGS="$COMPFLAGS /openmp" LINKFLAGS="$LINKFLAGS /openmp"
 *
 *========================================================*/

#include "mex.h"
#include <cmath>
#include <ctime>
#include <limits>
#include <omp.h>

typedef double double_;

#if __cplusplus != 199711L /* C++11 std */

#include <random>
typedef std::default_random_engine generator;
typedef std::uniform_real_distribution<double_> uniform_dist;
typedef std::gamma_distribution<double_> gamma_dist;

#else /* Boost */

#include <boost/math/special_functions/gamma.hpp>
using boost::math::lgamma;

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real_distribution.hpp>
#include <boost/random/gamma_distribution.hpp>
typedef boost::random::mt19937 generator;
typedef boost::random::uniform_real_distribution<double_> uniform_dist;
typedef boost::random::gamma_distribution<double_> gamma_dist;

#endif

void tCGGPsumrnd( const unsigned p,  const double logalpha, const double sigma,
        const double tau, const double * a, const double * b, const double * gamma,
        double_ T, const double_ Tmax, const unsigned seed,
        double_ * S_w );

void tCGGPsumrnd_tau0( const unsigned p,  const double logalpha, const double sigma,
        const double * a, const double * b, const double * gamma,
        double_ T, const double_ Tmax, const unsigned seed,
        double_ * S_w );

/* The gateway function */
void mexFunction( int nlhs, mxArray *plhs[],
                  int nrhs, const mxArray *prhs[] )
{
    /* check for proper number of arguments */
    if(nrhs<8) {
        mexErrMsgTxt("At least 8 inputs required.");
    }
    if(nlhs!=1) {
        mexErrMsgTxt("One output required.");
    }

    /* get the value of the input  */
    const unsigned p = mxGetScalar( prhs[0] );
    const double logalpha = mxGetScalar( prhs[1] );
    const double sigma = mxGetScalar( prhs[2] );
    const double tau = mxGetScalar( prhs[3] );
    double * a = mxGetPr( prhs[4] );
    const unsigned na = mxGetNumberOfElements( prhs[4] );
    double * b = mxGetPr( prhs[5] );
    const unsigned nb = mxGetNumberOfElements( prhs[5] );
    double * gamma = mxGetPr( prhs[6] );
    const unsigned ng = mxGetNumberOfElements( prhs[6] );
    const double T = mxGetScalar( prhs[7] );
    double seed = time(NULL);
    if ( nrhs >= 9 )
        seed = mxGetScalar( prhs[8] );
    // number of concurent jobs
    unsigned nthreads = omp_get_max_threads();
    if ( nrhs >= 10 )
        nthreads = unsigned(mxGetScalar( prhs[9] ));

    if ( na < p && na==1 ) {
        // duplicate value of a
        double a_val = *a;
        a = new double[p];
        for ( unsigned k = 0; k < p; ++k )
            a[k] = a_val;
    }
    if ( nb < p && nb==1 ) {
        // duplicate value of b
        double b_val = *a;
        b = new double[p];
        for ( unsigned k = 0; k < p; ++k )
            b[k] = b_val;
    }
    if ( ng < p && ng==1 ) {
        // duplicate value of gamma
        double g_val = *a;
        gamma = new double[p];
        for ( unsigned k = 0; k < p; ++k )
            gamma[k] = g_val;
    }

    // expected log number of jumps
    const double_ cst = logalpha - lgamma( 1.0 - sigma ) - log(sigma);
    const double_ log_njumps = cst - sigma * log(T);

    /* cut into nthreads intervals [Tcut(i), Tcut(i+1)] such that the expected
     * number of jumps is njumps/nthreads in each interval */
    double_ * Tcut = new double_[nthreads+1];
    Tcut[0] = T;
    for (unsigned i = 1; i < nthreads; ++i) {
        // TODO: find a better bound of the expected number of jumps
        Tcut[i] = pow( pow(Tcut[i-1], -sigma) - exp( log_njumps - log(double(nthreads)) - cst ), -1.0 / sigma);
    }
    Tcut[nthreads] = std::numeric_limits<double_>::infinity();

    double_ ** S_w = new double_*[nthreads];
    for (unsigned i = 0; i < nthreads; ++i)
        S_w[i] = new double_[p];

    /* call the computational routine */
    if ( tau < 1e-8 ) {
        // case tau==0
        #pragma omp parallel for
        for (int i = 0; i < nthreads; ++i) {
            tCGGPsumrnd_tau0( p, logalpha, sigma, a, b, gamma, Tcut[i], Tcut[i+1], unsigned(seed)+i,
                S_w[i] );
        }
    } else {
        // case tau>0
        #pragma omp parallel for
        for (int i = 0; i < nthreads; ++i) {
            tCGGPsumrnd( p, logalpha, sigma, tau, a, b, gamma, Tcut[i], Tcut[i+1], unsigned(seed)+i,
                S_w[i] );
        }
    }

    /* reduction */
    for (unsigned i = 1; i < nthreads; ++i) {
        for (unsigned k = 0; k < p; ++k)
            S_w[0][k] += S_w[i][k];
    }

    /* create the output */
    plhs[0] = mxCreateDoubleMatrix( 1, p, mxREAL );
    for (unsigned k = 0; k < p; ++k)
        mxGetPr( plhs[0] )[k] = S_w[0][k];

    /* deallocate memory */
    delete[] Tcut;
    for (unsigned i = 0; i < nthreads; ++i)
        delete[] S_w[i];
    delete[] S_w;
    if ( na < p && na==1 )
        delete[] a;
    if ( nb < p && nb==1 )
        delete[] b;
    if ( ng < p && ng==1 )
        delete[] gamma;
}

/* The computational routine */

void tCGGPsumrnd( const unsigned p,  const double logalpha, const double sigma,
        const double tau, const double * a, const double * b, const double * gamma,
        double_ T, const double_ Tmax, const unsigned seed,
        double_ * S_w )
{
    const double_ sigmap1 = sigma + 1.0;
    const double_ tauinv = 1.0 / tau;
    const double_ log_cst = logalpha - lgamma( 1.0 - sigma ) - log(tau);
    double_ * gamma_b = new double_[p];
    for ( unsigned k = 0; k < p; ++k )
        gamma_b[k] = gamma[k]/b[k];
    double_ log_r;
    double_ log_mgf;
    double_ t_new;
    double_ log_mgf_new;
    double_ log_G;
    generator gen(seed);
    uniform_dist unif( 0.0, 1.0 );
    gamma_dist gam;

    for ( unsigned k = 0; k < p; ++k )
        S_w[k] = 0.0;

    while (true) {
        log_r = log( -log( unif(gen) ) );
        log_mgf = 0.0;
        for ( unsigned k = 0; k < p; ++k )
            log_mgf -= a[k] * log1p( T * gamma_b[k] );
        log_G = log_mgf + log_cst - sigmap1 * log(T) - tau * T;
        if ( log_r > log_G )
            break;
        t_new = T - tauinv * log1p( -exp( log_r - log_G ) );
        if ( t_new > Tmax )
            break;
        log_mgf_new = 0.0;
        for ( unsigned k = 0; k < p; ++k )
            log_mgf_new -= a[k] * log1p( t_new * gamma_b[k] );
        if ( log( unif(gen) ) < sigmap1 * ( log(T) - log(t_new) ) + log_mgf_new - log_mgf ) {
            for ( unsigned k = 0; k < p; ++k ) {
                gam = gamma_dist( a[k], 1 / ( b[k] + t_new * gamma[k] ) );
                S_w[k] += exp( log(t_new) + log( gam(gen) ) );
                //gam = gamma_dist( a[k], 1 / ( b[k] / t_new + gamma[k] ) );
                //S_w[k] += gam(gen);
            }
        }
        T = t_new;
    }

    delete[] gamma_b;
}

void tCGGPsumrnd_tau0( const unsigned p,  const double logalpha, const double sigma,
        const double * a, const double * b, const double * gamma,
        double_ T, const double_ Tmax, const unsigned seed,
        double_ * S_w )
{
    const double_ log_cst = logalpha - lgamma( 1.0 - sigma ) - log(sigma);
    const double_ msigma = -sigma;
    const double_ msigmainv = 1.0 / msigma;
    double_ * gamma_b = new double_[p];
    for ( unsigned k = 0; k < p; ++k )
        gamma_b[k] = gamma[k]/b[k];
    double_ log_r;
    double_ log_mgf;
    double_ t_new;
    double_ log_mgf_new;
    double_ log_temp;
    double_ log_tmsigma;
    generator gen(seed);
    uniform_dist unif( 0.0, 1.0 );
    gamma_dist gam;

    for ( unsigned k = 0; k < p; ++k )
        S_w[k] = 0.0;

    while (true) {
        log_r = log( -log( unif(gen) ) );
        log_mgf = 0.0;
        for ( unsigned k = 0; k < p; ++k )
            log_mgf -= a[k] * log1p( T * gamma_b[k] );
        log_temp = log_r - log_mgf - log_cst;
        log_tmsigma = msigma * log(T);
        if ( log_temp > log_tmsigma )
            break;
        t_new = pow( exp(log_tmsigma) - exp(log_temp) , msigmainv );
        if ( t_new > Tmax )
            break;
        log_mgf_new = 0.0;
        for ( unsigned k = 0; k < p; ++k )
            log_mgf_new -= a[k] * log1p( t_new * gamma_b[k] );
        if ( log( unif(gen) ) < log_mgf_new - log_mgf ) {
            for ( unsigned k = 0; k < p; ++k ) {
                gam = gamma_dist( a[k], 1 / ( b[k] / t_new + gamma[k] ) );
                S_w[k] += gam(gen);
            }
        }
        T = t_new;
    }

    delete[] gamma_b;
}


/* Copyright (C) 2017 IBM Corp.
 *  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.
 */
/////////////////////////////
// sampleG - Sampling pre-image z such that G*z = v, G is the "easy" matrix
//////////////////////////////////////////

//#include <mpfr.h>
#include <NTL/mat_lzz_p.h>
#include "utils/timing.h"

NTL_CLIENT

#include "mat_l.h"
#include "stash.h"
#include "TDMatrix.h"

static zz_p
updateSyndromValue(vec_l &xOut, const Vec<vec_zz_p>& syndrom,
		   TDMatrixParams* params, long lFactor, long row,long offset);

//#define DEBUG

// Sample from a discrete Gaussian until you get a sample point equal
// to u modulo f. Uses a stack to put points that are not equal to u.
static long sampleMod(long u, long f,
                      Gaussian1Dsampler& sampler, SampleStash& stash)
{
  FHE_TIMER_START;

  long umodf = u%f;
  if (umodf<0) umodf += f;

  long val = (long)stash.getPoint(umodf);
  if (val != umodf+1) // found in stash
    return val;

  // If not found in stash, sample until you get it
  while (true) {
    NTL::Pair<bool,long> extra(false,0);
    val = sampler.getSample(/*mu=*/0.0, extra);
    long valmodf = val%f;
    if (valmodf<0) valmodf += f;

    if ( valmodf == umodf ) // found
      return val;
    else                    // not found, keep point for later
      stash.setPoint(valmodf, (int)val);
  }
}

// SampleG: Sample vector xOut such that G * xOut = syndrom. syndrom is given
// in CRT representation but x is a vector of longs.

// Generate a vector of size params>n*kFactors*e
// For each row in G, choose kFactors*e elements of xOut

// The function updates the syndrom vector, based on all previously
// chosen values in xOut. We update syndrom[lFactor][row]
// only just before we need to use it (as opposed to updating
// it for every new value of xOut as it is chosen)

// based on the updated syndrome, we sample a new entry in xOut,
// equal to uVal mod factor and append it to xOut

int sampleG(vec_l &xOut,
            const Vec<vec_zz_p>& syndrom, TDMatrixParams* params)
{
  FHE_TIMER_START;
  xOut.SetLength(0);
  xOut.SetMaxLength(params->m); // allocate space

  long sigma = params->r * params->maxFactor;
  Gaussian1Dsampler sampler(sigma);

  zz_pPush push; // backup the NTL current modulus

  // For each row in G, choose kFactors*e elements of xOut
  for (long row = 0; row < params->n; row++)
    {
      long offset = row * (params->kFactors) * (params->e);

      for (long lFactor = 0; lFactor < params->kFactors; lFactor++)
        {
	  long factor = params->factors[lFactor];
	  params->zzp_context[lFactor].restore(); // NTL-modulus := factor

	  // lazy update of the syndrom vector, based on all previously
	  // chosen values in xOut. We update syndrom[lFactor][row]
	  // only just before we need to use it (as opposed to updating
	  // it for every new value of xOut as it is chosen).

	  zz_p zzNewVal = updateSyndromValue(xOut, syndrom,params, lFactor, row, offset);

	  long uVal = conv<long>(zzNewVal);

	  // Done updating the uSyndrom, now choose next e elements in xOut

	  for (long lPower = 0; lPower < params->e; lPower++)
            {
	      // sample a new entry in xOut, equal to uVal mod factor
	      long samp = sampleMod(uVal, factor, sampler, params->stash[lFactor]);
	      assert( 0 == ((uVal-samp)%factor) ); // sanity check

	      xOut.append(samp);

	      // update uVal := (uVal-samp)/factor (over the integers)
	      uVal -= samp;
	      uVal /= factor;
            }
        }
    }
  return 0;
}

// lazy update of the syndrom vector, based on all previously chosen values
// in xOut. We update syndrom[lFactor][row] just before we need to use it
// (as opposed to updating it for every new value of xOut as it is chosen).

static zz_p
updateSyndromValue(vec_l &xOut, const Vec<vec_zz_p>& syndrom,
		   TDMatrixParams* params, long lFactor, long row, long offset)
{
    zz_p zzNewVal = syndrom[lFactor][row];
#ifdef DEBUG
    cout << zzNewVal << endl;
#endif
    long xIndex=offset;
    for (long vFactor = 0; vFactor < lFactor; vFactor++)
        for (long ie=0; ie < params->e; ie++) // repeat e times
        {
            long xVal = xOut[xIndex++];     // xIndex = offset+ie
            zzNewVal -= xVal;
            zzNewVal *= params->fInv[vFactor][lFactor];
            // update via u := (u - xval)/pi (mod pj)
#ifdef DEBUG
            cout << zzNewVal << endl;
#endif
        }
    return zzNewVal;
}


// Copyright 2013-2020 Lawrence Livermore National Security, LLC and other
// HavoqGT Project Developers. See the top-level LICENSE file for details.
//
// SPDX-License-Identifier: MIT

#ifndef HAVOQGT_MPI_PAGE_RANK_HPP_INCLUDED
#define HAVOQGT_MPI_PAGE_RANK_HPP_INCLUDED

#include <havoqgt/visitor_queue.hpp>
#include <boost/container/deque.hpp>
#include <vector>

namespace havoqgt {

template <typename Visitor>
class pr_queue
{

protected:
  std::vector< Visitor > m_data;
public:
  pr_queue() { }

  bool push(Visitor const & task)
  {
    m_data.push_back(task);
    return true;
  }

  void pop()
  {
    m_data.pop_back();
  }

  Visitor const & top() //const
  {
    return m_data.back();
  }

  size_t size() const
  {
    return m_data.size();;
  }

  bool empty() const
  {
    return m_data.empty();
  }

  void clear()
  {
    m_data.clear();
  }
};

template<typename Graph>
class pr_visitor {
public:
  typedef typename Graph::vertex_locator                 vertex_locator;
  pr_visitor(): rank(std::numeric_limits<double>::min())  { }

  pr_visitor(vertex_locator _vertex, double _rank)
    : vertex(_vertex)
    , rank(_rank) { }

  pr_visitor(vertex_locator _vertex)
    : vertex(_vertex)
    , rank(std::numeric_limits<double>::min()) { }

  template<typename AlgData>
  bool pre_visit(AlgData& alg_data) const {
    if(rank == std::numeric_limits<double>::min()) {
      HAVOQGT_ERROR_MSG("This is a damn logic error!");
      return true;
    }
    std::get<1>(alg_data)[vertex] += rank; //change to next_rank
    return false;
  }

  template<typename VisitorQueueHandle, typename AlgData>
  bool init_visit(Graph& g, VisitorQueueHandle vis_queue, AlgData& alg_data) const {
    return visit(g, vis_queue, alg_data);
  }

  template<typename VisitorQueueHandle, typename AlgData>
  bool visit(Graph& g, VisitorQueueHandle vis_queue, AlgData& alg_data) const {
    //change to cur_rank
    double old_rank = std::get<0>(alg_data)[vertex];
    uint64_t degree = g.degree(vertex);
    double send_rank = old_rank / double(degree);

    typedef typename Graph::edge_iterator eitr_type;
    for(eitr_type eitr = g.edges_begin(vertex); eitr != g.edges_end(vertex); ++eitr) {
      vertex_locator neighbor = eitr.target();
      pr_visitor new_visitor( neighbor, send_rank);
      vis_queue->queue_visitor(new_visitor);
    }
    return true;
  }

  friend inline bool operator>(const pr_visitor& v1, const pr_visitor& v2) {
    return false;
  }

  friend inline bool operator<(const pr_visitor& v1, const pr_visitor& v2) {
    return false;
  }

  vertex_locator   vertex;
  double           rank;
};

template <typename TGraph, typename PRData>
void page_rank(TGraph& g, PRData& cur_rank, PRData& next_rank, bool initial) {
  typedef  pr_visitor<TGraph>    visitor_type;
  auto alg_data = std::forward_as_tuple(cur_rank, next_rank);

  if(initial) {
    cur_rank.reset(double(1)/double(g.max_global_vertex_id()));
  }

  auto vq = create_visitor_queue<visitor_type, detail::visitor_priority_queue>(&g, alg_data);
  vq.init_visitor_traversal();
  next_rank.all_reduce();
}

} //end namespace havoqgt

#endif //HAVOQGT_MPI_PAGE_RANK_HPP_INCLUDED


/*
 * Copyright (c) 2009 Carnegie Mellon University.
 *     All rights reserved.
 *
 *  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.
 *
 * For more about this software visit:
 *
 *      http://www.graphlab.ml.cmu.edu
 *
 */

/**
 * \file cgs_lda.cpp
 *
 * \brief This file contains a GraphLab based implementation of the
 * Collapsed Gibbs Sampler (CGS) for the Latent Dirichlet Allocation
 * (LDA) model.
 *
 *
 *
 * \author Joseph Gonzalez, Diana Hu
 */

#include <vector>
#include <algorithm>

#include <graphlab/ui/mongoose/mongoose.h>
#include <boost/math/special_functions/gamma.hpp>
#include <vector>
#include <algorithm>
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
#include <graphlab/parallel/atomic.hpp>

// Global Types
// ============================================================================
typedef long count_type;

/**
 * \brief The factor type is used to store the counts of tokens in
 * each topic for words, documents, and assignments.
 *
 * Atomic counts are used because we violate the abstraction by
 * modifying adjacent vertex data on scatter.  As a consequence
 * multiple threads on the same machine may try to update the same
 * vertex data at the same time.  The graphlab::atomic type ensures
 * that multiple increments are serially consistent.
 */
typedef std::vector< graphlab::atomic<count_type> > factor_type;

/**
 * \brief We use the factor type in accumulators and so we define an
 * operator+=
 */
inline factor_type& operator+=(factor_type& lvalue,
                               const factor_type& rvalue) {
  if(!rvalue.empty()) {
    if(lvalue.empty()) lvalue = rvalue;
    else {
      for(size_t t = 0; t < lvalue.size(); ++t) lvalue[t] += rvalue[t];
    }
  }
  return lvalue;
} // end of operator +=

// We include the rest of GraphLab after we define the operator+= for
// vector.
#include <graphlab.hpp>
#include <graphlab/macros_def.hpp>

/**
 * \brief The latent topic id of a token is the smallest reasonable
 * type.
 */
typedef uint16_t topic_id_type;

// We require a null topic to represent the topic assignment for
// tokens that have not yet been assigned.
#define NULL_TOPIC (topic_id_type(-1))

/**
 * \brief The assignment type is used on each edge to store the
 * assignments of each token.  There can be several occurrences of the
 * same word in a given document and so a vector is used to store the
 * assignments of each occurrence.
 */
typedef std::vector< topic_id_type > assignment_type;

// Global Variables
// ============================================================================

/**
 * \brief The alpha parameter determines the sparsity of topics for
 * each document.
 */
double ALPHA = 1;

/**
 * \brief the Beta parameter determines the sparsity of words in each
 * document.
 */
double BETA = 0.1;

/**
 * \brief the total number of topics to uses
 */
size_t NTOPICS = 50;

/**
 * \brief The total number of words in the dataset.
 */
size_t NWORDS = 0;

/**
 * \brief The total number of docs in the dataset.
 */
size_t NDOCS = 0;

/**
 * \brief The total number of tokens in the corpus
 */
size_t NTOKENS = 0;

/**
 * \brief The number of top words to display during execution (from
 * each topic).
 */
size_t TOPK = 5;

/**
 * \brief The interval to display topics during execution.
 */
size_t INTERVAL = 10;

/**
 * \brief The interval to compute & display the likelihood
 */
size_t LIK_INTERVAL = 5;

/**
 * \brief The global variable storing the global topic count across
 * all machines.  This is maintained periodically using aggregation.
 */
factor_type GLOBAL_TOPIC_COUNT;

/**
 * \brief A dictionary of words used to print the top words during
 * execution.
 */
std::vector<std::string> DICTIONARY;

/**
 * \brief The maximum occurences allowed for an individual term-doc
 * pair. (edge data)
 */
size_t MAX_COUNT = 100;

/**
 * \brief The time to run until the first sample is taken.  If less
 * than zero then the sampler will run indefinitely.
 */
float BURNIN = -1;

/**
 * \brief The json top word struct contains the current set of top
 * words for each topic encoded in the form of a json string.
 */
struct top_words_type {
  graphlab::mutex lock;
  std::string json_string;
  top_words_type() :
    json_string("{\n" + json_header_string() + "\tvalues: [] \n }") { }
  inline std::string json_header_string() const {
    return
      "\t\"ntopics\": " + graphlab::tostr(NTOPICS) + ",\n" +
      "\t\"nwords\":  " + graphlab::tostr(NWORDS) + ",\n" +
      "\t\"ndocs\":   " + graphlab::tostr(NDOCS) + ",\n" +
      "\t\"ntokens\": " + graphlab::tostr(NTOKENS) + ",\n" +
      "\t\"alpha\":   " + graphlab::tostr(ALPHA) + ",\n" +
      "\t\"beta\":    " + graphlab::tostr(BETA) + ",\n";
  } // end of json header string
} TOP_WORDS;

/**
 * \brief This method is called by the web interface to construct and
 * return the word clouds.
 */
std::pair<std::string, std::string>
word_cloud_callback(std::map<std::string, std::string>& varmap) {
  TOP_WORDS.lock.lock();
  const std::pair<std::string, std::string>
    pair("text/html",TOP_WORDS.json_string);
  TOP_WORDS.lock.unlock();
  return pair;
}

/**
 * \brief Create a token changes event tracker which is reported in
 * the GraphLab metrics dashboard.
 */
DECLARE_EVENT(TOKEN_CHANGES);

// Graph Types
// ============================================================================

/**
 * \brief The vertex data represents each term and document in the
 * corpus and contains the counts of tokens in each topic.
 */
struct vertex_data {
  ///! The total number of updates
  uint32_t nupdates;
  ///! The total number of changes to adjacent tokens
  uint32_t nchanges;
  ///! The count of tokens in each topic
  factor_type factor;
  vertex_data() : nupdates(0), nchanges(0), factor(NTOPICS) { }
  void save(graphlab::oarchive& arc) const {
    arc << nupdates << nchanges << factor;
  }
  void load(graphlab::iarchive& arc) {
    arc >> nupdates >> nchanges >> factor;
  }
}; // end of vertex_data

/**
 * \brief The edge data represents the individual tokens (word,doc)
 * pairs and their assignment to topics.
 */
struct edge_data {
  ///! The number of changes on the last update
  uint16_t nchanges;
  ///! The assignment of all tokens
  assignment_type assignment;
  edge_data(size_t ntokens = 0) : nchanges(0), assignment(ntokens, NULL_TOPIC) { }
  void save(graphlab::oarchive& arc) const { arc << nchanges << assignment; }
  void load(graphlab::iarchive& arc) { arc >> nchanges >> assignment; }
}; // end of edge_data

/**
 * \brief The LDA graph is a bipartite graph with docs connected to
 * terms if the term occurs in the document.
 *
 * The edges store the number of occurrences of the term in the
 * document as a vector of the assignments of that term in that
 * document to topics.
 *
 * The vertices store the total topic counts.
 */
typedef graphlab::distributed_graph<vertex_data, edge_data> graph_type;

/**
 * \brief Edge data parser used in graph.load_json
 *
 * Make sure that the edge file list
 * has docids from -2 to -(total #docid) and wordids 0 to (total #words -1)
 */
bool eparser(edge_data& ed, const std::string& line){
  const int BASE = 10;
  char* next_char_ptr = NULL;
  size_t count = strtoul(line.c_str(), &next_char_ptr, BASE);
  if(next_char_ptr ==NULL) return false;

  //threshold count
  count = std::min(count, MAX_COUNT);
  ed = (edge_data(count));
  return true;
}

/**
 * \brief Vertex data parser used in graph.load_json
 */
bool vparser(vertex_data& vd, const std::string& line){
  vd = vertex_data();
  return true;
}

/**
 * \brief The graph loader is used by graph.load to parse lines of the
 * text data file.
 *
 * The global variable MAX_COUNT limits the number of tokens that can
 * be constructed on a particular edge.
 *
 * We use the relativley fast boost::spirit parser to parse each line.
 */
bool graph_loader(graph_type& graph, const std::string& fname,
                  const std::string& line) {
  ASSERT_FALSE(line.empty());
  namespace qi = boost::spirit::qi;
  namespace ascii = boost::spirit::ascii;
  namespace phoenix = boost::phoenix;

  graphlab::vertex_id_type doc_id(-1), word_id(-1);
  size_t count = 0;
  const bool success = qi::phrase_parse
    (line.begin(), line.end(),
     //  Begin grammar
     (
      qi::ulong_[phoenix::ref(doc_id) = qi::_1] >> -qi::char_(',') >>
      qi::ulong_[phoenix::ref(word_id) = qi::_1] >> -qi::char_(',') >>
      qi::ulong_[phoenix::ref(count) = qi::_1]
      )
     ,
     //  End grammar
     ascii::space);
  if(!success) return false;
  // Threshold the count
  count = std::min(count, MAX_COUNT);
  // since this is a bipartite graph I need a method to number the
  // left and right vertices differently.  To accomplish I make sure
  // all vertices have non-zero ids and then negate the right vertex.
  // Unfortunatley graphlab reserves -1 and so we add 2 and negate.
  doc_id += 2;
  ASSERT_GT(doc_id, 1);
  doc_id = -doc_id;
  ASSERT_NE(doc_id, word_id);
  // Create an edge and add it to the graph
  graph.add_edge(doc_id, word_id, edge_data(count));
  return true; // successful load
}; // end of graph loader

/**
 * \brief Determine if the given vertex is a word vertex or a doc
 * vertex.
 *
 * For simplicity we connect docs --> words and therefore if a vertex
 * has in edges then it is a word.
 */
inline bool is_word(const graph_type::vertex_type& vertex) {
  return vertex.num_in_edges() > 0 ? 1 : 0;
}

/**
 * \brief Determine if the given vertex is a doc vertex
 *
 * For simplicity we connect docs --> words and therefore if a vertex
 * has out edges then it is a doc
 */
inline bool is_doc(const graph_type::vertex_type& vertex) {
  return vertex.num_out_edges() > 0 ? 1 : 0;
}

/**
 * \brief return the number of tokens on a particular edge.
 */
inline size_t count_tokens(const graph_type::edge_type& edge) {
  return edge.data().assignment.size();
}

/**
 * \brief Get the other vertex in the edge.
 */
inline graph_type::vertex_type
get_other_vertex(const graph_type::edge_type& edge,
                 const graph_type::vertex_type& vertex) {
  return vertex.id() == edge.source().id()? edge.target() : edge.source();
}

// ========================================================
// The Collapsed Gibbs Sampler Function

/**
 * \brief The gather type for the collapsed Gibbs sampler is used to
 * collect the topic counts on adjacent edges so that the apply
 * function can compute the correct topic counts for the center
 * vertex.
 *
 */
struct gather_type {
  factor_type factor;
  uint32_t nchanges;
  gather_type() : nchanges(0) { };
  gather_type(uint32_t nchanges) : factor(NTOPICS), nchanges(nchanges) { };
  void save(graphlab::oarchive& arc) const { arc << factor << nchanges; }
  void load(graphlab::iarchive& arc) { arc >> factor >> nchanges; }
  gather_type& operator+=(const gather_type& other) {
    factor += other.factor;
    nchanges += other.nchanges;
    return *this;
  }
}; // end of gather type

/**
 * \brief The collapsed Gibbs sampler vertex program updates the topic
 * counts for the center vertex and then draws new topic assignments
 * for each edge durring the scatter phase.
 *
 */
class cgs_lda_vertex_program :
  public graphlab::ivertex_program<graph_type, gather_type>,
  public graphlab::IS_POD_TYPE {
public:

  /**
   * \brief At termination we want to disable sampling to allow the
   * correct final counts to be computed.
   */
  static bool DISABLE_SAMPLING;

  /** \brief gather on all edges */
  edge_dir_type gather_edges(icontext_type& context,
                             const vertex_type& vertex) const {
    return graphlab::ALL_EDGES;
  } // end of gather_edges

  /**
   * \brief Collect the current topic count on each edge.
   */
  gather_type gather(icontext_type& context, const vertex_type& vertex,
                     edge_type& edge) const {
    gather_type ret(edge.data().nchanges);
    const assignment_type& assignment = edge.data().assignment;
    foreach(topic_id_type asg, assignment) {
      if(asg != NULL_TOPIC) ++ret.factor[asg];
    }
    return ret;
  } // end of gather

  /**
   * \brief Update the topic count for the center vertex.  This
   * ensures that the center vertex has the correct topic count before
   * resampling the topics for each token along each edge.
   */
  void apply(icontext_type& context, vertex_type& vertex,
             const gather_type& sum) {
    const size_t num_neighbors = vertex.num_in_edges() + vertex.num_out_edges();
    ASSERT_GT(num_neighbors, 0);
    // There should be no new edge data since the vertex program has been cleared
    vertex_data& vdata = vertex.data();
    ASSERT_EQ(sum.factor.size(), NTOPICS);
    ASSERT_EQ(vdata.factor.size(), NTOPICS);
    vdata.nupdates++;
    vdata.nchanges = sum.nchanges;
    vdata.factor = sum.factor;
  } // end of apply

  /**
   * \brief Scatter on all edges if the computation is on-going.
   * Computation stops after bunrin or when disable sampling is set to
   * true.
   */
  edge_dir_type scatter_edges(icontext_type& context,
                              const vertex_type& vertex) const {
    return (DISABLE_SAMPLING || (BURNIN > 0 && context.elapsed_seconds() > BURNIN))?
      graphlab::NO_EDGES : graphlab::ALL_EDGES;
  }; // end of scatter edges

  /**
   * \brief Draw new topic assignments for each edge token.
   *
   * Note that we exploit the GraphLab caching model here by DIRECTLY
   * modifying the topic counts of adjacent vertices.  Making the
   * changes immediately visible to any adjacent vertex programs
   * running on the same machine.  However, these changes will be
   * overwritten during the apply step and are only used to accelerate
   * sampling.  This is a potentially dangerous violation of the
   * abstraction and should be taken with caution.  In our case all
   * vertex topic counts are preallocated and atomic operations are
   * used.  In addition during the sampling phase we must be careful
   * to guard against potentially negative temporary counts.
   */
  void scatter(icontext_type& context, const vertex_type& vertex,
               edge_type& edge) const {
    factor_type& doc_topic_count =  is_doc(edge.source()) ?
      edge.source().data().factor : edge.target().data().factor;
    factor_type& word_topic_count = is_word(edge.source()) ?
      edge.source().data().factor : edge.target().data().factor;
    ASSERT_EQ(doc_topic_count.size(), NTOPICS);
    ASSERT_EQ(word_topic_count.size(), NTOPICS);
    // run the actual gibbs sampling
    std::vector<double> prob(NTOPICS);
    assignment_type& assignment = edge.data().assignment;
    edge.data().nchanges = 0;
    foreach(topic_id_type& asg, assignment) {
      const topic_id_type old_asg = asg;
      if(asg != NULL_TOPIC) { // construct the cavity
        --doc_topic_count[asg];
        --word_topic_count[asg];
        --GLOBAL_TOPIC_COUNT[asg];
      }
      for(size_t t = 0; t < NTOPICS; ++t) {
        const double n_dt =
          std::max(count_type(doc_topic_count[t]), count_type(0));
        const double n_wt =
          std::max(count_type(word_topic_count[t]), count_type(0));
        const double n_t  =
          std::max(count_type(GLOBAL_TOPIC_COUNT[t]), count_type(0));
        prob[t] = (ALPHA + n_dt) * (BETA + n_wt) / (BETA * NWORDS + n_t);
      }
      asg = graphlab::random::multinomial(prob);
      // asg = std::max_element(prob.begin(), prob.end()) - prob.begin();
      ++doc_topic_count[asg];
      ++word_topic_count[asg];
      ++GLOBAL_TOPIC_COUNT[asg];
      if(asg != old_asg) {
        ++edge.data().nchanges;
        INCREMENT_EVENT(TOKEN_CHANGES,1);
      }
    } // End of loop over each token
    // singla the other vertex
    context.signal(get_other_vertex(edge, vertex));
  } // end of scatter function

}; // end of cgs_lda_vertex_program

bool cgs_lda_vertex_program::DISABLE_SAMPLING = false;

/**
 * \brief The icontext type associated with the cgs_lda_vertex program
 * is needed for all aggregators.
 */
typedef cgs_lda_vertex_program::icontext_type icontext_type;

// ========================================================
// Aggregators

/**
 * \brief The topk aggregator is used to periodically compute and
 * display the topk most common words in each topic.
 *
 * The number of words is determined by the global variable \ref TOPK
 * and the interval is determined by the global variable \ref INTERVAL.
 *
 */
class topk_aggregator {
  typedef std::pair<float, graphlab::vertex_id_type> cw_pair_type;
private:
  std::vector< std::set<cw_pair_type> > top_words;
  size_t nchanges, nupdates;
public:
  topk_aggregator(size_t nchanges = 0, size_t nupdates = 0) :
    nchanges(nchanges), nupdates(nupdates) { }

  void save(graphlab::oarchive& arc) const { arc << top_words << nchanges; }
  void load(graphlab::iarchive& arc) { arc >> top_words >> nchanges; }

  topk_aggregator& operator+=(const topk_aggregator& other) {
    nchanges += other.nchanges;
    nupdates += other.nupdates;
    if(other.top_words.empty()) return *this;
    if(top_words.empty()) top_words.resize(NTOPICS);
    for(size_t i = 0; i < top_words.size(); ++i) {
      // Merge the topk
      top_words[i].insert(other.top_words[i].begin(),
                          other.top_words[i].end());
      // Remove excess elements
      while(top_words[i].size() > TOPK)
        top_words[i].erase(top_words[i].begin());
    }
    return *this;
  } // end of operator +=

  static topk_aggregator map(icontext_type& context,
                             const graph_type::vertex_type& vertex) {
    topk_aggregator ret_value;
    const vertex_data& vdata = vertex.data();
    ret_value.nchanges = vdata.nchanges;
    ret_value.nupdates = vdata.nupdates;
    if(is_word(vertex)) {
      const graphlab::vertex_id_type wordid = vertex.id();
      ret_value.top_words.resize(vdata.factor.size());
      for(size_t i = 0; i < vdata.factor.size(); ++i) {
        const cw_pair_type pair(vdata.factor[i], wordid);
        ret_value.top_words[i].insert(pair);
      }
    }
    return ret_value;
  } // end of map function

  static void finalize(icontext_type& context,
                       const topk_aggregator& total) {
    if(context.procid() != 0) return;
    std::string json = "{\n"+ TOP_WORDS.json_header_string() +
      "\t\"values\": [\n";
    for(size_t i = 0; i < total.top_words.size(); ++i) {
      std::cout << "Topic " << i << ": ";
      json += "\t[\n";
      size_t counter = 0;
      rev_foreach(cw_pair_type pair, total.top_words[i])  {
      ASSERT_LT(pair.second, DICTIONARY.size());
        json += "\t\t[\"" + DICTIONARY[pair.second] + "\", " +
          graphlab::tostr(pair.first) + "]";
        if(++counter < total.top_words[i].size()) json += ", ";
        json += '\n';
        std::cout << DICTIONARY[pair.second]
                  << "(" << pair.first << ")" << ", ";
        // std::cout << DICTIONARY[pair.second] << ",  ";
      }
      json += "\t]";
      if(i+1 < total.top_words.size()) json += ", ";
      json += '\n';
      std::cout << std::endl;
    }
    json += "]}";
    // Post the change to the global variable
    TOP_WORDS.lock.lock();
    TOP_WORDS.json_string.swap(json);
    TOP_WORDS.lock.unlock();

    std::cout << "\nNumber of token changes: " << total.nchanges << std::endl;
    std::cout << "\nNumber of updates:       " << total.nupdates << std::endl;
  } // end of finalize
}; // end of topk_aggregator struct

/**
 * \brief The global counts aggregator computes the total number of
 * tokens in each topic across all words and documents and then
 * updates the \ref GLOBAL_TOPIC_COUNT variable.
 *
 */
struct global_counts_aggregator {
  typedef graph_type::vertex_type vertex_type;
  static factor_type map(icontext_type& context, const vertex_type& vertex) {
    return vertex.data().factor;
  } // end of map function

  static void finalize(icontext_type& context, const factor_type& total) {
    size_t sum = 0;
    for(size_t t = 0; t < total.size(); ++t) {
      GLOBAL_TOPIC_COUNT[t] =
        std::max(count_type(total[t]/2), count_type(0));
      sum += GLOBAL_TOPIC_COUNT[t];
    }
    context.cout() << "Total Tokens: " << sum << std::endl;
  } // end of finalize
}; // end of global_counts_aggregator struct

/**
 * Computing log_gamma can be a bit slow so this class precomptues
 * log gamma for a subset of values.
 */
class log_gamma {
  double offset;
  std::vector<double> values;
public:
  log_gamma(): offset(1.0) {}

  void init(const double& new_offset, const size_t& buckets) {
    using boost::math::lgamma;
    ASSERT_GT(offset, 0.0);
    values.resize(buckets);
    offset = new_offset;
    for(size_t i = 0; i < values.size(); ++i) {
      values[i] = lgamma(i + offset);
    }
  }

  double operator()(const count_type& index) const {
    using boost::math::lgamma;
    if(index < values.size() && index >= 0) { return values[index]; }
    else { return lgamma(index + offset); }
  }

};

log_gamma ALPHA_LGAMMA;
log_gamma BETA_LGAMMA;

/**
 * \brief The Likelihood aggregators maintains the current estimate of
 * the log-likelihood of the current token assignments.
 *
 *  llik_words_given_topics = ...
 *    ntopics * (gammaln(nwords * beta) - nwords * gammaln(beta)) - ...
 *    sum_t(gammaln( n_t + nwords * beta)) +
 *    sum_w(sum_t(gammaln(n_wt + beta)));
 *
 *  llik_topics = ...
 *    ndocs * (gammaln(ntopics * alpha) - ntopics * gammaln(alpha)) + ...
 *    sum_d(sum_t(gammaln(n_td + alpha)) - gammaln(sum_t(n_td) + ntopics * alpha));
 *
 * Latex formulation:
 *
    \mathcal{L}( w | z) & = T * \left( \log\Gamma(W * \beta) - W * \log\Gamma(\beta) \right) + \\
    & \sum_{t} \left( \left(\sum_{w} \log\Gamma(N_{wt} + \beta)\right) -
           \log\Gamma\left( W * \beta + \sum_{w} N_{wt}  \right) \right) \\
    & = T * \left( \log\Gamma(W * \beta) - W * \log\Gamma(\beta) \right) -
        \sum_{t} \log\Gamma\left( W * \beta + N_{t}  \right) + \\
    & \sum_{w} \sum_{t} \log\Gamma(N_{wt} + \beta)   \\
    \\
    \mathcal{L}(z) & = D * \left(\log\Gamma(T * \alpha) - T * \log\Gamma(\alpha) \right) + \\
    & \sum_{d} \left( \left(\sum_{t}\log\Gamma(N_{td} + \alpha)\right) -
        \log\Gamma\left( T * \alpha + \sum_{t} N_{td} \right) \right) \\
    \\
    \mathcal{L}(w,z) & = \mathcal{L}(w | z) + \mathcal{L}(z)
 *
 */
class likelihood_aggregator : public graphlab::IS_POD_TYPE {
  typedef graph_type::vertex_type vertex_type;
  double lik_words_given_topics;
  double lik_topics;
public:
  likelihood_aggregator() : lik_words_given_topics(0), lik_topics(0) { }

  likelihood_aggregator& operator+=(const likelihood_aggregator& other) {
    lik_words_given_topics += other.lik_words_given_topics;
    lik_topics += other.lik_topics;
    return *this;
  } // end of operator +=

  static likelihood_aggregator
  map(icontext_type& context, const vertex_type& vertex) {
    // using boost::math::lgamma;
    const factor_type& factor = vertex.data().factor;
    ASSERT_EQ(factor.size(), NTOPICS);
    likelihood_aggregator ret;
    if(is_word(vertex)) {
      for(size_t t = 0; t < NTOPICS; ++t) {
        const count_type value = std::max(count_type(factor[t]), count_type(0));
        //ret.lik_words_given_topics += lgamma(value + BETA);
        ret.lik_words_given_topics += BETA_LGAMMA(value);
      }
    } else {  ASSERT_TRUE(is_doc(vertex));
      double ntokens_in_doc = 0;
      for(size_t t = 0; t < NTOPICS; ++t) {
        const count_type value = std::max(count_type(factor[t]), count_type(0));
        //ret.lik_topics += lgamma(value + ALPHA);
        ret.lik_topics += ALPHA_LGAMMA(value);
        ntokens_in_doc += value;
      }
      ret.lik_topics -= lgamma(ntokens_in_doc + NTOPICS * ALPHA);
    }
    return ret;
  } // end of map function

  static void finalize(icontext_type& context, const likelihood_aggregator& total) {
    using boost::math::lgamma;
    // Address the global sum terms
    double denominator = 0;
    for(size_t t = 0; t < NTOPICS; ++t) {
      const count_type value =
        std::max(count_type(GLOBAL_TOPIC_COUNT[t]), count_type(0));
      denominator += lgamma(value + NWORDS * BETA);
    } // end of for loop

    const double lik_words_given_topics =
      NTOPICS * (lgamma(NWORDS * BETA) - NWORDS * lgamma(BETA)) -
      denominator + total.lik_words_given_topics;

    const double lik_topics =
      NDOCS * (lgamma(NTOPICS * ALPHA) - NTOPICS * lgamma(ALPHA)) +
      total.lik_topics;

    const double lik = lik_words_given_topics + lik_topics;
    context.cout() << "Likelihood: " << lik << std::endl;
  } // end of finalize
}; // end of likelihood_aggregator struct

/**
 * \brief The selective signal functions are used to signal only the
 * vertices corresponding to words or documents.  This is done by
 * using the iengine::map_reduce_vertices function.
 */
struct signal_only {
  /**
   * \brief Signal only the document vertices and skip the word
   * vertices.
   */
  static graphlab::empty
  docs(icontext_type& context, const graph_type::vertex_type& vertex) {
    if(is_doc(vertex)) context.signal(vertex);
    return graphlab::empty();
  } // end of signal_docs

 /**
  * \brief Signal only the word vertices and skip the document
  * vertices.
  */
  static graphlab::empty
  words(icontext_type& context, const graph_type::vertex_type& vertex) {
    if(is_word(vertex)) context.signal(vertex);
    return graphlab::empty();
  } // end of signal_words
}; // end of selective_only

/**
 * \brief This function is used to load and then initialize the data
 * graph (corpus) from a folder or file.
 *
 * The graph can be in either json form constructed using the graph
 * builder tools or in raw text form.  The raw text format contains a
 * token on each line of each file in the format:
 *
 \verbatim
 <docid> <wordid> <count>
          ...
 \endverbatim
 *
 * for example:
 \verbatim
    0    0     2
    0    4     1
    0    2     3
 \endverbatim
 *
 * implies that document zero contains word zero twice, word 4 once,
 * and word two three times.
 *
 * If a dictionary is used it is important that each word id
 * correspond to the index in the dictionary file (starting at zero).
 *
 * Once loaded the total number of words, documents, and tokens is
 * counted and saved to global variables which are read during the
 * execution of the sampler.
 *
 * \param [in] dc The distributed control object used to coordinate
 * between machines.
 *
 * \param [in,out] graph The graph object that is initialized.
 *
 * \param [in] corpus_dir The directory or file containing the graph
 * data.  The corpus directory can reside on hdfs in which case the
 * path should begin with "hdfs://namenode".  In addition the file(s)
 * may be gzipped and therefore must end in ".gz".
 *
 * \param [in] load_json Whether the graph data is in text format or
 * preprocessed json format using the graph builder tools.
 */
bool load_and_initialize_graph(graphlab::distributed_control& dc,
                               graph_type& graph,
                               const std::string& corpus_dir,
                               const std::string& format
			       ) {
  dc.cout() << "Loading graph." << std::endl;
  graphlab::timer timer; timer.start();

  if(format=="matrix"){
      dc.cout() << "matrix format" << std::endl;
      graph.load(corpus_dir, graph_loader);
  // } else if(format=="json"){
  //     dc.cout() << "json format" << std::endl;
  //     graph.load_json(corpus_dir, false, eparser, vparser);
  // } else if(format=="json-gzip"){
  //     dc.cout() <<"json gzip format" << std::endl;
  //     graph.load_json(corpus_dir, true, eparser, vparser);
  }else{
      dc.cout() << "Non supported format. See --help" << std::endl;
      return false;
  }

  dc.cout() << "Finalizing graph." << std::endl;
  timer.start();
  graph.finalize();
  dc.cout() << "Finalizing graph. Finished in "
            << timer.current_time() << " seconds." << std::endl;

  dc.cout() << "Computing number of words and documents." << std::endl;
  NWORDS = graph.map_reduce_vertices<size_t>(is_word);
  NDOCS = graph.map_reduce_vertices<size_t>(is_doc);
  NTOKENS = graph.map_reduce_edges<size_t>(count_tokens);

  dc.cout() << "Number of words:     " << NWORDS  << std::endl;
  dc.cout() << "Number of docs:      " << NDOCS   << std::endl;
  dc.cout() << "Number of tokens:    " << NTOKENS << std::endl;

  ASSERT_GT(NWORDS, 0);
  ASSERT_GT(NDOCS, 0);
  ASSERT_GT(NTOKENS, 0);

  // Prepare the json struct with the word counts
  TOP_WORDS.lock.lock();
  TOP_WORDS.json_string = "{\n" + TOP_WORDS.json_header_string() +
    "\t\"values\": [] \n }";
  TOP_WORDS.lock.unlock();
  return true;
} // end of load and initialize graph

/**
 * \brief Load the dictionary global variable from the file containing
 * the terms (one term per line).
 *
 * Note that while graphs can be loaded from multiple files the
 * dictionary must be in a single file.  The dictionary is loaded
 * entirely into memory and used to display word clouds and the top
 * terms in each topic.
 *
 * \param [in] fname the file containing the dictionary data.  The
 * data can be located on HDFS and can also be gzipped (must end in
 * ".gz").
 *
 */
bool load_dictionary(const std::string& fname)  {
  // std::cout << "staring load on: "
  //           << graphlab::get_local_ip_as_str() << std::endl;
  const bool gzip = boost::ends_with(fname, ".gz");
  // test to see if the graph_dir is an hadoop path
  if(boost::starts_with(fname, "hdfs://")) {
    graphlab::hdfs hdfs;
    graphlab::hdfs::fstream in_file(hdfs, fname);
    boost::iostreams::filtering_stream<boost::iostreams::input> fin;
    fin.set_auto_close(false);
    if(gzip) fin.push(boost::iostreams::gzip_decompressor());
    fin.push(in_file);
    if(!fin.good()) {
      logstream(LOG_ERROR) << "Error loading dictionary: "
                           << fname << std::endl;
      return false;
    }
    std::string term;
    while(std::getline(fin,term).good()) DICTIONARY.push_back(term);
    if (gzip) fin.pop();
    fin.pop();
    in_file.close();
  } else {
    std::cout << "opening: " << fname << std::endl;
    std::ifstream in_file(fname.c_str(),
                          std::ios_base::in | std::ios_base::binary);
    boost::iostreams::filtering_stream<boost::iostreams::input> fin;
    if (gzip) fin.push(boost::iostreams::gzip_decompressor());
    fin.push(in_file);
    if(!fin.good() || !fin.good()) {
      logstream(LOG_ERROR) << "Error loading dictionary: "
                           << fname << std::endl;
      return false;
    }
    std::string term;
    std::cout << "Loooping" << std::endl;
    while(std::getline(fin, term).good()) DICTIONARY.push_back(term);
    if (gzip) fin.pop();
    fin.pop();
    in_file.close();
  } // end of else
  // std::cout << "Finished load on: "
  //           << graphlab::get_local_ip_as_str() << std::endl;
  std::cout << "Dictionary Size: " << DICTIONARY.size() << std::endl;
  return true;
} // end of load dictionary

struct count_saver {
  bool save_words;
  count_saver(bool save_words) : save_words(save_words) { }
  typedef graph_type::vertex_type vertex_type;
  typedef graph_type::edge_type   edge_type;
  std::string save_vertex(const vertex_type& vertex) const {
    // Skip saving vertex data if the vertex type is not consistent
    // with the save type
    if((save_words && is_doc(vertex)) ||
       (!save_words && is_word(vertex))) return "";
    // Proceed to save
    std::stringstream strm;
    if(save_words) {
      const graphlab::vertex_id_type vid = vertex.id();
      strm << vid << '\t';
    } else { // save documents
      const graphlab::vertex_id_type vid = (-vertex.id()) - 2;
      strm << vid << '\t';
    }
    const factor_type& factor = vertex.data().factor;
    for(size_t i = 0; i < factor.size(); ++i) {
      strm << factor[i];
      if(i+1 < factor.size()) strm << '\t';
    }
    strm << '\n';
    return strm.str();
  }
  std::string save_edge(const edge_type& edge) const {
    return ""; //nop
  }
}; // end of prediction_saver

/**
 * \brief The omni engine type is used to allow switching between
 * synchronous and asynchronous computation.
 */
typedef graphlab::omni_engine<cgs_lda_vertex_program> engine_type;

int main(int argc, char** argv) {
  global_logger().set_log_level(LOG_INFO);
  global_logger().set_log_to_console(true);
  ///! Initialize control plain using mpi
  graphlab::mpi_tools::init(argc, argv);
  graphlab::distributed_control dc;
  //  INITIALIZE_EVENT_LOG(dc);
  ADD_CUMULATIVE_EVENT(TOKEN_CHANGES, "Token Changes", "Changes");

  // Parse command line options -----------------------------------------------
  const std::string description =
    "\n=========================================================================\n"
    "The Collapsed Gibbs Sampler for the LDA model implements\n"
    "a highly asynchronous version of parallel LDA in which document\n"
    "and word counts are maintained in an eventually consistent\n"
    "manner.\n"
    "\n"
    "The standard usage is: \n"
    "\t./cgs_lda --dictionary dictionary.txt --corpus doc_word_count.tsv\n"
    "where dictionary.txt contains: \n"
    "\taaa \n\taaai \n\tabalone \n\t   ... \n"
    "each line number corresponds to wordid (i.e aaa has wordid=0)\n\n"
    "and doc_word_count.tsv is formatted <docid> <wordid> <count>:\n"
    "(where wordid is indexed starting from zero and docid are positive integers)\n"
    "\t0\t0\t3\n"
    "\t0\t5\t1\n"
    "\t ...\n\n"
    "For JSON format, make sure docid are negative integers index starting from -2 \n\n"
    "To learn more about the NLP package and its applications visit\n\n"
    "\t\t http://graphlab.org \n\n"
    "Additional Options";
  graphlab::command_line_options clopts(description);
  std::string corpus_dir;
  std::string dictionary_fname;
  std::string doc_dir;
  std::string word_dir;
  std::string exec_type = "asynchronous";
  std::string format = "matrix";

  clopts.attach_option("dictionary", dictionary_fname,
                       "The file containing the list of unique words");
  clopts.attach_option("engine", exec_type,
                       "The engine type synchronous or asynchronous");
  clopts.attach_option("corpus", corpus_dir,
                       "The directory or file containing the corpus data.");
  clopts.add_positional("corpus");
  clopts.attach_option("ntopics", NTOPICS,
                       "Number of topics to use.");
  clopts.attach_option("alpha", ALPHA,
                       "The document hyper-prior");
  clopts.attach_option("beta", BETA,
                       "The word hyper-prior");
  clopts.attach_option("topk", TOPK,
                       "The number of words to report");
  clopts.attach_option("interval", INTERVAL,
                       "statistics reporting interval (in seconds)");
  clopts.attach_option("lik_interval", LIK_INTERVAL,
                       "likelihood reporting interval (in seconds)");
  clopts.attach_option("max_count", MAX_COUNT,
                       "The maximum number of occurences of a word in a document.");
  clopts.attach_option("format", format,
                       "Formats: matrix,json,json-gzip");
  clopts.attach_option("burnin", BURNIN,
                       "The time in second to run until a sample is collected. "
                       "If less than zero the sampler runs indefinitely.");
  clopts.attach_option("doc_dir", doc_dir,
                       "The output directory to save the final document counts.");
  clopts.attach_option("word_dir", word_dir,
                       "The output directory to save the final words counts.");

  if(!clopts.parse(argc, argv)) {
    graphlab::mpi_tools::finalize();
    return clopts.is_set("help")? EXIT_SUCCESS : EXIT_FAILURE;
  }

  if(dictionary_fname.empty()) {
    logstream(LOG_WARNING) << "No dictionary file was provided." << std::endl
                           << "Top k words will not be estimated." << std::endl;
  }

  if(corpus_dir.empty()) {
    logstream(LOG_ERROR) << "No corpus file was provided." << std::endl;
    return EXIT_FAILURE;
  }

  // Start the webserver
  graphlab::launch_metric_server();
  graphlab::add_metric_server_callback("wordclouds", word_cloud_callback);

  ///! Initialize global variables
  GLOBAL_TOPIC_COUNT.resize(NTOPICS);
  if(!dictionary_fname.empty()) {
    const bool success = load_dictionary(dictionary_fname);
    if(!success) {
      logstream(LOG_ERROR) << "Error loading dictionary." << std::endl;
      return EXIT_FAILURE;
    }
  }

  if(ALPHA <= 0) {
    logstream(LOG_ERROR)
      << "Alpha must be positive (alpha=" << ALPHA << ")!"  << std::endl;
    return EXIT_FAILURE;
  }

  if(BETA <= 0) {
    logstream(LOG_ERROR)
      << "Beta must be positive (beta=" << BETA << ")!"  << std::endl;
    return EXIT_FAILURE;
  }

  /// Initialize the log_gamma precached calculations.
  ALPHA_LGAMMA.init(ALPHA, 100000);
  BETA_LGAMMA.init(BETA, 1000000);

  ///! load the graph
  graph_type graph(dc, clopts);
  {
    const bool success =
      load_and_initialize_graph(dc, graph, corpus_dir, format);
    if(!success) {
      logstream(LOG_ERROR) << "Error loading graph." << std::endl;
      return EXIT_FAILURE;
    }
  }

  const size_t ntokens = graph.map_reduce_edges<size_t>(count_tokens);
  dc.cout() << "Total tokens: " << ntokens << std::endl;

  engine_type engine(dc, graph, exec_type, clopts);
  ///! Add an aggregator
  if(!DICTIONARY.empty()) {
    const bool success =
      engine.add_vertex_aggregator<topk_aggregator>
      ("topk", topk_aggregator::map, topk_aggregator::finalize) &&
      engine.aggregate_periodic("topk", INTERVAL);
    ASSERT_TRUE(success);
  }

  { // Add the Global counts aggregator
    const bool success =
      engine.add_vertex_aggregator<factor_type>
      ("global_counts",
       global_counts_aggregator::map,
       global_counts_aggregator::finalize) &&
      engine.aggregate_periodic("global_counts", 5);
    ASSERT_TRUE(success);
  }

  { // Add the likelihood aggregator
    const bool success =
      engine.add_vertex_aggregator<likelihood_aggregator>
      ("likelihood",
       likelihood_aggregator::map,
       likelihood_aggregator::finalize) &&
      engine.aggregate_periodic("likelihood", LIK_INTERVAL);
    ASSERT_TRUE(success);
  }

  ///! schedule only documents
  dc.cout() << "Running The Collapsed Gibbs Sampler" << std::endl;
  engine.map_reduce_vertices<graphlab::empty>(signal_only::docs);
  graphlab::timer timer;
  // Enable sampling
  cgs_lda_vertex_program::DISABLE_SAMPLING = false;
  // Run the engine
  engine.start();
  // Finalize the counts
  cgs_lda_vertex_program::DISABLE_SAMPLING = true;
  engine.signal_all();
  engine.start();

  const double runtime = timer.current_time();
  dc.cout()
    << "----------------------------------------------------------" << std::endl
    << "Final Runtime (seconds):   " << runtime
    << std::endl
    << "Updates executed: " << engine.num_updates() << std::endl
    << "Update Rate (updates/second): "
    << engine.num_updates() / runtime << std::endl;

  if(!word_dir.empty()) {
    // save word topic counts
    const bool gzip_output = false;
    const bool save_vertices = true;
    const bool save_edges = false;
    const size_t threads_per_machine = 2;
    const bool save_words = true;
    graph.save(word_dir, count_saver(save_words),
               gzip_output, save_vertices,
               save_edges, threads_per_machine);
  }

  if(!doc_dir.empty()) {
    // save doc topic counts
    const bool gzip_output = false;
    const bool save_vertices = true;
    const bool save_edges = false;
    const size_t threads_per_machine = 2;
    const bool save_words = false;
    graph.save(doc_dir, count_saver(save_words),
               gzip_output, save_vertices,
               save_edges, threads_per_machine);

  }

  graphlab::stop_metric_server_on_eof();
  graphlab::mpi_tools::finalize();
  return EXIT_SUCCESS;

} // end of main


#include <algorithm>
#include <boost/optional.hpp>
#include <climits>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <functional>
#include <iostream>
#include <limits>
#include <list>
#include <numeric>
#include <queue>
#include <sstream>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>

#define REP(i, n) for(int i = 0, i##_MACRO = (n); i < i##_MACRO; i++)
#define RANGE(i, a, b) for(int i = (a), i##_MACRO = (b); i < i##_MACRO; i++)
#define EACH(e, a) for(auto&& e : a)
#define ALL(a) (a).begin(), (a).end()
#define AALL(a, n) (a), ((a) + (n))
#define FILL(a, n) memset((a), n, sizeof(a))
#define FILLZ(a) FILL(a, 0)

using namespace std;

using ll = long long;
using VI = vector<int>;
using VI2D = vector<vector<int>>;

const int INF = 2e9;
const double EPS = 1e-10;
const double PI = acos(-1.0);

const int dx[] = {-1, 0, 1, 0};
const int dy[] = {0, -1, 0, 1};

template <typename T>
int sign(T x) {
	return x < 0 ? -1 : x > 0 ? 1 : 0;
}

template <>
int sign(double x) {
	return x < -EPS ? -1 : x > EPS ? 1 : 0;
}

template <typename T>
void chmax(T& m, T x) {
	m = max(m, x);
}

template <typename T>
void chmin(T& m, T x) {
	m = min(m, x);
}

template <typename T>
T square(T x) {
	return x * x;
}

inline int toInt(string s) {
	int v;
	istringstream sin(s);
	sin >> v;
	return v;
}

template <long long MOD = 1000000007>
class ModInt {
	public:
	long long n;

	ModInt() : n(0) {}
	ModInt(long long n) : n(n) {
		while(this->n < 0) {
			this->n += MOD;
		}
		this->n %= MOD;
	}

	long long get() const { return this->n; }
	long long get_mod() const { return MOD; }

	ModInt inv() const { return pow<ModInt<>>(*this, MOD - 2); }

	ModInt& operator=(const long long rhs) { return *this = ModInt(rhs); }
	ModInt& operator+=(const ModInt rhs) {
		return *this = ModInt(this->n + rhs.n);
	}
	ModInt& operator-=(const ModInt rhs) {
		return *this = ModInt(this->n - rhs.n);
	}
	ModInt& operator*=(const ModInt rhs) {
		return *this = ModInt(this->n * rhs.n);
	}
	ModInt& operator/=(const ModInt rhs) { return *this *= rhs.inv(); }
	bool operator==(const ModInt rhs) const { return this->n == rhs.n; }
};

template <long long MOD>
ModInt<MOD> operator+(const ModInt<MOD>& lhs, const ModInt<MOD>& rhs) {
	return ModInt<MOD>(lhs) += rhs;
}
template <long long MOD>
ModInt<MOD> operator+(const ModInt<MOD>& lhs, const long long& rhs) {
	return ModInt<MOD>(lhs) += rhs;
}
template <long long MOD>
ModInt<MOD> operator+(const long long& lhs, const ModInt<MOD>& rhs) {
	return ModInt<MOD>(lhs) += rhs;
}

template <long long MOD>
ModInt<MOD> operator-(const ModInt<MOD>& lhs, const ModInt<MOD>& rhs) {
	return ModInt<MOD>(lhs) -= rhs;
}
template <long long MOD>
ModInt<MOD> operator-(const ModInt<MOD>& lhs, const long long& rhs) {
	return ModInt<MOD>(lhs) -= rhs;
}
template <long long MOD>
ModInt<MOD> operator-(const long long& lhs, const ModInt<MOD>& rhs) {
	return ModInt<MOD>(lhs) -= rhs;
}

template <long long MOD>
ModInt<MOD> operator*(const ModInt<MOD>& lhs, const ModInt<MOD>& rhs) {
	return ModInt<MOD>(lhs) *= rhs;
}
template <long long MOD>
ModInt<MOD> operator*(const ModInt<MOD>& lhs, const long long& rhs) {
	return ModInt<MOD>(lhs) *= rhs;
}
template <long long MOD>
ModInt<MOD> operator*(const long long& lhs, const ModInt<MOD>& rhs) {
	return ModInt<MOD>(lhs) *= rhs;
}

template <long long MOD>
ModInt<MOD> operator/(const ModInt<MOD>& lhs, const ModInt<MOD>& rhs) {
	return ModInt<MOD>(lhs) /= rhs;
}
template <long long MOD>
ModInt<MOD> operator/(const ModInt<MOD>& lhs, const long long& rhs) {
	return ModInt<MOD>(lhs) /= rhs;
}
template <long long MOD>
ModInt<MOD> operator/(const long long& lhs, const ModInt<MOD>& rhs) {
	return ModInt<MOD>(lhs) /= rhs;
}

template <long long MOD>
std::ostream& operator<<(std::ostream& os, const ModInt<MOD>& x) {
	return os << x.n;
}

template <long long MOD>
std::istream& operator>>(std::istream& is, const ModInt<MOD>& x) {
	return is >> x.n;
}

// 繰り返し2乗法
// 計算量 O(logn)
template <typename T>
T pow(T a, int n) {
	T ret = 1;
	while(n != 0) {
		if(n % 2) {
			ret *= a;
		}
		a *= a;
		n /= 2;
	}
	return ret;
}

// 二項係数
// 計算量 O(r)
template <typename T>
T comb(long long n, long long r) {
	if(n < r || n < 0 || r < 0) {
		return T(0);
	}
	T ret = 1;
	r = std::min(r, n - r);
	for(int i = 0; i < r; i++) {
		ret *= (n - i);
		ret /= i + 1;
	}
	return ret;
}

int main() {
	int n, a, b;
	cin >> n >> a >> b;
	auto all = pow<ModInt<>>(2, n) - 1;
	cout << all - comb<ModInt<>>(n, a) - comb<ModInt<>>(n, b) << endl;
	return 0;
}


#include "truncated-svd-solver/linear-algebra-helpers.h"

#include <algorithm>
#include <cmath>

#include <cholmod.h>
#include <Eigen/Dense>
#include <glog/logging.h>
#include <spqr.hpp>
#include <SuiteSparseQR.hpp>

namespace truncated_svd_solver {

cholmod_sparse* columnSubmatrix(cholmod_sparse* A, std::ptrdiff_t
    col_start_idx, std::ptrdiff_t col_end_idx, cholmod_common* cholmod) {
  CHECK_NOTNULL(A);
  CHECK_NOTNULL(cholmod);

  CHECK_GE(col_end_idx, col_start_idx);
  CHECK_LT(col_end_idx, static_cast<std::ptrdiff_t>(A->ncol));
  CHECK_GE(col_start_idx, 0);

  const std::ptrdiff_t num_indices = col_end_idx - col_start_idx + 1;
  std::ptrdiff_t* col_indices = new std::ptrdiff_t[num_indices];
  for (std::ptrdiff_t j = col_start_idx; j <= col_end_idx; ++j) {
    col_indices[j - col_start_idx] = j;
  }

  cholmod_sparse* A_sub = cholmod_l_submatrix(A, nullptr, -1, col_indices,
    num_indices, 1, 1, cholmod);
  delete [] col_indices;
  CHECK(A_sub != nullptr) << "cholmod_l_submatrix failed.";

  return A_sub;
}

cholmod_sparse* rowSubmatrix(cholmod_sparse* A, std::ptrdiff_t row_start_idx,
    std::ptrdiff_t row_end_idx, cholmod_common* cholmod) {
  CHECK_NOTNULL(A);
  CHECK_NOTNULL(cholmod);

  CHECK_GE(row_end_idx, row_start_idx);
  CHECK_LT(row_end_idx, static_cast<std::ptrdiff_t>(A->nrow));
  CHECK_GE(row_start_idx, 0);

  const std::ptrdiff_t num_indices = row_end_idx - row_start_idx + 1;
  std::ptrdiff_t* row_indices = new std::ptrdiff_t[num_indices];
  for (std::ptrdiff_t i = row_start_idx; i <= row_end_idx; ++i){
    row_indices[i - row_start_idx] = i;
  }

  cholmod_sparse* A_sub = cholmod_l_submatrix(A, row_indices, num_indices,
    nullptr, -1, 1, 1, cholmod);
  delete [] row_indices;
  CHECK(A_sub != nullptr) << "cholmod_l_submatrix failed.";

  return A_sub;
}

double colNorm(const cholmod_sparse* A, std::ptrdiff_t col_idx) {
  CHECK_NOTNULL(A);
  CHECK_GE(col_idx, 0);
  CHECK_LT(col_idx, static_cast<std::ptrdiff_t>(A->ncol));

  const std::ptrdiff_t* col_ptr =
    reinterpret_cast<const std::ptrdiff_t*>(A->p);
  const double* values = reinterpret_cast<const double*>(A->x);
  const std::ptrdiff_t p = col_ptr[col_idx];
  const std::ptrdiff_t num_elements = col_ptr[col_idx + 1] - p;

  double norm = 0.0;
  for (std::ptrdiff_t i = 0; i < num_elements; ++i) {
    norm += values[p + i] * values[p + i];
  }
  return std::sqrt(norm);
}

cholmod_dense* columnScalingMatrix(cholmod_sparse* A, cholmod_common*
    cholmod, double eps) {
  CHECK_NOTNULL(A);
  CHECK_NOTNULL(cholmod);
  CHECK_GT(eps, 0.0);

  cholmod_dense* G = cholmod_l_allocate_dense(A->ncol, 1, A->ncol,
    CHOLMOD_REAL, cholmod);
  CHECK(G != nullptr) << "cholmod_l_allocate_dense failed.";

  const double norm_tolerance = std::sqrt(A->nrow * eps);
  double* values = reinterpret_cast<double*>(G->x);
  for (std::ptrdiff_t col_idx = 0;
      col_idx < static_cast<std::ptrdiff_t>(A->ncol);
      ++col_idx) {
    const double norm = colNorm(A, col_idx);
    if (norm < norm_tolerance) {
      values[col_idx] = 0.0;
    } else {
      values[col_idx] = 1.0 / norm;
    }
  }
  return G;
}

void cholmodSparseToEigenDenseCopy(const cholmod_sparse* in,
    Eigen::MatrixXd& out) {
  CHECK_NOTNULL(in);
  out.setZero(in->nrow, in->ncol);

  const std::ptrdiff_t* row_ind =
    reinterpret_cast<const std::ptrdiff_t*>(in->i);
  const std::ptrdiff_t* col_ptr =
    reinterpret_cast<const std::ptrdiff_t*>(in->p);
  const double* values = reinterpret_cast<const double*>(in->x);
  for (std::ptrdiff_t col_idx = 0;
       col_idx < static_cast<std::ptrdiff_t>(in->ncol); ++col_idx)
    for (std::ptrdiff_t val_idx = col_ptr[col_idx];
         val_idx < col_ptr[col_idx + 1]; ++val_idx) {
      out(row_ind[val_idx], col_idx) = values[val_idx];
      if (in->stype && col_idx != row_ind[val_idx]) {
        out(col_idx, row_ind[val_idx]) = values[val_idx];
      }
    }
}

void eigenDenseToCholmodDenseView(const Eigen::VectorXd& in, cholmod_dense*
    out) {
  CHECK_NOTNULL(out);

  out->nrow = in.size();
  out->ncol = 1;
  out->nzmax = in.size();
  out->d = in.size();
  out->x = reinterpret_cast<void*>(const_cast<double*>(in.data()));
  out->z = nullptr;
  out->xtype = CHOLMOD_REAL;
  out->dtype = CHOLMOD_DOUBLE;
}

cholmod_dense* eigenDenseToCholmodDenseCopy(const Eigen::VectorXd& in,
    cholmod_common* cholmod) {
  CHECK_NOTNULL(cholmod);
  cholmod_dense* out = cholmod_l_allocate_dense(in.size(), 1, in.size(),
    CHOLMOD_REAL, cholmod);
  CHECK(out != nullptr) << "cholmod_l_allocate_dense failed.";

  double* out_val = reinterpret_cast<double*>(out->x);
  const double* in_val = in.data();
  std::copy(in_val, in_val + in.size(), out_val);
  return out;
}

void cholmodDenseToEigenDenseCopy(const cholmod_dense* in, Eigen::VectorXd&
    out) {
  CHECK_NOTNULL(in);
  out.resize(in->nrow);
  const double* in_val = reinterpret_cast<const double*>(in->x);
  std::copy(in_val, in_val + in->nrow, out.data());
}

cholmod_sparse* eigenDenseToCholmodSparseCopy(const Eigen::MatrixXd& A,
    cholmod_common* cholmod, double eps) {
  CHECK_NOTNULL(cholmod);
  CHECK_GT(eps, 0.0);

  size_t nzmax = 0;
  for (std::ptrdiff_t i = 0; i < A.rows(); ++i) {
    for (std::ptrdiff_t j = 0; j < A.cols(); ++j) {
      if (std::fabs(A(i, j)) > eps) {
        nzmax++;
      }
    }
  }

  cholmod_sparse* A_cholmod = cholmod_l_allocate_sparse(A.rows(), A.cols(),
    nzmax, 1, 1, 0, CHOLMOD_REAL, cholmod);
  CHECK(A_cholmod != nullptr) << "cholmod_l_allocate_sparse failed.";

  std::ptrdiff_t* row_ind = reinterpret_cast<std::ptrdiff_t*>(A_cholmod->i);
  std::ptrdiff_t* col_ptr = reinterpret_cast<std::ptrdiff_t*>(A_cholmod->p);
  double* values = reinterpret_cast<double*>(A_cholmod->x);
  std::ptrdiff_t row_it = 0;
  std::ptrdiff_t col_it = 1;
  for (std::ptrdiff_t c = 0; c < A.cols(); ++c) {
    for (std::ptrdiff_t r = 0; r < A.rows(); ++r)
      if (std::fabs(A(r, c)) > eps) {
        values[row_it] = A(r, c);
        row_ind[row_it] = r;
        row_it++;
      }
    col_ptr[col_it] = row_it;
    col_it++;
  }
  return A_cholmod;
}

std::ptrdiff_t estimateNumericalRank(const Eigen::VectorXd& singular_values,
                                     double tolerance) {
  CHECK_GE(tolerance, 0.0);

  std::ptrdiff_t numerical_rank = singular_values.size();
  for (std::ptrdiff_t i = singular_values.size() - 1; i >= 0; --i) {
    if (singular_values(i) > tolerance) {
      // Assumes that the singular values are ordered.
      break;
    } else {
      --numerical_rank;
    }
  }
  return numerical_rank;
}

double rankTol(const Eigen::VectorXd& singular_values, double eps) {
  CHECK_GT(singular_values.rows(), 0) << "Empty singular values vector.";
  CHECK_GT(eps, 0.0);
  return singular_values(0) * eps * singular_values.size();
}

double qrTol(cholmod_sparse* A, cholmod_common* cholmod, double eps) {
  CHECK_NOTNULL(A);
  CHECK_NOTNULL(cholmod);
  CHECK_GT(eps, 0.0);
  return 20.0 * static_cast<double>(A->nrow + A->ncol) * eps *
    spqr_maxcolnorm<double>(A, cholmod);
}

double svGap(const Eigen::VectorXd& singular_values, std::ptrdiff_t rank) {
  CHECK_LE(rank, singular_values.rows());
  CHECK_GE(rank, 0);

  if (rank == 0) {
    return 0.0;
  } else if (rank < singular_values.size()) {
    return singular_values(rank - 1) / singular_values(rank);
  }
  return std::numeric_limits<double>::infinity();
}

void reduceLeftHandSide(SuiteSparseQR_factorization<double>* factor,
    cholmod_sparse* A_rt, cholmod_sparse** Omega, cholmod_sparse** A_rtQ,
    cholmod_common* cholmod) {
  CHECK_NOTNULL(A_rt);
  CHECK_NOTNULL(cholmod);
  CHECK_NOTNULL(Omega);

  // Handle the special case where the QR part is empty.
  if (factor == nullptr) {
    // NO QR part!
    *Omega = cholmod_l_aat(A_rt, nullptr, 0, 1, cholmod);
    return;
  } else {
    CHECK(factor->QRsym != nullptr) << "Run symbolic factorization first.";
    CHECK(factor->QRnum != nullptr) << "Run QR decomposition first.";
  }
  CHECK_NOTNULL(factor);
  CHECK_NOTNULL(A_rtQ);

  cholmod_sparse* A_rtQFull = SuiteSparseQR_qmult<double>(SPQR_XQ, factor,
    A_rt, cholmod);
  CHECK(A_rtQFull != nullptr) << "SuiteSparseQR_qmult failed.";

  *A_rtQ = columnSubmatrix(A_rtQFull, 0, factor->QRsym->n - 1, cholmod);
  cholmod_l_free_sparse(&A_rtQFull, cholmod);

  cholmod_sparse* A_rtQ2 = cholmod_l_aat(*A_rtQ, nullptr, 0, 1, cholmod);
  CHECK(A_rtQ2 != nullptr) << "cholmod_l_aat failed.";
  A_rtQ2->stype = 1;

  cholmod_sparse* A_rt2 = cholmod_l_aat(A_rt, nullptr, 0, 1, cholmod);
  CHECK(A_rt2 != nullptr) << "cholmod_l_aat failed.";
  A_rt2->stype = 1;

  double alpha[2];
  alpha[0] = 1.0;
  double beta[2];
  beta[0] = -1.0;

  *Omega = cholmod_l_add(A_rt2, A_rtQ2, alpha, beta, 1, 1, cholmod);
  CHECK(*Omega != nullptr) << "cholmod_l_add failed.";

  cholmod_l_free_sparse(&A_rt2, cholmod);
  cholmod_l_free_sparse(&A_rtQ2, cholmod);
}

cholmod_dense* reduceRightHandSide(SuiteSparseQR_factorization<double>*
    factor, cholmod_sparse* A_rt, cholmod_sparse* A_rtQ, cholmod_dense* b,
    cholmod_common* cholmod) {
  CHECK_NOTNULL(A_rt);
  CHECK_NOTNULL(b);
  CHECK_NOTNULL(cholmod);

  // Handle the special case where the QR part is empty.
  if (factor == nullptr) {
    // NO QR part!
    double one = 1, zero = 0;
    cholmod_dense* b_reduced = cholmod_l_allocate_dense(
        A_rt->nrow, 1, A_rt->nrow, CHOLMOD_REAL, cholmod);
    CHECK(b_reduced != nullptr) << "cholmod_l_allocate_dense failed.";

    const int status = cholmod_l_sdmult(A_rt, 0, &one, &zero, b, b_reduced,
                                        cholmod);
    CHECK(status) << "cholmod_l_sdmult failed";
    return b_reduced;
  } else {
    CHECK(factor->QRsym != nullptr) << "Run symbolic factorization first.";
    CHECK(factor->QRnum != nullptr) << "Run QR decomposition first.";
  }
  CHECK_NOTNULL(factor);
  CHECK_NOTNULL(A_rtQ);

  cholmod_sparse* b_sparse = cholmod_l_dense_to_sparse(b, 1, cholmod);
  CHECK(b_sparse != nullptr) << "cholmod_l_dense_to_sparse failed.";

  cholmod_sparse* A_rtb = cholmod_l_ssmult(A_rt, b_sparse, 0, 1, 1, cholmod);
  CHECK(A_rtb != nullptr) << "cholmod_l_ssmult failed.";

  cholmod_sparse* QtbFull = SuiteSparseQR_qmult<double>(SPQR_QTX, factor,
    b_sparse, cholmod);
  CHECK(QtbFull != nullptr) << "SuiteSparseQR_qmult failed.";
  cholmod_l_free_sparse(&b_sparse, cholmod);

  cholmod_sparse* Qtb;
  Qtb = rowSubmatrix(QtbFull, 0, factor->QRsym->n - 1, cholmod);
  cholmod_l_free_sparse(&QtbFull, cholmod);

  cholmod_sparse* A_rtQQtb = cholmod_l_ssmult(A_rtQ, Qtb, 0, 1, 1, cholmod);
  CHECK(A_rtQQtb != nullptr) << "cholmod_l_ssmult failed.";
  cholmod_l_free_sparse(&Qtb, cholmod);

  double alpha[2];
  alpha[0] = 1.0;
  double beta[2];
  beta[0] = -1.0;

  cholmod_sparse* b_reduced_sparse = cholmod_l_add(A_rtb, A_rtQQtb, alpha,
    beta, 1, 1, cholmod);
  CHECK(b_reduced_sparse != nullptr) << "cholmod_l_add failed.";
  cholmod_l_free_sparse(&A_rtb, cholmod);
  cholmod_l_free_sparse(&A_rtQQtb, cholmod);

  cholmod_dense* b_reduced = cholmod_l_sparse_to_dense(b_reduced_sparse,
    cholmod);
  CHECK(b_reduced != nullptr) << "cholmod_l_sparse_to_dense failed.";
  cholmod_l_free_sparse(&b_reduced_sparse, cholmod);
  return b_reduced;
}

void analyzeSVD(const cholmod_sparse* Omega, Eigen::VectorXd& sv,
    Eigen::MatrixXd& U, Eigen::MatrixXd& V) {
  CHECK_NOTNULL(Omega);

  Eigen::MatrixXd OmegaDense;
  cholmodSparseToEigenDenseCopy(Omega, OmegaDense);
  const Eigen::JacobiSVD<Eigen::MatrixXd> svd(OmegaDense,
    Eigen::ComputeThinU | Eigen::ComputeThinV);
  U = svd.matrixU();
  V = svd.matrixV();
  sv = svd.singularValues();
}

void solveSVD(const cholmod_dense* b, const Eigen::VectorXd& sv, const
    Eigen::MatrixXd& U, const Eigen::MatrixXd& V, std::ptrdiff_t rank,
    Eigen::VectorXd& x) {
  CHECK_NOTNULL(b);
  CHECK_LE(rank, V.cols());
  CHECK_EQ(V.cols(), sv.rows());
  CHECK_EQ(U.rows(), static_cast<std::ptrdiff_t>(b->nrow));

  Eigen::Map<const Eigen::VectorXd> b_eigen(
    reinterpret_cast<const double*>(b->x), b->nrow);
  x = V.leftCols(rank) * sv.head(rank).asDiagonal().inverse() *
    U.leftCols(rank).adjoint() * b_eigen;
}

cholmod_dense* solveQR(SuiteSparseQR_factorization<double>* factor,
    cholmod_dense* b, cholmod_sparse* A_r, const Eigen::VectorXd& x_r,
    cholmod_common* cholmod) {
  CHECK_NOTNULL(factor);
  CHECK(factor->QRsym != nullptr) << "Run symbolic factorization first.";
  CHECK(factor->QRnum != nullptr) << "Run QR decomposition first.";
  CHECK_NOTNULL(b);
  CHECK_NOTNULL(cholmod);

  cholmod_dense* QtbmA_rx_r = nullptr;
  if (A_r != nullptr) {
    CHECK_EQ(x_r.size(), static_cast<int>(A_r->ncol))
      << "Dimension mismatch between x_r and A_r.";

    cholmod_dense x_r_cholmod;
    eigenDenseToCholmodDenseView(x_r, &x_r_cholmod);
    cholmod_dense* A_rx_r = cholmod_l_allocate_dense(A_r->nrow, 1, A_r->nrow,
      CHOLMOD_REAL, cholmod);
    CHECK(A_rx_r != nullptr) << "cholmod_l_allocate_dense failed.";

    double alpha[2];
    alpha[0] = 1.0;
    double beta[2];
    beta[0] = 0.0;
    const bool success = cholmod_l_sdmult(A_r, 0, alpha, beta, &x_r_cholmod,
                                          A_rx_r, cholmod);
    CHECK(success) << "cholmod_l_sdmult failed.";

    Eigen::Map<const Eigen::VectorXd> bEigen(
      reinterpret_cast<const double*>(b->x), b->nrow);
    Eigen::Map<const Eigen::VectorXd> A_rx_rEigen(
      reinterpret_cast<const double*>(A_rx_r->x), A_rx_r->nrow);
    const Eigen::VectorXd bmA_rx_rEigen = bEigen - A_rx_rEigen;
    cholmod_l_free_dense(&A_rx_r, cholmod);
    cholmod_dense bmA_rx_r;
    eigenDenseToCholmodDenseView(bmA_rx_rEigen, &bmA_rx_r);
    QtbmA_rx_r = SuiteSparseQR_qmult<double>(SPQR_QTX, factor, &bmA_rx_r,
                                             cholmod);
  } else {
    QtbmA_rx_r = SuiteSparseQR_qmult<double>(SPQR_QTX, factor, b, cholmod);
  }
  CHECK(QtbmA_rx_r != nullptr) << "SuiteSparseQR_qmult failed.";

  cholmod_dense* x_l = SuiteSparseQR_solve<double>(SPQR_RETX_EQUALS_B,
    factor, QtbmA_rx_r, cholmod);
  CHECK(x_l != nullptr) << "SuiteSparseQR_solve failed.";
  cholmod_l_free_dense(&QtbmA_rx_r, cholmod);
  return x_l;
}

}  // namespace truncated_svd_solver


//---------------------------------------------------------------------------//
// Copyright (c) 2019 Mikhail Komarov <nemo@nil.foundation>
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_MAC_SIPHASH_FUNCTIONS_HPP
#define CRYPTO3_MAC_SIPHASH_FUNCTIONS_HPP

#include <boost/integer.hpp>
#include <boost/container/static_vector.hpp>

#include <nil/crypto3/mac/detail/siphash/siphash_policy.hpp>

namespace nil {
    namespace crypto3 {
        namespace mac {
            namespace detail {
                template<std::size_t Rounds, std::size_t FinalRounds>
                struct siphash_functions : public siphash_policy<Rounds, FinalRounds> {
                    typedef siphash_policy<Rounds, FinalRounds> policy_type;

                    constexpr static const std::size_t rounds = policy_type::rounds;
                    constexpr static const std::size_t final_rounds = policy_type::final_rounds;

                    constexpr static const std::size_t word_bits = policy_type::word_bits;
                    typedef typename policy_type::word_type word_type;

                    constexpr static const std::size_t key_bits = policy_type::key_bits;
                    constexpr static const std::size_t key_words = policy_type::key_words;
                    typedef typename policy_type::key_type key_type;

                    constexpr static const std::size_t key_schedule_bits = policy_type::key_schedule_bits;
                    constexpr static const std::size_t key_schedule_words = policy_type::key_schedule_words;
                    typedef typename policy_type::key_schedule_type key_schedule_type;

                    template<std::size_t InternalRounds>
                    void sip_rounds(key_schedule_type& V, word_type M) {
                        word_type V0 = V[0], V1 = V[1], V2 = V[2], V3 = V[3];

                        V3 ^= M;

                        for (size_t i = 0; i != InternalRounds; ++i) {
                            V0 += V1;
                            V2 += V3;
                            V1 = policy_type::template rotl<13>(V1);
                            V3 = policy_type::template rotl<16>(V3);
                            V1 ^= V0;
                            V3 ^= V2;
                            V0 = policy_type::template rotl<32>(V0);

                            V2 += V1;
                            V0 += V3;
                            V1 = policy_type::template rotl<17>(V1);
                            V3 = policy_type::template rotl<21>(V3);
                            V1 ^= V2;
                            V3 ^= V0;
                            V2 = policy_type::template rotl<32>(V2);
                        }
                        V0 ^= M;

                        V[0] = V0;
                        V[1] = V1;
                        V[2] = V2;
                        V[3] = V3;
                    }
                };
            }    // namespace detail
        }        // namespace mac
    }            // namespace crypto3
}    // namespace nil

#endif    // CRYPTO3_SIPHASH_POLICY_HPP


//Machine-generated by miind.py. Edit at your own risk.

#include <boost/timer/timer.hpp>
#include <GeomLib.hpp>
#include <MPILib/include/MPINetworkCode.hpp>
#include <MPILib/include/RateAlgorithmCode.hpp>
#include <MPILib/include/SimulationRunParameter.hpp>
#include <MPILib/include/report/handler/RootReportHandler.hpp>
#include <MPILib/include/WilsonCowanAlgorithm.hpp>
#include <MPILib/include/PersistantAlgorithm.hpp>
#include <MPILib/include/DelayAlgorithmCode.hpp>
#include <MPILib/include/RateFunctorCode.hpp>

typedef MPILib::MPINetwork<double, MPILib::utilities::CircularDistribution> Network;

int main(int argc, char *argv[]) {
    Network network;
    boost::timer::auto_cpu_timer t;

#ifdef ENABLE_MPI
    // initialise the mpi environment this cannot be forwarded to a class
    boost::mpi::environment env(argc, argv);
#endif

    try {	// generating algorithms
        const MPILib::Time t_mem_0 = 50e-3;
        const double f_noise_0 = 1.0;
        MPILib::Rate f_max_0 = 10.0;
        MPILib::Rate I_ext_0 = 0;
        MPILib::WilsonCowanParameter  par_wil_0(t_mem_0,f_max_0,f_noise_0,I_ext_0);
        MPILib::WilsonCowanAlgorithm alg_wc_0(par_wil_0);
        MPILib::RateAlgorithm<double> rate_alg_1(100.0);
        // generating nodes
        MPILib::NodeId id_0 = network.addNode(alg_wc_0,MPILib::EXCITATORY_GAUSSIAN);
        MPILib::NodeId id_1 = network.addNode(rate_alg_1,MPILib::EXCITATORY_GAUSSIAN);
        // generating connections
        double con_1_0(0.1);
        network.makeFirstInputOfSecond(id_1,id_0,con_1_0);
        // generation simulation parameter
        const MPILib::Time tmin = 0;
        const MPILib::Time tmax = 0.3;
        const MPILib::Rate fmin = 0;
        const MPILib::Rate fmax = 10;
        const MPILib::Potential statemin = 0;
        const MPILib::Potential statemax = 0.02;
        const MPILib::Potential densemin = 0;
        const MPILib::Potential densemax = 250;
        MPILib::CanvasParameter par_canvas(tmin,tmax,fmin,fmax,statemin,statemax,densemin,densemax);

        MPILib::report::handler::RootReportHandler handler("wilsoncowan",true,true, par_canvas);
        handler.addNodeToCanvas(id_0);
        SimulationRunParameter par_run( handler,1000000,0,0.3,1e-03,1e-03,"wilson.log",1e-03);
        network.configureSimulation(par_run);
        network.evolve();
    } catch(std::exception exc) {
        std::cout << exc.what() << std::endl;
#ifdef ENABLE_MPI
        //Abort the MPI environment in the correct way :
        env.abort(1);
#endif
    }

    MPILib::utilities::MPIProxy().barrier();
    t.stop();
    if (MPILib::utilities::MPIProxy().getRank() == 0) {

        std::cout << "Overall time spend\n";
        t.report();
    }
    return 0;
}


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

#include <vital/types/homography.h>

#include <Eigen/Core>

#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>

namespace py=pybind11;
namespace kwiver {
namespace vital  {
namespace python {

class PyHomographyBase
{

  public:

    virtual ~PyHomographyBase() = default;

    virtual char get_type() {return '0';};
    virtual py::object get_matrix() {return py::none();};

    virtual py::object inverse() {return py::none();};
    virtual py::object map(py::object) {return py::none();};
    virtual py::object normalize() {return py::none();};

    bool operator==(std::shared_ptr<PyHomographyBase> &);
    bool operator!=(std::shared_ptr<PyHomographyBase> &other) {return !(*this == other);};
    virtual std::shared_ptr<PyHomographyBase> operator*(std::shared_ptr<PyHomographyBase> &) {return std::shared_ptr<PyHomographyBase>();};
};

class PyHomographyD
: public PyHomographyBase
{
  kwiver::vital::homography_<double> homog;

  public:

    PyHomographyD() {};
    PyHomographyD(Eigen::Matrix<double, 3, 3> mat) : homog(kwiver::vital::homography_<double>(mat)) {};
    PyHomographyD(kwiver::vital::homography_<double> mat) : homog(mat) {};

    char get_type() {return 'd';};
    py::object get_matrix() { return py::cast<Eigen::Matrix<double, 3, 3>>(homog.get_matrix()); };

    py::object inverse();
    py::object map(py::object);
    py::object normalize();

    std::shared_ptr<PyHomographyBase> operator*(std::shared_ptr<PyHomographyBase> &);
};

class PyHomographyF
: public PyHomographyBase
{
  kwiver::vital::homography_<float> homog;

  public:

    PyHomographyF() {};
    PyHomographyF(Eigen::Matrix<float, 3, 3> mat) : homog(kwiver::vital::homography_<float>(mat)) {};
    PyHomographyF(kwiver::vital::homography_<float> mat) : homog(mat) {};

    char get_type() {return 'f';};
    py::object get_matrix() { return py::cast<Eigen::Matrix<float, 3, 3>>(homog.get_matrix()); };

    py::object inverse();
    py::object map(py::object);
    py::object normalize();

    std::shared_ptr<PyHomographyBase> operator*(std::shared_ptr<PyHomographyBase> &);
};

py::object
PyHomographyD
::inverse()
{
  auto new_homog = this->homog.inverse();
  auto new_matrix = new_homog->matrix();
  return py::cast<Eigen::Matrix<double, 3, 3>>(new_matrix);
}

py::object
PyHomographyD
::map(py::object p_obj)
{
  auto p = p_obj.cast<Eigen::Matrix<double,2,1>>();
  auto new_matrix = this->homog.map_point(p);
  return py::cast<Eigen::Matrix<double, 2, 1>>(new_matrix);
}

py::object
PyHomographyD
::normalize()
{
  auto new_homog = this->homog.normalize();
  auto new_matrix = new_homog->matrix();
  return py::cast<Eigen::Matrix<double, 3, 3>>(new_matrix);
}

std::shared_ptr<PyHomographyBase>
PyHomographyD::
operator*(std::shared_ptr<PyHomographyBase> &other)
{
  auto mat = other->get_matrix().cast<Eigen::Matrix<double,3,3>>(); //this part is a bit silly, could be done better
  auto other_homog = kwiver::vital::homography_<double>(mat);
  return std::shared_ptr<PyHomographyBase>(new PyHomographyD(this->homog*other_homog));
}

py::object
PyHomographyF
::inverse()
{
  auto new_homog = this->homog.inverse();
  auto new_matrix = new_homog->matrix();
  return py::cast<Eigen::Matrix<double, 3, 3>>(new_matrix);
}

py::object
PyHomographyF
::map(py::object p_obj)
{
  auto p = p_obj.cast<Eigen::Matrix<float,2,1>>();
  auto new_matrix = this->homog.map_point(p);
  return py::cast<Eigen::Matrix<float, 2, 1>>(new_matrix);
}

py::object
PyHomographyF
::normalize()
{
  auto new_homog = this->homog.normalize();
  auto new_matrix = new_homog->matrix();
  return py::cast<Eigen::Matrix<double, 3, 3>>(new_matrix);
}

std::shared_ptr<PyHomographyBase>
PyHomographyF::
operator*(std::shared_ptr<PyHomographyBase> &other)
{
  auto mat = other->get_matrix().cast<Eigen::Matrix<float,3,3>>(); //this part is a bit silly, could be done better
  auto other_homog = kwiver::vital::homography_<float>(mat);
  return std::shared_ptr<PyHomographyBase>(new PyHomographyF(this->homog*other_homog));
}

bool
PyHomographyBase::
operator==(std::shared_ptr<PyHomographyBase> &other)
{
  if(this->get_type() == 'd')
  {
    auto this_mat = this->get_matrix().cast<Eigen::Matrix<double, 3, 3>>();
    auto other_mat = other->get_matrix().cast<Eigen::Matrix<double, 3, 3>>();
    return this_mat.isApprox(other_mat);
  }
  else if(this->get_type() == 'f')
  {
    auto this_mat = this->get_matrix().cast<Eigen::Matrix<float, 3, 3>>();
    auto other_mat = other->get_matrix().cast<Eigen::Matrix<float, 3, 3>>();
    return this_mat.isApprox(other_mat, 0.001);
  }

  return false;
}

std::shared_ptr<PyHomographyBase>
new_homography(char ctype)
{
  std::shared_ptr<PyHomographyBase> retVal;
  if(ctype == 'd')
  {
    retVal = std::shared_ptr<PyHomographyBase>(new PyHomographyD());
  }
  else if(ctype == 'f')
  {
    retVal = std::shared_ptr<PyHomographyBase>(new PyHomographyF());
  }
  return retVal;
}

std::shared_ptr<PyHomographyBase>
new_homography_from_matrix(py::object data_obj, char ctype)
{
  std::shared_ptr<PyHomographyBase> retVal;
  if(ctype == 'd')
  {
    auto data = data_obj.cast<Eigen::Matrix<double, 3,3>>();
    retVal = std::shared_ptr<PyHomographyBase>(new PyHomographyD(data));
  }
  else if(ctype == 'f')
  {
    auto data = data_obj.cast<Eigen::Matrix<float, 3,3>>();
    retVal = std::shared_ptr<PyHomographyBase>(new PyHomographyF(data));
  }
  return retVal;
}

std::shared_ptr<PyHomographyBase>
new_random_homography(char ctype)
{
  py::object data_obj;
  if(ctype == 'd')
  {
    data_obj = py::cast<Eigen::Matrix<double, 3, 3>>(Eigen::MatrixXd::Random(3,3));
  }
  else if(ctype == 'f')
  {
    data_obj = py::cast<Eigen::Matrix<float, 3, 3>>(Eigen::MatrixXf::Random(3,3));
  }
  return new_homography_from_matrix(data_obj, ctype);
}

}
}
}

using namespace kwiver::vital::python;
PYBIND11_MODULE(homography, m)
{
  py::class_<PyHomographyBase, std::shared_ptr<PyHomographyBase>>(m, "Homography")
  .def(py::init(&new_homography),
    py::arg("type")='d')
  .def_static("from_matrix", &new_homography_from_matrix,
    py::arg("data"), py::arg("type")='d')
  .def_static("random", &new_random_homography,
    py::arg("type")='d')
  .def_property_readonly("type_name", &PyHomographyBase::get_type)
  .def("as_matrix", &PyHomographyBase::get_matrix)
  .def("inverse", &PyHomographyBase::inverse)
  .def("map", &PyHomographyBase::map,
    py::arg("point"))
  .def("normalize", &PyHomographyBase::normalize)
  .def("__eq__", &PyHomographyBase::operator==)
  .def("__ne__", &PyHomographyBase::operator!=)
  .def("__mul__", &PyHomographyBase::operator*)
  ;
}