#include <gmp.h>
#include <fc/crypto/alt_bn128.hpp>

#include <libff/algebra/curves/alt_bn128/alt_bn128_g1.hpp>
#include <libff/algebra/curves/alt_bn128/alt_bn128_g2.hpp>
#include <libff/algebra/curves/alt_bn128/alt_bn128_pairing.hpp>
#include <libff/algebra/curves/alt_bn128/alt_bn128_pp.hpp>

#include <libff/common/profiling.hpp>
#include <boost/throw_exception.hpp>
#include <algorithm>

namespace fc {

    using Scalar = libff::bigint<libff::alt_bn128_q_limbs>;

    void initLibSnark() noexcept {
        static bool s_initialized = []() noexcept {
            libff::inhibit_profiling_info = true;
            libff::inhibit_profiling_counters = true;
            libff::alt_bn128_pp::init_public_params();
            return true;
        }();
        (void)s_initialized;
    }

    Scalar to_scalar(const bytes& be) noexcept {
        mpz_t m;
        mpz_init(m);
        mpz_import(m, be.size(), /*order=*/1, /*size=*/1, /*endian=*/0, /*nails=*/0, &be[0]);
        Scalar out{m};
        mpz_clear(m);
        return out;
    }

    // Notation warning: Yellow Paper's p is the same libff's q.
    // Returns x < p (YP notation).
    static bool valid_element_of_fp(const Scalar& x) noexcept {
        return mpn_cmp(x.data, libff::alt_bn128_modulus_q.data, libff::alt_bn128_q_limbs) < 0;
    }

    std::variant<alt_bn128_error, libff::alt_bn128_G1> decode_g1_element(const bytes& bytes64_be) noexcept {
        if(bytes64_be.size() != 64) {
            return alt_bn128_error::input_len_error;
        }

        bytes sub1(bytes64_be.begin(), bytes64_be.begin()+32);
        bytes sub2(bytes64_be.begin()+32, bytes64_be.begin()+64);

        Scalar x{to_scalar(sub1)};
        Scalar y{to_scalar(sub2)};

        if (!valid_element_of_fp(x) || !valid_element_of_fp(y)) {
            return alt_bn128_error::operand_component_invalid;
        }

        if (x.is_zero() && y.is_zero()) {
            return alt_bn128_error::operand_at_origin;
        }

        libff::alt_bn128_G1 point{x, y, libff::alt_bn128_Fq::one()};
        if (!point.is_well_formed()) {
            return alt_bn128_error::operand_not_in_curve;
        }
        return point;
    }

    std::variant<alt_bn128_error, libff::alt_bn128_Fq2> decode_fp2_element(const bytes& bytes64_be) noexcept {
        if(bytes64_be.size() != 64) {
            return alt_bn128_error::input_len_error;
        }

        // big-endian encoding
        bytes sub1(bytes64_be.begin()+32, bytes64_be.begin()+64);
        bytes sub2(bytes64_be.begin(), bytes64_be.begin()+32);

        Scalar c0{to_scalar(sub1)};
        Scalar c1{to_scalar(sub2)};

        if (!valid_element_of_fp(c0) || !valid_element_of_fp(c1)) {
            return alt_bn128_error::operand_component_invalid;
        }

        return libff::alt_bn128_Fq2{c0, c1};
    }

    std::variant<alt_bn128_error, libff::alt_bn128_G2> decode_g2_element(const bytes& bytes128_be) noexcept {

        bytes sub1(bytes128_be.begin(), bytes128_be.begin()+64);
        auto maybe_x = decode_fp2_element(sub1);
        if (std::holds_alternative<alt_bn128_error>(maybe_x)) {
            return std::get<alt_bn128_error>(maybe_x);
        }

        bytes sub2(bytes128_be.begin()+64, bytes128_be.begin()+128);
        auto maybe_y = decode_fp2_element(sub2);
        if (std::holds_alternative<alt_bn128_error>(maybe_y)) {
            return std::get<alt_bn128_error>(maybe_y);
        }

        const auto& x = std::get<libff::alt_bn128_Fq2>(maybe_x);
        const auto& y = std::get<libff::alt_bn128_Fq2>(maybe_y);

        if (x.is_zero() && y.is_zero()) {
            return alt_bn128_error::operand_at_origin;
        }

        libff::alt_bn128_G2 point{x, y, libff::alt_bn128_Fq2::one()};
        if (!point.is_well_formed()) {
            return alt_bn128_error::operand_not_in_curve;
        }

        if (!(libff::alt_bn128_G2::order() * point).is_zero()) {
            // wrong order, doesn't belong to the subgroup G2
            return alt_bn128_error::operand_outside_g2;
        }

        return point;
    }

    bytes encode_g1_element(libff::alt_bn128_G1 p) noexcept {
        bytes out(64, '\0');
        if (p.is_zero()) {
            return out;
        }

        p.to_affine_coordinates();

        auto x{p.X.as_bigint()};
        auto y{p.Y.as_bigint()};

        std::memcpy(&out[0], y.data, 32);
        std::memcpy(&out[32], x.data, 32);

        std::reverse(out.begin(), out.end());
        return out;
    }

    std::variant<alt_bn128_error, bytes> alt_bn128_add(const bytes& op1, const bytes& op2) {
        fc::initLibSnark();

        auto maybe_x = decode_g1_element(op1);
        if (std::holds_alternative<alt_bn128_error>(maybe_x)) {
            return std::get<alt_bn128_error>(maybe_x);
        }

        auto maybe_y = decode_g1_element(op2);
        if (std::holds_alternative<alt_bn128_error>(maybe_y)) {
            return std::get<alt_bn128_error>(maybe_y);
        }

        const auto& x = std::get<libff::alt_bn128_G1>(maybe_x);
        const auto& y = std::get<libff::alt_bn128_G1>(maybe_y);

        libff::alt_bn128_G1 g1Sum = x + y;
        return encode_g1_element(g1Sum);
    }

    std::variant<alt_bn128_error, bytes> alt_bn128_mul(const bytes& g1_point, const bytes& scalar) {
        initLibSnark();

        auto maybe_x = decode_g1_element(g1_point);
        if (std::holds_alternative<alt_bn128_error>(maybe_x)) {
            return std::get<alt_bn128_error>(maybe_x);
        }

        auto& x = std::get<libff::alt_bn128_G1>(maybe_x);

        if(scalar.size() != 32) {
            return alt_bn128_error::invalid_scalar_size;
        }

        Scalar n{to_scalar(scalar)};

        libff::alt_bn128_G1 g1Product = n * x;
        return encode_g1_element(g1Product);
    }

    static constexpr size_t kSnarkvStride{192};

    std::variant<alt_bn128_error, bool>  alt_bn128_pair(const bytes& g1_g2_pairs, const yield_function_t& yield) {
        if (g1_g2_pairs.size() % kSnarkvStride != 0) {
            return alt_bn128_error::pairing_list_size_error;
        }

        size_t k{g1_g2_pairs.size() / kSnarkvStride};

        initLibSnark();
        using namespace libff;

        static const auto one{alt_bn128_Fq12::one()};
        auto accumulator{one};

        for (size_t i{0}; i < k; ++i) {
            auto offset = i * kSnarkvStride;

            bytes sub1(g1_g2_pairs.begin()+offset, g1_g2_pairs.begin()+offset+64);
            auto maybe_a = decode_g1_element(sub1);
            if (std::holds_alternative<alt_bn128_error>(maybe_a)) {
                return std::get<alt_bn128_error>(maybe_a);
            }

            bytes sub2(g1_g2_pairs.begin()+offset+64, g1_g2_pairs.begin()+offset+64+128);
            auto maybe_b = decode_g2_element(sub2);
            if (std::holds_alternative<alt_bn128_error>(maybe_b)) {
                return std::get<alt_bn128_error>(maybe_b);
            }

            const auto& a = std::get<libff::alt_bn128_G1>(maybe_a);
            const auto& b = std::get<libff::alt_bn128_G2>(maybe_b);

            if (a.is_zero() || b.is_zero()) {
                continue;
            }

            accumulator = accumulator * alt_bn128_miller_loop(alt_bn128_precompute_G1(a), alt_bn128_precompute_G2(b));
            yield();
        }

        bool pair_result = false;
        if (alt_bn128_final_exponentiation(accumulator) == one) {
            pair_result = true;
        }

        return pair_result;
    }
}


#include<fastenvelope/obb.h>
#include <Eigen/Eigenvalues>
#include<fstream>
#include <iomanip>
#include<fastenvelope/FastEnvelope.h>// for is_triangle_degenerated()
namespace fastEnvelope {

	std::vector<obb> obb::build_obb_matrixs(const std::vector<Vector3>& face_vertices, const int p_face[8][3], const int c_face[6][3],
		const std::array<std::vector<int>, 8>& p_facepoint, const std::array<std::array<int, 4>, 6>& c_facepoint) {
		std::vector<Vector3> points;

		int dege;
		Vector3 normal;
		std::vector<obb> M;
		M.reserve(8);

		if (face_vertices.size() == polyhedron_point_number1) {
			//M.resize(polyhedron_face_number1);

			for (int j = 0; j < polyhedron_face_number1; j++) {
				//triangle to get normal: {envelope_vertices[i][p_face[j][0]],envelope_vertices[i][p_face[j][1]],envelope_vertices[i][p_face[j][2]]}
#ifdef DO_NOT_HAVE_DEGENERATED_FACES
				if (((face_vertices[p_face[j][0]] - face_vertices[p_face[j][1]]).cross(
					face_vertices[p_face[j][0]] - face_vertices[p_face[j][2]])).norm() < SCALAR_ZERO)
					normal = FastEnvelope::accurate_normal_vector(face_vertices[p_face[j][0]], face_vertices[p_face[j][1]],
						face_vertices[p_face[j][0]], face_vertices[p_face[j][2]]);
				else {
					normal = ((face_vertices[p_face[j][0]] - face_vertices[p_face[j][1]]).cross(face_vertices[p_face[j][0]] - face_vertices[p_face[j][2]])).normalized();
				}
#else
					//triangle to get normal: {envelope_vertices[i][p_face[j][0]],envelope_vertices[i][p_face[j][1]],envelope_vertices[i][p_face[j][2]]}

				dege = FastEnvelope::is_triangle_degenerated(face_vertices[p_face[j][0]], face_vertices[p_face[j][1]], face_vertices[p_face[j][2]]);
				if (dege == FastEnvelope::DEGENERATED_SEGMENT || dege == FastEnvelope::DEGENERATED_POINT) {
					std::cout << "need to fix here, face degeneration" << std::endl;
					exit(0);
				}

				if (dege == FastEnvelope::NERLY_DEGENERATED) {
					normal = FastEnvelope::accurate_normal_vector(face_vertices[p_face[j][0]], face_vertices[p_face[j][1]],
						face_vertices[p_face[j][0]], face_vertices[p_face[j][2]]);
				}
				if (dege == FastEnvelope::NOT_DEGENERATED) {
					normal = ((face_vertices[p_face[j][0]] - face_vertices[p_face[j][1]]).cross(face_vertices[p_face[j][0]] - face_vertices[p_face[j][2]])).normalized();
				}
#endif
				points.clear();
				points.resize(p_facepoint[j].size());
				for (int k = 0; k < p_facepoint[j].size(); k++) {
					points[k] = face_vertices[p_facepoint[j][k]];
				}

				M.emplace_back();
				build_obb(points, normal, OBB_OFFSET, M.back().Trans, M.back().invTrans);
			}

		}

		if (face_vertices.size() == polyhedron_point_number2) {
			M.resize(polyhedron_face_number2);

			for (int j = 0; j < polyhedron_face_number2; j++) {
#ifdef DO_NOT_HAVE_DEGENERATED_FACES
				if (((face_vertices[c_face[j][0]] - face_vertices[c_face[j][1]]).cross(
					face_vertices[c_face[j][0]] - face_vertices[c_face[j][2]])).norm() < SCALAR_ZERO)
					normal = FastEnvelope::accurate_normal_vector(face_vertices[c_face[j][0]], face_vertices[c_face[j][1]],
						face_vertices[c_face[j][0]], face_vertices[c_face[j][2]]);
				else {
					normal = (face_vertices[c_face[j][0]] - face_vertices[c_face[j][1]]).cross(face_vertices[c_face[j][0]] - face_vertices[c_face[j][2]]).normalized();
				}
#else

				//triangle to get normal: {envelope_vertices[i][p_face[j][0]],envelope_vertices[i][p_face[j][1]],envelope_vertices[i][p_face[j][2]]}

				dege = FastEnvelope::is_triangle_degenerated(face_vertices[c_face[j][0]], face_vertices[c_face[j][1]], face_vertices[c_face[j][2]]);
				if (dege == FastEnvelope::DEGENERATED_SEGMENT || dege == FastEnvelope::DEGENERATED_POINT) {
					std::cout << "need to fix here, face degeneration" << std::endl;
					exit(0);
				}

				if (dege == FastEnvelope::NERLY_DEGENERATED) {
					normal = FastEnvelope::accurate_normal_vector(face_vertices[c_face[j][0]], face_vertices[c_face[j][1]],
						face_vertices[c_face[j][0]], face_vertices[c_face[j][2]]);
				}
				if (dege == FastEnvelope::NOT_DEGENERATED) {
					normal = (face_vertices[c_face[j][0]] - face_vertices[c_face[j][1]]).cross(face_vertices[c_face[j][0]] - face_vertices[c_face[j][2]]).normalized();
				}

#endif
				points.clear();
				points.resize(c_facepoint[j].size());
				for (int k = 0; k < c_facepoint[j].size(); k++) {
					points[k] = face_vertices[c_facepoint[j][k]];
				}

				M.emplace_back();
				build_obb(points, normal, OBB_OFFSET, M.back().Trans, M.back().invTrans);
			}
		}
		return M;

	}

	bool obb::intersects(const obb& M2) const {
		return obb_intersection(Trans, invTrans, M2.Trans, M2.invTrans);
	}

	bool obb::intersects(const obb& M2, const obb& M3) const {

		//bool flag1=obb_intersection(Trans, invTrans, M2.Trans, M2.invTrans);
		bool flag1 = intersects(M2);
		if (flag1 == false) return false;

		//bool flag2 = obb_intersection(Trans, invTrans, M3.Trans, M3.invTrans);
		bool flag2 = intersects(M3);
		if (flag2 == false) return false;

		//bool flag3 = obb_intersection(M2.Trans, M2.invTrans, M3.Trans, M3.invTrans);
		bool flag3 = M2.intersects(M3);
		if (flag3 == false) return false;

		return true;
	}

	obb obb::build_triangle_obb_matrixs(const Vector3&t0, const Vector3&t1, const Vector3&t2) {
		Vector3 normal;
		obb res;
		if (((t0 - t1).cross(t0 - t2)).norm() < SCALAR_ZERO)
			normal = FastEnvelope::accurate_normal_vector(t0, t1,
				t0, t2);
		else {
			normal = ((t0 - t1).cross(t0 - t2)).normalized();
		}
		std::vector<Vector3> points(3);
		points[0] = t0; points[1] = t1; points[2] = t2;
		build_obb(points, normal, OBB_OFFSET, res.Trans, res.invTrans);
		return res;
	}

	void obb::build_obb(const std::vector<Vector3>& points, const Vector3& normal, const Scalar offset,
		Eigen::Matrix4d &M, Eigen::Matrix4d &invM)
	{
		Eigen::MatrixXd  PS(3, points.size()), PS1(3, points.size());
		Vector3 cent;
		cent <<
			0, 0, 0;

		for (int i = 0; i < points.size(); i++) {
			PS(0, i) = points[i][0];
			PS(1, i) = points[i][1];
			PS(2, i) = points[i][2];

		}
		cent[0] = PS.row(0).sum() / points.size();
		cent[1] = PS.row(1).sum() / points.size();
		cent[2] = PS.row(2).sum() / points.size();
		PS1.row(0) = PS.row(0) - Eigen::MatrixXd::Ones(1, points.size())*cent[0];
		PS1.row(1) = PS.row(1) - Eigen::MatrixXd::Ones(1, points.size())*cent[1];
		PS1.row(2) = PS.row(2) - Eigen::MatrixXd::Ones(1, points.size())*cent[2];
		Eigen::MatrixXd PCA = PS1 * PS1.transpose();
		Eigen::EigenSolver<Eigen::MatrixXd> eg(PCA);
		Eigen::VectorXcd ev = eg.eigenvalues();

		int maxid = 0;

		for (int i = 1; i < 3; i++) {

			if (ev[maxid].real() <= ev(i).real()) {
				maxid = i;
			}
		}
		assert(ev(maxid).real() > 0);
		Eigen::VectorXcd v = eg.eigenvectors().col(maxid);
		Vector3 y;
		y[0] = v[0].real(); y[1] = v[1].real(); y[2] = v[2].real();
		y = y.normalized();
		assert(normal.dot(y) < SCALAR_ZERO);

		Vector3 z = normal.cross(y);
		Matrix3 R;
		R.col(0) = normal;
		R.col(1) = y;
		R.col(2) = z;

		Matrix3 invR = R.transpose();//inv(R)=trans(R)
		Vector3 T0 = -1 * R*cent, T1;//R*cent+T0=[0,0,0]
		Eigen::Matrix4d Trans0, invTrans0, Trans1, invTrans1, Scalling, invScalling;

		Trans0 << R, T0,
			0, 0, 0, 1;
		invTrans0 << invR, cent,
			0, 0, 0, 1;
		Eigen::MatrixXd  PSG(4, points.size()), prog(4, points.size());
		PSG << PS,
			Eigen::MatrixXd::Ones(1, points.size());
		prog = Trans0 * PSG;//contains the projection of the points in new axis
		Vector3 min, max, mid, corner;
		for (int i = 0; i < 3; i++) {
			min[i] = prog.row(i).minCoeff();
			max[i] = prog.row(i).maxCoeff();
		}
		mid = (min + max) / 2;
		corner = max - mid + Vector3(offset, offset, offset);
		Eigen::MatrixXd procg(4, 1), centng(4, 1), centn(3, 1);
		procg << mid,
			1;
		centng = invTrans0 * procg;
		centn(0) = centng(0); centn(1) = centng(1); centn(2) = centng(2);
		T1 = -1 * R*centn;
		Trans1 << R, T1,
			0, 0, 0, 1;
		invTrans1 << invR, centn,
			0, 0, 0, 1;
		Scalling = Eigen::Matrix4d::Zero(4, 4);
		Scalling(0, 0) = corner(0);
		Scalling(1, 1) = corner(1);
		Scalling(2, 2) = corner(2);
		Scalling(3, 3) = 1;
		invScalling = Scalling.inverse();
		M = invScalling * Trans1;
		invM = invTrans1 * Scalling;
	}

	bool obb::obb_intersection(const Eigen::Matrix4d &M1, const Eigen::Matrix4d &invM1, const Eigen::Matrix4d &M2, const Eigen::Matrix4d &invM2) {

		Eigen::MatrixXd ub(8, 4);

		ub << -1, -1, -1, 1,
			1, -1, -1, 1,
			1, 1, -1, 1,
			-1, 1, -1, 1,
			-1, 1, 1, 1,
			-1, -1, 1, 1,
			1, -1, 1, 1,
			1, 1, 1, 1;
		Eigen::MatrixXd b21(4, 8), b12(4, 8);

		Scalar minx, miny, minz, maxx, maxy, maxz;

		b12 = M2 * invM1*ub.transpose();

		minx = b12.row(0).minCoeff();
		if (minx >= 1) return false;
		miny = b12.row(1).minCoeff();
		if (miny >= 1) return false;
		minz = b12.row(2).minCoeff();
		if (minz >= 1) return false;

		maxx = b12.row(0).maxCoeff();
		if (maxx <= -1) return false;
		maxy = b12.row(1).maxCoeff();
		if (maxy <= -1) return false;
		maxz = b12.row(2).maxCoeff();
		if (maxz <= -1) return false;

		b21 = M1 * invM2*ub.transpose();

		minx = b21.row(0).minCoeff();
		if (minx >= 1) return false;
		miny = b21.row(1).minCoeff();
		if (miny >= 1) return false;
		minz = b21.row(2).minCoeff();
		if (minz >= 1) return false;

		maxx = b21.row(0).maxCoeff();
		if (maxx <= -1) return false;
		maxy = b21.row(1).maxCoeff();
		if (maxy <= -1) return false;
		maxz = b21.row(2).maxCoeff();
		if (maxz <= -1) return false;

		return true;

	}
#include <time.h>
	void obb::test() {
		std::vector<Vector3> p, p1;
		Vector3 dis;
		srand(int(time(0)));
		dis = Vector3(rand() % 100, rand() % 100, rand() % 100) / 50;
		p.resize(20);
		for (int i = 0; i < p.size(); i++) {
			p[i] = Vector3(rand() % 100, rand() % 100, rand() % 100) / 100 + dis;
		}
		Eigen::MatrixXd  PS(3, p.size());

		for (int i = 0; i < p.size(); i++) {
			PS(0, i) = p[i][0];
			PS(1, i) = p[i][1];
			PS(2, i) = p[i][2];

		}

		Eigen::MatrixXd PCA = PS * PS.transpose();
		Eigen::EigenSolver<Eigen::MatrixXd> eg(PCA);
		Eigen::VectorXcd ev = eg.eigenvalues();

		int minid = 0;

		for (int i = 1; i < 3; i++) {

			if (ev[minid].real() >= ev(i).real()) {//find the minimal eigenvalue
				minid = i;
			}
		}

		Eigen::VectorXcd v = eg.eigenvectors().col(minid);
		Vector3 normal;
		normal[0] = v[0].real(); normal[1] = v[1].real(); normal[2] = v[2].real();
		Scalar offset = 0.1;
		Eigen::Matrix4d M, invM;
		build_obb(p, normal, offset, M, invM);
		std::ofstream fout;
		fout.open("D:\\vs\\fast_envelope\\obb\\points.txt");

		for (int i = 0; i < p.size(); i++) {

			fout << std::setprecision(17) << p[i][0] << " " << p[i][1] << " " << p[i][2] << std::endl;

		}
		fout.close();
		fout.open("D:\\vs\\fast_envelope\\obb\\matrixes.txt");

		for (int i = 0; i < 4; i++) {

			fout << std::setprecision(17) << M(i, 0) << " " << M(i, 1) << " " << M(i, 2) << " " << M(i, 3) << std::endl;

		}
		for (int i = 0; i < 4; i++) {

			fout << std::setprecision(17) << invM(i, 0) << " " << invM(i, 1) << " " << invM(i, 2) << " " << invM(i, 3) << std::endl;

		}

		fout.close();

		//////////////////////////

		//srand(int(time(0)));
		dis = Vector3(rand() % 100, rand() % 100, rand() % 100) / 100 * 3;
		p.clear();
		p.resize(20);
		for (int i = 0; i < p.size(); i++) {
			p[i] = Vector3(rand() % 100, rand() % 100, rand() % 100) / 100 + dis;
		}
		Eigen::MatrixXd  PS1(3, p.size());

		for (int i = 0; i < p.size(); i++) {
			PS1(0, i) = p[i][0];
			PS1(1, i) = p[i][1];
			PS1(2, i) = p[i][2];

		}

		Eigen::MatrixXd PCA1 = PS1 * PS1.transpose();
		Eigen::EigenSolver<Eigen::MatrixXd> eg1(PCA1);
		Eigen::VectorXcd ev1 = eg1.eigenvalues();

		minid = 0;

		for (int i = 1; i < 3; i++) {

			if (ev1[minid].real() >= ev1(i).real()) {//find the minimal eigenvalue
				minid = i;
			}
		}

		Eigen::VectorXcd v1 = eg1.eigenvectors().col(minid);

		normal[0] = v1[0].real(); normal[1] = v1[1].real(); normal[2] = v1[2].real();

		Eigen::Matrix4d M1, invM1;
		build_obb(p, normal, offset, M1, invM1);

		fout.open("D:\\vs\\fast_envelope\\obb\\points1.txt");

		for (int i = 0; i < p.size(); i++) {

			fout << std::setprecision(17) << p[i][0] << " " << p[i][1] << " " << p[i][2] << std::endl;

		}
		fout.close();
		fout.open("D:\\vs\\fast_envelope\\obb\\matrixes1.txt");

		for (int i = 0; i < 4; i++) {

			fout << std::setprecision(17) << M1(i, 0) << " " << M1(i, 1) << " " << M1(i, 2) << " " << M1(i, 3) << std::endl;

		}
		for (int i = 0; i < 4; i++) {

			fout << std::setprecision(17) << invM1(i, 0) << " " << invM1(i, 1) << " " << invM1(i, 2) << " " << invM1(i, 3) << std::endl;

		}

		fout.close();

		std::cout << "is intersected? " << obb_intersection(M, invM, M1, invM1) << std::endl;
	}
}


#include <CGAL/Simple_cartesian.h>
#include <CGAL/boost/graph/graph_traits_Linear_cell_complex_for_combinatorial_map.h>
#include <CGAL/boost/graph/properties_Linear_cell_complex_for_combinatorial_map.h>

#include <boost/graph/graph_concepts.hpp>
#include <CGAL/boost/graph/graph_concepts.h>
#include <CGAL/boost/graph/Euler_operations.h>

typedef CGAL::Simple_cartesian<double> Kernel;
typedef CGAL::Linear_cell_complex_traits<3, Kernel> MyTraits;
typedef CGAL::Linear_cell_complex_for_bgl_combinatorial_map_helper
            <2, 3, MyTraits>::type LCC;

typedef boost::graph_traits< LCC > Traits;
typedef Traits::edge_descriptor edge_descriptor;
typedef Traits::halfedge_descriptor halfedge_descriptor;
typedef Traits::vertex_descriptor vertex_descriptor;
typedef Traits::face_descriptor face_descriptor;
typedef LCC::Traits::Point Point_3;

template<typename Graph>
void concept_check_polyhedron() {
  boost::function_requires< boost::GraphConcept<LCC> >();
  boost::function_requires< boost::VertexListGraphConcept<LCC> >();
  boost::function_requires< boost::EdgeListGraphConcept<LCC> >();
  boost::function_requires< boost::IncidenceGraphConcept<LCC> >();
  boost::function_requires< boost::AdjacencyMatrixConcept<LCC> >();
  boost::function_requires< boost::BidirectionalGraphConcept<LCC> >();
  boost::function_requires< CGAL::HalfedgeGraphConcept<LCC> >();
  boost::function_requires< CGAL::HalfedgeListGraphConcept<LCC> >();
  boost::function_requires< CGAL::FaceGraphConcept<LCC> >();
  boost::function_requires< CGAL::FaceListGraphConcept<LCC> >();
  boost::function_requires< CGAL::MutableHalfedgeGraphConcept<LCC> >();
  boost::function_requires< CGAL::MutableFaceGraphConcept<LCC> >();

  boost::function_requires< boost::concepts::PropertyGraph<
    LCC, halfedge_descriptor, boost::halfedge_index_t> >();
  boost::function_requires< boost::concepts::ReadablePropertyGraph<
    LCC, edge_descriptor, boost::edge_index_t> >();
  boost::function_requires< boost::concepts::ReadablePropertyGraph<
     LCC, edge_descriptor, boost::edge_weight_t> >();
  boost::function_requires< boost::PropertyGraphConcept<
    LCC, vertex_descriptor, boost::vertex_point_t> >();
  boost::function_requires< boost::concepts::PropertyGraph<
    LCC, vertex_descriptor, boost::vertex_index_t> >();
  boost::function_requires< boost::concepts::PropertyGraph<
    LCC, face_descriptor, boost::face_index_t> >();

  // null
  boost::graph_traits<LCC>::null_vertex();
  boost::graph_traits<LCC>::null_halfedge();
  boost::graph_traits<LCC>::null_face();
}

template<typename LCC>
void runtime_check_halfedgegraph()
{
  // u        v
  // +--------+
  // |\      /|
  // | \ f2 / |
  // |  \y /  |
  // | f3\/ f1|
  // |   /\   |
  // |  /  \  |
  // | / f4 \ |
  // |/      \|
  // +--------+
  // w        x
  LCC p;
  vertex_descriptor
    u = add_vertex(Point_3(0,2,0), p),
    v = add_vertex(Point_3(2,2,0), p),
    w = add_vertex(Point_3(0,0,0), p),
    x = add_vertex(Point_3(2,0,0), p),
    y = add_vertex(Point_3(1,1,0), p);

  std::vector<vertex_descriptor> face;
  face.push_back(v); face.push_back(u); face.push_back(y);
  CGAL::Euler::add_face(face, p);

  face.clear();
  face.push_back(v); face.push_back(y); face.push_back(x);
  CGAL::Euler::add_face(face, p);

  face.clear();
  face.push_back(x); face.push_back(y); face.push_back(w);
  CGAL::Euler::add_face(face, p);

  face.clear();
  face.push_back(w); face.push_back(y); face.push_back(u);
  CGAL::Euler::add_face(face, p);

  std::cout<<"num_edges(p)"<<num_edges(p)<<std::endl;
  std::cout<<"num_halfedges(p)"<<num_halfedges(p)<<std::endl;
  std::cout<<"num_faces(p)"<<num_faces(p)<<std::endl;

  assert(num_edges(p) ==  8);
  assert(num_halfedges(p) == 16);
  assert(num_faces(p) == 4);
}

int main()
{
  concept_check_polyhedron<LCC>();
  runtime_check_halfedgegraph<LCC>();
  std::cerr << "done\n";
  return 0;
}


/*
@copyright Louis Dionne 2015
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_HANA_EXAMPLE_CPPCON_2014_MATRIX_MATRIX_HPP
#define BOOST_HANA_EXAMPLE_CPPCON_2014_MATRIX_MATRIX_HPP

#include <boost/hana/constant.hpp>
#include <boost/hana/core/operators.hpp>
#include <boost/hana/functional/on.hpp>
#include <boost/hana/functional/partial.hpp>
#include <boost/hana/fwd/comparable.hpp>
#include <boost/hana/fwd/group.hpp>
#include <boost/hana/fwd/monoid.hpp>
#include <boost/hana/fwd/ring.hpp>
#include <boost/hana/tuple.hpp>

#include <utility>

namespace cppcon {
    template <unsigned Rows, unsigned Columns>
    struct Matrix {
        struct hana {
            struct operators
                : boost::hana::operators::of<
                      boost::hana::Comparable
                    , boost::hana::Monoid
                    , boost::hana::Group
                    , boost::hana::Ring
                >
            { };
        };
    };

    template <unsigned Rows, unsigned Columns, typename Storage,
        typename = boost::hana::operators::adl
    >
    struct matrix_type {
        struct hana { using datatype = Matrix<Rows, Columns>; };

        Storage rows_;
        constexpr auto ncolumns() const
        { return boost::hana::length(boost::hana::head(rows_)); }

        constexpr auto nrows() const
        { return boost::hana::length(rows_); }

        constexpr auto size() const
        { return nrows() * ncolumns(); }

        template <typename I, typename J>
        constexpr decltype(auto) at(I i, J j) const
        { return boost::hana::at(j, boost::hana::at(i, rows_)); }
    };

    auto row = boost::hana::make_tuple;

    auto matrix = [](auto&& ...rows) -> decltype(auto) {
        using namespace boost::hana;
        auto storage = make<Tuple>(std::forward<decltype(rows)>(rows)...);
        auto ncolumns = length(head(storage));
        BOOST_HANA_CONSTANT_CHECK(
            all_of(tail(storage), [&](auto const& row) {
                return length(row) == ncolumns;
            })
        );

        return matrix_type<
            sizeof...(rows), value(ncolumns), decltype(storage)
        >{std::move(storage)};
    };

    auto vector = boost::hana::on(matrix, row);

    // More operations
    auto rows = [](auto&& m) -> decltype(auto) {
        return std::forward<decltype(m)>(m).rows_;
    };

    auto transpose = [](auto&& m) -> decltype(auto) {
        return boost::hana::unpack(
            boost::hana::unzip(rows(std::forward<decltype(m)>(m))),
            matrix
        );
    };

    auto columns = [](auto&& m) -> decltype(auto) {
        return rows(transpose(std::forward<decltype(m)>(m)));
    };

    auto element_wise = [](auto&& f) -> decltype(auto) {
        using namespace boost::hana;
        return [f(std::forward<decltype(f)>(f))](auto&& ...m) -> decltype(auto) {
            return unpack(
                zip.with(partial(zip.with, f),
                    rows(std::forward<decltype(m)>(m))...
                ),
                matrix
            );
        };
    };

    namespace detail {
        auto tuple_scalar_product = [](auto&& u, auto&& v) -> decltype(auto) {
            using namespace boost::hana;
            return sum(zip.with(mult,
                std::forward<decltype(u)>(u),
                std::forward<decltype(v)>(v)
            ));
        };
    }
} // end namespace cppcon

#endif // !BOOST_HANA_EXAMPLE_CPPCON_2014_MATRIX_MATRIX_HPP


// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

/*
 *   File "lstm_b_tapenade_generated.c" is generated by Tapenade 3.14 (r7259) from this file.
 *   To reproduce such a generation you can use Tapenade CLI
 *   (can be downloaded from http://www-sop.inria.fr/tropics/tapenade/downloading.html)
 *
 *   After installing use the next command to generate a file:
 *
 *      tapenade -b -o lstm_tapenade -head "lstm_objective(loss)/(main_params extra_params)" lstm.c
 *
 *   This will produce a file "lstm_tapenade_b.c" which content will be the same as the content of the file "lstm_b_tapenade_generated.c",
 *   except one-line header. Moreover a log-file "lstm_tapenade_b.msg" will be produced.
 *
 *   NOTE: the code in "lstm_b_tapenade_generated.c" is wrong and won't work.
 *         REPAIRED SOURCE IS STORED IN THE FILE "lstm_b.c".
 *         You can either use diff tool or read "lstm_b.c" header to figure out what changes was performed to fix the code.
 *
 *   NOTE: you can also use Tapenade web server (http://tapenade.inria.fr:8080/tapenade/index.jsp)
 *         for generating but the result can be slightly different.
 */

#include "../adbench/lstm.h"

extern "C" {
#include "lstm.h"

// UTILS
// Sigmoid on scalar
double sigmoid(double x)
{
    return 1.0 / (1.0 + exp(-x));
}

// log(sum(exp(x), 2))
double logsumexp(double const* vect, int sz)
{
    double sum = 0.0;
    int i;

    for (i = 0; i < sz; i++)
    {
        sum += exp(vect[i]);
    }

    sum += 2;
    return log(sum);
}

// LSTM OBJECTIVE
// The LSTM model
void lstm_model(
    int hsize,
    double const* __restrict weight,
    double const* __restrict bias,
    double* __restrict hidden,
    double* __restrict cell,
    double const* __restrict input
)
{
    // TODO NOTE THIS
    //__builtin_assume(hsize > 0);

    double* gates = (double*)malloc(4 * hsize * sizeof(double));
    double* forget = &(gates[0]);
    double* ingate = &(gates[hsize]);
    double* outgate = &(gates[2 * hsize]);
    double* change = &(gates[3 * hsize]);

    int i;
    // caching input
    // hidden (needed)
    for (i = 0; i < hsize; i++)
    {
        forget[i] = sigmoid(input[i] * weight[i] + bias[i]);
        ingate[i] = sigmoid(hidden[i] * weight[hsize + i] + bias[hsize + i]);
        outgate[i] = sigmoid(input[i] * weight[2 * hsize + i] + bias[2 * hsize + i]);
        change[i] = tanh(hidden[i] * weight[3 * hsize + i] + bias[3 * hsize + i]);
    }

    // caching cell (needed)
    for (i = 0; i < hsize; i++)
    {
        cell[i] = cell[i] * forget[i] + ingate[i] * change[i];
    }

    for (i = 0; i < hsize; i++)
    {
        hidden[i] = outgate[i] * tanh(cell[i]);
    }

    free(gates);
}

// Predict LSTM output given an input
void lstm_predict(
    int l,
    int b,
    double const* __restrict w,
    double const* __restrict w2,
    double* __restrict s,
    double const* __restrict x,
    double* __restrict x2
)
{
    int i;
    for (i = 0; i < b; i++)
    {
        x2[i] = x[i] * w2[i];
    }

    double* xp = x2;
    for (i = 0; i <= 2 * l * b - 1; i += 2 * b)
    {
        lstm_model(b, &(w[i * 4]), &(w[(i + b) * 4]), &(s[i]), &(s[i + b]), xp);
        xp = &(s[i]);
    }

    for (i = 0; i < b; i++)
    {
        x2[i] = xp[i] * w2[b + i] + w2[2 * b + i];
    }
}

// LSTM objective (loss function)
void lstm_objective(
    int l,
    int c,
    int b,
    double const* __restrict main_params,
    double const* __restrict extra_params,
    double* __restrict state,
    double const* __restrict sequence,
    double* __restrict loss
)
{
    int i, t;
    double total = 0.0;
    int count = 0;
    const double* input = &(sequence[0]);
    double* ypred = (double*)malloc(b * sizeof(double));
    double* ynorm = (double*)malloc(b * sizeof(double));
    const double* ygold;
    double lse;

    __builtin_assume(b>0);
    for (t = 0; t <= (c - 1) * b - 1; t += b)
    {
        lstm_predict(l, b, main_params, extra_params, state, input, ypred);
        lse = logsumexp(ypred, b);
        for (i = 0; i < b; i++)
        {
            ynorm[i] = ypred[i] - lse;
        }

        ygold = &(sequence[t + b]);
        for (i = 0; i < b; i++)
        {
            total += ygold[i] * ynorm[i];
        }

        count += b;
        input = ygold;
    }

    *loss = -total / count;

    free(ypred);
    free(ynorm);
}

extern int enzyme_const;
extern int enzyme_dup;
extern int enzyme_dupnoneed;
void __enzyme_autodiff(...) noexcept;

// *      tapenade -b -o lstm_tapenade -head "lstm_objective(loss)/(main_params extra_params)" lstm.c

void dlstm_objective(
    int l,
    int c,
    int b,
    double const* main_params,
    double* dmain_params,
    double const* extra_params,
    double* dextra_params,
    double* state,
    double const* sequence,
    double* loss,
    double* dloss
)
{
    __enzyme_autodiff(lstm_objective,
        enzyme_const, l,
        enzyme_const, c,
        enzyme_const, b,
        enzyme_dup, main_params, dmain_params,
        enzyme_dup, extra_params, dextra_params,
        enzyme_const, state,
        enzyme_const, sequence,
        enzyme_dupnoneed, loss, dloss
    );
}

}

//! Tapenade
extern "C" {

#include <adBuffer.h>

/*
  Differentiation of sigmoid in reverse (adjoint) mode:
   gradient     of useful results: sigmoid
   with respect to varying inputs: x
*/
// UTILS
// Sigmoid on scalar
void sigmoid_b(double x, double *xb, double sigmoidb) {
    double temp;
    double sigmoid;
    temp = exp(-x) + 1.0;
    *xb = exp(-x)*sigmoidb/(temp*temp);
}

// UTILS
// Sigmoid on scalar
double sigmoid_nodiff(double x) {
    return 1.0/(1.0+exp(-x));
}

/*
  Differentiation of logsumexp in reverse (adjoint) mode:
   gradient     of useful results: logsumexp *vect
   with respect to varying inputs: *vect
   Plus diff mem management of: vect:in
*/
// log(sum(exp(x), 2))
void logsumexp_b(const double *vect, double *vectb, int sz, double logsumexpb)
{
    double sum = 0.0;
    double sumb = 0.0;
    int i;
    double logsumexp;
    for (i = 0; i < sz; ++i)
        sum = sum + exp(vect[i]);
    sum = sum + 2;
    sumb = logsumexpb/sum;
    for (i = sz-1; i > -1; --i)
        vectb[i] = vectb[i] + exp(vect[i])*sumb;
}

// log(sum(exp(x), 2))
double logsumexp_nodiff(const double *vect, int sz) {
    double sum = 0.0;
    int i;
    for (i = 0; i < sz; ++i)
        sum += exp(vect[i]);
    sum += 2;
    return log(sum);
}

/*
  Differentiation of lstm_model in reverse (adjoint) mode:
   gradient     of useful results: alloc(*gates) *cell *bias *hidden
                *weight *input
   with respect to varying inputs: alloc(*gates) *cell *bias *hidden
                *weight *input
   Plus diff mem management of: cell:in bias:in hidden:in weight:in
                input:in
*/
// LSTM OBJECTIVE
// The LSTM model
void lstm_model_b(int hsize, const double *weight, double *weightb, const
        double *bias, double *biasb, double *hidden, double *hiddenb, double *
        cell, double *cellb, const double *input, double *inputb) {
    double *gates;
    double *gatesb;
    double arg1;
    double arg1b;
    int ii1;
    double temp;
    double tempb;
    gatesb = (double *)malloc(4*hsize*sizeof(double));
    for (ii1 = 0; ii1 < 4*hsize; ++ii1)
        gatesb[ii1] = 0.0;
    gates = (double *)malloc(4*hsize*sizeof(double));
    double *forget = &(gates[0]);
    double *forgetb = &(gatesb[0]);
    double *ingate = &(gates[hsize]);
    double *ingateb = &(gatesb[hsize]);
    double *outgate = &(gates[2*hsize]);
    double *outgateb = &(gatesb[2*hsize]);
    double *change = &(gates[3*hsize]);
    double *changeb = &(gatesb[3*hsize]);
    int i;
    for (i = 0; i < hsize; ++i) {
        arg1 = input[i]*weight[i] + bias[i];
        forget[i] = sigmoid_nodiff(arg1);
        arg1 = hidden[i]*weight[hsize+i] + bias[hsize + i];
        ingate[i] = sigmoid_nodiff(arg1);
        arg1 = input[i]*weight[2*hsize+i] + bias[2*hsize + i];
        outgate[i] = sigmoid_nodiff(arg1);
        change[i] = tanh(hidden[i]*weight[3*hsize+i] + bias[3*hsize + i]);
    }
    for (i = 0; i < hsize; ++i) {
        pushReal8(cell[i]);
        cell[i] = cell[i]*forget[i] + ingate[i]*change[i];
    }
    for (i = hsize-1; i > -1; --i) {
        outgateb[i] = outgateb[i] + tanh(cell[i])*hiddenb[i];
        cellb[i] = cellb[i] + outgate[i]*(1.0-tanh(cell[i])*tanh(cell[i]))*
            hiddenb[i];
        hiddenb[i] = 0.0;
    }
    for (i = hsize-1; i > -1; --i) {
        popReal8(&(cell[i]));
        forgetb[i] = forgetb[i] + cell[i]*cellb[i];
        ingateb[i] = ingateb[i] + change[i]*cellb[i];
        changeb[i] = changeb[i] + ingate[i]*cellb[i];
        cellb[i] = forget[i]*cellb[i];
    }
    for (i = hsize-1; i > -1; --i) {
        temp = weight[3*hsize + i];
        tempb = (1.0-tanh(hidden[i]*temp+bias[3*hsize+i])*tanh(hidden[i]*temp+
            bias[3*hsize+i]))*changeb[i];
        hiddenb[i] = hiddenb[i] + temp*tempb;
        weightb[3*hsize + i] = weightb[3*hsize + i] + hidden[i]*tempb;
        biasb[3*hsize + i] = biasb[3*hsize + i] + tempb;
        changeb[i] = 0.0;
        arg1 = input[i]*weight[2*hsize+i] + bias[2*hsize + i];
        sigmoid_b(arg1, &arg1b, outgateb[i]);
        outgateb[i] = 0.0;
        inputb[i] = inputb[i] + weight[2*hsize+i]*arg1b;
        weightb[2*hsize + i] = weightb[2*hsize + i] + input[i]*arg1b;
        biasb[2*hsize + i] = biasb[2*hsize + i] + arg1b;
        arg1 = hidden[i]*weight[hsize+i] + bias[hsize + i];
        sigmoid_b(arg1, &arg1b, ingateb[i]);
        ingateb[i] = 0.0;
        hiddenb[i] = hiddenb[i] + weight[hsize+i]*arg1b;
        weightb[hsize + i] = weightb[hsize + i] + hidden[i]*arg1b;
        biasb[hsize + i] = biasb[hsize + i] + arg1b;
        arg1 = input[i]*weight[i] + bias[i];
        sigmoid_b(arg1, &arg1b, forgetb[i]);
        forgetb[i] = 0.0;
        inputb[i] = inputb[i] + weight[i]*arg1b;
        weightb[i] = weightb[i] + input[i]*arg1b;
        biasb[i] = biasb[i] + arg1b;
    }
    free(gates);
    free(gatesb);
}

// LSTM OBJECTIVE
// The LSTM model
void lstm_model_nodiff(int hsize, const double *weight, const double *bias,
        double *hidden, double *cell, const double *input) {
    double *gates;
    double arg1;
    gates = (double *)malloc(4*hsize*sizeof(double));
    double *forget = &(gates[0]);
    double *ingate = &(gates[hsize]);
    double *outgate = &(gates[2*hsize]);
    double *change = &(gates[3*hsize]);
    int i;
    for (i = 0; i < hsize; ++i) {
        arg1 = input[i]*weight[i] + bias[i];
        forget[i] = sigmoid_nodiff(arg1);
        arg1 = hidden[i]*weight[hsize+i] + bias[hsize + i];
        ingate[i] = sigmoid_nodiff(arg1);
        arg1 = input[i]*weight[2*hsize+i] + bias[2*hsize + i];
        outgate[i] = sigmoid_nodiff(arg1);
        change[i] = tanh(hidden[i]*weight[3*hsize+i] + bias[3*hsize + i]);
    }
    for (i = 0; i < hsize; ++i)
        cell[i] = cell[i]*forget[i] + ingate[i]*change[i];
    for (i = 0; i < hsize; ++i)
        hidden[i] = outgate[i]*tanh(cell[i]);
    free(gates);
}

/*
  Differentiation of lstm_predict in reverse (adjoint) mode:
   gradient     of useful results: alloc(*gates) *s *w *w2 *x2
   with respect to varying inputs: alloc(*gates) *s *w *w2 *x2
   Plus diff mem management of: s:in w:in w2:in x2:in
*/
// Predict LSTM output given an input
void lstm_predict_b(int l, int b, const double *w, double *wb, const double *
        w2, double *w2b, double *s, double *sb, const double *x, double *x2,
        double *x2b) {
    int i;
    double tmp;
    double tmpb;
    for (i = 0; i < b; ++i) {
        pushReal8(x2[i]);
        x2[i] = x[i]*w2[i];
    }
    double *xp = x2;
    double *xpb = x2b;
    for (i = 0; i <= 2*l*b-1; i += 2*b) {
        pushReal8Array(s + i, 2 * b); /* TFIX */
        lstm_model_nodiff(b, &(w[i*4]), &(w[(i+b)*4]), &(s[i]), &(s[i + b]),
                          xp);
        pushPointer8(xpb);
        xpb = &(sb[i]);
        pushPointer8(xp);
        xp = &(s[i]);
    }
    for (i = 0; i < b; ++i) {
        tmp = xp[i]*w2[b+i] + w2[2*b + i];
        pushReal8(x2[i]);
        x2[i] = tmp;
    }
    for (i = b-1; i > -1; --i) {
        popReal8(&(x2[i]));
        tmpb = x2b[i];
        x2b[i] = 0.0;
        xpb[i] = xpb[i] + w2[b+i]*tmpb;
        w2b[b + i] = w2b[b + i] + xp[i]*tmpb;
        w2b[2*b + i] = w2b[2*b + i] + tmpb;
    }
    for (i = 2*l*b-(2*l*b-1)%(2*b)-1; i >= 0; i += -(2*b)) { /* TFIX */
        popPointer8((void **)&xp);
        popPointer8((void **)&xpb);
        popReal8Array(s + i, 2 * b); /* TFIX */
        lstm_model_b(b, &(w[i*4]), &(wb[i*4]), &(w[(i+b)*4]), &(wb[(i+b)*4]),
                     &(s[i]), &(sb[i]), &(s[i + b]), &(sb[i + b]), xp, xpb);
    }
    for (i = b-1; i > -1; --i) {
        popReal8(&(x2[i]));
        w2b[i] = w2b[i] + x[i]*x2b[i];
        x2b[i] = 0.0;
    }
}

// Predict LSTM output given an input
void lstm_predict_nodiff(int l, int b, const double *w, const double *w2,
        double *s, const double *x, double *x2) {
    int i;
    for (i = 0; i < b; ++i)
        x2[i] = x[i]*w2[i];
    double *xp = x2;
    for (i = 0; i <= 2*l*b-1; i += 2*b) {
        lstm_model_nodiff(b, &(w[i*4]), &(w[(i+b)*4]), &(s[i]), &(s[i + b]),
                          xp);
        xp = &(s[i]);
    }
    for (i = 0; i < b; ++i)
        x2[i] = xp[i]*w2[b+i] + w2[2*b + i];
}

/*
  Differentiation of lstm_objective in reverse (adjoint) mode:
   gradient     of useful results: *loss
   with respect to varying inputs: *main_params *extra_params
                *loss
   RW status of diff variables: *main_params:out *extra_params:out
                *loss:in-out
   Plus diff mem management of: extra_params:in loss:in
*/
// LSTM objective (loss function)
void lstm_objective_b(int l, int c, int b, const double *main_params, double *
        main_paramsb, const double *extra_params, double *extra_paramsb,
        double *state, const double *sequence, double *loss, double *lossb) {
    int i, t;
    double total = 0.0;
    double totalb = 0.0;
    int count = 0;
    const double *input = &(sequence[0]);
    double *ypred;
    double *ypredb;
    int ii1;
    int branch;
    double* stateb = (double*)malloc(2 * l * b * sizeof(double)); /* TFIX */
    ypredb = (double *)malloc(b*sizeof(double));
    for (ii1 = 0; ii1 < b; ++ii1)
        ypredb[ii1] = 0.0;
    ypred = (double *)malloc(b*sizeof(double));
    double *ynorm;
    double *ynormb;
    ynormb = (double *)malloc(b*sizeof(double));
    for (ii1 = 0; ii1 < b; ++ii1)
        ynormb[ii1] = 0.0;
    ynorm = (double *)malloc(b*sizeof(double));
    const double* ygold = NULL; /* TFIX */
    double lse;
    double lseb;
    for (t = 0; t <= (c-1)*b-1; t += b) {
        if (ypred) {
            pushReal8Array(ypred, b); /* TFIX */
            pushControl1b(1);
        } else
            pushControl1b(0);
        pushReal8Array(state, 2 * b * l); /* TFIX */
        lstm_predict_nodiff(l, b, main_params, extra_params, state, input,
                            ypred);
        pushPointer8((void*)ygold);
        ygold = &(sequence[t + b]);
        count = count + b;
        pushPointer8((void*)input);
        input = ygold;
    }
    totalb = -(*lossb/count);
    *lossb = 0.0;
    for (ii1 = 0; ii1 < 8 * l * b; ii1++) /* TFIX */
        main_paramsb[ii1] = 0.0;
    for (ii1 = 0; ii1 < 3 * b; ii1++) /* TFIX */
        extra_paramsb[ii1] = 0.0;
    for (t = 0; t < 2 * l * b; t++) /* TFIX */
        stateb[t] = 0.0;
    for (t = (c-1)*b-((c-1)*b-1)%b-1; t >= 0; t += -b) { /* TFIX */
        popPointer8((void **)&input);
        for (i = b-1; i > -1; --i)
            ynormb[i] = ynormb[i] + ygold[i]*totalb;
        popPointer8((void **)&ygold);
        lseb = 0.0;
        for (i = b-1; i > -1; --i) {
            ypredb[i] = ypredb[i] + ynormb[i];
            lseb = lseb - ynormb[i];
            ynormb[i] = 0.0;
        }
        logsumexp_b(ypred, ypredb, b, lseb);
        popReal8Array(state, 2 * b * l); /* TFIX */
        popControl1b(&branch);
        if (branch == 1)
            popReal8Array(ypred, b); /* TFIX */
        lstm_predict_b(l, b, main_params, main_paramsb, extra_params,
                       extra_paramsb, state, stateb, input, ypred, ypredb);
    }
    free(ynorm);
    free(ynormb);
    free(ypred);
    free(ypredb);
    free(stateb); /* TFIX */ // Added to dispose memory allocated in repaired code
}

}

#if 1
//! Adept
#include <adept_source.h>
#include <adept.h>
#include <adept_arrays.h>
using adept::adouble;
using adept::aVector;

namespace adeptTest {
// Sigmoid on scalar
template<typename T>
T sigmoid(T x) {
    return adouble(1) / (adouble(1) + exp(-x));
}

// log(sum(exp(x), 2))
template<typename T>
T logsumexp(const T* vect, int sz) {
    T sum = 0.0;
    for (int i = 0; i < sz; ++i)
        sum += exp(vect[i]);
    sum += adouble(2);
    return log(sum);
}

// LSTM OBJECTIVE

// The LSTM model
template<typename T>
void lstm_model(
    int hsize,
    T* weight,
    T* bias,
    T* hidden,
    T* cell,
    T* input
)
{

    T* gates = new T[4*hsize];
    T* forget = &(gates[0]);
    T* ingate = &(gates[hsize]);
    T* outgate = &(gates[2 * hsize]);
    T* change = &(gates[3 * hsize]);

    int i;
    // caching input
    // hidden (needed)
    for (i = 0; i < hsize; i++)
    {
        forget[i] = sigmoid<adouble>(input[i] * weight[i] + bias[i]);
        ingate[i] = sigmoid<adouble>(hidden[i] * weight[hsize + i] + bias[hsize + i]);
        outgate[i] = sigmoid<adouble>(input[i] * weight[2 * hsize + i] + bias[2 * hsize + i]);
        change[i] = tanh(hidden[i] * weight[3 * hsize + i] + bias[3 * hsize + i]);
    }

    // caching cell (needed)
    for (i = 0; i < hsize; i++)
    {
        cell[i] = cell[i] * forget[i] + ingate[i] * change[i];
    }

    for (i = 0; i < hsize; i++)
    {
        hidden[i] = outgate[i] * tanh(cell[i]);
    }

    delete[] gates;
}

// Predict LSTM output given an input
template<typename T>
void lstm_predict(
    int l,
    int b,
    T* w,
    T* w2,
    T* s,
    T* x,
    T* x2
)
{
    int i;
    for (i = 0; i < b; i++)
    {
        x2[i] = x[i] * w2[i];
    }

    T* xp = x2;
    for (i = 0; i <= 2 * l * b - 1; i += 2 * b)
    {
        lstm_model(b, &(w[i * 4]), &(w[(i + b) * 4]), &(s[i]), &(s[i + b]), xp);
        xp = &(s[i]);
    }

    for (i = 0; i < b; i++)
    {
        x2[i] = xp[i] * w2[b + i] + w2[2 * b + i];
    }
}

// LSTM objective (loss function)
template<typename T>
void lstm_objective(
    int l,
    int c,
    int b,
    T * __restrict main_params,
    T * __restrict extra_params,
    T* __restrict state,
    T * __restrict sequence,
    T* __restrict loss
)
{
    int i, t;
    T total = 0.0;
    int count = 0;
    T* input = &(sequence[0]);
    T* ypred = new T[b];
    T* ynorm = new T[b];
    T* ygold;
    T lse;

    __builtin_assume(b>0);
    for (t = 0; t <= (c - 1) * b - 1; t += b)
    {
        lstm_predict(l, b, main_params, extra_params, state, input, ypred);
        lse = logsumexp(ypred, b);
        for (i = 0; i < b; i++)
        {
            ynorm[i] = ypred[i] - lse;
        }

        ygold = &(sequence[t + b]);
        for (i = 0; i < b; i++)
        {
            total += ygold[i] * ynorm[i];
        }

        count += b;
        input = ygold;
    }

    *loss = -total / adouble(count);

    delete[] ypred;
    delete[] ynorm;
}
};

// Note ADBench did not have an adept impl
void adept_dlstm_objective(int l, int c, int b, const double *main_params, double *
        main_paramsb, const double *extra_params, double *extra_paramsb,
        double *state, const double *sequence, double *loss, double *lossb) {

    int main_sz = 2 * l * 4 * b;
    int extra_sz = 3 * b;
    int state_sz = 2 * l * b;
    int seq_sz = c* b;

  adept::Stack stack;

  adouble *amain = new adouble[main_sz];
  adouble *aextra = new adouble[extra_sz];
  adouble *astate = new adouble[state_sz];
  adouble *aseq = new adouble[seq_sz];

      adept::set_values(amain, main_sz, main_params);
      adept::set_values(aextra, extra_sz, extra_params);
      adept::set_values(astate, state_sz, state);
      adept::set_values(aseq, seq_sz, sequence);

      adouble aloss;

      stack.new_recording();
      adouble aerr;

      adeptTest::lstm_objective(l, c, b, amain, aextra, astate, aseq, &aloss);
      aloss.set_gradient(1.); // only one J row here

      stack.compute_adjoint();

      adept::get_gradients(amain, main_sz, main_paramsb);
      adept::get_gradients(aextra, extra_sz, extra_paramsb);

}
#endif


//////////////////////////////////////////////////////////////////////////////
//
// (C) Copyright Ion Gaztanaga 2015-2016.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
// See http://www.boost.org/libs/move for documentation.
//
//////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_MOVE_MERGE_HPP
#define BOOST_MOVE_MERGE_HPP

#include <boost/move/algo/move.hpp>
#include <boost/move/adl_move_swap.hpp>
#include <boost/move/algo/detail/basic_op.hpp>
#include <boost/move/detail/iterator_traits.hpp>
#include <boost/move/detail/destruct_n.hpp>
#include <boost/move/algo/predicate.hpp>
#include <boost/move/detail/iterator_to_raw_pointer.hpp>
#include <boost/assert.hpp>

namespace lslboost {
namespace movelib {

// @cond

/*
template<typename Unsigned>
inline Unsigned gcd(Unsigned x, Unsigned y)
{
   if(0 == ((x &(x-1)) | (y & (y-1)))){
      return x < y ? x : y;
   }
   else{
      do
      {
         Unsigned t = x % y;
         x = y;
         y = t;
      } while (y);
      return x;
   }
}
*/

//Modified version from "An Optimal In-Place Array Rotation Algorithm", Ching-Kuang Shene
template<typename Unsigned>
Unsigned gcd(Unsigned x, Unsigned y)
{
   if(0 == ((x &(x-1)) | (y & (y-1)))){
      return x < y ? x : y;
   }
   else{
      Unsigned z = 1;
      while((!(x&1)) & (!(y&1))){
         z <<=1, x>>=1, y>>=1;
      }
      while(x && y){
         if(!(x&1))
            x >>=1;
         else if(!(y&1))
            y >>=1;
         else if(x >=y)
            x = (x-y) >> 1;
         else
            y = (y-x) >> 1;
      }
      return z*(x+y);
   }
}

template<typename RandIt>
RandIt rotate_gcd(RandIt first, RandIt middle, RandIt last)
{
   typedef typename iterator_traits<RandIt>::size_type size_type;
   typedef typename iterator_traits<RandIt>::value_type value_type;

   if(first == middle)
      return last;
   if(middle == last)
      return first;
   const size_type middle_pos = size_type(middle - first);
   RandIt ret = last - middle_pos;
   if (middle == ret){
      lslboost::adl_move_swap_ranges(first, middle, middle);
   }
   else{
      const size_type length = size_type(last - first);
      for( RandIt it_i(first), it_gcd(it_i + gcd(length, middle_pos))
         ; it_i != it_gcd
         ; ++it_i){
         value_type temp(lslboost::move(*it_i));
         RandIt it_j = it_i;
         RandIt it_k = it_j+middle_pos;
         do{
            *it_j = lslboost::move(*it_k);
            it_j = it_k;
            size_type const left = size_type(last - it_j);
            it_k = left > middle_pos ? it_j + middle_pos : first + (middle_pos - left);
         } while(it_k != it_i);
         *it_j = lslboost::move(temp);
      }
   }
   return ret;
}

template <class RandIt, class T, class Compare>
RandIt lower_bound
   (RandIt first, const RandIt last, const T& key, Compare comp)
{
   typedef typename iterator_traits
      <RandIt>::size_type size_type;
   size_type len = size_type(last - first);
   RandIt middle;

   while (len) {
      size_type step = len >> 1;
      middle = first;
      middle += step;

      if (comp(*middle, key)) {
         first = ++middle;
         len -= step + 1;
      }
      else{
         len = step;
      }
   }
   return first;
}

template <class RandIt, class T, class Compare>
RandIt upper_bound
   (RandIt first, const RandIt last, const T& key, Compare comp)
{
   typedef typename iterator_traits
      <RandIt>::size_type size_type;
   size_type len = size_type(last - first);
   RandIt middle;

   while (len) {
      size_type step = len >> 1;
      middle = first;
      middle += step;

      if (!comp(key, *middle)) {
         first = ++middle;
         len -= step + 1;
      }
      else{
         len = step;
      }
   }
   return first;
}

template<class RandIt, class Compare, class Op>
void op_merge_left( RandIt buf_first
                    , RandIt first1
                    , RandIt const last1
                    , RandIt const last2
                    , Compare comp
                    , Op op)
{
   for(RandIt first2=last1; first2 != last2; ++buf_first){
      if(first1 == last1){
         op(forward_t(), first2, last2, buf_first);
         return;
      }
      else if(comp(*first2, *first1)){
         op(first2, buf_first);
         ++first2;
      }
      else{
         op(first1, buf_first);
         ++first1;
      }
   }
   if(buf_first != first1){//In case all remaining elements are in the same place
                           //(e.g. buffer is exactly the size of the second half
                           //and all elements from the second half are less)
      op(forward_t(), first1, last1, buf_first);
   }
}

// [buf_first, first1) -> buffer
// [first1, last1) merge [last1,last2) -> [buf_first,buf_first+(last2-first1))
// Elements from buffer are moved to [last2 - (first1-buf_first), last2)
// Note: distance(buf_first, first1) >= distance(last1, last2), so no overlapping occurs
template<class RandIt, class Compare>
void merge_left
   (RandIt buf_first, RandIt first1, RandIt const last1, RandIt const last2, Compare comp)
{
   op_merge_left(buf_first, first1, last1, last2, comp, move_op());
}

// [buf_first, first1) -> buffer
// [first1, last1) merge [last1,last2) -> [buf_first,buf_first+(last2-first1))
// Elements from buffer are swapped to [last2 - (first1-buf_first), last2)
// Note: distance(buf_first, first1) >= distance(last1, last2), so no overlapping occurs
template<class RandIt, class Compare>
void swap_merge_left
   (RandIt buf_first, RandIt first1, RandIt const last1, RandIt const last2, Compare comp)
{
   op_merge_left(buf_first, first1, last1, last2, comp, swap_op());
}

template<class RandIt, class Compare, class Op>
void op_merge_right
   (RandIt const first1, RandIt last1, RandIt last2, RandIt buf_last, Compare comp, Op op)
{
   RandIt const first2 = last1;
   while(first1 != last1){
      if(last2 == first2){
         op(backward_t(), first1, last1, buf_last);
         return;
      }
      --last2;
      --last1;
      --buf_last;
      if(comp(*last2, *last1)){
         op(last1, buf_last);
         ++last2;
      }
      else{
         op(last2, buf_last);
         ++last1;
      }
   }
   if(last2 != buf_last){  //In case all remaining elements are in the same place
                           //(e.g. buffer is exactly the size of the first half
                           //and all elements from the second half are less)
      op(backward_t(), first2, last2, buf_last);
   }
}

// [last2, buf_last) - buffer
// [first1, last1) merge [last1,last2) -> [first1+(buf_last-last2), buf_last)
// Note: distance[last2, buf_last) >= distance[first1, last1), so no overlapping occurs
template<class RandIt, class Compare>
void merge_right
   (RandIt first1, RandIt last1, RandIt last2, RandIt buf_last, Compare comp)
{
   op_merge_right(first1, last1, last2, buf_last, comp, move_op());
}

// [last2, buf_last) - buffer
// [first1, last1) merge [last1,last2) -> [first1+(buf_last-last2), buf_last)
// Note: distance[last2, buf_last) >= distance[first1, last1), so no overlapping occurs
template<class RandIt, class Compare>
void swap_merge_right
   (RandIt first1, RandIt last1, RandIt last2, RandIt buf_last, Compare comp)
{
   op_merge_right(first1, last1, last2, buf_last, comp, swap_op());
}

//Complexity: min(len1,len2)^2 + max(len1,len2)
template<class RandIt, class Compare>
void merge_bufferless_ON2(RandIt first, RandIt middle, RandIt last, Compare comp)
{
   if((middle - first) < (last - middle)){
      while(first != middle){
         RandIt const old_last1 = middle;
         middle = lslboost::movelib::lower_bound(middle, last, *first, comp);
         first = rotate_gcd(first, old_last1, middle);
         if(middle == last){
            break;
         }
         do{
            ++first;
         } while(first != middle && !comp(*middle, *first));
      }
   }
   else{
      while(middle != last){
         RandIt p = lslboost::movelib::upper_bound(first, middle, last[-1], comp);
         last = rotate_gcd(p, middle, last);
         middle = p;
         if(middle == first){
            break;
         }
         --p;
         do{
            --last;
         } while(middle != last && !comp(last[-1], *p));
      }
   }
}

static const std::size_t MergeBufferlessONLogNRotationThreshold = 32;

template <class RandIt, class Distance, class Compare>
void merge_bufferless_ONlogN_recursive
   (RandIt first, RandIt middle, RandIt last, Distance len1, Distance len2, Compare comp)
{
   typedef typename iterator_traits<RandIt>::size_type size_type;

   while(1) {
      //trivial cases
      if (!len2) {
         return;
      }
      else if (!len1) {
         return;
      }
      else if (size_type(len1 | len2) == 1u) {
         if (comp(*middle, *first))
            adl_move_swap(*first, *middle);
         return;
      }
      else if(size_type(len1+len2) < MergeBufferlessONLogNRotationThreshold){
         merge_bufferless_ON2(first, middle, last, comp);
         return;
      }

      RandIt first_cut = first;
      RandIt second_cut = middle;
      Distance len11 = 0;
      Distance len22 = 0;
      if (len1 > len2) {
         len11 = len1 / 2;
         first_cut +=  len11;
         second_cut = lslboost::movelib::lower_bound(middle, last, *first_cut, comp);
         len22 = size_type(second_cut - middle);
      }
      else {
         len22 = len2 / 2;
         second_cut += len22;
         first_cut = lslboost::movelib::upper_bound(first, middle, *second_cut, comp);
         len11 = size_type(first_cut - first);
      }
      RandIt new_middle = rotate_gcd(first_cut, middle, second_cut);

      //Avoid one recursive call doing a manual tail call elimination on the biggest range
      const Distance len_internal = len11+len22;
      if( len_internal < (len1 + len2 - len_internal) ) {
         merge_bufferless_ONlogN_recursive(first,      first_cut,  new_middle, len11,        len22,        comp);
         first = new_middle;
         middle = second_cut;
         len1 -= len11;
         len2 -= len22;
      }
      else {
         merge_bufferless_ONlogN_recursive(new_middle, second_cut, last,       len1 - len11, len2 - len22, comp);
         middle = first_cut;
         last = new_middle;
         len1 = len11;
         len2 = len22;
      }
   }
}

//Complexity: NlogN
template<class RandIt, class Compare>
void merge_bufferless_ONlogN(RandIt first, RandIt middle, RandIt last, Compare comp)
{
   merge_bufferless_ONlogN_recursive
      (first, middle, last, middle - first, last - middle, comp);
}

template<class RandIt, class Compare>
void merge_bufferless(RandIt first, RandIt middle, RandIt last, Compare comp)
{
   #define BOOST_ADAPTIVE_MERGE_NLOGN_MERGE
   #ifdef BOOST_ADAPTIVE_MERGE_NLOGN_MERGE
   merge_bufferless_ONlogN(first, middle, last, comp);
   #else
   merge_bufferless_ON2(first, middle, last, comp);
   #endif   //BOOST_ADAPTIVE_MERGE_NLOGN_MERGE
}

// [r_first, r_last) are already in the right part of the destination range.
template <class Compare, class InputIterator, class InputOutIterator, class Op>
void op_merge_with_right_placed
   ( InputIterator first, InputIterator last
   , InputOutIterator dest_first, InputOutIterator r_first, InputOutIterator r_last
   , Compare comp, Op op)
{
   BOOST_ASSERT((last - first) == (r_first - dest_first));
   while ( first != last ) {
      if (r_first == r_last) {
         InputOutIterator end = op(forward_t(), first, last, dest_first);
         BOOST_ASSERT(end == r_last);
         (void)end;
         return;
      }
      else if (comp(*r_first, *first)) {
         op(r_first, dest_first);
         ++r_first;
      }
      else {
         op(first, dest_first);
         ++first;
      }
      ++dest_first;
   }
   // Remaining [r_first, r_last) already in the correct place
}

template <class Compare, class InputIterator, class InputOutIterator>
void swap_merge_with_right_placed
   ( InputIterator first, InputIterator last
   , InputOutIterator dest_first, InputOutIterator r_first, InputOutIterator r_last
   , Compare comp)
{
   op_merge_with_right_placed(first, last, dest_first, r_first, r_last, comp, swap_op());
}

// [first, last) are already in the right part of the destination range.
template <class Compare, class Op, class BidirIterator, class BidirOutIterator>
void op_merge_with_left_placed
   ( BidirOutIterator const first, BidirOutIterator last, BidirOutIterator dest_last
   , BidirIterator const r_first, BidirIterator r_last
   , Compare comp, Op op)
{
   BOOST_ASSERT((dest_last - last) == (r_last - r_first));
   while( r_first != r_last ) {
      if(first == last) {
         BidirOutIterator res = op(backward_t(), r_first, r_last, dest_last);
         BOOST_ASSERT(last == res);
         (void)res;
         return;
      }
      --r_last;
      --last;
      if(comp(*r_last, *last)){
         ++r_last;
         --dest_last;
         op(last, dest_last);
      }
      else{
         ++last;
         --dest_last;
         op(r_last, dest_last);
      }
   }
   // Remaining [first, last) already in the correct place
}

// @endcond

// [irst, last) are already in the right part of the destination range.
template <class Compare, class BidirIterator, class BidirOutIterator>
void merge_with_left_placed
   ( BidirOutIterator const first, BidirOutIterator last, BidirOutIterator dest_last
   , BidirIterator const r_first, BidirIterator r_last
   , Compare comp)
{
   op_merge_with_left_placed(first, last, dest_last, r_first, r_last, comp, move_op());
}

// [r_first, r_last) are already in the right part of the destination range.
template <class Compare, class InputIterator, class InputOutIterator>
void merge_with_right_placed
   ( InputIterator first, InputIterator last
   , InputOutIterator dest_first, InputOutIterator r_first, InputOutIterator r_last
   , Compare comp)
{
   op_merge_with_right_placed(first, last, dest_first, r_first, r_last, comp, move_op());
}

// [r_first, r_last) are already in the right part of the destination range.
// [dest_first, r_first) is uninitialized memory
template <class Compare, class InputIterator, class InputOutIterator>
void uninitialized_merge_with_right_placed
   ( InputIterator first, InputIterator last
   , InputOutIterator dest_first, InputOutIterator r_first, InputOutIterator r_last
   , Compare comp)
{
   BOOST_ASSERT((last - first) == (r_first - dest_first));
   typedef typename iterator_traits<InputOutIterator>::value_type value_type;
   InputOutIterator const original_r_first = r_first;

   destruct_n<value_type, InputOutIterator> d(dest_first);

   while ( first != last && dest_first != original_r_first ) {
      if (r_first == r_last) {
         for(; dest_first != original_r_first; ++dest_first, ++first){
            ::new((iterator_to_raw_pointer)(dest_first)) value_type(::lslboost::move(*first));
            d.incr();
         }
         d.release();
         InputOutIterator end = ::lslboost::move(first, last, original_r_first);
         BOOST_ASSERT(end == r_last);
         (void)end;
         return;
      }
      else if (comp(*r_first, *first)) {
         ::new((iterator_to_raw_pointer)(dest_first)) value_type(::lslboost::move(*r_first));
         d.incr();
         ++r_first;
      }
      else {
         ::new((iterator_to_raw_pointer)(dest_first)) value_type(::lslboost::move(*first));
         d.incr();
         ++first;
      }
      ++dest_first;
   }
   d.release();
   merge_with_right_placed(first, last, original_r_first, r_first, r_last, comp);
}

/*
// [r_first, r_last) are already in the right part of the destination range.
// [dest_first, r_first) is uninitialized memory
template <class Compare, class BidirOutIterator, class BidirIterator>
void uninitialized_merge_with_left_placed
   ( BidirOutIterator dest_first, BidirOutIterator r_first, BidirOutIterator r_last
   , BidirIterator first, BidirIterator last
   , Compare comp)
{
   BOOST_ASSERT((last - first) == (r_last - r_first));
   typedef typename iterator_traits<BidirOutIterator>::value_type value_type;
   BidirOutIterator const original_r_last = r_last;

   destruct_n<value_type> d(&*dest_last);

   while ( first != last && dest_first != original_r_first ) {
      if (r_first == r_last) {
         for(; dest_first != original_r_first; ++dest_first, ++first){
            ::new(&*dest_first) value_type(::lslboost::move(*first));
            d.incr();
         }
         d.release();
         BidirOutIterator end = ::lslboost::move(first, last, original_r_first);
         BOOST_ASSERT(end == r_last);
         (void)end;
         return;
      }
      else if (comp(*r_first, *first)) {
         ::new(&*dest_first) value_type(::lslboost::move(*r_first));
         d.incr();
         ++r_first;
      }
      else {
         ::new(&*dest_first) value_type(::lslboost::move(*first));
         d.incr();
         ++first;
      }
      ++dest_first;
   }
   d.release();
   merge_with_right_placed(first, last, original_r_first, r_first, r_last, comp);
}
*/

}  //namespace movelib {
}  //namespace lslboost {

#endif   //#define BOOST_MOVE_MERGE_HPP


/*
LICENSE: see isogeometric_application/LICENSE.txt
*/

//
//   Project Name:        Kratos
//   Last modified by:    $Author: hbui $
//   Date:                $Date: Nov 24, 2017 $
//   Revision:            $Revision: 1.0 $
//
//

// System includes
#include <string>

// External includes
#include <boost/foreach.hpp>
#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>
#include <boost/python/operators.hpp>

// Project includes
#include "includes/define.h"
#include "custom_utilities/multipatch.h"
#include "custom_utilities/nurbs/pbbsplines_basis_function.h"
#include "custom_utilities/tsplines/tcell.h"
#include "custom_utilities/tsplines/tsplines_utils.h"
#include "custom_utilities/tsplines/tsmesh_2d.h"
#include "custom_utilities/tsplines/tsplines_fespace.h"
#include "custom_utilities/tsplines/nonconforming_tsplines_multipatch_lagrange_mesh.h"
#include "custom_python/add_tsplines_to_python.h"
#include "custom_python/add_import_export_to_python.h"
#ifdef ISOGEOMETRIC_USE_TSPLINE
#include "custom_utilities/import_export/tsplines_patch_tsm_importer.h"
#endif

namespace Kratos
{

void TSplinesUtils_CreateFromBSplines(TSplinesUtils& rDummy, TsMesh2D& tmesh,
    const BSplinesFESpace<2>& rFESpace)
{
    rDummy.CreateFromBSplines(tmesh, rFESpace);
}

void TSplinesUtils_ReadFromFile(TSplinesUtils& rDummy, TsMesh2D& tmesh,
    const std::string& fn)
{
    rDummy.ReadFromFile(tmesh, fn);
}

void TSplinesUtils_ExportMatlab(TSplinesUtils& rDummy, TsMesh2D& tmesh,
    const std::string& fn, const std::string& mesh_type)
{
    rDummy.ExportMatlab(tmesh, fn, mesh_type);
}

void TSplinesUtils_ExportMDPA(TSplinesUtils& rDummy, const TsMesh2D& tmesh,
    const std::string& fn, const int& division1, const int& division2)
{
    rDummy.ExportMDPA(tmesh, fn, std::vector<int>{division1, division2});
}

void TSplinesUtils_ExportMDPA2(TSplinesUtils& rDummy, const TsMesh2D& tmesh,
    const std::string& fn)
{
    rDummy.ExportMDPA(tmesh, fn, std::vector<int>{});
}

namespace Python
{

using namespace boost::python;

void IsogeometricApplication_AddTSplinesToPython()
{

    /////////////////////////////////////////////////////////////////
    /////////////////////////TMESH///////////////////////////////////
    /////////////////////////////////////////////////////////////////

    class_<TsMesh2D, TsMesh2D::Pointer, boost::noncopyable>
    ("TsMesh2D", init<>())
    .def("BeginConstruct", &TsMesh2D::BeginConstruct)
    .def("EndConstruct", &TsMesh2D::EndConstruct)
    .def("BuildExtendedTmesh", &TsMesh2D::BuildExtendedTmesh)
    .def("IsAnalysisSuitable", &TsMesh2D::IsAnalysisSuitable)
    .def("BuildAnchors", &TsMesh2D::BuildAnchors)
    .def("BuildCells", &TsMesh2D::BuildCells)
    //    .def("FindKnots2", &TsMesh2D::FindKnots2)
    .def(self_ns::str(self))
    ;

    /////////////////////////////////////////////////////////////////
    /////////////////////////T-SPLINES FESPACE///////////////////////
    /////////////////////////////////////////////////////////////////

    typedef PBBSplinesBasisFunction<2, TCell> PBBSplinesBasisFunctionType;
    typedef PBBSplinesFESpace<2, PBBSplinesBasisFunctionType, BCellManager<2, typename PBBSplinesBasisFunctionType::CellType> > PBBSplinesFESpaceType;
    typedef TSplinesFESpace<2, PBBSplinesBasisFunctionType, BCellManager<2, typename PBBSplinesBasisFunctionType::CellType> > TSplinesFESpaceType;
    class_<TSplinesFESpaceType, TSplinesFESpaceType::Pointer, bases<PBBSplinesFESpaceType>, boost::noncopyable>
    ("TSplinesFESpace2D", init<>())
    .def(self_ns::str(self))
    ;

    typedef NonConformingTSplinesMultipatchLagrangeMesh<TSplinesFESpaceType> NonConformingTSplinesMultipatchLagrangeMeshType;
    class_<NonConformingTSplinesMultipatchLagrangeMeshType, typename NonConformingTSplinesMultipatchLagrangeMeshType::Pointer, boost::noncopyable>
    ("NonConformingTSplinesMultipatchLagrangeMesh", init<typename MultiPatch<2>::Pointer>())
    .def("SetBaseElementName", &NonConformingTSplinesMultipatchLagrangeMeshType::SetBaseElementName)
    .def("SetLastNodeId", &NonConformingTSplinesMultipatchLagrangeMeshType::SetLastNodeId)
    .def("SetLastElemId", &NonConformingTSplinesMultipatchLagrangeMeshType::SetLastElemId)
    .def("SetLastPropId", &NonConformingTSplinesMultipatchLagrangeMeshType::SetLastPropId)
    .def("SetDivision", &NonConformingTSplinesMultipatchLagrangeMeshType::SetDivision)
    .def("SetUniformDivision", &NonConformingTSplinesMultipatchLagrangeMeshType::SetUniformDivision)
    .def("WriteModelPart", &NonConformingTSplinesMultipatchLagrangeMeshType::WriteModelPart)
    .def(self_ns::str(self))
    ;

    /////////////////////////////////////////////////////////////////
    /////////////////////////UTILITIES///////////////////////////////
    /////////////////////////////////////////////////////////////////

    class_<TSplinesUtils, TSplinesUtils::Pointer, boost::noncopyable>
    ("TSplinesUtils", init<>())
    .def("CreateFromBSplines", &TSplinesUtils_CreateFromBSplines)
    .def("ReadFromFile", &TSplinesUtils_ReadFromFile)
    .def("ExportMatlab", &TSplinesUtils_ExportMatlab)
    .def("ExportMDPA", &TSplinesUtils_ExportMDPA)
    .def("ExportMDPA", &TSplinesUtils_ExportMDPA2)
    .def(self_ns::str(self))
    ;

    /////////////////////////////////////////////////////////////////
    /////////////////////////IMPORT/EXPORT///////////////////////////
    /////////////////////////////////////////////////////////////////

    #ifdef ISOGEOMETRIC_USE_TSPLINE
    class_<TSplinesPatchTSMImporter, TSplinesPatchTSMImporter::Pointer, boost::noncopyable>
    ("TSplinesPatchTSMImporter", init<>())
    .def("ImportSingle", &TSplinesPatchTSMImporter::ImportSingle)
    .def(self_ns::str(self))
    ;
    #endif

}

}  // namespace Python.

} // Namespace Kratos


/**
 * \file CustomFIRFilter.cpp
 */

#include "CustomFIRFilter.h"
#include "IIRFilter.h"

#include <cassert>
#include <cmath>

#include <boost/math/constants/constants.hpp>

namespace ATK
{
  template <typename DataType>
  CustomFIRCoefficients<DataType>::CustomFIRCoefficients(int nb_channels)
    :Parent(nb_channels, nb_channels)
  {
  }

  template<typename DataType_>
  void ATK::CustomFIRCoefficients<DataType_>::set_coefficients_in( std::vector<DataType> coefficients_in )
  {
    in_order = static_cast<int>(coefficients_in.size() - 1);
    this->coefficients_in = std::move(coefficients_in);

    setup();
  }

  template class CustomFIRCoefficients<float>;
  template class CustomFIRCoefficients<double>;

  template class IIRFilter<CustomFIRCoefficients<float> >;
  template class IIRFilter<CustomFIRCoefficients<double> >;
}


/// \file   test_mathematics_interval.cpp
///
/// \brief
///
/// \authors    Maarten P. Scholl
/// \date       2019-09-09
/// \copyright  Copyright 2017-2019 The Institute for New Economic Thinking,
///             Oxford Martin School, University of Oxford
///
///             Licensed under the Apache License, Version 2.0 (the "License");
///             you may not use this file except in compliance with the License.
///             You may obtain a copy of the License at
///
///                 http://www.apache.org/licenses/LICENSE-2.0
///
///             Unless required by applicable law or agreed to in writing,
///             software distributed under the License is distributed on an "AS
///             IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
///             express or implied. See the License for the specific language
///             governing permissions and limitations under the License.
///
///             You may obtain instructions to fulfill the attribution
///             requirements in CITATION.cff
///
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE interval

#include <boost/test/included/unit_test.hpp>

#include <esl/mathematics/interval.hpp>
using esl::mathematics::interval;

BOOST_AUTO_TEST_SUITE(ESL)

BOOST_AUTO_TEST_CASE(interval_constructor)
{
    interval<int, false, false> i {-1, 3};

    BOOST_CHECK(i.lower == -1);
    BOOST_CHECK(i.upper == 3);
}

BOOST_AUTO_TEST_CASE(integer_interval)
{
    //     [l, u]
    {
        interval<int, false, false> i_lo_ro_n1 {2, 1};
        BOOST_CHECK(i_lo_ro_n1.empty());
        BOOST_CHECK(!i_lo_ro_n1.singleton());
        BOOST_CHECK(i_lo_ro_n1.degenerate());
    }

    {
        interval<int, false, false> i_lo_ro_0 {1, 1};
        BOOST_CHECK(!i_lo_ro_0.empty());
        BOOST_CHECK(i_lo_ro_0.singleton());
        BOOST_CHECK(i_lo_ro_0.degenerate());
    }

    {
        interval<int, false, false> i_lo_ro_0 {1, 2};
        BOOST_CHECK(!i_lo_ro_0.empty());
        BOOST_CHECK(!i_lo_ro_0.singleton());
        BOOST_CHECK(!i_lo_ro_0.degenerate());
    }
    //     (l, u]
    {
        interval<int, true, false> i_lc_ro_0 {1, 1};
        BOOST_CHECK(i_lc_ro_0.empty());
        BOOST_CHECK(!i_lc_ro_0.singleton());
        BOOST_CHECK(i_lc_ro_0.degenerate());
    }

    {
        interval<int, true, false> i_lc_ro_1 {1, 2};
        BOOST_CHECK(!i_lc_ro_1.empty());
        BOOST_CHECK(i_lc_ro_1.singleton());
        BOOST_CHECK(i_lc_ro_1.degenerate());
    }
    //     [l, u)
    {
        interval<int, false, true> i_lo_rc_0 {1, 1};
        BOOST_CHECK(i_lo_rc_0.empty());
        BOOST_CHECK(!i_lo_rc_0.singleton());
        BOOST_CHECK(i_lo_rc_0.degenerate());
    }

    {
        interval<int, false, true> i_lo_rc_1 {1, 2};
        BOOST_CHECK(!i_lo_rc_1.empty());
        BOOST_CHECK(i_lo_rc_1.singleton());
        BOOST_CHECK(i_lo_rc_1.degenerate());
    }
    //     (l, u)
    {
        interval<int, true, true> i_lc_rc_0 {1, 1};
        BOOST_CHECK(i_lc_rc_0.empty());
        BOOST_CHECK(!i_lc_rc_0.singleton());
        BOOST_CHECK(i_lc_rc_0.degenerate());
    }

    {
        interval<int, true, true> i_lc_rc_1 {1, 2};
        BOOST_CHECK(i_lc_rc_1.empty());
        BOOST_CHECK(!i_lc_rc_1.singleton());
        BOOST_CHECK(i_lc_rc_1.degenerate());
    }

    {
        interval<int, true, true> i_lc_rc_2 {1, 3};
        BOOST_CHECK(!i_lc_rc_2.empty());
        BOOST_CHECK(i_lc_rc_2.singleton());
        BOOST_CHECK(i_lc_rc_2.degenerate());
    }
}  // BOOST_AUTO_TEST_CASE integer_interval

BOOST_AUTO_TEST_SUITE_END()  // ESL


//#include <Eigen/Dense>
#include <array>
#include <fstream>
#include <iostream>
#include <map>
#include <vector>

// Written by Christopher Wilkes, Cambridge University.
// In future, classes will be used to print vertices, elements and then boundaries in txt not json.
// Arpril 2021

//using Eigen::MatrixXd;
int main() {

// how many elements in each direction
 const int xlen = 94;
 const int ylen = 34;
 const int zlen = 334;

//Length of each element (global)
 double spacing = 0.03;

 const double xorigin = 0.00001;

 double xcoord = xorigin;

 double zorigin = 0.00001;

 double ycoord = 0.00001;
 double zcoord = 0.00001;

double yorigin = 0.00001;

 unsigned counter = 0;
 unsigned size = 0;

 unsigned esize = 0;
 unsigned vsize = 0;

 std::vector<std::vector<std::vector<int> > > mesh;

   // Set up sizes
   mesh.resize(xlen);
   for (int i = 0; i < xlen; ++i) {
     mesh[i].resize(ylen);

     for (int j = 0; j < ylen; ++j)
       mesh[i][j].resize(zlen);
   }

 int indices = 0;

 //(rows - 1)

//create 3D array
 for (unsigned x = 0; x < xlen; ++x) {
   for (unsigned y = 0; y < ylen; ++y) {
     for (unsigned z = 0; z < zlen; ++z) {
       mesh[x][y][z] = indices;
       ++indices;
     }
   }
 }

//calculate the number of elements to add to start of file
 for (unsigned x = 0; x < xlen; ++x) {
   for (unsigned y = 0; y < ylen; ++y) {
     for (unsigned z = 0; z < zlen; ++z) {
       if ((x > 0) && (x < (xlen - 1))) {
         if ((y > 0) && (y < (ylen - 1))) {
           if ((z > 0) && (z < (zlen - 1))) {
             if (y > 1 && (zlen - z) > 2 && (xlen - x) > 2) {
               esize ++;
             }
           }
         }
       }
     }
   }
 }

//! Output file LOCATION IMPORTANT PLEASE CHANGE
std::string vertifilename = "/work/06435/cw646/frontera/frontera/gimp_col_kks/vertices.txt";
std::fstream verfile;
verfile.open(vertifilename, std::ios::out);

//calculate number of vertices
vsize = xlen*ylen*zlen;

//print number of vertices and elements
std::cout << "Vertices: " << vsize << "  Elements: " << esize << '\n';

//write number of vertices and elements to start of file
verfile << vsize << '\t' << esize <<'\n';
//define coordinates
 for (unsigned x = 0; x < xlen; ++x) {
   for (unsigned y = 0; y < ylen; ++y) {
     for (unsigned z = 0; z < zlen; ++z) {

       //std::array<douable, 3> coord = {xcoord, ycoord, zcoord};

        // write coordinates to file
       verfile << xcoord<< '\t' << ycoord << '\t' << zcoord << '\n';

       zcoord += spacing;
     }
     zcoord = zorigin;
     ycoord += spacing;
   }
   ycoord = yorigin;
   xcoord += spacing;
   std::cout << xcoord << '\n';
 }

// verfile.close();

 std::cout << "Checkpoint 1" << '\n';

 //std::string elefilename = "elements.txt";
 //std::fstream elefile;
 //elefile.open(elefilename, std::ios::out);

//crete elements by parsing 3D array
 for (unsigned x = 0; x < xlen; ++x) {

   for (unsigned y = 0; y < ylen; ++y) {
     for (unsigned z = 0; z < zlen; ++z) {

       // check node is not on boundaxy of mesh
       if ((x > 0) && (x < (xlen - 1))) {
         if ((y > 0) && (y < (ylen - 1))) {
           if ((z > 0) && (z < (zlen - 1))) {
             // check if rows above and to the side to genexate a GIMP

             //allocate nodes to cell and write to file
             if (y > 1 && (zlen - z) > 2 && (xlen - x) > 2) {
               verfile << mesh[x][y][z]      // 0
                   <<" "<< mesh[x + 1][y][z]         // 1
                   <<" "<< mesh[x + 1][y][z + 1]     // 2
                   <<" "<< mesh[x][y][z + 1]         // 3
                   <<" "<< mesh[x][y - 1][z]         // 4
                   <<" "<< mesh[x + 1][y - 1][z]     // 5
                   <<" "<< mesh[x + 1][y - 1][z + 1] // 6
                   <<" "<< mesh[x][y - 1][z + 1]     // 7
                   <<" "<< mesh[x - 1][y + 1][z - 1] // 8
                   <<" "<< mesh[x][y + 1][z - 1]     // 9
                   <<" "<< mesh[x + 1][y + 1][z - 1] // 10
                   <<" "<< mesh[x + 2][y + 1][z - 1] // 11
                   <<" "<< mesh[x - 1][y + 1][z]     // 12
                   <<" "<< mesh[x][y + 1][z]         // 13
                   <<" "<< mesh[x + 1][y + 1][z]     // 14
                   <<" "<< mesh[x + 2][y + 1][z]     // 15
                   <<" "<< mesh[x - 1][y + 1][z + 1] // 16
                   <<" "<< mesh[x][y + 1][z + 1]     // 17
                   <<" "<< mesh[x + 1][y + 1][z + 1] // 18
                   <<" "<< mesh[x + 2][y + 1][z + 1] // 19
                   <<" "<< mesh[x - 1][y + 1][z + 2] // 20
                   <<" "<< mesh[x][y + 1][z + 2]     // 21
                   <<" "<< mesh[x + 1][y + 1][z + 2] // 22
                   <<" "<< mesh[x + 2][y + 1][z + 2] // 23
                   <<" "<< mesh[x - 1][y][z - 1]     // 24
                   <<" "<< mesh[x][y][z - 1]         // 25
                   <<" "<< mesh[x + 1][y][z - 1]     // 26
                   <<" "<< mesh[x + 2][y][z - 1]     // 27
                   <<" "<< mesh[x - 1][y][z]         // 28
                   <<" "<< mesh[x + 2][y][z]         // 29
                   <<" "<< mesh[x - 1][y][z + 1]     // 30
                   <<" "<< mesh[x + 2][y][z + 1]     // 31
                   <<" "<< mesh[x - 1][y][z + 2]     // 32
                   <<" "<< mesh[x][y][z + 2]         // 33
                   <<" "<< mesh[x + 1][y][z + 2]     // 34
                   <<" "<< mesh[x + 2][y][z + 2]     // 35
                   <<" "<< mesh[x - 1][y - 1][z - 1] // 36
                   <<" "<< mesh[x][y - 1][z - 1]     // 37
                   <<" "<< mesh[x + 1][y - 1][z - 1] // 39
                   <<" "<< mesh[x + 2][y - 1][z - 1] // 39
                   <<" "<< mesh[x - 1][y - 1][z]     // 40
                   <<" "<< mesh[x + 2][y - 1][z]     // 41
                   <<" "<< mesh[x - 1][y - 1][z + 1] // 42
                   <<" "<< mesh[x + 2][y - 1][z + 1] // 43
                   <<" "<< mesh[x - 1][y - 1][z + 2] // 44
                   <<" "<< mesh[x][y - 1][z + 2]     // 45
                   <<" "<< mesh[x + 1][y - 1][z + 2] // 46
                   <<" "<< mesh[x + 2][y - 1][z + 2] // 47
                   <<" "<< mesh[x - 1][y - 2][z - 1] // 48
                   <<" "<< mesh[x][y - 2][z - 1]     // 49
                   <<" "<< mesh[x + 1][y - 2][z - 1] // 50
                   <<" "<< mesh[x + 2][y - 2][z - 1] // 51
                   <<" "<< mesh[x - 1][y - 2][z]     // 52
                   <<" "<< mesh[x][y - 2][z]         // 53
                   <<" "<< mesh[x + 1][y - 2][z]     // 54
                   <<" "<< mesh[x + 2][y - 2][z]     // 55
                   <<" "<< mesh[x - 1][y - 2][z + 1] // 56
                   <<" "<< mesh[x][y - 2][z + 1]     // 57
                   <<" "<< mesh[x + 1][y - 2][z + 1] // 58
                   <<" "<< mesh[x + 2][y - 2][z + 1] // 59
                   <<" "<< mesh[x - 1][y - 2][z + 2] // 60
                   <<" "<< mesh[x][y - 2][z + 2]     // 61
                   <<" "<< mesh[x + 1][y - 2][z + 2] // 62
                   <<" "<< mesh[x + 2][y - 2][z + 2] << '\n'; // 63

             }
           }
         }
       }
     }
   }
   std::cout << x << '\n';
 }

  verfile.close();

//elefile.close();

//! Boundaries
std::cout << "Boundaries" << '\n';

// x begin
std::vector<int> x_left_boundaries;
std::vector<int> x_right_boundaries;

x_left_boundaries.resize(3*ylen*zlen);
x_right_boundaries.resize(3*ylen*zlen);

unsigned xl_i = 0;
unsigned xr_i = 0;

//y begin
std::vector<int> y_front_boundaries;
std::vector<int> y_back_boundaries;

y_front_boundaries.resize(3*xlen*zlen);
y_back_boundaries.resize(3*xlen*zlen);

unsigned yf_i = 0;
unsigned yb_i = 0;

//z begin
std::vector<int> z_bot_boundaries;
std::vector<int> z_top_boundaries;

z_bot_boundaries.resize(3*xlen*ylen);
z_top_boundaries.resize(3*xlen*ylen);

unsigned zb_i = 0;
unsigned zt_i = 0;

//calculate the number of elements to add to start of file
    for (unsigned x = 0; x < xlen; ++x) {
        for (unsigned y = 0; y < ylen; ++y) {
            for (unsigned z = 0; z < zlen; ++z) {

                if (x < 3) {
                    // x left
                    x_left_boundaries[xl_i] = mesh[x][y][z];
                    xl_i++;
                }

                if (x > (xlen - 4)) {
                    // x right
                    x_right_boundaries[xr_i] = mesh[x][y][z];
                    xr_i++;
                }

                if (y < 3) {
                    // x left
                    y_back_boundaries[yb_i] = mesh[x][y][z];
                    yb_i++;
                }

                if (y > (ylen - 4)) {
                    // x right
                    y_front_boundaries[yf_i] = mesh[x][y][z];
                    yf_i++;
                }

                if (z < 3) {
                    // x left
                    z_bot_boundaries[zb_i] = mesh[x][y][z];
                    zb_i++;
                }

                if (z > (zlen - 4)) {
                    // x right
                    z_top_boundaries[zt_i] = mesh[x][y][z];
                    zt_i++;
                }

            }
        }
    }

  //! Output file
  std::string boundaryfileneame = "/work/06435/cw646/frontera/frontera/gimp_col_kks/boundaries.json";
  std::fstream boundaryfile;
  boundaryfile.open(boundaryfileneame, std::ios::out);
  if (boundaryfile.is_open()) {
    unsigned counter = 0;

    boundaryfile << "{" << '\n';
    boundaryfile << '\t' << " \"node_sets\" : [ " << '\n';
    boundaryfile << '\t' << '\t' << " { " << '\n';

    // ----------------- X START -------------------------------
    //set 0
    boundaryfile << '\t' << '\t' << " \"id\" : 0," << '\n';
    boundaryfile << '\t' << '\t' << " \"set\" : [";

      //! x left (x_left_boundaries)
      counter = 0;
      for (auto const& boundaries : x_left_boundaries) {
        if (counter < (xl_i-1)) {
          boundaryfile << '\t' << '\t' << '\t' << boundaries  << "," << '\n';
          } else {
            boundaryfile << '\t' << '\t' << '\t' << boundaries;
          }
          counter++;
      }

    boundaryfile << " ]" << '\n';
    boundaryfile << '\t' << '\t' << " }, " << '\n';

    boundaryfile << '\t' << '\t' << " { " << '\n';
    //set 1
    boundaryfile << '\t' << '\t' << " \"id\" : 1," << '\n';
    boundaryfile << '\t' << '\t' << " \"set\" : [";

      //! x right (x_right_boundaries)
      counter = 0;
      for (auto const& boundaries : x_right_boundaries) {
        if (counter < (xr_i-1)) {
         boundaryfile << '\t' << '\t'<< '\t' << boundaries << "," << '\n';
          } else {
            boundaryfile << '\t' << '\t' << '\t' << boundaries;
          }
          counter++;
      }
    boundaryfile << " ]" << '\n';
    boundaryfile << '\t' << '\t' << " }, " << '\n';

    // ----------------- X END -------------------------------

    // ----------------- Y START -------------------------------
    boundaryfile << '\t' << '\t' << " { " << '\n';
    //set 2
    boundaryfile << '\t' << '\t' << " \"id\" : 2," << '\n';
    boundaryfile << '\t' << '\t' << " \"set\" : [";

      //! x right (y_back_boundaries)
      counter = 0;
      for (auto const& boundaries : y_back_boundaries) {
        if (counter < (yb_i-1)) {
         boundaryfile << '\t' << '\t'<< '\t' << boundaries << "," << '\n';
          } else {
            boundaryfile << '\t' << '\t' << '\t' << boundaries;
          }
          counter++;
      }

    boundaryfile << " ]" << '\n';
    boundaryfile << '\t' << '\t' << " }, " << '\n';

    boundaryfile << '\t' << '\t' << " { " << '\n';
    //set 3
    boundaryfile << '\t' << '\t' << " \"id\" : 3," << '\n';
    boundaryfile << '\t' << '\t' << " \"set\" : [";

      //! x right (y_front_boundaries)
      counter = 0;
      for (auto const& boundaries : y_front_boundaries) {
        if (counter < (yf_i-1)) {
         boundaryfile << '\t' << '\t'<< '\t' << boundaries << "," << '\n';
          } else {
            boundaryfile << '\t' << '\t' << '\t' << boundaries;
          }
          counter++;
      }

    // ----------------- Y END -------------------------------

    boundaryfile << " ]" << '\n';
    boundaryfile << '\t' << '\t' << " }, " << '\n';

    // ----------------- Z START -------------------------------

    boundaryfile << '\t' << '\t' << " { " << '\n';
    //set 4
    boundaryfile << '\t' << '\t' << " \"id\" : 4," << '\n';
    boundaryfile << '\t' << '\t' << " \"set\" : [";

      //! x right (z_bot_boundaries)
      counter = 0;
      for (auto const& boundaries : z_bot_boundaries) {
        if (counter < (zb_i-1)) {
         boundaryfile << '\t' << '\t'<< '\t' << boundaries << "," << '\n';
          } else {
            boundaryfile << '\t' << '\t' << '\t' << boundaries;
          }
          counter++;
      }

    boundaryfile << " ]" << '\n';
    boundaryfile << '\t' << '\t' << " }, " << '\n';

    boundaryfile << '\t' << '\t' << " { " << '\n';
    //set 5
    boundaryfile << '\t' << '\t' << " \"id\" : 5," << '\n';
    boundaryfile << '\t' << '\t' << " \"set\" : [";

      //! x right (z_top_boundaries)
      counter = 0;
      for (auto const& boundaries : z_top_boundaries) {
        if (counter < (zt_i-1)) {
         boundaryfile << '\t' << '\t'<< '\t' << boundaries << "," << '\n';
          } else {
            boundaryfile << '\t' << '\t' << '\t' << boundaries;
          }
          counter++;
      }

    //close system
    boundaryfile << " ]" << '\n';
    boundaryfile << '\t' << '\t' << " } " << '\n';

    //close system
    boundaryfile << '\t' << " ]" << '\n';
    boundaryfile << " } " << '\n';

    boundaryfile.close();

  } else {
    std::cout << "File not open";
  }

/*
if ((y > 0) && (y < (ylen - 1))) {
                        if ((z > 0) && (z < (zlen - 1))) {
                            if (y > 1 && (zlen - z) > 2 && (xlen - x) > 2) {
                                esize ++;
                            }
                        }
                    }

&& (x < (xlen - 3))*/

 std::cout << "Finish" << '\n';

}