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

#ifndef CRYPTO3_DETAIL_ENDIAN_SHIFT_HPP
#define CRYPTO3_DETAIL_ENDIAN_SHIFT_HPP

#include <boost/assert.hpp>

#include <nil/crypto3/detail/stream_endian.hpp>
#include <nil/crypto3/detail/basic_functions.hpp>
#include <nil/crypto3/detail/unbounded_shift.hpp>

namespace nil {
    namespace crypto3 {
        namespace detail {

            template<typename Endianness, std::size_t WordBits>
            struct endian_shift;

            template<int UnitBits, std::size_t WordBits>
            struct endian_shift<stream_endian::big_unit_big_bit<UnitBits>, WordBits>
                : public basic_functions<WordBits> {

                constexpr static const std::size_t word_bits = basic_functions<WordBits>::word_bits;
                typedef typename basic_functions<WordBits>::word_type word_type;

                static word_type &to_msb(word_type &w, std::size_t shift) {
                    // shift to most significant bits according to endianness
                    w = unbounded_shl(w, shift);
                    return w;
                }
            };

            template<int UnitBits, std::size_t WordBits>
            struct endian_shift<stream_endian::little_unit_big_bit<UnitBits>, WordBits>
                : public basic_functions<WordBits> {

                constexpr static const std::size_t word_bits = basic_functions<WordBits>::word_bits;
                typedef typename basic_functions<WordBits>::word_type word_type;

                static word_type &to_msb(word_type &w, std::size_t shift) {
                    // shift to most significant bits according to endianness
                    std::size_t shift_rem = shift % UnitBits;
                    std::size_t shift_unit_bits = shift - shift_rem;

                    std::size_t sz[2] = {UnitBits - shift_rem, shift_rem};
                    word_type masks[2];
                    masks[0] = unbounded_shl(low_bits<word_bits>(~word_type(), sz[0]), shift_unit_bits);
                    masks[1] =
                        unbounded_shl(low_bits<word_bits>(~word_type(), sz[1]), shift_unit_bits + UnitBits + sz[0]);
                    std::size_t bits_left = word_bits - shift;

                    word_type w_combined = 0;
                    int ind = 0;

                    while (bits_left) {
                        w_combined |= (!ind ? unbounded_shl(w & masks[0], shift_rem) :
                                              unbounded_shr(w & masks[1], UnitBits + sz[0]));
                        bits_left -= sz[ind];
                        masks[ind] = unbounded_shl(masks[ind], UnitBits);
                        ind = 1 - ind;
                    }

                    w = unbounded_shr(w_combined, shift_unit_bits);
                    return w;
                }
            };

            template<int UnitBits, std::size_t WordBits>
            struct endian_shift<stream_endian::big_unit_little_bit<UnitBits>, WordBits>
                : public basic_functions<WordBits> {

                constexpr static const std::size_t word_bits = basic_functions<WordBits>::word_bits;
                typedef typename basic_functions<WordBits>::word_type word_type;

                static word_type &to_msb(word_type &w, std::size_t shift) {
                    // shift to most significant bits according to endianness
                    std::size_t shift_rem = shift % UnitBits;
                    std::size_t shift_unit_bits = shift - shift_rem;

                    std::size_t sz[2] = {UnitBits - shift_rem, shift_rem};
                    word_type masks[2] = {
                        unbounded_shr(high_bits<word_bits>(~word_type(), sz[0]), shift_unit_bits),
                        unbounded_shr(high_bits<word_bits>(~word_type(), sz[1]), shift_unit_bits + UnitBits + sz[0])};

                    std::size_t bits_left = word_bits - shift;
                    word_type w_combined = 0;
                    int ind = 0;

                    while (bits_left) {
                        w_combined |= (!ind ? unbounded_shr(w & masks[0], shift_rem) :
                                              unbounded_shl(w & masks[1], UnitBits + sz[0]));
                        bits_left -= sz[ind];
                        masks[ind] = unbounded_shr(masks[ind], UnitBits);
                        ind = 1 - ind;
                    }

                    w = unbounded_shl(w_combined, shift_unit_bits);
                    return w;
                }
            };

            template<int UnitBits, std::size_t WordBits>
            struct endian_shift<stream_endian::little_unit_little_bit<UnitBits>, WordBits>
                : public basic_functions<WordBits> {

                constexpr static const std::size_t word_bits = basic_functions<WordBits>::word_bits;
                typedef typename basic_functions<WordBits>::word_type word_type;

                static word_type &to_msb(word_type &w, std::size_t shift) {

                    // shift to most significant bits according to endianness
                    w = unbounded_shr(w, shift);
                    return w;
                }
            };
        }    // namespace detail
    }        // namespace crypto3
}    // namespace nil

#endif    // CRYPTO3_DETAIL_ENDIAN_SHIFT_HPP


// Boost.Geometry

// Copyright (c) 2020-2022, Oracle and/or its affiliates.

// 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_STRATEGIES_ENVELOPE_SPHERICAL_HPP
#define BOOST_GEOMETRY_STRATEGIES_ENVELOPE_SPHERICAL_HPP

#include <type_traits>

#include <boost/geometry/strategy/spherical/envelope_box.hpp>
#include <boost/geometry/strategy/spherical/envelope_boxes.hpp>
#include <boost/geometry/strategy/spherical/envelope_multipoint.hpp>
#include <boost/geometry/strategy/spherical/envelope_point.hpp>
#include <boost/geometry/strategy/spherical/envelope_range.hpp>
#include <boost/geometry/strategy/spherical/envelope_segment.hpp>

#include <boost/geometry/strategies/envelope/services.hpp>
#include <boost/geometry/strategies/expand/spherical.hpp>

namespace boost { namespace geometry
{

namespace strategies { namespace envelope
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail
{

template <typename RadiusTypeOrSphere, typename CalculationType>
struct spherical
    : strategies::expand::detail::spherical<RadiusTypeOrSphere, CalculationType>
{
    spherical() = default;

    template <typename RadiusOrSphere>
    explicit spherical(RadiusOrSphere const& radius_or_sphere)
        : strategies::expand::detail::spherical<RadiusTypeOrSphere, CalculationType>(radius_or_sphere)
    {}

    template <typename Geometry, typename Box>
    static auto envelope(Geometry const&, Box const&,
                         util::enable_if_point_t<Geometry> * = nullptr)
    {
        return strategy::envelope::spherical_point();
    }

    template <typename Geometry, typename Box>
    static auto envelope(Geometry const&, Box const&,
                         util::enable_if_multi_point_t<Geometry> * = nullptr)
    {
        return strategy::envelope::spherical_multipoint();
    }

    template <typename Geometry, typename Box>
    static auto envelope(Geometry const&, Box const&,
                         util::enable_if_box_t<Geometry> * = nullptr)
    {
        return strategy::envelope::spherical_box();
    }

    template <typename Geometry, typename Box>
    static auto envelope(Geometry const&, Box const&,
                         util::enable_if_segment_t<Geometry> * = nullptr)
    {
        return strategy::envelope::spherical_segment<CalculationType>();
    }

    template <typename Geometry, typename Box>
    static auto envelope(Geometry const&, Box const&,
                         util::enable_if_linestring_t<Geometry> * = nullptr)
    {
        return strategy::envelope::spherical_linestring<CalculationType>();
    }

    template <typename Geometry, typename Box>
    static auto envelope(Geometry const&, Box const&,
                         std::enable_if_t
                            <
                                util::is_ring<Geometry>::value
                             || util::is_polygon<Geometry>::value
                            > * = nullptr)
    {
        return strategy::envelope::spherical_ring<CalculationType>();
    }

    template <typename Geometry, typename Box>
    static auto envelope(Geometry const&, Box const&,
                         std::enable_if_t
                            <
                                util::is_multi_linestring<Geometry>::value
                             || util::is_multi_polygon<Geometry>::value
                             || util::is_geometry_collection<Geometry>::value
                            > * = nullptr)
    {
        return strategy::envelope::spherical_boxes();
    }
};

} // namespace detail
#endif // DOXYGEN_NO_DETAIL

template <typename CalculationType = void>
class spherical
    : public strategies::envelope::detail::spherical<void, CalculationType>
{};

namespace services
{

template <typename Geometry, typename Box>
struct default_strategy<Geometry, Box, spherical_tag>
{
    using type = strategies::envelope::spherical<>;
};

template <typename Geometry, typename Box>
struct default_strategy<Geometry, Box, spherical_equatorial_tag>
{
    using type = strategies::envelope::spherical<>;
};

template <typename Geometry, typename Box>
struct default_strategy<Geometry, Box, spherical_polar_tag>
{
    using type = strategies::envelope::spherical<>;
};

template <>
struct strategy_converter<strategy::envelope::spherical_point>
{
    static auto get(strategy::envelope::spherical_point const& )
    {
        return strategies::envelope::spherical<>();
    }
};

template <>
struct strategy_converter<strategy::envelope::spherical_multipoint>
{
    static auto get(strategy::envelope::spherical_multipoint const&)
    {
        return strategies::envelope::spherical<>();
    }
};

template <>
struct strategy_converter<strategy::envelope::spherical_box>
{
    static auto get(strategy::envelope::spherical_box const& )
    {
        return strategies::envelope::spherical<>();
    }
};

template <typename CT>
struct strategy_converter<strategy::envelope::spherical_segment<CT> >
{
    static auto get(strategy::envelope::spherical_segment<CT> const&)
    {
        return strategies::envelope::spherical<CT>();
    }
};

template <typename CT>
struct strategy_converter<strategy::envelope::spherical<CT> >
{
    static auto get(strategy::envelope::spherical<CT> const&)
    {
        return strategies::envelope::spherical<CT>();
    }
};

} // namespace services

}} // namespace strategies::envelope

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_ENVELOPE_SPHERICAL_HPP


//
// Copyright © 2017 Arm Ltd. All rights reserved.
// SPDX-License-Identifier: MIT
//

#include "RefWorkloadUtils.hpp"
#include <backendsCommon/WorkloadData.hpp>
#include <armnn/Tensor.hpp>

#include <boost/assert.hpp>
#include "Splitter.hpp"

#include <cmath>
#include <limits>

#include "Decoders.hpp"
#include "Encoders.hpp"

namespace armnn
{

void Split(const SplitterQueueDescriptor& data)
{
    const TensorInfo& inputInfo = GetTensorInfo(data.m_Inputs[0]);

    std::unique_ptr<Decoder<float>> decoderPtr =
        MakeDecoder<float>(inputInfo, data.m_Inputs[0]->Map());
    Decoder<float>& decoder = *decoderPtr;

    for (unsigned int index = 0; index < inputInfo.GetNumElements(); ++index)
    {
        unsigned int indices[MaxNumOfTensorDimensions] = { 0 };

        unsigned int indexRemainder = index;
        unsigned int dimensionStride = inputInfo.GetNumElements();

        for (unsigned int i = 0; i<inputInfo.GetNumDimensions(); i++)
        {
            dimensionStride /= inputInfo.GetShape()[i];
            indices[i] = indexRemainder / dimensionStride; // Use integer division to round down.
            indexRemainder -= indices[i] * dimensionStride;
        }

        for (unsigned int viewIdx = 0; viewIdx < data.m_ViewOrigins.size(); ++viewIdx)
        {
            SplitterQueueDescriptor::ViewOrigin const& view = data.m_ViewOrigins[viewIdx];

            //Split view extents are defined by the size of (the corresponding) input tensor.
            const TensorInfo& outputInfo = GetTensorInfo(data.m_Outputs[viewIdx]);
            BOOST_ASSERT(outputInfo.GetNumDimensions() == inputInfo.GetNumDimensions());

            // Check all dimensions to see if this element is inside the given input view.
            bool insideView = true;
            for (unsigned int i = 0; i<outputInfo.GetNumDimensions(); i++)
            {
                if (indices[i] < view.m_Origin[i])
                {
                    insideView = false;
                }
                if (indices[i] >= view.m_Origin[i] + outputInfo.GetShape()[i])
                {
                    insideView = false;
                }
            }

            if (insideView)
            {
                std::unique_ptr<Encoder<float>> encoderPtr =
                    MakeEncoder<float>(outputInfo, data.m_Outputs[viewIdx]->Map());
                Encoder<float>& encoder = *encoderPtr;

                unsigned int outIndex = 0;
                unsigned int dimensionStride = 1;
                float inputValue = 0.f;

                for (unsigned int i = outputInfo.GetNumDimensions(); i-- > 0;)
                {
                    outIndex += dimensionStride * (indices[i] - view.m_Origin[i]);
                    dimensionStride *= outputInfo.GetShape()[i];
                }

                decoder += index;
                inputValue = decoder.Get();
                decoder -= index;

                encoder += outIndex;
                encoder.Set(inputValue);
                break;
            }
        }
    }
}

}


#pragma once

#define MAX_QUAD_POINTS -1

#include <Eigen/Dense>
#include <Eigen/Sparse>

namespace polyfem
{
	// Stack-allocated vectors of size either 2 or 3
	typedef Eigen::Matrix<double, Eigen::Dynamic, 1, 0, 3, 1> VectorNd;
	typedef Eigen::Matrix<double, 1, Eigen::Dynamic, Eigen::RowMajor, 1, 3> RowVectorNd;
	typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor, 3, 3> MatrixNd;

	typedef Eigen::Matrix<double, Eigen::Dynamic, 1, 0, MAX_QUAD_POINTS, 1> QuadratureVector;

#ifdef POLYSOLVE_LARGE_INDEX
	typedef Eigen::SparseMatrix<double, Eigen::ColMajor, std::ptrdiff_t> StiffnessMatrix;
#else
	typedef Eigen::SparseMatrix<double, Eigen::ColMajor> StiffnessMatrix;
#endif
} // namespace polyfem


//  Copyright John Maddock 2015.
//  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)

#ifdef _MSC_VER
#  pragma warning (disable : 4224)
#endif

#include <boost/math/special_functions/beta.hpp>
#include <boost/array.hpp>
#include <boost/lexical_cast.hpp>
#include "../../test/table_type.hpp"
#include "table_helper.hpp"
#include "performance.hpp"
#include <iostream>

typedef double T;
#define SC_(x) static_cast<double>(x)

int main()
{
#  include "ibeta_small_data.ipp"
#  include "ibeta_data.ipp"
#  include "ibeta_large_data.ipp"
#  include "ibeta_int_data.ipp"

   add_data(ibeta_small_data);
   add_data(ibeta_data);
   add_data(ibeta_large_data);
   add_data(ibeta_int_data);

   unsigned data_total = data.size();

   std::cout << "Screening boost data:\n";
   screen_data([](const std::vector<double>& v){  return boost::math::ibetac(v[0], v[1], v[2]);  }, [](const std::vector<double>& v){ return v[6];  });

#if defined(TEST_RMATH) && !defined(COMPILER_COMPARISON_TABLES)
   std::cout << "Screening libstdc++ data:\n";
      screen_data([](const std::vector<double>& v){  return ::pbeta(v[2], v[0], v[1], 0, 0);  }, [](const std::vector<double>& v){ return v[6];  });
#endif

   unsigned data_used = data.size();
   std::string function = "ibetac[br](" + boost::lexical_cast<std::string>(data_used) + "/" + boost::lexical_cast<std::string>(data_total) + " tests selected)";
   std::string function_short = "ibetac";

   double time;

   time = exec_timed_test([](const std::vector<double>& v){  return boost::math::ibetac(v[0], v[1], v[2]);  });
   std::cout << time << std::endl;
#if !defined(COMPILER_COMPARISON_TABLES) && (defined(TEST_GSL) || defined(TEST_RMATH))
   report_execution_time(time, std::string("Library Comparison with ") + std::string(compiler_name()) + std::string(" on ") + platform_name(), function, boost_name());
#endif
   report_execution_time(time, std::string("Compiler Comparison on ") + std::string(platform_name()), function_short, compiler_name() + std::string("[br]") + boost_name());
   //
   // Boost again, but with promotion to long double turned off:
   //
#if !defined(COMPILER_COMPARISON_TABLES)
   if(sizeof(long double) != sizeof(double))
   {
      time = exec_timed_test([](const std::vector<double>& v){  return boost::math::ibetac(v[0], v[1], v[2], boost::math::policies::make_policy(boost::math::policies::promote_double<false>()));  });
      std::cout << time << std::endl;
#if !defined(COMPILER_COMPARISON_TABLES) && (defined(TEST_GSL) || defined(TEST_RMATH))
      report_execution_time(time, std::string("Library Comparison with ") + std::string(compiler_name()) + std::string(" on ") + platform_name(), function, boost_name() + "[br]promote_double<false>");
#endif
      report_execution_time(time, std::string("Compiler Comparison on ") + std::string(platform_name()), function_short, compiler_name() + std::string("[br]") + boost_name() + "[br]promote_double<false>");
   }
#endif

#if defined(TEST_RMATH) && !defined(COMPILER_COMPARISON_TABLES)
   time = exec_timed_test([](const std::vector<double>& v){  return ::pbeta(v[2], v[0], v[1], 0, 0);  });
   std::cout << time << std::endl;
   report_execution_time(time, std::string("Library Comparison with ") + std::string(compiler_name()) + std::string(" on ") + platform_name(), function, "Rmath "  R_VERSION_STRING);
#endif

   return 0;
}


#include <iostream>                    // std::flush
#include <fstream>                     // std::ofstream
#include <string>                      // std::string
#include <sstream>                     // std::stringstream
#include <tuple>                       // std::tuple
#include <vector>                      // std::vector
// For External Library
#include <torch/torch.h>               // torch
#include <boost/program_options.hpp>   // boost::program_options
// For Original Header
#include "loss.hpp"                    // Loss
#include "networks.hpp"                // GAN_Generator, GAN_Discriminator
#include "dataloader.hpp"              // DataLoader::ImageFolderWithPaths
#include "visualizer.hpp"              // visualizer

// Define Namespace
namespace po = boost::program_options;

// -------------------
// Validation Function
// -------------------
void valid(po::variables_map &vm, DataLoader::ImageFolderWithPaths &valid_dataloader, torch::Device &device, Loss &criterion, GAN_Generator &gen, GAN_Discriminator &dis, const size_t epoch, visualizer::graph &writer, visualizer::graph &writer_dis){

    constexpr std::string_view extension = "jpg";  // the extension of file name to save sample images
    constexpr std::pair<float, float> output_range = {-1.0, 1.0};  // range of the value in output images

    // (0) Initialization and Declaration
    size_t iteration;
    size_t mini_batch_size;
    size_t max_counter;
    float ave_gen_loss, total_gen_loss;
    float ave_dis_real_loss, total_dis_real_loss;
    float ave_dis_fake_loss, total_dis_fake_loss;
    float value;
    std::stringstream ss;
    std::ofstream ofs;
    std::tuple<torch::Tensor, std::vector<std::string>> mini_batch;
    torch::Tensor image, fake_image, z;
    torch::Tensor dis_real_out, dis_fake_out;
    torch::Tensor gen_loss, dis_real_loss, dis_fake_loss;
    torch::Tensor label_real, label_fake;
    torch::Tensor output, outputs;

    // (1) Tensor Forward per Mini Batch
    torch::NoGradGuard no_grad;
    gen->eval();
    dis->eval();
    iteration = 0;
    total_gen_loss = 0.0; total_dis_real_loss = 0.0; total_dis_fake_loss = 0.0;
    while (valid_dataloader(mini_batch)){

        image = std::get<0>(mini_batch).to(device);
        mini_batch_size = image.size(0);

        // (1.1) Set Target Label
        label_real = torch::full({(long int)mini_batch_size}, /*value=*/1.0, torch::TensorOptions().dtype(torch::kFloat)).to(device);
        label_fake = torch::full({(long int)mini_batch_size}, /*value=*/0.0, torch::TensorOptions().dtype(torch::kFloat)).to(device);

        // (1.2) Discriminator Loss
        z = torch::randn({(long int)mini_batch_size, (long int)vm["nz"].as<size_t>()}).to(device);
        fake_image = gen->forward(z);
        dis_fake_out = dis->forward(fake_image).view({-1});
        dis_real_out = dis->forward(image).view({-1});
        dis_fake_loss = criterion(dis_fake_out, label_fake);
        dis_real_loss = criterion(dis_real_out, label_real);

        // (1.3) Generator Loss
        gen_loss = criterion(dis_fake_out, label_real);

        // (1.4) Update Loss
        total_gen_loss += gen_loss.item<float>();
        total_dis_real_loss += dis_real_loss.item<float>();
        total_dis_fake_loss += dis_fake_loss.item<float>();

        iteration++;
    }

    // (2) Calculate Average Loss
    ave_gen_loss = total_gen_loss / (float)iteration;
    ave_dis_real_loss = total_dis_real_loss / (float)iteration;
    ave_dis_fake_loss = total_dis_fake_loss / (float)iteration;

    // (3.1) Record Loss (Log)
    ofs.open("checkpoints/" + vm["dataset"].as<std::string>() + "/log/valid.txt", std::ios::app);
    ofs << "epoch:" << epoch << '/' << vm["epochs"].as<size_t>() << ' ' << std::flush;
    ofs << "G:" << ave_gen_loss << ' ' << std::flush;
    ofs << "D_Real:" << ave_dis_real_loss << ' ' << std::flush;
    ofs << "D_Fake:" << ave_dis_fake_loss << std::endl;
    ofs.close();

    // (3.2) Record Loss (Graph)
    writer.plot(/*base=*/epoch, /*value=*/{ave_gen_loss, ave_dis_real_loss + ave_dis_fake_loss});
    writer_dis.plot(/*base=*/epoch, /*value=*/{ave_dis_real_loss + ave_dis_fake_loss, ave_dis_real_loss, ave_dis_fake_loss});

    // (4) Image Generation
    max_counter = (int)(vm["valid_sigma_max"].as<float>() / vm["valid_sigma_inter"].as<float>() * 2) + 1;
    z = torch::full({1, (long int)vm["nz"].as<size_t>()}, /*value=*/-vm["valid_sigma_max"].as<float>(), torch::TensorOptions().dtype(torch::kFloat)).to(device);
    outputs = gen->forward(z);
    for (size_t i = 1; i < max_counter; i++){
        value = -vm["valid_sigma_max"].as<float>() + (float)i * vm["valid_sigma_inter"].as<float>();
        z = torch::full({1, (long int)vm["nz"].as<size_t>()}, /*value=*/value, torch::TensorOptions().dtype(torch::kFloat)).to(device);
        output = gen->forward(z);
        outputs = torch::cat({outputs, output}, /*dim=*/0);
    }
    ss.str(""); ss.clear(std::stringstream::goodbit);
    ss << "checkpoints/" << vm["dataset"].as<std::string>() << "/samples/epoch_" << epoch << "-valid."  << extension;
    visualizer::save_image(outputs.detach(), ss.str(), /*range=*/output_range, /*cols=*/max_counter);

    // End Processing
    return;

}


// ------------------------------------------------------------------------
//  Copyright (C)
//  Jordi Pont-Tuset <jponttuset@vision.ee.ethz.ch>
//  July 2015
// ------------------------------------------------------------------------

#ifndef MASK2POLY_HPP
#define MASK2POLY_HPP

#include <iostream>
#include <list>
#include <set>
#include <map>
#include <algorithm>
#include <cpp/containers.hpp>
#include <Eigen/Dense>

namespace PolyCont
{
    void explore_contour(Path& curr_cont,
                         std::vector<Junction>& junction_set,
                         Eigen::Matrix<bool, Eigen::Dynamic, Eigen::Dynamic>& visited,
                         Eigen::ArrayXXi& junction_ids,
                         const Eigen::Array<bool,Eigen::Dynamic,Eigen::Dynamic>& is_cont)
    {
        /* This will contain the next contour coordinate to explore at each iteration */
        Point next_to_visit = curr_cont[0];
        curr_cont.pop_back();

        /* Store the next 'Junction point' to be visited */
        Point junct_visited(junction_set[curr_cont.orig_junc-1].pos);
        Point junct_to_visit = junct_visited;

        /* Keep exploring until we find a Junction */
        bool found = false;
        while(!found)
        {
            /* Mark the visited Junction point as visited */
            visited(junct_visited.X+1,junct_visited.Y+1) = true;

            /* Get the coordinate to explore */
            Point cont_to_visit = next_to_visit;

            /* Is it a vertical or horizontal contour? */
            Point extreme1;
            Point extreme2;
            if (cont_to_visit.X % 2) // Vertical
            {
                extreme1.X = cont_to_visit.X+1;
                extreme1.Y = cont_to_visit.Y;
                extreme2.X = cont_to_visit.X-1;
                extreme2.Y = cont_to_visit.Y;
            }
            else // Horizontal
            {
                extreme1.X = cont_to_visit.X;
                extreme1.Y = cont_to_visit.Y-1;
                extreme2.X = cont_to_visit.X;
                extreme2.Y = cont_to_visit.Y+1;
            }

            /* Get the next Junction point to visit */
            if(!visited(extreme1.X+1,extreme1.Y+1))       /* Next 'Junction point' not visited */
            {
                junct_to_visit.X = extreme1.X;
                junct_to_visit.Y = extreme1.Y;
            }
            else if (!visited(extreme2.X+1,extreme2.Y+1)) /* Other not visited */
            {
                junct_to_visit.X = extreme2.X;
                junct_to_visit.Y = extreme2.Y;
            }
            else  /* Both visited, look for the Junction, which will be the end of the piece */
            {
                if (extreme1.X==junct_to_visit.X && extreme1.Y==junct_to_visit.Y)
                {
                    junct_to_visit.X = extreme2.X;
                    junct_to_visit.Y = extreme2.Y;
                }
                else
                {
                    assert(extreme2.X==junct_to_visit.X && extreme2.Y==junct_to_visit.Y);

                    junct_to_visit.X = extreme1.X;
                    junct_to_visit.Y = extreme1.Y;
                }
            }

            /* Mark the contour and Junction as visited */
            visited( cont_to_visit.X+1, cont_to_visit.Y+1) = true;
            visited(junct_to_visit.X+1,junct_to_visit.Y+1) = true;

            /* Is the 'Junction point' to be visited really a Junction? */
            if (junction_ids(junct_to_visit.X,junct_to_visit.Y)>0) /* Yes it is, so we finish */
            {
                found = true;
                curr_cont.end_junc = junction_ids(junct_to_visit.X,junct_to_visit.Y);

    //             std::ostringstream stringStream;
    //             stringStream << "End Junction found (" << junct_to_visit.X << "," << junct_to_visit.Y << ") id:" << curr_cont.end_junc << std::endl;
    //             mexPrintf(stringStream.str().c_str());
            }
            else /* It is not a Junction, keep visiting */
            {
                /* Look for the next unvisited direction with contour*/
                if      (is_cont(junct_to_visit.X+1+1, junct_to_visit.Y  +1) && !visited(junct_to_visit.X+1+1,junct_to_visit.Y  +1))
                    next_to_visit = Point(junct_to_visit.X+1,junct_to_visit.Y);
                else if (is_cont(junct_to_visit.X-1+1, junct_to_visit.Y  +1) && !visited(junct_to_visit.X-1+1,junct_to_visit.Y  +1))
                    next_to_visit = Point(junct_to_visit.X-1,junct_to_visit.Y);
                else if (is_cont(junct_to_visit.X  +1, junct_to_visit.Y+1+1) && !visited(junct_to_visit.X  +1,junct_to_visit.Y+1+1))
                    next_to_visit = Point(junct_to_visit.X,junct_to_visit.Y+1);
                else
                {
                    assert(is_cont(junct_to_visit.X  +1,junct_to_visit.Y-1+1) && !visited(junct_to_visit.X  +1,junct_to_visit.Y-1+1));
//                     {
//                         std::ostringstream stringStream;
//                         stringStream << "Oh oh: Position (" << junct_to_visit.X << "," << junct_to_visit.Y << ") no unvisited contour found!";
//                         mexErrMsgTxt(stringStream.str().c_str());
//                     }
                    next_to_visit = Point(junct_to_visit.X,junct_to_visit.Y-1);
                }
            }

            /* Add the contour coordinate to the list */
            curr_cont.push_back(Point(cont_to_visit.X,cont_to_visit.Y));
    //         mexPrintf("  Explored (%d,%d)\n",cont_to_visit.X,cont_to_visit.Y);
        }
    }

    /* Implementation of the actual function: from a mask we get the
     * set of junctions and the set of contour pieces that form it.
     */
    template<typename ArrayOrMap>
    void mask2poly(ArrayOrMap & mask,                    /* Input mask */
                   ContContainer& all_cont_pieces,       /* Vector containing the finished contour pieces */
                   Eigen::ArrayXXi& junction_ids,        /* Position of all junctions in the contour grid */
                   std::vector<Junction>& junction_set)  /* Vector containing all Junction descriptions   */
    {
        /* Input size */
        int sx = (int)mask.rows();
        int sy = (int)mask.cols();

        /* Add padding to mask */
        Eigen::Array<bool,Eigen::Dynamic,Eigen::Dynamic> mask_pad = Eigen::Array<bool,Eigen::Dynamic,Eigen::Dynamic>::Zero(sx+2, sy+2);
        for (int xx=1; xx<=sx; ++xx)
            for (int yy=1; yy<=sy; ++yy)
                mask_pad(xx,yy) = mask(xx-1,yy-1);

        /*--------------------------------------------------------------------*/
        /*           Sweep in all directions to get contour places            */
        /*--------------------------------------------------------------------*/
        Eigen::Array<bool,Eigen::Dynamic,Eigen::Dynamic> is_cont = Eigen::Array<bool,Eigen::Dynamic,Eigen::Dynamic>::Zero(2*sx+3,2*sy+3);

        /* Vertical contours */
        for (int xx=0; xx<sx; ++xx)
            for (int yy=-1; yy<sy; ++yy)
                is_cont(2*xx+1+1,2*yy+2+1) = (mask_pad(xx+1,yy+1)^mask_pad(xx+1,yy+1+1));

        /* Horizontal contours */
        for (int xx=-1; xx<sx; ++xx)
            for (int yy=0; yy<sy; ++yy)
                is_cont(2*xx+2+1,2*yy+1+1) = (mask_pad(xx+1,yy+1)^mask_pad(xx+1+1,yy+1));

    //     /* Debug */
    //     mexPrintf("is_cont\n");
    //     for(std::size_t ii=0; ii<is_cont.rows(); ++ii)
    //     {
    //         for(std::size_t jj=0; jj<is_cont.cols(); ++jj)
    //             if (is_cont(ii,jj))
    //                 mexPrintf("1 ");
    //             else
    //                 mexPrintf("0 ");
    //         mexPrintf("\n");
    //     }

        /*-------------------------------------------*/
        /*            Look for junctions             */
        /*-------------------------------------------*/
        /* Containers */
        junction_ids = Eigen::ArrayXXi::Zero(2*sx+1,2*sy+1);
        std::size_t n_junctions = 0;

        /*       1
         *       |
         *  2 -- X -- 4
         *       |
         *       3
         */
        for (std::size_t xx=2; xx<2*sx; xx+=2)
        {
            for (std::size_t yy=2; yy<2*sy; yy+=2)
            {
                Junction tmp_jct = Junction(n_junctions+1,xx,yy);

                std::set<double> curr_neighs;
                if (is_cont(xx  +1,yy+1+1) &
                    is_cont(xx+1+1,yy  +1) &
                    is_cont(xx  +1,yy-1+1) &
                    is_cont(xx-1+1,yy  +1))
                {
                    tmp_jct.cont_pos.push_back(Point(xx  ,yy+1));
                    tmp_jct.cont_pos.push_back(Point(xx+1,yy  ));
                    tmp_jct.cont_pos.push_back(Point(xx  ,yy-1));
                    tmp_jct.cont_pos.push_back(Point(xx-1,yy  ));
                    junction_set.push_back(tmp_jct);
                    junction_ids(xx,yy) = (int)n_junctions + 1;
                    n_junctions++;
                }
            }
        }

    //     /* Debug */
    //     mexPrintf("junction_ids\n");
    //     for(std::size_t ii=0; ii<junction_ids.rows(); ++ii)
    //     {
    //         for(std::size_t jj=0; jj<junction_ids.cols(); ++jj)
    //             mexPrintf("%d ",junction_ids(ii,jj));
    //         mexPrintf("\n");
    //     }

        /*--------------------------------------------------------------------*/
        /*     Sweep all contour places and connect them in a sorted way      */
        /*--------------------------------------------------------------------*/
        /* Vector containing the next contours to explore */
        std::vector<Path> to_explore;
        /* Mask of the visited positions */
        Eigen::Matrix<bool,Eigen::Dynamic,Eigen::Dynamic> visited = Eigen::Matrix<bool,Eigen::Dynamic,Eigen::Dynamic>::Zero(2*sx+3,2*sy+3);

        /* Explore all junctions to start from there. Please note that the hole contours will *
         * not be added here because they have no Junction, but they will be added afterwards */
        for(std::size_t jj=0; jj<junction_set.size(); ++jj)
            for(std::size_t ii=0; ii<junction_set[jj].cont_pos.size(); ++ii)
                to_explore.push_back(Path(junction_set[jj].id,
                                          junction_set[jj].cont_pos[ii]));

        while (to_explore.size()>0)
        {
            /* Get the current contour to explore */
            Path curr_cont = to_explore.back();
            to_explore.pop_back();

            /* Check it was not explored before */
            if(visited(curr_cont.back().X+1,curr_cont.back().Y+1))
                continue;

    //         mexPrintf("Exploring (%d,%d)\n",curr_cont.back().X,curr_cont.back().Y);
            /* Do the actual exploration */
            explore_contour(curr_cont, junction_set, visited, junction_ids, is_cont);

            /* Store it as finished */
            all_cont_pieces.push_back(curr_cont);

    //         /* Debug */
    //         mexPrintf("visited iteration %d\n",to_explore.size());
    //         for(std::size_t ii=0; ii<visited.rows(); ++ii)
    //         {
    //             for(std::size_t jj=0; jj<visited.cols(); ++jj)
    //                 if (visited(ii,jj))
    //                     mexPrintf("1 ");
    //                 else
    //                     mexPrintf("0 ");
    //             mexPrintf("\n");
    //         }
        }

        /*--------------------------------------------------------------------*/
        /*   Rescan contours to find holes and create artificial junctions    */
        /*--------------------------------------------------------------------*/

        /* Vertical contours */
        /* Horizontal contours are not necessary because there will always be a vertical contour in any hole */
        for (int xx=0; xx<sx; ++xx)
        {
            for (int yy=-1; yy<sy; ++yy)
            {
                if (is_cont(2*xx+1+1,2*yy+2+1) && (visited(2*xx+1+1,2*yy+2+1)==0))
                {
                    /* Create an 'artificial' Junction */
                    n_junctions++;
                    Junction tmp_jct(n_junctions,2*xx,2*yy+2);
                    tmp_jct.cont_pos.push_back(Point(2*xx+1,2*yy+2));

                    /* Add it to the set of junctions */
                    junction_set.push_back(tmp_jct);
                    junction_ids(2*xx,2*yy+2) = (int)n_junctions;

                    /* Create the contour piece to explore */
                    Path curr_cont = Path(tmp_jct.id,
                                                      tmp_jct.cont_pos[0]);

                    /* Do the actual exploration */
                    explore_contour(curr_cont, junction_set, visited, junction_ids, is_cont);

                    /* Store it as finished */
                    all_cont_pieces.push_back(curr_cont);
                }
            }
        }

        /*-------------------------------------------*/
        /*       Fill Junction contour pieces        */
        /*-------------------------------------------*/
        for (std::size_t ii=0; ii<all_cont_pieces.size(); ++ii)
        {
            junction_set[all_cont_pieces[ii].orig_junc-1].cont_pieces.insert(ii);
            junction_set[all_cont_pieces[ii].end_junc-1].cont_pieces.insert(ii);
        }
    }
}

#endif


#include <iostream>
#include <memory>
#include <string>
#include <boost/program_options.hpp>
#include <univalue.h>
#include <insertionfinder/algorithm.hpp>
#include <insertionfinder/cube.hpp>
#include "commands.hpp"
namespace po = boost::program_options;
using InsertionFinder::Algorithm;
using InsertionFinder::AlgorithmError;
using InsertionFinder::Cube;
namespace CLI = InsertionFinder::CLI;

namespace {
    struct CycleStatus {
        Algorithm scramble;
        Algorithm skeleton;
        bool parity;
        int corner_cycles;
        int edge_cycles;
        int center_cycles;
    };

    CycleStatus verify(const std::string& scramble_string, const std::string& skeleton_string) try {
        Algorithm scramble(scramble_string);
        Algorithm skeleton(skeleton_string);
        scramble.simplify();
        skeleton.simplify();
        Cube cube = Cube() * scramble * skeleton;
        const Cube placed_cube = cube.best_placement();
        return {
            scramble, skeleton,
            placed_cube.has_parity(),
            placed_cube.corner_cycles(),
            placed_cube.edge_cycles(),
            Cube::center_cycles[placed_cube.placement()]
        };
    } catch (const AlgorithmError& e) {
        throw CLI::CommandExecutionError(e.what());
    }
};

namespace {
    struct Printer {
        virtual void print_result(const CycleStatus& status) = 0;
        virtual ~Printer() = default;
    };

    struct StandardPrinter: Printer {
        void print_result(const CycleStatus& status) override {
            std::cout << "Scramble: " << status.scramble << std::endl;
            std::cout << "Skeleton: " << status.skeleton << std::endl;
            std::cout << "The cube ";
            if (!status.parity && status.corner_cycles == 0 && status.edge_cycles == 0 && status.center_cycles == 0) {
                std::cout << "is already solved";
            } else {
                std::cout << "has ";
                if (status.center_cycles) {
                    if (status.center_cycles > 1) {
                        std::cout << "parity center rotation";
                    } else {
                        std::cout << "center rotation";
                    }
                    if (status.corner_cycles || status.edge_cycles) {
                        std::cout << " with ";
                    }
                }
                if (status.corner_cycles == 1) {
                    std::cout << "1 corner-3-cycle";
                } else if (status.corner_cycles > 1) {
                    std::cout << status.corner_cycles << " corner-3-cycles";
                }
                if (status.corner_cycles && status.edge_cycles) {
                    std::cout << " and ";
                }
                if (status.edge_cycles == 1) {
                    std::cout << "1 edge-3-cycle";
                } else if (status.edge_cycles > 1) {
                    std::cout << status.edge_cycles << " edge-3-cycles";
                }
                if (status.center_cycles <= 1 && status.parity) {
                    if (status.corner_cycles || status.edge_cycles) {
                        std::cout << " with parity";
                    } else {
                        std::cout << "parity";
                    }
                }
            }
            std::cout << '.' << std::endl;
        }
    };

    struct JSONPrinter: Printer {
        void print_result(const CycleStatus& status) override {
            UniValue map(UniValue::VOBJ);
            map.pushKV("scramble", status.scramble.str());
            map.pushKV("skeleton", status.skeleton.str());
            map.pushKV("parity", status.parity);
            map.pushKV("corner_cycles", status.corner_cycles);
            map.pushKV("edge_cycles", status.edge_cycles);
            map.pushKV("center_cycles", status.center_cycles);
            std::cout << map.write() << std::flush;
        }
    };
};

void CLI::verify_cube(const po::variables_map& vm) {
    std::unique_ptr<Printer> printer;
    if (vm.count("json")) {
        printer = std::make_unique<JSONPrinter>();
    } else {
        printer = std::make_unique<StandardPrinter>();
    }
    std::string scramble_string;
    std::string skeleton_string;
    std::getline(std::cin, scramble_string);
    std::getline(std::cin, skeleton_string);
    CycleStatus status = verify(scramble_string, skeleton_string);
    printer->print_result(status);
}


/**
 * @file layer_names.hpp
 * @author Sreenik Seal
 *
 * Implementation of a class that converts a given ann layer to string format.
 *
 * mlpack is free software; you may redistribute it and/or modify it under the
 * terms of the 3-clause BSD license.  You should have received a copy of the
 * 3-clause BSD license along with mlpack.  If not, see
 * http://www.opensource.org/licenses/BSD-3-Clause for more information.
 */

#include <mlpack/core.hpp>
#include <mlpack/methods/ann/layer/layer.hpp>
#include <mlpack/methods/ann/layer/layer_types.hpp>
#include <boost/variant/static_visitor.hpp>
#include <string>

using namespace mlpack::ann;

/**
 * Implementation of a class that returns the string representation of the
 * name of the given layer.
 */
class LayerNameVisitor : public boost::static_visitor<std::string>
{
 public:
  //! Create the LayerNameVisitor object.
  LayerNameVisitor()
  {
  }

  /**
   * Return the name of the given layer of type AdaptiveMaxPooling as string.
   *
   * @param Given layer of type AdaptiveMaxPooling.
   * @return The string representation of the layer.
   */
  std::string LayerString(AdaptiveMaxPooling<> * /*layer*/) const
  {
    return "adaptivemaxpooling";
  }

  /**
   * Return the name of the given layer of type AdaptiveMeanPooling as string.
   *
   * @param Given layer of type AdaptiveMeanPooling.
   * @return The string representation of the layer.
   */
  std::string LayerString(AdaptiveMeanPooling<> * /*layer*/) const
  {
    return "adaptivemeanpooling";
  }

  /**
   * Return the name of the given layer of type AtrousConvolution as a string.
   *
   * @param Given layer of type AtrousConvolution.
   * @return The string representation of the layer.
   */
  std::string LayerString(AtrousConvolution<>* /*layer*/) const
  {
    return "atrousconvolution";
  }

  /**
   * Return the name of the given layer of type AlphaDropout as a string.
   *
   * @param Given layer of type AlphaDropout.
   * @return The string representation of the layer.
   */
  std::string LayerString(AlphaDropout<>* /*layer*/) const
  {
    return "alphadropout";
  }

  /**
   * Return the name of the given layer of type BatchNorm as a string.
   *
   * @param Given layer of type BatchNorm.
   * @return The string representation of the layer.
   */
  std::string LayerString(BatchNorm<>* /*layer*/) const
  {
    return "batchnorm";
  }

  /**
   * Return the name of the given layer of type Constant as a string.
   *
   * @param Given layer of type Constant.
   * @return The string representation of the layer.
   */
  std::string LayerString(Constant<>* /*layer*/) const
  {
    return "constant";
  }

  /**
   * Return the name of the given layer of type Convolution as a string.
   *
   * @param Given layer of type Convolution.
   * @return The string representation of the layer.
   */
  std::string LayerString(Convolution<>* /*layer*/) const
  {
    return "convolution";
  }

  /**
   * Return the name of the given layer of type DropConnect as a string.
   *
   * @param Given layer of type DropConnect.
   * @return The string representation of the layer.
   */
  std::string LayerString(DropConnect<>* /*layer*/) const
  {
    return "dropconnect";
  }

  /**
   * Return the name of the given layer of type Dropout as a string.
   *
   * @param Given layer of type Dropout.
   * @return The string representation of the layer.
   */
  std::string LayerString(Dropout<>* /*layer*/) const
  {
    return "dropout";
  }

  /**
   * Return the name of the given layer of type FlexibleReLU as a string.
   *
   * @param Given layer of type FlexibleReLU.
   * @return The string representation of the layer.
   */
  std::string LayerString(FlexibleReLU<>* /*layer*/) const
  {
    return "flexiblerelu";
  }

  /**
   * Return the name of the given layer of type LayerNorm as a string.
   *
   * @param Given layer of type LayerNorm.
   * @return The string representation of the layer.
   */
  std::string LayerString(LayerNorm<>* /*layer*/) const
  {
    return "layernorm";
  }

  /**
   * Return the name of the given layer of type Linear as a string.
   *
   * @param Given layer of type Linear.
   * @return The string representation of the layer.
   */
  std::string LayerString(Linear<>* /*layer*/) const
  {
    return "linear";
  }

  /**
   * Return the name of the given layer of type LinearNoBias as a string.
   *
   * @param Given layer of type LinearNoBias.
   * @return The string representation of the layer.
   */
  std::string LayerString(LinearNoBias<>* /*layer*/) const
  {
    return "linearnobias";
  }

  /**
   * Return the name of the given layer of type MaxPooling as a string.
   *
   * @param Given layer of type MaxPooling.
   * @return The string representation of the layer.
   */
  std::string LayerString(MaxPooling<>* /*layer*/) const
  {
    return "maxpooling";
  }

  /**
   * Return the name of the given layer of type MeanPooling as a string.
   *
   * @param Given layer of type MeanPooling.
   * @return The string representation of the layer.
   */
  std::string LayerString(MeanPooling<>* /*layer*/) const
  {
    return "meanpooling";
  }

  /**
   * Return the name of the given layer of type MultiplyConstant as a string.
   *
   * @param Given layer of type MultiplyConstant.
   * @return The string representation of the layer.
   */
  std::string LayerString(MultiplyConstant<>* /*layer*/) const
  {
    return "multiplyconstant";
  }

  /**
   * Return the name of the given layer of type ReLULayer as a string.
   *
   * @param Given layer of type ReLULayer.
   * @return The string representation of the layer.
   */
  std::string LayerString(ReLULayer<>* /*layer*/) const
  {
    return "relu";
  }

  /**
   * Return the name of the given layer of type TransposedConvolution as a
   * string.
   *
   * @param Given layer of type TransposedConvolution.
   * @return The string representation of the layer.
   */
  std::string LayerString(TransposedConvolution<>* /*layer*/) const
  {
    return "transposedconvolution";
  }

  /**
   * Return the name of the given layer of type IdentityLayer as a string.
   *
   * @param Given layer of type IdentityLayer.
   * @return The string representation of the layer.
   */
  std::string LayerString(IdentityLayer<>* /*layer*/) const
  {
    return "identity";
  }

  /**
   * Return the name of the given layer of type TanHLayer as a string.
   *
   * @param Given layer of type TanHLayer.
   * @return The string representation of the layer.
   */
  std::string LayerString(TanHLayer<>* /*layer*/) const
  {
    return "tanh";
  }

  /**
   * Return the name of the given layer of type ELU as a string.
   *
   * @param Given layer of type ELU.
   * @return The string representation of the layer.
   */
  std::string LayerString(ELU<>* /*layer*/) const
  {
    return "elu";
  }

  /**
   * Return the name of the given layer of type HardTanH as a string.
   *
   * @param Given layer of type HardTanH.
   * @return The string representation of the layer.
   */
  std::string LayerString(HardTanH<>* /*layer*/) const
  {
    return "hardtanh";
  }

  /**
   * Return the name of the given layer of type LeakyReLU as a string.
   *
   * @param Given layer of type LeakyReLU.
   * @return The string representation of the layer.
   */
  std::string LayerString(LeakyReLU<>* /*layer*/) const
  {
    return "leakyrelu";
  }

  /**
   * Return the name of the given layer of type PReLU as a string.
   *
   * @param Given layer of type PReLU.
   * @return The string representation of the layer.
   */
  std::string LayerString(PReLU<>* /*layer*/) const
  {
    return "prelu";
  }

  /**
   * Return the name of the given layer of type SigmoidLayer as a string.
   *
   * @param Given layer of type SigmoidLayer.
   * @return The string representation of the layer.
   */
  std::string LayerString(SigmoidLayer<>* /*layer*/) const
  {
    return "sigmoid";
  }

  /**
   * Return the name of the given layer of type LogSoftMax as a string.
   *
   * @param Given layer of type LogSoftMax.
   * @return The string representation of the layer.
   */
  std::string LayerString(LogSoftMax<>* /*layer*/) const
  {
    return "logsoftmax";
  }

  /*
   * Return the name of the given layer of type LSTM as a string.
   *
   * @param Given layer of type LSTM.
   * @return The string representation of the layer.
   */
  std::string LayerString(LSTM<>* /*layer*/) const
  {
    return "lstm";
  }

  /**
   * Return the name of the given layer of type CReLU as a string.
   *
   * @param Given layer of type CReLU.
   * @return The string representation of the layer.
   */
  std::string LayerString(CReLU<>* /*layer*/) const
  {
    return "crelu";
  }

  /**
   * Return the name of the given layer of type Highway as a string.
   *
   * @param Given layer of type Highway.
   * @return The string representation of the layer.
   */
  std::string LayerString(Highway<>* /*layer*/) const
  {
    return "highway";
  }

  /**
   * Return the name of the given layer of type GRU as a string.
   *
   * @param Given layer of type GRU.
   * @return The string representation of the layer.
   */
  std::string LayerString(GRU<>* /*layer*/) const
  {
    return "gru";
  }

  /**
   * Return the name of the given layer of type Glimpse as a string.
   *
   * @param Given layer of type Glimpse.
   * @return The string representation of the layer.
   */
  std::string LayerString(Glimpse<>* /*layer*/) const
  {
    return "glimpse";
  }

  /**
   * Return the name of the given layer of type FastLSTM as a string.
   *
   * @param Given layer of type FastLSTM.
   * @return The string representation of the layer.
   */
  std::string LayerString(FastLSTM<>* /*layer*/) const
  {
    return "fastlstm";
  }

  /**
   * Return the name of the given layer of type WeightNorm as a string.
   *
   * @param Given layer of type WeightNorm.
   * @return The string representation of the layer.
   */
  std::string LayerString(WeightNorm<>* /*layer*/) const
  {
    return "weightnorm";
  }

  /**
   * Return the name of the layer of specified type as a string.
   *
   * @param Given layer of any type.
   * @return A string declaring that the layer is unsupported.
   */
  template<typename T>
  std::string LayerString(T* /*layer*/) const
  {
    return "unsupported";
  }

  //! Overload function call.
  std::string operator()(MoreTypes layer) const
  {
    return layer.apply_visitor(*this);
  }

  //! Overload function call.
  template<typename LayerType>
  std::string operator()(LayerType* layer) const
  {
    return LayerString(layer);
  }
};


//==============================================================================
//         Copyright 2003 - 2011   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 20011 - 2011   LRI    UMR 11623 CNRS/Univ Paris Sud XI
//
//          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
//==============================================================================
#ifndef BOOST_SIMD_CONSTANT_CONSTANTS_FACT_11_HPP_INCLUDED
#define BOOST_SIMD_CONSTANT_CONSTANTS_FACT_11_HPP_INCLUDED

#include <boost/simd/include/functor.hpp>
#include <boost/simd/constant/register.hpp>
#include <boost/simd/constant/hierarchy.hpp>
#include <boost/config.hpp>

#ifdef BOOST_MSVC
#pragma warning(push)
#pragma warning(disable: 4310) // truncation of constant
#endif

namespace boost { namespace simd
{
  namespace tag
  {
   /*!
     @brief Fact_11 generic tag

     Represents the Fact_11 constant in generic contexts.

     @par Models:
        Hierarchy
   **/
    BOOST_SIMD_CONSTANT_REGISTER( Fact_11,double
                                , 39916800,0x4c184540,0x418308a800000000ll
                                )
  }
  namespace ext
  {
   template<class Site>
   BOOST_FORCEINLINE generic_dispatcher<tag::Fact_11, Site> dispatching_Fact_11(adl_helper, boost::dispatch::meta::unknown_<Site>, ...)
   {
     return generic_dispatcher<tag::Fact_11, Site>();
   }
   template<class... Args>
   struct impl_Fact_11;
  }
  /*!
    Generates 11! that is 39916800,

    @par Semantic:

    @code
    T r = Fact_11<T>();
    @endcode
    is similar to:

    @code
    T r = T(39916800);
    @endcode
  **/
  BOOST_SIMD_CONSTANT_IMPLEMENTATION(boost::simd::tag::Fact_11, Fact_11)
} }

#ifdef BOOST_MSVC
#pragma warning(pop)
#endif

#include <boost/simd/constant/common.hpp>

#endif


/**
 * relocvis: main.cpp
 * Copyright (c) Torr Vision Group, University of Oxford, 2016. All rights reserved.
 */

#include <algorithm>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
#include <Eigen/Geometry>
#include <map>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <opencv2/viz.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <sstream>
#include "tvgutil/filesystem/PathFinder.h"
#include "tvgutil/timing/TimeUtil.h"

namespace fs = boost::filesystem;
using namespace cv::viz;

//#################### FUNCTIONS ####################

fs::path generate_path(const fs::path basePath, const std::string &mask,
    int index)
{
  char buf[2048];
  sprintf(buf, mask.c_str(), index);

  return basePath / buf;
}

Eigen::Matrix4f read_pose_from_file(const fs::path &fileName)
{
  if (!fs::is_regular(fileName))
    throw std::runtime_error("File not found: " + fileName.string());

  std::ifstream in(fileName.c_str());

  Eigen::Matrix4f res;

  in >> res(0, 0) >> res(0, 1) >> res(0, 2) >> res(0, 3);
  in >> res(1, 0) >> res(1, 1) >> res(1, 2) >> res(1, 3);
  in >> res(2, 0) >> res(2, 1) >> res(2, 2) >> res(2, 3);
  in >> res(3, 0) >> res(3, 1) >> res(3, 2) >> res(3, 3);

  return res;
}

std::vector<Eigen::Matrix4f> read_sequence_trajectory(const fs::path &basePath,
    const std::string &fileMask)
{
  std::vector<Eigen::Matrix4f> res;

  while (true)
  {
    const fs::path posePath = generate_path(basePath, fileMask, res.size());

    if (!fs::is_regular(posePath))
      break;

    res.push_back(read_pose_from_file(posePath));
  }

  return res;
}

cv::Affine3f eigen_to_cv(const Eigen::Matrix4f &eigM)
{
  cv::Affine3f res;

  for (int i = 0; i < 16; ++i)
    res.matrix.val[i] = eigM.data()[i];

  // Col Major to row major
  res.matrix = res.matrix.t();

  return res;
}

std::vector<cv::Affine3f> eigen_to_cv(
    const std::vector<Eigen::Matrix4f> &trajectory)
{
  std::vector<cv::Affine3f> res;

  for (auto eigM : trajectory)
  {
    res.push_back(eigen_to_cv(eigM));
  }

  return res;
}

std::vector<cv::Affine3f> subsample_trajectory(
    const std::vector<cv::Affine3f> &trajectory, int factor)
{
  std::vector<cv::Affine3f> res;

  for (size_t i = 0; i < trajectory.size(); i += factor)
  {
    res.push_back(trajectory[i]);
  }

  return res;
}

std::vector<std::vector<cv::Affine3f>> split_trajectory(
    const std::vector<cv::Affine3f> &trajectory, int every)
{
  std::vector<std::vector<cv::Affine3f>> res;

  for (size_t i = 0; i < trajectory.size(); ++i)
  {
    if (i % every == 0)
      res.push_back(std::vector<cv::Affine3f>());

    res.back().push_back(trajectory[i]);
  }

  return res;
}

float angular_separation(const Eigen::Matrix3f& r1, const Eigen::Matrix3f& r2)
{
  // First calculate the rotation matrix which maps r1 to r2.
  Eigen::Matrix3f dr = r2 * r1.transpose();

  Eigen::AngleAxisf aa(dr);
  return aa.angle();
}

bool pose_matches(const Eigen::Matrix4f &gtPose,
    const Eigen::Matrix4f &testPose, float translationMaxError,
    float angleMaxError)
{
  const Eigen::Matrix3f gtR = gtPose.block<3, 3>(0, 0);
  const Eigen::Matrix3f testR = testPose.block<3, 3>(0, 0);
  const Eigen::Vector3f gtT = gtPose.block<3, 1>(0, 3);
  const Eigen::Vector3f testT = testPose.block<3, 1>(0, 3);

  const float translationError = (gtT - testT).norm();
  const float angleError = angular_separation(gtR, testR);
//  const float angleError = angular_separation_approx(gtR, testR);

  return translationError <= translationMaxError && angleError <= angleMaxError;
}

struct TestICPPair
{
  Eigen::Matrix4f trainPose;
  Eigen::Matrix4f testPose;
  Eigen::Matrix4f icpPose;

  size_t trainIdx;
  size_t testIdx;
};

struct ErrorThreshold
{
  float translationMaxError;
  float angleMaxError;
  Color color;
};

std::vector<std::vector<TestICPPair>> classify_poses(
    const std::vector<Eigen::Matrix4f> &trainPoses,
    const std::vector<Eigen::Matrix4f> &testPoses,
    const std::vector<Eigen::Matrix4f> &icpPoses,
    const std::vector<ErrorThreshold> &thresholds)
{
  std::vector<std::vector<TestICPPair>> res;

  res.resize(thresholds.size() + 1);

  // For each test pose:
  for (size_t testIndex = 0, testCount = testPoses.size();
      testIndex < testCount; ++testIndex)
  {
    const Eigen::Matrix4f& testPose = testPoses[testIndex];
    const Eigen::Matrix4f& icpPose = icpPoses[testIndex];

    if (!pose_matches(testPose, icpPose, 0.05f, 5.f * M_PI / 180.f))
      continue;

    size_t closestTrainingIdx = std::numeric_limits<size_t>::max();
    float closestTrainPoseDist = std::numeric_limits<float>::max();
    Eigen::Matrix4f closestTrainPose;
    closestTrainPose.setConstant(std::numeric_limits<float>::quiet_NaN());

    // Determine a difficulty bin for the test.
    size_t chosenBin = thresholds.size();
    for (size_t trainIndex = 0, trainCount = trainPoses.size();
        trainIndex < trainCount; ++trainIndex)
    {
      const Eigen::Matrix4f& trainPose = trainPoses[trainIndex];
      for (size_t binIndex = 0; binIndex < chosenBin; ++binIndex)
      {
        if (pose_matches(trainPose, testPose,
            thresholds[binIndex].translationMaxError,
            thresholds[binIndex].angleMaxError))
        {
          const float dist = (trainPose.block<3, 1>(0, 3)
              - testPose.block<3, 1>(0, 3)).norm();
          if (dist < closestTrainPoseDist)
          {
            closestTrainPoseDist = dist;
            closestTrainPose = trainPose;
            closestTrainingIdx = trainIndex;
            chosenBin = binIndex;
          }
        }
      }
    }

    // Store the test/icp pair in the right bucket
    res[chosenBin].push_back(TestICPPair
    { closestTrainPose, testPose, icpPose, closestTrainingIdx, testIndex });
  }

  return res;
}

std::vector<std::vector<TestICPPair> > prune_near_poses(
    const std::vector<std::vector<TestICPPair> > &poses)
{
  std::vector<std::vector<TestICPPair> > result(poses.size());
  for (size_t binIdx = poses.size() - 1; binIdx < poses.size(); --binIdx)
  {
    for (const TestICPPair &pose : poses[binIdx])
    {
      Eigen::Vector3f candidateTestT = pose.testPose.block<3, 1>(0, 3);
//      Eigen::Vector3f candidateTrainT = pose.trainPose.block<3, 1>(0, 3);

      // If there are no similar poses in the results vector insert the current candidate
      bool insert = true;
      for (size_t checkBinIdx = result.size() - 1;
          checkBinIdx < result.size() && insert; --checkBinIdx)
      {
        for (size_t i = 0; i < result[checkBinIdx].size() && insert; ++i)
        {
          auto &resultPose = result[checkBinIdx][i];
          Eigen::Vector3f resultTestT = resultPose.testPose.block<3, 1>(0, 3);
//          Eigen::Vector3f resultTrainT = resultPose.trainPose.block<3, 1>(0, 3);
          Eigen::Vector3f diffTest = candidateTestT - resultTestT;
//          Eigen::Vector3f diffTrain = candidateTrainT - resultTrainT;

          if (diffTest.norm() < 0.5f) // || diffTrain.norm() < 0.5)
          {
            insert = false;
          }
        }
      }

      if (insert)
      {
        result[binIdx].push_back(pose);
      }
    }
  }

  return result;
}

std::vector<TestICPPair> prune_near_poses(const std::vector<TestICPPair> &poses)
{
  std::vector<TestICPPair> result;
  for (const TestICPPair &pose : poses)
  {
    Eigen::Vector3f candidateTestT = pose.testPose.block<3, 1>(0, 3);
    Eigen::Vector3f candidateTrainT = pose.trainPose.block<3, 1>(0, 3);

    // If there are no similar poses in the results vector insert the current candidate
    bool insert = true;
    for (size_t i = 0; i < result.size() && insert; ++i)
    {
      auto &resultPose = result[i];
      Eigen::Vector3f resultTestT = resultPose.testPose.block<3, 1>(0, 3);
      Eigen::Vector3f resultTrainT = resultPose.trainPose.block<3, 1>(0, 3);
      Eigen::Vector3f diffTest = candidateTestT - resultTestT;
      Eigen::Vector3f diffTrain = candidateTrainT - resultTrainT;

      if (diffTest.norm() < 0.5f || diffTrain.norm() < 0.5)
      {
        insert = false;
      }
    }

    if (insert)
      result.push_back(pose);
  }

  return result;
}

template<typename T>
void printWidth(const T &item, int width, bool leftAlign = false)
{
  std::cout << (leftAlign ? std::left : std::right) << std::setw(width)
      << std::fixed << std::setprecision(2) << item;
}

struct Cookie
{
  bool quit;
  bool refresh;
  bool screenshot;
};

void key_cb(const KeyboardEvent& event, void* cookie_v)
{
  Cookie *cookie = (Cookie*) cookie_v;
  if (event.action == KeyboardEvent::KEY_DOWN)
  {
    if (event.code == 27)
      cookie->quit = true;
    if (event.code == ' ')
      cookie->refresh = true;
    if (event.code == 'm')
      cookie->screenshot = true;
  }
}

int main(int argc, char *argv[])
{
//  static const int TRAJ_SUB_FACTOR = 100;
  static const int SUBTRAJ_LENGTH = 1000;
  static const cv::Matx33d intrinsics(585, 0, 320, 0, 585, 240, 0, 0, 1);

  std::vector<ErrorThreshold> errorThresholds;

//  errorThresholds.push_back(ErrorThreshold
//  { 0.05f, 5.f * M_PI / 180.f, Color(0x00, 0xFF, 0x00) });
//  errorThresholds.push_back(ErrorThreshold
//  { 0.10f, 10.f * M_PI / 180.f, Color(0x00, 0xF4, 0x2A) });
//  errorThresholds.push_back(ErrorThreshold
//  { 0.20f, 20.f * M_PI / 180.f, Color(0x00, 0xAA, 0x55) });
//  errorThresholds.push_back(ErrorThreshold
//  { 0.30f, 30.f * M_PI / 180.f, Color(0x00, 0x7F, 0x7F) });
//  errorThresholds.push_back(ErrorThreshold
//  { 0.40f, 40.f * M_PI / 180.f, Color(0x00, 0x55, 0xAA) });
//  errorThresholds.push_back(ErrorThreshold
//  { 0.50f, 50.f * M_PI / 180.f, Color(0x00, 0x2A, 0xD4) });
//  errorThresholds.push_back(ErrorThreshold
//  { 0.7f, 70.f * M_PI / 180.f, Color(0x00, 0x00, 0xFF) });

//  errorThresholds.push_back(ErrorThreshold
//  { 0.10f, 10.f * M_PI / 180.f, Color(0x00, 0xFF, 0x00) });
//  errorThresholds.push_back(ErrorThreshold
//  { 0.30f, 30.f * M_PI / 180.f, Color(0x00, 0xA5, 0xFF) });
//  errorThresholds.push_back(ErrorThreshold
//  { 0.60f, 60.f * M_PI / 180.f, Color(0x00, 0x00, 0xFF) });

  errorThresholds.push_back(ErrorThreshold
  { 0.10f, 10.f * M_PI / 180.f, Color(0x00, 0xFF, 0x00) });
  errorThresholds.push_back(ErrorThreshold
  { 0.35f, 35.f * M_PI / 180.f, Color(0x00, 0xFF, 0x00) });
  errorThresholds.push_back(ErrorThreshold
  { 0.7f, 70.f * M_PI / 180.f, Color(0x00, 0x00, 0xFF) });

//  errorThresholds.push_back(ErrorThreshold
//  { 0.05f, 5.f * M_PI / 180.f, Color(0x00, 0xFF, 0x00) });
//  errorThresholds.push_back(ErrorThreshold
//  { 0.15f, 15.f * M_PI / 180.f, Color(0x00, 0xBF, 0x3F) });
//  errorThresholds.push_back(ErrorThreshold
//  { 0.30f, 30.f * M_PI / 180.f, Color(0x00, 0x7F, 0x7F) });
//  errorThresholds.push_back(ErrorThreshold
//  { 0.45f, 45.f * M_PI / 180.f, Color(0x00, 0x3F, 0xBF) });

  if (argc < 4)
  {
    std::cerr << "Usage: " << argv[0]
        << " \"train folder\" \"test folder\" \"reloc output folder\" \"mesh\""
        << std::endl;
    return 1;
  }

  fs::path trainFolder = argv[1];
  fs::path testFolder = argv[2];
  fs::path relocFolder = argv[3];
  fs::path meshPath = argv[4];

  Mesh seqMesh = Mesh::load(meshPath.string(), Mesh::LOAD_OBJ);
  Mesh cameraMesh =
      Mesh::load(
          (tvgutil::find_subdir_from_executable("resources") / "frustum.ply").string(),
          Mesh::LOAD_PLY);

  WMesh meshWidget(seqMesh);

  std::cout << "Loaded mesh from: " << meshPath << "\n\tVertices: "
      << seqMesh.cloud.cols << "\n\tTriangles: " << seqMesh.polygons.cols
      << '\n';

  std::vector<Eigen::Matrix4f> trainTrajectory = read_sequence_trajectory(
      trainFolder, "posem%06i.txt");
  auto trainTrajectorySplit = split_trajectory(eigen_to_cv(trainTrajectory),
      SUBTRAJ_LENGTH);
//  std::vector<cv::Affine3f> trainTrajectorySub = subsample_trajectory(
//      trainTrajectory, 10);
  std::cout << "Training trajectory has " << trainTrajectory.size()
      << " poses. Subtrajectories: " << trainTrajectorySplit.size() << "\n";

  std::vector<Eigen::Matrix4f> testTrajectory = read_sequence_trajectory(
      testFolder, "posem%06i.txt");
//  auto testTrajectorySub = subsample_trajectory(
//      testTrajectory, TRAJ_SUB_FACTOR);
//  std::cout << "Testing trajectory has " << testTrajectory.size()
//      << " poses.\n";

  std::vector<Eigen::Matrix4f> icpTrajectory = read_sequence_trajectory(
      relocFolder, "pose-%06i.icp.txt");
  auto icpTrajectorySplit = split_trajectory(eigen_to_cv(icpTrajectory),
      SUBTRAJ_LENGTH);
//  std::vector<cv::Affine3f> icpTrajectorySub = subsample_trajectory(
//      icpTrajectory, TRAJ_SUB_FACTOR);
  std::cout << "ICP trajectory has " << icpTrajectory.size()
      << " poses. Subtrajectories: " << icpTrajectorySplit.size() << "\n";

// Visualizer
  Viz3d visualizer("relocviz");
//  Viz3d visualizerTrajectory("relocviz-trajectory");

// show training trajectory
  {
    Color col = Color::yellow();
    for (size_t i = 0; i < trainTrajectorySplit.size(); ++i)
    {
      std::string tName = "trainTrajectory_"
          + boost::lexical_cast<std::string>(i);
      WTrajectory traj(trainTrajectorySplit[i], WTrajectory::PATH, 0.1, col);
//      visualizerTrajectory.showWidget(tName, traj);
//      visualizerTrajectory.setRenderingProperty(tName, LINE_WIDTH, 2);

      visualizer.showWidget(tName, traj);
      visualizer.setRenderingProperty(tName, LINE_WIDTH, 2);

//      auto subsampledTraj = subsample_trajectory(trainTrajectorySplit[i], 60);
//      WTrajectory trajF(subsampledTraj, WTrajectory::FRAMES, 0.15, col);
//      visualizer.showWidget(tName + "F", trajF);
//      visualizer.setRenderingProperty(tName + "F", LINE_WIDTH, 2);
    }
  }

//// show ICP trajectory
//  {
//    Color col = Color::apricot();
//    for (size_t i = 0; i < icpTrajectorySplit.size(); ++i)
//    {
//      std::string tName = "icpTrajectory_"
//          + boost::lexical_cast<std::string>(i);
//      auto subsampled = subsample_trajectory(icpTrajectorySplit[i], 50);
//      WTrajectoryFrustums traj(subsampled, intrinsics, 0.25, col);
//      visualizer.showWidget(tName, traj);
//      visualizer.setRenderingProperty(tName, LINE_WIDTH, 3);
//    }
//  }

// Classify poses
  auto classifiedPoses = classify_poses(trainTrajectory, testTrajectory,
      icpTrajectory, errorThresholds);

  for (size_t binIdx = 0; binIdx < classifiedPoses.size(); ++binIdx)
  {
    auto &posePairs = classifiedPoses[binIdx];
    std::cout << "The bin " << binIdx << " had " << posePairs.size()
        << " poses.\n";
  }

  // prune near poses (global)
  classifiedPoses = prune_near_poses(classifiedPoses);

  for (size_t binIdx = 0; binIdx < classifiedPoses.size(); ++binIdx)
  {
    auto &posePairs = classifiedPoses[binIdx];
    std::cout << "After pruning the bin " << binIdx << " had "
        << posePairs.size() << " poses.\n";
  }

//  // Prune nearby poses (per bin)
//  for (size_t binIdx = 0; binIdx < classifiedPoses.size(); ++binIdx)
//  {
//    auto &posePairs = classifiedPoses[binIdx];
//    std::cout << "The bin " << binIdx << " has " << posePairs.size()
//        << " poses.\n";
//
//    posePairs = prune_near_poses(posePairs);
//    std::cout << "After pruning closest poses has " << posePairs.size()
//        << " poses.\n";
//  }

//  // Shuffle them for visualization
//  for (size_t binIdx = 0; binIdx < classifiedPoses.size(); ++binIdx)
//  {
//    std::random_shuffle(classifiedPoses[binIdx].begin(),
//        classifiedPoses[binIdx].end());
//  }

// Show some pose examples
  static const int MAX_CAMERAS = 4;
  static const int MAX_SAMPLES = 8;
  std::vector<std::string> trainExamplePaths;
  std::vector<std::string> testExamplePaths;
  std::vector<std::string> inlierImagePaths;

  for (int binIdx = classifiedPoses.size() - 2;
      binIdx >= 0 && trainExamplePaths.size() < MAX_SAMPLES; --binIdx)
  {
    const auto &posePairs = classifiedPoses[binIdx];
    for (size_t pairIdx = 0;
        pairIdx < posePairs.size() && trainExamplePaths.size() < MAX_SAMPLES;
        ++pairIdx)
    {
      const auto &pair = posePairs[pairIdx];
      trainExamplePaths.push_back(
          generate_path(trainFolder, "rgbm%06i.ppm", pair.trainIdx).string());
      testExamplePaths.push_back(
          generate_path(testFolder, "rgbm%06i.ppm", pair.testIdx).string());
      inlierImagePaths.push_back(
          generate_path(relocFolder, "ransac-%06i.inliers.png", pair.testIdx).string());
    }
  }

  static const int IMG_WIDTH = 640;
  static const int IMG_HEIGHT = 480;
  static const int IMG_STEP = 5;
  cv::Mat samplePoseImg = cv::Mat::zeros(IMG_HEIGHT * 2 + IMG_STEP * 1,
      IMG_WIDTH * trainExamplePaths.size()
          + IMG_STEP * (trainExamplePaths.size() - 1),
      CV_8UC3);

  for (size_t sampleIdx = 0; sampleIdx < trainExamplePaths.size(); ++sampleIdx)
  {
    std::string trainPath = trainExamplePaths[sampleIdx];
    std::string testPath = testExamplePaths[sampleIdx];
    std::string inliersPath = inlierImagePaths[sampleIdx];
    std::cout << "Loading training image from: " << trainPath
        << "\nLoading test image from: " << testPath << '\n';

    cv::Mat trainImg = cv::imread(trainPath);
    cv::Mat testImg = cv::imread(testPath);
//    cv::Mat inliersImg = cv::imread(inliersPath);

    // Print camera numbers on the images
    if (sampleIdx < MAX_CAMERAS)
    {
      const std::string cameraId = boost::lexical_cast<std::string>(
          sampleIdx + 1);
      const int thickness = 4;
      const float size = 3.0f;

      // Shadow
      cv::Point shadowPosition(23, trainImg.rows - 17);
      cv::putText(trainImg, cameraId, shadowPosition, CV_FONT_HERSHEY_SIMPLEX,
          size, cv::Scalar::all(0), thickness, cv::LINE_AA);
      cv::putText(testImg, cameraId, shadowPosition, CV_FONT_HERSHEY_SIMPLEX,
          size, cv::Scalar::all(0), thickness, cv::LINE_AA);

      // Actual number
      cv::Point position(20, trainImg.rows - 20);
      cv::putText(trainImg, cameraId, position, CV_FONT_HERSHEY_SIMPLEX, size,
          cv::Scalar(247, 134,  79), thickness, cv::LINE_AA);
      cv::putText(testImg, cameraId, position, CV_FONT_HERSHEY_SIMPLEX, size,
          cv::Scalar(0, 0, 255), thickness, cv::LINE_AA);
    }

    cv::Rect trainRect(sampleIdx * (IMG_WIDTH + IMG_STEP), 0, IMG_WIDTH,
        IMG_HEIGHT);
    cv::Rect testRect(sampleIdx * (IMG_WIDTH + IMG_STEP), IMG_HEIGHT + IMG_STEP,
        IMG_WIDTH, IMG_HEIGHT);
    cv::Rect inliersRect(sampleIdx * (IMG_WIDTH + IMG_STEP),
        2 * (IMG_HEIGHT + IMG_STEP), IMG_WIDTH, IMG_HEIGHT);

    trainImg.copyTo(samplePoseImg(trainRect));
    testImg.copyTo(samplePoseImg(testRect));
//    inliersImg.copyTo(samplePoseImg(inliersRect));
  }

  cv::namedWindow("Sample images", cv::WINDOW_KEEPRATIO);
  cv::imshow("Sample images", samplePoseImg);
//  cv::imwrite(tvgutil::TimeUtil::get_iso_timestamp() + "-poses.png", samplePoseImg);

  int key = 0;
  while (key != 27)
  {
    key = cv::waitKey();
  }

  cv::destroyAllWindows();

//  cv::Mat trainImg = cv::imread(trainExamplePaths[0]);
//  cv::Mat testImg = cv::imread(testExamplePaths[0]);
//
//  cv::imshow("Train", trainImg);
//  cv::imshow("Test", testImg);
//  cv::waitKey();

// Show up to trainExamplePaths.size() cameras
  size_t cameraCounter = 0;
  for (size_t binIdx = classifiedPoses.size() - 1;
      binIdx < classifiedPoses.size()
          && cameraCounter < trainExamplePaths.size(); --binIdx)
  {
    auto &posePairs = classifiedPoses[binIdx];
    // Show at most N poses for each bin
    posePairs.resize(std::min<size_t>(posePairs.size(), 4));
//    posePairs.resize(std::min<size_t>(posePairs.size() / 10, 10));

// Extract the poses from the pairs
    std::vector<Eigen::Matrix4f> trainPoses, testPoses;
    for (const auto &x : posePairs)
    {
      trainPoses.push_back(x.trainPose);
      testPoses.push_back(x.testPose);
    }

    auto trainPosesCv = eigen_to_cv(trainPoses);
    auto testPosesCv = eigen_to_cv(testPoses);

    Color posesColor =
        binIdx < errorThresholds.size() ?
            errorThresholds[binIdx].color : Color::red();

    for (size_t cameraIdx = 0; cameraIdx < trainPosesCv.size(); ++cameraIdx)
    {

      // Draw the training camera frustums
      {
        std::string tName = "trainPose_"
            + boost::lexical_cast<std::string>(binIdx);
//    auto subsampled = subsample_trajectory(posesCv, 50);
//      WTrajectoryFrustums traj(trainPosesCv, intrinsics, 0.15,
//          Color::bluberry());
//      visualizer.showWidget(tName, traj);
//      visualizer.setRenderingProperty(tName, LINE_WIDTH, 3);

        std::string wName = tName + "_"
            + boost::lexical_cast<std::string>(cameraIdx);
        WMesh cameraWidget(cameraMesh);
        cameraWidget.setColor(Color::bluberry());

        visualizer.showWidget(wName, cameraWidget, trainPosesCv[cameraIdx]);
        visualizer.setRenderingProperty(wName, SHADING, SHADING_FLAT);

//      visualizerTrajectory.showWidget(tName, traj);
//      visualizerTrajectory.setRenderingProperty(tName, LINE_WIDTH, 3);
      }

      // Draw the lines connecting them
      const cv::Affine3f &trainPose = trainPosesCv[cameraIdx];
      const cv::Affine3f &testPose = testPosesCv[cameraIdx];

      WLine wLine(cv::Point3f(trainPose.translation()),
          cv::Point3f(testPose.translation()), Color::magenta());
      std::string wName = "trainTest_"
          + boost::lexical_cast<std::string>(binIdx) + "_"
          + boost::lexical_cast<std::string>(cameraIdx);
      visualizer.showWidget(wName, wLine);
      visualizer.setRenderingProperty(wName, LINE_WIDTH, 2);

      {
        std::string tName = "testTrajectory_"
            + boost::lexical_cast<std::string>(binIdx);
//    auto subsampled = subsample_trajectory(posesCv, 50);
//      WTrajectoryFrustums traj(testPosesCv, intrinsics, 0.15, posesColor);
//      visualizer.showWidget(tName, traj);
//      visualizer.setRenderingProperty(tName, LINE_WIDTH, 3);

        std::string wName = tName + "_"
            + boost::lexical_cast<std::string>(cameraIdx);
        WMesh cameraWidget(cameraMesh);
        cameraWidget.setColor(posesColor);

        visualizer.showWidget(wName, cameraWidget, testPosesCv[cameraIdx]);
        visualizer.setRenderingProperty(wName, SHADING, SHADING_FLAT);

//        if (cameraCounter < trainExamplePaths.size())
        if (cameraCounter < MAX_CAMERAS)
        {
          wName += "_text";
          WText3D wTestCameraText(
              boost::lexical_cast<std::string>(cameraCounter + 1),
              cv::Point3f(testPosesCv[cameraIdx].translation()), 0.085, true,
              posesColor);
          visualizer.showWidget(wName, wTestCameraText);

          wName += "_train";
          WText3D wTrainCameraText(
              boost::lexical_cast<std::string>(cameraCounter + 1),
              cv::Point3f(trainPosesCv[cameraIdx].translation()), 0.085, true,
              Color::bluberry());
          visualizer.showWidget(wName, wTrainCameraText);

          cameraCounter++;
        }
      }

//      if (!testPosesCv.empty())
//      {
////        cameraWidget.setPose(testPosesCv[0]);
//        visualizer.showWidget(tName + "_C", cameraWidget, testPosesCv[0]);
//      }
    }
  }

//  WTrajectory trainTrajectoryW(trainTrajectory, WTrajectory::PATH, 1.0,
//      Color::green());
//  WTrajectoryFrustums trainTrajectoryF(trainTrajectorySub, intrinsics, 0.05,
//      Color::green());

//  WTrajectory testTrajectoryW(testTrajectory, WTrajectory::PATH, 1.0,
//      Color::blue());
//  WTrajectoryFrustums testTrajectoryF(testTrajectorySub, intrinsics, 0.25,
//      Color::blue());
//
//  WTrajectory icpTrajectoryW(icpTrajectory, WTrajectory::PATH, 1.0,
//      Color::red());
//  WTrajectoryFrustums icpTrajectoryF(icpTrajectorySub, intrinsics, 0.25,
//      Color::red());

  visualizer.showWidget("meshWidget", meshWidget);
//  visualizerTrajectory.showWidget("meshWidget", meshWidget);
//  visualizer.setRenderingProperty("meshWidget", SHADING, SHADING_PHONG);

//  visualizer.showWidget("trainTrajectoryW", trainTrajectoryW);
//  visualizer.showWidget("trainTrajectoryF", trainTrajectoryF);

//  visualizer.showWidget("testTrajectoryW", testTrajectoryW);
//  visualizer.showWidget("testTrajectoryF", testTrajectoryF);
//
//  visualizer.showWidget("icpTrajectoryW", icpTrajectoryW);
//  visualizer.showWidget("icpTrajectoryF", icpTrajectoryF);

  visualizer.resetCamera();

  Cookie cookie;
  cookie.quit = false;
  cookie.refresh = false;
  cookie.screenshot = false;

  visualizer.registerKeyboardCallback(key_cb, &cookie);
//  visualizerTrajectory.registerKeyboardCallback(key_cb, &cookie);

  while (!cookie.quit)
  {
    visualizer.spinOnce();

    if (cookie.refresh)
    {
//      auto camera = visualizer.getViewerPose();
//      visualizerTrajectory.setViewerPose(camera);
//      visualizerTrajectory.spinOnce();
      cookie.refresh = false;
    }

    if (cookie.screenshot)
    {
//      visualizerTrajectory.spinOnce();

      std::string timestamp = tvgutil::TimeUtil::get_iso_timestamp();

      visualizer.saveScreenshot(timestamp + "-novelPoses.png");
//      visualizerTrajectory.saveScreenshot(timestamp + "-trajectory.png");

      cookie.screenshot = false;
    }
  }

  return 0;
}


// Copyright (C) 2006 Douglas Gregor <doug.gregor@gmail.com>

// Use, modification and distribution is 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)

// An example using Boost.MPI's reduce() to compute a minimum value.
#include <boost/mpi.hpp>
#include <iostream>
#include <cstdlib>
namespace mpi = boost::mpi;

int main(int argc, char* argv[])
{
  mpi::environment env(argc, argv);
  mpi::communicator world;

  std::srand(time(0) + world.rank());
  int my_number = std::rand();

  if (world.rank() == 0) {
    int minimum;
    reduce(world, my_number, minimum, mpi::minimum<int>(), 0);
    std::cout << "The minimum value is " << minimum << std::endl;
  } else {
    reduce(world, my_number, mpi::minimum<int>(), 0);
  }

  return 0;
}


#ifndef GRAPH_HPP_
#define GRAPH_HPP_

#include <boost/functional/hash.hpp>
#include <functional>
#include <map>
#include <set>
#include <string>
#include <unordered_set>
#include <vector>

using std::function;
using std::map;
using std::pair;
using std::set;
using std::string;
using std::unordered_set;
using std::vector;

typedef int NodeId;
typedef int NodeTime;
typedef set<NodeId> NodeSet;
typedef pair<NodeSet, pair<NodeTime, NodeTime>> NodeSetInterval;

typedef pair<NodeTime, NodeTime> Interval;

typedef unordered_set<NodeSet, boost::hash<NodeSet>> NodeSetSet;
typedef unordered_set<NodeSetInterval, boost::hash<NodeSetInterval>> NodeSetIntervalSet;

#define NODETIME_MAX INT_MAX
#define NODETIME_MIN INT_MIN

enum TemporalIsolationType { ALLTIME_MAX, MAX_USUALLY, AVG_ALLTIME, USUALLY_AVG, ALLTIME_AVG, USUALLY_MAX };

struct SEdge {
	NodeId nodeFrom, nodeTo;

	SEdge() {
	}

	SEdge(NodeId nodeFrom, NodeId nodeTo) : nodeFrom(nodeFrom), nodeTo(nodeTo) {
	}

	bool operator==(const SEdge &rhs) const {
		return nodeFrom == rhs.nodeFrom && nodeTo == rhs.nodeTo;
	}
};

struct TEdge {
	NodeId nodeFrom, nodeTo;
	NodeTime tStart, tStop;

	TEdge() {
	}

	TEdge(NodeId nodeFrom, NodeId nodeTo, NodeTime tStart, NodeTime tStop)
	    : nodeFrom(nodeFrom), nodeTo(nodeTo), tStart(tStart), tStop(tStop) {
	}

	bool operator==(const TEdge &rhs) const {
		return nodeFrom == rhs.nodeFrom && nodeTo == rhs.nodeTo && tStart == rhs.tStart && tStop == rhs.tStop;
	}
};

std::size_t hash_value(SEdge const& e);

std::size_t hash_value(TEdge const& e);

class SGraph {
	map<NodeId, unordered_set<SEdge, boost::hash<SEdge>>> adj_list;

      public:
	SGraph();

	SGraph(vector<SEdge> &edgeList);

	SGraph(unordered_set<SEdge, boost::hash<SEdge>> &edgeList);

	SGraph(const SGraph &g2);

	void addNode(NodeId n);

	void addEdge(SEdge e);

	int getNodesCount();

	int getEdgesCount();

	int degree(NodeId node);

	int degree(NodeId node, const NodeSet &restriction);

	int outdegree(NodeId node, const NodeSet &in);

	int mindegree(const NodeSet &restriction);

	NodeSet neighbourhood(NodeId node);

	NodeSet neighbourhood(NodeId node, const NodeSet &restriction);

	NodeSet neighbourhoodGreaterDeg(NodeId node);

	NodeSet neighbourhoodSmallerDeg(NodeId node);

	SGraph buildComplement(const NodeSet &restriction);

	void forallNodes(function<void(NodeId)> callback, bool parallel);

	void forallNeighbours(NodeId node, function<void(NodeId)> callback, bool parallel);

	NodeSet getNodes();

	bool hasEdge(NodeId u, NodeId v);

	bool isConnected(const NodeSet &subset);

	NodeSet getReachableNodes(NodeId u);

	bool isKplex(const NodeSet &plex, int k);

	int outdegree_sum(const NodeSet &restriction);
};

class TGraph {
	map<NodeId, unordered_set<TEdge, boost::hash<TEdge>>> adj_list;
	NodeTime lifetime_begin, lifetime_end;

      public:
	TGraph();

	TGraph(vector<TEdge> &edgeList);

	TGraph(const TGraph &g2);

	void addNode(NodeId n);

	void addEdge(TEdge e);

	void forallNeighbours(NodeId node, NodeTime t, function<void(NodeId &)> callback, bool parallel);

	int getNodesCount();

	int getEdgesCount();

	int getEdgesInstantsCount();

	NodeTime getLifetimeBegin();

	NodeTime getLifetimeEnd();

	NodeSet getNodes();

	SGraph buildIntersectionGraph(const NodeSet &restriction, NodeTime t_start, NodeTime t_stop);

	SGraph buildAuxGraph(const NodeSet &restriction, NodeTime t_start, NodeTime t_stop, NodeTime t_crit);

	int outdegree(NodeId node, const NodeSet &in, NodeTime t);

	int outdegree_max(NodeId node, const NodeSet &in, NodeTime t_start, NodeTime t_stop);

	int outdegree_time_sum(NodeId node, const NodeSet &restriction, NodeTime t_start, NodeTime t_stop);

	int degree_sum(NodeId node, NodeTime t_start, NodeTime t_stop);

	void forallNodes(function<void(NodeId)> callback, bool parallel);

	bool isKplex(const NodeSet &plex, int k, NodeTime t_start, NodeTime t_stop);

	int degree(NodeId node, NodeTime t, const NodeSet &restriction);

	bool isIsolated(const NodeSet &plex, NodeTime t_start, NodeTime t_stop, TemporalIsolationType isolation, int c);

	map<NodeId, unordered_set<TEdge, boost::hash<TEdge>>> getAdjacencyList();
};

string nodeset_to_string(const NodeSet &nodeset);

string nodesetset_to_string(const NodeSetSet &nodesetset);

string nodesetinterval_to_string(const NodeSetInterval &nodeset);

#endif


/*===================================================================

The Medical Imaging Interaction Toolkit (MITK)

Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.

This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.

See LICENSE.txt or http://www.mitk.org for details.

===================================================================*/

#include <mitkImageCast.h>
#include "mitkCommandLineParser.h"
#include <boost/algorithm/string.hpp>
#include <mitkImage.h>
#include <itkNonLocalMeansDenoisingFilter.h>
#include <itkImage.h>
#include <mitkIOUtil.h>
#include <mitkDiffusionPropertyHelper.h>
#include <mitkImageCast.h>
#include <mitkITKImageImport.h>
#include <mitkProperties.h>

using namespace std;

typedef mitk::Image DiffusionImageType;
typedef itk::Image<short, 3> ImageType;

/**
 * Denoises DWI using the Nonlocal - Means algorithm
 */
int main(int argc, char* argv[])
{
  mitkCommandLineParser parser;

  parser.setTitle("DWI Denoising");
  parser.setCategory("Preprocessing Tools");
  parser.setContributor("MBI");
  parser.setDescription("Denoising for diffusion weighted images using a non-local means algorithm.");

  parser.setArgumentPrefix("--", "-");
  parser.addArgument("input", "i", mitkCommandLineParser::InputFile, "Input:", "input image (DWI)", us::Any(), false);
  parser.addArgument("variance", "v", mitkCommandLineParser::Float, "Variance:", "noise variance", us::Any(), false);

  parser.addArgument("mask", "m", mitkCommandLineParser::InputFile, "Mask:", "brainmask for input image", us::Any(), true);
  parser.addArgument("search", "s", mitkCommandLineParser::Int, "Search radius:", "search radius", us::Any(), true);
  parser.addArgument("compare", "c", mitkCommandLineParser::Int, "Comparison radius:", "comparison radius", us::Any(), true);
  parser.addArgument("joint", "j", mitkCommandLineParser::Bool, "Joint information:", "use joint information");
  parser.addArgument("rician", "r", mitkCommandLineParser::Bool, "Rician adaption:", "use rician adaption");

  parser.changeParameterGroup("Output", "Output of this miniapp");

  parser.addArgument("output", "o", mitkCommandLineParser::OutputFile, "Output:", "output image (DWI)", us::Any(), false);

  map<string, us::Any> parsedArgs = parser.parseArguments(argc, argv);
  if (parsedArgs.size()==0)
      return EXIT_FAILURE;

  string inFileName = us::any_cast<string>(parsedArgs["input"]);
  double variance = static_cast<double>(us::any_cast<float>(parsedArgs["variance"]));
  string maskName;
  if (parsedArgs.count("mask"))
    maskName = us::any_cast<string>(parsedArgs["mask"]);
  string outFileName = us::any_cast<string>(parsedArgs["output"]);
//  boost::algorithm::erase_all(outFileName, ".dwi");
  int search = 4;
  if (parsedArgs.count("search"))
    search = us::any_cast<int>(parsedArgs["search"]);
  int compare = 1;
  if (parsedArgs.count("compare"))
    compare = us::any_cast<int>(parsedArgs["compare"]);
  bool joint = false;
  if (parsedArgs.count("joint"))
    joint = true;
  bool rician = false;
  if (parsedArgs.count("rician"))
    rician = true;

  try
  {

    if( boost::algorithm::ends_with(inFileName, ".dwi"))
    {

      DiffusionImageType::Pointer dwi = mitk::IOUtil::LoadImage(inFileName);

      mitk::DiffusionPropertyHelper::ImageType::Pointer itkVectorImagePointer = mitk::DiffusionPropertyHelper::ImageType::New();
      mitk::CastToItkImage(dwi, itkVectorImagePointer);

      itk::NonLocalMeansDenoisingFilter<short>::Pointer filter = itk::NonLocalMeansDenoisingFilter<short>::New();
      filter->SetNumberOfThreads(12);
      filter->SetInputImage( itkVectorImagePointer );

      if (!maskName.empty())
      {
        mitk::Image::Pointer mask = mitk::IOUtil::LoadImage(maskName);
        ImageType::Pointer itkMask = ImageType::New();
        mitk::CastToItkImage(mask, itkMask);
        filter->SetInputMask(itkMask);
      }

      filter->SetUseJointInformation(joint);
      filter->SetUseRicianAdaption(rician);
      filter->SetSearchRadius(search);
      filter->SetComparisonRadius(compare);
      filter->SetVariance(variance);
      filter->Update();

      DiffusionImageType::Pointer output = mitk::GrabItkImageMemory( filter->GetOutput() );
      output->SetProperty( mitk::DiffusionPropertyHelper::REFERENCEBVALUEPROPERTYNAME.c_str(), mitk::FloatProperty::New( mitk::DiffusionPropertyHelper::GetReferenceBValue(dwi) ) );
      output->SetProperty( mitk::DiffusionPropertyHelper::GRADIENTCONTAINERPROPERTYNAME.c_str(), mitk::GradientDirectionsProperty::New( mitk::DiffusionPropertyHelper::GetGradientContainer(dwi) ) );
      mitk::DiffusionPropertyHelper propertyHelper( output );
      propertyHelper.InitializeImage();

//      std::stringstream name;
//      name << outFileName << "_NLM_" << search << "-" << compare << "-" << variance << ".dwi";

      mitk::IOUtil::Save(output, outFileName.c_str());
    }
    else
    {
      std::cout << "Only supported for .dwi!";
    }
  }
  catch (itk::ExceptionObject e)
  {
      std::cout << e;
      return EXIT_FAILURE;
  }
  catch (std::exception e)
  {
      std::cout << e.what();
      return EXIT_FAILURE;
  }
  catch (...)
  {
      std::cout << "ERROR!?!";
      return EXIT_FAILURE;
  }
  return EXIT_SUCCESS;
}


/* pcmsolver_copyright_start */
/*
 *     PCMSolver, an API for the Polarizable Continuum Model
 *     Copyright (C) 2013-2016 Roberto Di Remigio, Luca Frediani and contributors
 *
 *     This file is part of PCMSolver.
 *
 *     PCMSolver is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     PCMSolver is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with PCMSolver.  If not, see <http://www.gnu.org/licenses/>.
 *
 *     For information on the complete list of contributors to the
 *     PCMSolver API, see: <http://pcmsolver.readthedocs.io/>
 */
/* pcmsolver_copyright_end */

#include <cmath>
#include <cstdlib>
#include <iostream>
#include <iomanip>
#include <limits>

#include <Eigen/Core>

#include "bi_operators/CollocationIntegrator.hpp"
#include "bi_operators/PurisimaIntegrator.hpp"
#include "bi_operators/NumericalIntegrator.hpp"
#include "green/DerivativeTypes.hpp"
#include "cavity/Element.hpp"
#include "cavity/GePolCavity.hpp"
#include "green/SphericalDiffuse.hpp"
#include "TestingMolecules.hpp"
#include "green/AnisotropicLiquid.hpp"
#include "green/IonicLiquid.hpp"
#include "green/SphericalDiffuse.hpp"
#include "green/UniformDielectric.hpp"
#include "green/Vacuum.hpp"
#include "utils/MathUtils.hpp"

extern "C"
void host_writer(const char * message, int message_length);

void save_vacuum_collocation();
void save_uniform_dielectric_collocation();
void save_tanh_spherical_diffuse_collocation();

void save_vacuum_purisima();
void save_uniform_dielectric_purisima();

void save_vacuum_numerical();
void save_uniform_dielectric_numerical();
void save_ionic_liquid_numerical();
void save_anisotropic_liquid_numerical();
void save_tanh_spherical_diffuse_numerical();

int main()
{
    initBohrToAngstrom(bohrToAngstrom);
    save_vacuum_collocation();
    save_uniform_dielectric_collocation();
    save_tanh_spherical_diffuse_collocation();

    save_vacuum_purisima();
    save_uniform_dielectric_purisima();

    return EXIT_SUCCESS;
}

void save_vacuum_collocation() {
    Molecule molec = dummy<0>(1.44 / bohrToAngstrom());
    double area = 10.0;
    GePolCavity cavity(molec, area, 0.0, 100.0);

    Vacuum<> gf;

    Eigen::MatrixXd S_results = gf.singleLayer(cavity.elements());
    cnpy::custom::npy_save("vacuum_S_collocation.npy", S_results);
    Eigen::MatrixXd D_results = gf.doubleLayer(cavity.elements());
    cnpy::custom::npy_save("vacuum_D_collocation.npy", D_results);
}

void save_uniform_dielectric_collocation() {
    double epsilon = 80.0;
    Molecule molec = dummy<0>(1.44 / bohrToAngstrom());
    double area = 10.0;
    GePolCavity cavity(molec, area, 0.0, 100.0);

    UniformDielectric<> gf(epsilon);

    Eigen::MatrixXd S_results = gf.singleLayer(cavity.elements());
    cnpy::custom::npy_save("uniformdielectric_S_collocation.npy", S_results);
    Eigen::MatrixXd D_results = gf.doubleLayer(cavity.elements());
    cnpy::custom::npy_save("uniformdielectric_D_collocation.npy", D_results);
}

void save_tanh_spherical_diffuse_collocation() {
    double epsilon = 80.0;
    double width = 5.0;
    double sphereRadius = 100.0;
    Molecule molec = dummy<0>(1.44 / bohrToAngstrom());
    double area = 10.0;
    GePolCavity cavity(molec, area, 0.0, 100.0);

    SphericalDiffuse<> gf(epsilon, epsilon, width, sphereRadius, Eigen::Vector3d::Zero(), 3);

    Eigen::MatrixXd S_results = gf.singleLayer(cavity.elements());
    cnpy::custom::npy_save("tanhsphericaldiffuse_S_collocation.npy", S_results);
    Eigen::MatrixXd D_results = gf.doubleLayer(cavity.elements());
    cnpy::custom::npy_save("tanhsphericaldiffuse_D_collocation.npy", D_results);
}

void save_vacuum_purisima() {
    Molecule molec = dummy<0>(1.44 / bohrToAngstrom());
    double area = 10.0;
    GePolCavity cavity(molec, area, 0.0, 100.0);

    Vacuum<AD_directional, PurisimaIntegrator> gf;

    Eigen::MatrixXd D_results = gf.doubleLayer(cavity.elements());
    cnpy::custom::npy_save("vacuum_D_purisima.npy", D_results);
}

void save_uniform_dielectric_purisima() {
    double epsilon = 80.0;
    Molecule molec = dummy<0>(1.44 / bohrToAngstrom());
    double area = 10.0;
    GePolCavity cavity(molec, area, 0.0, 100.0);

    UniformDielectric<AD_directional, PurisimaIntegrator> gf(epsilon);

    Eigen::MatrixXd D_results = gf.doubleLayer(cavity.elements());
    cnpy::custom::npy_save("uniformdielectric_D_purisima.npy", D_results);
}

void save_vacuum_numerical() {
    Molecule molec = dummy<0>(1.44 / bohrToAngstrom());
    double area = 10.0;
    GePolCavity cavity(molec, area, 0.0, 100.0);

    Vacuum<AD_directional, NumericalIntegrator> gf;

    Eigen::MatrixXd S_results = gf.singleLayer(cavity.elements());
    cnpy::custom::npy_save("vacuum_S_numerical.npy", S_results);
    Eigen::MatrixXd D_results = gf.doubleLayer(cavity.elements());
    cnpy::custom::npy_save("vacuum_D_numerical.npy", D_results);
}

void save_uniform_dielectric_numerical() {
    double epsilon = 80.0;
    Molecule molec = dummy<0>(1.44 / bohrToAngstrom());
    double area = 10.0;
    GePolCavity cavity(molec, area, 0.0, 100.0);

    UniformDielectric<AD_directional, NumericalIntegrator> gf(epsilon);

    Eigen::MatrixXd S_results = gf.singleLayer(cavity.elements());
    cnpy::custom::npy_save("uniformdielectric_S_numerical.npy", S_results);
    Eigen::MatrixXd D_results = gf.doubleLayer(cavity.elements());
    cnpy::custom::npy_save("uniformdielectric_D_numerical.npy", D_results);
}

void save_ionic_liquid_numerical() {
    double eps = 80.0;
    double kappa = 1.5;
    Molecule molec = dummy<0>(1.44 / bohrToAngstrom());
    double area = 10.0;
    GePolCavity cavity(molec, area, 0.0, 100.0);

    IonicLiquid<AD_directional, NumericalIntegrator> gf(eps, kappa);

    Eigen::MatrixXd S_results = gf.singleLayer(cavity.elements());
    cnpy::custom::npy_save("ionicliquid_S_numerical.npy", S_results);
    Eigen::MatrixXd D_results = gf.doubleLayer(cavity.elements());
    cnpy::custom::npy_save("ionicliquid_D_numerical.npy", D_results);
}

void save_anisotropic_liquid_numerical() {
    Eigen::Vector3d epsilon = Eigen::Vector3d::Zero();
    epsilon << 80.0, 80.0, 80.0;
    Eigen::Vector3d euler = Eigen::Vector3d::Zero();
    euler << 0.0, 0.0, 0.0;
    Molecule molec = dummy<0>(1.44 / bohrToAngstrom());
    double area = 10.0;
    GePolCavity cavity(molec, area, 0.0, 100.0);

    AnisotropicLiquid<AD_directional, NumericalIntegrator> gf(epsilon, euler);

    Eigen::MatrixXd S_results = gf.singleLayer(cavity.elements());
    cnpy::custom::npy_save("anisotropicliquid_S_numerical.npy", S_results);
    Eigen::MatrixXd D_results = gf.doubleLayer(cavity.elements());
    cnpy::custom::npy_save("anisotropicliquid_D_numerical.npy", D_results);
}

void save_tanh_spherical_diffuse_numerical() {
    double epsilon = 80.0;
    double width = 5.0;
    double sphereRadius = 100.0;
    Molecule molec = dummy<0>(1.44 / bohrToAngstrom());
    double area = 10.0;
    GePolCavity cavity(molec, area, 0.0, 100.0);

    SphericalDiffuse<NumericalIntegrator, OneLayerTanh> gf(epsilon, epsilon, width, sphereRadius, Eigen::Vector3d::Zero(), 3);

    Eigen::MatrixXd S_results = gf.singleLayer(cavity.elements());
    cnpy::custom::npy_save("tanhsphericaldiffuse_S_numerical.npy", S_results);
    Eigen::MatrixXd D_results = gf.doubleLayer(cavity.elements());
    cnpy::custom::npy_save("tanhsphericaldiffuse_D_numerical.npy", D_results);
}

extern "C"
void host_writer(const char * /* message */, int /* message_length */) {}