/*  ==========================================================================================
    Author: Leonardo Citraro
    Company:
    Filename: ext3DLBPpy.cpp
    Last modifed:   6.1.2019 by Leonardo Citraro
    Description:    Boost-Python wrappers
    *
    Please cite the article:    L. Citraro, S. Mahmoodi, A. Darekar, B. Vollmer,
                                Extended three-dimensional rotation invariant local binary patterns,
                                Image and Vision Computing (2017),
                                http://dx.doi.org/10.1016/j.imavis.2017.03.004

    ==========================================================================================
    Copyright (c) 2017 Leonardo Citraro <ldo.citraro@gmail.com>

    Permission is hereby granted, free of charge, to any person obtaining a copy of this
    software and associated documentation files (the "Software"), to deal in the Software
    without restriction, including without limitation the rights to use, copy, modify,
    merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    permit persons to whom the Software is furnished to do so, subject to the following
    conditions:

    The above copyright notice and this permission notice shall be included in all copies
    or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
    PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
    FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
    OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.
    ==========================================================================================
*/
#include <Python.h>
#include <boost/python/numpy.hpp>
#include <numpy/ndarrayobject.h>
#include <numpy/arrayobject.h>
#include "ext3DLBP.hpp"

using namespace std;
using namespace ext3DLBP;
namespace p = boost::python;
namespace np = boost::python::numpy;

/**
  *   @brief  This function deletes dynamically allocated memory
  *           from python side when the data is no loguer required.
  */
template<typename T>
inline void CapsuleDestructor(PyObject *ptr){
    T *tmp = reinterpret_cast<T*>(PyCapsule_GetPointer(ptr, NULL));
    if(tmp){
        delete tmp;
    }
}

template<typename T>
inline np::ndarray make_ndarray(T* const ptr, const int H, const int W=0, const int D=0){

    // figuring out the size of the array
    int L;
    if(W<=0 && D<=0){
        L = H;
    }else if(W<=0 && H<=0){
        L = D;
    }else if(H<=0 && D<=0){
        L = H;
    }else if(H<=0){
        L = W*D;
    }else if(W<=0){
        L = H*D;
    }else if(D<=0){
        L = H*W;
    }else{
        L=H*W*D;
    }

    // The object owner is responsible for deleting the dynamic array from the python side
    // This is the trick to prevent memory leaks!
    p::object owner(p::handle<>((PyCapsule_New((void*)ptr, NULL,
                                (PyCapsule_Destructor)&CapsuleDestructor<T>))));

    // Creates a boost numpy array, this is the actual object we return to python
    np::ndarray py_array = np::from_data(ptr,
                                         np::dtype::get_builtin<T>(),
                                         p::make_tuple(L),
                                         p::make_tuple(sizeof(T)),
                                         owner);

    p::tuple shape;
    if(H<=0){
        shape = p::make_tuple(W,D);
    }else if(W<=0){
        shape = p::make_tuple(H,D);
    }else if(D<=0){
        shape = p::make_tuple(H,W);
    }else{
        shape = p::make_tuple(H,W,D);
    }

    py_array = py_array.reshape(shape);

    return py_array;
}

template<typename T, size_t K>
Array3D<T,K,K,K> convert_numpy_to_STL(const np::ndarray& py_array) {
    Array3D<T,K,K,K> array;
    T* p_vector;
    for(int k=0; k<K; ++k) {
        for(int j=0; j<K; ++j) {
            for(int i=0; i<K; ++i) {
                T x = p::extract<T>(py_array[i][j][k]);
                array[i][j][k] = x;
            }
        }
    }
    return array;
}

template<typename T, size_t K>
Array3D<T,K,K,K> my_slice(const np::ndarray& py_volume, const int istart, const int jstart, const int kstart) {
    Array3D<T,K,K,K> array;
    for(int k=0; k<K; ++k) {
        for(int j=0; j<K; ++j) {
            for(int i=0; i<K; ++i) {
                array[i][j][k] = p::extract<T>(py_volume[i+istart][j+jstart][k+kstart]);
            }
        }
    }
    return array;
}

p::tuple convert_to_tuple(const Array1D<int,2>& data) {
    return p::make_tuple(data[0], data[1]);
}
p::tuple convert_to_tuple(const Array1D<int,3>& data) {
    return p::make_tuple(data[0], data[1], data[2]);
}

//=========================================================================
// CI-LBP
//=========================================================================
struct CI_LBP_ : public CI_LBP {
    const static int bins = CI_LBP::bins;
    CI_LBP_(const double mur) : CI_LBP(mur) {}
    // converts a volume of size 3, 5 or 7 voxels
    auto convert(const np::ndarray py_volume) {
        int H = py_volume.shape(0);
        if(H == 3)
            return CI_LBP::convert(convert_numpy_to_STL<int,3>(py_volume));
        else if(H == 5)
            return CI_LBP::convert(convert_numpy_to_STL<int,5>(py_volume));
        else if(H == 7)
            return CI_LBP::convert(convert_numpy_to_STL<int,7>(py_volume));
    }
    // converts a full volume
    auto convert_3d_image(const np::ndarray& py_volume) {
        int H = py_volume.shape(0);
        int W = py_volume.shape(1);
        int D = py_volume.shape(2);

        int* out = new int[H*W*D];

        for(int k=0; k<(D); ++k) {
            for(int j=0; j<(W); ++j) {
                for(int i=0; i<(H); ++i) {
                    int chunk = p::extract<int>(py_volume[i][j][k]);
                    out[k*D*W + j*W + i] = CI_LBP::convert(chunk);
                }
            }
        }

        np::ndarray py_out = make_ndarray(out,H,W,D);

        return py_out;
    }
};
const int CI_LBP_::bins;

//=========================================================================
// NI-LBP
//=========================================================================

#define wrapper_class_1     struct fullname : public parent {\
                                const static int P = parent::P;\
                                const static int O = parent::O;\
                                const static int bins = parent::bins;\
                                const static int R = parent::R;\
                                const static int K = parent::K;\
                                fullname(const double mur, const int V) : parent(mur,V) {}\
                                auto convert(const np::ndarray& py_volume) {\
                                    return parent::convert(convert_numpy_to_STL<int,K>(py_volume));\
                                }\
                                auto convert_3d_image(const np::ndarray& py_volume) {\
                                    int H = py_volume.shape(0);\
                                    int W = py_volume.shape(1);\
                                    int D = py_volume.shape(2);\
                                    \
                                    int H_ = H-2*R;\
                                    int W_ = W-2*R;\
                                    int D_ = D-2*R;\
                                    int* out = new int[H_*W_*D_];\
                                    \
                                    for(int k=0; k<D_; ++k) {\
                                        for(int j=0; j<W_; ++j) {\
                                            for(int i=0; i<H_; ++i) {\
                                                auto chunk = my_slice<int,K>(py_volume,i,j,k);\
                                                out[k*D_*W_ + j*W_ + i] = parent::convert(chunk);\
                                            }\
                                        }\
                                    }\
                                    np::ndarray py_out = make_ndarray(out,H_, W_, D_);\
                                    return py_out;\
                                }\
                            };\
                            const int fullname::P;\
                            const int fullname::bins;\
                            const int fullname::O;\
                            const int fullname::R;\
                            const int fullname::K;\

#define wrapper_class_2     struct fullname : public parent {\
                                const static int P = parent::P;\
                                const static int O = parent::O;\
                                const static int bins = parent::bins;\
                                const static int R = parent::R;\
                                const static int K = parent::K;\
                                fullname(const double mur, const int V) : parent(mur,V) {}\
                                auto convert(const np::ndarray& py_volume) {\
                                    return convert_to_tuple(parent::convert(convert_numpy_to_STL<int,K>(py_volume)));\
                                }\
                                auto convert_3d_image(const np::ndarray& py_volume) {\
                                    int H = py_volume.shape(0);\
                                    int W = py_volume.shape(1);\
                                    int D = py_volume.shape(2);\
                                    \
                                    int H_ = H-2*R;\
                                    int W_ = W-2*R;\
                                    int D_ = D-2*R;\
                                    int* out1 = new int[H_*W_*D_];\
                                    int* out2 = new int[H_*W_*D_];\
                                    \
                                    for(int k=0; k<D_; ++k) {\
                                        for(int j=0; j<W_; ++j) {\
                                            for(int i=0; i<H_; ++i) {\
                                                auto chunk = my_slice<int,K>(py_volume,i,j,k);\
                                                Array1D<int,2> codes = parent::convert(chunk);\
                                                out1[k*D_*W_ + j*W_ + i] = codes[0];\
                                                out2[k*D_*W_ + j*W_ + i] = codes[1];\
                                            }\
                                        }\
                                    }\
                                    np::ndarray py_out1 = make_ndarray(out1, H_, W_, D_);\
                                    np::ndarray py_out2 = make_ndarray(out2, H_, W_, D_);\
                                    return p::make_tuple(py_out1, py_out2);\
                                }\
                            };\
                            const int fullname::P;\
                            const int fullname::bins;\
                            const int fullname::O;\
                            const int fullname::R;\
                            const int fullname::K;\

#define wrapper_class_3     struct fullname : public parent {\
                                const static int P = parent::P;\
                                const static int O = parent::O;\
                                const static int bins = parent::bins;\
                                const static int R = parent::R;\
                                const static int K = parent::K;\
                                fullname(const double mur, const int V) : parent(mur,V) {}\
                                auto convert(const np::ndarray& py_volume) {\
                                    return convert_to_tuple(parent::convert(convert_numpy_to_STL<int,K>(py_volume)));\
                                }\
                                auto convert_3d_image(const np::ndarray& py_volume) {\
                                    int H = py_volume.shape(0);\
                                    int W = py_volume.shape(1);\
                                    int D = py_volume.shape(2);\
                                    \
                                    int H_ = H-2*R;\
                                    int W_ = W-2*R;\
                                    int D_ = D-2*R;\
                                    int* out1 = new int[H_*W_*D_];\
                                    int* out2 = new int[H_*W_*D_];\
                                    int* out3 = new int[H_*W_*D_];\
                                    \
                                    for(int k=0; k<D_; ++k) {\
                                        for(int j=0; j<W_; ++j) {\
                                            for(int i=0; i<H_; ++i) {\
                                                auto chunk = my_slice<int,K>(py_volume,i,j,k);\
                                                Array1D<int,3> codes = parent::convert(chunk);\
                                                out1[k*D_*W_ + j*W_ + i] = codes[0];\
                                                out2[k*D_*W_ + j*W_ + i] = codes[1];\
                                                out3[k*D_*W_ + j*W_ + i] = codes[2];\
                                            }\
                                        }\
                                    }\
                                    np::ndarray py_out1 = make_ndarray(out1,H_, W_, D_);\
                                    np::ndarray py_out2 = make_ndarray(out2,H_, W_, D_);\
                                    np::ndarray py_out3 = make_ndarray(out3,H_, W_, D_);\
                                    return p::make_tuple(py_out1, py_out2, py_out3);\
                                }\
                            };\
                            const int fullname::P;\
                            const int fullname::bins;\
                            const int fullname::O;\
                            const int fullname::R;\
                            const int fullname::K;\

// P42g
#define fullname NI_LBP_P42g_R1
#define parent NI_LBP<P42g, R1>
wrapper_class_1

#define fullname NI_LBP_P42g_R2
#define parent NI_LBP<P42g, R2>
wrapper_class_1

// P92g
#define fullname NI_LBP_P92g_R1
#define parent NI_LBP<P92g, R1>
wrapper_class_1

#define fullname NI_LBP_P92g_R2
#define parent NI_LBP<P92g, R2>
wrapper_class_1

#define fullname NI_LBP_P92g_R3
#define parent NI_LBP<P92g, R3>
wrapper_class_1

// P252g
#define fullname NI_LBP_P252g_R2
#define parent NI_LBP<P252g, R2>
wrapper_class_1

#define fullname NI_LBP_P252g_R3
#define parent NI_LBP<P252g, R3>
wrapper_class_1

//=========================================================================
// RD-LBP
//=========================================================================
// P42g
#define fullname RD_LBP_P42g_R1
#define parent RD_LBP<P42g, R1>
wrapper_class_1

#define fullname RD_LBP_P42g_R2
#define parent RD_LBP<P42g, R2>
wrapper_class_1

// P92g
#define fullname RD_LBP_P92g_R1
#define parent RD_LBP<P92g, R1>
wrapper_class_1

#define fullname RD_LBP_P92g_R2
#define parent RD_LBP<P92g, R2>
wrapper_class_1

#define fullname RD_LBP_P92g_R3
#define parent RD_LBP<P92g, R3>
wrapper_class_1

// P252g
#define fullname RD_LBP_P252g_R2
#define parent RD_LBP<P252g, R2>
wrapper_class_1

#define fullname RD_LBP_P252g_R3
#define parent RD_LBP<P252g, R3>
wrapper_class_1

//=========================================================================
// NI-RD-LBP
//=========================================================================
//P42g
#define fullname NI_RD_LBP_P42g_R1
#define parent NI_RD_LBP<P42g, R1>
wrapper_class_2

#define fullname NI_RD_LBP_P42g_R2
#define parent NI_RD_LBP<P42g, R2>
wrapper_class_2

//P92g
#define fullname NI_RD_LBP_P92g_R1
#define parent NI_RD_LBP<P92g, R1>
wrapper_class_2

#define fullname NI_RD_LBP_P92g_R2
#define parent NI_RD_LBP<P92g, R2>
wrapper_class_2

#define fullname NI_RD_LBP_P92g_R3
#define parent NI_RD_LBP<P92g, R3>
wrapper_class_2

//P252g
#define fullname NI_RD_LBP_P252g_R2
#define parent NI_RD_LBP<P252g, R2>
wrapper_class_2

#define fullname NI_RD_LBP_P252g_R3
#define parent NI_RD_LBP<P252g, R3>
wrapper_class_2

//=========================================================================
// NI-RD-CI-LBP
//=========================================================================
//P42g
#define fullname NI_RD_CI_LBP_P42g_R1
#define parent NI_RD_CI_LBP<P42g, R1>
wrapper_class_3

#define fullname NI_RD_CI_LBP_P42g_R2
#define parent NI_RD_CI_LBP<P42g, R2>
wrapper_class_3

//P92g
#define fullname NI_RD_CI_LBP_P92g_R1
#define parent NI_RD_CI_LBP<P92g, R1>
wrapper_class_3

#define fullname NI_RD_CI_LBP_P92g_R2
#define parent NI_RD_CI_LBP<P92g, R2>
wrapper_class_3

#define fullname NI_RD_CI_LBP_P92g_R3
#define parent NI_RD_CI_LBP<P92g, R3>
wrapper_class_3

//P252g
#define fullname NI_RD_CI_LBP_P252g_R2
#define parent NI_RD_CI_LBP<P252g, R2>
wrapper_class_3

#define fullname NI_RD_CI_LBP_P252g_R3
#define parent NI_RD_CI_LBP<P252g, R3>
wrapper_class_3

BOOST_PYTHON_MODULE(ext3DLBPpy) {
    Py_Initialize();
    np::initialize();

    // CI-LBP
    p::class_<CI_LBP_>("CI_LBP",p::init<double>())
        .def_readonly("mur", &CI_LBP_::mur)
        .def_readonly("bins", &CI_LBP_::bins)
        .def("convert", &CI_LBP_::convert)
        .def("convert_3d_image", &CI_LBP_::convert_3d_image);

#define STRINGIZE_NX(A) #A
#define STRINGIZE(A) STRINGIZE_NX(A)
#define boost_python_definition p::class_<fullname>(STRINGIZE(fullname),p::init<double, int>())\
                                .def_readonly("P", &fullname::P)\
                                .def_readonly("O", &fullname::O)\
                                .def_readonly("bins", &fullname::bins)\
                                .def_readonly("R", &fullname::R)\
                                .def_readonly("K", &fullname::K)\
                                .def_readonly("mur", &fullname::mur)\
                                .def_readonly("V", &fullname::V)\
                                .def("convert", &fullname::convert) \
                                .def("convert_3d_image", &fullname::convert_3d_image);
// NI-LBP
#define fullname NI_LBP_P42g_R1
boost_python_definition

#define fullname NI_LBP_P42g_R2
boost_python_definition

#define fullname NI_LBP_P92g_R1
boost_python_definition

#define fullname NI_LBP_P92g_R2
boost_python_definition

#define fullname NI_LBP_P92g_R3
boost_python_definition

#define fullname NI_LBP_P252g_R2
boost_python_definition

#define fullname NI_LBP_P252g_R3
boost_python_definition

// RD-LBP
#define fullname RD_LBP_P42g_R1
boost_python_definition

#define fullname RD_LBP_P42g_R2
boost_python_definition

#define fullname RD_LBP_P92g_R1
boost_python_definition

#define fullname RD_LBP_P92g_R2
boost_python_definition

#define fullname RD_LBP_P92g_R3
boost_python_definition

#define fullname RD_LBP_P252g_R2
boost_python_definition

#define fullname RD_LBP_P252g_R3
boost_python_definition

// NI-RD-LBP
#define fullname NI_RD_LBP_P42g_R1
boost_python_definition

#define fullname NI_RD_LBP_P42g_R2
boost_python_definition

#define fullname NI_RD_LBP_P92g_R1
boost_python_definition

#define fullname NI_RD_LBP_P92g_R2
boost_python_definition

#define fullname NI_RD_LBP_P92g_R3
boost_python_definition

#define fullname NI_RD_LBP_P252g_R2
boost_python_definition

#define fullname NI_RD_LBP_P252g_R3
boost_python_definition

// NI-RD-CI-LBP
#define fullname NI_RD_CI_LBP_P42g_R1
boost_python_definition

#define fullname NI_RD_CI_LBP_P42g_R2
boost_python_definition

#define fullname NI_RD_CI_LBP_P92g_R1
boost_python_definition

#define fullname NI_RD_CI_LBP_P92g_R2
boost_python_definition

#define fullname NI_RD_CI_LBP_P92g_R3
boost_python_definition

#define fullname NI_RD_CI_LBP_P252g_R2
boost_python_definition

#define fullname NI_RD_CI_LBP_P252g_R3
boost_python_definition

};


/**************************************************************************\
|
|    Copyright (C) 2009 Marc Stevens
|
|    This program is free software: you can redistribute it and/or modify
|    it under the terms of the GNU General Public License as published by
|    the Free Software Foundation, either version 3 of the License, or
|    (at your option) any later version.
|
|    This program is distributed in the hope that it will be useful,
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
|    GNU General Public License for more details.
|
|    You should have received a copy of the GNU General Public License
|    along with this program.  If not, see <http://www.gnu.org/licenses/>.
|
\**************************************************************************/

#include <cmath>
#include <algorithm>
#include <map>
#include <set>

#include <hashclash/saveload_bz2.hpp>
#include <hashclash/sha1differentialpath.hpp>
#include <hashclash/sha1detail.hpp>
#include <hashclash/sha1messagespace.hpp>
#include <hashclash/booleanfunction.hpp>
#include <hashclash/progress_display.hpp>
#include <hashclash/rng.hpp>

#include <boost/lexical_cast.hpp>

#include "main.hpp"

void getline(std::istream& ifs)
{
	char c = 0;
	while (ifs && c != '\n' && c != '\r')
		ifs.get(c);
	while (ifs && (c == '\n' || c == '\r'))
		ifs.get(c);
	if (ifs)
		ifs.putback(c);
}

int pathfromtext(parameters_type& parameters)
{
	if (parameters.outfile1 == "") {
		cout << "No outputfile1 given!" << endl;
		return 2;
	}
	if (parameters.infile1 == "") {
		cout << "No inputfile1 given!" << endl;
		return 2;
	}
	parameters.show_mdiffs();
	sha1differentialpath path;
	vector<sha1differentialpath> vecpath;
	ifstream ifs(parameters.infile1.c_str());
	if (!ifs) {
		cerr << "Error: could not open " << parameters.infile1 << "!" << endl;
		return 1;
	}
	cout << "Parsing inputfile:";
	while (ifs) {
		cout << endl;
		char c;
		ifs >> c;
		cout << c << " ";
		if (!ifs) break;
		if (c != 'Q') {
			cout << "expected 'Q' here, going to next line";
			getline(ifs);
			continue;
		}
		int t;
		if (!(ifs >> t)) break;
		cout << t << " ";
		if (t < -4 || t > 80) {
			cout << "expected integer t with -4 <= t <= 80 here, going to next line";
			getline(ifs);
			continue;
		}
		while (ifs && c != '|') {
			ifs >> c;
			cout << c << " ";
		}
		if (c != '|') {
			cout << "expected '|' here, going to next line";
			getline(ifs);
			continue;
		}
		wordconditions wc;
		ifs >> wc.bytes[3] >> wc.bytes[2] >> wc.bytes[1] >> wc.bytes[0] >> c;
		cout << wc << " " << c << " ";
		if (!ifs) break;
		if (c != '|') {
			cout << "expected '|' here, going to next line";
			getline(ifs);
			continue;
		}
		path[t] = wc;
		path.getme(t).clear();
		if (t >= 0 && t < 80) {
			sdr met;
			ifs >> met;
			if (ifs)
				path.getme(t) = met;
		}
		getline(ifs);
	}
	cout << endl << "Parsed path:" << endl;
	show_path(path);
	vecpath.push_back(path);
	cout << "Saving " << parameters.outfile1 << "..." << flush;
	try {
		save_bz2(vecpath, binary_archive, parameters.outfile1);
		cout << "done." << endl;
	} catch (...) {
		cout << "failed." << endl;
		return 1;
	}
	return 0;
}

int split(parameters_type& parameters)
{
	vector<sha1differentialpath> vecpath, splitvec;
	cout << "Loading " << parameters.infile1 << "..." << flush;
	try {
		load_bz2(vecpath, binary_archive, parameters.infile1);
		cout << "done (loaded " << vecpath.size() << " paths)." << endl;
	} catch (...) {
		cout << "failed." << endl;
		return 1;
	}
	if (parameters.unique) {
		sort(vecpath.begin(), vecpath.end());
		vecpath.erase( unique(vecpath.begin(), vecpath.end()), vecpath.end() );
		cout << "Reduced to " << vecpath.size() << " unique paths." << endl;
	}
	double step = double(vecpath.size()) / double(parameters.split);
	uint32 start = 0;
	uint32 end = vecpath.size();
	for (unsigned i = 0; i < parameters.split; ++i)
	{
		uint32 iend = uint32(double(step*double(i+1)));
		if (i+1 == parameters.split || iend > end)
			iend = end;
		splitvec.clear();
		splitvec.reserve(iend-start);
		for (unsigned j = start; j < iend; ++j)
			splitvec.push_back(vecpath[j]);
		start = iend;
		string filename = parameters.infile1 + "_"
					+ boost::lexical_cast<string>(i) + "of"
					+ boost::lexical_cast<string>(parameters.split) + ".bin";
		cout << "Saving " << splitvec.size() << " paths to " << filename << "..." << flush;
		try {
			save_bz2(splitvec, binary_archive, filename);
			cout << "done." << endl;
		} catch (...) {
			cout << "failed." << endl;
			return 1;
		}
	}
	return 0;
}

int join(parameters_type& parameters)
{
	if (parameters.outfile1 == "") {
		cout << "No outputfile1 given!" << endl;
		return 2;
	}
	vector<sha1differentialpath> vecpath, joinvec;
	for (unsigned i = 0; i < parameters.files.size(); ++i)
	{
		joinvec.clear();
		cout << "Loading " << parameters.files[i] << "..." << flush;
		try {
			load_bz2(joinvec, binary_archive, parameters.files[i]);
			cout << "done (loaded " << joinvec.size() << " paths)." << endl;
			for (unsigned j = 0; j < joinvec.size(); ++j)
				vecpath.push_back(joinvec[j]);
		} catch (...) {
			cout << "failed." << endl;
		}
	}
	if (parameters.unique) {
		for (unsigned i = 0; i < vecpath.size(); ++i) {
			for (int t = vecpath[i].tbegin(); t < vecpath[i].tend(); ++t) {
				if (!(t-4 >= vecpath[i].tbegin() && t+1 < vecpath[i].tend()))
					vecpath[i].getme(t) = sdr();
			}
		}
		sort(vecpath.begin(), vecpath.end());
		vecpath.erase( unique(vecpath.begin(), vecpath.end()), vecpath.end() );
		cout << "Reduced to " << vecpath.size() << " unique paths." << endl;
	}
	cout << "Saving " << vecpath.size() << " paths to " << parameters.outfile1 << "..." << flush;
	try {
		save_bz2(vecpath, binary_archive, parameters.outfile1);
		cout << "done." << endl;
	} catch (...) {
		cout << "failed." << endl;
		return 1;
	}
	return 0;
}

int convert(parameters_type& parameters)
{
	sha1differentialpath path;
	vector<sha1differentialpath> vecpath;
	set<sha1differentialpath> setpath;
	bool failed = true;
	if (failed) {
		cout << "Trying to read vector of differential paths in binary..." << flush;
		try {
			load_bz2(vecpath, binary_archive, parameters.infile1);
			if (vecpath.size() > 0) {
				failed = false;
				cout << "success." << endl;
				cout << "Trying to save vector of differential paths in text..." << flush;
				save_bz2(vecpath, text_archive, parameters.infile1 + ".txt.bz2");
				cout << "success." << endl;
			}
		} catch (std::exception& e) {
			cout << "failed." << e.what() << endl;
		}
	}
	if (failed) {
		cout << "Trying to read vector of differential paths in text..." << flush;
		try {
			load_bz2(vecpath, text_archive, parameters.infile1);
			if (vecpath.size() > 0) {
				failed = false;
				cout << "success." << endl;
				cout << "Trying to save vector of differential paths in binary..." << flush;
				save_bz2(vecpath, binary_archive, parameters.infile1 + ".bin");
				cout << "success." << endl;
			}
		} catch (std::exception& e) {
			cout << "failed." << e.what() << endl;
		}
	}
	if (failed) {
		cout << "Trying to read set of differential paths in binary..." << flush;
		try {
			load_bz2(setpath, binary_archive, parameters.infile1);
			if (setpath.size() > 0) {
				vecpath.assign(setpath.begin(),setpath.end());
				failed = false;
				cout << "success." << endl;
				cout << "Trying to save vector of differential paths in text and binary..." << flush;
				save_bz2(vecpath, text_archive, parameters.infile1 + ".txt.bz2");
				save_bz2(vecpath, binary_archive, parameters.infile1 + ".bin");
				cout << "success." << endl;
			}
		} catch (std::exception& e) {
			cout << "failed." << e.what() << endl;
		}
	}
	if (failed) {
		cout << "Trying to read set of differential paths in text..." << flush;
		try {
			load_bz2(setpath, text_archive, parameters.infile1);
			if (setpath.size() > 0) {
				vecpath.assign(setpath.begin(),setpath.end());
				failed = false;
				cout << "success." << endl;
				cout << "Trying to save vector of differential paths in text and binary..." << flush;
				save_bz2(vecpath, text_archive, parameters.infile1 + ".txt.bz2");
				save_bz2(vecpath, binary_archive, parameters.infile1 + ".bin");
				cout << "success." << endl;
			}
		} catch (std::exception& e) {
			cout << "failed." << e.what() << endl;
		}
	}
	if (failed) {
		cout << "Trying to read differential path in binary..." << flush;
		try {
			load_bz2(path, binary_archive, parameters.infile1);
			failed = false;
			cout << "success." << endl;
			cout << "Trying to save differential path in text..." << flush;
			save_bz2(path, text_archive, parameters.infile1 + ".txt.bz2");
			cout << "success." << endl;
		} catch (std::exception& e) {
			cout << "failed." << e.what() << endl;
		}
	}
	if (failed) {
		cout << "Trying to read differential path in text..." << flush;
		try {
			load_bz2(path, text_archive, parameters.infile1);
			failed = false;
			cout << "success." << endl;
			cout << "Trying to save differential path in binary..." << flush;
			save_bz2(path, binary_archive, parameters.infile1 + ".bin");
			cout << "success." << endl;
		} catch (std::exception& e) {
			cout << "failed." << e.what() << endl;
		}
	}
	if (failed)
		return 1;
	return 0;
}

struct dqt_sort {
	uint32 data[5];

	bool operator< (const dqt_sort& r) const
	{
		if (data[4] < r.data[4]) return true;
		if (data[4] > r.data[4]) return false;
		if (data[3] < r.data[3]) return true;
		if (data[3] > r.data[3]) return false;
		if (data[2] < r.data[2]) return true;
		if (data[2] > r.data[2]) return false;
		if (data[1] < r.data[1]) return true;
		if (data[1] > r.data[1]) return false;
		if (data[0] < r.data[0]) return true;
		return false;
	}
	bool operator== (const dqt_sort& r) const
	{
		for (int i = 4; i >= 0; --i)
			if (data[i] != r.data[i]) return false;
		return true;
	}

	template<class Archive>
	void serialize(Archive& ar, const unsigned int file_version)
	{
		ar & boost::serialization::make_nvp("data", data);
	}
};
void analyze_indepsection_prob(sha1messagespace& mespace, int tbegin, const uint32 dmmask[80], vector<dqt_sort>& target_dihvs);
int showmespaceconditions(parameters_type& parameters)
{
	sha1messagespace space;
	vector< vector<uint32> > bitrels;
	bool failed = true;
	cout << "Trying to read space in text..." << flush;
	try {
		load_bz2(space, text_archive, parameters.infile1);
		space.tobitrelations_80(bitrels);
		save(bitrels, text_archive, parameters.infile1 + ".bitrel.txt");
		failed = false;
		cout << "success." << endl;
	} catch (...) {
		cout << "failed." << endl;
	}
	if (failed) {
		cout << "Trying to read space in binary..." << flush;
		try {
			load_bz2(space, binary_archive, parameters.infile1);
			space.tobitrelations_80(bitrels);
			failed = false;
			cout << "success." << endl;
		} catch (...) {
			cout << "failed." << endl;
		}
	}
	if (failed) {
		cout << "Trying to read bitconditions in text..." << flush;
		try {
			load(bitrels, text_archive, parameters.infile1);
			space.frombitrelations_80(bitrels);
			failed = false;
			cout << "success." << endl;
			save_bz2(space, text_archive, parameters.infile1 + ".mespace.txt.bz2");
		} catch (std::exception& e) {
			cout << "failed:" << endl << e.what() << endl;
		} catch (...) {
			cout << "failed." << endl;
		}
	}
	if (failed) return 1;
        for (unsigned i = 0; i < bitrels.size(); ++i) {
                cout << " - ";
                bool firstone = true;
                for (unsigned t = 0; t < 80; ++t)
                        for (unsigned b = 0; b < 32; ++b)
                                if (bitrels[i][t] & (1<<b)) {
                                        if (firstone)
                                                firstone = false;
                                        else
                                                cout << " + ";
                                        cout << "M[" << t << "," << b << "]";
                                }
                cout << " = " << (bitrels[i][80]&1) << endl;
        }
//        return 0;
	int tbegin = 67;
	vector<dqt_sort> tmp;
#if 1
	vector<dqt_sort> base(6);
	for (unsigned i = 0; i < 6; ++i) {
		base[i].data[0] = 1<<31;
		base[i].data[1] = 1<<1;
		base[i].data[2] = (i<2) ? (1<<31) : 0;
		base[i].data[3] = base[i].data[4] = 0;
		if (i==1 || i==4 || i==5) base[i].data[3] += 1<<4;
		if (i==0 || i==1) base[i].data[3] += 1<<6; else base[i].data[3] += 1<<7;
		if (i==0 || i==1) base[i].data[4] += (1<<11)+(1<<4)-(1<<2); else base[i].data[4] += (1<<12);
		if (i==1 || i==4 || i==5) base[i].data[4] += (1<<9);
		if (i==2 || i==4) base[i].data[4] += (1<<1)+(1<<3);
		if (i==3 || i==5) base[i].data[4] += (1<<4)-(1<<1);
	}
	for (unsigned t794 = 0; t794 < 2; ++t794)
	 for (unsigned t792 = 0; t792 < 2; ++t792)
  	  for (unsigned t787 = 0; t787 < 2; ++t787)
	   for (unsigned t783 = 0; t783 < 2; ++t783)
	    for (unsigned t763778 = 0; t763778 < 2; ++t763778)
	     for (unsigned t771772 = 0; t771772 < 2; ++t771772)
	     {
	   	for (unsigned i = 0; i < base.size(); ++i) {
	   		dqt_sort tmptmp = base[i];
	   		if (t794) tmptmp.data[4] -= (1<<5);
	   		if (t792) tmptmp.data[4] += (1<<3);
	   		if (t787) { tmptmp.data[3] -= (1<<8); tmptmp.data[4] -= (1<<13); }
	   		if (t783) { tmptmp.data[3] -= (1<<4); tmptmp.data[4] -= (1<<9); }
	   		if (t763778) tmptmp.data[1] -= (1<<2);
	   		if (t771772 && tmptmp.data[2]) { tmptmp.data[3] += (1<<7); tmptmp.data[4] += (1<<12); }
	   		tmp.push_back(tmptmp);
	   	}
	     }
	cout << "# dIHVs: " << tmp.size() << endl;
#endif
	analyze_indepsection_prob(space, tbegin, parameters.m_mask, tmp);
}

void analyze_indepsection_prob(sha1messagespace& mespace, int tbegin, const uint32 dmmask[80], vector<dqt_sort>& target_dihvs)
{
	const unsigned int tend = 80;
	cout << "Analyzing probability for t=[" << tbegin << "-" << tend << ")." << endl;

	vector< vector<uint32> > pathbitrelations16;
	vector< vector< vector<uint32> > > pathbitrelationsmatrix;
	mespace.tobitrelations_16(pathbitrelations16);
	cout << "Bitrelations: " << pathbitrelations16.size() << endl;
	pathbitrelationsmatrix.resize(16);
	for (unsigned i = 0; i < 16; ++i)
		pathbitrelationsmatrix[i].resize(32);
	for (unsigned i = 0; i < pathbitrelations16.size(); ++i) {
		int lastcol = -1;
		for (int col = 16*32-1; col >= 0; --col)
			if (pathbitrelations16[i][col>>5]&(1<<(col&31))) {
				lastcol = col;
				unsigned t = lastcol>>5;
				unsigned b = lastcol&31;
				pathbitrelationsmatrix[t][b] = pathbitrelations16[i];
				break;
			}
		if (lastcol == -1) throw;
	}

	const int offset = 4;
	uint32 Q[85];
	uint32 Q2[85];
	uint32 m[80];
	uint32 m2[80];

	bool forceddihvs = false;
	vector<dqt_sort> target_dihvs2;
	if (target_dihvs.size()) {
		forceddihvs = true;
		sort(target_dihvs.begin(), target_dihvs.end());
		cout << "Forcing target dIHVs (#=" << target_dihvs.size() << ")." << endl;
	}

	if (0) //target_dihvs.size() == 0)
	{
		cout << "Determining target dIHVs..." << endl;
		uint64 oksize = 1;
		while (true) {
			if (oksize >= (1<<28)) break;
			bool oksizeok = true;
			try {
				vector<dqt_sort> tmpdihvs(oksize*2);
			} catch (std::exception&) {oksizeok = false;} catch(...) {oksizeok = false;}
			if (!oksizeok)
				break;
			oksize *= 2;
		}
		uint64 bufsize = oksize;
		cout << "Maximum size temporary dIHV buffer: " << bufsize << endl;
		vector<dqt_sort> dihvs(bufsize);
		progress_display pd(dihvs.size());
		for (unsigned i = 0; i < dihvs.size(); ++i,++pd) {
			for (int t = 0; t < 16; ++t) {
				uint32 metmask = 0;
				uint32 metset1 = 0;
				for (unsigned b = 0; b < 32; ++b) {
					if (pathbitrelationsmatrix[t][b].size()) {
						metmask |= pathbitrelationsmatrix[t][b][t];
						uint32 v = pathbitrelationsmatrix[t][b][16]&1;
						for (int t1 = 0; t1 < t; ++t1)
							v ^= m[t1]&pathbitrelationsmatrix[t][b][t1];
						if (hw(v)&1)
							metset1 |= 1<<b;
					}
				}
				m[t] = (xrng128() & ~metmask) | metset1;
				m2[t] = m[t] ^ dmmask[t];
			}
			for (int t = 16; t < 80; ++t) {
				m[t]=rotate_left(m[t-3] ^ m[t-8] ^ m[t-14] ^ m[t-16], 1);
				m2[t]=rotate_left(m2[t-3] ^ m2[t-8] ^ m2[t-14] ^ m2[t-16], 1);
			}
			for (int t = tbegin-4; t <= tbegin; ++t)
				Q2[offset+t] = Q[offset+t] = xrng128();
			for (int t = tbegin; t < 60; ++t) {
				sha1_step_round3(t, Q, m);
				sha1_step_round3(t, Q2, m2);
			}
			for (int t = (tbegin<60?60:tbegin); t < 80; ++t) {
				sha1_step_round4(t, Q, m);
				sha1_step_round4(t, Q2, m2);
			}
			dihvs[i].data[0] = rotate_left(Q2[offset+80-4],30)-rotate_left(Q[offset+80-4],30);
			dihvs[i].data[1] = rotate_left(Q2[offset+80-3],30)-rotate_left(Q[offset+80-3],30);
			dihvs[i].data[2] = rotate_left(Q2[offset+80-2],30)-rotate_left(Q[offset+80-2],30);
			dihvs[i].data[3] = Q2[offset+80-1]-Q[offset+80-1];
			dihvs[i].data[4] = Q2[offset+80]-Q[offset+80];
		}
		sort(dihvs.begin(), dihvs.end());
		unsigned i = 0;
		vector<unsigned> bestcnts;
		while (i < dihvs.size()) {
			unsigned j = i+1;
			while (j < dihvs.size() && dihvs[j] == dihvs[i]) ++j;
			bestcnts.push_back(j-i);
			i = j;
		}
		cout << "Prob t=[" << tbegin << "-" << 80 << "): " << flush;
		sort(bestcnts.begin(),bestcnts.end());
		unsigned bestcnt = bestcnts[bestcnts.size()-1];
		if (bestcnt == 1) {
			cerr << "Warning: bestcount = 1: no best dIHVs discernable!" << endl;
			exit(0);
		}

		i = 0;
		while (i < dihvs.size()) {
			unsigned j = i+1;
			while (j < dihvs.size() && dihvs[j] == dihvs[i]) ++j;
			if (forceddihvs) {
				if (binary_search(target_dihvs.begin(), target_dihvs.end(), dihvs[i])) {
					cout << "\tprob=" << log(double(j-i)/double(dihvs.size()))/log(2.0) << ":\t";
					for (unsigned k = 0; k < 5; ++k)
						cout << naf(dihvs[i].data[k]) << " ";
					cout << endl;
				} else if ((j-i)*2 >= bestcnt) {
					cout << "N.I.\tprob=" << log(double(j-i)/double(dihvs.size()))/log(2.0) << ":\t";
					for (unsigned k = 0; k < 5; ++k)
						cout << naf(dihvs[i].data[k]) << " ";
					cout << endl;
					target_dihvs2.push_back(dihvs[i]);
					sort(target_dihvs2.begin(), target_dihvs2.end());
				}
			} else {
				if ((j-i)*2 >= bestcnt) {
 					cout << "\tprob=" << log(double(j-i)/double(dihvs.size()))/log(2.0) << ":\t";
					for (unsigned k = 0; k < 5; ++k)
						cout << naf(dihvs[i].data[k]) << " ";
					cout << endl;
					target_dihvs.push_back(dihvs[i]);
				}
			}
			i = j;
		}
		{ vector<dqt_sort> tmptmp(1); dihvs.swap(tmptmp); } // free memory
		sort(target_dihvs.begin(), target_dihvs.end());
	}

	cout << "Checking cumulative probability target dIHVs: " << flush;
	uint64 cnt = 0, okcnt = 0, okcnt2 = 0;
	dqt_sort dihv;
	while (true) {
		for (int t = 0; t < 16; ++t) {
			uint32 metmask = 0;
			uint32 metset1 = 0;
			for (unsigned b = 0; b < 32; ++b) {
				if (pathbitrelationsmatrix[t][b].size()) {
					metmask |= pathbitrelationsmatrix[t][b][t];
					uint32 v = pathbitrelationsmatrix[t][b][16]&1;
					for (int t1 = 0; t1 < t; ++t1)
						v ^= m[t1]&pathbitrelationsmatrix[t][b][t1];
					if (hw(v)&1)
						metset1 |= 1<<b;
				}
			}
			m[t] = (xrng128() & ~metmask) | metset1;
			m2[t] = m[t] ^ dmmask[t];
		}
		for (int t = 16; t < 80; ++t) {
			m[t]=rotate_left(m[t-3] ^ m[t-8] ^ m[t-14] ^ m[t-16], 1);
			m2[t]=rotate_left(m2[t-3] ^ m2[t-8] ^ m2[t-14] ^ m2[t-16], 1);
		}
		for (int t = tbegin-4; t <= tbegin; ++t)
			Q2[offset+t] = Q[offset+t] = xrng64();
		for (int t = tbegin; t < 60; ++t) {
			sha1_step_round3(t, Q, m);
			sha1_step_round3(t, Q2, m2);
		}
		for (int t = (tbegin<60?60:tbegin); t < 80; ++t) {
			sha1_step_round4(t, Q, m);
			sha1_step_round4(t, Q2, m2);
		}
		dihv.data[0] = rotate_left(Q2[offset+80-4],30)-rotate_left(Q[offset+80-4],30);
		dihv.data[1] = rotate_left(Q2[offset+80-3],30)-rotate_left(Q[offset+80-3],30);
		dihv.data[2] = rotate_left(Q2[offset+80-2],30)-rotate_left(Q[offset+80-2],30);
		dihv.data[3] = Q2[offset+80-1]-Q[offset+80-1];
		dihv.data[4] = Q2[offset+80]-Q[offset+80];
		++cnt;
		if (binary_search(target_dihvs.begin(), target_dihvs.end(), dihv)) {
			++okcnt;
			if (forceddihvs)
				++okcnt2;
		} else {
			if (forceddihvs && binary_search(target_dihvs2.begin(), target_dihvs2.end(), dihv))
				++okcnt2;
		}
		if (hw(cnt)+hw(cnt>>32)==1 && okcnt > 1) {
			cout << "[" << cnt << ":p=" << log(double(okcnt)/double(cnt))/log(2.0);
			if (forceddihvs)
				cout << "|p2=" << log(double(okcnt2)/double(cnt))/log(2.0);
			cout << "]" << flush;
		}
	}
	cout << endl;
}


#include <iostream>
#include <boost/algorithm/string.hpp>
#include <storm/exceptions/WrongFormatException.h>
#include "storm/io/file.h"
#include "storm/utility/constants.h"
#include "storm/storage/expressions/Expression.h"
#include "storm/storage/expressions/ExpressionManager.h"
#include "storm-pomdp/analysis/WinningRegion.h"

namespace storm {
namespace pomdp {
    WinningRegion::WinningRegion(std::vector<uint64_t> const& observationSizes) : observationSizes(observationSizes)
    {
        for (uint64_t i = 0; i < observationSizes.size(); ++i) {
            winningRegion.push_back(std::vector<storm::storage::BitVector>());
        }
    }

    void WinningRegion::setObservationIsWinning(uint64_t observation) {
        winningRegion[observation] = { storm::storage::BitVector(observationSizes[observation], true) };
    }

    void WinningRegion::addTargetStates(uint64_t observation, storm::storage::BitVector const& offsets) {
        assert(!offsets.empty());
        if(winningRegion[observation].empty()) {
            winningRegion[observation].push_back(offsets);
            return;
        }
        std::vector<storm::storage::BitVector> newWinningSupport = std::vector<storm::storage::BitVector>();

        for (auto const& support : winningRegion[observation]) {
            newWinningSupport.push_back(support | offsets);
        }
        // TODO it may be worthwhile to check whether something changed. If nothing changed, there is no need for the next routine.
        // TODO the following code is  bit naive.
        winningRegion[observation].clear(); // This prevents some overhead.
        for (auto const& newWinning : newWinningSupport) {
            update(observation, newWinning);
        }
    }

    bool WinningRegion::update(uint64_t observation, storm::storage::BitVector const& winning) {
        std::vector<storm::storage::BitVector> newWinningSupport = std::vector<storm::storage::BitVector>();
        bool changed = false;
        for (auto const& support : winningRegion[observation]) {
            if (winning.isSubsetOf(support)) {
                // This new winning support is already covered.
                return false;
            }
            if(support.isSubsetOf(winning)) {
                // This new winning support extends the previous support, thus the previous support is now spurious
                changed = true;
            } else {
                newWinningSupport.push_back(support);
            }
        }

        // only if changed.
        if (changed) {
            newWinningSupport.push_back(winning);
            winningRegion[observation] = newWinningSupport;
        } else {
            winningRegion[observation].push_back(winning);
        }
        return true;

    }

    bool WinningRegion::query(uint64_t observation, storm::storage::BitVector const& currently) const {
        for(storm::storage::BitVector winning : winningRegion[observation]) {
            if(currently.isSubsetOf(winning)) {
                return true;
            }
        }
        return false;
    }

    storm::expressions::Expression WinningRegion::extensionExpression(uint64_t observation, std::vector<storm::expressions::Expression>& varsForStates) const {
        std::vector<storm::expressions::Expression> expressionForEntry;

        for(auto const& winningForObservation : winningRegion[observation]) {
            if (winningForObservation.full()) {
                assert(winningRegion[observation].size() == 1);
                return varsForStates.front().getManager().boolean(false);
            }
            std::vector<storm::expressions::Expression> subexpr;
            std::vector<storm::expressions::Expression> leftHandSides;
            assert(varsForStates.size() == winningForObservation.size());
            for(uint64_t i = 0; i < varsForStates.size(); ++i) {
                if (winningForObservation.get(i)) {
                    leftHandSides.push_back(varsForStates[i]);
                } else {
                    subexpr.push_back(varsForStates[i]);
                }
            }
            storm::expressions::Expression rightHandSide = storm::expressions::disjunction(subexpr);
            for(auto const& lhs : leftHandSides) {
                expressionForEntry.push_back(storm::expressions::implies(lhs,rightHandSide));
            }
            expressionForEntry.push_back(storm::expressions::disjunction(varsForStates));

        }
        return storm::expressions::conjunction(expressionForEntry);
    }

    /**
     * If we observe this observation, do we surely win?
     * @param observation
     * @return yes, if all supports for this observation are winning.
     */
    bool WinningRegion::observationIsWinning(uint64_t observation) const {
        return winningRegion[observation].size() == 1 && winningRegion[observation].front().full();
    }

    void WinningRegion::print() const {
        uint64_t observation = 0;
        std::vector<uint64_t> winningObservations;
        std::vector<uint64_t> loosingObservations;

        for (auto const& winningSupport : winningRegion) {
            if (observationIsWinning(observation)) {
                winningObservations.push_back(observation);
            } else if(winningRegion[observation].empty()) {
                loosingObservations.push_back(observation);
            } else {
                std::cout << "***** observation" << observation << std::endl;
                for (auto const& support : winningSupport) {
                    std::cout << " " << support;
                }
                std::cout << std::endl;
            }
            observation++;
        }
        std::cout << "and  " << winningObservations.size() << " winning observations: (";
        for (auto const& obs : winningObservations) {
            std::cout << obs << " ";
        }
        std::cout << ") and " << loosingObservations.size() << " loosing observations: (";
        for (auto const& obs : loosingObservations) {
            std::cout << obs << " ";
        }

    }

    /**
     * How many different observations are there?
     * @return
     */
    uint64_t WinningRegion::getNumberOfObservations() const {
        assert(winningRegion.size() == observationSizes.size());
        return observationSizes.size();
    }

    bool WinningRegion::empty() const {
        for (auto const& ob : winningRegion) {
            if (!ob.empty()) {
                return false;
            }
        }
        return true;
    }

    std::vector<storm::storage::BitVector> const& WinningRegion::getWinningSetsPerObservation(uint64_t observation) const {

        assert(observation < getNumberOfObservations());
        return winningRegion[observation];
    }

    storm::RationalNumber WinningRegion::beliefSupportStates() const {
        storm::RationalNumber total = 0;
        storm::RationalNumber two = storm::utility::convertNumber<storm::RationalNumber>(2);
        for (auto const& size : observationSizes) {
            total += carl::pow(two,size) - 1;
        }
        return total;
    }

    std::pair<storm::RationalNumber, storm::RationalNumber> count(std::vector<storm::storage::BitVector> const&  origSets, std::vector<storm::storage::BitVector> const& intersects,
            std::vector<storm::storage::BitVector> const& intersectsInfo,
            storm::RationalNumber val,
            bool plus, uint64_t remdepth) {
        assert(intersects.size() == intersectsInfo.size());
        storm::RationalNumber newVal = val;
        storm::RationalNumber two = storm::utility::convertNumber<storm::RationalNumber>(2);
        for (uint64_t i = 0; i < intersects.size(); ++i) {
            if(plus) {
                newVal += carl::pow(two, intersects[i].getNumberOfSetBits());
            } else {
                newVal -= carl::pow(two, intersects[i].getNumberOfSetBits());
            }
        }

        storm::RationalNumber diff = val-newVal;
        storm::RationalNumber max = storm::utility::max(val,newVal);

        diff = storm::utility::abs(diff);
        if (remdepth == 0 || 20 * diff < max) {
            if (plus) {
                return std::make_pair(val, newVal);
            } else {
                return std::make_pair(newVal, val);
            }
        } else {
            storm::RationalNumber skipped = 0;
            uint64_t upperBoundElements =  origSets.size() * intersects.size();
            STORM_LOG_DEBUG("Upper bound on number of elements to be considered " << upperBoundElements);
            STORM_LOG_DEBUG("Value " << val << " newVal " << newVal);
            uint64_t oom = 0;
            uint64_t critoom = 0;
            storm::RationalNumber n = 1;
            while (n < max) {
                oom += 1;
                n *= 2;
            }
            STORM_LOG_DEBUG("Order of magnitude = " << oom);

            critoom = oom - floor(log2(upperBoundElements));

            STORM_LOG_DEBUG("Crit Order of magnitude = " << critoom);

            uint64_t intersectSetSkip = critoom - floor(log2(origSets.size()));

            std::vector<storm::storage::BitVector> useIntersects;
            std::vector<storm::storage::BitVector> useInfo;
            for(uint64_t i = 0; i < intersects.size(); ++i) {
                if (upperBoundElements > 10000 && intersects[i].getNumberOfSetBits() < intersectSetSkip - 3) {
                    skipped += (carl::pow(two, intersects[i].getNumberOfSetBits()) * origSets.size());
                    STORM_LOG_DEBUG("Skipped " << skipped);
                } else {
                    useIntersects.push_back(intersects[i]);
                    useInfo.push_back(intersectsInfo[i]);
                }
            }

            uint64_t origSetSkip = critoom - floor(log2(useIntersects.size()));
            STORM_LOG_DEBUG("OrigSkip= " << origSetSkip);

            std::vector<storm::storage::BitVector> newIntersects;
            std::vector<storm::storage::BitVector> newInfo;

            for (uint64_t i = 0; i < origSets.size(); ++i) {
                if (upperBoundElements > 20000 && origSets[i].getNumberOfSetBits() < origSetSkip - 3) {
                    skipped += (carl::pow(two, origSets[i].getNumberOfSetBits()) * useIntersects.size());
                    STORM_LOG_DEBUG("Skipped " << skipped);
                    continue;
                }
                for (uint64_t j = 0; j < useIntersects.size(); ++j ) {
                    if (useInfo[j].get(i)) {
                        continue;
                    }
                    storm::storage::BitVector newinf = useInfo[j];
                    newinf.set(i);
                    if (newinf == useInfo[j]) {
                        continue;
                    }
                    bool exists = false;
                    for( auto const& entry : newInfo) {
                        if (entry == newinf) {
                            exists = true;
                            break;
                        }
                    }
                    if(!exists) {
                        newInfo.push_back(newinf);
                        newIntersects.push_back(origSets[i] & useIntersects[j]);
                    }
                }
            }

            auto res =  count(origSets, newIntersects, newInfo, newVal, !plus, remdepth - 1);
            if (plus) {
                storm::RationalNumber tmp = res.first - skipped;
                return std::make_pair(storm::utility::max(tmp, val), res.second);
            } else {
                storm::RationalNumber tmp = res.second + skipped;
                return std::make_pair(res.first, storm::utility::min(tmp, val));
            }
        }
    }

    std::pair<storm::RationalNumber,storm::RationalNumber> WinningRegion::computeNrWinningBeliefs() const {
        storm::RationalNumber upper = 0;
        storm::RationalNumber lower = 0;
        storm::RationalNumber two = storm::utility::convertNumber<storm::RationalNumber>(2);
        for (auto const& winningSets : winningRegion) {
            storm::RationalNumber totalForObs = 0;
            storm::RationalNumber two = storm::utility::convertNumber<storm::RationalNumber>(2);

            std::vector<storm::storage::BitVector> info; // which intersections are part of this
            for (uint64_t i = 0; i < winningSets.size(); ++i) {
                storm::storage::BitVector entry(winningSets.size());
                entry.set(i);
                info.push_back(entry);
            }
            auto res = count(winningSets, winningSets, info, totalForObs, true, 40);
            lower += res.first;
            upper += res.second;
        }
        if (lower > 0) {
            lower -= 1;
            upper -= 1;
        }
        return std::make_pair(lower,upper);
    }

    uint64_t WinningRegion::getStorageSize() const {
        uint64_t result = 0;
        for (uint64_t i = 0; i < getNumberOfObservations(); ++i) {
            result += winningRegion[i].size() * observationSizes[i];
        }
        return result;
    }

    void WinningRegion::storeToFile(std::string const& path, std::string const& preamble, bool append) const {
        std::ofstream file;
        storm::utility::openFile(path, file, append);
        file << ":preamble" << std::endl;
        file << preamble << std::endl;
        file << ":winningregion" << std::endl;
        bool firstLine = true;
        for (auto const& i : observationSizes) {
            if(!firstLine) {
                file << " ";
            } else {
                firstLine = false;
            }
            file << i;
        }
        file << std::endl;
        for (auto const& obsWr : winningRegion) {
            for (auto const& bv : obsWr) {
                bv.store(file);
                file << ";";
            }
            file << std::endl;
        }
        storm::utility::closeFile(file);
    }

    std::pair<WinningRegion,std::string> WinningRegion::loadFromFile(std::string const& path) {
        std::ifstream file;
        std::vector<uint64_t> observationSizes;
        storm::utility::openFile(path, file);
        std::string line;
        uint64_t state = 0; // 0 = expect preamble
        uint64_t observation = 0;
        WinningRegion wr({1});
        std::stringstream preamblestream;
        while (std::getline(file, line))
        {
            if (boost::starts_with(line, "#")) {
                continue;
            }
            if (state == 0) {
                STORM_LOG_THROW(line == ":preamble", storm::exceptions::WrongFormatException, "Expected to see :preamble");
                state = 1; // state = 1: preamble
            } else if (state == 1) {
                if (line == ":winningregion") {
                    state = 2; // get sizes
                } else {
                    preamblestream << line << std::endl;
                }
            } else if (state == 2) {
                std::vector<std::string> entries;
                boost::split(entries, line, boost::is_space());
                std::vector<uint64_t> observationSizes;
                for (auto const &entry : entries) {
                    observationSizes.push_back(std::stoul(entry));
                }
                wr = WinningRegion(observationSizes);
                state = 3;
            } else if (state == 3) {
                std::vector<std::string> entries;
                boost::split(entries, line, boost::is_any_of(";"));
                entries.pop_back();
                for (std::string const& bvString : entries) {
                    wr.update(observation, storm::storage::BitVector::load(bvString));
                }
                ++observation;
            }
        }
        storm::utility::closeFile(file);
        return {wr,preamblestream.str()};
    }

}
}


/* Copyright (c) 2016 - 2021, the adamantine authors.
 *
 * This file is subject to the Modified BSD License and may not be distributed
 * without copyright and license information. Please refer to the file LICENSE
 * for the text and further information on this license.
 */

#ifndef THERMAL_OPERATOR_HH
#define THERMAL_OPERATOR_HH

#include <HeatSource.hh>
#include <MaterialProperty.hh>
#include <ThermalOperatorBase.hh>

#include <deal.II/matrix_free/matrix_free.h>

namespace adamantine
{
/**
 * This class is the operator associated with the heat equation, i.e., vmult
 * performs \f$ dst = -\nabla k \nabla src \f$.
 */
template <int dim, int fe_degree, typename MemorySpaceType>
class ThermalOperator final : public ThermalOperatorBase<dim, MemorySpaceType>
{
public:
  ThermalOperator(MPI_Comm const &communicator, BoundaryType boundary_type,
                  std::shared_ptr<MaterialProperty<dim>> material_properties,
                  std::vector<std::shared_ptr<HeatSource<dim>>> heat_sources);

  /**
   * Associate the AffineConstraints<double> and the MatrixFree objects to the
   * underlying Triangulation.
   */
  void reinit(dealii::DoFHandler<dim> const &dof_handler,
              dealii::AffineConstraints<double> const &affine_constraints,
              dealii::hp::QCollection<1> const &quad) override;

  /**
   * Compute the inverse of the mass matrix and update the material properties.
   */
  void compute_inverse_mass_matrix(
      dealii::DoFHandler<dim> const &dof_handler,
      dealii::AffineConstraints<double> const &affine_constraints,
      dealii::hp::FECollection<dim> const &fe_collection) override;

  /**
   * Clear the MatrixFree object and resize the inverse of the mass matrix to
   * zero.
   */
  void clear();

  dealii::types::global_dof_index m() const override;

  dealii::types::global_dof_index n() const override;

  /**
   * Return a shared pointer to the inverse of the mass matrix.
   */
  std::shared_ptr<dealii::LA::distributed::Vector<double, MemorySpaceType>>
  get_inverse_mass_matrix() const override;

  /**
   * Return a shared pointer to the underlying MatrixFree object.
   */
  dealii::MatrixFree<dim, double> const &get_matrix_free() const;

  void vmult(dealii::LA::distributed::Vector<double, MemorySpaceType> &dst,
             dealii::LA::distributed::Vector<double, MemorySpaceType> const
                 &src) const override;

  void Tvmult(dealii::LA::distributed::Vector<double, MemorySpaceType> &dst,
              dealii::LA::distributed::Vector<double, MemorySpaceType> const
                  &src) const override;

  void vmult_add(dealii::LA::distributed::Vector<double, MemorySpaceType> &dst,
                 dealii::LA::distributed::Vector<double, MemorySpaceType> const
                     &src) const override;

  void Tvmult_add(dealii::LA::distributed::Vector<double, MemorySpaceType> &dst,
                  dealii::LA::distributed::Vector<double, MemorySpaceType> const
                      &src) const override;

  void
  jacobian_vmult(dealii::LA::distributed::Vector<double, MemorySpaceType> &dst,
                 dealii::LA::distributed::Vector<double, MemorySpaceType> const
                     &src) const override;

  void initialize_dof_vector(
      dealii::LA::distributed::Vector<double, MemorySpaceType> &vector)
      const override;

  void get_state_from_material_properties() override;

  void set_state_to_material_properties() override;

  /**
   * Evaluate the material properties for a given state field.
   */
  // This function should be removed once the MaterialProperty for the device
  // has been reworked and the material properties on the surface is computed
  // correctly. The current name does not correctly explain what this
  // function does but the name is still correct for the device.
  void evaluate_material_properties(
      dealii::LA::distributed::Vector<double, dealii::MemorySpace::Host> const
          &state) override;

  /**
   * Return the value of \f$ \frac{1}{\rho C_p} \f$ for a given cell.
   */
  double
  get_inv_rho_cp(typename dealii::DoFHandler<dim>::cell_iterator const &cell,
                 unsigned int q) const override;

  void set_time_and_source_height(double t, double height) override;

private:
  /**
   * Update the ratios of the material state.
   */
  void
  update_state_ratios(unsigned int cell, unsigned int q,
                      dealii::VectorizedArray<double> temperature,
                      std::array<dealii::VectorizedArray<double>,
                                 static_cast<unsigned int>(MaterialState::SIZE)>
                          &state_ratios) const;
  /**
   * Return the value of \f$ \frac{1}{\rho C_p} \f$ for a given matrix-free cell
   * and quadrature point.
   */
  dealii::VectorizedArray<double>
  get_inv_rho_cp(unsigned int cell, unsigned int q,
                 std::array<dealii::VectorizedArray<double>,
                            static_cast<unsigned int>(MaterialState::SIZE)>
                     state_ratios,
                 dealii::VectorizedArray<double> temperature) const;

  /**
   * Apply the operator on a given set of quadrature points.
   */
  void cell_local_apply(
      dealii::MatrixFree<dim, double> const &data,
      dealii::LA::distributed::Vector<double, MemorySpaceType> &dst,
      dealii::LA::distributed::Vector<double, MemorySpaceType> const &src,
      std::pair<unsigned int, unsigned int> const &cell_range) const;

  /**
   * MPI communicator.
   */
  MPI_Comm const &_communicator;
  /**
   * Type of boundary.
   */
  BoundaryType _boundary_type;
  /**
   * Current time of the simulation.
   */
  double _time = 0.;
  /**
   * Current height of the heat sources.
   */
  double _current_source_height = 0.;
  /**
   * Data to configure the MatrixFree object.
   */
  typename dealii::MatrixFree<dim, double>::AdditionalData _matrix_free_data;
  /**
   * Store the \f$ \frac{1}{\rho C_p}\f$ coefficient.
   */
  mutable dealii::Table<2, dealii::VectorizedArray<double>> _inv_rho_cp;
  /**
   * Table of thermal conductivity coefficient.
   */
  dealii::Table<2, dealii::VectorizedArray<double>> _thermal_conductivity;
  /**
   * Material properties associated with the domain.
   */
  std::shared_ptr<MaterialProperty<dim>> _material_properties;
  /**
   * Vector of heat sources.
   */
  std::vector<std::shared_ptr<HeatSource<dim>>> _heat_sources;
  /**
   * Underlying MatrixFree object.
   */
  dealii::MatrixFree<dim, double> _matrix_free;
  /**
   * Non-owning pointer to the AffineConstraints from ThermalPhysics.
   */
  dealii::AffineConstraints<double> const *_affine_constraints;
  /**
   * The inverse of the mass matrix is computed using an inexact
   * Gauss-Lobatto quadrature. This inexact quadrature makes the mass matrix
   * and therefore also its inverse, a diagonal matrix.
   */
  std::shared_ptr<dealii::LA::distributed::Vector<double, MemorySpaceType>>
      _inverse_mass_matrix;
  /**
   * Map between the cell iterator and the position in _inv_rho_cp table.
   */
  std::map<typename dealii::DoFHandler<dim>::cell_iterator,
           std::pair<unsigned int, unsigned int>>
      _cell_it_to_mf_cell_map;
  /**
   * Table of the powder fraction, mutable so that it can be changed in
   * local_apply, which is const.
   */
  mutable dealii::Table<2, dealii::VectorizedArray<double>> _liquid_ratio;
  /**
   * Table of the powder fraction, mutable so that it can be changed in
   * local_apply, which is const.
   */
  mutable dealii::Table<2, dealii::VectorizedArray<double>> _powder_ratio;
  /**
   * Table of the material index, mutable so that it can be changed in
   * local_apply, which is const.
   */
  mutable dealii::Table<2, std::array<dealii::types::material_id,
                                      dealii::VectorizedArray<double>::size()>>
      _material_id;
};

template <int dim, int fe_degree, typename MemorySpaceType>
inline double ThermalOperator<dim, fe_degree, MemorySpaceType>::get_inv_rho_cp(
    typename dealii::DoFHandler<dim>::cell_iterator const &cell,
    unsigned int q) const
{
  auto cell_comp_pair = _cell_it_to_mf_cell_map.find(cell);
  ASSERT(cell_comp_pair != _cell_it_to_mf_cell_map.end(), "Internal error");

  return _inv_rho_cp(cell_comp_pair->second.first,
                     q)[cell_comp_pair->second.second];
}

template <int dim, int fe_degree, typename MemorySpaceType>
inline dealii::types::global_dof_index
ThermalOperator<dim, fe_degree, MemorySpaceType>::m() const
{
  return _matrix_free.get_vector_partitioner()->size();
}

template <int dim, int fe_degree, typename MemorySpaceType>
inline dealii::types::global_dof_index
ThermalOperator<dim, fe_degree, MemorySpaceType>::n() const
{
  return _matrix_free.get_vector_partitioner()->size();
}

template <int dim, int fe_degree, typename MemorySpaceType>
inline std::shared_ptr<dealii::LA::distributed::Vector<double, MemorySpaceType>>
ThermalOperator<dim, fe_degree, MemorySpaceType>::get_inverse_mass_matrix()
    const
{
  return _inverse_mass_matrix;
}

template <int dim, int fe_degree, typename MemorySpaceType>
inline dealii::MatrixFree<dim, double> const &
ThermalOperator<dim, fe_degree, MemorySpaceType>::get_matrix_free() const
{
  return _matrix_free;
}

template <int dim, int fe_degree, typename MemorySpaceType>
inline void ThermalOperator<dim, fe_degree, MemorySpaceType>::jacobian_vmult(
    dealii::LA::distributed::Vector<double, MemorySpaceType> &dst,
    dealii::LA::distributed::Vector<double, MemorySpaceType> const &src) const
{
  vmult(dst, src);
}

template <int dim, int fe_degree, typename MemorySpaceType>
inline void
ThermalOperator<dim, fe_degree, MemorySpaceType>::initialize_dof_vector(
    dealii::LA::distributed::Vector<double, MemorySpaceType> &vector) const
{
  _matrix_free.initialize_dof_vector(vector);
}

template <int dim, int fe_degree, typename MemorySpaceType>
inline void
ThermalOperator<dim, fe_degree, MemorySpaceType>::set_time_and_source_height(
    double t, double height)
{
  _time = t;
  _current_source_height = height;
}
} // namespace adamantine

#endif