/**
 * @file slae.hpp:  System of Linear Algebraic Equations utilities.
 *
 * This module contains functions for solving SLAE,
 * particularly for Cube Product Strassen-like algorithm finding.
 *
 * @author Eugene Petkevich
 * @version pre-alpha
 */

#ifndef SLAE_HPP_INCLUDED
#define SLAE_HPP_INCLUDED

#include <vector>
#include <map>

#include <boost/dynamic_bitset.hpp>

#include "utils.hpp"

using namespace std;

//=============================================================================

/// All the following functions solve SLAE using different methods.

template <size_t N>
bool gauss_solve(vector<mm_bitset<N>> a, mm_bitset<N> b);

class Gauss_Presolve_Data;
template <size_t N>
void gauss_presolve(vector<mm_bitset<N>> a, Gauss_Presolve_Data& p);
template <size_t N>
bool gauss_solve(const Gauss_Presolve_Data& p, mm_bitset<N> b);

template <size_t N>
bool binary_solve(const vector<mm_bitset<N>>& a, const mm_bitset<N>& b);

template <size_t N>
bool binary_solve_recursive(const vector<mm_bitset<N>>& a, const mm_bitset<N>& b);

template <size_t N>
bool gauss_solve_randomized(vector<mm_vector_with_properties<N>> a, mm_vector_with_properties<N> b);

template <size_t N>
class Gauss_WP_Presolve_Data;
template <size_t N>
bool gauss_wp_presolve(const vector<mm_vector_with_properties<N>>& a, Gauss_WP_Presolve_Data<N>& p);
template <size_t N>
bool gauss_wp_solve(Gauss_WP_Presolve_Data<N>& p, const mm_vector_with_properties<N>& b);
template <size_t N>
bool gauss_wp_solve_cached(Gauss_WP_Presolve_Data<N>& p, const mm_vector_with_properties<N>& b);
template <size_t N>
bool gauss_wp_solve_fast_cached(Gauss_WP_Presolve_Data<N>& p, const mm_vector_with_properties<N>& b);

//=============================================================================

/// Comparison functions for bitvectors.

template <size_t N>
bool compare_lexical (const mm_bitset<N>& a, const mm_bitset<N>& b);

template <size_t N>
bool compare_count (const mm_bitset<N>& a, const mm_bitset<N>& b);

template <size_t N>
bool compare_combined (const mm_bitset<N>& a, const mm_bitset<N>& b);

//=============================================================================
//=============================================================================

/**
 * Sovle SLAE by Gaussian elimination.
 *
 * @param a: the coefficient matrix;
 * @param b: the resulting vector;
 *
 * @return if there is a solution.
 */
template <size_t N>
bool
gauss_solve(vector<mm_bitset<N>> a, mm_bitset<N> b)
{
    int r = 0;
    for (int i = 0; i < a.size(); ++i) { // for all columns
        // first we exchange rows if needed, so that a[i][i] = 1
        if (!a[i][r]) {
            int k = r+1;
            while ((k < N) && (!a[i][k])) {
                ++k;
            }
            if (k >= N) {
                continue;
            } else {
                bool tmp = b[k];
                b[k] = b[r];
                b[r] = tmp;
                for (int l = i; l < a.size(); ++l) {
                    tmp = a[l][k];
                    a[l][k] = a[l][r];
                    a[l][r] = tmp;
                }
            }
        }
        // second, we make all coefficients under a[i][i] equal to 0
        for (int j = r+1; j < b.size(); ++j) { // for all rows
            if (a[i][j]) {
                for (int k = i; k < a.size(); ++k) {
                    a[k][j] = (a[k][j] != a[k][r]);
                }
                b[j] = (b[j] != b[r]);
            }
        }
        ++r;
    }
    // we check all i from 7 to 15 for zero coefficients
    for (int i = r; i < b.size(); ++i) {
        if (b[i])
            return false;
    }
    return true;
}

//=============================================================================

/**
 * Data for Gaussian elimination presolve.
 */
class Gauss_Presolve_Data {
public:
    size_t n; /// Size of the matrix.
    int r;
    vector<int> rows_s;
    vector<int> rows_d;
    vector<bool> rows_o;
};

/**
 * Presolve SLAE by Gaussian elimination.
 *
 * Collects Gaussian elimination operations that are needed
 * to selve a SLAE with a specified matrix.
 *
 * @param a: the coefficient matrix;
 * @param p: varuable to store presolve data;
 */
template <size_t N>
void
gauss_presolve(vector<mm_bitset<N>> a, Gauss_Presolve_Data & p)
{
    p.n = a.size();
    p.rows_s.clear();
    p.rows_d.clear();
    p.rows_o.clear();
    int r = 0;
    for (int i = 0; i < a.size(); ++i) { // for all columns
        // first we exchange rows if needed, so that a[i][i] = 1
        if (!a[i][r]) {
            int k = r+1;
            while ((k < N) && (!a[i][k])) {
                ++k;
            }
            if (k >= N) {
                continue;
            } else {
                bool tmp;
                p.rows_s.push_back(r);
                p.rows_d.push_back(k);
                p.rows_o.push_back(true);
                for (int l = i; l < a.size(); ++l) {
                    tmp = a[l][k];
                    a[l][k] = a[l][r];
                    a[l][r] = tmp;
                }
            }
        }
        // second, we make all coefficients under a[i][i] equal to 0
        for (int j = r+1; j < N; ++j) { // for all rows
            if (a[i][j]) {
                for (int k = i; k < a.size(); ++k) {
                    a[k][j] = (a[k][j] != a[k][r]);
                }
                p.rows_s.push_back(r);
                p.rows_d.push_back(j);
                p.rows_o.push_back(false);
            }
        }
        ++r;
    }
    p.r = r;
}

/**
 * Sovle SLAE by Gaussian elimination using precomputed data.
 *
 * @param p: the presolve data;
 * @param b: the resulting vector;
 *
 * @return if there is a solution.
 */
template <size_t N>
bool
gauss_solve(const Gauss_Presolve_Data& p, mm_bitset<N> b)
{
    for (int i = 0; i < p.rows_o.size(); ++i) {
        if (p.rows_o[i]) {
            bool tmp = b[p.rows_s[i]];
            b[p.rows_s[i]] = b[p.rows_d[i]];
            b[p.rows_d[i]] = tmp;
        } else {
            b[p.rows_d[i]] = (b[p.rows_d[i]] != b[p.rows_s[i]]);
        }
    }
    for (int i = p.r; i < b.size(); ++i) {
        if (b[i])
            return false;
    }
    return true;
}

//=============================================================================

/**
 * Solve SLAE by trying all solutions.
 *
 * @param a: the coefficient matrix;
 * @param b: the resulting vector;
 *
 * @return if there is a solution.
 */
template <size_t N>
bool
binary_solve(const vector<mm_bitset<N>>& a, const mm_bitset<N>& b)
{
    uint_least64_t counter;
    uint_least64_t limit = power(2,a.size());
    for (counter = 1; counter < limit; ++counter) {
        boost::dynamic_bitset<> x(a.size(),counter);
        mm_bitset<N> r(0);
        bool is_good = true;
        // we multiply a by x
        for (int i = 0; i < b.size(); ++i) {
            for (int j = 0; j < a.size(); ++j) {
                if (x[j]) {
                    r[i] = (r[i] != a[j][i]);
                }
            }
            if (r[i] != b[i]) {
                is_good = false;
                break;
            }
        }
        if (is_good)
            return true;
    }
    return false;
}

/**
 * Solve SLAE by trying all solutions.
 *
 * @param a: the coefficient matrix;
 * @param b: the resulting vector;
 *
 * @return if there is a solution.
 */
template <size_t N>
bool
binary_solve(const vector<mm_vector_with_properties<N>>& a, const mm_vector_with_properties<N>& b)
{
    vector<mm_bitset<N>> sa;
    for (auto& vp: a) {
        sa.push_back(vp.v);
    }
    return binary_solve(sa, b.v);
}

/**
 * Solve SLAE by trying all solutions and return the result.
 *
 * The SLAE should have a solution.
 *
 * @param a: the coefficient matrix;
 * @param b: the resulting vector;
 *
 * @return solution (if SLAE has no solution, return value could be anything).
 */
template <size_t N>
boost::dynamic_bitset<>
binary_solve_result(const vector<mm_bitset<N>>& a, const mm_bitset<N>& b)
{
    uint_least64_t counter;
    uint_least64_t limit = power(2,a.size());
    for (counter = 1; counter < limit; ++counter) {
        boost::dynamic_bitset<> x(a.size(),counter);
        mm_bitset<N> r(0);
        bool is_good = true;
        // we multiply a by x
        for (int i = 0; i < b.size(); ++i) {
            for (int j = 0; j < a.size(); ++j) {
                if (x[j]) {
                    r[i] = (r[i] != a[j][i]);
                }
            }
            if (r[i] != b[i]) {
                is_good = false;
                break;
            }
        }
        if (is_good)
            return x;
    }
    return boost::dynamic_bitset<>();
}

//=============================================================================

/**
 * Subroutine for solving SLAE via backtracking.
 *
 * @param a: the coefficient matrix;
 * @param b: the resulting vector;
 * @param depth: backtracking tree current depth;
 * @param current: current value of guessed solution.
 *
 * @return if there is a solution.
 */
template <size_t N>
bool
binary_solve_explore(const vector<mm_bitset<N>>* a, const mm_bitset<N>* b, int depth, const mm_bitset<N>* current)
{
    if (depth == -1) {
        for (int k = 0; k < N; ++k) {
            if (current->test(k) != b->test(k))
                return false;
        }
        return true;
    } else {
        bool result = binary_solve_explore(a, b, depth-1, current);
        if (result)
            return true;
        mm_bitset<N>* current_add = new mm_bitset<N>();
        for (int k = 0; k < N; ++k) {
            (*current_add)[k] = current->test(k) != (*a)[depth][k];
        }
        result = binary_solve_explore(a, b, depth-1, current_add);
        delete current_add;
        return result;
    }
}

/**
 * Solve SLAE by trying solutions via backtracking.
 *
 * @param a: the coefficient matrix;
 * @param b: the resulting vector;
 *
 * @return if there is a solution.
 */
template <size_t N>
bool
binary_solve_recursive(const vector<mm_bitset<N>>& a, const mm_bitset<N>& b)
{
    uint_least64_t depth = a.size()-1;
    mm_bitset<N>* current = new mm_bitset<N>();
    current->reset();
    bool result = binary_solve_explore(&a, &b, depth, current);
    delete current;
    return result;
}

//=============================================================================

/**
 * TODO
 */
template <size_t N>
bool
gauss_solve_randomized(vector<mm_vector_with_properties<N>> a, mm_vector_with_properties<N> b)
{
    return true;
}

//=============================================================================

/**
 * Compare function to sort bitsets lexicographically.
 *
 * @param N: bitset size;
 */
template <size_t N>
bool
compare_lexical (const mm_bitset<N>& a, const mm_bitset<N>& b)
{
    for (int i = 0; i < N; ++i) {
        if (a[i] < b[i]) {
            return true;
        } else if (a[i] > b[i]) {
            return false;
        }
    }
    return true;
}

/**
 * Compare function to sort bitsets by number of 1-bits.
 *
 * @param N: bitset size;
 */
template <size_t N>
bool
compare_count (const mm_bitset<N>& a, const mm_bitset<N>& b)
{
    return (a.count() < b.count());
}

/**
 * Compare function to sort bitsets by number of bits,
 * and if it is the same, lexicographically.
 *
 * @param N: bitset size;
 */
template <size_t N>
bool
compare_combined (const mm_bitset<N>& a, const mm_bitset<N>& b)
{
    if (a.count() < b.count()) {
        return true;
    } else if (a.count() > b.count()) {
        return false;
    } else {
        for (int i = 0; i < N; ++i) {
            if (a[i] > b[i]) {
                return true;
            } else if (a[i] < b[i]) {
                return false;
            }
        }
        return true;
    }
}

//=============================================================================

/**
 * Presolve data for Gaussian elimination variant for solving SLAE.
 *
 * @param N: size of result and variable vectors.
 */
template <size_t N>
class Gauss_WP_Presolve_Data {
public:
    vector<mm_vector_with_properties<N>> am; /// Transformed Matrix.
    vector<int> imi;
    map<int, mm_vector_with_properties<N>> cache; /// cache with sums of vectors
    mm_vector_with_properties<N>* memo_array; /// cache with sums of vectors
    bool* memo_status; /// cache status

    Gauss_WP_Presolve_Data(int f_count);
    ~Gauss_WP_Presolve_Data();
};

/**
 * Constructor.
 *
 * @param f_count:  number of vectors in matrix.
 */
template <size_t N>
Gauss_WP_Presolve_Data<N>::
Gauss_WP_Presolve_Data(int f_count)
{
    memo_array = new mm_vector_with_properties<N>[power(2, f_count)];
    memo_status = new bool[power(2, f_count)];
}

/**
 * Destructor.
 */
template <size_t N>
Gauss_WP_Presolve_Data<N>::
~Gauss_WP_Presolve_Data()
{
    delete [] memo_array;
    delete [] memo_status;
}

/**
 * Collect operoationsdata for Gaussian elimination variant for solving SLAE.
 *
 * @param N: size of result and variable vectors.
 *
 * @param a: the coefficient matrix;
 * @param p: variable to store presolve data.
 */
template <size_t N>
bool
gauss_wp_presolve(const vector<mm_vector_with_properties<N>>& a, Gauss_WP_Presolve_Data<N>& p)
{
    p.am = a;
    p.imi.clear();
    p.cache.clear();
    for (typename vector<mm_vector_with_properties<N>>::iterator i = p.am.begin(); i != p.am.end();) { // for all columns
        int k = 0;
        while (((k < N) && (!(*i).v[k])) || (find(p.imi.begin(), p.imi.end(), k) != p.imi.end())) {
            ++k;
        }
        if (k == N) { // linearly dependent
            i = p.am.erase(i);
            return false;
        } else { // make 0's all items in a row k except for i'th column
            for (typename vector<mm_vector_with_properties<N>>::iterator j = p.am.begin(); j != i; ++j) {
                if ((*j).v[k]) {
                    (*j).v ^= (*i).v;
                    (*j).r ^= (*i).r;
                }
            }
            for (typename vector<mm_vector_with_properties<N>>::iterator j = i+1; j != p.am.end(); ++j) {
                if ((*j).v[k]) {
                    (*j).v ^= (*i).v;
                    (*j).r ^= (*i).r;
                }
            }
            p.imi.push_back(k);
            ++i;
        }
    }
    return true;
}

/**
 * Sovle SLAE by Gaussian elimination using precomputed data.
 *
 * @param N: size of result and variable vectors.
 *
 * @param p: the presolve data;
 * @param b: the resulting vector;
 *
 * @return if there is a solution.
 */
template <size_t N>
bool
gauss_wp_solve(Gauss_WP_Presolve_Data<N>& p, const mm_vector_with_properties<N>& b)
{
    mm_vector_with_properties<N> c;
    typename vector<mm_vector_with_properties<N>>::const_iterator j = p.am.begin();
    for (vector<int>::const_iterator i = p.imi.begin(); i != p.imi.end(); ++i) {
        if (b.v[*i]) {
            c.v ^= (*j).v;
        }
        ++j;
    }
    return (c.v == b.v);
}

/**
 * Sovle SLAE by Gaussian elimination using precomputed data using memoization.
 *
 * @param N: size of result and variable vectors.
 *
 * @param p: the presolve data;
 * @param b: the resulting vector;
 *
 * @return if there is a solution.
 */
template <size_t N>
bool
gauss_wp_solve_cached(Gauss_WP_Presolve_Data<N>& p, const mm_vector_with_properties<N>& b)
{
    /*
    c.r.reset();
    j = p.am.begin();
    for (vector<int>::const_iterator i = p.imi.begin(); i != p.imi.end(); ++i) {
        if (b.v[*i]) {
            c.r ^= (*j).r;
        }
        ++j;
    }
    */
    // first check number of ones
    /*
    if (c.r.count() != b.r_count) {
        return false;
    }
    */
    // then every bit
    /*
    if (c.r != b.r)
        return false;
    */
    int matrix_multiplier = 0;
    for (vector<int>::const_iterator i = p.imi.begin(); i != p.imi.end(); ++i) {
        matrix_multiplier <<= 1;
        matrix_multiplier += b.v[*i];
    }
    typename map<int, mm_vector_with_properties<N>>::const_iterator memo_result = p.cache.find(matrix_multiplier);
    if (memo_result != p.cache.end()) {
        return (memo_result->second.v == b.v);
    } else {
        mm_vector_with_properties<N> c;
        typename vector<mm_vector_with_properties<N>>::const_iterator j = p.am.begin();
        for (vector<int>::const_iterator i = p.imi.begin(); i != p.imi.end(); ++i) {
            if (b.v[*i]) {
                c.v ^= (*j).v;
            }
            ++j;
        }
        p.cache[matrix_multiplier] = c;
        return (c.v == b.v);
    }
    // first check number of ones
    /*
    if (c.v.count() != b.v_count) {
        return false;
    }
    */
}

/**
 * Sovle SLAE by Gaussian elimination using precomputed data using memoization.
 *
 * @param N: size of result and variable vectors.
 *
 * @param p: the presolve data;
 * @param b: the resulting vector;
 *
 * @return if there is a solution.
 */
template <size_t N>
bool
gauss_wp_solve_fast_cached(Gauss_WP_Presolve_Data<N>& p, const mm_vector_with_properties<N>& b)
{
    int matrix_multiplier = 0;
    for (vector<int>::const_iterator i = p.imi.begin(); i != p.imi.end(); ++i) {
        matrix_multiplier <<= 1;
        matrix_multiplier += b.v[*i];
    }
    if (p.memo_status[matrix_multiplier]) {
        return (p.memo_array[matrix_multiplier].v == b.v);
    } else {
        mm_vector_with_properties<N> c;
        typename vector<mm_vector_with_properties<N>>::const_iterator j = p.am.begin();
        for (vector<int>::const_iterator i = p.imi.begin(); i != p.imi.end(); ++i) {
            if (b.v[*i]) {
                c.v ^= (*j).v;
            }
            ++j;
        }
        p.memo_array[matrix_multiplier] = c;
        p.memo_status[matrix_multiplier] = true;
        return (c.v == b.v);
    }
}

//=============================================================================

#endif // SLAE_HPP_INCLUDED


// NormalFunction.cpp
//
// Some exact formulae.
//
// (C) Datasim Education BV 2011
//

#include <cmath>
#include <iostream>
using namespace std;
#include <boost/math/distributions/normal.hpp>
#include <boost/math/distributions.hpp> // For non-member functions of distributions
using namespace boost::math;

using namespace boost::math;

double N(double x)
{

	normal_distribution<> myNormal(0.0, 1.0);

	return cdf(myNormal, x);

}

double n(double x)
{

	normal_distribution<> myNormal(0.0, 1.0);

	return pdf(myNormal, x);

}

int main()
{

	double x = 2.0;
	cout << "pdf: " << n(x) << ", cdf: " << N(x) << endl;

	return 0;
}


#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <math.h>
#include <vector>
#include <algorithm>

#include <time.h>

#include <cryptoTools/Network/IOService.h>

#include <cryptoTools/Common/CLP.h>
#include <Eigen/Dense>

#include "aby3-ML/Regression.h"
#include "aby3-ML/LinearModelGen.h"

#include "aby3-ML/aby3ML.h"
#include "aby3-ML/PlainML.h"

using namespace std;
using namespace Eigen;
using namespace oc;
namespace aby3
{

	int logistic_plain_main(CLP& cmd)
	{
		auto N = cmd.getOr<int>("N", 10000);
		auto D = cmd.getOr<int>("D", 1000);
		auto B = cmd.getOr<int>("B", 128);
		auto IT = cmd.getOr<int>("I", 10000);
		auto testN = cmd.getOr<int>("testN", 1000);

		PRNG prng(toBlock(1));
		LogisticModelGen gen;

		eMatrix<double> model(D, 1);
		for (u64 i = 0; i < D; ++i)
		{
			model(i, 0) = prng.get<int>() % 10;
			std::cout << model(i, 0) << " ";
		}

		std::cout << std::endl;
		gen.setModel(model);

		eMatrix<double> train_data(N, D), train_label(N, 1);
		eMatrix<double> test_data(testN, D), test_label(testN, 1);
		gen.sample(train_data, train_label);
		gen.sample(test_data, test_label);

		std::cout << "training __" << std::endl;

		RegressionParam params;
		params.mBatchSize = B;
		params.mIterations = IT;
		params.mLearningRate = 1.0 / (1 << 3);
		PlainML engine;

		eMatrix<double> W2(D, 1);
		W2.setZero();
		//engine.mPrint = cmd.isSet("print");

		SGD_Logistic(params, engine, train_data, train_label, W2, &test_data, &test_label);

		for (u64 i = 0; i < D; ++i)
		{
			std::cout << i << " " << gen.mModel(i, 0) << " " << W2(i, 0) << std::endl;
		}

		return 0;
	}

	int logistic_main_3pc_sh(int N, int dim, int B, int IT, int testN, int pIdx, bool print, CLP& cmd, Session& chlPrev, Session& chlNext)
	{

		PRNG prng(toBlock(1));
		LogisticModelGen gen;

		eMatrix<double> model(dim, 1);
		for (u64 i = 0; i < std::min(dim, 10); ++i)
		{
			model(i, 0) = prng.get<int>() % 10;
		}
		gen.setModel(model);

		eMatrix<double> val_train_data(N, dim), val_train_label(N, 1), val_W2(dim, 1);
		eMatrix<double> val_test_data(testN, dim), val_test_label(testN, 1);
		gen.sample(val_train_data, val_train_label);
		gen.sample(val_test_data, val_test_label);
		val_W2.setZero();

		RegressionParam params;
		params.mBatchSize = B;
		params.mIterations = IT;
		params.mLearningRate = 1.0 / (1 << 3);

		const Decimal D = D16;
		aby3ML p;

		p.init(pIdx, chlPrev, chlNext, toBlock(pIdx));

		//p.mPrint = cmd.isSet("print");

		sf64Matrix<D> train_data, train_label, W2, test_data, test_label;

		if (pIdx == 0)
		{
			train_data = p.localInput<D>(val_train_data);
			train_label = p.localInput<D>(val_train_label);
			W2 = p.localInput<D>(val_W2);
			test_data = p.localInput<D>(val_test_data);
			test_label = p.localInput<D>(val_test_label);
		}
		else
		{
			train_data = p.remoteInput<D>(0);
			train_label = p.remoteInput<D>(0);
			W2 = p.remoteInput<D>(0);
			test_data = p.remoteInput<D>(0);
			test_label = p.remoteInput<D>(0);
		}

		p.mPreproNext.resetStats();
		p.mPreproPrev.resetStats();

		auto preStart = std::chrono::system_clock::now();

		p.preprocess((B + dim) * IT, D);

		double preBytes = p.mPreproNext.getTotalDataSent() + p.mPreproPrev.getTotalDataSent();

		p.mNext.resetStats();
		p.mPrev.resetStats();

		auto start = std::chrono::system_clock::now();

		if (cmd.isSet("noOnline") == false)
			SGD_Logistic(params, p, train_data, train_label, W2);
		//val_W2 = p.reveal(W2);

		auto end = std::chrono::system_clock::now();

		//engine.sync();
		auto now = std::chrono::system_clock::now();
		auto preSeconds = std::chrono::duration_cast<std::chrono::milliseconds>(start - preStart).count() / 1000.0;
		auto seconds = std::chrono::duration_cast<std::chrono::milliseconds>(now - start).count() / 1000.0;

		double bytes = p.mNext.getTotalDataSent() + p.mPrev.getTotalDataSent();

		if (print)
		{
			ostreamLock ooo(std::cout);
			ooo << "N: " << N << " D:" << dim << " B:" << B << " IT:" << IT << " => "
				<< (double(IT) / seconds) << "  iters/s  " << (bytes * 8 / 1024 / 2024) / seconds << " Mbps"
				<< " offline: " << (double(IT) / preSeconds) << "  iters/s  " << (preBytes * 8 / 1024 / 2024) / preSeconds << " Mbps" << std::endl;
		}

		auto w2Val = p.reveal(W2);

		if (print)
		{

			for (u64 i = 0; i < dim; ++i)
			{
				std::cout << i << " " << gen.mModel(i, 0) << " " << w2Val(i, 0) << std::endl;
			}
		}

		return 0;
	}

	int logistic_main_3pc_sh(oc::CLP & cmd)
	{

		auto N = cmd.getManyOr<int>("N", { 10000 });
		auto D = cmd.getManyOr<int>("D", { 1000 });
		auto B = cmd.getManyOr<int>("B", { 128 });
		auto IT = cmd.getManyOr<int>("I", { 10000 });
		auto testN = cmd.getOr("testN", 1000);

		IOService ios(cmd.isSet("p") ? 2 : 6);

		std::vector<std::thread> thrds;
		for (u64 i = 0; i < 3; ++i)
		{
			if (cmd.isSet("p") == false || cmd.get<int>("p") == i)
			{
				thrds.emplace_back(std::thread([i, N, D, B, IT, testN, &cmd, &ios]() {

					auto next = (i + 1) % 3;
					auto prev = (i + 2) % 3;
					auto cNameNext = std::to_string(std::min(i, next)) + std::to_string(std::max(i, next));
					auto cNamePrev = std::to_string(std::min(i, prev)) + std::to_string(std::max(i, prev));

					auto modeNext = i < next ? SessionMode::Server : SessionMode::Client;
					auto modePrev = i < prev ? SessionMode::Server : SessionMode::Client;

					auto portNext = 1212 + std::min(i, next);
					auto portPrev = 1212 + std::min(i, prev);

					Session epNext(ios, "127.0.0.1", portNext, modeNext, cNameNext);
					Session epPrev(ios, "127.0.0.1", portPrev, modePrev, cNamePrev);

					std::cout << "party " << i << " next " << portNext << " mode=server?:" << (modeNext == SessionMode::Server) << " name " << cNameNext << std::endl;
					std::cout << "party " << i << " prev " << portPrev << " mode=server?:" << (modePrev == SessionMode::Server) << " name " << cNamePrev << std::endl;
					auto chlNext = epNext.addChannel();
					auto chlPrev = epPrev.addChannel();

					chlNext.waitForConnection();
					chlPrev.waitForConnection();

					chlNext.send(i);
					chlPrev.send(i);
					u64 prevAct, nextAct;
					chlNext.recv(nextAct);
					chlPrev.recv(prevAct);

					if (next != nextAct)
						std::cout << " bad next party idx, act: " << nextAct << " exp: " << next << std::endl;
					if (prev != prevAct)
						std::cout << " bad prev party idx, act: " << prevAct << " exp: " << prev << std::endl;

					ostreamLock(std::cout) << "party " << i << " start" << std::endl;

					auto print = cmd.isSet("p") || i == 0;

					for (auto n : N)
					{
						for (auto d : D)
						{
							for (auto b : B)
							{
								for (auto it : IT)
								{
									logistic_main_3pc_sh(n, d, b, it, testN, i, print, cmd, epPrev, epNext);
								}
							}
						}
					}
					}));
			}
		}

		for (auto& t : thrds)
			t.join();

		return 0;
	}

}


/**
 * @file exponentialintegrator.cc
 * @brief NPDE homework ExponentialIntegrator code
 * @author Unknown, Oliver Rietmann
 * @date 04.04.2021
 * @copyright Developed at ETH Zurich
 */

#include "exponentialintegrator.h"

#include <Eigen/Core>
#include <cassert>
#include <unsupported/Eigen/MatrixFunctions>

namespace ExponentialIntegrator {

//! \brief Function $\phi$ used in the Exponential Euler
//! single step method for an autonomous ODE.
Eigen::MatrixXd phim(const Eigen::MatrixXd &Z) {
  int n = Z.cols();
  assert(n == Z.rows() && "Matrix must be square.");
  Eigen::MatrixXd C(2 * n, 2 * n);
  C << Z, Eigen::MatrixXd::Identity(n, n), Eigen::MatrixXd::Zero(n, 2 * n);
  return C.exp().block(0, n, n, n);
}

}  // namespace ExponentialIntegrator


#include <Eigen/Dense>
#include <iostream>
#include <cassert>

template <class matrix>
matrix gramschmidt(const matrix &A){
	unsigned n=A.cols();
	matrix Q=A;
	Q.col(0).normalize();

	for(int i=1;i<n;i++){
		Q.col(i)-= Q.leftCols(i)*(Q.leftCols(i).transpose() * A.col(i));
	//assert(Q.col(i).norm()<10e-16*A.col(i).norm() && "can't compute because A has lin dependant rows");
	Q.col(i).normalize();
	}
	return Q;

};

int main() {
	int n=5;
	Eigen::MatrixXd A;
	Eigen::MatrixXd Q;
	A = Eigen::MatrixXd::Random(n,n);
	Q=gramschmidt(A);
	std::cout << A<< std::endl;
	Q*=Q.transpose();
	/*for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			if (abs(Q(i,j))<10e-10)Q(i,j)=0;}}

*/
	std::cout << Q << std::endl;
	return 0;
}


#include <iostream>
#include <NTL/ZZ.h>
#include <chrono>
#include <vector>
#include <fstream>

/**
 * Compute fibonacci number for 2**i mod m. Using double and add method.
 */
NTL::ZZ fibonacci_mod(int i, const NTL::ZZ& m) {
    NTL::ZZ x;
    x = 0;
    NTL::ZZ y;
    y = 1;

    NTL::ZZ tmp;

    // add for the first 1
    tmp = x;
    x = y;
    y = (tmp + y) % m;

    while (true) {
        // double for each 0
        tmp = x;
        x = x*(2*y-x) % m;
        y = (tmp * tmp + y * y) % m;

        if (i == 1) {
            return x;
        }
        i -= 1;
    }
}

int main(int argc, char **argv) {
    // program takes two ints as arguments for the search bounds
    if (argc != 3)
        return 1;

    // get search bounds from arguments
    uint lower_bound = std::stoll(argv[1]);
    uint upper_bound = std::stoll(argv[2]);

    if (lower_bound < 2) {
        lower_bound = 2;
    }

    std::cout << "lower bound: " << lower_bound << std::endl;
    std::cout << "upper bound: " << upper_bound << std::endl;

    // start clock
    auto start = std::chrono::steady_clock::now();

    // vector of resulting pseudoprimes
    std::vector<NTL::ZZ> results;

    NTL::ZZ two;
    two = 2;

    for (uint i = lower_bound; i <= upper_bound; i++) {
        NTL::ZZ n; // n = 2**i-1
        n = 2;
        n = n << i-1;
        n -= 1;

        NTL::ZZ tmp;

        // fermat test
        tmp = NTL::PowerMod(two, n - 1, n);
        if (tmp != 1) {
            continue;
        }

        // lucas test
        tmp = fibonacci_mod(i, n);
        if (tmp != 0) {
            continue;
        }

        std::cout << "found: " << i << std::endl;
        results.push_back(n);
    }

    // stop clock
    auto stop = std::chrono::steady_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::seconds>(stop - start).count();
    std::cout << "duration (seconds): " << duration << std::endl;

    // write results to file
    std::ofstream f ("results.txt", std::ios::out | std::ios::trunc);
    f << "lower bound: " << lower_bound << std::endl;
    f << "upper bound: " << upper_bound << std::endl;
    for (const NTL::ZZ& p: results) {
        f << "found: " << p << std::endl;
    }
    f << "duration (seconds): " << duration << std::endl;
    f.close();

    return 0;
}


#include <Eigen/Core>
#include <iostream>
#include <LBFGSpp/LBFGS.h>

using Eigen::VectorXd;
using Eigen::MatrixXd;
using namespace LBFGSpp;

class Rosenbrock
{
private:
    int n;
public:
    Rosenbrock(int n_) : n(n_) {}
    double operator()(const VectorXd& x, VectorXd& grad)
    {
        double fx = 0.0;
        for(int i = 0; i < n; i += 2)
        {
            double t1 = 1.0 - x[i];
            double t2 = 10 * (x[i + 1] - x[i] * x[i]);
            grad[i + 1] = 20 * t2;
            grad[i]     = -2.0 * (x[i] * grad[i + 1] + t1);
            fx += t1 * t1 + t2 * t2;
        }
        assert( ! std::isnan(fx) );
        return fx;
    }
};

int main()
{
    LBFGSParam<double> param;
    LBFGSSolver<double, LineSearchBracketing> solver(param);

    for( int n=2; n <= 16; n += 2 )
    {
        std::cout << "n = " << n << std::endl;
        Rosenbrock fun(n);
        for( int test=0; test < 1024; test++ )
        {
            VectorXd x = VectorXd::Random(n);
            double fx;
            int niter = solver.minimize(fun, x, fx);

            assert( ( (x.array() - 1.0).abs() < 1e-4 ).all() );
        }
        std::cout << "Test passed!" << std::endl << std::endl;
    }

    return 0;
}


#include <iostream>
#include <vector>
#include <functional>
#include <cmath>
#include <Eigen/Dense>

#include "Derivative.h"

using Eigen::MatrixXd;
using Eigen::VectorXd;

using Eigen::Derivative;

using std::function;
using std::vector;

VectorXd LevenbergMarquardt(vector<Derivative> fs, VectorXd x){
    int x_size = x.size(), f_size = fs.size();

    vector< vector<Derivative> > Jac( f_size, vector<Derivative>(x_size) );

    for(int lf = 0;lf < f_size;lf++)
        for(int lx = 0;lx < x_size;lx++)
            Jac[lf][lx] = fs[lf].diffPartial(lx);

    // Set eps to be very larg
    double mu = 0.01, eps = 10000000;

    MatrixXd I = MatrixXd::Identity(x_size, x_size);

    for(int iter = 0;iter < 200;iter++){
        MatrixXd J(f_size, x_size);
        for(int lf = 0;lf < f_size;lf++)
            for(int lx = 0;lx < x_size;lx++)
                J(lf, lx) = Jac[lf][lx](x);

        VectorXd rx(f_size);
        for(int lf = 0;lf < f_size;lf++)
            rx[lf] = fs[lf](x);

        double rx_norm = rx.norm();

        VectorXd delta = (mu*I + J.transpose()*J).inverse()*J.transpose()*rx;
        x -= delta;

        if(eps < rx_norm)
            mu *= 2;
        else if(eps > rx_norm)
            mu /= 2;

        if(rx_norm < 0.0001 or delta.norm() < 0.0001)
            break;

        eps = rx_norm;
    }

    return x;
}

int main(){
    // Solve : xx + yy + zz = 2
    //          x + y + 2*z = 1

    Derivative x = Derivative::Variable(0),
               y = Derivative::Variable(1),
               z = Derivative::Variable(2);

    Derivative f1 = x*x + y*y + z*z - 2, f2 = x + y + 2*z - 1;

    VectorXd v(3);
    v << 1, 1, 1;

    std::cout << LevenbergMarquardt({f1, f2}, v).transpose() << std::endl;

    return 0;
}