/*
	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;
}


/*=========================================================================
 *
 *  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


// g2o - General Graph Optimization
// Copyright (C) 2011 Kurt Konolige
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright notice,
//   this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
//   notice, this list of conditions and the following disclaimer in the
//   documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include <Eigen/StdVector>
#include <random>
#include <iostream>
#include <stdint.h>

#include "g2o/core/sparse_optimizer.h"
#include "g2o/core/block_solver.h"
#include "g2o/core/solver.h"
#include "g2o/core/optimization_algorithm_levenberg.h"
#include "g2o/solvers/csparse/linear_solver_csparse.h"
#include "g2o/types/icp/types_icp.h"

using namespace Eigen;
using namespace std;
using namespace g2o;

// sampling distributions
  class Sample
  {

    static default_random_engine gen_real;
    static default_random_engine gen_int;
  public:
    static int uniform(int from, int to);

    static double uniform();

    static double gaussian(double sigma);
  };

  default_random_engine Sample::gen_real;
  default_random_engine Sample::gen_int;

  int Sample::uniform(int from, int to)
  {
    uniform_int_distribution<int> unif(from, to);
    int sam = unif(gen_int);
    return  sam;
  }

  double Sample::uniform()
  {
    std::uniform_real_distribution<double> unif(0.0, 1.0);
    double sam = unif(gen_real);
    return  sam;
  }

  double Sample::gaussian(double sigma)
  {
    std::normal_distribution<double> gauss(0.0, sigma);
    double sam = gauss(gen_real);
    return  sam;
  }

//
// set up simulated system with noise, optimize it
//

int main(int argc, char **argv)
{
  int num_points = 0;

  // check for arg, # of points to use in projection SBA
  if (argc > 1)
    num_points = atoi(argv[1]);

  double euc_noise = 0.1;      // noise in position, m
  double pix_noise = 1.0;       // pixel noise
  //  double outlier_ratio = 0.1;

  SparseOptimizer optimizer;
  optimizer.setVerbose(false);

  // variable-size block solver
  g2o::OptimizationAlgorithmLevenberg* solver = new g2o::OptimizationAlgorithmLevenberg(
    g2o::make_unique<BlockSolverX>(g2o::make_unique<LinearSolverCSparse<g2o::BlockSolverX::PoseMatrixType>>()));

  optimizer.setAlgorithm(solver);

  vector<Vector3d> true_points;
  for (size_t i=0;i<1000; ++i)
  {
    true_points.push_back(Vector3d((Sample::uniform()-0.5)*3,
                                   Sample::uniform()-0.5,
                                   Sample::uniform()+10));
  }

  // set up camera params
  Vector2d focal_length(500,500); // pixels
  Vector2d principal_point(320,240); // 640x480 image
  double baseline = 0.075;      // 7.5 cm baseline

  // set up camera params and projection matrices on vertices
  g2o::VertexSCam::setKcam(focal_length[0],focal_length[1],
                           principal_point[0],principal_point[1],
                           baseline);

  // set up two poses
  int vertex_id = 0;
  for (size_t i=0; i<2; ++i)
  {
    // set up rotation and translation for this node
    Vector3d t(0,0,i);
    Quaterniond q;
    q.setIdentity();

    Eigen::Isometry3d cam;           // camera pose
    cam = q;
    cam.translation() = t;

    // set up node
    VertexSCam *vc = new VertexSCam();
    vc->setEstimate(cam);
    vc->setId(vertex_id);      // vertex id

    cerr << t.transpose() << " | " << q.coeffs().transpose() << endl;

    // set first cam pose fixed
    if (i==0)
      vc->setFixed(true);

    // make sure projection matrices are set
    vc->setAll();

    // add to optimizer
    optimizer.addVertex(vc);

    vertex_id++;
  }

  // set up point matches for GICP
  for (size_t i=0; i<true_points.size(); ++i)
  {
    // get two poses
    VertexSE3* vp0 =
      dynamic_cast<VertexSE3*>(optimizer.vertices().find(0)->second);
    VertexSE3* vp1 =
      dynamic_cast<VertexSE3*>(optimizer.vertices().find(1)->second);

    // calculate the relative 3D position of the point
    Vector3d pt0,pt1;
    pt0 = vp0->estimate().inverse() * true_points[i];
    pt1 = vp1->estimate().inverse() * true_points[i];

    // add in noise
    pt0 += Vector3d(Sample::gaussian(euc_noise ),
                    Sample::gaussian(euc_noise ),
                    Sample::gaussian(euc_noise ));

    pt1 += Vector3d(Sample::gaussian(euc_noise ),
                    Sample::gaussian(euc_noise ),
                    Sample::gaussian(euc_noise ));

    // form edge, with normals in varioius positions
    Vector3d nm0, nm1;
    nm0 << 0, i, 1;
    nm1 << 0, i, 1;
    nm0.normalize();
    nm1.normalize();

    Edge_V_V_GICP * e           // new edge with correct cohort for caching
        = new Edge_V_V_GICP();

    e->vertices()[0]            // first viewpoint
      = dynamic_cast<OptimizableGraph::Vertex*>(vp0);

    e->vertices()[1]            // second viewpoint
      = dynamic_cast<OptimizableGraph::Vertex*>(vp1);

    EdgeGICP meas;

    meas.pos0 = pt0;
    meas.pos1 = pt1;
    meas.normal0 = nm0;
    meas.normal1 = nm1;
    e->setMeasurement(meas);
    meas = e->measurement();
    //        e->inverseMeasurement().pos() = -kp;

    // use this for point-plane
    e->information() = meas.prec0(0.01);

    // use this for point-point
    //    e->information().setIdentity();

    //    e->setRobustKernel(true);
    //e->setHuberWidth(0.01);

    optimizer.addEdge(e);
  }

  // set up SBA projections with some number of points

  true_points.clear();
  for (int i=0;i<num_points; ++i)
  {
    true_points.push_back(Vector3d((Sample::uniform()-0.5)*3,
                                   Sample::uniform()-0.5,
                                   Sample::uniform()+10));
  }

  // add point projections to this vertex
  for (size_t i=0; i<true_points.size(); ++i)
  {
    g2o::VertexSBAPointXYZ * v_p
        = new g2o::VertexSBAPointXYZ();

    v_p->setId(vertex_id++);
    v_p->setMarginalized(true);
    v_p->setEstimate(true_points.at(i)
        + Vector3d(Sample::gaussian(1),
                   Sample::gaussian(1),
                   Sample::gaussian(1)));

    optimizer.addVertex(v_p);

    for (size_t j=0; j<2; ++j)
      {
        Vector3d z;
        dynamic_cast<g2o::VertexSCam*>
          (optimizer.vertices().find(j)->second)
          ->mapPoint(z,true_points.at(i));

        if (z[0]>=0 && z[1]>=0 && z[0]<640 && z[1]<480)
        {
          z += Vector3d(Sample::gaussian(pix_noise),
                        Sample::gaussian(pix_noise),
                        Sample::gaussian(pix_noise/16.0));

          g2o::Edge_XYZ_VSC * e
              = new g2o::Edge_XYZ_VSC();

          e->vertices()[0]
              = dynamic_cast<g2o::OptimizableGraph::Vertex*>(v_p);

          e->vertices()[1]
              = dynamic_cast<g2o::OptimizableGraph::Vertex*>
              (optimizer.vertices().find(j)->second);

          e->setMeasurement(z);
          //e->inverseMeasurement() = -z;
          e->information() = Matrix3d::Identity();

          //e->setRobustKernel(false);
          //e->setHuberWidth(1);

          optimizer.addEdge(e);
        }

      }
  } // done with adding projection points

  // move second cam off of its true position
  VertexSE3* vc =
    dynamic_cast<VertexSE3*>(optimizer.vertices().find(1)->second);
  Eigen::Isometry3d cam = vc->estimate();
  cam.translation() = Vector3d(-0.1,0.1,0.2);
  vc->setEstimate(cam);
  optimizer.initializeOptimization();
  optimizer.computeActiveErrors();
  cout << "Initial chi2 = " << FIXED(optimizer.chi2()) << endl;

  optimizer.setVerbose(true);

  optimizer.optimize(20);

  cout << endl << "Second vertex should be near 0,0,1" << endl;
  cout <<  dynamic_cast<VertexSE3*>(optimizer.vertices().find(0)->second)
    ->estimate().translation().transpose() << endl;
  cout <<  dynamic_cast<VertexSE3*>(optimizer.vertices().find(1)->second)
    ->estimate().translation().transpose() << endl;
}


/*
 This program is free software; you can redistribute it and/or modify it under
 the terms of the European Union Public Licence - EUPL v.1.1 as published by
 the European Commission.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE. See the European Union Public Licence - EUPL v.1.1
 for more details.

 You should have received a copy of the European Union Public Licence - EUPL v.1.1
 along with this program.

 Further information about the European Union Public Licence - EUPL v.1.1 can
 also be found on the world wide web at http://ec.europa.eu/idabc/eupl
 */

/*
 ------ Copyright (C) 2010 STA Steering Board (space.trajectory.analysis AT gmail.com) ----
 ------------------ Author: Ricardo Noriega  ----------------------------------------------
 ------------------ email: ricardonor@gmail.com  ------------------------------------------
Totally changed by Guillermo in May 2011 due to the numerioud bugs in the original code that could not calculate properly and crashed STA.
Commenting properly all methods to allow the poor new guy that will update the code

 */

#include <QDebug>

#include <math.h>
#include <Eigen/Core>
#include <Eigen/Geometry>

#include "commanalysis.h"
#include "Main/findDataFolder.h"
#include "Locations/environmentdialog.h"
#include "Payloads/receiverPayloadDialog.h"
#include <Astro-Core/surfaceVelocity.h>

#ifdef Q_WS_MAC
#include <CoreFoundation/CFBundle.h>
#endif

//Definition of constructors, the default one contains a warning.

using namespace Eigen;

bool tracking=true;//This allows to set a tracking system for the calculations, so the antenna of the groundstation follows the spacecraft!

CommAnalysis::CommAnalysis()
{
    qDebug()<< "Warning: CommAnalysis object created without any participant to make the calculations";
}

CommAnalysis::CommAnalysis(ScenarioTransmitterPayloadType *transmitter, ScenarioReceiverPayloadType *receiver, ScenarioGroundStationEnvironment* environment, PropagatedScenario* propagatedScenario, int indexSC, int indexGS, int indexMA, bool flagTX, bool flagRX):m_transmitter(transmitter), m_receiver(receiver), m_propagatedScenario(propagatedScenario), m_environment(environment), m_indexSC(indexSC), m_indexGS(indexGS), m_indexMA(indexMA), m_flagTX(flagTX), m_flagRX(flagRX)
{
    // Nothing to be done
}

//Default destructor

CommAnalysis::~CommAnalysis()
{
    // Noting to be done
}

double lightSpeed=SPEED_OF_LIGHT;
double Pi=mypi;

////////////////////////////////////////////// FUNCTIONS ////////////////////////////////////////////////////////

/** Calculates the Doppler shift due to motion between a receiver and a emmitter
  * The calculation is done for a given participant a a given mission arc of this participant
  * Accepts as parameters the lenght of the list that actually corresponds to each of the
  *
  * \return a QList of double numbers containing all doppler shifts in time
  *
  */
QList<double> CommAnalysis::getDopplerShiftList(int numberOfRows)
{

    QList<double> myDopplerShiftList;
    double dopplerShift;

    for(int i=0; i<numberOfRows; i++)
    {
        double frequency=m_transmitter->Budget()->FrequencyBand();
        double t=m_propagatedScenario->spaceObjects().at(m_indexSC)->mission().at(m_indexMA)->trajectorySampleTime(i);

        bool visibility;
        if(m_propagatedScenario->groundObjects().at(m_indexGS)->elevationAngle(m_propagatedScenario->spaceObjects().at(m_indexSC),t)>=5.0*DEG2RAD)
        {
            visibility=true;
            sta::StateVector samples;

            samples=m_propagatedScenario->spaceObjects().at(m_indexSC)->mission().at(m_indexMA)->trajectorySample(i);

            double latitudeGS=m_propagatedScenario->groundObjects().at(m_indexGS)->latitude;
            double longitudeGS=m_propagatedScenario->groundObjects().at(m_indexGS)->longitude;
            double altitudeGS=m_propagatedScenario->groundObjects().at(m_indexGS)->altitude;

            latitudeGS=DEG2RAD*(latitudeGS);
            longitudeGS=DEG2RAD*(longitudeGS);

            longitudeGS=longitudeGS+getGreenwichHourAngle(m_propagatedScenario->spaceObjects().at(m_indexSC)->mission().at(m_indexMA)->trajectorySampleTime(i)+2400000.5);

            Vector3d normPosition(cos(latitudeGS) * cos(longitudeGS),
                                  cos(latitudeGS) * sin(longitudeGS),
                                  sin(latitudeGS));

            Vector3d stationPos=normPosition.cwise() * STA_SOLAR_SYSTEM->lookup("Earth")->radii() + normPosition * altitudeGS;

            Vector3d toSpacecraft = (samples.position - stationPos).normalized();

            Vector3d velSat=samples.velocity;
            surfaceVelocity earthVel;
            double speedGS=earthVel.earthStationLinearVelocity(latitudeGS);
            Vector3d velGS(speedGS*sin(longitudeGS),speedGS*cos(longitudeGS),0);
            toSpacecraft.normalize();

            double radialVelToGS = ((velGS-velSat).dot(toSpacecraft)) * 1000; //The result is in Km/sec so I convert it to m/s

            dopplerShift=radialVelToGS*frequency/lightSpeed;

            myDopplerShiftList.append(dopplerShift);

        }
        else
        {
            visibility=false;
            myDopplerShiftList.append(0.0);
        }
    }

    return myDopplerShiftList;
} ///////////////////////////////////////// end of the method //////////////////////////////////////////

/** Calculates the free space loss due to motion between a receiver and a emmitter
  * The calculation is done for a given participant a a given mission arc of this participant
  * Accepts as parameters the lenght of the list that actually corresponds to each of the
  *
  * \return a QList of double numbers containing all free space losses in time
  *
  */
QList<double> CommAnalysis::getFreeSpaceLossList(int numberOfRows)
{
    QList<double> myFreeSpaceLossList;
    double eachFreeSpaceLoss, eachFreeSpaceLossIndBs;

    //The inputs for this function will be the frequency and the range between both participants
    double frequency = m_transmitter->Budget()->FrequencyBand();
    double range, t;

    for(int i=0; i<numberOfRows; i++)
    {
        t = m_propagatedScenario->spaceObjects().at(m_indexSC)->mission().at(m_indexMA)->trajectorySampleTime(i);
        range = m_propagatedScenario->groundObjects().at(m_indexGS)->getRange(m_propagatedScenario->spaceObjects().at(m_indexSC), t) * 1000;  // In meters

        bool visibility;
        // The following condition shall be upgraded to make it gereric (not only 5.0*DEG2GRAD times)
        if (m_propagatedScenario->groundObjects().at(m_indexGS)->elevationAngle(m_propagatedScenario->spaceObjects().at(m_indexSC),t) >= 5.0*DEG2RAD)
        {
            visibility=true;
            //mjdList.append(t);
            //range=range*1000;
            //rangeList.append(range);//In metres
            eachFreeSpaceLoss = pow((4 * Pi * range * frequency / lightSpeed), 2);  // This formula is defined to be in the denominator
                                                                                // of the C/No when it is expressed in natural units
            eachFreeSpaceLossIndBs = 10*log10(eachFreeSpaceLoss); //This value shall be preceded by a minus when it is in decibels
            myFreeSpaceLossList.append(eachFreeSpaceLossIndBs);
        }
        else
        {
            visibility=false;
            myFreeSpaceLossList.append(0.0);
        }

    }

    return myFreeSpaceLossList;
} ///////////////////////////////////////// end of the method //////////////////////////////////////////

double CommAnalysis::OxygenSpecificAttenuation(double frequency)
{ //THE FREQUENCY HAS TO BE IN GHZ!!!!!!!

    double oxSpecAtt;

    oxcheck = m_environment->OxChoice();

    if(oxcheck==QString("true"))
    {

        oxSpecAtt=((7.1/(pow(frequency,2)+0.36))+(4.5/(pow((frequency-57),2)+0.98)))*pow(frequency,2)/1000;  //dB/Km
        /*oxSpecAtt=(7.1/(pow(frequency,2)+0.36));
        oxSpecAtt=oxSpecAtt+(4.5/(pow((frequency-57),2)+0.98));
        oxSpecAtt=oxSpecAtt*pow(frequency,2)/1000;*/
    }
    else if(oxcheck==QString("false"))
    {
        oxSpecAtt=0;
    }

    return oxSpecAtt;
} ///////////////////////////////////////// end of the method //////////////////////////////////////////

/** Calculates the ????????????
  * For applications which require an annual mean value, the surface water vapour densities exceeded
  *  for 50% of the year, shown for quick reference in Fig. 1, ITU-R P.836-3, should be used
  *
  * \return a double
  *
  */
double CommAnalysis::WaterVapourSpecificAttenuation(double frequency, double latitude, double longitude)
{
    double wpSpecAtt;
    double waterDensity;

    wvcheck=m_environment->WaterVapourChoice();

    QString whereIsTheDataFolder = findDataFolder();

    if(wvcheck=="true")
    {
        QString pathLat(whereIsTheDataFolder + "/" + "data/maps/surfwv_lat.txt"); QFile fileLat(pathLat);
        QString pathLon(whereIsTheDataFolder + "/" + "data/maps/surfwv_lon.txt"); QFile fileLon(pathLon);
        QString pathWaterVapour(whereIsTheDataFolder + "/" + "data/maps/surfwv_50.txt"); QFile fileWaterVapour(pathWaterVapour);

        if((!fileLat.exists()) || (!fileLon.exists()) || (!fileWaterVapour.exists()))
        {
            qDebug()<<" file error: surfwv_lat.txt does not exist";
        }
        else // In this case we found the file and we can read it
        {
            fileLat.open(QIODevice::ReadOnly);
            QTextStream latStream(&fileLat);
            double latPoint;

            int i=0;
            do
            {
                latStream>>latPoint;
                i++;
            } while(latPoint>latitude);

            fileLat.close();
            if(longitude<0) longitude=longitude+360;

            fileLon.open(QIODevice::ReadOnly);
            QTextStream lonStream(&fileLon);
            double lonPoint;

            int k;
            for(k=0; k<i; k++)
            {
                lonStream>>lonPoint;
            }

            int j=0;
            do
            {
                lonStream>>lonPoint;
                j++;
            } while(lonPoint<longitude);

            fileLon.close();

            fileWaterVapour.open(QIODevice::ReadOnly);
            QTextStream waterVapourStream(&fileWaterVapour);

            int z=0;
            int target=i+j;

            for(z; z<target; z++)
            {
                waterVapourStream>>waterDensity;
            }

            wpSpecAtt=(0.067+(3/(pow(((frequency)-22.3),2)+7.3)))*waterDensity*pow((frequency),2)/10000; //This result is in dB/Km
        }
    }
    else if (wvcheck=="false")
    {
        wpSpecAtt=0.0;
    }

    return wpSpecAtt;
} ///////////////////////////////////////// end of the method //////////////////////////////////////////

/** Calculates the atmospheric attenuation
  * This takes the elevation angle of the participant that is on the ground station.
  * Satellites cannot carry antennas with tracking systems since there is not attitude control in STA
  *
  * \return a double
  *
  */
QList<double> CommAnalysis::getAtmosphericAttenuation(double frequency, double wpSpecAtt, double oxSpecAtt, int numberOfRows)
{
    QList<double> myAtmosphericAttenuationList;
    double eachAtmosphericAttenuation;

    double h0 = 6;//equivalent height of a uniform medium for oxygen calculations (Km)
    double hw; //equivalent height for water vapour calculations
    frequency = frequency / 1000000000;
    hw = 2.2 + (3 / (pow((frequency - 22.3),2) + 3)); // In Km
    double elevationAngle;

    for(int i=0; i<numberOfRows; i++)
    {
        double t=m_propagatedScenario->spaceObjects().at(m_indexSC)->mission().at(m_indexMA)->trajectorySampleTime(i);

        if(tracking==true)
        {
            elevationAngle = m_propagatedScenario->groundObjects().at(m_indexGS)->elevationAngle(m_propagatedScenario->spaceObjects().at(m_indexSC),t);
        }
        else if(tracking==false)
        {
            if(m_flagTX==false && m_flagRX==true)
                elevationAngle = m_transmitter->Transmitter()->PointingDirection()->elevation();
            else if(m_flagRX==false && m_flagTX==true)
                elevationAngle = m_receiver->Receiver()->PointingDirection()->elevation();
        }

        bool visibility;

        if(m_propagatedScenario->groundObjects().at(m_indexGS)->elevationAngle(m_propagatedScenario->spaceObjects().at(m_indexSC),t)>=5.0*DEG2RAD)
        {
            visibility=true;
            //elevationAngleList.append(elevationAngle);
            //oxAttList.append(h0*oxSpecAtt*exp(-(hw/h0))/sin(elevationAngle));
            //wvAttList.append((hw*wpSpecAtt)/sin(elevationAngle));
            eachAtmosphericAttenuation = (((h0 * oxSpecAtt * exp(-(hw / h0))) + (hw * wpSpecAtt))) / sin(elevationAngle);
            myAtmosphericAttenuationList.append(eachAtmosphericAttenuation);
        }
        else
        {
            visibility=false;
            myAtmosphericAttenuationList.append(0.0);
        }
    }

    return myAtmosphericAttenuationList;

} ///////////////////////////////////////// end of the method //////////////////////////////////////////

/** Calculates the rain attenuation
  * This is the position of the groundstation, the parent object should be read
  * This function is following the recommendation ITU-R P.618-10 for Rain Attenuation estimation (section 2.2)
  *
  * \return a double
  *
  */
QList<double>  CommAnalysis::getRainAttenuation(double latitude, double longitude, int numberOfRows)
{
    QList<double> myRainAttenuationList;
    double eachRainAttenuation;

    ////////////////////////////// DATA AND COEFFICIENTS FOR THIS FUNCTION ////////////////////////////////////////
    double akh[4]={-5.3398, -0.3531, -0.23789, -0.94158};
    double bkh[4]={-0.10008, 1.26970, 0.86036, 0.64552};
    double ckh[4]={1.13098, 0.45400, 0.15354, 0.16817};

    double akv[4]={-3.80595, -3.44965, -0.39902, 0.50167};
    double bkv[4]={0.56934, -0.22911, 0.73042, 1.07319};
    double ckv[4]={0.81061, 0.51059, 0.11899, 0.27195};

    double aah[5]={-0.14318, 0.29591, 0.32177, -5.37610, 16.1721};
    double bah[5]={1.82442, 0.77564, 0.63773, -0.96230, -3.29980};
    double cah[5]={-0.55187, 0.19822, 0.13164, 1.47828, 3.43990};

    double aav[5]={-0.07771, 0.56727, -0.20238, -48.2991, 48.5833};
    double bav[5]={2.33840, 0.95545, 1.14520, 0.791669, 0.791459};
    double cav[5]={-0.76284, 0.54039, 0.26809, 0.116226, 0.116479};

    double logf;
    double frequency;

    if(m_flagTX==false && m_flagRX==true)
    {
        logf=log10(m_transmitter->Budget()->FrequencyBand()/1000000000);
        frequency= m_transmitter->Budget()->FrequencyBand()/1000000000;
    }
    else if(m_flagRX==false && m_flagTX==true)
    {
        logf=log10(m_receiver->Budget()->FrequencyBand()/1000000000);
        frequency= m_receiver->Budget()->FrequencyBand()/1000000000;
    }

    double logkh= (akh[0]*exp(-pow((logf-bkh[0])/ckh[0],2)))+(akh[1]*exp(-pow((logf-bkh[1])/ckh[1],2)))+(akh[2]*exp(-pow((logf-bkh[2])/ckh[2],2)))+(akh[3]*exp(-pow((logf-bkh[3])/ckh[3],2)))-(logf*0.18961)+0.71147;
    double logkv= (akv[0]*exp(-pow((logf-bkv[0])/ckv[0],2)))+(akv[1]*exp(-pow((logf-bkv[1])/ckv[1],2)))+(akv[2]*exp(-pow((logf-bkv[2])/ckv[2],2)))+(akv[3]*exp(-pow((logf-bkv[3])/ckv[3],2)))-(logf*0.16398)+0.63297;

    double alphah=(aah[0]*exp(-pow((logf-bah[0])/cah[0],2)))+(aah[1]*exp(-pow((logf-bah[1])/cah[1],2)))+(aah[2]*exp(-pow((logf-bah[2])/cah[2],2)))+(aah[3]*exp(-pow((logf-bah[3])/cah[3],2)))+(aah[4]*exp(-pow((logf-bah[4])/cah[4],2)))+(logf*0.67849)-1.95537;
    double alphav=(aav[0]*exp(-pow((logf-bav[0])/cav[0],2)))+(aav[1]*exp(-pow((logf-bav[1])/cav[1],2)))+(aav[2]*exp(-pow((logf-bav[2])/cav[2],2)))+(aav[3]*exp(-pow((logf-bav[3])/cav[3],2)))+(aav[4]*exp(-pow((logf-bav[4])/cav[4],2)))-(logf*0.053739)+0.83433;

    double kh=pow(10, logkh);

    double kv=pow(10, logkv);

    double tilt;

    if(m_flagTX==false && m_flagRX==true)
        tilt=m_transmitter->Transmitter()->EMproperties()->TiltAngle();
    else if(m_flagRX==false && m_flagTX==true)
        tilt=m_receiver->Receiver()->EMproperties()->TiltAngle();

    //////////////////////////////////////STEP 1: Determination of rain height given in ITU-R P.839//////////////////////////////

    // Find out now the correct path of the files
    QString staResourcesPath = findDataFolder();

    QString pathLat(staResourcesPath + "/" + "data/maps/ESALAT.TXT");
    QFile fileLat(pathLat);

    if(!fileLat.exists())
        qDebug()<<" file error";
    fileLat.open(QIODevice::ReadOnly);

    QTextStream latStream(&fileLat);
    double latPoint;

    int i1=0;
    do
    {
        latStream>>latPoint;
        i1++;

    } while(latPoint>latitude);

    fileLat.close();

    if(longitude<0)
        longitude=longitude+360;

    QString pathLon(staResourcesPath + "/" + "data/maps/ESALON.TXT");

    QFile fileLon(pathLon);

    if(!fileLon.exists())
        qDebug()<<" file error";
    fileLon.open(QIODevice::ReadOnly);

    QTextStream lonStream(&fileLon);
    double lonPoint;

    int k1=0;
    for(k1; k1<i1; k1++)
        lonStream>>lonPoint;

    int j1=0;
    do
    {
        lonStream>>lonPoint;
        j1++;
    } while(lonPoint<longitude);

    fileLon.close();

    QString pathHeight(staResourcesPath + "/" + "data/maps/ESA0HEIGHT.TXT");

    QFile fileHeight(pathHeight);

    if(!fileHeight.exists())
        qDebug()<<" file error";
    fileHeight.open(QIODevice::ReadOnly);

    QTextStream heightStream(&fileHeight);
    double heightPoint;

    int z1=0;
    int target1=i1+j1;

    for(z1; z1<target1; z1++)
        heightStream>>heightPoint;

    double hr;

    hr=heightPoint+0.36; //in Km

    //This step is working properly

    ///////////////////////////////////////////STEP 4: Get the rainfall rate exceeded for 0.01% of an average year//////////////////////////////////////////////

    QString pathLatRain(staResourcesPath + "/" + "data/maps/ESARAIN_LAT_v5.TXT");

    QFile fileLatRain(pathLatRain);

    if(!fileLatRain.exists())
        qDebug()<<" file error";
    fileLatRain.open(QIODevice::ReadOnly);

    QTextStream latStreamRain(&fileLatRain);
    double latPointRain;

    int i2=0;
    do
    {
        latStreamRain>>latPointRain;
        i2++;
    }while(latPointRain>latitude);

    fileLatRain.close();

    if(longitude<0)
        longitude=longitude+360;

    QString pathLonRain(staResourcesPath + "/" + "data/maps/ESARAIN_LON_v5.TXT");

    QFile fileLonRain(pathLonRain);

    if(!fileLonRain.exists())
        qDebug()<<" file error";
    fileLonRain.open(QIODevice::ReadOnly);

    QTextStream lonStreamRain(&fileLonRain);
    double lonPointRain;

    int k2=0;
    for(k2; k2<i2; k2++)
        lonStreamRain>>lonPointRain;

    int j2=0;
    do
    {
        lonStreamRain>>lonPointRain;
        j2++;

    } while(lonPointRain<longitude);
    fileLonRain.close();

    QString pathRain001(staResourcesPath + "/" + "data/maps/R0_01.TXT");

    QFile fileRain001(pathRain001);

    if(!fileRain001.exists())
        qDebug()<<" file error";
    fileRain001.open(QIODevice::ReadOnly);

    QTextStream rain001Stream(&fileRain001);
    double r001;

    int t=0;
    int target2=i2+j2;

    for(t; t<target2; t++)
        rain001Stream>>r001;

    //////////////////////////////////////////STEP 2, 3: Get the slant-path range length, below the rain height and its projection////////////////////////////////////////////

    //////////////////////////////////////////STEP 5: get the Specific Attenuation in dB/Km according to ITU-R P.838-3////////////////////////////////////////////////////////

    ///////////////////////////////////////// STEP 6: Calculate the horizontal reduction factor for 0.01% of the time ////////////////////////////////////////////////////////

    //////////////////////////////////////////STEP 7: Calculate the vertical reduction factor for 0.01% of the time //////////////////////////////////////////////////////////

    //////////////////////////////////////////STEP 8: The effective path length //////////////////////////////////////////////////////////

    //////////////////////////////////////////STEP 9: The predicted attenuation exceeded for 0.01% of an average year //////////////////////////////////////////////////////////

    //////////////////////////////////////////STEP 10: Other percentages //////////////////////////////////////////////////////////////////////////////////////////////////////

    double elevationAngle;
    double finalAtt;

    for(int counter=0; counter<numberOfRows; counter++)
    {
        double t=m_propagatedScenario->spaceObjects().at(m_indexSC)->mission().at(m_indexMA)->trajectorySampleTime(counter);

        if(tracking==true)
        {
            elevationAngle=m_propagatedScenario->groundObjects().at(m_indexGS)->elevationAngle(m_propagatedScenario->spaceObjects().at(m_indexSC),t);
        }
        else if(tracking==false)
        {

            if(m_flagTX==false && m_flagRX==true)
                elevationAngle=m_transmitter->Transmitter()->PointingDirection()->elevation();
            else if(m_flagRX==false && m_flagTX==true)
                elevationAngle=m_receiver->Receiver()->PointingDirection()->elevation();

        } //This takes the elevation angle of the participant that is on the ground station. Satellites cannot carry antennas with tracking systems since there is not attitude control in STA

        bool visibility;
        if(m_propagatedScenario->groundObjects().at(m_indexGS)->elevationAngle(m_propagatedScenario->spaceObjects().at(m_indexSC),t)>=5.0*DEG2RAD)
        {
            visibility=true;
        }
        else
        {
            visibility=false;
        }

        double gsAlt=m_propagatedScenario->groundObjects().at(m_indexGS)->altitude;
        gsAlt=gsAlt/1000;//Now gsAlt is in Km

        if(visibility==true )
        {
            raincheck=m_environment->Rain()->RainChoice();

            if(raincheck=="true")
            {
                double slantPath;
                slantPath=(hr-gsAlt)/sin(elevationAngle);

                double Lg=slantPath*cos(elevationAngle); //Horizontal projection of the slant path length

                //////////////////////// This belongs to step 5 //////////////////////
                double ktot=(kh+kv+((kh-kv)*pow(cos(elevationAngle),2)*cos(2*tilt)))/2;
                double alphatot=((kh*alphah)+(kv*alphav)+((kh*alphah-kv*alphav)*pow(cos(elevationAngle),2)*cos(2*tilt)))/(2*ktot);
                double specAttenuation= ktot*pow(r001, alphatot); //Since it is specific attenuation, the units are dB/Km.

                ////////////////////////// This belongs to step 6 /////////////////////
                double horiz_reduc=1/(1+(0.78*sqrt(Lg*specAttenuation/frequency))-(0.38*(1-exp(-2*Lg))));

                ////////////////////////// This belongs to step 7 ////////////////////////
                double zeta=atan((hr-gsAlt)/(Lg*horiz_reduc));

                double Lr;
                double chi;

                if(zeta>elevationAngle)
                    Lr=(Lg*horiz_reduc)/cos(elevationAngle);
                else
                    Lr=slantPath;

                if(fabs(latitude)<36)
                    chi=36-fabs(latitude);
                else
                    chi=0;

                chi=chi*DEG2RAD;
                double ver_reduc=1/(1+(sqrt(sin(elevationAngle))*((31*(1-exp(-(elevationAngle/(1+chi))))*(sqrt((Lr*specAttenuation))/pow(frequency, 2)))-0.45)));

                ////////////////////////// This belongs to step 8 /////////////////////////
                double Le=Lr*ver_reduc;

                ////////////////////////// This belongs to step 9 /////////////////////////
                double estimatedAtt001=specAttenuation*Le;

                //////////////////////// This belongs to step 10 //////////////////////////
                double beta;
                double percentage=m_environment->Rain()->PercentageExceededLimit();
                if(percentage>1 || fabs(latitude)>=36)
                    beta=0;
                else if(percentage<1 && fabs(latitude)<36 && elevationAngle>=(25*DEG2RAD))
                    beta=-0.005*(fabs(latitude)-36);
                else
                    beta=-0.005*(fabs(latitude)-36)+1.8-(4.25*sin(elevationAngle));

                eachRainAttenuation = estimatedAtt001*pow((percentage/0.01),(-(0.655+(0.033*log(percentage))-(0.045*log(estimatedAtt001))-(beta*sin(elevationAngle)*(1-percentage))))); //In dB!!

            }
            else if(raincheck=="false")
            {
                eachRainAttenuation = 0;
            }

            myRainAttenuationList.append(eachRainAttenuation);

        }
        else
        {
            myRainAttenuationList.append(0.0);
        }

    }

    return myRainAttenuationList;
} ///////////////////////////////////////// end of the method //////////////////////////////////////////

double CommAnalysis::SystemTempCalculations()
{

    double antennaTemp, skyTemp, groundTemp, rainAttenuation, systemTemp, equivalentTemp, rxFigureNoise, rxFeederLoss, thermoTempFeeder;

    rxFigureNoise=pow(10, m_receiver->Receiver()->SystemTemperature()->RxNoiseFigure()/10);//Now in natural units
    rxFeederLoss=pow(10, m_receiver->Receiver()->FeederLossRx()/10); //Now in natural units
    thermoTempFeeder=m_receiver->Receiver()->SystemTemperature()->ThermoFeeder();

    double elevationAngle;
    antennaTempChoice=m_receiver->Receiver()->SystemTemperature()->choiceTantenna();

    for (int j=0; j<elevationAngleList.length(); j++){

        elevationAngle=elevationAngleList[j];
        if(antennaTempChoice=="calculated")
        {
               if(m_flagRX==true)
                    antennaTemp=290;
                else if(m_flagRX==false)
                {

                        skyTemp=4+275*(1-pow(10,(-atmosphericAttList[j]/10)));
                        rainAttenuation=pow(10, rainAttenuationList[j]/10);

                        if (elevationAngle<=-10*DEG2RAD){ groundTemp=290;}
                        else if (elevationAngle>-10*DEG2RAD && elevationAngle<=0*DEG2RAD){ groundTemp=150;}
                        else if (elevationAngle>0*DEG2RAD && elevationAngle<=10*DEG2RAD){ groundTemp=50;}
                        else if (elevationAngle>10*DEG2RAD && elevationAngle<=90*DEG2RAD){ groundTemp=10;}

                        antennaTemp=(skyTemp/rainAttenuation)+groundTemp+275*(1-(1/rainAttenuation));

                 }
         }else if(antennaTempChoice=="constant"){
             antennaTemp=m_receiver->Receiver()->SystemTemperature()->Tantenna();
         }
        antennaTempList.append(antennaTemp);

        equivalentTemp=290*(rxFigureNoise-1);

    systemTemp=(antennaTemp/rxFeederLoss)+equivalentTemp+(thermoTempFeeder*(1-(1/rxFeederLoss))); //In Kelvin
    systemTempList.append(systemTemp);

    }

  return systemTemp;
} ///////////////////////////////////////// end of the method //////////////////////////////////////////

double CommAnalysis::Modulations(double EbNo)
{ //the EbNo is passed in dB

    QString modType=m_transmitter->Transmitter()->Modulation()->ModulationType();
    double ber, a, b;

    if(modType==QString("BPSK"))
    {
        a=sqrt(EbNo);
        b=erfc(a);
        ber=0.5*b;
    }
    else if (modType=="DE-BPSK")
    {
        a=sqrt(EbNo);
        ber=erfc(a);
    }
    else if (modType=="D-BPSK")
    {
        a=exp((-EbNo));
        ber=0.5*a;
    }
    else if (modType=="QPSK")
    {
        a=sqrt(EbNo);
        b=erfc(a);
        ber=0.5*b;
    }
    else if (modType=="DE-QPSK")
    {
        a=sqrt(EbNo);
        ber=erfc(a);
    }
    else if (modType=="D-QPSK")
    {
        a=exp((-EbNo));
        ber=0.5*a;
    }
    else if (modType=="OQPSK")
    {
        a=sqrt(EbNo);
        b=erfc(a);
        ber=0.5*b;
    }

    return ber;
} ///////////////////////////////////////// end of the method //////////////////////////////////////////

void CommAnalysis::CommReports()
{
    // Find out now the correct path of the files
    QString staResourcesPath = findDataFolder();

    QString firstFile = staResourcesPath + "/" + "reportComm1.txt";
    QString secondFile = staResourcesPath + "/" + "reportComm2.txt";
    QString thirdFile = staResourcesPath + "/" + "reportComm3.txt";

    QFile reportComm1(firstFile);
    QFile reportComm2(secondFile);
    QFile reportComm3(thirdFile);

    //qDebug() << firstFile << endl;
    //qDebug() << secondFile << endl;
    //qDebug() << thirdFile << endl;

    reportComm1.open(QIODevice::WriteOnly|QIODevice::ReadWrite);
    reportComm2.open(QIODevice::WriteOnly|QIODevice::ReadWrite);
    reportComm3.open(QIODevice::WriteOnly|QIODevice::ReadWrite);

    QTextStream streamReportComm1(&reportComm1);
    QTextStream streamReportComm2(&reportComm2);
    QTextStream streamReportComm3(&reportComm3);

    streamReportComm1.setRealNumberPrecision(16);
    streamReportComm2.setRealNumberPrecision(16);
    streamReportComm3.setRealNumberPrecision(16);

    streamReportComm1<<"MJD"<<"\t"<<"EIRP"<<"\t"<<"Rcvd.Freq."<<"\t"<<"Doppler Shift"<<"\t"<<"Rcvd. Power"<<"\t"<<"Flux Density"<<"\t"<<"OverlapBWfactor"<<endl;
    streamReportComm2<<"MJD"<<"\t"<<"FSL"<<"\t"<<"OxLoss"<<"\t"<<"WvLoss"<<"\t"<<"RainLoss"<<"\t"<<"AtmosLoss"<<"\t"<<"PropLoss"<<endl;
    streamReportComm3<<"MJD"<<"\t"<<"G/Y"<<"\t"<<"C/No"<<"\t"<<"C/N"<<"\t"<<"Eb/No"<<"\t"<<"BER"<<endl;

    // Starting now the calculations
    double frequency;

    if(m_flagTX==false && m_flagRX==true)
    {
        frequency = m_transmitter->Budget()->FrequencyBand()/1000000000;
    }
    else if(m_flagRX==false && m_flagTX==true)
    {
        frequency = m_receiver->Budget()->FrequencyBand()/1000000000;
    }

    /////////////// FIXED VALUES THAT COME FROM TRANSMITTER AND RECEIVER //////////////////////
    double potTxDb = 10*log10(m_transmitter->Transmitter()->TransmittingPower());
    double gainTxDb = m_transmitter->Transmitter()->EMproperties()->GainMax();
    double txFeederLossDb = m_transmitter->Transmitter()->FedderLossTx();
    double txDepointingLossDb = m_transmitter->Transmitter()->DepointingLossTx();

    double polLoss, rcvdPower, fluxDensity;

    if (m_transmitter->Transmitter()->EMproperties()->Polarisation()=="rightCircular" && m_receiver->Receiver()->EMproperties()->Polarisation()=="leftCircular")
        polLoss=20;//in dB
    else if (m_receiver->Receiver()->EMproperties()->Polarisation()=="rightCircular" && m_transmitter->Transmitter()->EMproperties()->Polarisation()=="leftCircular")
        polLoss=20;//in dB
    else
    {
        double txAngle = m_transmitter->Transmitter()->EMproperties()->TiltAngle();
        double rxAngle = m_receiver->Receiver()->EMproperties()->TiltAngle();
        polLoss = -20*(log10(cos(fabs(txAngle-rxAngle))));
    }

    double gainRxDb = m_receiver->Receiver()->EMproperties()->GainMax();
    double rxFeederLossDb = m_receiver->Receiver()->FeederLossRx();
    double rxDepointingLossDb = m_receiver->Receiver()->DepointingLossRx();
    double transmittedFrequency = m_transmitter->Budget()->FrequencyBand();

    //Calculate some fix parameters
    double eirp = potTxDb + gainTxDb - (txFeederLossDb + txDepointingLossDb);  //The units are in dBW
    double txBW = m_transmitter->Transmitter()->EMproperties()->BandWidth();
    double rxBW = m_receiver->Receiver()->EMproperties()->BandWidth();
    double txDataRate = m_transmitter->Transmitter()->Modulation()->DataRate();

    double overLapBWfactor;

    if(txBW>=rxBW)
        overLapBWfactor=rxBW/txBW;
    else if(rxBW>txBW)
        overLapBWfactor=txBW/rxBW;

    double propLoss;
    double carrierToNoise;
    double EbNo, BER;
    QList<double> rcvdFrequencyList;
    QList<double> pathLossList;
    QList<double> GoverTList;

    double myJulianDay;
    int myIndex;
    int p;
    int myNumberOfRows = m_propagatedScenario->spaceObjects().at(m_indexSC)->mission().at(m_indexMA)->trajectorySampleCount();  qDebug() << "myNumberOfRows" << myNumberOfRows << endl;

    double receiverFrequency, receiverPower, myGoverT, pathLoss, carrierToNoiseDensity;
    double theRange;

    // Get the list of all Doppler shifts:
    QList<double> myRealDopplerShiftList = CommAnalysis::getDopplerShiftList(myNumberOfRows);  qDebug() << "myRealDopplerShiftListLenght" << myRealDopplerShiftList.length() << endl;
    // Get the list of all free space losses:
    QList<double> myRealFreeSpaceLossList = CommAnalysis::getFreeSpaceLossList(myNumberOfRows);
    double oxAttenuation = CommAnalysis::OxygenSpecificAttenuation(frequency);
    double wpAttenuation = CommAnalysis::WaterVapourSpecificAttenuation(frequency, m_propagatedScenario->groundObjects().at(m_indexGS)->latitude,m_propagatedScenario->groundObjects().at(m_indexGS)->longitude);
    QList<double> myRealAtmosphericAttenuationList = CommAnalysis::getAtmosphericAttenuation(frequency, wpAttenuation, oxAttenuation, myNumberOfRows); qDebug() << "myRealAtmosphericAttenuationListLenght" << myRealAtmosphericAttenuationList.length() << endl;
    QList<double> myRealRainAttenuationList = CommAnalysis::getRainAttenuation(m_propagatedScenario->groundObjects().at(m_indexGS)->latitude,m_propagatedScenario->groundObjects().at(m_indexGS)->longitude, myNumberOfRows);  qDebug() << "myRealRainAttenuationListLengh" << myRealRainAttenuationList.length() << endl;
    double SystemTempCalculations = CommAnalysis::SystemTempCalculations();

    for(myIndex=0; myIndex<myNumberOfRows; myIndex++)
    {
        myJulianDay = m_propagatedScenario->spaceObjects().at(m_indexSC)->mission().at(m_indexMA)->trajectorySampleTime(myIndex);
        receiverFrequency = transmittedFrequency + myRealDopplerShiftList[myIndex];  //qDebug() << receiverFrequency << endl;
        theRange = m_propagatedScenario->groundObjects().at(m_indexGS)->getRange(m_propagatedScenario->spaceObjects().at(m_indexSC), myIndex) * 1000;
        fluxDensity = eirp - (10 * log10(4 * theRange * theRange * Pi)); //qDebug() << fluxDensity << endl;
        pathLoss = myRealRainAttenuationList[myIndex] + myRealAtmosphericAttenuationList[myIndex] + myRealFreeSpaceLossList[myIndex];
        //myGoverT = gainRxDb - (rxFeederLossDb + rxDepointingLossDb + pathLoss) - (10*log10(systemTempList[myIndex]));  //The units are dBHz
        myGoverT = gainRxDb - (rxFeederLossDb + rxDepointingLossDb + pathLoss) - (10*log10(1.0));
        carrierToNoiseDensity = eirp - pathLoss + myGoverT + 228.6 + (10*log10(overLapBWfactor));  //The units are dBHz, the 228.6 factor is in dBW/HzK and it is the Bolztman constant in decibels
        receiverPower = eirp - propLoss;
        carrierToNoise = carrierToNoiseDensity - (10*log10(txBW));
        BER = CommAnalysis::Modulations(EbNo);

        streamReportComm1 << myJulianDay << "\t" << eirp << "\t" << receiverFrequency << "\t" << myRealDopplerShiftList[myIndex] << "\t" << receiverPower << "\t" << fluxDensity << "\t" << overLapBWfactor << endl;
        //streamReportComm2 << myJulianDay << "\t" << myRealFreeSpaceLossList[myIndex] << "\t" << oxAttList[p] << "\t" << wvAttList[myIndex] << "\t" << myRealRainAttenuationList[myIndex] << "\t" << myRealAtmosphericAttenuationList[myIndex] << "\t" << pathLoss << endl;
        streamReportComm2 << myJulianDay << "\t" << myRealFreeSpaceLossList[myIndex] << "\t" << 0.0 << "\t" << 0.0 << "\t" << myRealRainAttenuationList[myIndex] << "\t" << myRealAtmosphericAttenuationList[myIndex] << "\t" << pathLoss << endl;
        streamReportComm3 << myJulianDay << "\t" << myGoverT << "\t" << carrierToNoiseDensity << "\t" << carrierToNoise << "\t" << EbNo << "\t" << BER <<endl;
    }

    reportComm1.close();
    reportComm2.close();
    reportComm3.close();
}


// graph-tool -- a general graph modification and manipulation thingy
//
// Copyright (C) 2006-2015 Tiago de Paula Peixoto <tiago@skewed.de>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#include "graph_filtering.hh"

#include <boost/python.hpp>
#include <boost/graph/betweenness_centrality.hpp>

#include "graph.hh"
#include "graph_selectors.hh"
#include "graph_util.hh"

using namespace std;
using namespace boost;
using namespace graph_tool;

template <class Graph, class EdgeBetweenness, class VertexBetweenness>
void normalize_betweenness(const Graph& g,
                           EdgeBetweenness edge_betweenness,
                           VertexBetweenness vertex_betweenness,
                           size_t n)
{
    double vfactor = (n > 2) ? 1.0/((n-1)*(n-2)) : 1.0;
    double efactor = (n > 1) ? 1.0/(n*(n-1)) : 1.0;
    if (std::is_convertible<typename graph_traits<Graph>::directed_category,
                            undirected_tag>::value)
    {
        vfactor *= 2;
        efactor *= 2;
    }

    int i, N = num_vertices(g);
    #pragma omp parallel for default(shared) private(i)   \
        schedule(runtime) if (N > 100)
    for (i = 0; i < N; ++i)
    {
        typename graph_traits<Graph>::vertex_descriptor v = vertex(i, g);
        if (v == graph_traits<Graph>::null_vertex())
            continue;
        put(vertex_betweenness, v, vfactor * get(vertex_betweenness, v));
    }

    typename graph_traits<Graph>::edge_iterator e, e_end;
    for (tie(e, e_end) = edges(g); e != e_end; ++e)
    {
        put(edge_betweenness, *e, efactor * get(edge_betweenness, *e));
    }
}

struct get_betweenness
{
    typedef void result_type;
    template <class Graph, class EdgeBetweenness, class VertexBetweenness>
    void operator()(Graph& g,
                    GraphInterface::vertex_index_map_t index_map,
                    EdgeBetweenness edge_betweenness,
                    VertexBetweenness vertex_betweenness,
                    bool normalize, size_t n) const
    {
        vector<vector<typename graph_traits<Graph>::edge_descriptor> >
            incoming_map(num_vertices(g));
        vector<size_t> distance_map(num_vertices(g));
        vector<typename property_traits<VertexBetweenness>::value_type>
            dependency_map(num_vertices(g));
        vector<size_t> path_count_map(num_vertices(g));
        brandes_betweenness_centrality
            (g, vertex_betweenness, edge_betweenness,
             make_iterator_property_map(incoming_map.begin(), index_map),
             make_iterator_property_map(distance_map.begin(), index_map),
             make_iterator_property_map(dependency_map.begin(), index_map),
             make_iterator_property_map(path_count_map.begin(), index_map),
             index_map);
        if (normalize)
            normalize_betweenness(g, edge_betweenness, vertex_betweenness, n);
    }
};

struct get_weighted_betweenness
{
    typedef void result_type;
    template <class Graph, class EdgeBetweenness, class VertexBetweenness,
              class VertexIndexMap>
        void operator()(Graph& g, VertexIndexMap vertex_index,
                        EdgeBetweenness edge_betweenness,
                        VertexBetweenness vertex_betweenness,
                        boost::any weight_map, bool normalize,
                        size_t n, size_t max_eindex) const
    {
        vector<vector<typename graph_traits<Graph>::edge_descriptor> >
            incoming_map(num_vertices(g));
        vector<typename property_traits<EdgeBetweenness>::value_type>
            distance_map(num_vertices(g));
        vector<typename property_traits<VertexBetweenness>::value_type>
            dependency_map(num_vertices(g));
        vector<size_t> path_count_map(num_vertices(g));

        typename EdgeBetweenness::checked_t weight =
            any_cast<typename EdgeBetweenness::checked_t>(weight_map);

        brandes_betweenness_centrality
            (g, vertex_betweenness, edge_betweenness,
             make_iterator_property_map(incoming_map.begin(), vertex_index),
             make_iterator_property_map(distance_map.begin(), vertex_index),
             make_iterator_property_map(dependency_map.begin(), vertex_index),
             make_iterator_property_map(path_count_map.begin(), vertex_index),
             vertex_index, weight.get_unchecked(max_eindex+1));
        if (normalize)
            normalize_betweenness(g, edge_betweenness, vertex_betweenness, n);
    }
};

void betweenness(GraphInterface& g, boost::any weight,
                 boost::any edge_betweenness,
                 boost::any vertex_betweenness,
                 bool normalize)
{
    if (!belongs<edge_floating_properties>()(edge_betweenness))
        throw ValueException("edge property must be of floating point value"
                             " type");

    if (!belongs<vertex_floating_properties>()(vertex_betweenness))
        throw ValueException("vertex property must be of floating point value"
                             " type");

    if (!weight.empty())
    {
        run_action<>()
            (g, std::bind<>(get_weighted_betweenness(),
                            std::placeholders::_1, g.GetVertexIndex(),
                            std::placeholders::_2,
                            std::placeholders::_3, weight, normalize,
                            g.GetNumberOfVertices(), g.GetMaxEdgeIndex()),
             edge_floating_properties(),
             vertex_floating_properties())
            (edge_betweenness, vertex_betweenness);
    }
    else
    {
        run_action<>()
            (g, std::bind<void>(get_betweenness(), std::placeholders::_1,
                                g.GetVertexIndex(), std::placeholders::_2,
                                std::placeholders::_3, normalize,
                                g.GetNumberOfVertices()),
             edge_floating_properties(),
             vertex_floating_properties())
            (edge_betweenness, vertex_betweenness);
    }
}

struct get_central_point_dominance
{
    template <class Graph, class VertexBetweenness>
    void operator()(Graph& g, VertexBetweenness vertex_betweenness, double& c)
        const
    {
        c = double(central_point_dominance(g, vertex_betweenness));
    }
};

double central_point(GraphInterface& g,
                     boost::any vertex_betweenness)
{
    double c = 0.0;
    run_action<graph_tool::detail::never_reversed>()
        (g, std::bind<>(get_central_point_dominance(), std::placeholders::_1,
                        std::placeholders::_2, std::ref(c)),
         vertex_scalar_properties()) (vertex_betweenness);
    return c;
}

void export_betweenness()
{
    using namespace boost::python;
    def("get_betweenness", &betweenness);
    def("get_central_point_dominance", &central_point);
}