/*
 * Copyright [2019] [Christopher Syben]
 *
 * 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.
 *
 * Ray-driven cone-beam projector CUDA kernel using texture inteprolation
 * Implementation adapted from CONRAD
 * PYRO-NN is developed as an Open Source project under the Apache License, Version 2.0.
*/
#if GOOGLE_CUDA
#define EIGEN_USE_GPU
#include "../helper_headers/helper_grid.h"
#include "../helper_headers/helper_math.h"
#include "../helper_headers/helper_eigen.h"
#include "../helper_headers/helper_geometry_gpu.h"
#include <Eigen/QR>
texture<float, 3, cudaReadModeElementType> volume_as_texture;
#define CUDART_INF_F __int_as_float(0x7f800000)

#define BLOCKSIZE_X           16
#define BLOCKSIZE_Y           16

#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); }
inline void gpuAssert(cudaError_t code, const char *file, int line, bool abort=true)
{
   if (code != cudaSuccess)
   {
      fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
      exit(code);
   }
}

__device__ float kernel_project3D_tex_interp(const float3 source_point, const float3 ray_vector, const float step_size, const uint3 volume_size)
{
    float pixel = 0.0f;
    // Step 1: compute alpha value at entry and exit point of the volume
    float min_alpha, max_alpha;
    min_alpha = 0;
    max_alpha = CUDART_INF_F;

    if (0.0f != ray_vector.x)
    {
        float volume_min_edge_point = 0 - 0.5f;
        float volume_max_edge_point = volume_size.x - 0.5f;

        float reci = 1.0f / ray_vector.x;
        float alpha0 = (volume_min_edge_point - source_point.x) * reci;
        float alpha1 = (volume_max_edge_point - source_point.x) * reci;
        min_alpha = fmin(alpha0, alpha1);
        max_alpha = fmax(alpha0, alpha1);
    }

    if (0.0f != ray_vector.y)
    {
        float volume_min_edge_point = 0 - 0.5f;
        float volume_max_edge_point = volume_size.y - 0.5f;

        float reci = 1.0f / ray_vector.y;
        float alpha0 = (volume_min_edge_point - source_point.y) * reci;
        float alpha1 = (volume_max_edge_point - source_point.y) * reci;
        min_alpha = fmax(min_alpha, fmin(alpha0, alpha1));
        max_alpha = fmin(max_alpha, fmax(alpha0, alpha1));
    }

    if (0.0f != ray_vector.z)
    {
        float volume_min_edge_point = 0 - 0.5f;
        float volume_max_edge_point = volume_size.z - 0.5f;

        float reci = 1.0f / ray_vector.z;
        float alpha0 = (volume_min_edge_point - source_point.z) * reci;
        float alpha1 = (volume_max_edge_point - source_point.z) * reci;
        min_alpha = fmax(min_alpha, fmin(alpha0, alpha1));
        max_alpha = fmin(max_alpha, fmax(alpha0, alpha1));
    }
    // we start not at the exact entry point
    // => we can be sure to be inside the volume
    min_alpha += step_size * 0.5f;

    // Step 2: Cast ray if it intersects the volume
    // Trapezoidal rule (interpolating function = piecewise linear func)

    float px, py, pz;

    // Entrance boundary
    // For the initial interpolated value, only a half stepsize is
    //  considered in the computation.
    if (min_alpha < max_alpha)
    {
        px = source_point.x + min_alpha * ray_vector.x;
        py = source_point.y + min_alpha * ray_vector.y;
        pz = source_point.z + min_alpha * ray_vector.z;

        pixel += 0.5f * tex3D(volume_as_texture, px+0.5f, py+0.5f, pz+0.5f );

        min_alpha += step_size;
    }
    // Mid segments
    while (min_alpha < max_alpha)
    {
        px = source_point.x + min_alpha * ray_vector.x;
        py = source_point.y + min_alpha * ray_vector.y;
        pz = source_point.z + min_alpha * ray_vector.z;

        pixel += tex3D(volume_as_texture, px+0.5f, py+0.5f, pz+0.5f );

        min_alpha += step_size;
    }
    // Scaling by stepsize;
    pixel *= step_size;

    // Last segment of the line
    if (pixel > 0.0f)
    {
        pixel -= 0.5f * step_size * tex3D(volume_as_texture, px+0.5f, py+0.5f, pz+0.5f );

        min_alpha -= step_size;
        float last_step_size = max_alpha - min_alpha;

        pixel += 0.5f * last_step_size* tex3D(volume_as_texture, px+0.5f, py+0.5f, pz+0.5f );

        px = source_point.x + max_alpha * ray_vector.x;
        py = source_point.y + max_alpha * ray_vector.y;
        pz = source_point.z + max_alpha * ray_vector.z;

        // The last segment of the line integral takes care of the
        // varying length.
        pixel += 0.5f * last_step_size * tex3D(volume_as_texture, px+0.5f, py+0.5f, pz+0.5f);
    }
    return pixel;
}

__global__ void project_3Dcone_beam_kernel_tex_interp(float *pSinogram, const float *d_inv_AR_matrices, const float3 *d_src_points, const float *sampling_step_size,
                                          const uint3 volume_size, const float *volume_spacing_ptr,
                                          const uint2 detector_size, const int number_of_projections)
{
    uint2 detector_idx = make_uint2( blockIdx.x * blockDim.x + threadIdx.x,  blockIdx.y* blockDim.y + threadIdx.y  );
    uint projection_number = blockIdx.z;
    //Prep: Wrap pointer to float3 for better readable code
    float3 volume_spacing = make_float3(*(volume_spacing_ptr+2), *(volume_spacing_ptr+1), *volume_spacing_ptr);
    if (detector_idx.x >= detector_size.x || detector_idx.y >= detector_size.y || blockIdx.z >= number_of_projections)
    {
        return;
    }
    // //Preparations:
	d_inv_AR_matrices += projection_number * 9;
    float3 source_point = d_src_points[projection_number];

    //Compute ray direction
    const float rx = d_inv_AR_matrices[2] + detector_idx.y * d_inv_AR_matrices[1] + detector_idx.x * d_inv_AR_matrices[0];
    const float ry = d_inv_AR_matrices[5] + detector_idx.y * d_inv_AR_matrices[4] + detector_idx.x * d_inv_AR_matrices[3];
    const float rz = d_inv_AR_matrices[8] + detector_idx.y * d_inv_AR_matrices[7] + detector_idx.x * d_inv_AR_matrices[6];

    float3 ray_vector = make_float3(rx,ry,rz);
    ray_vector = normalize(ray_vector);

    float pixel = kernel_project3D_tex_interp(
        source_point,
        ray_vector,
        *sampling_step_size,
        volume_size);

    pixel *= sqrt((ray_vector.x * volume_spacing.x) * (ray_vector.x * volume_spacing.x) +
            (ray_vector.y * volume_spacing.y) * (ray_vector.y * volume_spacing.y) +
            (ray_vector.z * volume_spacing.z) * (ray_vector.z * volume_spacing.z));

    unsigned sinogram_idx = projection_number * detector_size.y * detector_size.x +  detector_idx.y * detector_size.x + detector_idx.x;

    pSinogram[sinogram_idx] = pixel;
    return;
}

/*************** WARNING ******************./
    *
    *   Tensorflow is allocating the whole GPU memory for itself and just leave a small slack memory
    *   using cudaMalloc and cudaMalloc3D will allocate memory in this small slack memory !
    *   Therefore, currently only small volumes can be used (they have to fit into the slack memory which TF does not allocae !)
    *
    *   This is the kernel based on texture interpolation, thus, the allocations are not within the Tensorflow managed memory.
    *   If memory errors occure:
    *    1. start Tensorflow with less gpu memory and allow growth
    *    2. switch to software-based interpolation.
    *
    *   TODO: use context->allocate_tmp and context->allocate_persistent instead of cudaMalloc for the inv_AR_matrix and src_points array
    *       : https://stackoverflow.com/questions/48580580/tensorflow-new-op-cuda-kernel-memory-managment
    *
    */
void Cone_Projection_Kernel_Tex_Interp_Launcher(const float* volume_ptr, float *out, const float *inv_AR_matrix,const float *src_points,
                                    const int number_of_projections, const int volume_width, const int volume_height, const int volume_depth,
                                    const float *volume_spacing, const int detector_width, const int detector_height,const float *step_size)
{
    cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc<float>();
    volume_as_texture.addressMode[0] = cudaAddressModeBorder;
    volume_as_texture.addressMode[1] = cudaAddressModeBorder;
    volume_as_texture.addressMode[2] = cudaAddressModeBorder;
    volume_as_texture.filterMode = cudaFilterModeLinear;
    volume_as_texture.normalized = false;

    // //COPY inv AR matrix to graphics card as float array
    auto matrices_size_b = number_of_projections * 9 * sizeof(float);
    float *d_inv_AR_matrices;
    gpuErrchk(cudaMalloc(&d_inv_AR_matrices, matrices_size_b));
    gpuErrchk(cudaMemcpy(d_inv_AR_matrices, inv_AR_matrix, matrices_size_b, cudaMemcpyHostToDevice));
    //COPY source points to graphics card as float3
    auto src_points_size_b = number_of_projections * sizeof(float3);
    float3 *d_src_points;
    gpuErrchk(cudaMalloc(&d_src_points, src_points_size_b));
    gpuErrchk(cudaMemcpy(d_src_points, src_points, src_points_size_b, cudaMemcpyHostToDevice));

    //COPY volume to graphics card
    //Malloc cuda array for texture
    cudaExtent volume_extent = make_cudaExtent(  volume_width, volume_height, volume_depth );
    cudaExtent volume_extent_byte = make_cudaExtent( sizeof(float)*volume_width, volume_height, volume_depth );

    cudaPitchedPtr d_volumeMem = make_cudaPitchedPtr( const_cast<float*>( volume_ptr ),
                                                volume_width*sizeof(float),
                                                volume_width,
                                                volume_height
                                            );

    cudaArray *volume_array;
    gpuErrchk(cudaMalloc3DArray(&volume_array, &channelDesc, volume_extent));

    cudaMemcpy3DParms copyParams = {0};
    copyParams.srcPtr = d_volumeMem;
    copyParams.dstArray = volume_array;
    copyParams.extent = volume_extent;
    copyParams.kind = cudaMemcpyDeviceToDevice;

    gpuErrchk(cudaMemcpy3D(&copyParams));

    gpuErrchk(cudaBindTextureToArray(volume_as_texture, volume_array, channelDesc))
    uint3 volume_size = make_uint3(volume_width, volume_height, volume_depth);
    uint2 detector_size = make_uint2(detector_width, detector_height);

    const dim3 blocksize = dim3( BLOCKSIZE_X, BLOCKSIZE_Y, 1 );
    const dim3 gridsize = dim3( detector_size.x / blocksize.x + 1, detector_size.y / blocksize.y + 1 , number_of_projections+1);

    project_3Dcone_beam_kernel_tex_interp<<<gridsize, blocksize>>>(out, d_inv_AR_matrices, d_src_points, step_size,
                                        volume_size, volume_spacing, detector_size, number_of_projections);

    cudaDeviceSynchronize();

    // check for errors
    gpuErrchk( cudaPeekAtLastError() );
    gpuErrchk(cudaFreeArray(volume_array));
    gpuErrchk(cudaUnbindTexture(volume_as_texture));
    gpuErrchk(cudaFree(d_inv_AR_matrices));
    gpuErrchk(cudaFree(d_src_points));
}

#endif


#pragma once
//===----------------------------------------------------------------------===//
#include <dtl/dtl.hpp>
#include <dtl/math.hpp>

#include <boost/dynamic_bitset.hpp>

#include <string>
#include <type_traits>
#include <vector>
//===----------------------------------------------------------------------===//
namespace dtl {
//===----------------------------------------------------------------------===//
/// Rank implementation that uses a precomputed lookup table (aka a dictionary).
template<typename _rank_logic>
struct rank1 {
  using rank_logic = _rank_logic;
  using word_type = typename std::remove_cv<typename rank_logic::word_type>::type;
  using size_type = typename rank_logic::size_type;

  /// Lookup table for the pre-computed rank values on block level.
  std::vector<size_type> lut;

  static constexpr u64 word_bitlength = sizeof(word_type) * 8;

  ~rank1() = default;

  /// Initializes the rank LuT based on the given bitmap. // TODO remove boost dependency
  void
  init(const boost::dynamic_bitset<word_type>& bitmap) noexcept {
    u64 bitmap_bitlength = bitmap.m_bits.size() * word_bitlength;
    u64 lut_entry_cnt = rank_logic::lut_entry_cnt(bitmap_bitlength);
    lut.resize(lut_entry_cnt, 0);
    rank_logic::init_inplace(
        bitmap.m_bits.data(),
        bitmap.m_bits.data() + bitmap.m_bits.size(),
        lut.data());
  }

  /// Initializes the rank LuT based on the given bitmap.
  void
  init(const word_type* const bitmap_begin,
      const word_type* const bitmap_end) noexcept {
    u64 bitmap_word_cnt = bitmap_end - bitmap_begin;
    u64 bitmap_bitlength = bitmap_word_cnt * word_bitlength;
    u64 lut_entry_cnt = rank_logic::lut_entry_cnt(bitmap_bitlength);
    lut.resize(lut_entry_cnt, 0);
    rank_logic::init_inplace(bitmap_begin, bitmap_end, lut.data());
  }

  /// Returns the size of the rank LuT in bytes for a bitmap of the given size.
  static constexpr u64
  estimate_size_in_bytes(u64 bitmap_size) noexcept {
    return rank_logic::estimate_size_in_bytes(bitmap_size);
  }

  /// Computes the rank1 of the bit at position 'idx'.
  size_type __forceinline__
  get(u64 idx, const word_type* bitmap_ptr) const noexcept {
    return rank_logic::get(lut.data(), idx, bitmap_ptr);
  }

  /// Computes the rank1 of the bit at position 'idx'.
  size_type __forceinline__
  operator()(u64 idx, const word_type* bitmap_ptr) const noexcept {
    return get(idx, bitmap_ptr);
  }

  /// Returns the size in bytes.
  u64
  size_in_bytes() const noexcept {
    return lut.size() * sizeof(size_type); // lut size
  }

  /// Returns the important properties in JSON.
  std::string
  info() const noexcept {
    return "{\"name\":"
        + std::string("\"") + rank_logic::name() + std::string("\"")
        + ",\"size\":" + std::to_string(size_in_bytes())
        + ",\"block_size\":" + std::to_string(rank_logic::block_bitlength / 8)
        + "}";
  }
  // TODO remove. rank_size should go 1 level up in the JSON
  std::string
  info(u64 bitmap_size) const noexcept {
    return "{\"name\":"
        + std::string("\"") + rank_logic::name() + std::string("\"")
        + ",\"size\":" + std::to_string(estimate_size_in_bytes(bitmap_size))
        + ",\"block_size\":" + std::to_string(rank_logic::block_bitlength / 8)
        + "}";
  }

  /// Returns true if the rank is inclusive, false otherwise.
  inline u1
  is_inclusive() noexcept {
    return rank_logic::is_inclusive;
  }
};
//===----------------------------------------------------------------------===//
} // namespace dtl


// Copyright (c) 2016 Graphcore Ltd. All rights reserved.

#ifndef poplibs_test_Convolution_hpp
#define poplibs_test_Convolution_hpp

#include <boost/multi_array.hpp>

namespace poplibs_test {
namespace conv {

void convolution(const std::vector<unsigned> &inputFieldSize,
                 const std::vector<unsigned> &truncationLower,
                 const std::vector<unsigned> &truncationUpper,
                 const std::vector<unsigned> &inputDilation,
                 const std::vector<unsigned> &paddingLower,
                 const std::vector<unsigned> &paddingUpper,
                 const std::vector<bool> &flipInput,
                 const std::vector<unsigned> &kernelSize,
                 const std::vector<unsigned> &kernelTruncationLower,
                 const std::vector<unsigned> &kernelTruncationUpper,
                 const std::vector<unsigned> &kernelDilation,
                 const std::vector<unsigned> &kernelPaddingLower,
                 const std::vector<unsigned> &kernelPaddingUpper,
                 const std::vector<bool> &flipKernel,
                 const std::vector<unsigned> &outputTruncationLower,
                 const std::vector<unsigned> &outputTruncationUpper,
                 const std::vector<unsigned> &stride,
                 const std::vector<unsigned> &outputPaddingLower,
                 const std::vector<unsigned> &outputPaddingUpper,
                 boost::const_multi_array_ref<double, 3> in,
                 boost::const_multi_array_ref<double, 4> weights,
                 boost::const_multi_array_ref<double, 1> biases,
                 boost::multi_array_ref<double, 3> out);

void convolutionBackward(const std::vector<unsigned> &inputFieldSize,
                         const std::vector<unsigned> &truncationLower,
                         const std::vector<unsigned> &truncationUpper,
                         const std::vector<unsigned> &inputDilation,
                         const std::vector<unsigned> &paddingLower,
                         const std::vector<unsigned> &paddingUpper,
                         const std::vector<bool> &flipInput,
                         const std::vector<unsigned> &kernelSize,
                         const std::vector<unsigned> &kernelTruncationLower,
                         const std::vector<unsigned> &kernelTruncationUpper,
                         const std::vector<unsigned> &kernelDilation,
                         const std::vector<unsigned> &kernelPaddingLower,
                         const std::vector<unsigned> &kernelPaddingUpper,
                         const std::vector<bool> &flipKernel,
                         const std::vector<unsigned> &outputTruncationLower,
                         const std::vector<unsigned> &outputTruncationUpper,
                         const std::vector<unsigned> &stride,
                         const std::vector<unsigned> &outputPaddingLower,
                         const std::vector<unsigned> &outputPaddingUpper,
                         boost::const_multi_array_ref<double, 3> in,
                         boost::const_multi_array_ref<double, 4> weights,
                         boost::multi_array_ref<double, 3> out);

void weightUpdate(const std::vector<unsigned> &inputFieldSize,
                  const std::vector<unsigned> &truncationLower,
                  const std::vector<unsigned> &truncationUpper,
                  const std::vector<unsigned> &inputDilation,
                  const std::vector<unsigned> &paddingLower,
                  const std::vector<unsigned> &paddingUpper,
                  const std::vector<bool> &flipInput,
                  const std::vector<unsigned> &kernelSize,
                  const std::vector<unsigned> &kernelTruncationLower,
                  const std::vector<unsigned> &kernelTruncationUpper,
                  const std::vector<unsigned> &kernelDilation,
                  const std::vector<unsigned> &kernelPaddingLower,
                  const std::vector<unsigned> &kernelPaddingUpper,
                  const std::vector<bool> &flipKernel,
                  const std::vector<unsigned> &outputTruncationLower,
                  const std::vector<unsigned> &outputTruncationUpper,
                  const std::vector<unsigned> &stride,
                  const std::vector<unsigned> &outputPaddingLower,
                  const std::vector<unsigned> &outputPaddingUpper,
                  double learningRate,
                  boost::const_multi_array_ref<double, 3> activations,
                  boost::const_multi_array_ref<double, 3> deltas,
                  boost::multi_array_ref<double, 4> weights,
                  boost::multi_array_ref<double, 1> biases);

} // namespace conv
} // namespace poplibs_test

#endif // poplibs_test_Convolution_hpp


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2014-2020, Oracle and/or its affiliates.
// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_GEOMETRY_TEST_SYM_DIFFERENCE_LINEAR_LINEAR_HPP
#define BOOST_GEOMETRY_TEST_SYM_DIFFERENCE_LINEAR_LINEAR_HPP

#include <limits>

#include <boost/range/value_type.hpp>

#include <boost/geometry/geometry.hpp>
#include "../test_set_ops_linear_linear.hpp"
#include <from_wkt.hpp>
#include <to_svg.hpp>

//==================================================================
//==================================================================
// symmetric difference of (linear) geometries
//==================================================================
//==================================================================

template <typename Geometry1, typename Geometry2, typename MultiLineString>
inline void check_result(Geometry1 const& geometry1,
                         Geometry2 const& geometry2,
                         MultiLineString const& mls_output,
                         MultiLineString const& mls_sym_diff,
                         std::string const& case_id,
                         double tolerance)
{
    BOOST_CHECK_MESSAGE( equals::apply(mls_sym_diff, mls_output, tolerance),
                         "case id: " << case_id
                         << ", sym diff L/L: " << bg::wkt(geometry1)
                         << " " << bg::wkt(geometry2)
                         << " -> Expected: " << bg::wkt(mls_sym_diff)
                         << " computed: " << bg::wkt(mls_output) );
}

template
<
    typename Geometry1, typename Geometry2,
    typename MultiLineString
>
class test_sym_difference_of_geometries
{
private:
    static inline void base_test(Geometry1 const& geometry1,
                                 Geometry2 const& geometry2,
                                 MultiLineString const& mls_sym_diff,
                                 std::string const& case_id,
                                 double tolerance,
                                 bool test_vector_and_deque = false)
    {
        static bool vector_deque_already_tested = false;

        typedef typename boost::range_value<MultiLineString>::type LineString;
        typedef std::vector<LineString> linestring_vector;
        typedef std::deque<LineString> linestring_deque;

        MultiLineString mls_output;

        linestring_vector ls_vector_output;
        linestring_deque ls_deque_output;

        // Check strategy passed explicitly
        typedef typename bg::strategy::relate::services::default_strategy
            <
                Geometry1, Geometry2
            >::type strategy_type;
        bg::sym_difference(geometry1, geometry2, mls_output, strategy_type());

        check_result(geometry1, geometry2, mls_output, mls_sym_diff, case_id, tolerance);

        // Check normal behaviour
        bg::clear(mls_output);
        bg::sym_difference(geometry1, geometry2, mls_output);

        check_result(geometry1, geometry2, mls_output, mls_sym_diff, case_id, tolerance);

        set_operation_output("sym_difference", case_id,
                             geometry1, geometry2, mls_output);

#ifdef BOOST_GEOMETRY_TEST_DEBUG
        std::cout << "Geometry #1: " << bg::wkt(geometry1) << std::endl;
        std::cout << "Geometry #2: " << bg::wkt(geometry2) << std::endl;
        std::cout << "sym_difference : " << bg::wkt(mls_output) << std::endl;
        std::cout << "expected sym_difference : " << bg::wkt(mls_sym_diff)
                  << std::endl;
        std::cout << std::endl;
        std::cout << "************************************" << std::endl;
        std::cout << std::endl;
        std::cout << std::endl;
#endif

        if ( !vector_deque_already_tested && test_vector_and_deque )
        {
            vector_deque_already_tested = true;
#ifdef BOOST_GEOMETRY_TEST_DEBUG
            std::cout << std::endl;
            std::cout << "Testing with vector and deque as output container..."
                      << std::endl;
#endif
            bg::sym_difference(geometry1, geometry2, ls_vector_output);
            bg::sym_difference(geometry1, geometry2, ls_deque_output);

            BOOST_CHECK(multilinestring_equals
                        <
                            false
                        >::apply(mls_sym_diff, ls_vector_output, tolerance));

            BOOST_CHECK(multilinestring_equals
                        <
                            false
                        >::apply(mls_sym_diff, ls_deque_output, tolerance));

#ifdef BOOST_GEOMETRY_TEST_DEBUG
            std::cout << "Done!" << std::endl << std::endl;
#endif
        }

        // check the symmetric difference where the order of the two
        // geometries is reversed
        bg::clear(mls_output);
        bg::sym_difference(geometry2, geometry1, mls_output);

        BOOST_CHECK_MESSAGE( equals::apply(mls_sym_diff, mls_output, tolerance),
                             "case id: " << case_id
                             << ", sym diff L/L: " << bg::wkt(geometry2)
                             << " " << bg::wkt(geometry1)
                             << " -> Expected: " << bg::wkt(mls_sym_diff)
                             << " computed: " << bg::wkt(mls_output) );

#ifdef BOOST_GEOMETRY_TEST_DEBUG
        std::cout << "Geometry #1: " << bg::wkt(geometry2) << std::endl;
        std::cout << "Geometry #2: " << bg::wkt(geometry1) << std::endl;
        std::cout << "sym_difference : " << bg::wkt(mls_output) << std::endl;
        std::cout << "expected sym_difference : " << bg::wkt(mls_sym_diff)
                  << std::endl;
        std::cout << std::endl;
        std::cout << "************************************" << std::endl;
        std::cout << std::endl;
        std::cout << std::endl;
#endif
    }

public:
    static inline void apply(Geometry1 const& geometry1,
                             Geometry2 const& geometry2,
                             MultiLineString const& mls_sym_diff,
                             std::string const& case_id,
                             double tolerance
                                 = std::numeric_limits<double>::epsilon())
    {
#ifdef BOOST_GEOMETRY_TEST_DEBUG
        std::cout << "test case: " << case_id << std::endl;
        std::stringstream sstr;
        sstr << "svgs/" << case_id << ".svg";
#ifdef TEST_WITH_SVG
        to_svg(geometry1, geometry2, sstr.str());
#endif
#endif

        Geometry1 rg1(geometry1);
        bg::reverse<Geometry1>(rg1);

        Geometry2 rg2(geometry2);
        bg::reverse<Geometry2>(rg2);

        test_get_turns_ll_invariance<>::apply(geometry1, geometry2);
#ifdef BOOST_GEOMETRY_TEST_DEBUG
        std::cout << std::endl
                  << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"
                  << std::endl << std::endl;
#endif
        test_get_turns_ll_invariance<>::apply(rg1, geometry2);

        base_test(geometry1, geometry2, mls_sym_diff, case_id, tolerance, true);
        //        base_test(geometry1, rg2, mls_sym_diff);
        //        base_test(rg1, geometry2, mls_sym_diff);
        base_test(rg1, rg2, mls_sym_diff, case_id, tolerance);

#ifdef BOOST_GEOMETRY_TEST_DEBUG
        std::cout << std::endl;
        std::cout << std::endl;
#endif
    }
};

#endif // BOOST_GEOMETRY_TEST_SYM_DIFFERENCE_LINEAR_LINEAR_HPP


/*
 * Copyright (C) 2008-2011, Gostai S.A.S.
 *
 * This software is provided "as is" without warranty of any kind,
 * either expressed or implied, including but not limited to the
 * implied warranties of fitness for a particular purpose.
 *
 * See the LICENSE file for more information.
 */

#include <iostream>
#include <boost/lexical_cast.hpp>
#include <boost/mpl/integral_c.hpp>
#include <boost/numeric/conversion/converter.hpp>

#include <libport/cmath>
#include <libport/cstdlib>
#include <libport/ufloat.hh>

#ifdef LIBPORT_URBI_UFLOAT_LONG_LONG
# include <libport/ull-fixed-point.cc>
#endif

#ifdef LIBPORT_URBI_UFLOAT_FLOATING
# include <libport/uffloat.cc>
#endif

namespace libport
{
#ifdef LIBPORT_URBI_UFLOAT_TABULATED

# ifndef SINTABLE_POWER
  // The tables will containe 2^sintable_power elements.
#  define SINTABLE_POWER 10
# endif

  void buildSinusTable(int powersize);
  class DummyInit
  {
  public:
    DummyInit()
    {
      buildSinusTable(SINTABLE_POWER);
    }
  };

  DummyInit _dummyInit__;

  static ufloat* sinTable=0; //we store [O, PI/2[
  static ufloat* asinTable=0; //we store [O, 1[

  static unsigned long tableSize; //must be a power of two
  static int tableShift;

  void buildSinusTable(int powersize)
  {
    size_t size = 1<<powersize;
    tableShift = powersize;
    //don't use a step-based generation or errors will accumulate
    delete [] sinTable;
    delete [] asinTable;

    sinTable = new ufloat[size];
    asinTable = new ufloat[size];

    tableSize = size;
    for (size_t i=0;i<size;i++)
    {
      double idx = (double)i*(M_PI/2.0)/(double)size;
      float val = ::sin(idx);
      sinTable[i]=val;

      double idx2 = (double)i/(double)size;
      asinTable[i] = ::asin(idx2);
    }
  }

#endif

#if defined LIBPORT_URBI_UFLOAT_TABULATED \
 && !defined LIBPORT_URBI_UFLOAT_FLOATING

  ufloat tabulatedSin(ufloat val)
  {
    ufloat fidx = (val*(ufloat)tableSize / (M_PI/2.0));
    int idx = (int) fidx;
    ufloat rem = fidx -(ufloat)idx;

    idx = idx & (tableSize-1);

    if (fmod(val, M_PI) >= M_PI/2)
      idx = (tableSize-idx-1); //sin(pi/2+x) = sin(pi/2-x)
    ufloat interp = sinTable(idx)*(1.0-rem)+sinTable[(idx+1)%tableSize]*rem;

    return fmod(val, M_PI*2) > M_PI ? -interp : interp;
  }

  ufloat tabulatedCos(ufloat val)
  {
    ufloat fidx = (val*(ufloat)tableSize / (M_PI/2.0));
    int idx = (int) fidx;
    ufloat rem = fidx -(ufloat)idx;

    idx = idx & (tableSize-1);

    if (fmod(val, M_PI) < M_PI/2)
      idx = (tableSize-idx-1); //sin(pi/2+x) = sin(pi/2-x)

    ufloat interp = sinTable(idx)*(1.0-rem)+sinTable[(idx+1)%tableSize]*rem;

    return fmod(val, M_PI*2) > M_PI ? -interp : interp;
  }

  ufloat tabulatedASin(ufloat val)
  {
    ufloat fidx = val *(ufloat)tableSize;
    int idx =(int) fidx;
    ufloat rem = fidx -(ufloat)idx;
    idx = idx & (tableSize-1);
    ufloat interp = asinTable(idx)*(1.0-rem)+asinTable[(idx+1)%tableSize]*rem;

    return val < 0.0 ? -interp : interp;
  }

#endif

  /*------------------------.
  | From string to ufloat.  |
  `------------------------*/

  // MSVC makes it uselessly complex to pass isdigit-like functions by
  // pointer.
  /// \param xdigit  whether hexadecimal instead of decimal.
  static inline
  bool
  is_digit(int c, bool xdigit)
  {
    return xdigit ? isxdigit(c) : isdigit(c);
  }

  /// Remove underscores, allowed only between two digits.
  ///
  /// \param xdigit  whether hexadecimal instead of decimal.
  static
  std::string
  as_ufloat_strip(const std::string& s, bool xdigit)
  {
    std::string res;
    size_t len = s.size();
    for (size_t i = 0; i < len; /* nothing */)
      if (s[i] == '_')
      {
        // Look for the next non-underscore character.
        size_t next = s.find_first_not_of("_", i);
        if (res.empty()
            || !is_digit(res[res.size() - 1], xdigit)
            || !(next < len)
            || !is_digit(s[next], xdigit))
          throw boost::bad_lexical_cast();
        i = next;
      }
      else
      {
        res.append(1, s[i]);
        ++i;
      }
    return res;
  }

  ufloat
  as_ufloat(const std::string& s)
  {
    // Convert.
    if (s.substr(0, 2) == "0x")
    {
      std::string pure = as_ufloat_strip(s, true);
      char* end = 0;
      ufloat res = strtoll(pure.c_str(), &end, 0);
      // Refuse garbage after.
      if (end && *end)
        throw boost::bad_lexical_cast();
      return res;
    }
    else
      return boost::lexical_cast<libport::ufloat>
        (as_ufloat_strip(s, false));
  }

  /*------------------.
  | cast exceptions.  |
  `------------------*/

  const char *bad_numeric_cast::what() const throw()
  {
    return "bad numeric conversion: overflow or non empty fractional part";
  }

  const char *negative_overflow::what() const throw()
  {
    return "bad numeric conversion: negative overflow";
  }

  const char *positive_overflow::what() const throw()
  {
    return "bad numeric conversion: positive overflow";
  }

  /*-------------------.
  | ufloat converter.  |
  `-------------------*/

  template<typename S>
  struct ExactFloat2IntRounderPolicy
  {
    typedef S source_type;
    typedef S argument_type;

    static source_type nearbyint(argument_type s)
    {
      if (s != ceil(s))
	throw boost::numeric::bad_numeric_cast();
      return s;
    }

    typedef boost::mpl::integral_c<std::float_round_style,std::round_to_nearest>
      round_style;
  };

  /*-------------------.
  | numeric_castable.  |
  `-------------------*/

  template <typename T>
  bool
  numeric_castable(ufloat val)
  {
    double int_part;
    if (modf(val, &int_part) != 0)
      return false;

    static boost::numeric::converter
      <T,
      ufloat,
      boost::numeric::conversion_traits<T, ufloat>,
      boost::numeric::def_overflow_handler,
      ExactFloat2IntRounderPolicy<ufloat> > converter;

    return converter.out_of_range(val) == boost::numeric::cInRange;
  }

# define UFLOAT_CAST(Type)                                      \
  template                                                      \
  bool numeric_castable<Type>(ufloat v);

  // Instantiate.
  UFLOAT_CASTS

#undef UFLOAT_CAST

  /*---------------.
  | numeric_cast.  |
  `---------------*/

  template <typename T>
  T
  numeric_cast(ufloat val) throw (bad_numeric_cast)
  {
    try
    {
      static boost::numeric::converter
        <T,
        ufloat,
        boost::numeric::conversion_traits<T, ufloat>,
        boost::numeric::def_overflow_handler,
        ExactFloat2IntRounderPolicy<ufloat> > converter;

      return converter(val);
    }
#define RETHROW(Name)                           \
    catch (boost::numeric::Name&)               \
    {                                           \
      throw Name();                             \
    }
    RETHROW(negative_overflow)
    RETHROW(positive_overflow)
    RETHROW(bad_numeric_cast)
#undef RETHROW
  }

  /*----------------.
  | rounding_cast.  |
  `----------------*/

  template <typename T>
  T
  rounding_cast(ufloat val) throw (bad_numeric_cast)
  {
    try
    {
      static boost::numeric::converter
        <T,
        ufloat,
        boost::numeric::conversion_traits<T, ufloat>,
        boost::numeric::def_overflow_handler,
        boost::numeric::RoundEven<ufloat> > converter;

      return converter(val);
    }
#define RETHROW(Name)                           \
    catch (boost::numeric::Name&)               \
    {                                           \
      throw Name();                             \
    }
    RETHROW(negative_overflow)
    RETHROW(positive_overflow)
    RETHROW(bad_numeric_cast)
#undef RETHROW
  }

# define UFLOAT_CAST(Type)                                      \
  template                                                      \
  Type                                                          \
  numeric_cast<Type>(ufloat v) throw (bad_numeric_cast);        \
                                                                \
  template                                                      \
  Type                                                          \
  rounding_cast<Type>(ufloat v) throw (bad_numeric_cast);

  // Instantiate.
  UFLOAT_CASTS

#undef UFLOAT_CAST

} // namespace libport


/*******************************************************************************
 * Copyright 2013-2014 Sebastian Niemann <niemann@sra.uni-hannover.de>.
 *
 * Licensed under the MIT License (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://opensource.org/licenses/MIT
 *
 * Developers:
 *   Sebastian Niemann - Lead developer
 *   Daniel Kiechle - Unit testing
 ******************************************************************************/
#pragma once

#include <string>
using std::string;

#include <utility>
using std::pair;

#include <vector>
using std::vector;

#include <InputClass.hpp>
using armadilloJava::InputClass;

#include <armadillo>
using arma::Mat;

namespace armadilloJava {
  class Input {
    public:
      static vector<vector<pair<string, void*>>> getTestParameters(const vector<InputClass>& testClasses);

    protected:
      static vector<vector<pair<string, void*>>> cartesianProduct(const vector<vector<pair<string, void*>>>& inputs);
      static vector<pair<string, void*>> vectorUnion(const vector<vector<pair<string, void*>>>& inputs);

      static Mat<double> getHilbertMatrix(int n_rows, int n_cols);
      static Mat<double> getKMSMatrix(int n_rows, int n_cols);

      static vector<pair<string, void*>> getElemInd();
      static vector<pair<string, void*>> getExtElemInd();
      static vector<pair<string, void*>> getColInd();
      static vector<pair<string, void*>> getExtColInd();
      static vector<pair<string, void*>> getRowInd();
      static vector<pair<string, void*>> getExtRowInd();
      static vector<pair<string, void*>> getNumElems();
      static vector<pair<string, void*>> getNumCols();
      static vector<pair<string, void*>> getNumRows();
      static vector<pair<string, void*>> getNormal();
      static vector<pair<string, void*>> getDim();
      static vector<pair<string, void*>> getExp();
      static vector<pair<string, void*>> getMatNormInt();
      static vector<pair<string, void*>> getVecNormInt();
      static vector<pair<string, void*>> getGenDouble();
      static vector<pair<string, void*>> getSinValTol();
      static vector<pair<string, void*>> getElemIndRange();
      static vector<pair<string, void*>> getColIndRange();
      static vector<pair<string, void*>> getRowIndRange();
      static vector<pair<string, void*>> getMatSize();
      static vector<pair<string, void*>> getGenMat();
      static vector<pair<string, void*>> getSquMat();
      static vector<pair<string, void*>> getInvMat();
      static vector<pair<string, void*>> getSymMat();
      static vector<pair<string, void*>> getSymPDMat();
      static vector<pair<string, void*>> getLogicMat();
      static vector<pair<string, void*>> getOOMat();
      static vector<pair<string, void*>> getGenColVec();
      static vector<pair<string, void*>> getMonColVec();
      static vector<pair<string, void*>> getLogicColVec();
      static vector<pair<string, void*>> getOOColVec();
      static vector<pair<string, void*>> getGenRowVec();
      static vector<pair<string, void*>> getMonRowVec();
      static vector<pair<string, void*>> getLogicRowVec();
      static vector<pair<string, void*>> getOORowVec();
      static vector<pair<string, void*>> getElemInds();
      static vector<pair<string, void*>> getColInds();
      static vector<pair<string, void*>> getRowInds();
      static vector<pair<string, void*>> getText();
      static vector<pair<string, void*>> getMatNormString();
      static vector<pair<string, void*>> getVecNormString();
      static vector<pair<string, void*>> getSort();
      static vector<pair<string, void*>> getSearch();
      static vector<pair<string, void*>> getSinValSel();
      static vector<pair<string, void*>> getDistrParam();
      static vector<pair<string, void*>> getFill();
      static vector<pair<string, void*>> getRandom();
  };
}


/* ----------------------------------------------------------------------------
 * Copyright 2021, Jesus Tordesillas Torres, Aerospace Controls Laboratory
 * Massachusetts Institute of Technology
 * All Rights Reserved
 * Authors: Jesus Tordesillas, et al.
 * See LICENSE file for the license information
 * -------------------------------------------------------------------------- */

#pragma once
#ifndef OCTOPUS_SEARCH_HPP
#define OCTOPUS_SEARCH_HPP

#include <vector>
#include <Eigen/Dense>
#include "panther_types.hpp"

#include "separator.hpp"
#include "cgal_utils.hpp"

#include <unordered_map>
#include <queue>
#include <tuple>

//#include "solvers/cvxgen/solver_cvxgen.hpp"

typedef struct Node Node;  // needed to be able to have a pointer inside the struct

struct Node
{
  Eigen::Vector3d qi;
  Node* previous = NULL;
  double g = 0;
  double h = 0;
  int index = 2;  // Start with q2_
  int idx;
  int idy;
  int idz;
};

// Taken from https://wjngkoh.wordpress.com/2015/03/04/c-hash-function-for-eigen-matrix-and-vector/
template <typename T>
struct matrix_hash : std::unary_function<T, size_t>
{
  std::size_t operator()(T const& matrix) const
  {
    // Note that it is obvious to the storage order of Eigen matrix (column- or
    // row-major). It will give you the same hash value for two different matrices if they
    // are the transpose of each other in different storage order.
    size_t seed = 0;
    for (size_t i = 0; i < matrix.size(); ++i)
    {
      auto elem = *(matrix.data() + i);
      seed ^= std::hash<typename T::Scalar>()(elem) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    }
    return seed;
  }
};

class OctopusSearch
{
public:
  OctopusSearch(std::string basis, int num_seg, int deg_pos, double alpha_shrink);
  void setUp(double t_min, double t_max, const ConvexHullsOfCurves_Std& hulls);
  ~OctopusSearch();

  void setMaxValuesAndSamples(Eigen::Vector3d& v_max, Eigen::Vector3d& a_max, int num_samples_x, int num_samples_y,
                              int num_samples_z, double fraction_voxel_size);
  void setq0q1q2(Eigen::Vector3d& q0, Eigen::Vector3d& q1, Eigen::Vector3d& q2);
  void setGoal(Eigen::Vector3d& goal);
  void setSamples(int num_samples_x, int num_samples_y, int num_samples_z);

  void setRunTime(double max_runtime);
  void setGoalSize(double goal_size);

  void setBasisUsedForCollision(int basis);

  void setBias(double bias);

  bool run(std::vector<Eigen::Vector3d>& result, std::vector<Eigen::Vector3d>& n, std::vector<double>& d);

  void recoverPath(Node* node1_ptr);

  void getAllTrajsFound(std::vector<mt::trajectory>& all_trajs_found);

  void computeInverses();

  void setBBoxSearch(double x, double y, double z);

  void setVisual(bool visual);

  void setXYZMinMaxAndRa(double x_min, double x_max, double y_min, double y_max, double z_min, double z_max, double Ra);

  int getNumOfLPsRun();
  double getMeanTimeSolveLPMs();
  void resetNumLPsAndTime();

  void getBestTrajFound(mt::trajectory& best_traj_found, mt::PieceWisePol& pwp, double dc);
  void getEdgesConvexHulls(mt::Edges& edges_convex_hulls);

  int B_SPLINE = 1;  // B-Spline Basis
  int MINVO = 2;     // Minimum volume basis
  int BEZIER = 3;    // Bezier basis

  struct CompareCost
  {
    double bias;
    bool operator()(const Node& left, const Node& right)
    {
      double cost_left = left.g + bias * left.h;
      double cost_right = right.g + bias * right.h;
      if (fabs(cost_left - cost_right) < 1e-5)
      {
        return left.h > right.h;  // If two costs are ~the same, decide only upon heuristic
      }
      else
      {
        return cost_left > cost_right;
      }
    }
  };

  bool collidesWithObstaclesGivenVertexes(const Eigen::Matrix<double, 3, 4>& last4Cps, int index_lastCP);
  bool collidesWithObstacles(Node& current);
  double getCost();

protected:
private:
  // void computeAxisForNextInterval(const int i, const Eigen::Vector3d& viM1, int axis, double& constraint_L,
  //                                 double& constraint_U);

  Eigen::Matrix<double, 3, 4> transformBSpline2otherBasis(const Eigen::Matrix<double, 3, 4>& Qbs, int interval);
  Eigen::Matrix<double, 3, 4> transformOtherBasis2BSpline(const Eigen::Matrix<double, 3, 4>& Qmv, int interval);

  void computeLimitsVoxelSize(double& min_voxel_size, double& max_voxel_size);
  bool computeUpperAndLowerConstraints(const int i, const Eigen::Vector3d& qiM2, const Eigen::Vector3d& qiM1,
                                       const Eigen::Vector3d& qi, double& constraint_xL, double& constraint_xU,
                                       double& constraint_yL, double& constraint_yU, double& constraint_zL,
                                       double& constraint_zU);

  void plotExpandedNodesAndResult(std::vector<Node>& expanded_nodes, Node* result_ptr);
  void expandAndAddToQueue(Node& current, double constraint_xL, double constraint_xU, double constraint_yL,
                           double constraint_yU, double constraint_zL, double constraint_zU);
  void printPath(Node& node1);
  double h(Node& node);
  double g(Node& node);
  double weightEdge(Node& node1, Node& node2);

  bool checkFeasAndFillND(std::vector<Eigen::Vector3d>& result, std::vector<Eigen::Vector3d>& n,
                          std::vector<double>& d);

  // bias should be >=1.0
  double bias_;  // page 34 of https://www.cs.cmu.edu/~motionplanning/lecture/Asearch_v8.pdf

  int basis_ = B_SPLINE;

  Eigen::Vector3d goal_;
  Eigen::Vector3d v_max_;
  Eigen::Vector3d a_max_;

  ConvexHullsOfCurves_Std hulls_;
  separator::Separator* separator_solver_;

  int num_samples_x_ = 3;
  int num_samples_y_ = 3;
  int num_samples_z_ = 3;

  std::vector<int> indexes_samples_x_;
  std::vector<int> indexes_samples_y_;
  std::vector<int> indexes_samples_z_;

  std::vector<std::tuple<int, int, int>> all_combinations_;

  int p_;
  int N_;
  int M_;
  int num_of_obst_;
  int num_of_segments_;
  int num_of_normals_;

  Eigen::Vector3d orig_;  // origin of the search box (left bottom corner)

  Eigen::Vector3d q0_;
  Eigen::Vector3d q1_;
  Eigen::Vector3d q2_;

  Eigen::RowVectorXd knots_;

  // stores the closest node found
  double closest_dist_so_far_ = std::numeric_limits<double>::max();
  Node* closest_result_so_far_ptr_ = NULL;

  // stores the closest node found that has full length (i.e. its index == (N_ - 2) )
  double complete_closest_dist_so_far_ = std::numeric_limits<double>::max();
  Node* complete_closest_result_so_far_ptr_ = NULL;

  double goal_size_ = 0.5;    //[m]
  double max_runtime_ = 0.5;  //[s]

  std::vector<Eigen::MatrixXd> Ainverses_;

  double time_solving_lps_ = 0.0;
  double time_expanding_ = 0.0;

  double voxel_size_;

  double bbox_x_;
  double bbox_y_;
  double bbox_z_;

  bool visual_ = false;

  double x_min_ = -std::numeric_limits<double>::max();
  double x_max_ = std::numeric_limits<double>::max();

  double y_min_ = -std::numeric_limits<double>::max();
  double y_max_ = std::numeric_limits<double>::max();

  double z_min_ = -std::numeric_limits<double>::max();
  double z_max_ = std::numeric_limits<double>::max();

  // transformation between the B-spline control points and other basis (MINVO or Bezier)
  std::vector<Eigen::Matrix<double, 4, 4>> M_pos_bs2basis_;
  std::vector<Eigen::Matrix<double, 3, 3>> M_vel_bs2basis_;
  std::vector<Eigen::Matrix<double, 4, 4>> M_pos_bs2basis_inverse_;  // Mbs2basis_

  int num_seg_;

  std::vector<Eigen::Vector3d> result_;

  std::vector<Node> expanded_valid_nodes_;

  std::unordered_map<Eigen::Vector3i, bool, matrix_hash<Eigen::Vector3i>> map_open_list_;

  std::priority_queue<Node, std::vector<Node>, CompareCost> openList_;  //= OpenSet, = Q

  double Ra_ = 1e10;

  // SolverCvxgen cvxgen_solver_;

  double alpha_shrink_;
};

#endif


#include <iostream>
#include <cmath>
#include <algorithm>
#include <vector>
#include <boost/multiprecision/cpp_int.hpp>
using namespace boost::multiprecision;
using namespace std;
int main() {
    long long int n; cin >> n;
    vector<long long int> a(5, 0);
    for (int i = 0; i < 5; i++) cin >> a[i];
    sort(a.begin(), a.end());
    if (n % a[0] == 0) n = n / a[0];
    else n = (n / a[0]) + 1;
    cout << n + 4 << endl;
}


#ifndef __SIMPLIFY_HPP_DEFINED__
#define __SIMPLIFY_HPP_DEFINED__

#include <boost/logic/tribool.hpp>
#include <ilasynth/ast.hpp>
#include <ilasynth/rewriter.hpp>
#include <ilasynth/smt.hpp>
#include <stack>

namespace ilasynth {
class ITESimplifier {
  z3::context ctx;
  z3::solver S;
  Z3ExprAdapter adapter;

  rwmap_t rwmap;

  void _add(const nptr_t& a, bool negate = false);
  boost::logic::tribool _isConstant(const nptr_t& c);

  void getNewArgs(const Node* op, nptr_vec_t& args);
  nptr_t getRepl(const Node* n) const;
  nptr_t rewriteITE(const Node* n);
  void dfs(const Node* n);

public:
  // constructor.
  ITESimplifier(const nptr_t& assump);
  // destructor.
  virtual ~ITESimplifier();
  // add a vector of assumption.
  void addAssumptions(const nptr_vec_t& assumps);
  // add exactly one assumption.
  void addAssumption(const nptr_t& a);
  // create clone
  void createClone(const Node* n);

  void reset() { rwmap.clear(); }
  nptr_t simplify(Node* n);
};
} // namespace ilasynth
#endif


//  (C) Copyright Nick Thompson and Matt Borland 2020.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <random>
#include <boost/math/statistics/univariate_statistics.hpp>
#include <benchmark/benchmark.h>

template <class Z>
void test_mode(benchmark::State& state)
{
    using boost::math::statistics::sorted_mode;

    std::random_device rd;
    std::mt19937_64 mt(rd());
    std::uniform_int_distribution<> dist {1, 10};

    auto gen = [&dist, &mt](){return dist(mt);};

    std::vector<Z> v(state.range(0));
    std::generate(v.begin(), v.end(), gen);

    for (auto _ : state)
    {
        std::vector<Z> modes;
        benchmark::DoNotOptimize(sorted_mode(v.begin(), v.end(), std::back_inserter(modes)));
    }

    state.SetComplexityN(state.range(0));
}

template <class Z>
void sequential_test_mode(benchmark::State& state)
{
    using boost::math::statistics::sorted_mode;

    std::vector<Z> v(state.range(0));

    size_t current_num {1};
    // produces {1, 2, 3, 4, 5...}
    for(size_t i {}; i < v.size(); ++i)
    {
        v[i] = current_num;
        ++current_num;
    }

    for (auto _ : state)
    {
        std::vector<Z> modes;
        benchmark::DoNotOptimize(sorted_mode(v, std::back_inserter(modes)));
    }

    state.SetComplexityN(state.range(0));
}

template <class Z>
void sequential_pairs_test_mode(benchmark::State& state)
{
    using boost::math::statistics::sorted_mode;

    std::vector<Z> v(state.range(0));

    size_t current_num {1};
    size_t current_num_counter {};
    // produces {1, 1, 2, 2, 3, 3, ...}
    for(size_t i {}; i < v.size(); ++i)
    {
        v[i] = current_num;
        ++current_num_counter;
        if(current_num_counter > 2)
        {
            ++current_num;
            current_num_counter = 0;
        }
    }

    for (auto _ : state)
    {
        std::vector<Z> modes;
        benchmark::DoNotOptimize(sorted_mode(v, std::back_inserter(modes)));
    }

    state.SetComplexityN(state.range(0));
}

template <class Z>
void sequential_multiple_test_mode(benchmark::State& state)
{
    using boost::math::statistics::sorted_mode;

    std::vector<Z> v(state.range(0));

    size_t current_num {1};
    size_t current_num_counter {};
    // produces {1, 2, 2, 3, 3, 3, 4, 4, 4, 4, ...}
    for(size_t i {}; i < v.size(); ++i)
    {
        v[i] = current_num;
        ++current_num_counter;
        if(current_num_counter > current_num)
        {
            ++current_num;
            current_num_counter = 0;
        }
    }

    for (auto _ : state)
    {
        std::vector<Z> modes;
        benchmark::DoNotOptimize(sorted_mode(v, std::back_inserter(modes)));
    }

    state.SetComplexityN(state.range(0));
}

BENCHMARK_TEMPLATE(test_mode, int32_t)->RangeMultiplier(2)->Range(1<<1, 1<<22)->Complexity();
BENCHMARK_TEMPLATE(test_mode, int64_t)->RangeMultiplier(2)->Range(1<<1, 1<<22)->Complexity();
BENCHMARK_TEMPLATE(test_mode, uint32_t)->RangeMultiplier(2)->Range(1<<1, 1<<22)->Complexity();
BENCHMARK_TEMPLATE(sequential_test_mode, int32_t)->RangeMultiplier(2)->Range(1<<1, 1<<22)->Complexity();
BENCHMARK_TEMPLATE(sequential_test_mode, int64_t)->RangeMultiplier(2)->Range(1<<1, 1<<22)->Complexity();
BENCHMARK_TEMPLATE(sequential_test_mode, uint32_t)->RangeMultiplier(2)->Range(1<<1, 1<<22)->Complexity();
BENCHMARK_TEMPLATE(sequential_pairs_test_mode, int32_t)->RangeMultiplier(2)->Range(1<<1, 1<<22)->Complexity();
BENCHMARK_TEMPLATE(sequential_pairs_test_mode, int64_t)->RangeMultiplier(2)->Range(1<<1, 1<<22)->Complexity();
BENCHMARK_TEMPLATE(sequential_pairs_test_mode, uint32_t)->RangeMultiplier(2)->Range(1<<1, 1<<22)->Complexity();
BENCHMARK_TEMPLATE(sequential_multiple_test_mode, int32_t)->RangeMultiplier(2)->Range(1<<1, 1<<22)->Complexity();
BENCHMARK_TEMPLATE(sequential_multiple_test_mode, int64_t)->RangeMultiplier(2)->Range(1<<1, 1<<22)->Complexity();
BENCHMARK_TEMPLATE(sequential_multiple_test_mode, uint32_t)->RangeMultiplier(2)->Range(1<<1, 1<<22)->Complexity();

BENCHMARK_MAIN();


//==============================================================================
//         Copyright 2009 - 2013 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//         Copyright 2012 - 2014 MetaScale SAS
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================

#include <boost/simd/sdk/simd/pack.hpp>
#include <boost/simd/include/functions/aligned_load.hpp>
#include <boost/simd/include/functions/multiplies.hpp>
#include <boost/simd/include/functions/plus.hpp>
#include <boost/simd/include/functions/sum.hpp>
#include <boost/simd/include/constants/zero.hpp>
#include <boost/simd/memory/allocator.hpp>
#include <vector>

#include <nt2/sdk/bench/benchmark.hpp>
#include <nt2/sdk/bench/metric/gflops.hpp>
#include <nt2/sdk/bench/protocol/max_duration.hpp>
#include <nt2/sdk/bench/setup/geometric.hpp>
#include <nt2/sdk/bench/stats/median.hpp>

using namespace nt2::bench;
using namespace nt2;

template <class A0>
BOOST_FORCEINLINE A0 Tdot_work(A0 const& X, A0 const& Y)
{
  return(X*Y);
}

template<typename T> struct dot_simd
{
    typedef void experiment_is_immutable;
  dot_simd(std::size_t n)
                  :  size_(n)
  {
    X.resize(size_); Y.resize(size_);
    for(std::size_t i = 0; i<size_; ++i)
      X[i] = Y[i] = T(i);
  }

  void operator()()
  {
    using boost::simd::pack;
    using boost::simd::aligned_load;

    typedef pack<T> type;
    std::size_t step_size = boost::simd::meta::cardinal_of<type>::value;
    type res_pack = boost::simd::Zero<type>();
    for (std::size_t i = 0; i<size_; i+=step_size)
    {
      type X_pack = aligned_load<type>(&X[i]);
      type Y_pack = aligned_load<type>(&Y[i]);
      res_pack += X_pack * Y_pack;
    }
    res_ = boost::simd::sum(res_pack);
  }

  friend std::ostream& operator<<(std::ostream& os, dot_simd<T> const& p)
  {
    return os << "(" << p.size() << ")";
  }

  std::size_t size() const { return size_; }
  double flops() const { return 2.*size_; }

private:
  T res_;
  std::size_t size_;
  typename std::vector<T, boost::simd::allocator<T> > X, Y;
};

NT2_REGISTER_BENCHMARK_TPL( dot_simd, NT2_SIMD_REAL_TYPES )
{
  std::size_t size_min  = args("size_min",   16*16);
  std::size_t size_max  = args("size_max", 4096*4096);
  std::size_t size_step = args("size_step",   4*4);

  run_during_with< dot_simd<T> > ( 1.
                                  , geometric(size_min,size_max,size_step)
                                  , gflops<stats::median_>()
                                  );
}


#include <complex>
#include <exception>
#include <fstream>
#include <sstream>
#include <iostream>
#include <string>
#include <vector>
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/home/x3.hpp>
#include <range/v3/all.hpp>
#include <cstdint>
#include <boost/dynamic_bitset.hpp>
using mybitset = boost::dynamic_bitset<unsigned>;
mybitset convertimpl(const mybitset &bitset)
{

  boost::dynamic_bitset<unsigned> zero(1, 0);
  boost::dynamic_bitset<unsigned> bb;
  std::string sbb;
  std::ostringstream obb(sbb);
  obb << ~bitset;
  std::string sbbcpy;
  ranges::reverse_copy(obb.str(), std::back_inserter(sbbcpy));

  std::istringstream ibb(sbbcpy);
  ibb >> bb;

  bb.resize(bitset.size());
  std::string s;
  std::ostringstream ost(s);
  ost << bitset << zero << bb;
  boost::dynamic_bitset<unsigned> result;
  std::string b(ost.str());
  std::istringstream iss(b.substr(0, 35651584));
  iss >> result;
  return result;
}
mybitset convert(const mybitset &bitset)
{
  auto r = convertimpl(bitset);
  if (r.size() < 35651584) {
    return convert(r);
  } else {
    r.resize(35651584);
    return r;
  }
}
mybitset shrink(mybitset bitset)
{

  while (bitset.size() % 2 == 0) {
    mybitset r(bitset.size() / 2);
    int n = r.size();
    for (int i = 0; i < n; ++i) {
      bool lhs = bitset[2 * i];
      bool rhs = bitset[2 * i + 1];
      auto t = !(lhs ^ rhs);
      r.set(i, t);
    }
    bitset = r;
  }
  return bitset;
}

int main()
{
  boost::dynamic_bitset<unsigned> bitset(17, 0b00111101111101000);
  auto r = convert(bitset);

  auto s = shrink(r);
  std::cout << "result:" << s << std::endl;
  return 0;
}


/*
 * This is part of the fl library, a C++ Bayesian filtering library
 * (https://github.com/filtering-library)
 *
 * Copyright (c) 2015 Max Planck Society,
 * 				 Autonomous Motion Department,
 * 			     Institute for Intelligent Systems
 *
 * This Source Code Form is subject to the terms of the MIT License (MIT).
 * A copy of the license can be found in the LICENSE file distributed with this
 * source code.
 */

/**
 * \file discrete_distribution.hpp
 * \date 05/25/2014
 * \author Manuel Wuthrich (manuel.wuthrich@gmail.com)
 * \author Jan Issac (jan.issac@gmail.com)
 */
#pragma once

#include <Eigen/Core>

#include <vector>

#include <fl/util/types.hpp>
#include <fl/util/traits.hpp>
#include <fl/util/assertions.hpp>
#include <fl/distribution/interface/moments.hpp>
#include <fl/distribution/interface/standard_gaussian_mapping.hpp>

namespace fl
{

template <typename Variate, int Locations = Eigen::Dynamic>
class DiscreteDistribution
    : public Moments<typename FirstMomentOf<Variate>::Type>,
      public StandardGaussianMapping<Variate, 1>
{
public:
    typedef Moments<typename FirstMomentOf<Variate>::Type>  MomentsInterface;
    typedef typename MomentsInterface::Variate              Mean;
    typedef typename MomentsInterface::SecondMoment         Covariance;

    typedef Eigen::Array<Real,    Locations, 1> Function;
    typedef Eigen::Array<Variate, Locations, 1> LocationArray;

    typedef StandardGaussianMapping<Variate, 1> StdGaussianMapping;
    typedef typename StdGaussianMapping::StandardVariate StandardVariate;

public:
    /// constructor and destructor *********************************************
    explicit
    DiscreteDistribution(int size = MaxOf<Locations, 1>::value)
    {
        set_uniform(size);
    }

    virtual ~DiscreteDistribution() noexcept { }

    /// non-const functions ****************************************************

    // set ---------------------------------------------------------------------
    virtual void log_unnormalized_prob_mass(const Function& log_prob_mass)
    {
        // rescale for numeric stability
        log_prob_mass_ = log_prob_mass - log_prob_mass.maxCoeff();

        // copy to prob mass
        prob_mass_ = log_prob_mass_.exp();
        Real sum = prob_mass_.sum();

        // normalize
        prob_mass_ /= sum;
        log_prob_mass_ -= std::log(sum);

        // compute cdf
        cumul_distr_.resize(log_prob_mass_.size());

        cumul_distr_[0] = prob_mass_[0];
        for(int i = 1; i < cumul_distr_.size(); i++)
        {
            cumul_distr_[i] = cumul_distr_[i-1] + prob_mass_[i];
        }

        // resize locations
        locations_.resize(log_prob_mass_.size());
    }

    virtual void delta_log_prob_mass(const Function& delta)
    {
        log_unnormalized_prob_mass(log_prob_mass_ + delta);
    }

    virtual void set_uniform(int new_size = -1)
    {
        if (new_size == -1) new_size = size();

        log_unnormalized_prob_mass(Function::Zero(new_size));
    }

    virtual Variate& location(int i)
    {
        return locations_[i];
    }

    template <typename Distribution>
    void from_distribution(const Distribution& distribution, const int& new_size)
    {
        // we first create a local array to sample to. this way, if this
        // is passed as an argument the locations and pmf are not overwritten
        // while sampling
        LocationArray new_locations(new_size);

        for(int i = 0; i < new_size; i++)
        {
            new_locations[i] = distribution.sample();
        }

        set_uniform(new_size);
        locations_ = new_locations;
    }

    /// const functions ********************************************************

    // sampling ----------------------------------------------------------------
    virtual Variate map_standard_normal(const StandardVariate& gaussian_sample,
                                        int& index) const
    {
        StandardVariate scaled_sample = gaussian_sample / std::sqrt(2.0);
        StandardVariate uniform_sample = 0.5 * (1.0 + std::erf(scaled_sample));

        return map_standard_uniform(uniform_sample, index);
    }

    virtual Variate map_standard_uniform(const StandardVariate& uniform_sample,
                                         int& index) const
    {
        index = 0;
        for (index = 0; index < cumul_distr_.size(); ++index)
        {
            if (cumul_distr_[index] >= uniform_sample) break;
        }

        return locations_[index];
    }

    using StdGaussianMapping::sample;

    virtual Variate sample(int& index) const
    {
        return map_standard_normal(this->standard_gaussian_.sample(), index);
    }

    virtual Variate map_standard_normal(const StandardVariate& gaussian_sample) const
    {
        int index;
        return map_standard_normal(gaussian_sample, index);
    }

    virtual Variate map_standard_uniform(const StandardVariate& uniform_sample) const
    {
        int index;
        return map_standard_uniform(uniform_sample, index);
    }

    // get ---------------------------------------------------------------------
    virtual const Variate& location(int i) const
    {
        return locations_[i];
    }

    virtual const LocationArray& locations() const
    {
        return locations_;
    }

    virtual Real log_prob_mass(const int& i) const
    {
        return log_prob_mass_(i);
    }

    virtual Function log_prob_mass() const
    {
        return log_prob_mass_;
    }

    virtual Real prob_mass(const int& i) const
    {
        return prob_mass_(i);
    }

    virtual Function prob_mass() const
    {
        return prob_mass_;
    }

    virtual int size() const
    {
        return locations_.size();
    }

    virtual int dimension() const
    {
        return locations_[0].rows();
    }

    // compute properties ------------------------------------------------------
    virtual const Mean& mean() const
    {
        mu_ = Mean::Zero(dimension());

        for(int i = 0; i < locations_.size(); i++)
        {
            mu_ += prob_mass(i) * locations_[i].template cast<Real>();
        }

        return mu_;
    }

    virtual const Variate& max() const
    {
        int max_index;
        log_prob_mass_.maxCoeff(&max_index);

        return locations_(max_index);
    }

    virtual const Covariance& covariance() const
    {
        Mean mu = mean();
        cov_ = Covariance::Zero(dimension(), dimension());
        for(int i = 0; i < locations_.size(); i++)
        {
            Mean delta = (locations_[i].template cast<Real>()-mu);
            cov_ += prob_mass(i) * delta * delta.transpose();
        }

        return cov_;
    }

    virtual Real entropy() const
    {
        return - log_prob_mass_.cwiseProduct(prob_mass_).sum();
    }

    // implements KL(p||u) where p is this distr, and u is the uniform distr
    virtual Real kl_given_uniform() const
    {
        return std::log(Real(size())) - entropy();
    }

protected:
    /// member variables *******************************************************
    LocationArray locations_;

    Function log_prob_mass_;
    Function prob_mass_;
    Function cumul_distr_;

    mutable Mean mu_;
    mutable Covariance cov_;
};

}


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

// Third party includes
#include <boost/math/constants/constants.hpp>

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

// Local VOTCA includes
#include <votca/xtp/orbitals.h>

// Local private VOTCA includes
#include "spectrum.h"

namespace votca {
namespace xtp {

void Spectrum::Initialize(const tools::Property& user_options) {

  tools::Property options =
      LoadDefaultsAndUpdateWithUserOptions("xtp", user_options);

  _job_name = options.ifExistsReturnElseReturnDefault<std::string>("job_name",
                                                                   _job_name);

  // orbitals file or pure DFT output
  _orbfile = options.ifExistsReturnElseReturnDefault<std::string>(
      ".orbitals", _job_name + ".orb");

  _output_file = options.ifExistsReturnElseReturnDefault<std::string>(
      ".output", _job_name + "_spectrum.dat");

  _n_pt = options.get(".points").as<Index>();
  _lower = options.get(".lower").as<double>();
  _upper = options.get(".upper").as<double>();
  _fwhm = options.get(".fwhm").as<double>();

  _spectrum_type = options.get(".type").as<std::string>();
  _minexc = options.get(".minexc").as<Index>();
  _maxexc = options.get(".maxexc").as<Index>();
  _shiftby = options.get(".shift").as<double>();
}

bool Spectrum::Evaluate() {
  OPENMP::setMaxThreads(_nThreads);
  _log.setReportLevel(Log::current_level);
  _log.setMultithreading(true);

  _log.setCommonPreface("\n... ...");

  XTP_LOG(Log::error, _log)
      << "Calculating absorption spectrum plot " << _orbfile << std::flush;

  Orbitals orbitals;
  // load the QM data from serialized orbitals object
  XTP_LOG(Log::error, _log)
      << " Loading QM data from " << _orbfile << std::flush;
  orbitals.ReadFromCpt(_orbfile);

  // check if orbitals contains singlet energies and transition dipoles
  if (!orbitals.hasBSESinglets()) {
    throw std::runtime_error(
        "BSE singlet energies not stored in QM data file!");
  }

  if (!orbitals.hasTransitionDipoles()) {
    throw std::runtime_error(
        "BSE transition dipoles not stored in QM data file!");
  }

  const Eigen::VectorXd BSESingletEnergies =
      orbitals.BSESinglets().eigenvalues() * tools::conv::hrt2ev;
  const std::vector<Eigen::Vector3d>& TransitionDipoles =
      orbitals.TransitionDipoles();
  Eigen::VectorXd osc = orbitals.Oscillatorstrengths();

  if (_maxexc > Index(TransitionDipoles.size())) {
    _maxexc = Index(TransitionDipoles.size()) - 1;
  }

  Index n_exc = _maxexc - _minexc + 1;
  XTP_LOG(Log::error, _log)
      << " Considering " << n_exc << " excitation with max energy "
      << BSESingletEnergies(_maxexc) << " eV / min wave length "
      << evtonm(BSESingletEnergies[_maxexc - 1]) << " nm" << std::flush;

  /*
   *
   * For a single excitation, broaden by Lineshape function L(v-W)
   *    eps(v) = f * L(v-W)
   *
   * where
   *       v: energy
   *       f: oscillator strength in dipole-length gauge
   *       W: excitation energy
   *
   * Lineshape function depend on FWHM and can be
   *
   *      Gaussian
   *          L(v-W) = 1/(sqrt(2pi)sigma) * exp(-0.5 (v-W)^2/sigma^2
   *
   *
   *            with sigma: derived from FWHM (FWHM/2.3548)
   *
   *     Lorentzian
   *          L(v-W) = 1/pi * 0.5 FWHM/( (v-w)^2 + 0.25*FWHM^2 )
   *
   * Full spectrum is superposition of individual spectra.
   *
   *  Alternatively, one can calculate the imaginary part of the
   *  frequency-dependent dielectric function
   *
   *   IM(eps(v)) ~ 1/v^2 * W^2 * |td|^2 * L(v-W)
   *              = 1/v^2 * W   * f      * L(v-W)
   *
   *
   */

  std::ofstream ofs(_output_file, std::ofstream::out);

  if (_spectrum_type == "energy") {
    ofs << "# E(eV)    epsGaussian    IM(eps)Gaussian   epsLorentz    "
           "Im(esp)Lorentz\n";
    for (Index i_pt = 0; i_pt <= _n_pt; i_pt++) {

      double e = (_lower + double(i_pt) * (_upper - _lower) / double(_n_pt));

      double eps_Gaussian = 0.0;
      double imeps_Gaussian = 0.0;
      double eps_Lorentzian = 0.0;
      double imeps_Lorentzian = 0.0;

      for (Index i_exc = _minexc; i_exc <= _maxexc; i_exc++) {
        eps_Gaussian +=
            osc[i_exc] *
            Gaussian(e, BSESingletEnergies(i_exc) + _shiftby, _fwhm);
        imeps_Gaussian += osc[i_exc] * BSESingletEnergies(i_exc) *
                          Gaussian(e, BSESingletEnergies(i_exc), _fwhm);
        eps_Lorentzian +=
            osc[i_exc] * Lorentzian(e, BSESingletEnergies(i_exc), _fwhm);
        imeps_Lorentzian += osc[i_exc] * BSESingletEnergies(i_exc) *
                            Lorentzian(e, BSESingletEnergies(i_exc), _fwhm);
      }

      ofs << e << "    " << eps_Gaussian << "   " << imeps_Gaussian << "   "
          << eps_Lorentzian << "   " << imeps_Lorentzian << std::endl;
    }

    XTP_LOG(Log::error, _log)
        << " Spectrum in energy range from  " << _lower << " to " << _upper
        << " eV and with broadening of FWHM " << _fwhm
        << " eV written to file  " << _output_file << std::flush;
  }

  if (_spectrum_type == "wavelength") {

    ofs << "# lambda(nm)    epsGaussian    IM(eps)Gaussian   epsLorentz    "
           "Im(esp)Lorentz\n";
    for (Index i_pt = 0; i_pt <= _n_pt; i_pt++) {

      double lambda =
          (_lower + double(i_pt) * (_upper - _lower) / double(_n_pt));
      double eps_Gaussian = 0.0;
      double imeps_Gaussian = 0.0;
      double eps_Lorentzian = 0.0;
      double imeps_Lorentzian = 0.0;

      for (Index i_exc = _minexc; i_exc <= _maxexc; i_exc++) {
        double exc_lambda = nmtoev(BSESingletEnergies(i_exc) + _shiftby);
        eps_Gaussian += osc[i_exc] * Gaussian(lambda, exc_lambda, _fwhm);
        imeps_Gaussian +=
            osc[i_exc] * exc_lambda * Gaussian(lambda, exc_lambda, _fwhm);
        eps_Lorentzian += osc[i_exc] * Lorentzian(lambda, exc_lambda, _fwhm);
        imeps_Lorentzian +=
            osc[i_exc] * exc_lambda * Lorentzian(lambda, exc_lambda, _fwhm);
      }

      ofs << lambda << "    " << eps_Gaussian << "   " << imeps_Gaussian
          << "   " << eps_Lorentzian << "   " << imeps_Lorentzian << std::endl;
    }
    XTP_LOG(Log::error, _log)
        << " Spectrum in wavelength range from  " << _lower << " to " << _upper
        << " nm and with broadening of FWHM " << _fwhm
        << " nm written to file  " << _output_file << std::flush;
  }

  ofs.close();
  return true;
}

double Spectrum::Lorentzian(double x, double center, double fwhm) {
  return 0.5 * fwhm / (std::pow(x - center, 2) + 0.25 * fwhm * fwhm) /
         boost::math::constants::pi<double>();
}

double Spectrum::Gaussian(double x, double center, double fwhm) {
  // FWHM = 2*sqrt(2 ln2) sigma = 2.3548 sigma
  double sigma = fwhm / 2.3548;
  return std::exp(-0.5 * std::pow((x - center) / sigma, 2)) / sigma /
         sqrt(2.0 * boost::math::constants::pi<double>());
}

double Spectrum::evtonm(double eV) { return 1241.0 / eV; }

double Spectrum::evtoinvcm(double eV) { return 8065.73 * eV; }

double Spectrum::nmtoinvcm(double nm) { return 1241.0 * 8065.73 / nm; }

double Spectrum::invcmtonm(double invcm) { return 1.0e7 / invcm; }

double Spectrum::nmtoev(double nm) { return 1241.0 / nm; }

}  // namespace xtp
}  // namespace votca


///////////////////////////////////////////////////////////////////////////////
// weighted_peaks_over_threshold.hpp
//
//  Copyright 2006 Daniel Egloff, Olivier Gygi. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_ACCUMULATORS_STATISTICS_WEIGHTED_PEAKS_OVER_THRESHOLD_HPP_DE_01_01_2006
#define BOOST_ACCUMULATORS_STATISTICS_WEIGHTED_PEAKS_OVER_THRESHOLD_HPP_DE_01_01_2006

#include <vector>
#include <limits>
#include <numeric>
#include <functional>
#include <boost/throw_exception.hpp>
#include <boost/range.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/placeholders.hpp>
#include <boost/parameter/keyword.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/accumulators/numeric/functional.hpp>
#include <boost/accumulators/framework/accumulator_base.hpp>
#include <boost/accumulators/framework/extractor.hpp>
#include <boost/accumulators/framework/parameters/sample.hpp>
#include <boost/accumulators/framework/depends_on.hpp>
#include <boost/accumulators/statistics_fwd.hpp>
#include <boost/accumulators/statistics/parameters/quantile_probability.hpp>
#include <boost/accumulators/statistics/peaks_over_threshold.hpp> // for named parameters pot_threshold_value and pot_threshold_probability
#include <boost/accumulators/statistics/sum.hpp>
#include <boost/accumulators/statistics/tail_variate.hpp>

#ifdef _MSC_VER
# pragma warning(push)
# pragma warning(disable: 4127) // conditional expression is constant
#endif

namespace boost { namespace accumulators
{

namespace impl
{

    ///////////////////////////////////////////////////////////////////////////////
    // weighted_peaks_over_threshold_impl
    //  works with an explicit threshold value and does not depend on order statistics of weighted samples
    /**
        @brief Weighted Peaks over Threshold Method for Weighted Quantile and Weighted Tail Mean Estimation

        @sa peaks_over_threshold_impl

        @param quantile_probability
        @param pot_threshold_value
    */
    template<typename Sample, typename Weight, typename LeftRight>
    struct weighted_peaks_over_threshold_impl
      : accumulator_base
    {
        typedef typename numeric::functional::multiplies<Weight, Sample>::result_type weighted_sample;
        typedef typename numeric::functional::average<weighted_sample, std::size_t>::result_type float_type;
        // for boost::result_of
        typedef boost::tuple<float_type, float_type, float_type> result_type;

        template<typename Args>
        weighted_peaks_over_threshold_impl(Args const &args)
          : sign_((is_same<LeftRight, left>::value) ? -1 : 1)
          , mu_(sign_ * numeric::average(args[sample | Sample()], (std::size_t)1))
          , sigma2_(numeric::average(args[sample | Sample()], (std::size_t)1))
          , w_sum_(numeric::average(args[weight | Weight()], (std::size_t)1))
          , threshold_(sign_ * args[pot_threshold_value])
          , fit_parameters_(boost::make_tuple(0., 0., 0.))
          , is_dirty_(true)
        {
        }

        template<typename Args>
        void operator ()(Args const &args)
        {
            this->is_dirty_ = true;

            if (this->sign_ * args[sample] > this->threshold_)
            {
                this->mu_ += args[weight] * args[sample];
                this->sigma2_ += args[weight] * args[sample] * args[sample];
                this->w_sum_ += args[weight];
            }
        }

        template<typename Args>
        result_type result(Args const &args) const
        {
            if (this->is_dirty_)
            {
                this->is_dirty_ = false;

                this->mu_ = this->sign_ * numeric::average(this->mu_, this->w_sum_);
                this->sigma2_ = numeric::average(this->sigma2_, this->w_sum_);
                this->sigma2_ -= this->mu_ * this->mu_;

                float_type threshold_probability = numeric::average(sum_of_weights(args) - this->w_sum_, sum_of_weights(args));

                float_type tmp = numeric::average(( this->mu_ - this->threshold_ )*( this->mu_ - this->threshold_ ), this->sigma2_);
                float_type xi_hat = 0.5 * ( 1. - tmp );
                float_type beta_hat = 0.5 * ( this->mu_ - this->threshold_ ) * ( 1. + tmp );
                float_type beta_bar = beta_hat * std::pow(1. - threshold_probability, xi_hat);
                float_type u_bar = this->threshold_ - beta_bar * ( std::pow(1. - threshold_probability, -xi_hat) - 1.)/xi_hat;
                this->fit_parameters_ = boost::make_tuple(u_bar, beta_bar, xi_hat);
            }

            return this->fit_parameters_;
        }

    private:
        short sign_;                         // for left tail fitting, mirror the extreme values
        mutable float_type mu_;              // mean of samples above threshold
        mutable float_type sigma2_;          // variance of samples above threshold
        mutable float_type w_sum_;           // sum of weights of samples above threshold
        float_type threshold_;
        mutable result_type fit_parameters_; // boost::tuple that stores fit parameters
        mutable bool is_dirty_;
    };

    ///////////////////////////////////////////////////////////////////////////////
    // weighted_peaks_over_threshold_prob_impl
    //  determines threshold from a given threshold probability using order statistics
    /**
        @brief Peaks over Threshold Method for Quantile and Tail Mean Estimation

        @sa weighted_peaks_over_threshold_impl

        @param quantile_probability
        @param pot_threshold_probability
    */
    template<typename Sample, typename Weight, typename LeftRight>
    struct weighted_peaks_over_threshold_prob_impl
      : accumulator_base
    {
        typedef typename numeric::functional::multiplies<Weight, Sample>::result_type weighted_sample;
        typedef typename numeric::functional::average<weighted_sample, std::size_t>::result_type float_type;
        // for boost::result_of
        typedef boost::tuple<float_type, float_type, float_type> result_type;

        template<typename Args>
        weighted_peaks_over_threshold_prob_impl(Args const &args)
          : sign_((is_same<LeftRight, left>::value) ? -1 : 1)
          , mu_(sign_ * numeric::average(args[sample | Sample()], (std::size_t)1))
          , sigma2_(numeric::average(args[sample | Sample()], (std::size_t)1))
          , threshold_probability_(args[pot_threshold_probability])
          , fit_parameters_(boost::make_tuple(0., 0., 0.))
          , is_dirty_(true)
        {
        }

        void operator ()(dont_care)
        {
            this->is_dirty_ = true;
        }

        template<typename Args>
        result_type result(Args const &args) const
        {
            if (this->is_dirty_)
            {
                this->is_dirty_ = false;

                float_type threshold = sum_of_weights(args)
                             * ( ( is_same<LeftRight, left>::value ) ? this->threshold_probability_ : 1. - this->threshold_probability_ );

                std::size_t n = 0;
                Weight sum = Weight(0);

                while (sum < threshold)
                {
                    if (n < static_cast<std::size_t>(tail_weights(args).size()))
                    {
                        mu_ += *(tail_weights(args).begin() + n) * *(tail(args).begin() + n);
                        sigma2_ += *(tail_weights(args).begin() + n) * *(tail(args).begin() + n) * (*(tail(args).begin() + n));
                        sum += *(tail_weights(args).begin() + n);
                        n++;
                    }
                    else
                    {
                        if (std::numeric_limits<float_type>::has_quiet_NaN)
                        {
                            return boost::make_tuple(
                                std::numeric_limits<float_type>::quiet_NaN()
                              , std::numeric_limits<float_type>::quiet_NaN()
                              , std::numeric_limits<float_type>::quiet_NaN()
                            );
                        }
                        else
                        {
                            std::ostringstream msg;
                            msg << "index n = " << n << " is not in valid range [0, " << tail(args).size() << ")";
                            boost::throw_exception(std::runtime_error(msg.str()));
                            return boost::make_tuple(Sample(0), Sample(0), Sample(0));
                        }
                    }
                }

                float_type u = *(tail(args).begin() + n - 1) * this->sign_;

                this->mu_ = this->sign_ * numeric::average(this->mu_, sum);
                this->sigma2_ = numeric::average(this->sigma2_, sum);
                this->sigma2_ -= this->mu_ * this->mu_;

                if (is_same<LeftRight, left>::value)
                    this->threshold_probability_ = 1. - this->threshold_probability_;

                float_type tmp = numeric::average(( this->mu_ - u )*( this->mu_ - u ), this->sigma2_);
                float_type xi_hat = 0.5 * ( 1. - tmp );
                float_type beta_hat = 0.5 * ( this->mu_ - u ) * ( 1. + tmp );
                float_type beta_bar = beta_hat * std::pow(1. - threshold_probability_, xi_hat);
                float_type u_bar = u - beta_bar * ( std::pow(1. - threshold_probability_, -xi_hat) - 1.)/xi_hat;
                this->fit_parameters_ = boost::make_tuple(u_bar, beta_bar, xi_hat);

            }

            return this->fit_parameters_;
        }

    private:
        short sign_;                                // for left tail fitting, mirror the extreme values
        mutable float_type mu_;                     // mean of samples above threshold u
        mutable float_type sigma2_;                 // variance of samples above threshold u
        mutable float_type threshold_probability_;
        mutable result_type fit_parameters_;        // boost::tuple that stores fit parameters
        mutable bool is_dirty_;
    };

} // namespace impl

///////////////////////////////////////////////////////////////////////////////
// tag::weighted_peaks_over_threshold
//
namespace tag
{
    template<typename LeftRight>
    struct weighted_peaks_over_threshold
      : depends_on<sum_of_weights>
      , pot_threshold_value
    {
        /// INTERNAL ONLY
        typedef accumulators::impl::weighted_peaks_over_threshold_impl<mpl::_1, mpl::_2, LeftRight> impl;
    };

    template<typename LeftRight>
    struct weighted_peaks_over_threshold_prob
      : depends_on<sum_of_weights, tail_weights<LeftRight> >
      , pot_threshold_probability
    {
        /// INTERNAL ONLY
        typedef accumulators::impl::weighted_peaks_over_threshold_prob_impl<mpl::_1, mpl::_2, LeftRight> impl;
    };
}

///////////////////////////////////////////////////////////////////////////////
// extract::weighted_peaks_over_threshold
//
namespace extract
{
    extractor<tag::abstract_peaks_over_threshold> const weighted_peaks_over_threshold = {};

    BOOST_ACCUMULATORS_IGNORE_GLOBAL(weighted_peaks_over_threshold)
}

using extract::weighted_peaks_over_threshold;

// weighted_peaks_over_threshold<LeftRight>(with_threshold_value) -> weighted_peaks_over_threshold<LeftRight>
template<typename LeftRight>
struct as_feature<tag::weighted_peaks_over_threshold<LeftRight>(with_threshold_value)>
{
    typedef tag::weighted_peaks_over_threshold<LeftRight> type;
};

// weighted_peaks_over_threshold<LeftRight>(with_threshold_probability) -> weighted_peaks_over_threshold_prob<LeftRight>
template<typename LeftRight>
struct as_feature<tag::weighted_peaks_over_threshold<LeftRight>(with_threshold_probability)>
{
    typedef tag::weighted_peaks_over_threshold_prob<LeftRight> type;
};

}} // namespace boost::accumulators

#ifdef _MSC_VER
# pragma warning(pop)
#endif

#endif


#include "stella_vslam/data/frame.h"
#include "stella_vslam/data/keyframe.h"
#include "stella_vslam/data/landmark.h"
#include "stella_vslam/optimize/pose_optimizer.h"
#include "stella_vslam/optimize/internal/se3/pose_opt_edge_wrapper.h"
#include "stella_vslam/util/converter.h"

#include <vector>
#include <mutex>

#include <Eigen/StdVector>
#include <g2o/core/solver.h>
#include <g2o/core/block_solver.h>
#include <g2o/core/sparse_optimizer.h>
#include <g2o/core/robust_kernel_impl.h>
#include <g2o/solvers/eigen/linear_solver_eigen.h>
#include <g2o/solvers/dense/linear_solver_dense.h>
#include <g2o/core/optimization_algorithm_levenberg.h>

namespace stella_vslam {
namespace optimize {

pose_optimizer::pose_optimizer(const unsigned int num_trials, const unsigned int num_each_iter)
    : num_trials_(num_trials), num_each_iter_(num_each_iter) {}

unsigned int pose_optimizer::optimize(const data::frame& frm, g2o::SE3Quat& optimized_pose, std::vector<bool>& outlier_flags) const {
    auto num_valid_obs = optimize(frm.get_pose_cw(), frm.frm_obs_, frm.orb_params_, frm.camera_,
                                  frm.landmarks_, optimized_pose, outlier_flags);
    return num_valid_obs;
}

unsigned int pose_optimizer::optimize(const data::keyframe* keyfrm, g2o::SE3Quat& optimized_pose, std::vector<bool>& outlier_flags) const {
    auto num_valid_obs = optimize(keyfrm->get_pose_cw(), keyfrm->frm_obs_, keyfrm->orb_params_, keyfrm->camera_,
                                  keyfrm->get_landmarks(), optimized_pose, outlier_flags);
    return num_valid_obs;
}

unsigned int pose_optimizer::optimize(const Mat44_t& cam_pose_cw, const data::frame_observation& frm_obs,
                                      const feature::orb_params* orb_params,
                                      const camera::base* camera,
                                      const std::vector<std::shared_ptr<data::landmark>>& landmarks,
                                      g2o::SE3Quat& optimized_pose,
                                      std::vector<bool>& outlier_flags) const {
    // 1. Construct an optimizer

    auto linear_solver = g2o::make_unique<g2o::LinearSolverEigen<g2o::BlockSolver_6_3::PoseMatrixType>>();
    auto block_solver = g2o::make_unique<g2o::BlockSolver_6_3>(std::move(linear_solver));
    auto algorithm = new g2o::OptimizationAlgorithmLevenberg(std::move(block_solver));

    g2o::SparseOptimizer optimizer;
    optimizer.setAlgorithm(algorithm);

    unsigned int num_init_obs = 0;

    // 2. Convert the frame to the g2o vertex, then set it to the optimizer

    auto frm_vtx = new internal::se3::shot_vertex();
    frm_vtx->setId(0);
    frm_vtx->setEstimate(util::converter::to_g2o_SE3(cam_pose_cw));
    frm_vtx->setFixed(false);
    optimizer.addVertex(frm_vtx);

    const unsigned int num_keypts = frm_obs.num_keypts_;
    outlier_flags.resize(num_keypts);
    std::fill(outlier_flags.begin(), outlier_flags.end(), false);

    // 3. Connect the landmark vertices by using projection edges

    // Container of the reprojection edges
    using pose_opt_edge_wrapper = internal::se3::pose_opt_edge_wrapper;
    std::vector<pose_opt_edge_wrapper> pose_opt_edge_wraps;
    pose_opt_edge_wraps.reserve(num_keypts);

    // Chi-squared value with significance level of 5%
    // Two degree-of-freedom (n=2)
    constexpr float chi_sq_2D = 5.99146;
    const float sqrt_chi_sq_2D = std::sqrt(chi_sq_2D);
    // Three degree-of-freedom (n=3)
    constexpr float chi_sq_3D = 7.81473;
    const float sqrt_chi_sq_3D = std::sqrt(chi_sq_3D);

    for (unsigned int idx = 0; idx < num_keypts; ++idx) {
        const auto& lm = landmarks.at(idx);
        if (!lm) {
            continue;
        }
        if (lm->will_be_erased()) {
            continue;
        }

        ++num_init_obs;

        // Connect the frame and the landmark vertices using the projection edges
        const auto& undist_keypt = frm_obs.undist_keypts_.at(idx);
        const float x_right = frm_obs.stereo_x_right_.empty() ? -1.0f : frm_obs.stereo_x_right_.at(idx);
        const float inv_sigma_sq = orb_params->inv_level_sigma_sq_.at(undist_keypt.octave);
        const auto sqrt_chi_sq = (camera->setup_type_ == camera::setup_type_t::Monocular)
                                     ? sqrt_chi_sq_2D
                                     : sqrt_chi_sq_3D;
        auto pose_opt_edge_wrap = pose_opt_edge_wrapper(camera, frm_vtx, lm->get_pos_in_world(),
                                                        idx, undist_keypt.pt.x, undist_keypt.pt.y, x_right,
                                                        inv_sigma_sq, sqrt_chi_sq);
        pose_opt_edge_wraps.push_back(pose_opt_edge_wrap);
        optimizer.addEdge(pose_opt_edge_wrap.edge_);
    }

    if (num_init_obs < 5) {
        return 0;
    }

    // 4. Perform robust Bundle Adjustment (BA)

    unsigned int num_bad_obs = 0;
    for (unsigned int trial = 0; trial < num_trials_; ++trial) {
        optimizer.initializeOptimization();
        optimizer.optimize(num_each_iter_);

        num_bad_obs = 0;

        for (auto& pose_opt_edge_wrap : pose_opt_edge_wraps) {
            auto edge = pose_opt_edge_wrap.edge_;

            if (outlier_flags.at(pose_opt_edge_wrap.idx_)) {
                edge->computeError();
            }

            if (pose_opt_edge_wrap.is_monocular_) {
                if (chi_sq_2D < edge->chi2()) {
                    outlier_flags.at(pose_opt_edge_wrap.idx_) = true;
                    pose_opt_edge_wrap.set_as_outlier();
                    ++num_bad_obs;
                }
                else {
                    outlier_flags.at(pose_opt_edge_wrap.idx_) = false;
                    pose_opt_edge_wrap.set_as_inlier();
                }
            }
            else {
                if (chi_sq_3D < edge->chi2()) {
                    outlier_flags.at(pose_opt_edge_wrap.idx_) = true;
                    pose_opt_edge_wrap.set_as_outlier();
                    ++num_bad_obs;
                }
                else {
                    outlier_flags.at(pose_opt_edge_wrap.idx_) = false;
                    pose_opt_edge_wrap.set_as_inlier();
                }
            }

            if (trial == num_trials_ - 2) {
                edge->setRobustKernel(nullptr);
            }
        }

        if (num_init_obs - num_bad_obs < 5) {
            break;
        }
    }

    // 5. Update the information

    optimized_pose = frm_vtx->estimate();

    return num_init_obs - num_bad_obs;
}

} // namespace optimize
} // namespace stella_vslam


// VMDモーションを間引く

#include <vector>
#include <Eigen/Core>
#include "VMD.h"
#include "interpolate.h"
#include "reducevmd.h"

#define _USE_MATH_DEFINES
#include <math.h>

// head番目からtail番目のボーンキーフレームのうち、残すべきものを再帰的に探して返す。
// ただし返値にtail番目のボーンは含まれないので、コール元で追加する必要がある。
vector<VMD_Frame> reduce_bone_frame_recursive(const vector<VMD_Frame>& v, int head, int tail, float threshold_pos, float threshold_rot, bool bezier)
{
  float max_pos_err = 0.0;
  float max_rot_err = 0.0;
  int max_idx_pos = 0;
  int max_idx_rot = 0;
  VMD_Frame head_frame = v[head];
  VMD_Frame tail_frame = v[tail];
  const int bezier_interpolation_limit = 60;
  if (bezier && tail - head < bezier_interpolation_limit) {
    optimize_bezier_parameter(tail_frame, v, head, tail);
  }

  // headフレームの値とtailフレームの値によって決まる補完曲線(直線)から最も離れた(誤差の大きい)フレームを探す
  for (int i = head + 1; i < tail; i++) {
    VMD_Frame f = interpolate_frame(head_frame, tail_frame, i, bezier);
    float pos_err = (f.position - v[i].position).norm();
    if (pos_err > max_pos_err) {
      max_idx_pos = i;
      max_pos_err = pos_err;
    }
    float rot_err = fabs(f.rotation.angularDistance(v[i].rotation) * 180 / M_PI);
    if (rot_err > max_rot_err) {
      max_idx_rot = i;
      max_rot_err = rot_err;
    }
  }

  // 補間曲線から最も離れたフレームの誤差が閾値を超えていたら、そのフレーム(max_idx_*)を残し、
  // [head, max_idx_*] と [max_idx_*, tail] のそれぞれの区間を再帰的に探す。
  vector<VMD_Frame> v1;
  if (max_pos_err > threshold_pos) {
    v1 = reduce_bone_frame_recursive(v, head, max_idx_pos, threshold_pos, threshold_rot, bezier);
    vector<VMD_Frame> v2 = reduce_bone_frame_recursive(v, max_idx_pos, tail, threshold_pos, threshold_rot, bezier);
    v1.insert(v1.end(), v2.begin(), v2.end());
  } else {
    if (max_rot_err > threshold_rot) {
      v1 = reduce_bone_frame_recursive(v, head, max_idx_rot, threshold_pos, threshold_rot, bezier);
      vector<VMD_Frame> v2 = reduce_bone_frame_recursive(v, max_idx_rot, tail, threshold_pos, threshold_rot, bezier);
      v1.insert(v1.end(), v2.begin(), v2.end());
    } else {
      v1.push_back(tail_frame);
    }
  }
  return v1;
}

// head番目からtail番目のボーンキーフレームのうち、残すべきものを探して返す。
vector<VMD_Frame> reduce_bone_frame(const vector<VMD_Frame>& v, int head, int tail, float threshold_pos, float threshold_rot, bool bezier)
{
  if (threshold_pos < 0 || threshold_rot < 0) {
    vector<VMD_Frame> v1(v);
    return v1;
  }

  vector<VMD_Frame> v1 = reduce_bone_frame_recursive(v, head, tail, threshold_pos, threshold_rot, bezier);
  v1.insert(v1.begin(), v.front());

  return v1;
}

// head番目からtail番目の表情キーフレームのうち、残すべきものを再帰的に探して返す。
// ただし返値にtail番目のフレームは含まれないので、コール元で追加する必要がある。
vector<VMD_Morph> reduce_morph_frame_recursive(const vector<VMD_Morph>& v, int head, int tail, float threshold)
{
  float max = 0.0;
  int max_idx = 0;
  int total = tail - head;
  for (int i = head + 1; i < tail; i++) {
    float iv = v[head].weight + (v[tail].weight - v[head].weight) * (i - head) / total;
    float e = abs(iv - v[i].weight);
    if (e > max) {
      max_idx = i;
      max = e;
    }
  }

  vector<VMD_Morph> v1;
  if (max > threshold) {
    v1 = reduce_morph_frame_recursive(v, head, max_idx, threshold);
    vector<VMD_Morph> v2 = reduce_morph_frame_recursive(v, max_idx, tail, threshold);
    v1.insert(v1.end(), v2.begin(), v2.end());
  } else {
    v1.push_back(v[head]);
  }
  return v1;
}

// head番目からtail番目の表情キーフレームのうち、残すべきものを探して返す。
vector<VMD_Morph> reduce_morph_frame(const vector<VMD_Morph>& v, int head, int tail, float threshold)
{
  if (threshold < 0) {
    vector<VMD_Morph> v1(v);
    return v1;
  }

  vector<VMD_Morph> v1 = reduce_morph_frame_recursive(v, head, tail, threshold);
  v1.push_back(v.back());
  return v1;
}


//
// VariationalBayesEstimatorOnATM.hpp
//
// Copyright (c) 2017 Shion Hosoda
//
// This software is released under the MIT License.
// http://opensource.org/licenses/mit-license.php
//

#ifndef VBATM
#define VBATM

#include<stdlib.h>
#include<math.h>
#include<cmath>
#include<iostream>
#include<vector>
#include<numeric>
#include<memory>
#include<random>
#include<iomanip>
#include<fstream>
#include <boost/math/special_functions/digamma.hpp>
#include <boost/math/special_functions/gamma.hpp>
#include"BOWFileParser.hpp"
#include"AuthorFileParser.hpp"
#include"utils.hpp"

enum BetaUpdateManner{
    SYMMETRY, ASYMMETRY
};

class VariationalBayesEstimatorOnATM{
protected:
    const std::vector<std::vector<unsigned int> > &_frequencyMatrix;
    const std::vector<std::vector<unsigned int> > &_docVoca;
    const unsigned int _K, _V, _D, _M;
    const std::vector<std::vector<unsigned int> > &_docAuth;
    double _convergenceDiterminationRate;
    std::vector<std::vector<std::vector<std::vector<double> > > > _qzy;
    std::vector<std::vector<double> > _thetaEx, _phiEx;
    std::vector<double> _alpha, _beta;
    double _alphaSum, _betaSum;
    std::vector<std::vector<double> > _alphaTimeSeries, _betaTimeSeries;
    std::vector<double> _nm, _nk;
    std::vector<std::vector<double> > _nmk, _nkv;
    double _variationalLowerBound;
    std::vector<double> _VLBTimeSeries;
public:
    VariationalBayesEstimatorOnATM(const BOWFileParser &parser, const AuthorFileParser &aParser, const unsigned int K, const double convergenceDiterminationRate);
    virtual ~VariationalBayesEstimatorOnATM();
    virtual void initializeParam();
    virtual void initializeHyperParam();
    virtual void calculateEx();
    virtual void calculateHyperParamSum();
    virtual void updateQzy();
    virtual void updateNEx();
    virtual void updateBeta(BetaUpdateManner manner);
    virtual void updateHyperParameters();
    virtual double calculateVariationalLowerBound()const;
    virtual void writeParameter(std::string thetaFilename, std::string phiFilename, std::string alphaFilename, std::string betaFilename)const;
    virtual void writeVariationalLowerBound(std::string VLBFilename, std::string VLBTimeSeriesFilename)const;
    virtual void runIteraions();
};

#endif