#ifndef ROVER_LINEAR_REGRESSION_HPP
#define ROVER_LINEAR_REGRESSION_HPP
#include <tuple>
#include <vector>
#include <dlib/matrix.h>

namespace Rover {

  //! Models a trial using linear regression.
  /*!
    \tparam T The arithmetic type used for calculations.
  */
  template<typename T = double>
  class LinearRegression {
    public:

      //! The arithmetic type used for calculations.
      using Type = T;

      //! Learns a trial represented by a ScalarView.
      template<typename ScalarView>
      void learn(const ScalarView& view);

      //! Predicts the dependent variable for a set of arguments.
      template<typename Arguments>
      Type predict(const Arguments& args) const;

    private:
      dlib::matrix<Type> m_transformation;

      template<typename ScalarView>
      static dlib::matrix<Type> compute_transformation_vector(
        const ScalarView& trial);
  };

  template<typename T>
  template<typename ScalarView>
  void LinearRegression<T>::learn(const ScalarView& view) {
    m_transformation = compute_transformation_vector(view);
  }

  template<typename T>
  template<typename Arguments>
  typename LinearRegression<T>::Type LinearRegression<T>::predict(const
      Arguments& args) const {
    auto x = dlib::matrix<Type, 1>(args.size() + 1);
    x(0, 0) = static_cast<Type>(1.);
    std::copy(args.begin(), args.end(), x.begin() + 1);
    auto result = x * m_transformation;
    return result;
  }

  template<typename T>
  template<typename ScalarView>
  dlib::matrix<typename LinearRegression<T>::Type>
      LinearRegression<T>::compute_transformation_vector(const ScalarView&
      view) {
    auto x = dlib::matrix<Type>(view.size(), view[0].m_arguments.size() + 1);
    auto y = dlib::matrix<Type>(view.size(), 1);
    for(auto i = std::size_t(0); i < view.size(); ++i) {
      auto sample = view[i];
      x(i, 0) = static_cast<Type>(1.);
      std::copy(sample.m_arguments.begin(), sample.m_arguments.end(), x.begin()
        + i * x.nc() + 1);
      y(0, i) = sample.m_result;
    }
    auto xtr = dlib::trans(x);
    auto result = dlib::inv(xtr * x) * xtr * y;
    return result;
  }
}

#endif


// Haney's induction calculation benchmark.
//
// See: Scott W. Haney, Is C++ Fast Enough for Scientific Computing?
//      Computers in Physics Vol. 8 No. 6 (1994), p. 690
//
//      Arch D. Robison, C++ Gets Faster for Scientific Computing,
//      Computers in Physics Vol. 10 No. 5 (1996), p. 458
//

#include <blitz/vector.h>
#include <blitz/rand-uniform.h>
#include <blitz/benchext.h>
#ifdef BZ_HAVE_STD
#include <valarray>
#else
#include <valarray.h>
#endif

BZ_USING_NAMESPACE(blitz)

#ifndef M_PI
 #define M_PI   3.14159265358979323846
#endif

#ifdef BZ_FORTRAN_SYMBOLS_WITH_TRAILING_UNDERSCORES
#define vecopsf    vecopsf_
#define vecopsfo   vecopsfo_
#endif

extern "C"
{
	void vecopsf(float *li, const float *R, const float *w, const int &N,
	             const int& iters);
	void vecopsfo(float *li, const float *R, const float *w, const int &N,
	              const int& iters);
}

inline float sqr(float x)
{
	return x*x;
}

const float Mu0 = 4.0 * M_PI * 1.0e-7;

void HaneyCVersion(BenchmarkExt<int>& bench);
void HaneyFortranVersion(BenchmarkExt<int>& bench);
void HaneyBlitzVersion(BenchmarkExt<int>& bench);

int main()
{
	BenchmarkExt<int> bench("Haney Inductance Calculation", 3);

	bench.setRateDescription("Operations/s");

	bench.beginBenchmarking();

	HaneyCVersion(bench);
	HaneyFortranVersion(bench);
	HaneyBlitzVersion(bench);

	bench.endBenchmarking();

	bench.saveMatlabGraph("haney.m");

	return 0;
}

void initializeRandom(float* data, int length)
{
	Random<Uniform> unif(1.0, 2.0);
	for (int i=0; i < length; ++i)
		data[i] = unif.random();
}

void HaneyCVersion(BenchmarkExt<int>& bench)
{
	bench.beginImplementation("Inlined C");

	while (!bench.doneImplementationBenchmark()) {
		int length = bench.getParameter();
		long iters = bench.getIterations();

		cout << "length = " << length << " iters = " << iters << endl;

		float* li = new float[length];
		float* R = new float[length];
		float* w = new float[length];

		initializeRandom(li, length);
		initializeRandom(R, length);
		initializeRandom(w, length);

		// Tickle the cache
		for (int i=0; i < length; ++i)
			li[i] = R[i] + log(w[i]);

		bench.start();

		for (long j=0; j < iters; ++j) {
			for (int i=0; i < length; ++i) {
				li[i] = Mu0 * R[i] *
				        (0.5 * (1.0 + (1.0/24.0)
				                * sqr(w[i]/R[i])) * log(32.0 * sqr(R[i]/w[i]))
				         + 0.05 * sqr(w[i]/R[i]) - 0.85);
			}
		}

		bench.stop();

		// Subtract the loop overhead
		bench.startOverhead();

		for (long j=0; j < iters; ++j) {}

		bench.stopOverhead();

		delete [] li;
		delete [] w;
		delete [] R;
	}

	bench.endImplementation();
}

void HaneyFortranVersion(BenchmarkExt<int>& bench)
{
	bench.beginImplementation("Fortran");

	while (!bench.doneImplementationBenchmark()) {
		int length = bench.getParameter();
		int iters = (int)bench.getIterations();

		cout << "length = " << length << " iters = " << iters << endl;

		float* li = new float[length];
		float* R = new float[length];
		float* w = new float[length];

		initializeRandom(li, length);
		initializeRandom(R, length);
		initializeRandom(w, length);

		// Tickle
		int oneIter = 1;
		vecopsf(li, R, w, length, oneIter);

		// Time
		bench.start();
		vecopsf(li, R, w, length, iters);
		bench.stop();

		// Time overhead
		bench.startOverhead();
		vecopsfo(li, R, w, length, iters);
		bench.stopOverhead();

		delete [] li;
		delete [] w;
		delete [] R;
	}

	bench.endImplementation();
}

void HaneyBlitzVersion(BenchmarkExt<int>& bench)
{
	bench.beginImplementation("Blitz++");

	while (!bench.doneImplementationBenchmark()) {
		int length = bench.getParameter();
		int iters = (int)bench.getIterations();

		Vector<float> li(length), R(length), w(length);
		initializeRandom(li.data(), length);
		initializeRandom(R.data(), length);
		initializeRandom(w.data(), length);

		cout << "length = " << length << " iters = " << iters << endl;

		// Tickle
		li = w + log(R);

		// Time
		bench.start();
		for (long i=0; i < iters; ++i) {
#if defined(__GNUC__) && (__GNUC__ < 3)
			li = Mu0 * R * ( (0.5 + (0.5/24.0) * sqr(w/R) )
			                 * log(32.0 * sqr(R/w)) + 0.05 * sqr(w/R) - 0.85);
#else
			li = Mu0 * R * (0.5 * (1.0 + (1.0/24.0) * sqr(w/R))
			                * log(32.0 * sqr(R/w)) + 0.05 * sqr(w/R) - 0.85);
#endif
		}
		bench.stop();

		// Time overhead
		bench.startOverhead();
		for (long i=0; i < iters; ++i) {
		}
		bench.stopOverhead();
	}

	bench.endImplementation();
}


#include <opencv2/opencv.hpp>
#include <string>
#include <chrono>
#include <Eigen/Core>
#include <Eigen/Dense>

using namespace std;
using namespace cv;

string file_1 = "../LK1.png";
string file_2 = "../LK2.png";

class OpticalFlowTracker {
public:
    OpticalFlowTracker(
            const Mat &img1_,
            const Mat &img2_,
            const vector<KeyPoint> &kp1_,
            vector<KeyPoint> &kp2_,
            vector<bool> &success_,
            bool inverse_ = true, bool has_initial_ = false) :
            img1(img1_), img2(img2_), kp1(kp1_), kp2(kp2_), success(success_), inverse(inverse_),
            has_initial(has_initial_) {}

    void calculateOpticalFlow(const Range &range);

private:
    const Mat &img1;
    const Mat &img2;
    const vector<KeyPoint> &kp1;
    vector<KeyPoint> &kp2;
    vector<bool> &success;
    bool inverse = true;
    bool has_initial = false;
};

void OpticalFlowSingleLevel(
        const Mat &img1,
        const Mat &img2,
        const vector<KeyPoint> &kp1,
        vector<KeyPoint> &kp2,
        vector<bool> &success,
        bool inverse = false,
        bool has_initial_guess = false
);

void OpticalFlowMultiLevel(
        const Mat &img1,
        const Mat &img2,
        const vector<KeyPoint> &kp1,
        vector<KeyPoint> &kp2,
        vector<bool> &success,
        bool inverse = false
);

inline float GetPixelValue(const cv::Mat &img, float x, float y) {
    // boundary check
    if (x < 0) x = 0;
    if (y < 0) y = 0;
    if (x >= img.cols - 1) x = img.cols - 2;
    if (y >= img.rows - 1) y = img.rows - 2;

    float xx = x - floor(x);
    float yy = y - floor(y);
    int x_a1 = std::min(img.cols - 1, int(x) + 1);
    int y_a1 = std::min(img.rows - 1, int(y) + 1);

    return (1 - xx) * (1 - yy) * img.at<uchar>(y, x)
           + xx * (1 - yy) * img.at<uchar>(y, x_a1)
           + (1 - xx) * yy * img.at<uchar>(y_a1, x)
           + xx * yy * img.at<uchar>(y_a1, x_a1);
}

int main(int argc, char **argv) {

    // images, note they are CV_8UC1, not CV_8UC3
    Mat img1 = imread(file_1, 0);
    Mat img2 = imread(file_2, 0);

    // key points, using GFTT here.
    vector<KeyPoint> kp1;
    Ptr<GFTTDetector> detector = GFTTDetector::create(500, 0.01, 20); // maximum 500 keypoints
    detector->detect(img1, kp1);

    // now lets track these key points in the second image
    // first use single level LK in the validation picture
    vector<KeyPoint> kp2_single;
    vector<bool> success_single;
    OpticalFlowSingleLevel(img1, img2, kp1, kp2_single, success_single);

    // then test multi-level LK
    vector<KeyPoint> kp2_multi;
    vector<bool> success_multi;
    chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
    OpticalFlowMultiLevel(img1, img2, kp1, kp2_multi, success_multi, true);
    chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
    auto time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
    cout << "optical flow by gauss-newton: " << time_used.count() << endl;

    // use opencv's flow for validation
    vector<Point2f> pt1, pt2;
    for (auto &kp: kp1) pt1.push_back(kp.pt);
    vector<uchar> status;
    vector<float> error;
    t1 = chrono::steady_clock::now();
    cv::calcOpticalFlowPyrLK(img1, img2, pt1, pt2, status, error);
    t2 = chrono::steady_clock::now();
    time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
    cout << "optical flow by opencv: " << time_used.count() << endl;

    // plot the differences of those functions
    Mat img2_single;
    cv::cvtColor(img2, img2_single, COLOR_GRAY2BGR);
    for (int i = 0; i < kp2_single.size(); i++) {
        if (success_single[i]) {
            cv::circle(img2_single, kp2_single[i].pt, 2, cv::Scalar(0, 250, 0), 2);
            cv::line(img2_single, kp1[i].pt, kp2_single[i].pt, cv::Scalar(0, 250, 0));
        }
    }

    Mat img2_multi;
    cv::cvtColor(img2, img2_multi, COLOR_GRAY2BGR);
    for (int i = 0; i < kp2_multi.size(); i++) {
        if (success_multi[i]) {
            cv::circle(img2_multi, kp2_multi[i].pt, 2, cv::Scalar(0, 250, 0), 2);
            cv::line(img2_multi, kp1[i].pt, kp2_multi[i].pt, cv::Scalar(0, 250, 0));
        }
    }

    Mat img2_CV;
    cv::cvtColor(img2, img2_CV, COLOR_GRAY2BGR);
    for (int i = 0; i < pt2.size(); i++) {
        if (status[i]) {
            cv::circle(img2_CV, pt2[i], 2, cv::Scalar(0, 250, 0), 2);
            cv::line(img2_CV, pt1[i], pt2[i], cv::Scalar(0, 250, 0));
        }
    }

    cv::imshow("tracked single level", img2_single);
    cv::imshow("tracked multi level", img2_multi);
    cv::imshow("tracked by opencv", img2_CV);
    cv::waitKey(0);

    return 0;
}

void OpticalFlowSingleLevel(
        const Mat &img1,
        const Mat &img2,
        const vector<KeyPoint> &kp1,
        vector<KeyPoint> &kp2,
        vector<bool> &success,
        bool inverse, bool has_initial) {
    kp2.resize(kp1.size());
    success.resize(kp1.size());
    OpticalFlowTracker tracker(img1, img2, kp1, kp2, success, inverse, has_initial);
    parallel_for_(Range(0, kp1.size()),
                  std::bind(&OpticalFlowTracker::calculateOpticalFlow, &tracker, placeholders::_1));
}

void OpticalFlowTracker::calculateOpticalFlow(const Range &range) {
    // parameters
    int half_patch_size = 4;
    int iterations = 10;
    for (size_t i = range.start; i < range.end; i++) {
        auto kp = kp1[i];
        double dx = 0, dy = 0; // dx,dy need to be estimated
        if (has_initial) {
            dx = kp2[i].pt.x - kp.pt.x;
            dy = kp2[i].pt.y - kp.pt.y;
        }

        double cost = 0, lastCost = 0;
        bool succ = true; // indicate if this point succeeded

        // Gauss-Newton iterations
        Eigen::Matrix2d H = Eigen::Matrix2d::Zero();    // hessian
        Eigen::Vector2d b = Eigen::Vector2d::Zero();    // bias
        Eigen::Vector2d J;  // jacobian
        for (int iter = 0; iter < iterations; iter++) {
            if (inverse == false) {
                H = Eigen::Matrix2d::Zero();
                b = Eigen::Vector2d::Zero();
            } else {
                // only reset b
                b = Eigen::Vector2d::Zero();
            }

            cost = 0;

            // compute cost and jacobian
            for (int x = -half_patch_size; x < half_patch_size; x++)
                for (int y = -half_patch_size; y < half_patch_size; y++) {
                    double error = GetPixelValue(img1, kp.pt.x + x, kp.pt.y + y) -
                                   GetPixelValue(img2, kp.pt.x + x + dx, kp.pt.y + y + dy);;  // Jacobian
                    if (inverse == false) {
                        J = -1.0 * Eigen::Vector2d(
                                0.5 * (GetPixelValue(img2, kp.pt.x + dx + x + 1, kp.pt.y + dy + y) -
                                       GetPixelValue(img2, kp.pt.x + dx + x - 1, kp.pt.y + dy + y)),
                                0.5 * (GetPixelValue(img2, kp.pt.x + dx + x, kp.pt.y + dy + y + 1) -
                                       GetPixelValue(img2, kp.pt.x + dx + x, kp.pt.y + dy + y - 1))
                        );
                    } else if (iter == 0) {
                        // in inverse mode, J keeps same for all iterations
                        // NOTE this J does not change when dx, dy is updated, so we can store it and only compute error
                        J = -1.0 * Eigen::Vector2d(
                                0.5 * (GetPixelValue(img1, kp.pt.x + x + 1, kp.pt.y + y) -
                                       GetPixelValue(img1, kp.pt.x + x - 1, kp.pt.y + y)),
                                0.5 * (GetPixelValue(img1, kp.pt.x + x, kp.pt.y + y + 1) -
                                       GetPixelValue(img1, kp.pt.x + x, kp.pt.y + y - 1))
                        );
                    }
                    // compute H, b and set cost;
                    b += -error * J;
                    cost += error * error;
                    if (inverse == false || iter == 0) {
                        // also update H
                        H += J * J.transpose();
                    }
                }

            // compute update
            Eigen::Vector2d update = H.ldlt().solve(b);

            if (std::isnan(update[0])) {
                // sometimes occurred when we have a black or white patch and H is irreversible
                cout << "update is nan" << endl;
                succ = false;
                break;
            }

            if (iter > 0 && cost > lastCost) {
                break;
            }

            // update dx, dy
            dx += update[0];
            dy += update[1];
            lastCost = cost;
            succ = true;

            if (update.norm() < 1e-2) {
                // converge
                break;
            }
        }

        success[i] = succ;

        // set kp2
        kp2[i].pt = kp.pt + Point2f(dx, dy);
    }
}

void OpticalFlowMultiLevel(
        const Mat &img1,
        const Mat &img2,
        const vector<KeyPoint> &kp1,
        vector<KeyPoint> &kp2,
        vector<bool> &success,
        bool inverse) {

    // parameters
    int pyramids = 4;
    double pyramid_scale = 0.5;
    double scales[] = {1.0, 0.5, 0.25, 0.125};

    // create pyramids
    chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
    vector<Mat> pyr1, pyr2; // image pyramids
    for (int i = 0; i < pyramids; i++) {
        if (i == 0) {
            pyr1.push_back(img1);
            pyr2.push_back(img2);
        } else {
            Mat img1_pyr, img2_pyr;
            cv::resize(pyr1[i - 1], img1_pyr,
                       cv::Size(pyr1[i - 1].cols * pyramid_scale, pyr1[i - 1].rows * pyramid_scale));
            cv::resize(pyr2[i - 1], img2_pyr,
                       cv::Size(pyr2[i - 1].cols * pyramid_scale, pyr2[i - 1].rows * pyramid_scale));
            pyr1.push_back(img1_pyr);
            pyr2.push_back(img2_pyr);
        }
    }
    chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
    auto time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
    cout << "build pyramid time: " << time_used.count() << endl;

    // coarse-to-fine LK tracking in pyramids
    vector<KeyPoint> kp1_pyr, kp2_pyr;
    for (auto &kp:kp1) {
        auto kp_top = kp;
        kp_top.pt *= scales[pyramids - 1];
        kp1_pyr.push_back(kp_top);
        kp2_pyr.push_back(kp_top);
    }

    for (int level = pyramids - 1; level >= 0; level--) {
        // from coarse to fine
        success.clear();
        t1 = chrono::steady_clock::now();
        OpticalFlowSingleLevel(pyr1[level], pyr2[level], kp1_pyr, kp2_pyr, success, inverse, true);
        t2 = chrono::steady_clock::now();
        auto time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);
        cout << "track pyr " << level << " cost time: " << time_used.count() << endl;

        if (level > 0) {
            for (auto &kp: kp1_pyr)
                kp.pt /= pyramid_scale;
            for (auto &kp: kp2_pyr)
                kp.pt /= pyramid_scale;
        }
    }

    for (auto &kp: kp2_pyr)
        kp2.push_back(kp);
}


#include <iostream>
#include <unordered_map>

#include <NTL/ZZ.h>

#include "RSA.h"

using namespace std;
using namespace NTL;

void usage(char *progname) {
	cout << "This program returns the private exponent `d` of the "
		"public key <n, e2> given the secret key <n, e', d'>."
		<< endl;
	cout << "Usage: " << progname << " n e' d' e" << endl;
}

int main(int argc, char *argv[]) {
	if (argc != 5) { usage(argv[0]); return 3; }

	ZZ n, e1, d1, e2, d2, phi;
	n = conv<ZZ>(argv[1]);
	e1 = conv<ZZ>(argv[2]);
	d1 = conv<ZZ>(argv[3]);
	e2 = conv<ZZ>(argv[4]);

	RSAkey k1 = RSAkey(n, e1, d1);
	phi = k1.get_param("phi");

	// d2 = e2^(-1) (mod phi)
	InvMod(d2, e2, phi);

	RSAkey k2 = RSAkey(n, e2, d2);
	cout << k2.get_param("d") << endl;
}


// Copyright PinaPL
//
// cell.cpp
// PinaPL
//
#include <math.h>
#include <Eigen/Dense>
#include <vector>
#include "weights.hpp"
#include "cell.hpp"
#include "functions.hpp"

Cell::Cell(Weights* weights) {
    this->weights = weights;
    this->reset();
}

void Cell::compute(Eigen::MatrixXd* input) {
/*    this->forget_gate_out =
        (this->weights->weight_in_forget_gate * input
        + this->weights->weight_st_forget_gate * previous_cell_state)
        .unaryExpr(&sigmoid); */
    this->inputs.push_back((*input));

    this->input_gate_out.push_back(
        (this->weights->weight_in_input_gate * (*input)
        + this->weights->weight_st_input_gate * this->cell_out.back()
        + this->weights->bias_input_gate).unaryExpr(&sigmoid));

    this->input_block_out.push_back(
        (this->weights->weight_in_input_block * (*input)
        + this->weights->weight_st_input_block * this->cell_out.back()
        + this->weights->bias_input_block).unaryExpr(&tanhyp));

    this->output_gate_out.push_back(
        (this->weights->weight_in_output_gate * (*input)
        + this->weights->weight_st_output_gate * this->cell_out.back()
        + this->weights->bias_output_gate).unaryExpr(&sigmoid));

    this->cell_state.push_back(
        (this->cell_state.back()
        + this->input_gate_out.back()
        .cwiseProduct(this->input_block_out.back())));

    this->cell_out.push_back(
        this->cell_state.back().unaryExpr(&tanhyp)
        .cwiseProduct(this->output_gate_out.back()));
}

Eigen::MatrixXd Cell::compute_gate_gradient(Eigen::MatrixXd* deltas, int time) {
    int output_size = this->weights->output_size;
    // Computes dy(t)
    delta_cell_out.push_back(
        (*deltas)
        + this->weights->weight_st_input_block * delta_input_block_out.back()
        + this->weights->weight_st_input_gate * delta_input_gate_out.back()
//      + this->weights->weight_st_forget_gate * delta_forget_gate_out.back()
        + this->weights->weight_st_output_gate * delta_output_gate_out.back() );

    // Computes do(t)
    delta_output_gate_out.push_back(delta_cell_out.back()
        .cwiseProduct(cell_state.at(time + 1).unaryExpr(&tanhyp))
        .cwiseProduct(output_gate_out.at(time + 1).cwiseProduct(
            Eigen::MatrixXd::Ones(output_size, 1)
            - output_gate_out.at(time + 1))));

    // Computes dc(t)
    delta_cell_state.push_back(
        delta_cell_out.back()
        .cwiseProduct(output_gate_out.at(time + 1))
        .cwiseProduct(cell_state.at(time + 1).unaryExpr(&tanh_derivative)));

    // Computes di(t)
    delta_input_gate_out.push_back(
        delta_cell_state.back()
        .cwiseProduct(input_block_out.at(time + 1))
        .cwiseProduct(input_gate_out.at(time + 1).cwiseProduct(
            Eigen::MatrixXd::Ones(output_size, 1)
            - input_gate_out.at(time + 1))) );

    // Computes dz(t)
    delta_input_block_out.push_back(
        delta_cell_state.back()
        .cwiseProduct(input_gate_out.at(time + 1))
        .cwiseProduct(input_block_out.at(time + 1).cwiseProduct(
            Eigen::MatrixXd::Ones(output_size, 1)
            - input_block_out.at(time + 1))) );

    // Computes dx(t)
    Eigen::MatrixXd delta_input =
    this->weights->weight_in_input_block.transpose()
      * delta_input_block_out.back()
    + this->weights->weight_in_input_gate.transpose()
      * delta_input_gate_out.back()
//  + this->weights->weight_in_input_block.transpose()
//    * delta_input_block_out.back()
    + this->weights->weight_in_output_gate.transpose()
      * delta_output_gate_out.back();

    return delta_input;
}

void Cell::compute_weight_gradient() {
    int last_item_index = this->inputs.size() - 1;
    // Computes dW
    for (int t = 0; t < last_item_index + 1; ++t) {
        // Computes dWz
        this->weights->delta_weight_in_input_block +=
            delta_input_block_out.at(last_item_index - t + 1)
            * inputs.at(t).transpose();
/*
        std::cout << "computeWG : "<< t << std::endl;
        std::cout << delta_input_gate_out.at(last_item_index - t + 1)
        << std::endl;
        std::cout << " * " << std::endl;
        std::cout << inputs.at(t).transpose() << std::endl;
        std::cout << " = " << std::endl;
        std::cout << delta_input_gate_out.at(last_item_index - t + 1)
        * inputs.at(t).transpose() << std::endl;
*/
        // Computes dWi
        this->weights->delta_weight_in_input_gate +=
            delta_input_gate_out.at(last_item_index - t + 1)
            * inputs.at(t).transpose();

        // Computes dWf
        /*
        this->weights->delta_weight_in_input_block +=
            delta_input_block_out.at(last_item_index - t + 1)
            * inputs.at(t).transpose(); */

        // Computes dWo
        this->weights->delta_weight_in_output_gate +=
            delta_output_gate_out.at(last_item_index - t + 1)
            * inputs.at(t).transpose();
    }
    // Computes dR
    for (int t = 0; t < last_item_index; ++t) {
        // Computes dRz
        this->weights->delta_weight_st_input_block +=
            delta_input_block_out.at(last_item_index - t)
            * cell_out.at(t + 1).transpose();

        // Computes dRi
        this->weights->delta_weight_st_input_gate +=
            delta_input_gate_out.at(last_item_index - t)
            * cell_out.at(t + 1).transpose();

        // Computes dRo
        this->weights->delta_weight_st_output_gate +=
            delta_output_gate_out.at(last_item_index - t)
            * cell_out.at(t + 1).transpose();
    }
    // Computes dB
    for (int t = 0; t < last_item_index + 1; ++t) {
        // Computes dBz
        this->weights->delta_bias_input_block +=
            delta_input_block_out.at(last_item_index - t + 1);
        // Computes dBi
        this->weights->delta_bias_input_gate +=
            delta_input_gate_out.at(last_item_index - t + 1);
        // Computes dBo
        this->weights->delta_bias_output_gate +=
            delta_output_gate_out.at(last_item_index - t + 1);
    }
}

void Cell::update_weights(double lambda) {
    this->weights->apply_gradient(lambda);
}

void Cell::reset() {
    int output_size = this->weights->output_size;

    this->inputs.clear();
    this->input_gate_out.clear();
    this->input_block_out.clear();
    this->output_gate_out.clear();
    this->cell_state.clear();
    this->cell_out.clear();

    this->delta_cell_out.clear();
    this->delta_output_gate_out.clear();
    this->delta_cell_state.clear();
    this->delta_input_gate_out.clear();
    this->delta_input_block_out.clear();

    this->input_gate_out.push_back(
        Eigen::MatrixXd::Zero(output_size, 1));
    this->input_block_out.push_back(
        Eigen::MatrixXd::Zero(output_size, 1));
    this->output_gate_out.push_back(
        Eigen::MatrixXd::Zero(output_size, 1));
    this->cell_state.push_back(
        Eigen::MatrixXd::Zero(output_size, 1));
    this->cell_out.push_back(
        Eigen::MatrixXd::Zero(output_size, 1));

    this->delta_cell_out.push_back(
        Eigen::MatrixXd::Zero(output_size, 1));
    this->delta_output_gate_out.push_back(
        Eigen::MatrixXd::Zero(output_size, 1));
    this->delta_cell_state.push_back(
        Eigen::MatrixXd::Zero(output_size, 1));
    this->delta_input_gate_out.push_back(
        Eigen::MatrixXd::Zero(output_size, 1));
    this->delta_input_block_out.push_back(
        Eigen::MatrixXd::Zero(output_size, 1));
}


// boost\math\distributions\binomial.hpp

// Copyright John Maddock 2006.
// Copyright Paul A. Bristow 2007.

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

// http://en.wikipedia.org/wiki/binomial_distribution

// Binomial distribution is the discrete probability distribution of
// the number (k) of successes, in a sequence of
// n independent (yes or no, success or failure) Bernoulli trials.

// It expresses the probability of a number of events occurring in a fixed time
// if these events occur with a known average rate (probability of success),
// and are independent of the time since the last event.

// The number of cars that pass through a certain point on a road during a given period of time.
// The number of spelling mistakes a secretary makes while typing a single page.
// The number of phone calls at a call center per minute.
// The number of times a web server is accessed per minute.
// The number of light bulbs that burn out in a certain amount of time.
// The number of roadkill found per unit length of road

// http://en.wikipedia.org/wiki/binomial_distribution

// Given a sample of N measured values k[i],
// we wish to estimate the value of the parameter x (mean)
// of the binomial population from which the sample was drawn.
// To calculate the maximum likelihood value = 1/N sum i = 1 to N of k[i]

// Also may want a function for EXACTLY k.

// And probability that there are EXACTLY k occurrences is
// exp(-x) * pow(x, k) / factorial(k)
// where x is expected occurrences (mean) during the given interval.
// For example, if events occur, on average, every 4 min,
// and we are interested in number of events occurring in 10 min,
// then x = 10/4 = 2.5

// http://www.itl.nist.gov/div898/handbook/eda/section3/eda366i.htm

// The binomial distribution is used when there are
// exactly two mutually exclusive outcomes of a trial.
// These outcomes are appropriately labeled "success" and "failure".
// The binomial distribution is used to obtain
// the probability of observing x successes in N trials,
// with the probability of success on a single trial denoted by p.
// The binomial distribution assumes that p is fixed for all trials.

// P(x, p, n) = n!/(x! * (n-x)!) * p^x * (1-p)^(n-x)

// http://mathworld.wolfram.com/BinomialCoefficient.html

// The binomial coefficient (n; k) is the number of ways of picking
// k unordered outcomes from n possibilities,
// also known as a combination or combinatorial number.
// The symbols _nC_k and (n; k) are used to denote a binomial coefficient,
// and are sometimes read as "n choose k."
// (n; k) therefore gives the number of k-subsets  possible out of a set of n distinct items.

// For example:
//  The 2-subsets of {1,2,3,4} are the six pairs {1,2}, {1,3}, {1,4}, {2,3}, {2,4}, and {3,4}, so (4; 2)==6.

// http://functions.wolfram.com/GammaBetaErf/Binomial/ for evaluation.

// But note that the binomial distribution
// (like others including the poisson, negative binomial & Bernoulli)
// is strictly defined as a discrete function: only integral values of k are envisaged.
// However because of the method of calculation using a continuous gamma function,
// it is convenient to treat it as if a continous function,
// and permit non-integral values of k.
// To enforce the strict mathematical model, users should use floor or ceil functions
// on k outside this function to ensure that k is integral.

#ifndef BOOST_MATH_SPECIAL_BINOMIAL_HPP
#define BOOST_MATH_SPECIAL_BINOMIAL_HPP

#include <boost/math/distributions/fwd.hpp>
#include <boost/math/special_functions/beta.hpp> // for incomplete beta.
#include <boost/math/distributions/complement.hpp> // complements
#include <boost/math/distributions/detail/common_error_handling.hpp> // error checks
#include <boost/math/distributions/detail/inv_discrete_quantile.hpp> // error checks
#include <boost/math/special_functions/fpclassify.hpp> // isnan.
#include <boost/math/tools/roots.hpp> // for root finding.

#include <utility>

namespace boost
{
  namespace math
  {

     template <class RealType, class Policy>
     class binomial_distribution;

     namespace binomial_detail{
        // common error checking routines for binomial distribution functions:
        template <class RealType, class Policy>
        inline bool check_N(const char* function, const RealType& N, RealType* result, const Policy& pol)
        {
           if((N < 0) || !(boost::math::isfinite)(N))
           {
               *result = policies::raise_domain_error<RealType>(
                  function,
                  "Number of Trials argument is %1%, but must be >= 0 !", N, pol);
               return false;
           }
           return true;
        }
        template <class RealType, class Policy>
        inline bool check_success_fraction(const char* function, const RealType& p, RealType* result, const Policy& pol)
        {
           if((p < 0) || (p > 1) || !(boost::math::isfinite)(p))
           {
               *result = policies::raise_domain_error<RealType>(
                  function,
                  "Success fraction argument is %1%, but must be >= 0 and <= 1 !", p, pol);
               return false;
           }
           return true;
        }
        template <class RealType, class Policy>
        inline bool check_dist(const char* function, const RealType& N, const RealType& p, RealType* result, const Policy& pol)
        {
           return check_success_fraction(
              function, p, result, pol)
              && check_N(
               function, N, result, pol);
        }
        template <class RealType, class Policy>
        inline bool check_dist_and_k(const char* function, const RealType& N, const RealType& p, RealType k, RealType* result, const Policy& pol)
        {
           if(check_dist(function, N, p, result, pol) == false)
              return false;
           if((k < 0) || !(boost::math::isfinite)(k))
           {
               *result = policies::raise_domain_error<RealType>(
                  function,
                  "Number of Successes argument is %1%, but must be >= 0 !", k, pol);
               return false;
           }
           if(k > N)
           {
               *result = policies::raise_domain_error<RealType>(
                  function,
                  "Number of Successes argument is %1%, but must be <= Number of Trials !", k, pol);
               return false;
           }
           return true;
        }
        template <class RealType, class Policy>
        inline bool check_dist_and_prob(const char* function, const RealType& N, RealType p, RealType prob, RealType* result, const Policy& pol)
        {
           if(check_dist(function, N, p, result, pol) && detail::check_probability(function, prob, result, pol) == false)
              return false;
           return true;
        }

         template <class T, class Policy>
         T inverse_binomial_cornish_fisher(T n, T sf, T p, T q, const Policy& pol)
         {
            BOOST_MATH_STD_USING
            // mean:
            T m = n * sf;
            // standard deviation:
            T sigma = sqrt(n * sf * (1 - sf));
            // skewness
            T sk = (1 - 2 * sf) / sigma;
            // kurtosis:
            // T k = (1 - 6 * sf * (1 - sf) ) / (n * sf * (1 - sf));
            // Get the inverse of a std normal distribution:
            T x = boost::math::erfc_inv(p > q ? 2 * q : 2 * p, pol) * constants::root_two<T>();
            // Set the sign:
            if(p < 0.5)
               x = -x;
            T x2 = x * x;
            // w is correction term due to skewness
            T w = x + sk * (x2 - 1) / 6;
            /*
            // Add on correction due to kurtosis.
            // Disabled for now, seems to make things worse?
            //
            if(n >= 10)
               w += k * x * (x2 - 3) / 24 + sk * sk * x * (2 * x2 - 5) / -36;
               */
            w = m + sigma * w;
            if(w < tools::min_value<T>())
               return sqrt(tools::min_value<T>());
            if(w > n)
               return n;
            return w;
         }

      template <class RealType, class Policy>
      RealType quantile_imp(const binomial_distribution<RealType, Policy>& dist, const RealType& p, const RealType& q)
      { // Quantile or Percent Point Binomial function.
        // Return the number of expected successes k,
        // for a given probability p.
        //
        // Error checks:
        BOOST_MATH_STD_USING  // ADL of std names
        RealType result;
        RealType trials = dist.trials();
        RealType success_fraction = dist.success_fraction();
        if(false == binomial_detail::check_dist_and_prob(
           "boost::math::quantile(binomial_distribution<%1%> const&, %1%)",
           trials,
           success_fraction,
           p,
           &result, Policy()))
        {
           return result;
        }

        // Special cases:
        //
        if(p == 0)
        {  // There may actually be no answer to this question,
           // since the probability of zero successes may be non-zero,
           // but zero is the best we can do:
           return 0;
        }
        if(p == 1)
        {  // Probability of n or fewer successes is always one,
           // so n is the most sensible answer here:
           return trials;
        }
        if (p <= pow(1 - success_fraction, trials))
        { // p <= pdf(dist, 0) == cdf(dist, 0)
          return 0; // So the only reasonable result is zero.
        } // And root finder would fail otherwise.

        // Solve for quantile numerically:
        //
        RealType guess = binomial_detail::inverse_binomial_cornish_fisher(trials, success_fraction, p, q, Policy());
        RealType factor = 8;
        if(trials > 100)
           factor = 1.01f; // guess is pretty accurate
        else if((trials > 10) && (trials - 1 > guess) && (guess > 3))
           factor = 1.15f; // less accurate but OK.
        else if(trials < 10)
        {
           // pretty inaccurate guess in this area:
           if(guess > trials / 64)
           {
              guess = trials / 4;
              factor = 2;
           }
           else
              guess = trials / 1024;
        }
        else
           factor = 2; // trials largish, but in far tails.

        typedef typename Policy::discrete_quantile_type discrete_quantile_type;
        boost::uintmax_t max_iter = policies::get_max_root_iterations<Policy>();
        return detail::inverse_discrete_quantile(
            dist,
            p,
            q,
            guess,
            factor,
            RealType(1),
            discrete_quantile_type(),
            max_iter);
      } // quantile

     }

    template <class RealType = double, class Policy = policies::policy<> >
    class binomial_distribution
    {
    public:
      typedef RealType value_type;
      typedef Policy policy_type;

      binomial_distribution(RealType n = 1, RealType p = 0.5) : m_n(n), m_p(p)
      { // Default n = 1 is the Bernoulli distribution
        // with equal probability of 'heads' or 'tails.
         RealType r;
         binomial_detail::check_dist(
            "boost::math::binomial_distribution<%1%>::binomial_distribution",
            m_n,
            m_p,
            &r, Policy());
      } // binomial_distribution constructor.

      RealType success_fraction() const
      { // Probability.
        return m_p;
      }
      RealType trials() const
      { // Total number of trials.
        return m_n;
      }

      enum interval_type{
         clopper_pearson_exact_interval,
         jeffreys_prior_interval
      };

      //
      // Estimation of the success fraction parameter.
      // The best estimate is actually simply successes/trials,
      // these functions are used
      // to obtain confidence intervals for the success fraction.
      //
      static RealType find_lower_bound_on_p(
         RealType trials,
         RealType successes,
         RealType probability,
         interval_type t = clopper_pearson_exact_interval)
      {
        static const char* function = "boost::math::binomial_distribution<%1%>::find_lower_bound_on_p";
        // Error checks:
        RealType result;
        if(false == binomial_detail::check_dist_and_k(
           function, trials, RealType(0), successes, &result, Policy())
            &&
           binomial_detail::check_dist_and_prob(
           function, trials, RealType(0), probability, &result, Policy()))
        { return result; }

        if(successes == 0)
           return 0;

        // NOTE!!! The Clopper Pearson formula uses "successes" not
        // "successes+1" as usual to get the lower bound,
        // see http://www.itl.nist.gov/div898/handbook/prc/section2/prc241.htm
        return (t == clopper_pearson_exact_interval) ? ibeta_inv(successes, trials - successes + 1, probability, static_cast<RealType*>(0), Policy())
           : ibeta_inv(successes + 0.5f, trials - successes + 0.5f, probability, static_cast<RealType*>(0), Policy());
      }
      static RealType find_upper_bound_on_p(
         RealType trials,
         RealType successes,
         RealType probability,
         interval_type t = clopper_pearson_exact_interval)
      {
        static const char* function = "boost::math::binomial_distribution<%1%>::find_upper_bound_on_p";
        // Error checks:
        RealType result;
        if(false == binomial_detail::check_dist_and_k(
           function, trials, RealType(0), successes, &result, Policy())
            &&
           binomial_detail::check_dist_and_prob(
           function, trials, RealType(0), probability, &result, Policy()))
        { return result; }

        if(trials == successes)
           return 1;

        return (t == clopper_pearson_exact_interval) ? ibetac_inv(successes + 1, trials - successes, probability, static_cast<RealType*>(0), Policy())
           : ibetac_inv(successes + 0.5f, trials - successes + 0.5f, probability, static_cast<RealType*>(0), Policy());
      }
      // Estimate number of trials parameter:
      //
      // "How many trials do I need to be P% sure of seeing k events?"
      //    or
      // "How many trials can I have to be P% sure of seeing fewer than k events?"
      //
      static RealType find_minimum_number_of_trials(
         RealType k,     // number of events
         RealType p,     // success fraction
         RealType alpha) // risk level
      {
        static const char* function = "boost::math::binomial_distribution<%1%>::find_minimum_number_of_trials";
        // Error checks:
        RealType result;
        if(false == binomial_detail::check_dist_and_k(
           function, k, p, k, &result, Policy())
            &&
           binomial_detail::check_dist_and_prob(
           function, k, p, alpha, &result, Policy()))
        { return result; }

        result = ibetac_invb(k + 1, p, alpha, Policy());  // returns n - k
        return result + k;
      }

      static RealType find_maximum_number_of_trials(
         RealType k,     // number of events
         RealType p,     // success fraction
         RealType alpha) // risk level
      {
        static const char* function = "boost::math::binomial_distribution<%1%>::find_maximum_number_of_trials";
        // Error checks:
        RealType result;
        if(false == binomial_detail::check_dist_and_k(
           function, k, p, k, &result, Policy())
            &&
           binomial_detail::check_dist_and_prob(
           function, k, p, alpha, &result, Policy()))
        { return result; }

        result = ibeta_invb(k + 1, p, alpha, Policy());  // returns n - k
        return result + k;
      }

    private:
        RealType m_n; // Not sure if this shouldn't be an int?
        RealType m_p; // success_fraction
      }; // template <class RealType, class Policy> class binomial_distribution

      typedef binomial_distribution<> binomial;
      // typedef binomial_distribution<double> binomial;
      // IS now included since no longer a name clash with function binomial.
      //typedef binomial_distribution<double> binomial; // Reserved name of type double.

      template <class RealType, class Policy>
      const std::pair<RealType, RealType> range(const binomial_distribution<RealType, Policy>& dist)
      { // Range of permissible values for random variable k.
        using boost::math::tools::max_value;
        return std::pair<RealType, RealType>(static_cast<RealType>(0), dist.trials());
      }

      template <class RealType, class Policy>
      const std::pair<RealType, RealType> support(const binomial_distribution<RealType, Policy>& dist)
      { // Range of supported values for random variable k.
        // This is range where cdf rises from 0 to 1, and outside it, the pdf is zero.
        return std::pair<RealType, RealType>(0,  dist.trials());
      }

      template <class RealType, class Policy>
      inline RealType mean(const binomial_distribution<RealType, Policy>& dist)
      { // Mean of Binomial distribution = np.
        return  dist.trials() * dist.success_fraction();
      } // mean

      template <class RealType, class Policy>
      inline RealType variance(const binomial_distribution<RealType, Policy>& dist)
      { // Variance of Binomial distribution = np(1-p).
        return  dist.trials() * dist.success_fraction() * (1 - dist.success_fraction());
      } // variance

      template <class RealType, class Policy>
      RealType pdf(const binomial_distribution<RealType, Policy>& dist, const RealType& k)
      { // Probability Density/Mass Function.
        BOOST_FPU_EXCEPTION_GUARD

        BOOST_MATH_STD_USING // for ADL of std functions

        RealType n = dist.trials();

        // Error check:
        RealType result;
        if(false == binomial_detail::check_dist_and_k(
           "boost::math::pdf(binomial_distribution<%1%> const&, %1%)",
           n,
           dist.success_fraction(),
           k,
           &result, Policy()))
        {
           return result;
        }

        // Special cases of success_fraction, regardless of k successes and regardless of n trials.
        if (dist.success_fraction() == 0)
        {  // probability of zero successes is 1:
           return static_cast<RealType>(k == 0 ? 1 : 0);
        }
        if (dist.success_fraction() == 1)
        {  // probability of n successes is 1:
           return static_cast<RealType>(k == n ? 1 : 0);
        }
        // k argument may be integral, signed, or unsigned, or floating point.
        // If necessary, it has already been promoted from an integral type.
        if (n == 0)
        {
          return 1; // Probability = 1 = certainty.
        }
        if (k == 0)
        { // binomial coeffic (n 0) = 1,
          // n ^ 0 = 1
          return pow(1 - dist.success_fraction(), n);
        }
        if (k == n)
        { // binomial coeffic (n n) = 1,
          // n ^ 0 = 1
          return pow(dist.success_fraction(), k);  // * pow((1 - dist.success_fraction()), (n - k)) = 1
        }

        // Probability of getting exactly k successes
        // if C(n, k) is the binomial coefficient then:
        //
        // f(k; n,p) = C(n, k) * p^k * (1-p)^(n-k)
        //           = (n!/(k!(n-k)!)) * p^k * (1-p)^(n-k)
        //           = (tgamma(n+1) / (tgamma(k+1)*tgamma(n-k+1))) * p^k * (1-p)^(n-k)
        //           = p^k (1-p)^(n-k) / (beta(k+1, n-k+1) * (n+1))
        //           = ibeta_derivative(k+1, n-k+1, p) / (n+1)
        //
        using boost::math::ibeta_derivative; // a, b, x
        return ibeta_derivative(k+1, n-k+1, dist.success_fraction(), Policy()) / (n+1);

      } // pdf

      template <class RealType, class Policy>
      inline RealType cdf(const binomial_distribution<RealType, Policy>& dist, const RealType& k)
      { // Cumulative Distribution Function Binomial.
        // The random variate k is the number of successes in n trials.
        // k argument may be integral, signed, or unsigned, or floating point.
        // If necessary, it has already been promoted from an integral type.

        // Returns the sum of the terms 0 through k of the Binomial Probability Density/Mass:
        //
        //   i=k
        //   --  ( n )   i      n-i
        //   >   |   |  p  (1-p)
        //   --  ( i )
        //   i=0

        // The terms are not summed directly instead
        // the incomplete beta integral is employed,
        // according to the formula:
        // P = I[1-p]( n-k, k+1).
        //   = 1 - I[p](k + 1, n - k)

        BOOST_MATH_STD_USING // for ADL of std functions

        RealType n = dist.trials();
        RealType p = dist.success_fraction();

        // Error check:
        RealType result;
        if(false == binomial_detail::check_dist_and_k(
           "boost::math::cdf(binomial_distribution<%1%> const&, %1%)",
           n,
           p,
           k,
           &result, Policy()))
        {
           return result;
        }
        if (k == n)
        {
          return 1;
        }

        // Special cases, regardless of k.
        if (p == 0)
        {  // This need explanation:
           // the pdf is zero for all cases except when k == 0.
           // For zero p the probability of zero successes is one.
           // Therefore the cdf is always 1:
           // the probability of k or *fewer* successes is always 1
           // if there are never any successes!
           return 1;
        }
        if (p == 1)
        { // This is correct but needs explanation:
          // when k = 1
          // all the cdf and pdf values are zero *except* when k == n,
          // and that case has been handled above already.
          return 0;
        }
        //
        // P = I[1-p](n - k, k + 1)
        //   = 1 - I[p](k + 1, n - k)
        // Use of ibetac here prevents cancellation errors in calculating
        // 1-p if p is very small, perhaps smaller than machine epsilon.
        //
        // Note that we do not use a finite sum here, since the incomplete
        // beta uses a finite sum internally for integer arguments, so
        // we'll just let it take care of the necessary logic.
        //
        return ibetac(k + 1, n - k, p, Policy());
      } // binomial cdf

      template <class RealType, class Policy>
      inline RealType cdf(const complemented2_type<binomial_distribution<RealType, Policy>, RealType>& c)
      { // Complemented Cumulative Distribution Function Binomial.
        // The random variate k is the number of successes in n trials.
        // k argument may be integral, signed, or unsigned, or floating point.
        // If necessary, it has already been promoted from an integral type.

        // Returns the sum of the terms k+1 through n of the Binomial Probability Density/Mass:
        //
        //   i=n
        //   --  ( n )   i      n-i
        //   >   |   |  p  (1-p)
        //   --  ( i )
        //   i=k+1

        // The terms are not summed directly instead
        // the incomplete beta integral is employed,
        // according to the formula:
        // Q = 1 -I[1-p]( n-k, k+1).
        //   = I[p](k + 1, n - k)

        BOOST_MATH_STD_USING // for ADL of std functions

        RealType const& k = c.param;
        binomial_distribution<RealType, Policy> const& dist = c.dist;
        RealType n = dist.trials();
        RealType p = dist.success_fraction();

        // Error checks:
        RealType result;
        if(false == binomial_detail::check_dist_and_k(
           "boost::math::cdf(binomial_distribution<%1%> const&, %1%)",
           n,
           p,
           k,
           &result, Policy()))
        {
           return result;
        }

        if (k == n)
        { // Probability of greater than n successes is necessarily zero:
          return 0;
        }

        // Special cases, regardless of k.
        if (p == 0)
        {
           // This need explanation: the pdf is zero for all
           // cases except when k == 0.  For zero p the probability
           // of zero successes is one.  Therefore the cdf is always
           // 1: the probability of *more than* k successes is always 0
           // if there are never any successes!
           return 0;
        }
        if (p == 1)
        {
          // This needs explanation, when p = 1
          // we always have n successes, so the probability
          // of more than k successes is 1 as long as k < n.
          // The k == n case has already been handled above.
          return 1;
        }
        //
        // Calculate cdf binomial using the incomplete beta function.
        // Q = 1 -I[1-p](n - k, k + 1)
        //   = I[p](k + 1, n - k)
        // Use of ibeta here prevents cancellation errors in calculating
        // 1-p if p is very small, perhaps smaller than machine epsilon.
        //
        // Note that we do not use a finite sum here, since the incomplete
        // beta uses a finite sum internally for integer arguments, so
        // we'll just let it take care of the necessary logic.
        //
        return ibeta(k + 1, n - k, p, Policy());
      } // binomial cdf

      template <class RealType, class Policy>
      inline RealType quantile(const binomial_distribution<RealType, Policy>& dist, const RealType& p)
      {
         return binomial_detail::quantile_imp(dist, p, RealType(1-p));
      } // quantile

      template <class RealType, class Policy>
      RealType quantile(const complemented2_type<binomial_distribution<RealType, Policy>, RealType>& c)
      {
         return binomial_detail::quantile_imp(c.dist, RealType(1-c.param), c.param);
      } // quantile

      template <class RealType, class Policy>
      inline RealType mode(const binomial_distribution<RealType, Policy>& dist)
      {
         BOOST_MATH_STD_USING // ADL of std functions.
         RealType p = dist.success_fraction();
         RealType n = dist.trials();
         return floor(p * (n + 1));
      }

      template <class RealType, class Policy>
      inline RealType median(const binomial_distribution<RealType, Policy>& dist)
      { // Bounds for the median of the negative binomial distribution
        // VAN DE VEN R. ; WEBER N. C. ;
        // Univ. Sydney, school mathematics statistics, Sydney N.S.W. 2006, AUSTRALIE
        // Metrika  (Metrika)  ISSN 0026-1335   CODEN MTRKA8
        // 1993, vol. 40, no3-4, pp. 185-189 (4 ref.)

        // Bounds for median and 50 percetage point of binomial and negative binomial distribution
        // Metrika, ISSN   0026-1335 (Print) 1435-926X (Online)
        // Volume 41, Number 1 / December, 1994, DOI   10.1007/BF01895303
         BOOST_MATH_STD_USING // ADL of std functions.
         RealType p = dist.success_fraction();
         RealType n = dist.trials();
         // Wikipedia says one of floor(np) -1, floor (np), floor(np) +1
         return floor(p * n); // Chose the middle value.
      }

      template <class RealType, class Policy>
      inline RealType skewness(const binomial_distribution<RealType, Policy>& dist)
      {
         BOOST_MATH_STD_USING // ADL of std functions.
         RealType p = dist.success_fraction();
         RealType n = dist.trials();
         return (1 - 2 * p) / sqrt(n * p * (1 - p));
      }

      template <class RealType, class Policy>
      inline RealType kurtosis(const binomial_distribution<RealType, Policy>& dist)
      {
         RealType p = dist.success_fraction();
         RealType n = dist.trials();
         return 3 - 6 / n + 1 / (n * p * (1 - p));
      }

      template <class RealType, class Policy>
      inline RealType kurtosis_excess(const binomial_distribution<RealType, Policy>& dist)
      {
         RealType p = dist.success_fraction();
         RealType q = 1 - p;
         RealType n = dist.trials();
         return (1 - 6 * p * q) / (n * p * q);
      }

    } // namespace math
  } // namespace boost

// This include must be at the end, *after* the accessors
// for this distribution have been defined, in order to
// keep compilers that support two-phase lookup happy.
#include <boost/math/distributions/detail/derived_accessors.hpp>

#endif // BOOST_MATH_SPECIAL_BINOMIAL_HPP


#include "gru_cell.hpp"
#include "generic/activity.hpp"
#include "generic/utils.hpp"
#include <Eigen/SVD>

namespace rnn {
gru_cell::gru_cell(const int inputDim, const int hiddenDim) {
  this->Wxr = MatD(hiddenDim, inputDim);
  this->Whr = MatD(hiddenDim, hiddenDim);
  this->br = VecD::Zero(hiddenDim);

  this->Wxz = MatD(hiddenDim, inputDim);
  this->Whz = MatD(hiddenDim, hiddenDim);
  this->bz = VecD::Zero(hiddenDim);

  this->Wxu = MatD(hiddenDim, inputDim);
  this->Whu = MatD(hiddenDim, hiddenDim);
  this->bu = VecD::Zero(hiddenDim);
}

void gru_cell::init(rnn::generic::rand &rnd, const real scale) {
  rnd.uniform(this->Wxr, scale);
  rnd.uniform(this->Whr, scale);

  rnd.uniform(this->Wxz, scale);
  rnd.uniform(this->Whz, scale);

  rnd.uniform(this->Wxu, scale);
  rnd.uniform(this->Whu, scale);

  this->Whr = Eigen::JacobiSVD<MatD>(this->Whr, Eigen::ComputeFullV |
      Eigen::ComputeFullU).matrixU();
  this->Whz = Eigen::JacobiSVD<MatD>(this->Whz, Eigen::ComputeFullV |
      Eigen::ComputeFullU).matrixU();
  this->Whu = Eigen::JacobiSVD<MatD>(this->Whu, Eigen::ComputeFullV |
      Eigen::ComputeFullU).matrixU();
}
void gru_cell::forward(const VecD &xt, const gru_cell::State *prev, gru_cell::State *cur) {

  cur->r = this->br + this->Wxr * xt + this->Whr * prev->h;
  cur->z = this->bz + this->Wxz * xt + this->Whz * prev->h;

  activity::logistic(cur->r);
  activity::logistic(cur->z);

  cur->rh = cur->r.array() * prev->h.array();
  cur->u = this->bu + this->Wxu * xt + this->Whu * cur->rh;
  activity::tanh(cur->u);
  cur->h = (1.0 - cur->z.array()) * prev->h.array() +
      cur->z.array() * cur->u.array();
}

void gru_cell::backward(gru_cell::State *prev, gru_cell::State *cur, gru_cell::Grad &grad,
                        const VecD &xt) {
  VecD delr, delz, delu, delrh;

  delz = activity::logisticPrime(cur->z).array() * cur->delh.array() *
      (cur->u - prev->h).array();
  delu =
      activity::tanhPrime(cur->u).array() * cur->delh.array() * cur->z.array();
  delrh = this->Whu.transpose() * delu;
  delr =
      activity::logisticPrime(cur->r).array() * delrh.array() * prev->h.array();

  cur->delx =
      this->Wxr.transpose() * delr +
          this->Wxz.transpose() * delz +
          this->Wxu.transpose() * delu;

  prev->delh.noalias() +=
      this->Whr.transpose() * delr +
          this->Whz.transpose() * delz;
  prev->delh.array() +=
      delrh.array() * cur->r.array() +
          cur->delh.array() * (1.0 - cur->z.array());

  grad.Wxr.noalias() += delr * xt.transpose();
  grad.Whr.noalias() += delr * prev->h.transpose();

  grad.Wxz.noalias() += delz * xt.transpose();
  grad.Whz.noalias() += delz * prev->h.transpose();

  grad.Wxu.noalias() += delu * xt.transpose();
  grad.Whu.noalias() += delu * cur->rh.transpose();

  grad.br += delr;
  grad.bz += delz;
  grad.bu += delu;
}

void gru_cell::sgd(const gru_cell::Grad &grad, const real learningRate) {
  this->Wxr -= learningRate * grad.Wxr;
  this->Whr -= learningRate * grad.Whr;
  this->br -= learningRate * grad.br;

  this->Wxz -= learningRate * grad.Wxz;
  this->Whz -= learningRate * grad.Whz;
  this->bz -= learningRate * grad.bz;

  this->Wxu -= learningRate * grad.Wxu;
  this->Whu -= learningRate * grad.Whu;
  this->bu -= learningRate * grad.bu;
}

void gru_cell::save(std::ofstream &ofs) {
  rnn::generic::save(ofs, this->Wxr);
  rnn::generic::save(ofs, this->Whr);
  rnn::generic::save(ofs, this->br);
  rnn::generic::save(ofs, this->Wxz);
  rnn::generic::save(ofs, this->Whz);
  rnn::generic::save(ofs, this->bz);
  rnn::generic::save(ofs, this->Wxu);
  rnn::generic::save(ofs, this->Whu);
  rnn::generic::save(ofs, this->bu);
}

void gru_cell::load(std::ifstream &ifs) {
  rnn::generic::load(ifs, this->Wxr);
  rnn::generic::load(ifs, this->Whr);
  rnn::generic::load(ifs, this->br);
  rnn::generic::load(ifs, this->Wxz);
  rnn::generic::load(ifs, this->Whz);
  rnn::generic::load(ifs, this->bz);
  rnn::generic::load(ifs, this->Wxu);
  rnn::generic::load(ifs, this->Whu);
  rnn::generic::load(ifs, this->bu);
}

void gru_cell::State::clear() {
  this->h = VecD();
  this->u = VecD();
  this->r = VecD();
  this->z = VecD();
  this->rh = VecD();
  this->delh = VecD();
  this->delx = VecD();
}

gru_cell::Grad::Grad(const gru_cell &gru) {
  this->Wxr = MatD::Zero(gru.Wxr.rows(), gru.Wxr.cols());
  this->Whr = MatD::Zero(gru.Whr.rows(), gru.Whr.cols());
  this->br = VecD::Zero(gru.br.rows());

  this->Wxz = MatD::Zero(gru.Wxz.rows(), gru.Wxz.cols());
  this->Whz = MatD::Zero(gru.Whz.rows(), gru.Whz.cols());
  this->bz = VecD::Zero(gru.bz.rows());

  this->Wxu = MatD::Zero(gru.Wxu.rows(), gru.Wxu.cols());
  this->Whu = MatD::Zero(gru.Whu.rows(), gru.Whu.cols());
  this->bu = VecD::Zero(gru.bu.rows());
};

void gru_cell::Grad::init() {
  this->Wxr.setZero();
  this->Whr.setZero();
  this->br.setZero();
  this->Wxz.setZero();
  this->Whz.setZero();
  this->bz.setZero();
  this->Wxu.setZero();
  this->Whu.setZero();
  this->bu.setZero();
}

real gru_cell::Grad::norm() {
  return
      this->Wxr.squaredNorm() + this->Whr.squaredNorm() +
          this->br.squaredNorm() +
          this->Wxz.squaredNorm() + this->Whz.squaredNorm() +
          this->bz.squaredNorm() +
          this->Wxu.squaredNorm() + this->Whu.squaredNorm() +
          this->bu.squaredNorm();
}

void gru_cell::Grad::operator+=(const gru_cell::Grad &grad) {
  this->Wxr += grad.Wxr;
  this->Whr += grad.Whr;
  this->br += grad.br;
  this->Wxz += grad.Wxz;
  this->Whz += grad.Whz;
  this->bz += grad.bz;
  this->Wxu += grad.Wxu;
  this->Whu += grad.Whu;
  this->bu += grad.bu;
}
}


#ifndef APP_ND_GRID_SIMPLEX
#define APP_ND_GRID_SIMPLEX

#include "Point.hpp"
#include "Triangulator.hpp"

#include <stdlib.h>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/numeric/ublas/matrix_proxy.hpp>
#include <boost/numeric/ublas/lu.hpp>

class Simplex {
public:
    Triangulator& triangulator;
    unsigned int num_dimensions;
    std::vector<Point> points;
    std::vector<Point> lines;

    Simplex(unsigned int num_dims, std::vector<std::vector<double>>& _points, Triangulator& _triangulator):
    num_dimensions(num_dims),
    points(_points.size()),
    lines(0),
    triangulator(_triangulator) {
        for (unsigned int i=0; i<_points.size(); i++) {
            points[i] = Point(_points[i]);
        }

        lines = generateLines();
    }

    Simplex(unsigned int num_dims, std::vector<Point> _points, Triangulator& _triangulator):
    num_dimensions(num_dims),
    points(_points),
    lines(0),
    triangulator(_triangulator) {

        lines = generateLines();
    }

    Simplex(const Simplex& other) :
    num_dimensions(other.num_dimensions),
    triangulator(other.triangulator) {
        points = std::vector<Point>(other.points.size());
        for(unsigned int i=0; i<other.points.size(); i++) {
            points[i] = other.points[i];
        }

        lines = std::vector<Point>(other.lines.size());
        for(unsigned int i=0; i<other.lines.size(); i++) {
            lines[i] = other.lines[i];
        }
    }

    Simplex& operator=(const Simplex &other) {
        num_dimensions = other.num_dimensions;
        triangulator = other.triangulator;
        points = std::vector<Point>(other.points.size());
        for(unsigned int i=0; i<other.points.size(); i++) {
            points[i] = other.points[i];
        }

        lines = std::vector<Point>(other.lines.size());
        for(unsigned int i=0; i<other.lines.size(); i++) {
            lines[i] = other.lines[i];
        }

        return *this;
    }

    std::vector<Point> generateLines() {
        std::vector<Point> lines(num_dimensions);
        for(unsigned int p=0; p<points.size()-1; p++) {
            std::vector<double> coords(num_dimensions);
            for (unsigned int c=0; c<num_dimensions; c++)
                coords[c] = points[p+1].coords[c] - points[0].coords[c];
            lines[p] = Point(coords);
        }
        return lines;
    }

    // CalcDeterminant by Richel Bilderbeek : http://www.richelbilderbeek.nl/CppUblasMatrixExample7.htm
    double CalcDeterminant(boost::numeric::ublas::matrix<double> m)
    {
        assert(m.size1() == m.size2() && "Can only calculate the determinant of square matrices");
        boost::numeric::ublas::permutation_matrix<std::size_t> pivots(m.size1() );

        const int is_singular = boost::numeric::ublas::lu_factorize(m, pivots);

        if (is_singular) return 0.0;

        double d = 1.0;
        const std::size_t sz = pivots.size();
        for (std::size_t i=0; i != sz; ++i)
        {
            if (pivots(i) != i)
            {
            d *= -1.0;
            }
            d *= m(i,i);
        }
        return d;
    }

    double getVolume() {
        boost::numeric::ublas::matrix<double> m(num_dimensions,num_dimensions);
        for (unsigned int l=0; l<num_dimensions; l++) {
            for(unsigned int c=0; c<num_dimensions; c++) {
                m(l,c) = lines[l].coords[c];
            }
        }

        unsigned int dim_fac = 0;
        for(unsigned int n=0; n<num_dimensions; n++)
            dim_fac += n;

        return std::abs(CalcDeterminant(m)/dim_fac)/2;
    }

    std::vector<std::vector<Simplex>> intersectWithHyperplane(unsigned int dim_index, double dim) {
        double eps = 0.00000000001;

        std::vector<Point*> lower;
        std::vector<Point*> upper;
        std::vector<Point*> equal;
        for (unsigned int i=0; i<points.size(); i++) {
            if(points[i].coords[dim_index] < dim - eps) lower.push_back(&points[i]);
            else if(points[i].coords[dim_index] > dim + eps) upper.push_back(&points[i]);
            else equal.push_back(&points[i]);
        }

        std::vector<Point> p_outs;
        for (Point* p0 : lower){
            for (Point* p1 : upper) {
                double t = (dim - p0->coords[dim_index]) / (p1->coords[dim_index] - p0->coords[dim_index]);
                std::vector<double> coords(num_dimensions);
                for (unsigned int i=0; i<num_dimensions; i++){
                    coords[i] = p0->coords[i] + ((p1->coords[i] - p0->coords[i])*t);
                }
                Point np(coords);
                np.hyper = true;
                p_outs.push_back(np);
            }
        }

        if (p_outs.size() == 0) {
            std::vector<std::vector<Simplex>> out;
            bool points_above = true;
            for(Point p : points)
                points_above &= p.coords[dim_index] >= dim - eps;

            std::vector<Simplex> less;
            std::vector<Simplex> greater;

            if (!points_above){
                less.push_back(Simplex(num_dimensions, points, triangulator));
                out.push_back(less);
                out.push_back(std::vector<Simplex>());
            } else {
                greater.push_back(Simplex(num_dimensions, points, triangulator));
                out.push_back(std::vector<Simplex>());
                out.push_back(greater);
            }
            return out;
        }

        unsigned int index = 0;
        std::vector<unsigned int> i_less(lower.size());
        for (unsigned int i=0; i<lower.size(); i++) {
            i_less[i] = i + index;
        }
        index += lower.size();

        std::vector<unsigned int> i_greater(upper.size());
        for (unsigned int i=0; i<upper.size(); i++) {
            i_greater[i] = i + index;
        }
        index += upper.size();

        std::vector<unsigned int> i_hyp(p_outs.size());
        for (unsigned int i=0; i<p_outs.size(); i++) {
            i_hyp[i] = i + index;
        }
        index += p_outs.size();

        for (unsigned int i=0; i<equal.size(); i++) i_hyp.push_back(i + index);

        std::vector<Point> p_total(lower.size()+upper.size()+p_outs.size()+equal.size());
        for(unsigned int i=0; i<lower.size(); i++){
            p_total[i] = *(lower[i]);
        }
        for(unsigned int i=0; i<upper.size(); i++){
            p_total[lower.size()+i] = *(upper[i]);
        }
        for(unsigned int i=0; i<p_outs.size(); i++){
            p_total[lower.size()+upper.size()+i] = p_outs[i];
        }
        for(unsigned int i=0; i<equal.size(); i++){
            p_total[lower.size()+upper.size()+p_outs.size()+i] = *(equal[i]);
        }

        std::vector<Simplex> simplices = triangulator.chooseTriangulation(num_dimensions, p_total, i_less, i_greater, i_hyp);

        std::vector<Simplex> less;
        std::vector<Simplex> greater;
        for (Simplex s : simplices){
            bool all_above = true;
            bool all_below = true;
            for (Point p : s.points) {
                all_above &= p.coords[dim_index] >= dim-eps;
                all_below &= p.coords[dim_index] <= dim+eps;
            }

            if (all_above)
                greater.push_back(s);

            if (all_below)
                less.push_back(s);
        }

        std::vector<std::vector<Simplex>> out;
        out.push_back(less);
        out.push_back(greater);
        return out;
    }
};

#endif


//
// Created by prostoichelovek on 12.06.19.
//

#ifndef VISUALODOMETRY_WRAPPER_HPP
#define VISUALODOMETRY_WRAPPER_HPP

#include <Eigen/Dense>

#include <opencv2/opencv.hpp>
#include <opencv2/core/eigen.hpp>

#include <iostream>
#include <iterator>
#include <vector>

#include "feature.h"
#include "utils.h"
#include "visualOdometry.h"

using namespace cv;
using namespace std;

class Wrapper {
public:
    Mat projMatrl, projMatrr;
    Mat rotation = Mat::eye(3, 3, CV_64F);
    Mat translation_stereo = Mat::zeros(3, 1, CV_64F);

    Mat Rpose = Mat::eye(3, 3, CV_64F);

    Mat frame_pose = Mat::eye(4, 4, CV_64F);
    Mat frame_pose32 = Mat::eye(4, 4, CV_32F);

    FeatureSet currentVOFeatures;
    Mat points4D, points3D;

    vector<Point2f> pointsLeft_t0, pointsRight_t0, pointsLeft_t1, pointsRight_t1;

    Wrapper(Mat &projMatrl, Mat &projMatrr)
            : projMatrl(projMatrl), projMatrr(projMatrr) {

    }

    Point computePos(Mat &imageLeft, Mat &imageRight) {
        if (imageLeft_t0.cols == 0) {
            imageLeft_t0 = imageLeft;
            imageRight_t0 = imageRight;
            return {-1, -1};
        }

        vector<Point2f> oldPointsLeft_t0 = currentVOFeatures.points;
        matchingFeatures(imageLeft_t0, imageRight_t0,
                         imageLeft, imageRight,
                         currentVOFeatures,
                         pointsLeft_t0, pointsRight_t0,
                         pointsLeft_t1, pointsRight_t1);

        imageLeft_t0 = imageLeft;
        imageRight_t0 = imageRight;

        vector<Point2f> &currentPointsLeft_t0 = pointsLeft_t0;
        vector<Point2f> &currentPointsLeft_t1 = pointsLeft_t1;

        vector<Point2f> newPoints;
        vector<bool> valid; // valid new points are true

        // ---------------------
        // Triangulate 3D Points
        // ---------------------
        Mat points3D_t0, points4D_t0;
        triangulatePoints(projMatrl, projMatrr, pointsLeft_t0, pointsRight_t0, points4D_t0);
        convertPointsFromHomogeneous(points4D_t0.t(), points3D_t0);

        Mat points3D_t1, points4D_t1;

        triangulatePoints(projMatrl, projMatrr, pointsLeft_t1, pointsRight_t1, points4D_t1);
        convertPointsFromHomogeneous(points4D_t1.t(), points3D_t1);

        // -----------------------
        // Tracking transformation
        // -----------------------
        trackingFrame2Frame(projMatrl, projMatrr, pointsLeft_t0, pointsLeft_t1, points3D_t0, rotation,
                            translation_stereo);

        points4D = points4D_t0;
        frame_pose.convertTo(frame_pose32, CV_32F);
        points4D = frame_pose32 * points4D;
        convertPointsFromHomogeneous(points4D.t(), points3D);

        // -----------
        // Integrating
        // -----------

        Vec3f rotation_euler = rotationMatrixToEulerAngles(rotation);

        Mat rigid_body_transformation;

        if (abs(rotation_euler[1]) < 0.1 && abs(rotation_euler[0]) < 0.1
            && abs(rotation_euler[2]) < 0.1) {
            integrateOdometryStereo(rigid_body_transformation, frame_pose, rotation,
                                    translation_stereo);
        }

        Rpose = frame_pose(Range(0, 3), Range(0, 3));
        Vec3f Rpose_euler = rotationMatrixToEulerAngles(Rpose);

        int x = int(frame_pose.col(3).at<double>(0));
        int y = int(frame_pose.col(3).at<double>(2));

        return Point(x, y);
    }

private:
    Mat imageLeft_t0, imageRight_t0;

};

#endif //VISUALODOMETRY_WRAPPER_HPP


// solving A * X = B
// using driver function gesv()

//#define BOOST_NO_FUNCTION_TEMPLATE_ORDERING

#include <cstddef>
#include <iostream>
#include <boost/numeric/bindings/blas.hpp>
#include <boost/numeric/bindings/atlas/clapack.hpp>
#include <boost/numeric/bindings/traits/tnt.hpp>
#ifndef F_FORTRAN
#  include <tnt/tnt_array2d_utils.h>
#else
#  include <tnt/tnt_fortran_array2d_utils.h>
#endif

namespace blas = boost::numeric::bindings::blas;

using std::size_t;
using std::cout;
using std::endl;

#ifndef F_FORTRAN
typedef TNT::Array2D<double> m_t;
#else
typedef TNT::Fortran_Array2D<double> m_t;
#endif

int main() {

  cout << endl;
  size_t n = 3, nrhs = 1;

  m_t a (n, n);   // system matrix
#ifndef F_FORTRAN
  a[0][0] = 1.; a[0][1] = 1.; a[0][2] = 1.;
  a[1][0] = 2.; a[1][1] = 3.; a[1][2] = 1.;
  a[2][0] = 1.; a[2][1] = -1.; a[2][2] = -1.;
#else
  a(1,1) = 1.; a(1,2) = 1.; a(1,3) = 1.;
  a(2,1) = 2.; a(2,2) = 3.; a(2,3) = 1.;
  a(3,1) = 1.; a(3,2) = -1.; a(3,3) = -1.;
#endif

// see leading comments for `gesv()' in clapack.hpp
#ifndef F_FORTRAN
  m_t b (nrhs, n);  // right-hand side matrix
  b[0][0] = 4.; b[0][1] = 9.; b[0][2] = -2.;
#else
  m_t b (n, nrhs);
  b(1,1) = 4.; b(2,1) = 9.; b(3,1) = -2.;
#endif

  cout << "A: " << a << endl;
  cout << "B: " << b << endl;

  blas::lu_solve (a, b);
  cout << "X: " << b << endl;

  cout << endl;
}


#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <iostream>
#include <boost/math/special_functions/bessel.hpp>

namespace py = pybind11;

// N is the number of zeros we are looking for
// L is an array of orders l
py::array_t<double> bessel_zeros(int N, py::array_t<uint64_t> L) {

  py::buffer_info info = L.request();
  if (info.ndim != 1)
      throw std::runtime_error("Number of dimensions must be one");
  // Number of entries in the L array
  int Nl = info.shape[0];
  // Accessing the array values
  uint64_t *Lptr = static_cast<uint64_t *>(info.ptr);

  // Allocate the qln table and copy over the zeros
  size_t size = Nl*N;
  double *qln = new double[size];

  #pragma omp parallel for schedule(dynamic)
  for(int l=0; l < Nl; l++) {
      std::vector<long double> roots;
      boost::math::cyl_bessel_j_zero((double) (Lptr[l]+0.5), 1, N, std::back_inserter(roots));
      for(int p=0; p <N; p++) {
          qln[N*l + p] = roots[p];
      }
  }
  // Create a Python object that will free the allocated
  // memory when destroyed:
  py::capsule free_when_done(qln, [](void *f) {
      double *qln = reinterpret_cast<double *>(f);
      delete[] qln;
  });

  return py::array_t<double>(
      {Nl, N}, // shape
      {N*8, 8}, // C-style contiguous strides for double
      qln, // the data pointer
      free_when_done); // numpy array references this parent
}

PYBIND11_MODULE(bessel_tools, m) {
  m.doc() = "Module for Bessel stuff";
  m.def("bessel_zeros", &bessel_zeros, "compute Bessel zeros");
}


/**
 * @file   Vector.cpp
 * @author ALIKAWA Hidehisa <alleyhide@gmail.com>
 * @date   2018/07/07
 *
 * @brief  class Vector
 *
 * Released under the MIT lisence
 */
#include <iostream>
#include <boost/numeric/ublas/io.hpp>

#include "gweyl.hpp"

namespace gweyl{

struct VectorRootSpace::Impl {
    RootSpace space_;
    NumberVector simpleCoefficients_;///< coefficients for simple roots coordinate
    NumberVector fundamentalCoefficients_;///< coefficients for fundamental weights coordinate
};

VectorRootSpace::VectorRootSpace(Type X, NumberVector& v, Coordinate c)
    : pImpl(std::make_unique<Impl>())
{
    RootSpace V(X, v.size());

    pImpl->space_ = V;

    if (c == Coordinate::simple){
        pImpl->simpleCoefficients_ = v;
        matrix A = pImpl->space_.CartanMatrix();
        pImpl->fundamentalCoefficients_ = prod(A, v);
    }else {
        matrix P = pImpl->space_.InverseCartanMatrix();
        pImpl->simpleCoefficients_ = prod(P, v);
        pImpl->fundamentalCoefficients_ = v;
    }
}

VectorRootSpace::VectorRootSpace(): pImpl(std::make_unique<Impl>())
{
}

VectorRootSpace::~VectorRootSpace(){
}

VectorRootSpace::VectorRootSpace(const VectorRootSpace& rhs): pImpl(std::make_unique<Impl>())
{
    RootSpace V(rhs.type(), rhs.rank());
    pImpl->space_ = V;
    pImpl->simpleCoefficients_ = rhs.simpleCoefficients();
    pImpl->fundamentalCoefficients_ = rhs.fundamentalCoefficients();
}

VectorRootSpace& VectorRootSpace::operator=(const VectorRootSpace& rhs){

    // this function does not check the equality of root space
    // because *this is may defined invalid

    RootSpace V(rhs.type(), rhs.rank());
    pImpl->space_ = V;
    pImpl->simpleCoefficients_ = rhs.simpleCoefficients();
    pImpl->fundamentalCoefficients_ = rhs.fundamentalCoefficients();

    return *this;
}

void VectorRootSpace::printf(){
    std::cout << "simple " << pImpl->simpleCoefficients_ << std::endl;
    std::cout << "fundamental " << pImpl->fundamentalCoefficients_ << std::endl;
}

NumberVector VectorRootSpace::simpleCoefficients() const{
    return pImpl->simpleCoefficients_;
}

NumberVector VectorRootSpace::simpleCoefficients(){
    return pImpl->simpleCoefficients_;
}

NumberVector VectorRootSpace::fundamentalCoefficients() const{
    return pImpl->fundamentalCoefficients_;
}

NumberVector VectorRootSpace::fundamentalCoefficients(){
    return pImpl->fundamentalCoefficients_;
}

Type VectorRootSpace::type(){
    return pImpl->space_.type();
}

Type VectorRootSpace::type() const{
    return pImpl->space_.type();
}

unsigned VectorRootSpace::rank(){
    return pImpl->space_.rank();
}

unsigned VectorRootSpace::rank() const{
    return pImpl->space_.rank();
}

bool VectorRootSpace::isInSameSpace(const VectorRootSpace& rhs){
    if (this->type() != rhs.type()){
        return false;
    }

    if (rank() != rhs.rank()){
        return false;
    }
    return true;
}

bool VectorRootSpace::operator==(const VectorRootSpace& rhs){

    if (!isInSameSpace(rhs)){
        return false;
    }

    if (!equal(pImpl->fundamentalCoefficients_, rhs.fundamentalCoefficients())){
        return false;
    }

    return true;
}

bool VectorRootSpace::operator!=(const VectorRootSpace& rhs){
    return !(*this == rhs);
}

VectorRootSpace& VectorRootSpace::operator+=(const VectorRootSpace& rhs){

    if (!isInSameSpace(rhs)){
        std::string msg{"+= of VectorRootSpace error "};
        msg += "LHS ";
        msg += std::to_string(static_cast<int>(type()));
        msg += " ";
        msg += std::to_string(rank());
        msg += ", RHS ";
        msg += std::to_string(static_cast<int>(rhs.type()));
        msg += " ";
        msg += std::to_string(rhs.rank());
        std::runtime_error e(msg);
        throw e;
    }

    pImpl->simpleCoefficients_ += rhs.simpleCoefficients();
    pImpl->fundamentalCoefficients_ += rhs.fundamentalCoefficients();

    return *this;
}

VectorRootSpace& VectorRootSpace::operator-=(const VectorRootSpace& rhs){

    if (!isInSameSpace(rhs)){
        std::string msg{"-= of VectorRootSpace error "};
        msg += "LHS ";
        msg += std::to_string(static_cast<int>(type()));
        msg += " ";
        msg += std::to_string(rank());
        msg += ", RHS ";
        msg += std::to_string(static_cast<int>(rhs.type()));
        msg += " ";
        msg += std::to_string(rhs.rank());
        std::runtime_error e(msg);
        throw e;
    }

    pImpl->simpleCoefficients_ -= rhs.simpleCoefficients();
    pImpl->fundamentalCoefficients_ -= rhs.fundamentalCoefficients();

    return *this;
}

VectorRootSpace& VectorRootSpace::operator*=(rational r){
    pImpl->simpleCoefficients_ *= r;
    pImpl->fundamentalCoefficients_ *= r;

    return *this;
}

bool VectorRootSpace::dominant(){

    NumberVector nv = fundamentalCoefficients();
    for (rational x : nv){
        if (x < 0){
            return false;
        }
    }

    return true;
}

bool VectorRootSpace::integral(){

    NumberVector nv = fundamentalCoefficients();
    for (rational x : nv){
        if (x.denominator() != 1){
            return false;
        }
    }

    return true;
}

bool VectorRootSpace::isDominantIntegral(){
    return ((dominant()) && (integral()));
}

VectorRootSpace operator+(const VectorRootSpace& v1, const VectorRootSpace& v2){
    VectorRootSpace w(v1);
    w += v2;
    return w;
}

VectorRootSpace operator-(const VectorRootSpace& v1, const VectorRootSpace& v2){
    VectorRootSpace w(v1);
    w -= v2;
    return w;
}

VectorRootSpace operator*(const VectorRootSpace& v1, const rational r){
    VectorRootSpace w(v1);
    w *= r;
    return w;
}

VectorRootSpace operator*(const rational r, const VectorRootSpace& v1){
    VectorRootSpace w(v1);
    w *= r;
    return w;
}

}


#include <Eigen/Dense>
using Eigen::MatrixXf;

#define SGA_USE_EIGEN
#include <sga.hpp>
#include "../common/common.hpp"

#include "../common/json.hpp"
using json = nlohmann::json;

#define STB_IMAGE_IMPLEMENTATION
#include "../common/stb_image.h"

#include <iostream>
#include <fstream>
#include <cassert>

inline bool file_exists(std::string path) {
  return std::ifstream(path).good();
}

void usage(char** argv){
  std::cout << "USAGE: " << argv[0] << " CONFIG_FILE" << std::endl;
}

Eigen::Matrix3f find_homography(std::vector<Eigen::Vector2f> points1, std::vector<Eigen::Vector2f> points2){
  assert(points1.size() == points2.size());
  Eigen::MatrixXf A = Eigen::MatrixXf::Zero(points1.size()*2,9);
  for(unsigned int i = 0; i < points1.size(); i++){
    float x1 = points1[i](0), y1 = points1[i](1);
    float x2 = points2[i](0), y2 = points2[i](1);
    Eigen::Matrix<float, 2, 9> Q;
    Q <<
      x1, y1, 1,  0,  0, 0, -x1*x2, -y1*x2, -x2,
      0 , 0 , 0, x1, y1, 1, -x1*y2, -y1*y2, -y2;
    A.block(i*2,0,2,9) = Q;
  }

  Eigen::JacobiSVD<Eigen::MatrixXf> svd(A, Eigen::ComputeThinU | Eigen::ComputeFullV);
  unsigned int P = svd.matrixV().cols();
  Eigen::Map<const Eigen::Matrix3f> view(svd.matrixV().block(0,P-1,9,1).data(), 3, 3);
  Eigen::Matrix3f H = view.transpose() / view(2,2);

  return H;
}

int main(int argc, char** argv){
  if(argc < 2) {
    usage(argv);
    return 1;
  }
  std::string config_path = argv[1];
  size_t q = config_path.find_last_of("/\\");
  std::string config_dir = config_path.substr(0,q);
  std::string output_path = "output.png";

  // Enable sga
  sga::init();
  renderdoc_tryenable();
  renderdoc_capture_start();

  // Open and parse config file
  std::ifstream config_file(config_path);
  if(!config_file.good()){
    std::cout << "Failed to open file \"" << config_path << "\"" << std::endl;
    return 1;
  }
  std::vector<std::string> image_paths;
  std::vector<std::pair<std::vector<Eigen::Vector2f>, std::vector<Eigen::Vector2f>>> points;
  unsigned int center = 0;
  unsigned int N;
  bool render_lines = false;
  try{
    json config;
    config_file >> config;
    auto images = config["images"];
    if(!images.is_array()){
      std::cout << "Json config must contain an array of strings \"images\" representing a list of input images." << std::endl;
      return 1;
    }
    for(auto& q : images){
      image_paths.push_back(q);
    }
    N = image_paths.size();
    if(config.count("center") > 0){
      center = config["center"];
    }
    if(config.count("lines") > 0){
      render_lines = config["lines"] != 0;
    }
    auto pointlist = config["points"];
    if(!pointlist.is_array() || pointlist.size() != N-1){
      std::cout << "Json config must contain an array of point coordinates \"points\" of the lentgh equal to the number of images minus one (" << N-1 << ")." << std::endl;
      return 1;
    }
    for(auto& pointspair : pointlist){
      std::vector<Eigen::Vector2f> pl;
      std::vector<Eigen::Vector2f> pr;
      if(pointspair.size() != 2){
        std::cout << "Each entry in \"points\" array must have two elements (a list of points in image N and a list of corresponding points on image N+1)" << std::endl;
        return 1;
      }
      for(auto& point : pointspair[0]){
        Eigen::Vector2f p;
        if(point.size() != 2){
          std::cout << "Each point must be an array of 2 numbers." << std::endl;
          return 1;
        }
        p(0) = point[0]; p(1) = point[1];
        pl.push_back(p);
      }
      for(auto& point : pointspair[1]){
        Eigen::Vector2f p;
        if(point.size() != 2){
          std::cout << "Each point must be an array of 2 numbers." << std::endl;
          return 1;
        }
        p(0) = point[0]; p(1) = point[1];
        pr.push_back(p);
      }
      points.push_back(std::make_pair(pl,pr));
    }
  }catch (json::exception& e){
    std::cout << "Error reading json config: " << e.what() << std::endl;
  }

  // Print out configuration
  /*
  for(unsigned int i = 0; i < N; i++){
    std::cout << image_paths[i] << std::endl;
    if(i < N-1){
      for(auto p : points[i].first){
        std::cout << "[" << p(0) << ", " << p(1) << "] ";
      }
      std::cout << std::endl;
      for(auto p : points[i].second){
        std::cout << "[" << p(0) << ", " << p(1) << "] ";
      }
      std::cout << std::endl;
    }
  }
  */

  // Compute homographies
  std::cout << "Preparing Hs" << std::endl;
  std::vector<Eigen::Matrix3f> Hs;
  Eigen::Matrix3f H0 = Eigen::Matrix3f::Identity(3,3);
  Hs.push_back(H0);

  for(unsigned int i = center+1; i < N; i++){
    std::cout << "Computing H between " << i-1 << " and " << i << std::endl;
    auto Hdiff = find_homography(points[i-1].first, points[i-1].second);
    auto Habs = Hdiff * Hs.back();
    Hs.push_back(Habs);
  }
  for(int i = center-1; i > -1; i--){
    std::cout << "Computing H between " << i+1 << " and " << i << std::endl;
    auto Hdiff = find_homography(points[i].second, points[i].first);
    auto Habs = Hdiff * Hs.front();
    Hs.insert(Hs.begin(), Habs);
  }
  assert(Hs.size() == N);

  // Compute inverses
  std::vector<Eigen::Matrix3f> His;
  for(const auto& H : Hs)
    His.push_back(H.inverse());

  // Load images
  std::cout << "Loading images" << std::endl;
  std::vector<sga::Image> images;
  for(std::string image_path : image_paths){
    image_path = config_dir + "/" + image_path;

    int w,h,n;
    stbi_set_flip_vertically_on_load(1);
    unsigned char* data = stbi_load(image_path.c_str(), &w, &h, &n, 4);
    if(!data){
      std::cout << "Opening image '" << image_path << "' failed: " << stbi_failure_reason() << std::endl;
      return 1;
    }
    sga::Image image(w, h, 4, sga::ImageFormat::NInt8, sga::ImageFilterMode::Anisotropic);
    image.putData(std::vector<uint8_t>(data, data + w*h*4));
    free(data);

    images.push_back(image);
  }

  std::cout << "Preparing render resources" << std::endl;

  // Prepare image bounding boxes
  Eigen::ArrayXXf imgBBs(4*N,3);
  for(unsigned int i = 0; i < N; i++){
    auto& p = images[i];
    auto& Hi = His[i];
    Eigen::Vector3f v0(0,0,1);
    Eigen::Vector3f v1(0,p.getHeight(),1);
    Eigen::Vector3f v2(p.getWidth(),0,1);
    Eigen::Vector3f v3(p.getWidth(),p.getHeight(),1);
    imgBBs.block(4*i + 0, 0, 1, 3) = (Hi*v0).transpose();
    imgBBs.block(4*i + 1, 0, 1, 3) = (Hi*v1).transpose();
    imgBBs.block(4*i + 2, 0, 1, 3) = (Hi*v2).transpose();
    imgBBs.block(4*i + 3, 0, 1, 3) = (Hi*v3).transpose();
  }
  imgBBs.colwise() /= imgBBs.col(2);
  Eigen::Vector3f BBmax, BBmin;
  BBmax = imgBBs.colwise().maxCoeff();
  BBmin = imgBBs.colwise().minCoeff();
  Eigen::Vector3f BBsize = BBmax - BBmin;
  Eigen::Vector3f offset = BBmin;
  std::cout << "offset " << offset << std::endl;

  // Create target image
  sga::Image result(BBsize(0), BBsize(1));

  // Prepare a VBO
  struct VertData{
    Eigen::Vector2f pos;
  };
  std::vector<VertData> vertices = {
    {{0,0}},{{0,1}},{{1,0}},
    {{1,1}},{{1,0}},{{0,1}}
  };
  sga::VBO vbo({sga::DataType::Float2}, vertices.size());
  vbo.write(vertices);

  // Prepare shaders
  auto vertShader = sga::VertexShader::createFromSource(R"(
    mat3 align = mat3(vec3(2, 0, 0),vec3(0, 2, 0),vec3(-1, -1, 1));
    void main(){
      vec2 pos_texturespace = in_position * textureSize(image,0);
      vec3 pos_transformed_homog = H * vec3(pos_texturespace,1);
      float perspective_factor = 1/pos_transformed_homog.z;
      vec2 pos_transformed = pos_transformed_homog.xy/pos_transformed_homog.z;
      vec2 pos3 = 2*(pos_transformed - offset)/sgaResolution.xy - 1;
      gl_Position = vec4(pos3,0,1);
      imageUVW = vec3(in_position.x, 1 - in_position.y, 1) * perspective_factor;
    }
  )");
  vertShader.addInput(sga::DataType::Float2, "in_position");
  vertShader.addOutput(sga::DataType::Float3, "imageUVW");
  vertShader.addSampler("image");
  vertShader.addUniform(sga::DataType::Mat3, "H");
  vertShader.addUniform(sga::DataType::Float2, "offset");
  auto fragShader = sga::FragmentShader::createFromSource(R"(
    void main(){
      out_color = textureProj(image, imageUVW);
    }
  )");
  fragShader.addOutput(sga::DataType::Float4, "out_color");
  fragShader.addInput(sga::DataType::Float3, "imageUVW");
  fragShader.addSampler("image");
  auto program = sga::Program::createAndCompile(vertShader,fragShader);

  // Prepare pipeline
  sga::Pipeline pipeline;
  pipeline.setProgram(program);
  pipeline.setTarget({result});
  pipeline.setFaceCull(sga::FaceCullMode::None);
  pipeline.setBlendModeColor(sga::BlendFactor::One, sga::BlendFactor::OneMinusSrcAlpha);
  pipeline.setBlendModeAlpha(sga::BlendFactor::One, sga::BlendFactor::OneMinusSrcAlpha);
  pipeline.clear();

  std::cout << "Rendering" << std::endl;

  // Draw!
  pipeline.uniform["offset"] = Eigen::Vector2f{offset(0), offset(1)};
  for(unsigned int i = 0; i < N; i++){
    pipeline.sampler["image"] = images[i];
    pipeline.uniform["H"] = His[i];
    std::cout << His[i] << std::endl;
    pipeline.draw(vbo);
  }

  // Prepare another pipeline, just for rendering image edges
  if(render_lines){
    auto linesFragShader = sga::FragmentShader::createFromSource(R"(
      void main(){out_color = vec4(1,0,0,0.2);}
    )");
    linesFragShader.addOutput(sga::DataType::Float4, "out_color");
    linesFragShader.addInput(sga::DataType::Float3, "imageUVW");
    auto lines_program = sga::Program::createAndCompile(vertShader,linesFragShader);
    sga::Pipeline lines_pipeline;
    lines_pipeline.setPolygonMode(sga::PolygonMode::LineStrip);
    lines_pipeline.setLineWidth(2.0);
    lines_pipeline.setTarget(result);
    lines_pipeline.setProgram(lines_program);
    lines_pipeline.setBlendModeColor(sga::BlendFactor::One, sga::BlendFactor::OneMinusSrcAlpha);
    lines_pipeline.setBlendModeAlpha(sga::BlendFactor::One, sga::BlendFactor::OneMinusSrcAlpha);

    // VBO for lines
    std::vector<VertData> lines_vertices = {
      {{0,0}},{{0,1}},{{1,1}},{{1,0}},{{0,0}}
    };
    sga::VBO lines_vbo({sga::DataType::Float2}, lines_vertices.size());
    lines_vbo.write(lines_vertices);

    lines_pipeline.uniform["offset"] = Eigen::Vector2f{offset(0),offset(1)};
    for(unsigned int i = 0; i < N; i++){
      lines_pipeline.sampler["image"] = images[i];
      lines_pipeline.uniform["H"] = His[i];
      lines_pipeline.draw(lines_vbo);
    }
  }

  // Save result
  std::cout << "Saving result to " << output_path << std::endl;
  result.savePNG(output_path);

  renderdoc_capture_end();

  sga::terminate();
}


#include "backend.h"
#include "sofunction.h"
#include <boost/multiprecision/cpp_dec_float.hpp>
#include "solvers/regulafalsi.h"
#include "solvers/secant.h"
#include "solvers/bisection.h"
#include <iomanip>
#include <fenv.h>

Backend::Backend() {

}

Backend::~Backend() {
    delete function;
}

std::string Backend::intervalToString(interval x, int decimals) {
    std::stringstream str;
    str << std::scientific;
    str << std::setprecision(decimals);

    if (singleton(x)) {
        str << "[" << median(x) << "]";
    } else {
        int old_rounding = fegetround();

        fesetround(FE_DOWNWARD);
        str << "[" << x.lower() << ", ";
        fesetround(FE_UPWARD);
        str << x.upper() << "]";

        fesetround(old_rounding);
    }

    return str.str();
}

long double Backend::stringToFloat(const std::string &value) {
    if (value.find("bla") != std::string::npos) {
        throw "Nie tym razem, panie profesorze ;)";
    }

    try {
        return std::stold(value);
    } catch (std::invalid_argument &error) {
        throw "Nie udało się zinterpretować wpisanych danych jako liczbę!";
    }
}

interval Backend::stringToInterval(const std::string &value, char separator) {
    size_t split_pos = value.find(separator);
    std::string left_str, right_str;
    boost::multiprecision::cpp_dec_float_50 left_mp, right_mp;
    long double left, right;

    if (split_pos == std::string::npos) {
        left_str = right_str = value;
    } else {
        // przedział
        left_str = value.substr(0, split_pos);
        right_str = value.substr(split_pos + 1);
    }

    try {
        left_mp.assign(left_str);
        right_mp.assign(right_str);
    } catch (std::runtime_error &error) {
        throw "Nie udało się zinterpretować wpisanych danych jako liczbę!";
    }

    int old_rounding = fegetround();
    fesetround(FE_DOWNWARD);
    left = left_mp.convert_to<long double>();
    fesetround(FE_UPWARD);
    right = right_mp.convert_to<long double>();
    fesetround(old_rounding);

    return interval(left, right);
}

struct SingleFloatSummary Backend::floatSummary(long double solution, std::string more) {
    struct SingleFloatSummary out;

    std::stringstream str;
    str << std::scientific;
    str << std::setprecision(decimals);

    str << solution;
    out.x = str.str();
    str.str(std::string());

    long double y = function->evaluate(solution);
    str << y;
    out.y = str.str();
    str.str(std::string());

    out.more = more;

    return out;
}

struct SingleIntervalSummary Backend::intervalSummary(interval solution, std::string more) {
    struct SingleIntervalSummary out;

    std::stringstream str;
    str << std::scientific;
    str << std::setprecision(decimals);

    out.x = intervalToString(solution, decimals);

    str << median(solution);
    out.median = str.str();
    str.str(std::string());

    int old_rounding = fegetround();
    fesetround(FE_UPWARD);
    str << upper(solution) - lower(solution);
    out.width = str.str();
    str.str(std::string());

    fesetround(old_rounding);

    interval y = function->evaluate(solution);
    out.y = intervalToString(y, decimals);

    out.more = more;

    return out;
}

void Backend::loadFunction(char filename[]) {
    Function *new_function;

    new_function = new SOFunction(filename);
    if (function != nullptr) {
        delete function;
    }

    function = new_function;
}

struct FloatSummary Backend::solveFloatingPoint(const std::string &a_str, const std::string &b_str) {
    long double a, b, x;
    a = stringToFloat(a_str);
    b = stringToFloat(b_str);

   struct FloatSummary out;
   bool secant_only = false;
   try {
       check_interval(a, b, function, true);
   } catch (int err) {
       if (err == NO_REAL_ROOTS) {
           secant_only = true;
       }
   }

    try {
        x = Secant(a, b, function);
        out.secant = floatSummary(x);

        if (!secant_only) {
            x = RegulaFalsi(a, b, function);
            out.regulafalsi = floatSummary(x);

            bool reached;
            x = Bisection(a, b, function, bisectionTolerance, bisectionIterations, reached);
            out.bisection = floatSummary(x, std::string("reached = ")+(reached?"true":"false"));
        }
    } catch(int err) {
        if (err == WRONG_INTERVAL) {
            throw "Lewy koniec przedziału musi być mniejszy od prawego końca!";
        } else if (err == NO_REAL_ROOTS) {
            throw "Brak rozwiązań rzeczywistych w tym przedziale. Upewnij się, że f(a) * f(b) < 0";
        }
    }

    return out;
}

struct IntervalSummary Backend::solveInterval(const std::string &a_str, const std::string &b_str) {
    interval a, b, x;

    try {
        a = stringToInterval(a_str);
        b = stringToInterval(b_str);
    } catch (std::runtime_error err) {
        throw "Nie można zbudować takiego przedziału!";
    }

    struct IntervalSummary out;
    bool secant_only = false;
    try {
        check_interval(a, b, function, true);
    } catch (int err) {
        if (err == NO_REAL_ROOTS) {
            secant_only = true;
        }
    }

    try {
        x = Secant(a, b, function);
        out.secant = intervalSummary(x);

        if (!secant_only) {
            bool reached;
            x = Bisection(a, b, function, bisectionTolerance, bisectionIterations, reached);
            out.bisection = intervalSummary(x, std::string("reached = ")+(reached?"true":"false"));

            x = RegulaFalsi(a, b, function);
            out.regulafalsi = intervalSummary(x);
        }
    } catch(int err) {
        if (err == WRONG_INTERVAL) {
            throw "Lewy koniec przedziału musi być mniejszy od prawego końca!";
        } else if (err == NO_REAL_ROOTS) {
            throw "Możliwy brak rozwiązań rzeczywistych w tym przedziale. Upewnij się, że f(a) * f(b) < 0";
        }
    }

    return out;
}