#include <iostream>
#include <map>
#include <string>
#include <vector>

#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
#define foreach BOOST_FOREACH

#include <rosbag/bag.h>
#include <rosbag/view.h>
#include <snd_msgs/Encoders.h>
#include <std_msgs/Float32.h>

#include <gnuplot-iostream.h>

#include "Odometry.h"

struct OdomInputs
{
  float leftWheelRadius;
  float rightWheelRadius;
  float wheelsSeparation;
};

struct OdomResults
{
  std::vector<std::pair<double, double>> x;
  std::vector<std::pair<double, double>> y;
  std::vector<std::pair<double, double>> theta;
  std::string plotStr;
};

OdomResults computeOdom(rosbag::View& bagView, const OdomInputs& in)
{
  Odometry odom;
  OdomResults res;

  std::stringstream ss;
  ss << std::fixed << std::setw(5) << std::setprecision(3)
     << "'-' with lines title 'left:" << in.leftWheelRadius
     << " right:" << in.rightWheelRadius << " sep:" << in.wheelsSeparation << "', ";
  res.plotStr = ss.str();

  odom.leftWheelRadius = in.leftWheelRadius;
  odom.rightWheelRadius = in.rightWheelRadius;
  odom.wheelSeparationMM = in.wheelsSeparation;

  odom.reset();

  foreach(rosbag::MessageInstance const m, bagView)
  {
    if(m.getTopic() == "/ticks_encodeurs")
    {
      snd_msgs::EncodersConstPtr ticks = m.instantiate<snd_msgs::Encoders>();
      if(ticks == nullptr) continue;
      const Pose pose = odom.computeOdom(ticks->left, ticks->right);
      const double time = m.getTime().toSec();
      res.x.emplace_back(time, pose.x);
      res.y.emplace_back(time, pose.y);
      res.theta.emplace_back(time, pose.theta);
    }
  }

  std::cout << std::fixed << std::setw(5) << std::setprecision(3)
            << "Left: " << odom.leftWheelRadius << " mm Right: " << odom.rightWheelRadius
            << " mm Sep: " << odom.wheelSeparationMM << " mm ";
  std::cout << std::fixed << std::setw(6) << std::setprecision(2)
            << "Final X: " << res.x.back().second << " mm Y: " << res.y.back().second
            << " mm Theta: " << std::setprecision(4) << res.theta.back().second << '\n';

  return res;
}

int main(int argc, char** argv)
{
  std::string bagFilePath;
  if(argc < 2)
  {
    std::cout << "USAGE: " << argv[0] << " <bag_file>\n";
    return -1;
  }

  bagFilePath = argv[1];

  if(!boost::filesystem::exists(bagFilePath))
  {
    std::cout << "The bag " << bagFilePath << " does not exixt\n";
    return -1;
  }

  rosbag::Bag bag;
  bag.open(bagFilePath, rosbag::bagmode::Read);

  std::vector<std::string> topics;
  topics.push_back(std::string("/ticks_encodeurs"));

  rosbag::View view(bag, rosbag::TopicQuery(topics));

  std::vector<OdomInputs> inputs;

  /*
  inputs.emplace_back(OdomInputs{26.125f, 26.098f, 111.725f});
  inputs.emplace_back(OdomInputs{26.125f, 26.100f, 111.725f});
  inputs.emplace_back(OdomInputs{26.125f, 26.102f, 111.725f});
  inputs.emplace_back(OdomInputs{26.125f, 26.104f, 111.725f});
  inputs.emplace_back(OdomInputs{26.125f, 26.106f, 111.725f});
  */

  /*
  inputs.emplace_back(OdomInputs{26.125f, 26.f, 111.725f});
  inputs.emplace_back(OdomInputs{26.125f, 26.f, 111.6f});
  inputs.emplace_back(OdomInputs{26.125f, 26.f, 111.65f});
  inputs.emplace_back(OdomInputs{26.125f, 26.f, 111.7f});
  inputs.emplace_back(OdomInputs{26.125f, 26.f, 111.75f});
  inputs.emplace_back(OdomInputs{26.125f, 26.f, 111.8f});
  */

  /*
  inputs.emplace_back(OdomInputs{26.120f, 26.100f, 111.725f});
  inputs.emplace_back(OdomInputs{26.125f, 26.100f, 111.725f});
  inputs.emplace_back(OdomInputs{26.130f, 26.100f, 111.725f});
  inputs.emplace_back(OdomInputs{26.120f, 26.102f, 111.725f});
  inputs.emplace_back(OdomInputs{26.125f, 26.102f, 111.725f});
  inputs.emplace_back(OdomInputs{26.130f, 26.102f, 111.725f});
  */

  inputs.emplace_back(OdomInputs{26.175f, 26.145f, 111.725f});
  inputs.emplace_back(OdomInputs{26.180f, 26.150f, 111.725f});
  inputs.emplace_back(OdomInputs{26.185f, 26.155f, 111.725f});
  inputs.emplace_back(OdomInputs{26.190f, 26.155f, 111.725f});
  inputs.emplace_back(OdomInputs{26.190f, 26.159f, 111.725f});
  inputs.emplace_back(OdomInputs{26.192f, 26.161f, 111.725f});
  inputs.emplace_back(OdomInputs{26.195f, 26.164f, 111.725f});

  std::vector<std::pair<OdomInputs, OdomResults>> results;
  for(const auto& input : inputs)
  {
    results.emplace_back(input, computeOdom(view, input));
  }

  bag.close();

  /*
  Gnuplot gpX;
  gpX << "set title 'X'\n";
  gpX << "set key left top\n";
  gpX << "plot ";
  Gnuplot gpY;
  gpY << "set title 'Y'\n";
  gpY << "set key left top\n";
  gpY << "plot ";
  Gnuplot gpTheta;
  gpTheta << "set title 'Theta'\n";
  gpTheta << "set key left top\n";
  gpTheta << "plot ";

  for(const auto& res : results)
  {
    gpX << res.second.plotStr;
    gpY << res.second.plotStr;
    gpTheta << res.second.plotStr;
  }

  gpX << '\n';
  gpY << '\n';
  gpTheta << '\n';

  for(const auto& res : results)
  {
    gpX.send1d(res.second.x);
    gpY.send1d(res.second.y);
    gpTheta.send1d(res.second.theta);
  }
  */

  return 0;
}


/*
 * This file belongs to the Galois project, a C++ library for exploiting
 * parallelism. The code is being released under the terms of the 3-Clause BSD
 * License (a copy is located in LICENSE.txt at the top-level directory).
 *
 * Copyright (C) 2018, The University of Texas at Austin. All rights reserved.
 * UNIVERSITY EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES CONCERNING THIS
 * SOFTWARE AND DOCUMENTATION, INCLUDING ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR ANY PARTICULAR PURPOSE, NON-INFRINGEMENT AND WARRANTIES OF
 * PERFORMANCE, AND ANY WARRANTY THAT MIGHT OTHERWISE ARISE FROM COURSE OF
 * DEALING OR USAGE OF TRADE.  NO WARRANTY IS EITHER EXPRESS OR IMPLIED WITH
 * RESPECT TO THE USE OF THE SOFTWARE OR DOCUMENTATION. Under no circumstances
 * shall University be liable for incidental, special, indirect, direct or
 * consequential damages or loss of profits, interruption of business, or
 * related expenses which may arise from use of Software or Documentation,
 * including but not limited to those resulting from defects in Software and/or
 * Documentation, or loss or inaccuracy of data of any kind.
 */

#include "galois/Timer.h"
#include "galois/Timer.h"
#include "galois/graphs/Graph.h"
#include "galois/Galois.h"
#include "galois/graphs/FileGraph.h"

#include "llvm/Support/CommandLine.h"
#include "Lonestar/BoilerPlate.h"

#include <string>
#include <sstream>
#include <limits>
#include <iostream>
#include <set>

#include <boost/config.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>

static const unsigned int DIST_INFINITY =
    std::numeric_limits<unsigned int>::max() - 1;

static const char* name = "Delta-Stepping";
static const char* desc =
    "Computes the shortest path from a source node to all nodes in a directed "
    "graph using the Delta-Stepping algorithm";
static const char* url = NULL;

namespace cll = llvm::cl;
static cll::opt<std::string>
    filename(cll::Positional, cll::desc("<input graph>"), cll::Required);
static cll::opt<unsigned int> startNode("startNode",
                                        cll::desc("Node to start search from"),
                                        cll::init(0));
static cll::opt<unsigned int>
    reportNode("reportNode", cll::desc("Node to report distance to"),
               cll::init(1));
static cll::opt<int> stepShift("delta",
                               cll::desc("Shift value for the deltastep"),
                               cll::init(10));
static cll::opt<bool> useBFS("bfs", cll::desc("Use BFS"), cll::init(false));

int main(int argc, char** argv) {
  galois::StatManager statManager;
  LonestarStart(argc, argv, name, desc, url);

  typedef galois::graphs::LC_CSR_Graph<void, unsigned int> Graph;
  Graph g;

  galois::graphs::readGraph(g, filename);
  std::cout << "Read " << g.size() << " nodes\n";
  std::cout << "Using delta-step of " << (1 << stepShift) << "\n";

  // Copy graph data out into edge and weight vectors
  typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS,
                                boost::no_property,
                                boost::property<boost::edge_weight_t, int>>
      BGraph;

  BGraph bg(g.size());
  // int numEdges = g.sizeEdges();

  for (Graph::iterator ii = g.begin(), ei = g.end(); ii != ei; ++ii) {
    for (Graph::edge_iterator jj = g.edge_begin(*ii), ej = g.edge_end(*ii);
         jj != ej; ++jj) {
      boost::add_edge(*ii, g.getEdgeDst(jj), useBFS ? 1 : g.getEdgeData(jj),
                      bg);
    }
  }

  typedef boost::property_map<BGraph, boost::vertex_index_t>::const_type
      IndexMap;
  typedef boost::iterator_property_map<std::vector<int>::iterator, IndexMap>
      DistanceMap;
  std::vector<int> distanceS(boost::num_vertices(bg), 0);
  DistanceMap distance(distanceS.begin(), boost::get(boost::vertex_index, bg));

  galois::StatTimer T;
  T.start();
  boost::dijkstra_shortest_paths(
      bg, boost::vertex(startNode, bg),
      boost::predecessor_map(boost::dummy_property_map())
          .distance_map(distance));
  T.stop();

  std::cout << reportNode << " " << distanceS[reportNode] << "\n";

  return 0;
}


#define BOOST_TEST_MODULE pcraster com algorithm
#include <boost/test/unit_test.hpp>
#include "stddefx.h"
#include "com_binaryoperators.h"

BOOST_AUTO_TEST_CASE(mv_cast)
{
  using namespace com;

  UINT1 to[] = {  2,  5,  3};
  UINT1 from = MV_UINT1;

 addValue(to, to+3, from);

 BOOST_CHECK(to[0]==MV_UINT1);
}

BOOST_AUTO_TEST_CASE(divide)
{
  using namespace com;

  double to[]   = { 10.0, 20.0, 30.0 };
  double from[] = {  2.0,  5.0,  3.0 };

  divideByRange(to, to + 3, from);

  BOOST_CHECK(to[0] ==  5.0);
  BOOST_CHECK(to[1] ==  4.0);
  BOOST_CHECK(to[2] == 10.0);

  divideByValue(to, to + 3, 2.0);
  BOOST_CHECK(to[0] == 2.5);
  BOOST_CHECK(to[1] == 2.0);
  BOOST_CHECK(to[2] == 5.0);

}


/*
	Author: Seyed-Vahid Sanei-Mehri
	Contact: vahid.sanei@gmail.com or vas@iastate.edu

	Here is the source code for ** BUTTERFLY COUNTING IN BIPARTITE NETWORKS **
	On Arxiv: Sanei-Mehri, Seyed-Vahid, Erdem Saryuce, and Srikanta Tirthapura. "Butterfly Counting in Bipartite Networks." arXiv preprint arXiv:1801.00338 (2017).

	Abstract:
		We consider the problem of counting motifs in bipartite affiliation networks, such as author-paper, user-product, and actor-movie relations.
		We focus on counting the number of occurrences of a "butterfly", a complete 2�2 biclique, the simplest cohesive higher-order structure in a bipartite graph.
		Our main contribution is a suite of randomized algorithms that can quickly approximate the number of butterflies in a graph with a provable guarantee on accuracy.
		An experimental evaluation on large real-world networks shows that our algorithms return accurate estimates within a few seconds, even for networks with trillions of
		butterflies and hundreds of millions of edges.
*/

#define _CRT_SECURE_NO_WARNINGS

#include <cstdlib>
#include <cstring>
#include <ctime>
#include <cassert>

#include <iostream>
#include <string>
#include <algorithm>
#include <numeric>
#include <set>
#include <unordered_set>
#include <map>
#include <sstream>
#include <random>
//#include <boost/random/mersenne_twister.hpp>
//#include <boost/random/uniform_int_distribution.hpp>
//#include <boost/random/uniform_real_distribution.hpp>
//#include <boost/random/random_device.hpp>

using namespace std;
//using namespace boost::random;

#define SZ(x) ((int)x.size())
#define ll long long
#define ull unsigned long long
#define ld long double
#define eps 1e-11
#define max(x, y) ((x)>(y)?(x):(y))
#define min(x, y) ((x)<(y)?(x):(y))

const int ITER_VER = 2200;
const ll shift = 1000 * 1000 * 1000LL;
const double TIME_LIMIT = 20;
const int N_WEDGE_ITERATIONS = 2 * 1000 * 1000 * 10;
const int ITERATIONS_SAMPLING = 5;
const int N_SPARSIFICATION_ITERATIONS = 5;
const int TIME_LIMIT_SPARSIFICATION = 10000; // !half an hour
const int N_FAST_EDGE_BFC_ITERATIONS = 2100; // used for fast edge sampling
const int N_FAST_WEDGE_ITERATIONS = 50; // used for fast wedge sampling

char input_address[2000], output_address[2000];

set<pair<int, int> > edges;
vector<pair<int, int> > list_of_edges;
map<int, int> vertices[2];
vector<int> index_map;
vector<int> vertices_in_left;
vector<int> vertices_in_right;
vector<vector<int> > adj;
vector<vector<int> > sampled_adj_list;
vector<bool> visited;
vector<int> list_of_vertices;
vector<int> vertex_counter;

ll n_vertices;
ll n_edges;
ld exact_n_bf;
ll n_wedge_in_partition[2];
ll largest_index_in_partition[2];

vector<int> clr;
vector<int> hashmap_C;
vector<ll> sum_wedges;
vector<ll> sum_deg_neighbors;
vector<int> aux_array_two_neighboorhood;

void clear_everything() {
    largest_index_in_partition[0] = largest_index_in_partition[1] = 0;
    n_vertices = 0;
    n_edges = 0;
    edges.clear();
    vertices[0].clear();
    vertices[1].clear();
    index_map.clear();
    vertices_in_left.clear();
    vertices_in_right.clear();
    adj.clear();
    sampled_adj_list.clear();
    visited.clear();
    list_of_edges.clear();
    vertex_counter.clear();
    clr.clear();
    hashmap_C.clear();
    sum_wedges.clear();
    sum_deg_neighbors.clear();
    aux_array_two_neighboorhood.clear();
}

void resize_all() {
    clr.resize(n_vertices);
    hashmap_C.resize(n_vertices);
    aux_array_two_neighboorhood.resize(n_vertices);
    sum_wedges.resize(n_vertices);
    visited.resize(n_vertices);
    index_map.resize(n_vertices);
    sum_deg_neighbors.resize(n_vertices);
}

// ------------- Read the graph ---------------------
void add_vertex(int A, int side) {
    if (vertices[side].find(A) == vertices[side].end()) {
        if (side == 0) vertices_in_left.push_back(A);
        else vertices_in_right.push_back(A);
        vertices[side][A] = 1;
    }
}

void get_index(int &A, int side) {
    if (vertices[side].find(A) == vertices[side].end()) {
        vertices[side][A] = largest_index_in_partition[side]++;
    }
    A = vertices[side][A];
}

void add_edge(int &A, int &B) {
    add_vertex(A, 0);
    add_vertex(B, 1);
    if (edges.find(make_pair(A, B)) == edges.end()) {
        edges.insert(make_pair(A, B));
        n_edges++;
    }
}

bool all_num(string &s) {
    for (int i = 0; i < SZ(s); i++) if ((s[i] >= '0' && s[i] <= '9') == false) return false;
    return true;
}

void get_graph() {
    freopen(input_address, "r", stdin);
    string s;
    cin.clear();
    while (getline(cin, s)) {
        stringstream ss;
        ss << s;
        vector<string> vec_str;
        for (string z; ss >> z; vec_str.push_back(z));
        if (SZ(vec_str) >= 2) {
            bool is_all_num = true;
            for (int i = 0; i < min (2, SZ(vec_str)); i++) is_all_num &= all_num(vec_str[i]);
            if (is_all_num) {
                int A, B;
                ss.clear();
                ss << vec_str[0];
                ss >> A;
                ss.clear();
                ss << vec_str[1];
                ss >> B;
                add_edge(A, B);
            }
        }
    }
    vertices[0].clear();
    vertices[1].clear();
    largest_index_in_partition[0] = 0;
    largest_index_in_partition[1] = SZ(vertices_in_left);
    n_vertices = SZ(vertices_in_left) + SZ(vertices_in_right);
    adj.resize(n_vertices, vector<int>());
    for (auto edge : edges) {
        int A = edge.first;
        int B = edge.second;
        get_index(A, 0);
        get_index(B, 1);
        adj[A].push_back(B);
        adj[B].push_back(A);
        list_of_edges.push_back(make_pair(A, B));
    }
    resize_all();

    n_wedge_in_partition[0] = 0;
    for (int i = 0; i < largest_index_in_partition[0]; i++) {
        n_wedge_in_partition[0] += (((ll) SZ(adj[i])) * (SZ(adj[i]) - 1)) >> 1;
    }
    n_wedge_in_partition[1] = 0;
    for (int i = largest_index_in_partition[0]; i < largest_index_in_partition[1]; i++) {
        n_wedge_in_partition[1] += ((ll) SZ(adj[i]) * (SZ(adj[i]) - 1)) >> 1;
    }
    for (int i = 0; i < n_vertices; i++) {
        sort(adj[i].begin(), adj[i].end());
        sum_deg_neighbors[i] = 0;
        for (auto neighbor : adj[i]) {
            sum_deg_neighbors[i] += SZ(adj[neighbor]);
        }
    }
    cerr << " for test # edges :: " << SZ(list_of_edges) << " left :: " << SZ(vertices_in_left) << " right :: "
         << SZ(vertices_in_right) << endl;
    sort(list_of_edges.begin(), list_of_edges.end());
    fclose(stdin);
}
// ------------- Read the graph ---------------------

int exact_neighbor_intersections(int a, int b) {
    int common = 0;
    if (SZ(adj[a]) > SZ(adj[b])) swap(a, b);
    unordered_set<int> set;
    for (int i = 0; i < SZ(adj[a]); i++) set.insert(adj[a][i]);
    for (int j = 0; j < SZ(adj[b]); j++) {
        if (set.find(adj[b][j]) != set.end())
            common++;
    }
    return common;
}

double fast_neighbor_intersections(int a, int b) {
    if (SZ(adj[a]) > SZ(adj[b])) swap(a, b);
    random_device rdw;
    mt19937 genedg(rdw());
    uniform_int_distribution<int> dis(0, SZ(adj[a]) - 1);
    ld sum_wedges = 0;
    int n_iterations = N_WEDGE_ITERATIONS;
    for (int i = 0; i < n_iterations; i++) {
        int c = adj[a][dis(genedg)];
        if (binary_search(adj[b].begin(), adj[b].end(), c)) {
            sum_wedges += SZ(adj[a]);
        }
    }
    sum_wedges /= n_iterations;
    return sum_wedges;
}

ll exact_butterfly_counting(vector<vector<int> > &graph) {
    int side = n_wedge_in_partition[0] < n_wedge_in_partition[1];
    ld res = 0;
    for (int vertex = side == 0 ? 0 : SZ(vertices_in_left); vertex < largest_index_in_partition[side]; vertex++) {
        int idx = 0;
        for (int j = 0; j < SZ(graph[vertex]); j++) {
            int neighbor = graph[vertex][j];
            for (int k = 0; k < SZ(graph[neighbor]); k++) {
                int two_hop_neighborhood = graph[neighbor][k];
                if (vertex > two_hop_neighborhood) {
                    res += hashmap_C[two_hop_neighborhood];
                    hashmap_C[two_hop_neighborhood]++;
                    if (hashmap_C[two_hop_neighborhood] == 1)
                        aux_array_two_neighboorhood[idx++] = two_hop_neighborhood;
                } else break;
            }
        }
        for (int j = 0; j < idx; j++) {
            hashmap_C[aux_array_two_neighboorhood[j]] = 0;
        }
    }
    return res;
}

ll compute_n_wedges() {
    ll wedges = 0;
    for (int i = 0; i < n_vertices; i++) {
        wedges += ((ll) (SZ(adj[i])) * ((ll) SZ(adj[i]) - 1)) >> 1;
        sum_wedges[i] = wedges;
    }
    return wedges;
}

ld error_percent(ld &res) {
    if (exact_n_bf == 0) return 0;
    ld error = (res - exact_n_bf) / exact_n_bf * 100.0;
    if (error < 0) error *= -1.0;
    return error;
}

ld wedge_sampling(uniform_int_distribution<ll> &dis, mt19937_64 &eng, int &iter, int &alpha, ll &n_wedges) {
    ld res_wedge_samp = 0;
    for (; iter < alpha; iter++) {
        int l1, l2;
        ll ran = dis(eng);
        int lo = 0, hi = n_vertices - 1;
        while (lo < hi) {
            int mid = (lo + hi) >> 1;
            if (sum_wedges[mid] < ran) {
                lo = mid + 1;
            } else {
                hi = mid;
            }
        }
        random_device rdev_wedge_low;
        mt19937 eng_wedge(rdev_wedge_low());
        uniform_int_distribution<int> dis1(0, SZ(adj[lo]) - 1);
        l1 = dis1(eng_wedge);
        uniform_int_distribution<int> dis2(0, SZ(adj[lo]) - 2);
        l2 = dis2(eng_wedge);
        if (l2 >= l1) l2++;
        l1 = adj[lo][l1];
        l2 = adj[lo][l2];
        res_wedge_samp += exact_neighbor_intersections(l1, l2) - 1;
    }
    return res_wedge_samp;
}

ld fast_wedge_sampling(uniform_int_distribution<ll> &dis, mt19937_64 &gen, int &iter, int &alpha, ll &n_wedges) {
    ld res_fast_wedge_samp = 0;
    for (; iter < alpha; iter++) {
        int l1, l2;
        ll ran = dis(gen);
        int lo = 0, hi = n_vertices - 1;
        while (lo < hi) {
            int mid = (lo + hi) >> 1;
            if (sum_wedges[mid] < ran) {
                lo = mid + 1;
            } else {
                hi = mid;
            }
        }
        random_device rdev_wedge_low;
        mt19937 eng_fast_wedge(rdev_wedge_low());
        uniform_int_distribution<int> dis1(0, SZ(adj[lo]) - 1);
        l1 = dis1(eng_fast_wedge);
        uniform_int_distribution<int> dis2(0, SZ(adj[lo]) - 2);
        l2 = dis2(eng_fast_wedge);
        if (l2 >= l1) l2++;
        l1 = adj[lo][l1];
        l2 = adj[lo][l2];
        res_fast_wedge_samp += fast_neighbor_intersections(l1, l2);
    }
    return res_fast_wedge_samp;
}

ld colorful_sparsification(int num_clr) {
    random_device rdev_colorful;
    mt19937 eng_colorful(rdev_colorful());
    uniform_int_distribution<int> dis(0, num_clr - 1);

    for (int i = 0; i < n_vertices; i++) {
        clr[i] = dis(eng_colorful);
    }
    sampled_adj_list.resize(n_vertices, (vector<int>()));

    n_wedge_in_partition[0] = n_wedge_in_partition[1] = 0;
    for (auto edge : edges) {
        int A = edge.first;
        int B = edge.second;
        if (A > B && clr[A] == clr[B]) {
            sampled_adj_list[A].push_back(B);
            sampled_adj_list[B].push_back(A);
            n_wedge_in_partition[0] += 2 * SZ(sampled_adj_list[A]) - 1;
            n_wedge_in_partition[1] += 2 * SZ(sampled_adj_list[B]) - 1;
        }
    }
    ld beta = exact_butterfly_counting(sampled_adj_list);
    return beta * num_clr * num_clr * num_clr;
}

ld edge_saprsification(double prob) {
    prob = prob > 1.0 ? 1.0 : prob;
    random_device rdev_edge_sprs;
    mt19937 eng_edg_sprs(rdev_edge_sprs());
    uniform_real_distribution<double> dis(0.0, 1.0);

    sampled_adj_list.clear();
    sampled_adj_list.resize(n_vertices, (vector<int>()));

    n_wedge_in_partition[0] = n_wedge_in_partition[1] = 0;
    for (auto edge : edges) {
        int A = edge.first;
        int B = edge.second;
        double coin = dis(eng_edg_sprs);
        if (coin <= prob || abs(coin - prob) <= eps) {
            sampled_adj_list[A].push_back(B);
            sampled_adj_list[B].push_back(A);
            n_wedge_in_partition[0] += 2 * SZ(sampled_adj_list[A]) - 1;
            n_wedge_in_partition[1] += 2 * SZ(sampled_adj_list[B]) - 1;
        }
    }
    ld beta = exact_butterfly_counting(sampled_adj_list);
    return (ld) beta / (prob * prob * prob * prob);
}

ld exact_BFC_per_edge(int a, int b) {
    ld bfc_per_edge = 0;
    for (int k = 0; k < SZ(adj[a]); k++) {
        int c = adj[a][k];
        if (c != b) {
            bfc_per_edge += exact_neighbor_intersections(c, b) - 1;
        }
    }
    return bfc_per_edge;
}

ld fast_exact_BFC_per_edge(int a, int b) {
    for (int i = 0; i < SZ(adj[a]); i++) {
        int neighbor = adj[a][i];
        if (neighbor != b) {
            for (int j = 0; j < SZ(adj[neighbor]); j++) {
                int two_hop_neighborhood = adj[neighbor][j];
                if (two_hop_neighborhood != a) {
                    index_map[two_hop_neighborhood]++;
                }
            }
        }
    }
    ld bfc_per_edge = 0;
    for (int i = 0; i < SZ(adj[b]); i++) {
        int neighbor = adj[b][i];
        bfc_per_edge += index_map[neighbor];
    }
    for (int i = 0; i < SZ(adj[a]); i++) {
        int neighbor = adj[a][i];
        for (int j = 0; j < SZ(adj[neighbor]); j++) {
            index_map[adj[neighbor][j]] = 0;
        }
    }
    return bfc_per_edge;
}

random_device rd_edge;
mt19937_64 eng_ran_bfc_per_edge(rd_edge());

ld randomized_BFC_per_edge(int a, int b) {
    if (SZ(adj[a]) <= 1 || SZ(adj[b]) <= 1) {
        return 0;
    }
    uniform_int_distribution<int> dis_a(0, SZ(adj[a]) - 1);
    uniform_int_distribution<int> dis_b(0, SZ(adj[b]) - 1);
    ld res_ran_bfc_per_edge = 0;
    for (int i = 0; i < N_FAST_EDGE_BFC_ITERATIONS; i++) {
        int x = adj[a][dis_a(eng_ran_bfc_per_edge)];
        int y = adj[b][dis_b(eng_ran_bfc_per_edge)];
        if (x != b && y != a && binary_search(adj[x].begin(), adj[x].end(), y)) {
            res_ran_bfc_per_edge += ((ld) SZ(adj[a])) * ((ld) SZ(adj[b]));
        }
    }
    res_ran_bfc_per_edge /= N_FAST_EDGE_BFC_ITERATIONS;
    return res_ran_bfc_per_edge;
}

ld exact_bfc_per_vertex(int vertex) {
    ld res = 0;
    for (int i = 0; i < SZ(adj[vertex]); i++) {
        int neighbor = adj[vertex][i];
        for (int j = 0; j < SZ(adj[neighbor]); j++) {
            int two_hop_neighborhood = adj[neighbor][j];
            if (two_hop_neighborhood != vertex) {
                res += index_map[two_hop_neighborhood];
                index_map[two_hop_neighborhood]++;
            }
        }
    }
    for (int i = 0; i < SZ(adj[vertex]); i++) {
        int neighbor = adj[vertex][i];
        for (int j = 0; j < SZ(adj[neighbor]); j++) {
            index_map[adj[neighbor][j]] = 0;
        }
    }
    return res;
}

ld edge_sampling(mt19937 &eng, uniform_int_distribution<int> &dis, int &iter, int &alpha) {
    ld ans = 0;
    for (; iter < alpha; iter++) {
        int random_edge = dis(eng);
        int a = list_of_edges[random_edge].first;
        int b = list_of_edges[random_edge].second;
        ans += fast_exact_BFC_per_edge(a, b);
    }
    return ans;
}

ld fast_edge_sampling(mt19937 &eng, uniform_int_distribution<int> &dis, int &iter, int &alpha) {
    ld res = 0;
    for (; iter < alpha; iter++) {
        int random_edge = dis(eng);
        int a = list_of_edges[random_edge].first;
        int b = list_of_edges[random_edge].second;
        if (sum_deg_neighbors[a] > sum_deg_neighbors[b]) {
            swap(a, b);
        }
        if (SZ(adj[b]) + sum_deg_neighbors[a] * 2 > N_FAST_EDGE_BFC_ITERATIONS)
            res += randomized_BFC_per_edge(a, b);
        else
            res += fast_exact_BFC_per_edge(a, b);
    }
    return res;
}

ld vertex_sampling(uniform_int_distribution<ll> &dis, mt19937_64 &eng, int &mx, int &iter, int &alpha) {
    ld res = 0;
    for (; iter < alpha; iter++) {
        int random_vertex = dis(eng);
        res += exact_bfc_per_vertex(random_vertex);
    }
    return res;
}

void edge_sparsfication_time_tracker() {
    for (double prob = 0.012; prob < 1.0; prob *= 2) {
        vector<pair<ld, pair<ld, ld> > > aux_res;
        for (int i = 0; i < N_SPARSIFICATION_ITERATIONS; i++) {
            double beg_clock = clock();
            ld res = edge_saprsification(prob);
            double end_clock = clock();
            ld error = error_percent(res);
            double elpased_time = double(end_clock - beg_clock) / CLOCKS_PER_SEC;
            aux_res.push_back(make_pair(error, make_pair(elpased_time, res)));
        }

        sort(aux_res.begin(), aux_res.end());
        double elpased_time = aux_res[N_SPARSIFICATION_ITERATIONS / 2].second.first;
        ld Er = aux_res[N_SPARSIFICATION_ITERATIONS / 2].first;

        if (prob >= 1) {
            cout << elpased_time << " " << 1 << " " << 0 << endl;
            if (elpased_time < TIME_LIMIT_SPARSIFICATION) {
                cout << TIME_LIMIT << " " << 1 << " " << 0 << endl;
            } else {
                cout << elpased_time << " " << 1 << " " << 0 << endl;
            }
            break;
        } else {
            cout << elpased_time << " " << prob << " " << Er << " " << endl;
        }
        if (elpased_time >= TIME_LIMIT_SPARSIFICATION)
            break;
    }
}

void coloful_sparsification_time_tracker() {
    for (int num_clr = 10000;; num_clr /= 2.0) {
        double prob = 1. / num_clr;
        vector<pair<ld, pair<ld, ld> > > aux_ans;
        for (int i = 0; i < N_SPARSIFICATION_ITERATIONS; i++) {
            double beg = clock();
            ld res = colorful_sparsification(num_clr);
            double prob = 1. / num_clr;
            double end = clock();
            ld error = error_percent(res);
            double elapsed_time = double(end - beg) / CLOCKS_PER_SEC;
            aux_ans.push_back(make_pair(error, make_pair(elapsed_time, res)));
        }
        sort(aux_ans.begin(), aux_ans.end());
        double elapsed_time = aux_ans[N_SPARSIFICATION_ITERATIONS / 2].second.first;
        ld error = aux_ans[N_SPARSIFICATION_ITERATIONS / 2].first;
        if (prob >= 1) {
            cout << elapsed_time << " " << 1 << " " << 0 << endl;
            if (elapsed_time < TIME_LIMIT_SPARSIFICATION)
                cout << TIME_LIMIT << " " << 1 << " " << 0 << endl;
            else
                cout << elapsed_time << " " << 1 << " " << 0 << endl;

            break;
        } else
            cout << elapsed_time << " " << prob << " " << error << endl;
        if (elapsed_time >= TIME_LIMIT_SPARSIFICATION)
            break;
    }
}

void edge_sampling_time_tracker() {
    random_device rd;
    mt19937 eng(rd());
    uniform_int_distribution<int> dis(0, n_edges - 1);
    double elapsed_time = 0;
    ld res = 0;
    vector<ld> total_res;
    for (int alpha = 10, iter = 0;; alpha += 10) {
        double cur_elapsed_time = 0;
        vector<pair<ld, pair<ld, ld> > > aux_res;
        double res_from_previous_iterations = 0;
        for (int i = 0; i < SZ(total_res); i++) {
            res_from_previous_iterations += (total_res[i] / 4.0) / alpha;
        }
        int previous_iteration = iter;
        for (int k = 0; k < ITERATIONS_SAMPLING; k++) { // run the algorithm ITERATION times
            iter = previous_iteration;
            double cur_beg_clock = clock();
            res = edge_sampling(eng, dis, iter, alpha);
            double cur_end_clock = clock();
            cur_elapsed_time = double(cur_end_clock - cur_beg_clock) / CLOCKS_PER_SEC;
            ld ans = (res / 4.0) / alpha;
            ans += res_from_previous_iterations;
            ans *= n_edges;
            ld error = error_percent(ans);
            aux_res.push_back(make_pair(error, make_pair(cur_elapsed_time, res)));
        }
        sort(aux_res.begin(), aux_res.end()); // take the median
        cur_elapsed_time = aux_res[ITERATIONS_SAMPLING / 2].second.first;
        ld error = aux_res[ITERATIONS_SAMPLING / 2].first;
        elapsed_time += cur_elapsed_time;

        total_res.push_back(aux_res[ITERATIONS_SAMPLING / 2].second.second);
        cout << elapsed_time << " " << iter << " " << error << endl;
        if (elapsed_time >= TIME_LIMIT) {
            cout << alpha << endl;
            break;
        }
    }
}

void fast_edge_sampling_time_tracker() {
    random_device rd;
    mt19937 genedg(rd());
    uniform_int_distribution<int> dis(0, n_edges - 1);
    double elapsed_time = 0;
    ld res = 0;
    vector<ld> total_res;
    for (int alpha = 1000, iter = 0;; alpha += 1000) {
        double cur_elapsed_time = 0;
        vector<pair<ld, pair<ld, ld> > > aux_res;
        double res_from_previous_iterations = 0;
        for (int i = 0; i < SZ(total_res); i++) {
            res_from_previous_iterations += (total_res[i] / 4.0) / alpha;
        }
        int previous_iterations = iter;
        for (int k = 0; k < ITERATIONS_SAMPLING; k++) { // run the algorithm ITERATION times
            iter = previous_iterations;
            double cur_beg_clock = clock();
            res = fast_edge_sampling(genedg, dis, iter, alpha);
            double cur_end_clock = clock();
            double cur_elapsed_time = double(cur_end_clock - cur_beg_clock) / CLOCKS_PER_SEC;
            ld ans = (res / 4.0) / (alpha);
            ans += res_from_previous_iterations;
            ans *= n_edges;
            ld error = error_percent(ans);
            aux_res.push_back(make_pair(error, make_pair(cur_elapsed_time, res)));
        }
        sort(aux_res.begin(), aux_res.end()); // take the median
        cur_elapsed_time = aux_res[ITERATIONS_SAMPLING / 2].second.first;
        ld error = aux_res[ITERATIONS_SAMPLING / 2].first;
        elapsed_time += cur_elapsed_time;
        total_res.push_back(aux_res[ITERATIONS_SAMPLING / 2].second.second);

        cout << elapsed_time << " " << iter << " " << error << endl;
        if (elapsed_time >= TIME_LIMIT) {
            cout << alpha << endl;
            break;
        }
    }
}

void vertex_sampling_time_tracker() {
    vector<int> cnt_vertex(n_vertices);
    random_device rd;
    mt19937_64 eng(rd());
    uniform_int_distribution<ll> dis(0, n_vertices - 1);
    double elapsed_time = 0;
    ld res = 0;
    vector<ld> total_res;
    int mx = 0;
    for (int alpha = 10, iter = 0;; alpha += 10) {
        double cur_elaped_time = 0;
        vector<pair<ld, pair<ld, ld> > > aux_res;
        double res_from_previous_iterations = 0;
        for (int I = 0; I < SZ(total_res); I++) {
            res_from_previous_iterations += (total_res[I] / 4.0) / alpha;
        }
        int previous_iterations = iter;
        for (int k = 0; k < ITERATIONS_SAMPLING; k++) { // run the algorithm ITERATION times
            iter = previous_iterations;
            double cur_beg_clock = clock();
            res = vertex_sampling(dis, eng, mx, iter, alpha);
            double cur_end_clock = clock();
            double cur_elapsed_time = double(cur_end_clock - cur_beg_clock) / CLOCKS_PER_SEC;
            ld ans = (res / 4.0) / alpha;
            ans += res_from_previous_iterations;
            ans *= n_vertices;
            ld error = error_percent(ans);
            aux_res.push_back(make_pair(error, make_pair(cur_elaped_time, res)));
        }
        sort(aux_res.begin(), aux_res.end()); // take the median
        cur_elaped_time = aux_res[ITERATIONS_SAMPLING / 2].second.first;
        ld error = aux_res[ITERATIONS_SAMPLING / 2].first;
        elapsed_time += cur_elaped_time;
        total_res.push_back(aux_res[ITERATIONS_SAMPLING / 2].second.second);

        cout << elapsed_time << " " << iter << " " << aux_res[ITERATIONS_SAMPLING / 2].first << endl;
        if (elapsed_time >= TIME_LIMIT) {
            cout << alpha << endl;
            break;
        }
    }
}

void wedge_sampling_time_tracker() {
    clock_t beg_clock_n_wedge = clock();
    ll n_wedges = compute_n_wedges();
    clock_t end_clock_n_wedge = clock();
    double time_n_wedges = double(end_clock_n_wedge - beg_clock_n_wedge) / CLOCKS_PER_SEC;
    random_device rd;
    mt19937_64 eng(rd());
    uniform_int_distribution<ll> dis(1, n_wedges);
    double total_elapsed_time = 0;
    ld res = 0;
    ld total = 0;
    vector<ld> total_res;
    for (int alpha = 1000, iter = 0;; alpha += 5000) {
        vector<pair<ld, pair<ld, ld> > > aux_res;
        ld res_sum_from_previous_results = 0;
        for (int i = 0; i < SZ(total_res); i++) {
            res_sum_from_previous_results += (total_res[i] / 4.0) / alpha;
        }
        ld cur_res = 0;
        int pre_iteration = iter;
        for (int k = 0; k < ITERATIONS_SAMPLING; k++) { // run the algorithm ITERATION times
            iter = pre_iteration;
            double beg = clock();
            res = wedge_sampling(dis, eng, iter, alpha, n_wedges);
            double end = clock();
            double cur_elapsed_time = double(end - beg) / CLOCKS_PER_SEC;
            cur_res = (res / 4.0) / alpha;
            cur_res += res_sum_from_previous_results;
            cur_res *= (ld) n_wedges;
            ld error = error_percent(cur_res);
            aux_res.push_back(make_pair(error, make_pair(cur_elapsed_time, res)));
        }
        sort(aux_res.begin(), aux_res.end()); // take the median
        double cur_elapsed_time = aux_res[ITERATIONS_SAMPLING / 2].second.first;
        ld error = aux_res[ITERATIONS_SAMPLING / 2].first;
        total_elapsed_time += cur_elapsed_time;
        total_res.push_back(aux_res[ITERATIONS_SAMPLING / 2].second.second);

        cout << cur_elapsed_time + time_n_wedges << " " << iter << " " << error << endl;
        if (total_elapsed_time + time_n_wedges >= TIME_LIMIT) {
            cout << "iterations: " << alpha << endl;
            break;
        }
    }
    return;
}

void fast_wedge_sampling_time_tracker() {
    clock_t beg_clock_n_wedge = clock();
    ll n_wedges = compute_n_wedges();
    clock_t end_clock_n_wedge = clock();
    double time_n_wedges = double(end_clock_n_wedge - beg_clock_n_wedge) / CLOCKS_PER_SEC;
    random_device rd;
    mt19937_64 eng(rd());
    uniform_int_distribution<ll> dis(1, n_wedges);
    double total_elapsed_time = 0;
    ld res = 0;
    ld total = 0;
    vector<ld> total_res;
    for (int alpha = 1000, iter = 0;; alpha += 5000) {
        double cur_elapsed_time = 0;
        vector<pair<ld, pair<ld, ld> > > aux_res;
        ld res = 0;
        int pre_iteration = iter;
        for (int k = 0; k < ITERATIONS_SAMPLING; k++) { // run the algorithm ITERATION times
            iter = pre_iteration;
            double beg = clock();
            res = fast_wedge_sampling(dis, eng, iter, alpha, n_wedges);
            double end = clock();
            double aux_elapsed_time = double(end - beg) / CLOCKS_PER_SEC;
            res += total;
            res /= 4.0;
            res /= alpha;
            res *= (ld) n_wedges;
            ld error = error_percent(res);
            aux_res.push_back(make_pair(error, make_pair(aux_elapsed_time, res)));
        }
        sort(aux_res.begin(), aux_res.end()); // take the median
        double aux_elapsed_time = aux_res[ITERATIONS_SAMPLING / 2].second.first;
        ld error = aux_res[ITERATIONS_SAMPLING / 2].first;
        total_elapsed_time += aux_elapsed_time;
        total += aux_res[ITERATIONS_SAMPLING / 2].second.second;

        cout << total_elapsed_time + time_n_wedges << " " << iter << " " << error << endl;
        if (total_elapsed_time + time_n_wedges >= TIME_LIMIT) {
            cout << "iterations: " << alpha << endl;
            break;
        }
    }
    return;
}

void exact_algorithm_time_tracker() {
    double beg_clock = clock();
    exact_n_bf = exact_butterfly_counting(adj);
    double end_clock = clock();
    double elapsed_time = (end_clock - beg_clock) / CLOCKS_PER_SEC;
    cout << " Exact algorithm is done in " << elapsed_time << " secs. There are " << exact_n_bf << " butterflies."
         << endl;
}

string algorithm_names[8] = {"Exact", "Edge Sampling", "Fast Edge Sampling", "Vertex Sampling", "Wedge Sampling",
                             "Edge Sparsification", "Colorful Sparsification"};

void read_the_graph() {
    clear_everything();
    cerr << " Insert the input (bipartite network) file location" << endl;
    cerr << " >>> ";
    cin >> input_address;
    cerr << " Insert the output file" << endl;
    cerr << " >>> ";
    cin >> output_address;
    freopen(output_address, "w", stdout);
    cerr
            << " ---------------------------------------------------------------------------------------------------------------------- \n";
    cerr << "| * Note that edges should be separated line by line.\n\
| In each line, the first integer number is considered as a vertex in the left partition of bipartite network, \n\
| and the second integer number is a vertex in the right partition. \n\
| In addition, multiple edges are removed from the given bipartite network.\n\
| Also, note that in this version of the source code, we did NOT remove vertices with degree zero.\n";
    cerr
            << " ---------------------------------------------------------------------------------------------------------------------- \n";

    cerr << " Processing the graph ... (please wait) \n";

    get_graph();

    cerr << " -------------------------------------------------------------------------- \n";
    cerr << " The graph is processed - there are " << n_vertices << " vertices and " << n_edges << " edges  \n";
    cerr << " -------------------------------------------------------------------------- \n";
}

void choose_algorithm() {
    string s;
    while (true) {
        cerr << " Insert one of the following numbers (1-7): \n\
	 [1]: Exact Algorithm \n\
	 [2]: Edge Sampling Algorithm \n\
	 [3]: Fast Edge Sampling Algorithm \n\
	 [4]: Vertex Sampling Algorithm \n\
	 [5]: Wedge Sampling Algorithm \n\
	 [6]: Edge Sparsification Algorithm \n\
	 [7]: Colorful Sparsification Algorithm \n";
        cerr << " >>> ";
        cin >> s;
        if (SZ(s) == 1 && s[0] >= '0' && s[0] <= '7') break;
    }
    int chosen = s[0] - '0';
    if (chosen > 1) {
        cerr << " " << algorithm_names[chosen - 1]
             << " Algorithm is a randomized algorithm. To report the accuracy, we need exact number of butterflies"
             << endl;
        cerr
                << " Insert the number of butterflies. In the case, you do not know the number of butterflies, insert \"N\".\n We will run the exact algorithm for you."
                << endl;
        string comm;
        cerr << " >>> ";
        cin >> comm;
        read_the_graph();
        if (all_num(comm)) {
            stringstream ss;
            ss << comm;
            ss >> exact_n_bf;
        } else {
            cerr
                    << " As you do not know the exact number of butterflies, we are going to run the exact algorithm ... \n";
            exact_algorithm_time_tracker();
        }
    } else {
        read_the_graph();
    }

    cerr << " " << algorithm_names[chosen - 1] << " Algorithm is running ... (please wait) " << endl;
    if (chosen == 1) {
        exact_algorithm_time_tracker();
    } else {
        if (chosen <= 5)
            cout << "Time(sec) #Iterations Error(%)" << endl;
        else
            cout << "Time(sec) Probability Error(%)" << endl;
        if (chosen == 2) {
            edge_sampling_time_tracker();
        } else if (chosen == 3) {
            fast_edge_sampling_time_tracker();
        } else if (chosen == 4) {
            vertex_sampling_time_tracker();
        } else if (chosen == 5) {
            wedge_sampling_time_tracker();
        } else if (chosen == 6) {
            edge_sparsfication_time_tracker();
        } else if (chosen == 7) {
            coloful_sparsification_time_tracker();
        }
    }
}

int main() {
    std::ios::sync_with_stdio(false);
    choose_algorithm();
    cerr << " Take a look at the output file ..." << endl;
    return 0;
}


/*
 * fitness.cpp
 * Date: 2015-04-04
 * Author: Karsten Ahnert (karsten.ahnert@gmx.de)
 * Copyright: Karsten Ahnert
 *
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or
 * copy at http://www.boost.org/LICENSE_1_0.txt)
 */

#include <gpcxx/eval/adjusted_fitness.hpp>
#include <gpcxx/eval/normalized_fitness.hpp>
#include <gpcxx/eval/hits.hpp>
#include <gpcxx/util/macros.hpp>

#include <boost/range/algorithm/count.hpp>
#include <boost/range/algorithm/for_each.hpp>

#include <iostream>
#include <vector>

int main( int argc , char** argv )
{
    {
        std::vector< double > fitness = { 2.0 , 10.0 , 40.0 , 0.5 , 0.00001 , 1.0 };
        //[ fitness_simple_example
        auto adjusted_fitness = gpcxx::adjusted_fitness_copy( fitness );
        auto normalized_fitness = gpcxx::normalized_fitness_copy( adjusted_fitness );
        auto hits = gpcxx::hits( adjusted_fitness , 0.001 );
        //]
        for( auto n : normalized_fitness ) std::cout << n << std::endl;
        for( auto h : hits ) std::cout << h << std::endl;
        std::cout << std::endl;
    }

    {
        std::vector< double > fitness = { 2.0 , 10.0 , 40.0 , 0.5 , 0.00001 , 1.0 };
        //[ fitness_simple_views_1
        auto adjusted_fitness = gpcxx::adjusted_fitness_view( fitness );
        auto normalized_fitness = gpcxx::normalized_fitness_copy( adjusted_fitness );
        auto hits = gpcxx::hits( adjusted_fitness , 0.001 );
        //]
        for( auto n : normalized_fitness ) std::cout << n << std::endl;
        for( auto h : hits ) std::cout << h << std::endl;
        std::cout << std::endl;
    }

    {
        std::vector< double > fitness = { 2.0 , 10.0 , 40.0 , 0.5 , 0.00001 , 1.0 };
        //[ fitness_simple_views_2
        auto adjusted_fitness = gpcxx::adjusted_fitness_view( fitness );
        auto normalized_fitness = gpcxx::normalized_fitness_view( adjusted_fitness );
        auto hits = gpcxx::hits_view( adjusted_fitness , 0.001 );
        //]
        for( auto n : normalized_fitness ) std::cout << n << std::endl;
        for( auto h : hits ) std::cout << h << std::endl;
        std::cout << std::endl;
    }

    {
        std::vector< double > fitness = { 2.0 , 10.0 , 40.0 , 0.5 , 0.00001 , 1.0 };
        //[ adjusted_fitness_example
        // computes the adjusted fitness and stores the result in a
        std::vector< double > a( fitness.size() , 0.0 );
        gpcxx::adjusted_fitness( fitness , a );

        // returns a vector containing the adjusted fitness
        auto ax = gpcxx::adjusted_fitness_copy( fitness );

        // returns a view containing the adjusted fitness,
        // hence av is not a container but a range which computes
        // the adjusted fitness only if it is needed
        auto av = gpcxx::adjusted_fitness_view( fitness );

        // computes the adjusted fitness inplace
        gpcxx::adjusted_fitness_inplace( fitness );
        //]
        GPCXX_UNUSED_VAR( ax );
        GPCXX_UNUSED_VAR( av );
    }

    {
        std::vector< double > fitness = { 2.0 , 10.0 , 40.0 , 0.5 , 0.00001 , 1.0 };
        //[ normalized_fitness_example
        // computes the normalized fitness and stores the result in n
        std::vector< double > n( fitness.size() , 0.0 );
        gpcxx::normalized_fitness( fitness , n );

        // returns a vector containing the normalized fitness
        auto nx = gpcxx::normalized_fitness_copy( fitness );

        // returns a view containing the normalized fitness,
        // hence nv is not a container but a range which computes
        // the normalized fitness only if it is needed
        auto nv = gpcxx::normalized_fitness_view( fitness );

        // computes the adjusted fitness inplace
        gpcxx::normalized_fitness_inplace( fitness );
        //]
        GPCXX_UNUSED_VAR( nx );
        GPCXX_UNUSED_VAR( nv );
    }

    {
        std::vector< double > fitness = { 2.0 , 10.0 , 40.0 , 0.5 , 0.00001 , 1.0 };
        //[ hits_example
        // computes the hits and stores the result in n
        std::vector< bool > h( fitness.size() , 0.0 );
        double tolerance = 1.0e-3;
        gpcxx::hits( fitness , h , tolerance );

        // returns a vector containing the hits
        auto hx = gpcxx::hits( fitness , tolerance );

        // returns a view containing the hits
        auto hv = gpcxx::hits_view( fitness , tolerance );

        // compute the number of hits
        auto number_of_hits = boost::count( hv , true );
         //]
        GPCXX_UNUSED_VAR( hx );
        GPCXX_UNUSED_VAR( number_of_hits );
    }

    return 0;
}


/*=========================================================================
 *
 *  Copyright 2011-2013 The University of North Carolina at Chapel Hill
 *  All rights reserved.
 *
 *  Licensed under the MADAI Software License. You may obtain a copy of
 *  this license at
 *
 *         https://madai-public.cs.unc.edu/visualization/software-license/
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/

#include "Model.h"

#include <Eigen/Dense>

#include <cassert>
#include <cmath>
#include <limits>
#include <set>

template<class S, class T>
int static findIndex(const S & v, const T & s)
{
  typename S::const_iterator it = std::find(v.begin(), v.end(), s);
  if (it == v.end())
    return -1;
  return std::distance(v.begin(), it);
}

namespace madai {

Model
::Model() :
  m_LogLikelihoodObservable( -1 ),
  m_GradientEstimateStepSize( 1.0e-4 ),
  m_StateFlag( UNINITIALIZED ),
  m_UseModelCovarianceToCalulateLogLikelihood( false )
{
}

Model
::~Model()
{
}

bool
Model
::IsReady() const
{
  return ( m_StateFlag == READY );
}

unsigned int
Model
::GetNumberOfParameters() const
{
  return static_cast<unsigned int>(m_Parameters.size());
}

const std::vector< Parameter > &
Model
::GetParameters() const
{
  return m_Parameters;
}

std::vector< std::string >
Model
::GetParameterNames() const
{
  std::vector< std::string > parameterNames;
  parameterNames.reserve( m_Parameters.size() );
  for ( std::vector< Parameter >::const_iterator iter = m_Parameters.begin();
        iter != m_Parameters.end();
        ++iter ) {
    parameterNames.push_back( iter->m_Name );
  }
  return parameterNames;
}

unsigned int
Model
::GetNumberOfScalarOutputs() const
{
  return static_cast<unsigned int>(m_ScalarOutputNames.size());
}

const std::vector< std::string > &
Model
::GetScalarOutputNames() const
{
  return m_ScalarOutputNames;
}

Model::ErrorType
Model
::GetScalarAndGradientOutputs(
  const std::vector< double > & parameters,
  const std::vector< bool > & activeParameters,
  std::vector< double > & scalars,
  std::vector< double > & gradient) const
{
  if ( static_cast< unsigned int >( activeParameters.size() ) !=
       this->GetNumberOfParameters() ) {
    return INVALID_ACTIVE_PARAMETERS;
  }

  // Make a copy of the parameters that we can work with
  std::vector< double > parametersCopy( parameters );

  // Clear the output vectors
  scalars.clear();
  gradient.clear();

  Model::ErrorType scalarOutputError;

  double h = m_GradientEstimateStepSize;
  for ( unsigned int i = 0; i < this->GetNumberOfParameters(); ++i ) {

    if ( activeParameters[i] ) {
      // Save the original parameter value
      double originalParameterValue = parametersCopy[i];

      // Compute the scalar outputs for a forward step
      parametersCopy[i] = parameters[i] + h;
      std::vector< double > forwardScalars;
      double forwardLogLikelihood;
      scalarOutputError = this->GetScalarOutputsAndLogLikelihood(
        parametersCopy, forwardScalars, forwardLogLikelihood );
      if ( scalarOutputError != NO_ERROR ) {
        return scalarOutputError;
      }

      // Compute the scalar outputs for a backward step
      parametersCopy[i] = parameters[i] - h;
      std::vector< double > backwardScalars;
      double backwardLogLikelihood;
      scalarOutputError = this->GetScalarOutputsAndLogLikelihood(
        parametersCopy, backwardScalars, backwardLogLikelihood );
      if ( scalarOutputError != NO_ERROR ) {
        return scalarOutputError;
      }

      // Compute the partial derivative with central differences
      double partialDerivative = ( forwardLogLikelihood - backwardLogLikelihood )
        / ( 2.0 * h );

      // Store the partial derivative in the gradient output
      gradient.push_back( partialDerivative );

      // Restore the original parameter value
      parametersCopy[i] = originalParameterValue;
    }

  }

  // Now compute the scalars
  scalarOutputError = this->GetScalarOutputs( parameters, scalars );
  if ( scalarOutputError != NO_ERROR ) {
    return scalarOutputError;
  }

  return NO_ERROR;
}

void
Model
::SetGradientEstimateStepSize( double stepSize )
{
  m_GradientEstimateStepSize = stepSize;
}

double
Model
::GetGradientEstimateStepSize() const
{
  return m_GradientEstimateStepSize;
}

std::string
Model
::GetErrorTypeAsString( Model::ErrorType error )
{
  std::string outputString( "NO_ERROR" );

  switch ( error ) {

  case INVALID_PARAMETER_INDEX:
    outputString = std::string( "INVALID_PARAMETER_INDEX" );
    break;

  case INVALID_ACTIVE_PARAMETERS:
    outputString = std::string( "INVALID_ACTIVE_PARAMETERS" );
    break;

  case FILE_NOT_FOUND_ERROR:
    outputString = std::string( "FILE_NOT_FOUND_ERROR" );
    break;

  case METHOD_NOT_IMPLEMENTED:
    outputString = std::string( "METHOD_NOT_IMPLEMENTED" );
    break;

  case WRONG_VECTOR_LENGTH:
    outputString = std::string( "WRONG_VECTOR_LENGTH" );
    break;

  case OTHER_ERROR:
    outputString = std::string( "OTHER_ERROR" );
    break;

  default:
    break;

  }

  return outputString;
}

bool
Model
::GetUseModelCovarianceToCalulateLogLikelihood()
{
  return m_UseModelCovarianceToCalulateLogLikelihood;
}

void
Model
::SetUseModelCovarianceToCalulateLogLikelihood(bool v)
{
  m_UseModelCovarianceToCalulateLogLikelihood = v;
}

void
Model
::AddParameter( const std::string & name,
                const Distribution & priorDistribution)
{
  m_Parameters.push_back(
    Parameter(name, priorDistribution) );
}

void
Model
::AddScalarOutputName( const std::string & name )
{
  //check to see if this is the log likelihood
  std::string nameLower = name;
  std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), ::tolower);
  if (nameLower == "log_likelihood" || nameLower == "loglikelihood") {
      m_LogLikelihoodObservable = m_ScalarOutputNames.size();
  }

  //actually add it to the list
  m_ScalarOutputNames.push_back( name );
}

Model::ErrorType
Model
::SetObservedScalarValues(const std::vector< double > & observedScalarValues)
{
  size_t size = observedScalarValues.size();
  if ((size != this->GetNumberOfScalarOutputs()) && (size != 0))
    return WRONG_VECTOR_LENGTH;
  // copy the vector
  this->m_ObservedScalarValues = observedScalarValues;
  return NO_ERROR;
}

Model::ErrorType
Model
::SetObservedScalarCovariance(
    const std::vector< double > & observedScalarCovariance)
{
  size_t size = observedScalarCovariance.size();
  if (size == 0) { // represents zero matrix.
    this->m_ObservedScalarCovariance.clear();
    return NO_ERROR;
  }
  size_t t = this->GetNumberOfScalarOutputs();
  if (size != (t * t))
    return WRONG_VECTOR_LENGTH;
  this->m_ObservedScalarCovariance = observedScalarCovariance;
  return NO_ERROR;
}

const std::vector< double > &
Model
::GetObservedScalarValues() const
{
  return m_ObservedScalarValues; // cast to const
}

const std::vector< double > &
Model
::GetObservedScalarCovariance() const
{
  return m_ObservedScalarCovariance; // cast to const;
}

Model::ErrorType
Model
::GetScalarOutputsAndCovariance(
      const std::vector< double > & parameters,
      std::vector< double > & scalars,
      std::vector< double > & scalarCovariance) const
{
  scalarCovariance.clear();
  return this->GetScalarOutputs(parameters, scalars);
}

Model::ErrorType
Model
::GetScalarOutputsAndLogLikelihood(
    const std::vector< double > & parameters,
    std::vector< double > & scalars,
    double & logLikelihood) const
{

  logLikelihood = std::numeric_limits< double >::signaling_NaN();
  double logPriorLikelihood
    = this->GetLogPriorLikelihood(parameters);

  size_t t = this->GetNumberOfScalarOutputs();
  assert(t > 0);
  std::vector< double > scalarCovariance;
  // initially scalarCovariance is a empty vector
  Model::ErrorType result;
  if (m_UseModelCovarianceToCalulateLogLikelihood) {
    result = this->GetScalarOutputsAndCovariance(
        parameters, scalars, scalarCovariance);
  } else {
    // (! m_UseModelCovarianceToCalulateLogLikelihood)
    result = this->GetScalarOutputs(parameters, scalars);
  }
  if (result != NO_ERROR)
    return result;
  if (scalars.size() != t)
    return OTHER_ERROR;

  // Use the model value for the log likelihood if it exists
  if (m_LogLikelihoodObservable > -1) {
    logLikelihood = scalars[m_LogLikelihoodObservable] + logPriorLikelihood;
    return NO_ERROR;
  }

  std::vector< double > scalarDifferences(t);
  std::vector<double> covariance(t * t);
  double distSq = 0.0;
  if (this->m_ObservedScalarValues.size() == 0) {
    for (size_t i = 0; i < t; ++i) {
      scalarDifferences[i] = scalars[i];
      distSq += std::pow(scalarDifferences[i],2);
    }
  } else {
    for (size_t i = 0; i < t; ++i) {
      scalarDifferences[i] = scalars[i] - this->m_ObservedScalarValues[i];
      distSq += std::pow(scalarDifferences[i],2);
    }
  }

  std::vector< double > constantCovariance;
  if ( !this->GetConstantCovariance(constantCovariance) ) {
    std::cerr << "Error getting the constant covariance matrix from the model\n";
    return OTHER_ERROR;
  }

  if ((scalarCovariance.size() == 0) &&
      (constantCovariance.size() == 0)) {
    // Infinite precision makes no sense, so assume variance of 1.0
    // for each variable.
    logLikelihood = ((-0.5) * distSq) + logPriorLikelihood;
    return NO_ERROR;
  } else if (scalarCovariance.size() == 0) {
    assert(constantCovariance.size() == (t*t));
    covariance = constantCovariance;
  } else if (constantCovariance.size() == 0) {
    assert(scalarCovariance.size() == (t*t));
    covariance = scalarCovariance;
  } else {
    for (size_t i = 0; i < (t*t); ++i)
      covariance[i]
        = scalarCovariance[i] + constantCovariance[i];
  }

  Eigen::Map< Eigen::VectorXd > diff(&(scalarDifferences[0]),t);
  Eigen::Map< Eigen::MatrixXd > cov(&(covariance[0]),t,t);

  // FIXME check for singular matrix -> return negative infinity!
  //assert( cov.determinant() >= 0.0 ); // is there a better way?

  double innerProduct = cov.colPivHouseholderQr().solve(diff).dot(diff);
  logLikelihood = ((-0.5) * innerProduct) + logPriorLikelihood;
  return NO_ERROR;
}

  // FIXME remove code overlap with GetScalarOutputsAndLogLikelihood
  // some major refactoring is required to make these less cumbersome
Model::ErrorType
Model
::GetScalarOutputsAndLogLikelihoodAndLikelihoodErrorGradient(
    const std::vector< double > & parameters,
    std::vector< double > & scalars,
    double & logLikelihood,
    std::vector< double > & value_gradient,
    std::vector< double > & error_gradient) const
{

  logLikelihood = std::numeric_limits< double >::signaling_NaN();
  double logPriorLikelihood
    = this->GetLogPriorLikelihood(parameters);

  size_t t = this->GetNumberOfScalarOutputs();
  assert(t > 0);
  std::vector< double > scalarCovariance;
  // initially scalarCovariance is a empty vector
  Model::ErrorType result;
  if (m_UseModelCovarianceToCalulateLogLikelihood) {
    result = this->GetScalarOutputsAndCovariance(
        parameters, scalars, scalarCovariance);
  } else {
    // (! m_UseModelCovarianceToCalulateLogLikelihood)
    result = this->GetScalarOutputs(parameters, scalars);
  }
  if (result != NO_ERROR)
    return result;
  if (scalars.size() != t)
    return OTHER_ERROR;

  std::vector< double > scalarDifferences(t);
  std::vector<double> covariance(t * t);
  double distSq = 0.0;
  if (this->m_ObservedScalarValues.size() == 0) {
    for (size_t i = 0; i < t; ++i) {
      scalarDifferences[i] = scalars[i];
      distSq += std::pow(scalarDifferences[i],2);
    }
  } else {
    for (size_t i = 0; i < t; ++i) {
      scalarDifferences[i] = scalars[i] - this->m_ObservedScalarValues[i];
      distSq += std::pow(scalarDifferences[i],2);
    }
  }

  std::vector< double > constantCovariance;
  if ( !this->GetConstantCovariance(constantCovariance) ) {
    std::cerr << "Error getting the constant covariance matrix from the model\n";
    return OTHER_ERROR;
  }

  if ((scalarCovariance.size() == 0) &&
      (constantCovariance.size() == 0)) {
    // Infinite precision makes no sense, so assume variance of 1.0
    // for each variable.
    logLikelihood = ((-0.5) * distSq) + logPriorLikelihood;
    return NO_ERROR;
  } else if (scalarCovariance.size() == 0) {
    assert(constantCovariance.size() == (t*t));
    covariance = constantCovariance;
  } else if (constantCovariance.size() == 0) {
    assert(scalarCovariance.size() == (t*t));
    covariance = scalarCovariance;
  } else {
    for (size_t i = 0; i < (t*t); ++i)
      covariance[i]
        = scalarCovariance[i] + constantCovariance[i];
  }

  Eigen::Map< Eigen::VectorXd > diff(&(scalarDifferences[0]),t);
  Eigen::Map< Eigen::MatrixXd > cov(&(covariance[0]),t,t);
  Eigen::ColPivHouseholderQR< Eigen::MatrixXd > qrDecomposition = cov.colPivHouseholderQr();

  // FIXME check for singular matrix -> return negative infinity!
  //assert( cov.determinant() >= 0.0 ); // is there a better way?

  double innerProduct = qrDecomposition.solve(diff).dot(diff);
  logLikelihood = ((-0.5) * innerProduct) + logPriorLikelihood;

  value_gradient.clear();
  error_gradient.clear();
  if (this->m_ObservedScalarValues.size() > 0) {
    Eigen::MatrixXd inverse  = qrDecomposition.inverse();
    //this is positive because diff = scalars - observedScalars
    Eigen::VectorXd gradient = inverse*diff;
    value_gradient.assign(gradient.data(), gradient.data()+t);
    Eigen::MatrixXd covDelta(t,t);
    for (size_t i = 0; i < t; ++i) {
      covDelta.setZero();
      for (size_t k = 0; k < t; k++) {
        if(k == i) {
          covDelta(i, k) = 2.0*sqrt(cov(i, k));
        }
        else {
          covDelta(i, k) = cov(i, k)/sqrt(cov(i, i));
          covDelta(k, i) = covDelta(i, k);
        }
      }
      error_gradient.push_back(0.5*((inverse*covDelta*inverse)*diff).dot(diff)*sqrt(cov(i,i)));
    }
  }

  return NO_ERROR;
}

/** return the sum of the LogPriorLikelihood for each x[i] */
double
Model
::GetLogPriorLikelihood(const std::vector< double > & x) const
{
  const std::vector< Parameter > & params = this->GetParameters();
  assert(x.size() == params.size());
  double logPriorLikelihood = 0.0;
  for ( size_t i = 0; i < params.size(); ++i ) {
    logPriorLikelihood +=
      params[i].GetPriorDistribution()->GetLogProbabilityDensity(x[i]);
  }
  return logPriorLikelihood;
}

/** return the gradient of the LogPriorLikelihood at x */
std::vector< double >
Model
::GetGradientOfLogPriorLikelihood(
  const std::vector< double > & x) const
{
  const std::vector< Parameter > & params = this->GetParameters();
  assert(x.size() == params.size());
  std::vector< double > gradient;
  for ( size_t i = 0; i < params.size(); i++ ) {
    gradient.push_back(
      params[i].GetPriorDistribution()->GetGradientLogProbabilityDensity( x[i]) );
  }
  return gradient;
}

bool
Model
::GetConstantCovariance(std::vector< double > & x) const
{
  x.clear();
  unsigned int t = static_cast< unsigned int >( m_ScalarOutputNames.size() );
  if ( m_ObservedScalarCovariance.size() != (t*t) ) {
    std::cerr << "Observed scalar covariance is of invalid size "
              << m_ObservedScalarCovariance.size() << "\n";
    return false;
  }

  x.resize(t*t);
  x = m_ObservedScalarCovariance;
  return true;
}

} // end namespace madai


/*
 * Copyright 2019 GridGain Systems, Inc. and Contributors.
 *
 * Licensed under the GridGain Community Edition License (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <boost/test/unit_test.hpp>

#include "sql_test_suite_fixture.h"

using namespace ignite;

using namespace boost::unit_test;

BOOST_FIXTURE_TEST_SUITE(SqlValueExpressionTestSuite, ignite::SqlTestSuiteFixture)

BOOST_AUTO_TEST_CASE(TestCase)
{
    TestType in;

    in.i32Field = 82;

    testCache.Put(1, in);

    SqlTestSuiteFixture::CheckSingleResult<SQLINTEGER>(
        "SELECT "
            "CASE i32Field WHEN 82 "
                "THEN (i32Field / 2) "
                "ELSE (i32Field / 3) "
            "END "
        "FROM TestType", in.i32Field / 2);

    SqlTestSuiteFixture::CheckSingleResult<SQLINTEGER>(
        "SELECT "
            "CASE i32Field WHEN 22 "
                "THEN (i32Field / 2) "
                "ELSE (i32Field / 3) "
            "END "
        "FROM TestType", in.i32Field / 3);
}

BOOST_AUTO_TEST_CASE(TestCast)
{
    TestType in;

    in.i32Field = 12345;
    in.strField = "54321";

    testCache.Put(1, in);

    CheckSingleResult<SQLINTEGER>("SELECT CAST(strField AS INT) + i32Field FROM TestType",
        common::LexicalCast<int32_t>(in.strField) + in.i32Field);

    CheckSingleResult<std::string>("SELECT CAST(i32Field AS VARCHAR) || strField FROM TestType",
        common::LexicalCast<std::string>(in.i32Field) + in.strField);
}

BOOST_AUTO_TEST_CASE(TestCoalesce)
{
    CheckSingleResult<std::string>("SELECT COALESCE('One', 'Two', 'Three')", "One");
    CheckSingleResult<std::string>("SELECT COALESCE(NULL, 'Two', 'Three')", "Two");
    CheckSingleResult<std::string>("SELECT COALESCE(NULL, 'Two', NULL)", "Two");
    CheckSingleResult<std::string>("SELECT COALESCE(NULL, NULL, 'Three')", "Three");
}

BOOST_AUTO_TEST_CASE(TestNullif)
{
    TestType in;

    in.strField = "SomeValue";

    testCache.Put(1, in);

    CheckSingleResult<std::string>("SELECT NULLIF(strField, 'blablabla') FROM TestType", in.strField);
    CheckSingleResult<std::string>("SELECT NULLIF(strField, 'SomeValue') FROM TestType", "");
}

BOOST_AUTO_TEST_SUITE_END()


/**
 * @file tests/main_tests/lmnn_test.cpp
 * @author Manish Kumar
 *
 * Test mlpackMain() of lmnn_main.cpp.
 *
 * 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 <string>

#define BINDING_TYPE BINDING_TYPE_TEST
static const std::string testName = "LMNN";

#include <mlpack/core.hpp>
#include <mlpack/core/util/mlpack_main.hpp>
#include <mlpack/core/metrics/lmetric.hpp>

#include "test_helper.hpp"
#include <mlpack/methods/lmnn/lmnn_main.cpp>

#include <boost/test/unit_test.hpp>
#include "../test_tools.hpp"

using namespace mlpack;

struct LMNNTestFixture
{
 public:
  LMNNTestFixture()
  {
    // Cache in the options for this program.
    IO::RestoreSettings(testName);
  }

  ~LMNNTestFixture()
  {
    // Clear the settings.
    bindings::tests::CleanMemory();
    IO::ClearSettings();
  }
};

BOOST_FIXTURE_TEST_SUITE(LMNNMainTest, LMNNTestFixture);

/**
 * Ensure that, when labels are implicitily given with input,
 * the last column is treated as labels and that we get the
 * desired shape of output.
 */
BOOST_AUTO_TEST_CASE(LMNNExplicitImplicitLabelsTest)
{
  // Dataset containing labels as last column.
  arma::mat inputData;
  if (!data::Load("iris_train.csv", inputData))
    BOOST_FAIL("Cannot load iris.csv!");

  SetInputParam("input", inputData);

  mlpackMain();

  // Check that final output has expected number of rows and colums.
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("output").n_rows,
      inputData.n_rows - 1);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("output").n_cols,
      inputData.n_rows - 1);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("transformed_data").n_rows,
      inputData.n_rows - 1);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("transformed_data").n_cols,
      inputData.n_cols);

  // Reset Settings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Now check that when labels are explicitely given, the last column
  // of input is not treated as labels.
  if (!data::Load("iris.csv", inputData))
    BOOST_FAIL("Cannot load iris.csv!");

  arma::Row<size_t> labels;
  if (!data::Load("iris_labels.txt", labels))
    BOOST_FAIL("Cannot load iris_labels.txt!");

  SetInputParam("input", inputData);
  SetInputParam("labels", std::move(labels));

  mlpackMain();

  // Check that final output has expected number of rows and colums.
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("output").n_rows,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("output").n_cols,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("transformed_data").n_rows,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("transformed_data").n_cols,
      inputData.n_cols);
}

/**
 * Ensure that when we pass optimizer of type lbfgs, we also get the desired
 * shape of output.
 */
BOOST_AUTO_TEST_CASE(LMNNOptimizerTest)
{
  arma::mat inputData;
  if (!data::Load("iris.csv", inputData))
    BOOST_FAIL("Cannot load iris.csv!");

  arma::Row<size_t> labels;
  if (!data::Load("iris_labels.txt", labels))
    BOOST_FAIL("Cannot load iris_labels.txt!");

  // Input random data points.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  // TODO: set back to bbsgd---this was done for #1490 and should be reverted
  // when that is fixed.
  SetInputParam("optimizer",  std::string("amsgrad"));

  mlpackMain();

  // Check that final output has expected number of rows and colums.
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("output").n_rows,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("output").n_cols,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("transformed_data").n_rows,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("transformed_data").n_cols,
      inputData.n_cols);

  // Reset rettings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Input random data points.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  SetInputParam("optimizer",  std::string("sgd"));

  mlpackMain();

  // Check that final output has expected number of rows and colums.
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("output").n_rows,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("output").n_cols,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("transformed_data").n_rows,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("transformed_data").n_cols,
      inputData.n_cols);

  // Reset rettings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Input random data points.
  SetInputParam("input", inputData);
  SetInputParam("labels", std::move(labels));
  SetInputParam("optimizer",  std::string("lbfgs"));

  mlpackMain();

  // Check that final output has expected number of rows and colums.
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("output").n_rows,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("output").n_cols,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("transformed_data").n_rows,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("transformed_data").n_cols,
      inputData.n_cols);
}

/**
 * Ensure that when we pass a valid initial learning point, we get
 * output of the same dimensions.
 */
BOOST_AUTO_TEST_CASE(LMNNValidDistanceTest)
{
  arma::mat inputData;
  if (!data::Load("iris.csv", inputData))
    BOOST_FAIL("Cannot load iris.csv!");

  arma::Row<size_t> labels;
  if (!data::Load("iris_labels.txt", labels))
    BOOST_FAIL("Cannot load iris_labels.txt!");

  // Initial learning point.
  arma::mat distance;
  distance.randu(inputData.n_rows - 1, inputData.n_rows);

  // Input random data points.
  SetInputParam("input", inputData);
  SetInputParam("labels", std::move(labels));
  SetInputParam("distance", std::move(distance));

  mlpackMain();

  // Check that final output has expected number of rows and colums.
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("output").n_rows,
      inputData.n_rows - 1);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("output").n_cols,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("transformed_data").n_rows,
      inputData.n_rows - 1);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("transformed_data").n_cols,
      inputData.n_cols);
}

/**
 * Ensure that when we pass a valid initial square matrix as the learning
 * point, we get output of the same dimensions.
 */
BOOST_AUTO_TEST_CASE(LMNNValidDistanceTest2)
{
  arma::mat inputData;
  if (!data::Load("iris.csv", inputData))
    BOOST_FAIL("Cannot load iris.csv!");

  arma::Row<size_t> labels;
  if (!data::Load("iris_labels.txt", labels))
    BOOST_FAIL("Cannot load iris_labels.txt!");

  // Initial learning point (square matrix).
  arma::mat distance;
  distance.randu(inputData.n_rows, inputData.n_rows);

  // Input random data points.
  SetInputParam("input", inputData);
  SetInputParam("labels", std::move(labels));
  SetInputParam("distance", std::move(distance));

  mlpackMain();

  // Check that final output has expected number of rows and colums.
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("output").n_rows,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("output").n_cols,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("transformed_data").n_rows,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("transformed_data").n_cols,
      inputData.n_cols);
}

/**
 * Ensure that when we pass an invalid initial learning point, we get
 * output as the square matrix.
 */
BOOST_AUTO_TEST_CASE(LMNNInvalidDistanceTest)
{
  arma::mat inputData;
  if (!data::Load("iris.csv", inputData))
    BOOST_FAIL("Cannot load iris.csv!");

  arma::Row<size_t> labels;
  if (!data::Load("iris_labels.txt", labels))
    BOOST_FAIL("Cannot load iris_labels.txt!");

  // Initial learning point.
  arma::mat distance;
  distance.randu(inputData.n_rows + 1, inputData.n_rows);

  // Input random data points.
  SetInputParam("input", inputData);
  SetInputParam("labels", std::move(labels));
  SetInputParam("distance", std::move(distance));

  mlpackMain();

  // Check that final output has expected number of rows and colums.
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("output").n_rows,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("output").n_cols,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("transformed_data").n_rows,
      inputData.n_rows);
  BOOST_REQUIRE_EQUAL(IO::GetParam<arma::mat>("transformed_data").n_cols,
      inputData.n_cols);
}

/**
 * Ensure that if number of available labels in a class is less than
 * the number of targets, an error occurs.
 */
BOOST_AUTO_TEST_CASE(LMNNNumTargetsTest)
{
  // Input Dataset
  arma::mat inputData      = "-0.1 -0.1 -0.1  0.1  0.1  0.1;"
                             " 1.0  0.0 -1.0  1.0  0.0 -1.0 ";
  arma::Row<size_t> labels = " 0    0    0    1    1    1";

  SetInputParam("input", std::move(inputData));
  SetInputParam("labels", std::move(labels));
  SetInputParam("k", (int) 5);

  // Check that an error is thrown.
  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

/**
 * Ensure that setting normalize as true results in a
 * different output matrix then when set to false.
 */
BOOST_AUTO_TEST_CASE(LMNNDiffNormalizationTest)
{
  arma::mat inputData;
  if (!data::Load("iris.csv", inputData))
    BOOST_FAIL("Cannot load iris.csv!");

  arma::Row<size_t> labels;
  if (!data::Load("iris_labels.txt", labels))
    BOOST_FAIL("Cannot load iris_labels.txt!");

  // Set parameters and set normalize to true.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  SetInputParam("linear_scan", true);
  SetInputParam("tolerance", 0.01);

  mlpackMain();

  arma::mat output = IO::GetParam<arma::mat>("output");
  arma::mat transformedData = IO::GetParam<arma::mat>("transformed_data");

  // Reset rettings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Use the same input but set normalize to false.
  SetInputParam("input", std::move(inputData));
  SetInputParam("labels", std::move(labels));
  SetInputParam("normalize", true);
  SetInputParam("linear_scan", true);
  SetInputParam("tolerance", 0.01);

  mlpackMain();

  // Check that the output matrices are different.
  BOOST_REQUIRE_GT(
      arma::accu(IO::GetParam<arma::mat>("output") != output), 0);
  BOOST_REQUIRE_GT(arma::accu(IO::GetParam<arma::mat>("transformed_data") !=
      transformedData), 0);
}

/**
 * Ensure that output is different when step_size is different.
 */
BOOST_AUTO_TEST_CASE(LMNNDiffStepSizeTest)
{
  arma::mat inputData;
  if (!data::Load("iris.csv", inputData))
    BOOST_FAIL("Cannot load iris.csv!");

  arma::Row<size_t> labels;
  if (!data::Load("iris_labels.txt", labels))
    BOOST_FAIL("Cannot load iris_labels.txt!");

  // Set parameters with a small step_size.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  SetInputParam("step_size", (double) 0.01);
  SetInputParam("linear_scan",  (bool) true);

  mlpackMain();

  arma::mat output = IO::GetParam<arma::mat>("output");
  arma::mat transformedData = IO::GetParam<arma::mat>("transformed_data");

  // Reset settings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Set parameters using the same input but with a larger step_size.
  SetInputParam("input", std::move(inputData));
  SetInputParam("labels", std::move(labels));
  SetInputParam("step_size", (double) 20.5);
  SetInputParam("linear_scan",  (bool) true);

  mlpackMain();
BOOST_REQUIRE_GT(arma::accu(IO::GetParam<arma::mat>("transformed_data") !=
      transformedData), 0);
  // Check that the output matrices are different.
  BOOST_REQUIRE_GT(
      arma::accu(IO::GetParam<arma::mat>("output") != output), 0);
  BOOST_REQUIRE_GT(arma::accu(IO::GetParam<arma::mat>("transformed_data") !=
      transformedData), 0);
}

/**
 * Ensure that output is different when the tolerance is different.
 */
BOOST_AUTO_TEST_CASE(LMNNDiffToleranceTest)
{
  arma::mat inputData;
  if (!data::Load("iris.csv", inputData))
    BOOST_FAIL("Cannot load iris.csv!");

  arma::Row<size_t> labels;
  if (!data::Load("iris_labels.txt", labels))
    BOOST_FAIL("Cannot load iris_labels.txt!");

  // Set parameters with a small tolerance.
  SetInputParam("input", inputData);
  SetInputParam("tolerance", (double) 1e-6);
  SetInputParam("linear_scan",  (bool) true);

  mlpackMain();

  arma::mat output = IO::GetParam<arma::mat>("output");
  arma::mat transformedData = IO::GetParam<arma::mat>("transformed_data");

  // Reset settings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Set parameters using the same input but with a larger tolerance.
  SetInputParam("input", std::move(inputData));
  SetInputParam("tolerance", (double) 0.3);
  SetInputParam("linear_scan",  (bool) true);

  mlpackMain();

  // Check that the output matrices are different.
  BOOST_REQUIRE_GT(
      arma::accu(IO::GetParam<arma::mat>("output") != output), 0);
  BOOST_REQUIRE_GT(arma::accu(IO::GetParam<arma::mat>("transformed_data") !=
      transformedData), 0);
}

/**
 * Ensure that output is different when batch_size is different.
 */
BOOST_AUTO_TEST_CASE(LMNNDiffBatchSizeTest)
{
  arma::mat inputData;
  if (!data::Load("iris.csv", inputData))
    BOOST_FAIL("Cannot load iris.csv!");

  arma::Row<size_t> labels;
  if (!data::Load("iris_labels.txt", labels))
    BOOST_FAIL("Cannot load iris_labels.txt!");

  // Set parameters with a small batch_size.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  SetInputParam("batch_size", (int) 20);
  SetInputParam("linear_scan",  (bool) true);

  mlpackMain();

  arma::mat output = IO::GetParam<arma::mat>("output");
  arma::mat transformedData = IO::GetParam<arma::mat>("transformed_data");

  // Reset settings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Set parameters using the same input but with a larger batch_size.
  SetInputParam("input", std::move(inputData));
  SetInputParam("labels", std::move(labels));
  SetInputParam("batch_size", (int) 30);
  SetInputParam("linear_scan",  (bool) true);

  mlpackMain();

  // Check that the output matrices are different.
  BOOST_REQUIRE_GT(
      arma::accu(IO::GetParam<arma::mat>("output") != output), 0);
  BOOST_REQUIRE_GT(arma::accu(IO::GetParam<arma::mat>("transformed_data") !=
      transformedData), 0);
}

/**
 * Ensure that different value of number of targets results in a
 * different output matrix.
 */
BOOST_AUTO_TEST_CASE(LMNNDiffNumTargetsTest)
{
  arma::mat inputData;
  if (!data::Load("iris.csv", inputData))
    BOOST_FAIL("Cannot load iris.csv!");

  arma::Row<size_t> labels;
  if (!data::Load("iris_labels.txt", labels))
    BOOST_FAIL("Cannot load iris_labels.txt!");

  // Set parameters.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  SetInputParam("k", 1);
  SetInputParam("linear_scan",  (bool) true);

  mlpackMain();

  arma::mat output = IO::GetParam<arma::mat>("output");
  arma::mat transformedData = IO::GetParam<arma::mat>("transformed_data");

  // Reset settings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Set different parameters.
  SetInputParam("input", std::move(inputData));
  SetInputParam("labels", std::move(labels));
  SetInputParam("k", 5);
  SetInputParam("linear_scan",  (bool) true);

  mlpackMain();

  // Check that the output matrices are different.
  BOOST_REQUIRE_GT(
      arma::accu(IO::GetParam<arma::mat>("output") != output), 0);
  BOOST_REQUIRE_GT(arma::accu(IO::GetParam<arma::mat>("transformed_data") !=
      transformedData), 0);
}

/**
 * Ensure that different value of regularization results in a
 * different output matrix.
 */
BOOST_AUTO_TEST_CASE(LMNNDiffRegularizationTest)
{
  arma::mat inputData;
  if (!data::Load("iris.csv", inputData))
    BOOST_FAIL("Cannot load iris.csv!");

  arma::Row<size_t> labels;
  if (!data::Load("iris_labels.txt", labels))
    BOOST_FAIL("Cannot load iris_labels.txt!");

  // Set parameters.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  SetInputParam("linear_scan",  (bool) true);
  SetInputParam("regularization", 1.0);

  mlpackMain();

  arma::mat output = IO::GetParam<arma::mat>("output");
  arma::mat transformedData = IO::GetParam<arma::mat>("transformed_data");

  // Reset settings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Set different parameters.
  SetInputParam("input", std::move(inputData));
  SetInputParam("labels", std::move(labels));
  SetInputParam("linear_scan",  (bool) true);
  SetInputParam("regularization", 0.1);

  mlpackMain();

  // Check that the output matrices are different.
  BOOST_REQUIRE_GT(
      arma::accu(IO::GetParam<arma::mat>("output") != output), 0);
  BOOST_REQUIRE_GT(arma::accu(IO::GetParam<arma::mat>("transformed_data") !=
      transformedData), 0);
}

/**
 * Ensure that different value of range results in a
 * different output matrix.
 */
BOOST_AUTO_TEST_CASE(LMNNDiffRangeTest)
{
  arma::mat inputData;
  if (!data::Load("iris.csv", inputData))
    BOOST_FAIL("Cannot load iris.csv!");

  arma::Row<size_t> labels;
  if (!data::Load("iris_labels.txt", labels))
    BOOST_FAIL("Cannot load iris_labels.txt!");

  // Set parameters.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  SetInputParam("linear_scan",  (bool) true);

  mlpackMain();

  arma::mat output = IO::GetParam<arma::mat>("output");
  arma::mat transformedData = IO::GetParam<arma::mat>("transformed_data");

  // Reset settings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Set different parameters.
  SetInputParam("input", std::move(inputData));
  SetInputParam("labels", std::move(labels));
  SetInputParam("linear_scan",  (bool) true);
  SetInputParam("range", 100);

  mlpackMain();

  // Check that the output matrices are different.
  BOOST_REQUIRE_GT(
      arma::accu(IO::GetParam<arma::mat>("output") != output), 0);
  BOOST_REQUIRE_GT(arma::accu(IO::GetParam<arma::mat>("transformed_data") !=
      transformedData), 0);
}

/**
 * Ensure that using a different value of max_iteration
 * results in a different output matrix.
 */
BOOST_AUTO_TEST_CASE(LMNNDiffMaxIterationTest)
{
  arma::mat inputData;
  if (!data::Load("iris.csv", inputData))
    BOOST_FAIL("Cannot load iris.csv!");

  arma::Row<size_t> labels;
  if (!data::Load("iris_labels.txt", labels))
    BOOST_FAIL("Cannot load iris_labels.txt!");

  // Set parameters with a small max_iterations.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  SetInputParam("linear_scan",  (bool) true);
  SetInputParam("optimizer",  std::string("lbfgs"));
  SetInputParam("k", 5);
  SetInputParam("max_iterations", (int) 2);

  mlpackMain();

  arma::mat output = IO::GetParam<arma::mat>("output");
  arma::mat transformedData = IO::GetParam<arma::mat>("transformed_data");

  // Reset settings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Set parameters using the same input but with a larger max_iterations.
  SetInputParam("input", std::move(inputData));
  SetInputParam("labels", labels);
  SetInputParam("linear_scan",  (bool) true);
  SetInputParam("optimizer",  std::string("lbfgs"));
  SetInputParam("k", 5);
  SetInputParam("max_iterations", (int) 500);

  mlpackMain();

  // Check that the output matrices are different.
  BOOST_REQUIRE_GT(
      arma::accu(IO::GetParam<arma::mat>("output") != output), 0);
  BOOST_REQUIRE_GT(arma::accu(IO::GetParam<arma::mat>("transformed_data") !=
      transformedData), 0);
}

/**
 * Ensure that using a different value of passes
 * results in a different output matrix.
 */
BOOST_AUTO_TEST_CASE(LMNNDiffPassesTest)
{
  arma::mat inputData;
  if (!data::Load("iris.csv", inputData))
    BOOST_FAIL("Cannot load iris.csv!");

  arma::Row<size_t> labels;
  if (!data::Load("iris_labels.txt", labels))
    BOOST_FAIL("Cannot load iris_labels.txt!");

  // Set parameters with a small passes.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  SetInputParam("linear_scan",  (bool) true);
  SetInputParam("passes", (int) 2);

  mlpackMain();

  arma::mat output = IO::GetParam<arma::mat>("output");
  arma::mat transformedData = IO::GetParam<arma::mat>("transformed_data");

  // Reset settings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Set parameters using the same input but with a larger passes.
  SetInputParam("input", std::move(inputData));
  SetInputParam("labels", labels);
  SetInputParam("linear_scan",  (bool) true);
  SetInputParam("passes", (int) 6);

  mlpackMain();

  // Check that the output matrices are different.
  BOOST_REQUIRE_GT(
      arma::accu(IO::GetParam<arma::mat>("output") != output), 0);
  BOOST_REQUIRE_GT(arma::accu(IO::GetParam<arma::mat>("transformed_data") !=
      transformedData), 0);
}

/**
 * Ensure that number of targets, range, batch size must be always positive
 * and regularization, step size, max iterations, rank, passes & tolerance are
 * always non-negative
 */
BOOST_AUTO_TEST_CASE(LMNNBoundsTest)
{
  arma::mat inputData;
  if (!data::Load("iris.csv", inputData))
    BOOST_FAIL("Cannot load iris.csv!");

  arma::Row<size_t> labels;
  if (!data::Load("iris_labels.txt", labels))
    BOOST_FAIL("Cannot load iris_labels.txt!");

  // Test for number of targets value.

  // Input training data.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  SetInputParam("k", (int) 0);

  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;

  // Reset settings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Test for range value.

  // Input training data.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  SetInputParam("range", (int) 0);

  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;

  // Reset settings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Test for batch size value.

  // Input training data.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  SetInputParam("batch_size", (int) 0);

  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;

  // Reset settings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Test for regularization value.

  // Input training data.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  SetInputParam("regularization", (double) -1.0);

  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;

  // Reset settings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Test for step size value.

  // Input training data.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  SetInputParam("step_size", (double) -1.0);

  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;

  // Reset settings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Test for max iterations value.

  // Input training data.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  SetInputParam("max_iterations", (int) -1.0);

  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;

  // Reset settings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Test for passes value.

  // Input training data.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  SetInputParam("passes", (int) -1.0);

  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;

  // Reset settings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Test for max iterations value.

  // Input training data.
  SetInputParam("input", inputData);
  SetInputParam("labels", labels);
  SetInputParam("rank", (int) -1.0);

  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;

  // Reset settings.
  IO::ClearSettings();
  IO::RestoreSettings(testName);

  // Test for tolerance value.

  // Input training data.
  SetInputParam("input", std::move(inputData));
  SetInputParam("labels", std::move(labels));
  SetInputParam("tolerance", (double) -1.0);

  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

BOOST_AUTO_TEST_SUITE_END();


// Copyright (C) 2009  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.

#include "../tester.h"
#include <dlib/matrix.h>

#ifndef DLIB_USE_BLAS
#error "BLAS bindings must be used for this test to make any sense"
#endif

namespace dlib
{
    namespace blas_bindings
    {
        // This is a little screwy.  This function is used inside the BLAS
        // bindings to count how many times each of the BLAS functions get called.
#ifdef DLIB_TEST_BLAS_BINDINGS
        int& counter_ger() { static int counter = 0; return counter; }
#endif

    }
}

namespace
{
    using namespace test;
    using namespace std;
    // Declare the logger we will use in this test.  The name of the logger
    // should start with "test."
    dlib::logger dlog("test.ger");

    class blas_bindings_ger_tester : public tester
    {
    public:
        blas_bindings_ger_tester (
        ) :
            tester (
                "test_ger", // the command line argument name for this test
                "Run tests for GER routines.", // the command line argument description
                0                     // the number of command line arguments for this test
            )
        {}

        template <typename matrix_type, typename cv_type, typename rv_type>
        void test_ger_stuff(
            matrix_type& m,
            rv_type& rv,
            cv_type& cv
        ) const
        {
            using namespace dlib;
            using namespace dlib::blas_bindings;

            rv_type rv2;
            cv_type cv2;
            matrix_type m2;

            counter_ger() = 0;
            m2 = m + cv*rv;
            DLIB_TEST_MSG(counter_ger() == 1, counter_ger());

            counter_ger() = 0;
            m += trans(rv)*rv;
            DLIB_TEST(counter_ger() == 1);

            counter_ger() = 0;
            m += trans(rv)*trans(cv);
            DLIB_TEST(counter_ger() == 1);

            counter_ger() = 0;
            m += cv*trans(cv);
            DLIB_TEST(counter_ger() == 1);

            counter_ger() = 0;
            m += trans(rv)*rv + trans(cv*3*rv);
            DLIB_TEST(counter_ger() == 2);
        }

        template <typename matrix_type, typename cv_type, typename rv_type>
        void test_ger_stuff_conj(
            matrix_type& m,
            rv_type& rv,
            cv_type& cv
        ) const
        {
            using namespace dlib;
            using namespace dlib::blas_bindings;

            rv_type rv2;
            cv_type cv2;
            matrix_type m2;

            counter_ger() = 0;
            m += cv*conj(rv);
            DLIB_TEST_MSG(counter_ger() == 1, counter_ger());

            counter_ger() = 0;
            m += trans(rv)*conj(rv);
            DLIB_TEST(counter_ger() == 1);

            counter_ger() = 0;
            m += trans(rv)*conj(trans(cv));
            DLIB_TEST(counter_ger() == 1);

            counter_ger() = 0;
            m += cv*trans(conj(cv));
            DLIB_TEST(counter_ger() == 1);

            counter_ger() = 0;
            m += trans(rv)*rv + trans(cv*3*conj(rv));
            DLIB_TEST(counter_ger() == 2);
        }

        void perform_test (
        )
        {
            using namespace dlib;
            typedef dlib::memory_manager<char>::kernel_1a mm;

            dlog << dlib::LINFO << "test double";
            {
                matrix<double> m = randm(4,4);
                matrix<double,1,0> rv = randm(1,4);
                matrix<double,0,1> cv = randm(4,1);
                test_ger_stuff(m,rv,cv);
            }

            dlog << dlib::LINFO << "test float";
            {
                matrix<float> m = matrix_cast<float>(randm(4,4));
                matrix<float,1,0> rv = matrix_cast<float>(randm(1,4));
                matrix<float,0,1> cv = matrix_cast<float>(randm(4,1));
                test_ger_stuff(m,rv,cv);
            }

            dlog << dlib::LINFO << "test complex<double>";
            {
                matrix<complex<double> > m = complex_matrix(randm(4,4), randm(4,4));
                matrix<complex<double>,1,0> rv = complex_matrix(randm(1,4), randm(1,4));
                matrix<complex<double>,0,1> cv = complex_matrix(randm(4,1), randm(4,1));
                test_ger_stuff(m,rv,cv);
                test_ger_stuff_conj(m,rv,cv);
            }

            dlog << dlib::LINFO << "test complex<float>";
            {
                matrix<complex<float> > m = matrix_cast<complex<float> >(complex_matrix(randm(4,4), randm(4,4)));
                matrix<complex<float>,1,0> rv = matrix_cast<complex<float> >(complex_matrix(randm(1,4), randm(1,4)));
                matrix<complex<float>,0,1> cv = matrix_cast<complex<float> >(complex_matrix(randm(4,1), randm(4,1)));
                test_ger_stuff(m,rv,cv);
                test_ger_stuff_conj(m,rv,cv);
            }

            dlog << dlib::LINFO << "test double";
            {
                matrix<double,0,0,mm,column_major_layout> m = randm(4,4);
                matrix<double,1,0,mm,column_major_layout> rv = randm(1,4);
                matrix<double,0,1,mm,column_major_layout> cv = randm(4,1);
                test_ger_stuff(m,rv,cv);
            }

            dlog << dlib::LINFO << "test float";
            {
                matrix<float,0,0,mm,column_major_layout> m = matrix_cast<float>(randm(4,4));
                matrix<float,1,0,mm,column_major_layout> rv = matrix_cast<float>(randm(1,4));
                matrix<float,0,1,mm,column_major_layout> cv = matrix_cast<float>(randm(4,1));
                test_ger_stuff(m,rv,cv);
            }

            dlog << dlib::LINFO << "test complex<double>";
            {
                matrix<complex<double>,0,0,mm,column_major_layout > m = complex_matrix(randm(4,4), randm(4,4));
                matrix<complex<double>,1,0,mm,column_major_layout> rv = complex_matrix(randm(1,4), randm(1,4));
                matrix<complex<double>,0,1,mm,column_major_layout> cv = complex_matrix(randm(4,1), randm(4,1));
                test_ger_stuff(m,rv,cv);
                test_ger_stuff_conj(m,rv,cv);
            }

            dlog << dlib::LINFO << "test complex<float>";
            {
                matrix<complex<float>,0,0,mm,column_major_layout > m = matrix_cast<complex<float> >(complex_matrix(randm(4,4), randm(4,4)));
                matrix<complex<float>,1,0,mm,column_major_layout> rv = matrix_cast<complex<float> >(complex_matrix(randm(1,4), randm(1,4)));
                matrix<complex<float>,0,1,mm,column_major_layout> cv = matrix_cast<complex<float> >(complex_matrix(randm(4,1), randm(4,1)));
                test_ger_stuff(m,rv,cv);
                test_ger_stuff_conj(m,rv,cv);
            }

            print_spinner();
        }
    };

    blas_bindings_ger_tester a;

}


//==================================================================================================
/*!

  Copyright 2015 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#include <boost/simd/constant/powlargelim.hpp>
#include <boost/simd/as.hpp>
#include <simd_test.hpp>

STF_CASE_TPL( "Check powlargelim behavior for integral types"
            , (std::uint32_t)(std::uint64_t)
              (std::int32_t)(std::int64_t)
            )
{
  using boost::simd::as;
  using boost::simd::detail::powlargelim;
  using boost::simd::Powlargelim;
  T ref = T(0);
  STF_TYPE_IS(decltype(Powlargelim<T>()), T);
  STF_EQUAL(Powlargelim<T>(), ref);
  STF_EQUAL(powlargelim( as(T{}) ),ref);
}

STF_CASE_TPL( "Check powlargelim behavior for double"
            , (double)
            )
{
  using boost::simd::as;
  using boost::simd::detail::powlargelim;
  using boost::simd::Powlargelim;
  T ref = T(16383);

  STF_TYPE_IS(decltype(Powlargelim<T>()), T);
  STF_IEEE_EQUAL(Powlargelim<T>(), ref);
  STF_IEEE_EQUAL(powlargelim( as(T{}) ), ref);
}

STF_CASE_TPL( "Check powlargelim behavior for float"
            , (float)
            )
{
  using boost::simd::as;
  using boost::simd::detail::powlargelim;
  using boost::simd::Powlargelim;
  T ref = T(2047);
  STF_TYPE_IS(decltype(Powlargelim<T>()), T);
  STF_IEEE_EQUAL(Powlargelim<T>(), ref);
  STF_IEEE_EQUAL(powlargelim( as(T{}) ), ref);
}