//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
//
// MIT License
//
// 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.
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_DETAIL_IMPLODER_HPP
#define CRYPTO3_DETAIL_IMPLODER_HPP

#include <nil/crypto3/detail/stream_endian.hpp>
#include <nil/crypto3/detail/unbounded_shift.hpp>

#include <boost/static_assert.hpp>

#include <climits>
#include <cstring>

namespace nil {
    namespace crypto3 {
        namespace detail {

            // By definition, for all imploders, InputBits < OutputBits,
            // so we're taking many smaller values and combining them into one value

            template<typename Endianness, int InputBits, int OutputBits, int k>
            struct imploder_step;

            template<int UnitBits, int InputBits, int OutputBits, int k>
            struct imploder_step<stream_endian::big_unit_big_bit<UnitBits>, InputBits, OutputBits, k> {
                template<typename InputValue, typename OutputValue>
                static void step(InputValue z, OutputValue &x) {
                    int const shift = OutputBits - (InputBits + k);
                    OutputValue y = low_bits<InputBits>(OutputValue(z));
                    x |= unbounded_shl<shift>(y);
                }
            };

            template<int UnitBits, int InputBits, int OutputBits, int k>
            struct imploder_step<stream_endian::little_unit_big_bit<UnitBits>, InputBits, OutputBits, k> {
                template<typename InputValue, typename OutputValue>
                static void step(InputValue z, OutputValue &x) {
                    int const kb = (k % UnitBits);
                    int const ku = k - kb;
                    int const shift = InputBits >= UnitBits  ? k :
                                      OutputBits >= UnitBits ? ku + (UnitBits - (InputBits + kb)) :
                                                               OutputBits - (InputBits + kb);
                    OutputValue y = low_bits<InputBits>(OutputValue(z));
                    x |= unbounded_shl<shift>(y);
                }
            };

            template<int UnitBits, int InputBits, int OutputBits, int k>
            struct imploder_step<stream_endian::big_unit_little_bit<UnitBits>, InputBits, OutputBits, k> {
                template<typename InputValue, typename OutputValue>
                static void step(InputValue z, OutputValue &x) {
                    int const kb = (k % UnitBits);
                    int const ku = k - kb;
                    int const shift = InputBits >= UnitBits  ? OutputBits - (InputBits + k) :
                                      OutputBits >= UnitBits ? OutputBits - (UnitBits + ku) + kb :
                                                               kb;
                    OutputValue y = low_bits<InputBits>(OutputValue(z));
                    x |= unbounded_shl<shift>(y);
                }
            };

            template<int UnitBits, int InputBits, int OutputBits, int k>
            struct imploder_step<stream_endian::little_unit_little_bit<UnitBits>, InputBits, OutputBits, k> {
                template<typename InputValue, typename OutputValue>
                static void step(InputValue z, OutputValue &x) {
                    int const shift = k;
                    OutputValue y = low_bits<InputBits>(OutputValue(z));
                    x |= unbounded_shl<shift>(y);
                }
            };

            template<int UnitBits, int InputBits, int OutputBits, int k>
            struct imploder_step<stream_endian::host_unit<UnitBits>, InputBits, OutputBits, k> {
                template<typename InputValue, typename OutputValue>
                static void step(InputValue z, OutputValue &x) {
                    BOOST_STATIC_ASSERT(sizeof(InputValue) * CHAR_BIT == InputBits);
                    BOOST_STATIC_ASSERT(sizeof(OutputValue) * CHAR_BIT == OutputBits);
                    std::memcpy((char *)&x + k / CHAR_BIT, &z, InputBits / CHAR_BIT);
                }
            };

            template<typename Endianness, int InputBits, int OutputBits, int k = 0>
            struct imploder;

            template<template<int> class Endian, int UnitBits, int InputBits, int OutputBits, int k>
            struct imploder<Endian<UnitBits>, InputBits, OutputBits, k> {

                // To keep the implementation managable, input and output sizes must
                // be multiples or factors of the unit size.
                // If one of these is firing, you may want a bit-only stream_endian
                // rather than one that mentions bytes or octets.
                BOOST_STATIC_ASSERT(!(InputBits % UnitBits && UnitBits % InputBits));
                BOOST_STATIC_ASSERT(!(OutputBits % UnitBits && UnitBits % OutputBits));

                typedef Endian<UnitBits> Endianness;
                typedef imploder_step<Endianness, InputBits, OutputBits, k> step_type;
                typedef imploder<Endianness, InputBits, OutputBits, k + InputBits> next_type;

                template<typename InIter, typename OutputValue>
                static void implode(InIter &in, OutputValue &x) {
                    step_type::step(*in++, x);
                    next_type::implode(in, x);
                }
            };

            template<template<int> class Endian, int UnitBits, int InputBits, int OutputBits>
            struct imploder<Endian<UnitBits>, InputBits, OutputBits, OutputBits> {
                template<typename InIter, typename OutputValue>
                static void implode(InIter &, OutputValue &) {
                }
            };

        }    // namespace detail
    }        // namespace crypto3
}    // namespace nil

#endif    // CRYPTO3_BLOCK_DETAIL_IMPLODER_HPP


// Dynamics used for integrating
#ifndef DYNAMICS_H
#define DYNAMICS_H

#include "segway_sim/common.hpp"
#include <Eigen/Dense>
#include <math.h>

static const double model[15] = {44.798,            //mb
                                 2.485,             //mw
                                 0.055936595310797, //Jw
                                 -0.02322718759275, //a2
                                 0.166845864363019, //c2
                                 3.604960049044268, //A2
                                 3.836289730154863, //B2
                                 1.069672194414735, //C2
                                 1.261650363363571, //K
                                 0.195,             //r
                                 0.5,               //L
                                 9.81,              //gGravity
                                 0.,                //FricCoeff 3.185188257847262
                                 1.0e-3,            //velEps
                                 1.225479467549329  //FricCoeff 1.225479467549329
                                 };

void dynamics(const double t,
              const double X[STATE_LENGTH],
              const double U[INPUT_LENGTH],
                    double xDot[STATE_LENGTH])
{
	double g[STATE_LENGTH*INPUT_LENGTH];
	double Fric;
	double a_tmp;
	double b_a_tmp;
	double f_tmp;
	double b_f_tmp;
	double c_f_tmp;
	double d_f_tmp;
	double e_f_tmp;
	double f_f_tmp;
	double g_f_tmp;
	double h_f_tmp;
	double i_f_tmp;
	double j_f_tmp;
	double k_f_tmp;
	double l_f_tmp;
	double m_f_tmp;
	double n_f_tmp;
	double o_f_tmp;
	double p_f_tmp;
	double q_f_tmp;
	double r_f_tmp;
	double s_f_tmp;
	double t_f_tmp;
	double u_f_tmp;
	double v_f_tmp;
	double f_tmp_tmp;
	double b_f_tmp_tmp;
	double w_f_tmp;
	double x_f_tmp;

/*  */
	Fric = X[3] - X[6] * model[9];
	Fric = model[12] * tanh(Fric / model[13]) + model[14] * Fric;
	a_tmp = cos(X[5]);
	b_a_tmp = sin(X[5]);
	xDot[0] = X[3] * cos(X[2]);
	xDot[1] = X[3] * sin(X[2]);
	xDot[2] = X[4];
	f_tmp = model[3] * model[3];
	b_f_tmp = model[9] * model[9];
	c_f_tmp = model[4] * model[4];
	d_f_tmp = model[0] * model[0];
	e_f_tmp = 4.0 * f_tmp;
	f_f_tmp = 4.0 * c_f_tmp;
	g_f_tmp = X[4] * X[4];
	h_f_tmp = X[6] * X[6];
	i_f_tmp = 4.0 * h_f_tmp + 3.0 * g_f_tmp;
	j_f_tmp = cos(2.0 * X[5]);
	k_f_tmp = cos(3.0 * X[5]);
	l_f_tmp = pow(model[3], 3.0);
	m_f_tmp = 4.0 * model[6] * model[4] * model[0];
	n_f_tmp = pow(model[4], 3.0);
	o_f_tmp = sin(2.0 * X[5]);
	p_f_tmp = model[5] * model[4] * model[0] * model[9] * g_f_tmp;
	q_f_tmp = -model[4] * model[7] * model[0] * model[9] * g_f_tmp;
	r_f_tmp = sin(3.0 * X[5]);
	s_f_tmp = 3.0 * f_tmp * model[4] * d_f_tmp * model[9] * g_f_tmp;
	t_f_tmp = -4.0 * model[3] * model[4];
	u_f_tmp = 2.0 * model[3] * model[4];
	v_f_tmp = f_tmp * d_f_tmp;
	f_tmp_tmp = v_f_tmp * b_f_tmp;
	b_f_tmp_tmp = c_f_tmp * d_f_tmp * b_f_tmp;
	f_f_tmp = 1.0 / ((((((((((4.0 * model[6] * model[2] + e_f_tmp * model[2] *
		model[0]) + f_f_tmp * model[2] * model[0]) + 2.0 * model[6] * model[0] *
	b_f_tmp) + f_tmp_tmp) + b_f_tmp_tmp) + 4.0 * model[6] * model[1] * b_f_tmp)
	+ e_f_tmp * model[0] * model[1] * b_f_tmp) + f_f_tmp *
	model[0] * model[1] * b_f_tmp) + (f_tmp + -c_f_tmp) *
	d_f_tmp * b_f_tmp * j_f_tmp) + u_f_tmp * d_f_tmp * b_f_tmp *
	o_f_tmp);
	w_f_tmp = 2.0 * f_tmp;
	xDot[3] = 0.5 * model[9] * f_f_tmp * (((((((((((((((((((((((-8.0 * model[6] *
		Fric + -8.0 * f_tmp * Fric * model[0]) + -8.0 * c_f_tmp * Fric * model[0]) +
	model[0] * model[9] * ((((-8.0 * model[4] * Fric + model[3] * (-model[5] +
		model[7]) * g_f_tmp) + 4.0 * model[3] * model[6] * (h_f_tmp + g_f_tmp)) +
	l_f_tmp * model[0] * i_f_tmp) + model[3] * c_f_tmp * model[0] * i_f_tmp) *
	a_tmp) + t_f_tmp * model[11] * d_f_tmp * model[9] * j_f_tmp) + model[3] *
	model[5] * model[0] * model[9] * g_f_tmp * k_f_tmp) + -model[3] * model[7] *
	model[0] * model[9] * g_f_tmp * k_f_tmp) + l_f_tmp * d_f_tmp * model[9] *
	g_f_tmp * k_f_tmp) + -3.0 * model[3] * c_f_tmp * d_f_tmp * model[9] *
	g_f_tmp * k_f_tmp) + 8.0 * model[3] * Fric * model[0] * model[9] * b_a_tmp)
	+ m_f_tmp * h_f_tmp * model[9] * b_a_tmp) + e_f_tmp * model[4] * d_f_tmp *
	h_f_tmp * model[9] * b_a_tmp) + 4.0 * n_f_tmp * d_f_tmp * h_f_tmp * model[9]
	* b_a_tmp) + p_f_tmp * b_a_tmp) + m_f_tmp * model[9] * g_f_tmp * b_a_tmp) +
	q_f_tmp * b_a_tmp) + s_f_tmp * b_a_tmp) + 3.0 * n_f_tmp * d_f_tmp * model[9]
	* g_f_tmp * b_a_tmp) + w_f_tmp * model[11] * d_f_tmp * model[9] * o_f_tmp) +
	-2.0 * c_f_tmp * model[11] * d_f_tmp * model[9] * o_f_tmp) + p_f_tmp *
	r_f_tmp) + q_f_tmp * r_f_tmp) + s_f_tmp * r_f_tmp) + -n_f_tmp * d_f_tmp *
	model[9] * g_f_tmp * r_f_tmp);
	e_f_tmp = model[10] * model[10];
	i_f_tmp = -2.0 * f_tmp;
	k_f_tmp = 2.0 * c_f_tmp;
	l_f_tmp = i_f_tmp * model[0];
	m_f_tmp = k_f_tmp * model[0];
	n_f_tmp = f_tmp * model[0];
	c_f_tmp *= model[0];
	p_f_tmp = model[4] * model[0];
	q_f_tmp = model[2] * e_f_tmp;
	e_f_tmp *= model[1];
	r_f_tmp = 2.0 * (model[7] + n_f_tmp);
	s_f_tmp = 2.0 * (model[5] + c_f_tmp);
	u_f_tmp *= model[0];
	xDot[4] = b_f_tmp * X[4] * ((-2.0 * model[3] * model[0] * X[3] * a_tmp + t_f_tmp *
		model[0] * X[6] * j_f_tmp) + -2.0 * (p_f_tmp * X[3] + (((model[5] + -model[7])
			+ l_f_tmp) + m_f_tmp) * X[6] * a_tmp) * b_a_tmp) * (1.0 / ((((q_f_tmp +
				e_f_tmp * b_f_tmp) + r_f_tmp * b_f_tmp * (a_tmp * a_tmp)) + s_f_tmp *
			b_f_tmp * (b_a_tmp * b_a_tmp)) + u_f_tmp * b_f_tmp * o_f_tmp));
			xDot[5] = X[6];
			t_f_tmp = 4.0 * model[4] * model[11];
			k_f_tmp = k_f_tmp * model[2] * model[0];
			m_f_tmp = m_f_tmp * model[1] * b_f_tmp;
			x_f_tmp = -(model[4] * model[4]) * d_f_tmp;
			xDot[6] = f_f_tmp * ((((((((((((((((((((8.0 * Fric * model[2] + 4.0 * Fric *
				model[0] * b_f_tmp) + 8.0 * Fric * model[1] * b_f_tmp) + 2.0 * model[0] *
			(2.0 * model[4] * Fric * model[9] + model[3] * model[11] * (2.0 * model[2] +
				(model[0] + 2.0 * model[1]) * b_f_tmp)) * a_tmp) + -2.0 * model[3] * model[4]
			* model[0] * (model[0] * h_f_tmp * b_f_tmp + -2.0 * (model[2] + model[1] *
				b_f_tmp) * g_f_tmp) * j_f_tmp) + t_f_tmp * model[2] * model[0] * b_a_tmp) +
			-4.0 * model[3] * Fric * model[0] * model[9] * b_a_tmp) + 2.0 * model[4] *
			model[11] * d_f_tmp * b_f_tmp * b_a_tmp) + t_f_tmp * model[0] * model[1] *
			b_f_tmp * b_a_tmp) + v_f_tmp * h_f_tmp * b_f_tmp * o_f_tmp) + x_f_tmp *
			h_f_tmp * b_f_tmp * o_f_tmp) + -2.0 * model[5] * model[2] * g_f_tmp *
			o_f_tmp) + 2.0 * model[7] * model[2] * g_f_tmp * o_f_tmp) + i_f_tmp * model
			[2] * model[0] * g_f_tmp * o_f_tmp) + k_f_tmp * g_f_tmp * o_f_tmp) + -model
			[5] * model[0] * b_f_tmp * g_f_tmp * o_f_tmp) + model[7] * model[0] *
			b_f_tmp * g_f_tmp * o_f_tmp) + -2.0 * model[5] * model[1]
			* b_f_tmp * g_f_tmp * o_f_tmp) + 2.0 * model[7] * model[1]
			* b_f_tmp * g_f_tmp * o_f_tmp) + l_f_tmp * model[1] *
			b_f_tmp * g_f_tmp * o_f_tmp) + m_f_tmp * g_f_tmp * o_f_tmp);
			t_f_tmp = x_f_tmp * b_f_tmp;
			l_f_tmp = (((((((2.0 * model[6] * model[2] + w_f_tmp * model[2] * model[0]) +
				k_f_tmp) + model[6] * model[0] * b_f_tmp) + f_tmp_tmp) +
			b_f_tmp_tmp) + 2.0 * model[6] * model[1] * b_f_tmp) + w_f_tmp *
			model[0] * model[1] * b_f_tmp) + m_f_tmp;
			j_f_tmp = -f_tmp * d_f_tmp * b_f_tmp;
			i_f_tmp = model[3] * model[4] * d_f_tmp * b_f_tmp * o_f_tmp;
			g_f_tmp = p_f_tmp * model[9] * a_tmp;
			h_f_tmp = -model[3] * model[0] * model[9] * b_a_tmp;
			Fric = model[8] * model[9] * ((((model[6] + n_f_tmp) + c_f_tmp) + g_f_tmp) +
				h_f_tmp);
			g[3] = Fric * (1.0 / (((l_f_tmp + t_f_tmp * (a_tmp * a_tmp)) + j_f_tmp *
				(b_a_tmp * b_a_tmp)) + i_f_tmp));
			g[10] = Fric * (1.0 / (((l_f_tmp + t_f_tmp * (a_tmp * a_tmp)) + j_f_tmp *
				(b_a_tmp * b_a_tmp)) + i_f_tmp));
			t_f_tmp = r_f_tmp * model[9];
			l_f_tmp = q_f_tmp * (1.0 / model[9]) + e_f_tmp * model[9];
			j_f_tmp = s_f_tmp * model[9];
			i_f_tmp = u_f_tmp * model[9] * o_f_tmp;
			g[4] = -model[8] * model[10] * (1.0 / (((l_f_tmp + t_f_tmp * (a_tmp * a_tmp))
				+ j_f_tmp * (b_a_tmp * b_a_tmp)) + i_f_tmp));
			g[11] = model[8] * model[10] * (1.0 / (((l_f_tmp + t_f_tmp * (a_tmp * a_tmp))
				+ j_f_tmp * (b_a_tmp * b_a_tmp)) + i_f_tmp));
			g[0] = 0.0;
			g[1] = 0.0;
			g[2] = 0.0;
			g[5] = 0.0;
			g[7] = 0.0;
			g[8] = 0.0;
			g[9] = 0.0;
			g[12] = 0.0;
			t_f_tmp = -2.0 * model[8] * ((((2.0 * model[2] + model[0] * b_f_tmp) + 2.0 *
				model[1] * b_f_tmp) + g_f_tmp) + h_f_tmp) * f_f_tmp;
			g[6] = t_f_tmp;
			g[13] = t_f_tmp;

			for(int i=0; i<STATE_LENGTH; i++)
			{
				for(int j=0; j<INPUT_LENGTH; j++)
				{
					xDot[i]+=g[i+j*STATE_LENGTH]*U[j];
				}
			}
		}

#endif


#include <string>
#include <vector>
#include <map>
#include <stdexcept>
#include <math.h>
#include <algorithm>
#include <cctype>

#include <fstream>
#include <iostream>
#include <sstream>
#include <iterator>

#include "thermodynamics.h"
#include "unitsets.h"

#include <Eigen/Dense>

using namespace std;
using namespace Thermodynamics::Types;

using namespace Thermodynamics::UOM::SI;

namespace Thermodynamics
{

    void Database::scan_database_file()
    {
        ifstream infile;
        infile.open(this->filename);
        if (infile.fail())
        { // oops the file did not exist for reading?
            cout << "Opening Property database file " << this->filename << " failed." << endl;
            exit(1);
        }

        string line;
        while (getline(infile, line))
        { //read data from file object and put it into string.
            std::istringstream iss(line);
            std::vector<std::string> results((std::istream_iterator<std::string>(iss)),
                                             std::istream_iterator<std::string>());

            if (results[0] == "SYST")
            {
                auto maxComp = stoi(results[3]);
                this->NC = maxComp;
                for (int i = 0; i < maxComp; i++)
                {
                    this->known_components.push_back(Substance());
                }
            }
            if (results[0] == "SHOR")
            {
                auto index = stoi(results[1]) - 1;
                this->known_components[index].identifier = results[2];
                this->component_names.push_back(results[2]);
            }
            if (results[0] == "NAME")
            {
                auto index = stoi(results[1]) - 1;
                this->known_components[index].name = results[2];
            }
            if (results[0] == "CASN")
            {
                auto index = stoi(results[1]) - 1;
                this->known_components[index].casNo = results[2];
            }

            if (results[0] == "MOLW")
            {
                auto index = stoi(results[1]) - 1;
                auto value = stod(results[3]);
                this->known_components[index].constants.insert(
                    {MolecularProperties::MolarWeight,
                     Quantity(results[0], results[0], value, kg / kmol)});
            }

            if (results[0] == "PC")
            {
                auto index = stoi(results[1]) - 1;
                auto value = stod(results[3]);
                this->known_components[index].constants.insert(
                    {MolecularProperties::CriticalPressure,
                     Quantity(results[0], results[0], value, Pa)});
            }
            if (results[0] == "TC")
            {
                auto index = stoi(results[1]) - 1;
                auto value = stod(results[3]);
                this->known_components[index].constants.insert(
                    {MolecularProperties::CriticalTemperature,
                     Quantity(results[0], results[0], value, K)});
            }
            if (results[0] == "AC")
            {
                auto index = stoi(results[1]) - 1;
                auto value = stod(results[3]);
                this->known_components[index].constants.insert(
                    {MolecularProperties::AcentricFactor,
                     Quantity(results[0], results[0], value, none)});
            }

            if (results[0] == "VP")
            {
                auto index = stoi(results[1]) - 1;
                auto function = this->parse_function(infile, results);
                function.property = PureProperties::VaporPressure;
                this->known_components[index].functions.insert(
                    {PureProperties::VaporPressure,
                     function});
            }

            if (results[0] == "CPID")
            {
                auto index = stoi(results[1]) - 1;
                auto function = this->parse_function(infile, results);
                function.property = PureProperties::IdealGasHeatCapacity;
                this->known_components[index].functions.insert(
                    {PureProperties::IdealGasHeatCapacity,
                     function});
            }
            if (results[0] == "HVAP")
            {
                auto index = stoi(results[1]) - 1;
                auto function = this->parse_function(infile, results);
                function.property = PureProperties::HeatOfVaporization;
                this->known_components[index].functions.insert(
                    {PureProperties::HeatOfVaporization,
                     function});
            }
            if (results[0] == "DENL")
            {
                auto index = stoi(results[1]) - 1;
                auto function = this->parse_function(infile, results);
                function.property = PureProperties::LiquidDensity;
                this->known_components[index].functions.insert(
                    {PureProperties::LiquidDensity,
                     function});
            }
            if (results[0] == "ST")
            {
                auto index = stoi(results[1]) - 1;
                auto function = this->parse_function(infile, results);
                function.property = PureProperties::SurfaceTension;
                this->known_components[index].functions.insert(
                    {PureProperties::SurfaceTension,
                     function});
            }
            if (results[0] == "CL")
            {
                auto index = stoi(results[1]) - 1;
                auto function = this->parse_function(infile, results);
                function.property = PureProperties::LiquidHeatCapacity;
                this->known_components[index].functions.insert(
                    {PureProperties::LiquidHeatCapacity,
                     function});
            }

            if (results[0] == "KLIQ")
            {
                auto index = stoi(results[1]) - 1;
                auto function = this->parse_function(infile, results);
                function.property = PureProperties::LiquidHeatConductivity;
                this->known_components[index].functions.insert(
                    {PureProperties::LiquidHeatConductivity,
                     function});
            }
            if (results[0] == "KVAP")
            {
                auto index = stoi(results[1]) - 1;
                auto function = this->parse_function(infile, results);
                function.property = PureProperties::VaporHeatConductivity;
                this->known_components[index].functions.insert(
                    {PureProperties::VaporHeatConductivity,
                     function});
            }
            if (results[0] == "VISL")
            {
                auto index = stoi(results[1]) - 1;
                auto function = this->parse_function(infile, results);
                function.property = PureProperties::LiquidViscosity;
                this->known_components[index].functions.insert(
                    {PureProperties::LiquidViscosity,
                     function});
            }
            if (results[0] == "VISV")
            {
                auto index = stoi(results[1]) - 1;
                auto function = this->parse_function(infile, results);
                function.property = PureProperties::VaporViscosity;
                this->known_components[index].functions.insert(
                    {PureProperties::VaporViscosity,
                     function});
            }
            if (results[0] == "LVEQ")
            {
                if (results[2] == "NRTL")
                {
                    this->binaryparameters.insert({"NRTL", BinaryParameterSet("NRTL", this->NC)});
                }
            }

            if (results[0] == "NRTL")
            {
                auto matrix = results[1];
                auto i = stoi(results[2]) - 1;
                auto j = stoi(results[3]) - 1;
                auto kij = stod(results[4]);
                auto kji = stod(results[5]);

                this->binaryparameters.at("NRTL").set_value(matrix, i, j, kij);
                this->binaryparameters.at("NRTL").set_value(matrix, j, i, kji);
            }
        }
        std::cout.flush();
        infile.close();
    }

    PureFunction Database::parse_function(ifstream &infile, vector<string> &results)
    {
        auto function = PureFunction();

        function.tmin = stod(results[5]);
        function.tmax = stod(results[6]);

        string paramline;
        getline(infile, paramline);
        std::istringstream iss2(paramline);
        std::vector<std::string> parameters((std::istream_iterator<std::string>(iss2)), std::istream_iterator<std::string>());

        function.c = Eigen::VectorXd(parameters.size());

        for (size_t i = 0; i < parameters.size(); i++)
        {
            /* code */
            function.c(i) = stod(parameters[i]);
        }

        function.xUnit= SI::K;
        if (Thermodynamics::Types::NameToCorrelation.count(results[3]) > 0)
            function.correlation = Thermodynamics::Types::NameToCorrelation[results[3]];
        else
            function.correlation = Thermodynamics::Types::PureCorrelations::None;

        return function;
    }

    Substance Database::find_component(std::string name)
    {

        for (auto comp : this->known_components)
        {
            if (comp.identifier == name || comp.name == name)
                return comp;
        }

        Substance comp;
        comp.name = "ERROR";
        comp.identifier = "ERROR";
        return comp;
    }

    void Database::fill_binary_parameters(ThermodynamicSystem *system)
    {
        auto nrtlsys = BinaryParameterSet("NRTL", system->NC);
        auto nrtldb = this->binaryparameters.at("NRTL");
        auto matrices = {"A", "B", "C", "D", "E", "F"};

        for (int i = 0; i < system->NC; i++)
        {
            for (int j = 0; j < system->NC; j++)
            {
                if (i == j)
                    continue;

                auto name_i = system->substances[i].identifier;
                auto name_j = system->substances[j].identifier;

                auto idb = find(this->component_names.begin(), this->component_names.end(), name_i);
                int ii = distance(this->component_names.begin(), idb);
                auto jdb = find(this->component_names.begin(), this->component_names.end(), name_j);
                int jj = distance(this->component_names.begin(), jdb);

                for (auto matrix : matrices)
                {
                    auto aij = nrtldb.get_value(matrix, ii, jj);
                    auto aji = nrtldb.get_value(matrix, jj, ii);
                    nrtlsys.set_value(matrix, i, j, aij);
                    nrtlsys.set_value(matrix, j, i, aji);
                }
            }
            system->binaryparameters.insert({"NRTL", nrtlsys});
        }
    }

    std::vector<string> Database::get_component_list()
    {
        return this->component_names;
    }

} // namespace Thermodynamics


/*
Copyright (c) 2009 Yahoo! Inc.  All rights reserved.  The copyrights
embodied in the content of this file are licensed under the BSD
(revised) open source license
 */
#include <fstream>
#include <vector>
#include <float.h>
#include <netdb.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include "parse_example.h"
#include "constant.h"
#include "sparse_dense.h"
#include "gd.h"
#include "lda_core.h"
#include "cache.h"
#include "multisource.h"
#include "simple_label.h"
#include "delay_ring.h"

#define MINEIRO_SPECIAL
#ifdef MINEIRO_SPECIAL

namespace {

inline float
fastlog2 (float x)
{
  union { float f; uint32_t i; } vx = { x };
  union { uint32_t i; float f; } mx = { (vx.i & 0x007FFFFF) | (0x7e << 23) };
  float y = vx.i;
  y *= 1.0f / (1 << 23);

  return
    y - 124.22544637f - 1.498030302f * mx.f - 1.72587999f / (0.3520887068f + mx.f);
}

inline float
fastlog (float x)
{
  return 0.69314718f * fastlog2 (x);
}

inline float
fastpow2 (float p)
{
  float offset = (p < 0) ? 1.0f : 0.0f;
  float clipp = (p < -126) ? -126.0f : p;
  int w = clipp;
  float z = clipp - w + offset;
  union { uint32_t i; float f; } v = { (uint32_t)((1 << 23) * (clipp + 121.2740838f + 27.7280233f / (4.84252568f - z) - 1.49012907f * z)) };

  return v.f;
}

inline float
fastexp (float p)
{
  return fastpow2 (1.442695040f * p);
}

inline float
fastpow (float x,
         float p)
{
  return fastpow2 (p * fastlog2 (x));
}

inline float
fastlgamma (float x)
{
  float logterm = fastlog (x * (1.0f + x) * (2.0f + x));
  float xp3 = 3.0f + x;

  return
    -2.081061466f - x + 0.0833333f / xp3 - logterm + (2.5f + x) * fastlog (xp3);
}

inline float
fastdigamma (float x)
{
  float twopx = 2.0f + x;
  float logterm = fastlog (twopx);

  return - (1.0f + 2.0f * x) / (x * (1.0f + x))
         - (13.0f + 6.0f * x) / (12.0f * twopx * twopx)
         + logterm;
}

#define log fastlog
#define exp fastexp
#define powf fastpow
#define mydigamma fastdigamma
#define mylgamma fastlgamma

#if defined(__SSE2__) && !defined(VW_LDA_NO_SSE)

#include <emmintrin.h>

typedef __m128 v4sf;
typedef __m128i v4si;

#define v4si_to_v4sf _mm_cvtepi32_ps
#define v4sf_to_v4si _mm_cvttps_epi32

static inline float
v4sf_index (const v4sf x,
            unsigned int i)
{
  union { v4sf f; float array[4]; } tmp = { x };

  return tmp.array[i];
}

static inline const v4sf
v4sfl (float x)
{
  union { float array[4]; v4sf f; } tmp = { { x, x, x, x } };

  return tmp.f;
}

static inline const v4si
v4sil (uint32_t x)
{
  uint64_t wide = (((uint64_t) x) << 32) | x;
  union { uint64_t array[2]; v4si f; } tmp = { { wide, wide } };

  return tmp.f;
}

static inline v4sf
vfastpow2 (const v4sf p)
{
  v4sf ltzero = _mm_cmplt_ps (p, v4sfl (0.0f));
  v4sf offset = _mm_and_ps (ltzero, v4sfl (1.0f));
  v4sf lt126 = _mm_cmplt_ps (p, v4sfl (-126.0f));
  v4sf clipp = _mm_andnot_ps (lt126, p) + _mm_and_ps (lt126, v4sfl (-126.0f));
  v4si w = v4sf_to_v4si (clipp);
  v4sf z = clipp - v4si_to_v4sf (w) + offset;

  const v4sf c_121_2740838 = v4sfl (121.2740838f);
  const v4sf c_27_7280233 = v4sfl (27.7280233f);
  const v4sf c_4_84252568 = v4sfl (4.84252568f);
  const v4sf c_1_49012907 = v4sfl (1.49012907f);
  union { v4si i; v4sf f; } v = {
    v4sf_to_v4si (
      v4sfl (1 << 23) *
      (clipp + c_121_2740838 + c_27_7280233 / (c_4_84252568 - z) - c_1_49012907 * z)
    )
  };

  return v.f;
}

inline v4sf
vfastexp (const v4sf p)
{
  const v4sf c_invlog_2 = v4sfl (1.442695040f);

  return vfastpow2 (c_invlog_2 * p);
}

inline v4sf
vfastlog2 (v4sf x)
{
  union { v4sf f; v4si i; } vx = { x };
  union { v4si i; v4sf f; } mx = { (vx.i & v4sil (0x007FFFFF)) | v4sil (0x3f000000) };
  v4sf y = v4si_to_v4sf (vx.i);
  y *= v4sfl (1.1920928955078125e-7f);

  const v4sf c_124_22551499 = v4sfl (124.22551499f);
  const v4sf c_1_498030302 = v4sfl (1.498030302f);
  const v4sf c_1_725877999 = v4sfl (1.72587999f);
  const v4sf c_0_3520087068 = v4sfl (0.3520887068f);

  return y - c_124_22551499
           - c_1_498030302 * mx.f
           - c_1_725877999 / (c_0_3520087068 + mx.f);
}

inline v4sf
vfastlog (v4sf x)
{
  const v4sf c_0_69314718 = v4sfl (0.69314718f);

  return c_0_69314718 * vfastlog2 (x);
}

inline v4sf
vfastdigamma (v4sf x)
{
  v4sf twopx = v4sfl (2.0f) + x;
  v4sf logterm = vfastlog (twopx);

  return (v4sfl (-48.0f) + x * (v4sfl (-157.0f) + x * (v4sfl (-127.0f) - v4sfl (30.0f) * x))) /
         (v4sfl (12.0f) * x * (v4sfl (1.0f) + x) * twopx * twopx)
         + logterm;
}

void
vexpdigammify (float* gamma)
{
  unsigned int n = global.lda;
  float extra_sum = 0.0f;
  v4sf sum = v4sfl (0.0f);
  size_t i;

  for (i = 0; i < n && ((uintptr_t) (gamma + i)) % 16 > 0; ++i)
    {
      extra_sum += gamma[i];
      gamma[i] = fastdigamma (gamma[i]);
    }

  for (; i + 4 < n; i += 4)
    {
      v4sf arg = _mm_load_ps (gamma + i);
      sum += arg;
      arg = vfastdigamma (arg);
      _mm_store_ps (gamma + i, arg);
    }

  for (; i < n; ++i)
    {
      extra_sum += gamma[i];
      gamma[i] = fastdigamma (gamma[i]);
    }

  extra_sum += v4sf_index (sum, 0) + v4sf_index (sum, 1) +
               v4sf_index (sum, 2) + v4sf_index (sum, 3);
  extra_sum = fastdigamma (extra_sum);
  sum = v4sfl (extra_sum);

  for (i = 0; i < n && ((uintptr_t) (gamma + i)) % 16 > 0; ++i)
    {
      gamma[i] = fmaxf (1e-10f, fastexp (gamma[i] - v4sf_index (sum, 0)));
    }

  for (; i + 4 < n; i += 4)
    {
      v4sf arg = _mm_load_ps (gamma + i);
      arg -= sum;
      arg = vfastexp (arg);
      arg = _mm_max_ps (v4sfl (1e-10f), arg);
      _mm_store_ps (gamma + i, arg);
    }

  for (; i < n; ++i)
    {
      gamma[i] = fmaxf (1e-10f, fastexp (gamma[i] - v4sf_index (sum, 0)));
    }
}

void
vexpdigammify_2(float*       gamma,
                const float* norm)
{
  size_t n = global.lda;
  size_t i;

  for (i = 0; i < n && ((uintptr_t) (gamma + i)) % 16 > 0; ++i)
    {
      gamma[i] = fmaxf (1e-10f, fastexp (fastdigamma (gamma[i]) - norm[i]));
    }

  for (; i + 4 < n; i += 4)
    {
      v4sf arg = _mm_load_ps (gamma + i);
      arg = vfastdigamma (arg);
      v4sf vnorm = _mm_loadu_ps (norm + i);
      arg -= vnorm;
      arg = vfastexp (arg);
      arg = _mm_max_ps (v4sfl (1e-10f), arg);
      _mm_store_ps (gamma + i, arg);
    }

  for (; i < n; ++i)
    {
      gamma[i] = fmaxf (1e-10f, fastexp (fastdigamma (gamma[i]) - norm[i]));
    }
}

#define myexpdigammify vexpdigammify
#define myexpdigammify_2 vexpdigammify_2

#else
#warning "lda IS NOT using sse instructions"
#define myexpdigammify expdigammify
#define myexpdigammify_2 expdigammify_2

#endif // __SSE2__

} // end anonymous namespace

#else

#include <boost/math/special_functions/digamma.hpp>
#include <boost/math/special_functions/gamma.hpp>

using namespace boost::math::policies;

#define mydigamma boost::math::digamma
#define mylgamma boost::math::lgamma
#define myexpdigammify expdigammify
#define myexpdigammify_2 expdigammify_2

#endif // MINEIRO_SPECIAL

size_t max_w = 0;

float decayfunc(float t, float old_t, float power_t) {
  float result = 1;
  for (float i = old_t+1; i <= t; i += 1)
    result *= (1-powf(i, -power_t));
  return result;
}

float decayfunc2(float t, float old_t, float power_t)
{
  float power_t_plus_one = 1. - power_t;
  float arg =  - ( powf(t, power_t_plus_one) -
                   powf(old_t, power_t_plus_one));
  return exp ( arg
               / power_t_plus_one);
}

float decayfunc3(double t, double old_t, double power_t)
{
  double power_t_plus_one = 1. - power_t;
  double logt = log(t);
  double logoldt = log(old_t);
  return (old_t / t) * exp(0.5*power_t_plus_one*(-logt*logt + logoldt*logoldt));
}

float decayfunc4(double t, double old_t, double power_t)
{
  if (power_t > 0.99)
    return decayfunc3(t, old_t, power_t);
  else
    return decayfunc2(t, old_t, power_t);
}

void expdigammify(float* gamma)
{
  float sum=0;
  for (size_t i = 0; i<global.lda; i++)
    {
      sum += gamma[i];
      gamma[i] = mydigamma(gamma[i]);
    }
  sum = mydigamma(sum);
  for (size_t i = 0; i<global.lda; i++)
    gamma[i] = fmax(1e-10, exp(gamma[i] - sum));
}

void expdigammify_2(float* gamma, float* norm)
{
  for (size_t i = 0; i<global.lda; i++)
    {
      gamma[i] = fmax(1e-10, exp(mydigamma(gamma[i]) - norm[i]));
    }
}

float average_diff(float* oldgamma, float* newgamma)
{
  float sum = 0.;
  float normalizer = 0.;
  for (size_t i = 0; i<global.lda; i++) {
    sum += fabsf(oldgamma[i] - newgamma[i]);
    normalizer += newgamma[i];
  }
  return sum / normalizer;
}

v_array<float> Elogtheta;

// Returns E_q[log p(\theta)] - E_q[log q(\theta)].
float theta_kl(float* gamma)
{
  float gammasum = 0;
  Elogtheta.erase();
  for (size_t k = 0; k < global.lda; k++) {
    push(Elogtheta, mydigamma(gamma[k]));
    gammasum += gamma[k];
  }
  float digammasum = mydigamma(gammasum);
  gammasum = mylgamma(gammasum);
  float kl = -(global.lda*mylgamma(global.lda_alpha));
  kl += mylgamma(global.lda_alpha*global.lda) - gammasum;
  for (size_t k = 0; k < global.lda; k++) {
    Elogtheta[k] -= digammasum;
    kl += (global.lda_alpha - gamma[k]) * Elogtheta[k];
    kl += mylgamma(gamma[k]);
  }

  return kl;
}

float find_cw(float* u_for_w, float* v)
{
  float c_w = 0;
  for (size_t k =0; k<global.lda; k++)
    c_w += u_for_w[k]*v[k];

  return 1.f / c_w;
}

v_array<float> new_gamma;
v_array<float> old_gamma;
// Returns an estimate of the part of the variational bound that
// doesn't have to do with beta for the entire corpus for the current
// setting of lambda based on the document passed in. The value is
// divided by the total number of words in the document This can be
// used as a (possibly very noisy) estimate of held-out likelihood.
float lda_loop(float* v,weight* weights,example* ec, float power_t)
{
  new_gamma.erase();
  old_gamma.erase();

  for (size_t i = 0; i < global.lda; i++)
    {
      push(new_gamma, 1.f);
      push(old_gamma, 0.f);
    }
  size_t num_words =0;
  for (size_t* i = ec->indices.begin; i != ec->indices.end; i++)
    num_words += ec->subsets[*i][1] - ec->subsets[*i][0];

  float xc_w = 0;
  float score = 0;
  float doc_length = 0;
  do
    {
      memcpy(v,new_gamma.begin,sizeof(float)*global.lda);
      myexpdigammify(v);

      memcpy(old_gamma.begin,new_gamma.begin,sizeof(float)*global.lda);
      memset(new_gamma.begin,0,sizeof(float)*global.lda);

      score = 0;
      size_t word_count = 0;
      doc_length = 0;
      for (size_t* i = ec->indices.begin; i != ec->indices.end; i++)
	{
	  feature *f = ec->subsets[*i][0];
	  for (; f != ec->subsets[*i][1]; f++)
	    {
	      float* u_for_w = &weights[(f->weight_index&global.thread_mask)+global.lda+1];
	      float c_w = find_cw(u_for_w,v);
	      xc_w = c_w * f->x;
              score += -f->x*log(c_w);
	      size_t max_k = global.lda;
	      for (size_t k =0; k<max_k; k++) {
		new_gamma[k] += xc_w*u_for_w[k];
	      }
	      word_count++;
              doc_length += f->x;
	    }
	}
      for (size_t k =0; k<global.lda; k++)
	new_gamma[k] = new_gamma[k]*v[k]+global.lda_alpha;
    }
  while (average_diff(old_gamma.begin, new_gamma.begin) > 0.001);

  ec->topic_predictions.erase();
  if (ec->topic_predictions.end_array - ec->topic_predictions.begin < (int)global.lda)
    reserve(ec->topic_predictions,global.lda);
  memcpy(ec->topic_predictions.begin,new_gamma.begin,global.lda*sizeof(float));

  score += theta_kl(new_gamma.begin);

  return score / doc_length;
}

class index_feature {
public:
  uint32_t document;
  feature f;
  bool operator<(const index_feature b) const { return f.weight_index < b.f.weight_index; }
};

std::vector<index_feature> sorted_features;

void start_lda(gd_thread_params t)
{
  regressor reg = t.reg;
  example* ec = NULL;

  v_array<float> total_lambda;
  v_array<float> total_new;
  v_array<example* > examples;
  v_array<int> doc_lengths;
  v_array<float> digammas;
  v_array<float> v;
  reserve(v, global.lda*global.minibatch);

  total_lambda.erase();

  for (size_t k = 0; k < global.lda; k++)
    push(total_lambda, 0.f);
  size_t stride = global.stride;
  weight* weights = reg.weight_vectors[0];

  for (size_t i =0; i <= global.thread_mask;i+=stride)
    for (size_t k = 0; k < global.lda; k++)
      total_lambda[k] += weights[i+k];

  v_array<float> decay_levels;
  push(decay_levels, 0.f);
  double example_t = global.initial_t;
  while ( true )
    {
      example_t++;
      total_new.erase();
      for (size_t k = 0; k < global.lda; k++)
	push(total_new, 0.f);

      sorted_features.resize(0);

      float eta = -1;
      float minuseta = -1;
      examples.erase();
      doc_lengths.erase();
      size_t batch_size = global.minibatch;
      for (size_t d = 0; d < batch_size; d++)
	{
          push(doc_lengths, 0);
	  if ((ec = get_example(0)) != NULL)//semiblocking operation.
	    {
	      push(examples, ec);
              for (size_t* i = ec->indices.begin; i != ec->indices.end; i++) {
                feature* f = ec->subsets[*i][0];
                for (; f != ec->subsets[*i][1]; f++) {
                  index_feature temp = {(uint32_t)d, *f};
                  sorted_features.push_back(temp);
                  doc_lengths[d] += f->x;
                }
              }
	    }
	  else if (thread_done(0))
	    batch_size = d;
	  else
	    d--;
	}

      sort(sorted_features.begin(), sorted_features.end());

      eta = global.eta * powf(example_t, -t.vars->power_t);
      minuseta = 1.0 - eta;
      eta *= global.lda_D / batch_size;
      push(decay_levels, decay_levels.last() + log(minuseta));

      digammas.erase();
      float additional = (float)(global.length()) * global.lda_rho;
      for (size_t i = 0; i<global.lda; i++) {
	push(digammas,mydigamma(total_lambda[i] + additional));
      }

      size_t last_weight_index = -1;
      for (index_feature* s = &sorted_features[0]; s <= &sorted_features.back(); s++)
	{
	  if (last_weight_index == s->f.weight_index)
	    continue;
	  last_weight_index = s->f.weight_index;
	  float* weights_for_w = &(weights[s->f.weight_index & global.thread_mask]);
          float decay = fmin(1.0, exp(decay_levels.end[-2] - decay_levels.end[(int)(-1-example_t+weights_for_w[global.lda])]));
	  float* u_for_w = weights_for_w + global.lda+1;

	  weights_for_w[global.lda] = example_t;
	  for (size_t k = 0; k < global.lda; k++)
	    {
	      weights_for_w[k] *= decay;
	      u_for_w[k] = weights_for_w[k] + global.lda_rho;
	    }
	  myexpdigammify_2(u_for_w, digammas.begin);
	}

      v.erase();

      for (size_t d = 0; d < batch_size; d++)
	{
          float score = lda_loop(&v[d*global.lda], weights, examples[d],t.vars->power_t);
          if (global.audit)
	    print_audit_features(reg, examples[d]);
          // If the doc is empty, give it loss of 0.
          if (doc_lengths[d] > 0) {
            global.sd->sum_loss -= score;
            global.sd->sum_loss_since_last_dump -= score;
          }
          finish_example(examples[d]);
	}

      for (index_feature* s = &sorted_features[0]; s <= &sorted_features.back();)
	{
	  index_feature* next = s+1;
	  while(next <= &sorted_features.back() && next->f.weight_index == s->f.weight_index)
	    next++;

	  float* word_weights = &(weights[s->f.weight_index & global.thread_mask]);
	  for (size_t k = 0; k < global.lda; k++) {
	    float new_value = minuseta*word_weights[k];
	    word_weights[k] = new_value;
	  }

	  for (; s != next; s++) {
	    float* v_s = &v[s->document*global.lda];
	    float* u_for_w = &weights[(s->f.weight_index & global.thread_mask) + global.lda + 1];
	    float c_w = eta*find_cw(u_for_w, v_s)*s->f.x;
	    for (size_t k = 0; k < global.lda; k++) {
	      float new_value = u_for_w[k]*v_s[k]*c_w;
	      total_new[k] += new_value;
 	      word_weights[k] += new_value;
	    }
	  }
	}
      for (size_t k = 0; k < global.lda; k++) {
	total_lambda[k] *= minuseta;
	total_lambda[k] += total_new[k];
      }

      if (thread_done(0))
	{
	  for (size_t i = 0; i < global.length(); i++) {
	    weight* weights_for_w = & (weights[i*global.stride]);
            float decay = fmin(1.0, exp(decay_levels.last() - decay_levels.end[(int)(-1-example_t+weights_for_w[global.lda])]));
	    for (size_t k = 0; k < global.lda; k++) {
	      weights_for_w[k] *= decay;
            }
	  }

	  if (global.local_prediction > 0)
	    shutdown(global.local_prediction, SHUT_WR);

	  return;
	}
    }
}

void end_lda()
{

}


// json parser
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <utility>
namespace pt = boost::property_tree;

// integration routine
#include "../Calculations/Integrations.h"

#include "../Permittivity/PermittivityFactory.h"
#include "../ReflectionCoefficients/ReflectionCoefficientsFactory.h"
#include "GreensTensorPlate.h"

GreensTensorPlate::GreensTensorPlate(
    double v, double beta, double za,
    std::shared_ptr<ReflectionCoefficients> reflection_coefficients,
    double delta_cut, const vec::fixed<2> &rel_err)
    : GreensTensor(v, beta), za(za), delta_cut(delta_cut), rel_err(rel_err),
      reflection_coefficients(std::move(reflection_coefficients)) {

  // assertions
  assert(this->za >= 0);
  assert(this->delta_cut >= 0);
  assert(this->rel_err(0) >= 0 && this->rel_err(1) >= 0);
}

GreensTensorPlate::GreensTensorPlate(const std::string &input_file)
    : GreensTensor(input_file) {
  this->reflection_coefficients =
      ReflectionCoefficientsFactory::create(input_file);

  // Create a root
  pt::ptree root;

  // Load the json file in this ptree
  pt::read_json(input_file, root);

  // check if type is right
  std::string type = root.get<std::string>("GreensTensor.type");
  assert(type == "plate");

  // read parameters
  this->za = root.get<double>("GreensTensor.za");
  this->delta_cut = root.get<double>("GreensTensor.delta_cut");
  this->rel_err(0) = root.get<double>("GreensTensor.rel_err_0");
  this->rel_err(1) = root.get<double>("GreensTensor.rel_err_1");

  // assertions
  assert(this->za >= 0);
  assert(this->delta_cut >= 0);
  assert(this->rel_err(0) >= 0 && this->rel_err(1) >= 0);
}

void GreensTensorPlate::calculate_tensor(double omega, vec::fixed<2> k,
                                         cx_mat::fixed<3, 3> &GT) const {
  // imaginary unit
  std::complex<double> I(0.0, 1.0);

  // load wavevectors from the struct into the corresponding variables
  double kx = k(0);
  double ky = k(1);
  double k_quad = kx * kx + ky * ky;

  // squared and absolute value of the frequency
  // The tensor is calculated for a positive frequency omega
  // and afterwards transformed with respect to the sign of omega
  double omega_abs = std::abs(omega);
  double omega_quad = omega_abs * omega_abs;

  // propagation in free space and within the surface material
  // kappa is defined to have either a purely
  // positive real part or purely negatively imaginary part
  std::complex<double> kappa =
      sqrt(std::complex<double>(k_quad - omega_quad, 0.));
  kappa = std::complex<double>(std::abs(kappa.real()), -std::abs(kappa.imag()));

  // produce the reflection coefficients in s- and p-polarization
  std::complex<double> r_p, r_s;
  reflection_coefficients->calculate(omega_abs, kappa, r_p, r_s);

  // For an better overview and a efficient calculation, the
  // pre-factors of the p and s polarization are collected separately
  std::complex<double> pre = (2 * M_PI) * exp(-(2 * za) * kappa);
  std::complex<double> prefactor_s =
      pre * r_s * omega_quad / (k_quad - omega_quad);
  std::complex<double> prefactor_p = pre * r_p;

  // In the following, odd orders in ky are already omitted
  GT.zeros();
  GT(0, 0) = (prefactor_p * kx * kx + prefactor_s * ky * ky) * kappa / k_quad;
  GT(1, 1) = (prefactor_p * ky * ky + prefactor_s * kx * kx) * kappa / k_quad;
  GT(2, 2) = prefactor_p * k_quad / kappa;
  GT(2, 0) = I * prefactor_p * kx;
  GT(0, 2) = -GT(2, 0);

  // In case of negative frequencies, the tensor has to be hermitian transposed
  if (omega < 0) {
    GT = trans(GT);
  }
}

void GreensTensorPlate::integrate_k(double omega, cx_mat::fixed<3, 3> &GT,
                                    Tensor_Options fancy_complex,
                                    Weight_Options weight_function) const {

  // imaginary unit
  std::complex<double> I(0.0, 1.0);

  // intialize Green's tensor
  GT.zeros();

  // calculate the five non-zero elements of the Green's tensor. Here, the
  // symmetry in y direction was already applied. Thus, the integration only
  // consideres twice the domain from 0 to pi.

  // the xx element
  auto F_xx = [=](double x) -> double {
    return this->integrand_1d_k(x, omega, {0, 0}, fancy_complex,
                                weight_function);
  };
  GT(0, 0) = cquad(F_xx, 0, M_PI, rel_err(1), 0) / M_PI;

  // the yy element
  auto F_yy = [=](double x) -> double {
    return this->integrand_1d_k(x, omega, {1, 1}, fancy_complex,
                                weight_function);
  };
  GT(1, 1) = cquad(F_yy, 0, M_PI, rel_err(1), 0) / M_PI;

  // the zz element
  auto F_zz = [=](double x) -> double {
    return this->integrand_1d_k(x, omega, {2, 2}, fancy_complex,
                                weight_function);
  };
  GT(2, 2) = cquad(F_zz, 0, M_PI, rel_err(1), 0) / M_PI;

  // the zx element
  auto F_zx = [=](double x) -> double {
    return this->integrand_1d_k(x, omega, {2, 0}, fancy_complex,
                                weight_function);
  };
  GT(2, 0) = I * cquad(F_zx, 0, M_PI, rel_err(1), 0) / M_PI;

  // the xz element
  GT(0, 2) = -GT(2, 0);
}

double GreensTensorPlate::integrand_1d_k(double phi, double omega,
                                         const uvec::fixed<2> &indices,
                                         Tensor_Options fancy_complex,
                                         Weight_Options weight_function) const {

  double result;

  // The cut-off parameters acts as upper bound of the kappa integration.
  double kappa_cut = delta_cut / (2 * za);

  // read integration variable phi
  double cos_phi = std::cos(phi);

  // define integrand
  auto F = [=](double x) -> double {
    return this->integrand_2d_k(x, omega, phi, indices, fancy_complex,
                                weight_function);
  };

  // Calculate low-temperature edge
  double edge = std::abs(omega / (v * cos_phi));

  // Calculate the integrand corresponding to the given options. To resolve the
  // probably sharp edge of the Bose-Einstein distribution, the integration is
  // split at the edge, if the edged lies below the cut-off kappa_cut.
  if ( (kappa_cut > edge) && (2*za/v < beta) ) {
    result = cquad(F, 0, std::abs(omega / (v * cos_phi)), rel_err(0), 0);
    result += cquad(F, edge, kappa_cut, rel_err(0), std::abs(result)*rel_err(0));
  } else {
    result = cquad(F, 0, kappa_cut, rel_err(0), 0);
  }
    result += cquad(F, -std::abs(omega), 0, rel_err(0), std::abs(result)*rel_err(0));

  return result;
}

double GreensTensorPlate::integrand_2d_k(double kappa_double, double omega,
                                         double phi,
                                         const uvec::fixed<2> &indices,
                                         Tensor_Options fancy_complex,
                                         Weight_Options weight_function) const {

  double v_quad = v * v;
  double omega_quad = omega * omega;
  double cos_phi = cos(phi);
  double cos_phi_quad = cos_phi * cos_phi;
  double sin_phi_quad = 1.0 - cos_phi_quad;

  // Before the real or imaginary part of the chosen matrix element can be
  // calculated, the complex result is stored in result_complex.
  std::complex<double> result_complex;
  double result = 0.;

  // imaginary unit
  std::complex<double> I(0.0, 1.0);

  // permittivity and propagation through vacuum (kappa) and surface material
  std::complex<double> kappa_complex;
  double kappa_quad;
  // Transfer kappa to the correct complex value
  if (kappa_double < 0.0) {
    kappa_complex = std::complex<double>(0.0, kappa_double);
    kappa_quad = -kappa_double * kappa_double;
  } else {
    kappa_complex = std::complex<double>(kappa_double, 0.0);
    kappa_quad = kappa_double * kappa_double;
  }

  // Express kappa via frequency and kappa.
  // In order to achieve the desired accuracy, we subtract first
  // (kappa^2 + omega^2), since this might be equal zero.
  double k = (sqrt((kappa_quad + omega_quad)- kappa_quad * v_quad * cos_phi_quad) +
              v * omega * cos_phi) / (1.E0 - v_quad * cos_phi_quad);
  double k_quad = k * k;

  // Define the Doppler-shifted frequency
  double omega_pl = (omega + k * cos_phi * v);
  double omega_pl_quad = omega_pl * omega_pl;

  // In order to obey reality in time, a positive omega_pl is used for the
  // actual calculation. Afterwards, the corresponding symmetry operation is
  // performed if the sign of omega_pl is negative.
  double omega_pl_abs = std::abs(omega_pl);

  // producing the reflection coefficients in p- and s-polarization
  // reflection coefficients and pre-factors of the corresponding polarization
  std::complex<double> r_p, r_s;
  reflection_coefficients->calculate(omega_pl_abs, kappa_complex, r_p, r_s);

  // Impose reality in time
  if (omega_pl < 0) {
    r_s = conj(r_s);
    r_p = conj(r_p);
    kappa_complex = conj(kappa_complex);
  }

  // helpful prefactors
  // general prefactor with volume element and exponential
  std::complex<double> prefactor = std::abs(kappa_complex) *
                                   exp(-2 * za * kappa_complex) /
                                   (1. - cos_phi * v * omega_pl / k);
  // For an better overview and a efficient calculation, we collect the
  // pre-factors of the p and s polarization separately
  std::complex<double> prefactor_s =
      prefactor * r_s * omega_pl_quad / kappa_complex;
  std::complex<double> prefactor_p = prefactor * r_p * kappa_complex;

  // Calculate the G_xx element
  if (indices(0) == 0 && indices(1) == 0) {
    result_complex = prefactor_p * cos_phi_quad + prefactor_s * sin_phi_quad;
  }
  // Calculate the G_yy element
  else if (indices(0) == 1 && indices(1) == 1) {
    result_complex = prefactor_p * sin_phi_quad + prefactor_s * cos_phi_quad;
  }
  // Calculate the G_zz element
  else if (indices(0) == 2 && indices(1) == 2) {
    result_complex = prefactor_p * k_quad / kappa_quad;
  }
  // Calculate the G_zx element
  else if (indices(0) == 2 && indices(1) == 0) {
    result_complex = prefactor_p * I * cos_phi * k / kappa_complex;
  }
  // Calculate the G_xz element
  else if (indices(0) == 0 && indices(1) == 2) {
    result_complex = -prefactor_p * I * cos_phi * k / kappa_complex;
  } else {
    result_complex = 0.;
  }

  // Add weighting function if demanded
  if (weight_function == KV) {
    result_complex *= k * cos_phi;
  } else if (weight_function == TEMP) {
    result_complex /= (1.0 - exp(-beta * omega_pl));
  } else if (weight_function == NON_LTE) {
    result_complex *=
        1. / (1.0 - exp(-beta * omega_pl)) - 1. / (1.0 - exp(-beta * omega));
  } else if (weight_function == KV_TEMP) {
    result_complex *= k * cos_phi / (1.0 - exp(-beta * omega_pl));
  } else if (weight_function == KV_NON_LTE) {
    result_complex *=
        k * cos_phi *
        (1. / (1.0 - exp(-beta * omega_pl)) - 1. / (1.0 - exp(-beta * omega)));
  }

  // Calculate fancy real part of the given matrix element
  if (fancy_complex == RE) {
    if ((indices(0) == 2 && indices(1) == 0) ||
        (indices(0) == 0 && indices(1) == 2)) {
      // Mind the missing leading I! This must be added after the double
      // integration!
      result = result_complex.imag();
    } else {
      result = result_complex.real();
    }
  }
  // Calculate fancy imaginary part of the given matrix element
  else if (fancy_complex == IM) {
    if ((indices(0) == 2 && indices(1) == 0) ||
        (indices(0) == 0 && indices(1) == 2)) {
      // Mind the missing leading I! This must be added after the double
      // integration!
      result = -result_complex.real();
    } else {
      result = result_complex.imag();
    }
  }

  return result;
}

std::complex<double> GreensTensorPlate::get_r_p(double omega, double k) const {
  std::complex<double> r_p, r_s;
  std::complex<double> kappa;
  if (k < omega) {
    kappa = std::complex<double>(0., -sqrt(omega * omega - k * k));
  } else {
    kappa = std::complex<double>(sqrt(k * k - omega * omega), 0.);
  }
  reflection_coefficients->calculate(omega, kappa, r_p, r_s);
  return r_p;
}

std::complex<double> GreensTensorPlate::get_r_s(double omega, double k) const {
  std::complex<double> r_s, r_p;
  std::complex<double> kappa;
  if (k < omega) {
    kappa = std::complex<double>(0., -sqrt(omega * omega - k * k));
  } else {
    kappa = std::complex<double>(sqrt(k * k - omega * omega), 0.);
  }
  reflection_coefficients->calculate(omega, kappa, r_p, r_s);
  return r_s;
}

double GreensTensorPlate::omega_ch() const {
  // Calculate omega_cut (reasonable for every plate setup)
  return this->delta_cut * this->v / this->za;
}

void GreensTensorPlate::print_info(std::ostream &stream) const {
  stream << "# GreensTensorPlate\n#\n"
         << "# v = " << v << "\n"
         << "# beta = " << beta << "\n"
         << "# za = " << za << "\n"
         << "# delta_cut = " << delta_cut << "\n"
         << "# rel_err = " << rel_err(0) << "," << rel_err(1) << "\n";
}


//==============================================================================
//         Copyright 2003 - 2012   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2013   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_LINALG_FUNCTIONS_LAPACK_PLINSOLVE_HPP_INCLUDED
#define NT2_LINALG_FUNCTIONS_LAPACK_PLINSOLVE_HPP_INCLUDED

#include <nt2/linalg/functions/plinsolve.hpp>
#include <nt2/include/functions/gesvx.hpp>
#include <nt2/include/functions/sysvx.hpp>
#include <nt2/include/functions/clinsolve.hpp>
#include <nt2/include/functions/posvx.hpp>
#include <nt2/include/functions/of_size.hpp>
#include <nt2/include/functions/resize.hpp>
#include <nt2/include/functions/zeros.hpp>
#include <nt2/linalg/options.hpp>
#include <nt2/linalg/functions/details/eval_linsolve.hpp>
#include <nt2/sdk/meta/settings_of.hpp>
#include <nt2/sdk/meta/as_real.hpp>

#include <nt2/include/functions/tie.hpp>
#include <nt2/core/container/table/table.hpp>
#include <boost/dispatch/meta/terminal_of.hpp>
#include <boost/core/ignore_unused.hpp>

namespace nt2 { namespace ext
{
  //============================================================================
  // LINSOLVE
  //============================================================================
  BOOST_DISPATCH_IMPLEMENT  ( plinsolve_, tag::cpu_
                            , (A0)(A1)(A2)(N2)
                            , ((ast_<A0, nt2::container::domain>))  // A
                              ((ast_<A1, nt2::container::domain>))  // B
                              ((node_<A2, nt2::tag::tie_             // X-R
                                    , N2, nt2::container::domain
                                     >
                              ))
                            )
  {
    typedef void  result_type;
    typedef typename A0::value_type ctype_t;
    typedef typename nt2::meta::as_real<ctype_t>::type   type_t;
    typedef typename meta::option<typename A0::settings_type,nt2::tag::shape_>::type shape;
    typedef nt2::memory::container<tag::table_, ctype_t, nt2::settings(nt2::_2D)> desired_semantic;
    typedef nt2::memory::container<tag::table_, ctype_t, nt2::settings(nt2::_2D,shape)> desired_semantic1;

    BOOST_FORCEINLINE result_type operator()( A0 const& a0, A1 const& a1, A2 const&  a2 ) const
    {
      eval(a0,a1,a2,N2(),shape());
    }

    //==========================================================================
    /// INTERNAL ONLY - Shape analysis

    /// INTERNAL ONLY - X = LINSOLVE(A,B) -- Rectangular shape
    BOOST_FORCEINLINE
    void eval ( A0 const& a0, A1 const& a1 , A2 const& a2 ,boost::mpl::long_<1> const
              , nt2::rectangular_ const&) const
    {
      type_t rcond;
      boost::proto::child_c<0>(a2).resize(nt2::of_size(a0.leading_size(),1));
      NT2_AS_TERMINAL_IN(desired_semantic1,a,a0);
      NT2_AS_TERMINAL_IN(desired_semantic,b,a1);
      nt2::gesvx( boost::proto::value(a), boost::proto::value(b)
              , boost::proto::value(boost::proto::child_c<0>(a2)), rcond );
    }

    //==========================================================================
    /// INTERNAL ONLY - [X,R] = LINSOLVE(A,B) -- Rectangular shape
    BOOST_FORCEINLINE
    void eval ( A0 const& a0, A1 const& a1 , A2 const& a2 ,boost::mpl::long_<2> const
              , nt2::rectangular_ const&) const
    {
      type_t rcond;
      boost::proto::child_c<0>(a2).resize(nt2::of_size(a0.leading_size(),1));
      NT2_AS_TERMINAL_IN(desired_semantic1,a,a0);
      NT2_AS_TERMINAL_IN(desired_semantic,b,a1);
      nt2::gesvx( boost::proto::value(a), boost::proto::value(b)
              , boost::proto::value(boost::proto::child_c<0>(a2))
              , rcond );
      boost::proto::child_c<1>(a2) = rcond;
    }

    //==========================================================================
    /// INTERNAL ONLY - X = LINSOLVE(A,B) -- symmetric shape
    BOOST_FORCEINLINE
    void eval ( A0 const& a0, A1 const& a1 , A2 const& a2 ,boost::mpl::long_<1> const
              , nt2::symmetric_ const&) const
    {
      type_t rcond;
      nt2::container::table<nt2_la_int> piv = nt2::zeros(a0.leading_size(), 1
                                            , nt2::meta::as_<nt2_la_int>() );
      boost::proto::child_c<0>(a2).resize(nt2::of_size(a0.leading_size(),1));
      NT2_AS_TERMINAL_IN(desired_semantic1,a,a0);
      NT2_AS_TERMINAL_IN(desired_semantic,b,a1);
      nt2_la_int iter = nt2::sysvx( boost::proto::value(a),boost::proto::value(piv)
                                 , boost::proto::value(b)
                                 , boost::proto::value(boost::proto::child_c<0>(a2))
                                 , rcond);
      boost::ignore_unused(iter);
    }

    //==========================================================================
    /// INTERNAL ONLY - [X,R] = LINSOLVE(A,B) -- symmetric shape
    BOOST_FORCEINLINE
    void eval ( A0 const& a0, A1 const& a1 , A2 const& a2 ,boost::mpl::long_<2> const
              , nt2::symmetric_ const&) const
    {
      type_t rcond;
      nt2::container::table<nt2_la_int> piv(nt2::of_size(a0.leading_size(),1));
      boost::proto::child_c<0>(a2).resize(nt2::of_size(a0.leading_size(),1));
      NT2_AS_TERMINAL_IN(desired_semantic1,a,a0);
      NT2_AS_TERMINAL_IN(desired_semantic,b,a1);
      nt2_la_int iter = nt2::sysvx( boost::proto::value(a),boost::proto::value(piv)
                                 , boost::proto::value(b)
                                 , boost::proto::value(boost::proto::child_c<0>(a2))
                                 , rcond);

      boost::ignore_unused(iter);
      boost::proto::child_c<1>(a2) = rcond;
    }

    //==========================================================================
    /// INTERNAL ONLY - X = LINSOLVE(A,B) -- positive definite shape
    BOOST_FORCEINLINE
    void eval ( A0 const& a0, A1 const& a1 , A2 const& a2 ,boost::mpl::long_<1> const
              , nt2::positive_definite_ const&) const
    {
      type_t rcond;
      boost::proto::child_c<0>(a2).resize(nt2::of_size(a0.leading_size(),1));
      NT2_AS_TERMINAL_IN(desired_semantic1,a,a0);
      NT2_AS_TERMINAL_IN(desired_semantic,b,a1);
      nt2_la_int iter = nt2::posvx( boost::proto::value(a), boost::proto::value(b)
                                  , boost::proto::value(boost::proto::child_c<0>(a2))
                                  , rcond);
      boost::ignore_unused(iter);
    }

    //==========================================================================
    /// INTERNAL ONLY - [X,R] = LINSOLVE(A,B) -- positive definite shape
    BOOST_FORCEINLINE
    void eval ( A0 const& a0, A1 const& a1 , A2 const& a2 ,boost::mpl::long_<2> const
              , nt2::positive_definite_ const&) const
    {
      type_t rcond;
      boost::proto::child_c<0>(a2).resize(nt2::of_size(a0.leading_size(),1));
      NT2_AS_TERMINAL_IN(desired_semantic1,a,a0);
      NT2_AS_TERMINAL_IN(desired_semantic,b,a1);
      nt2_la_int iter = nt2::posvx( boost::proto::value(a), boost::proto::value(b)
                                  , boost::proto::value(boost::proto::child_c<0>(a2))
                                  , rcond);

      boost::ignore_unused(iter);
      boost::proto::child_c<1>(a2) = rcond;
    }

    /// INTERNAL ONLY - No info on this shape
    template<typename N,typename sh>
    BOOST_FORCEINLINE
    void eval ( A0 const& a0, A1 const& a1, A2 const& a2, N const&, sh const&) const
    {
      nt2::clinsolve(a0,a1,a2);
    }

  };

} }

#endif


// This file is part of the dune-stuff project:
//   https://github.com/wwu-numerik/dune-stuff/
// Copyright holders: Rene Milk, Felix Schindler
// License: BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)

#ifndef DUNE_STUFF_LA_SOLVER_EIGEN_HH
#define DUNE_STUFF_LA_SOLVER_EIGEN_HH

#include <string>
#include <vector>
#include <algorithm>
#include <sstream>
#include <cmath>
#include <complex>

#include <dune/stuff/common/disable_warnings.hh>
#if HAVE_EIGEN
#include <Eigen/Dense>
#include <Eigen/SparseCore>
#include <Eigen/IterativeLinearSolvers>
#include <Eigen/SparseCholesky>
#include <Eigen/SparseLU>
#include <Eigen/SparseQR>
//#   if HAVE_UMFPACK
//#     include <Eigen/UmfPackSupport>
//#   endif
//#   include <Eigen/SPQRSupport>
//#   include <Eigen/CholmodSupport>
//#   if HAVE_SUPERLU
//#     include <Eigen/SuperLUSupport>
//#   endif
#endif // HAVE_EIGEN
#include <dune/stuff/common/reenable_warnings.hh>

#include <dune/stuff/common/exceptions.hh>
#include <dune/stuff/common/configuration.hh>
#include <dune/stuff/la/container/eigen.hh>

#include "../solver.hh"

namespace Dune {
namespace Stuff {
namespace LA {

#if HAVE_EIGEN

template <class S, class CommunicatorType>
class Solver<EigenDenseMatrix<S>, CommunicatorType> : protected SolverUtils
{
public:
  typedef EigenDenseMatrix<S> MatrixType;
  typedef typename MatrixType::RealType R;

  Solver(const MatrixType& matrix) : matrix_(matrix) {}

  Solver(const MatrixType& matrix, const CommunicatorType& /*communicator*/) : matrix_(matrix) {}

  static std::vector<std::string> types()
  {
    return {"lu.partialpiv",
            "qr.householder",
            "llt",
            "ldlt",
            "qr.colpivhouseholder",
            "qr.fullpivhouseholder",
            "lu.fullpiv"};
  } // ... types()

  static Common::Configuration options(const std::string type = "")
  {
    const std::string tp = !type.empty() ? type : types()[0];
    SolverUtils::check_given(tp, types());
    Common::Configuration default_options({"type", "post_check_solves_system", "check_for_inf_nan"}, {tp, "1e-5", "1"});
    // * for symmetric matrices
    if (tp == "ldlt" || tp == "llt") {
      default_options.set("pre_check_symmetry", "1e-8");
    }
    return default_options;
  } // ... options(...)

  template <class T1, class T2>
  void apply(const EigenBaseVector<T1, S>& rhs, EigenBaseVector<T2, S>& solution) const
  {
    apply(rhs, solution, types()[0]);
  }

  template <class T1, class T2>
  void apply(const EigenBaseVector<T1, S>& rhs, EigenBaseVector<T2, S>& solution, const std::string& type) const
  {
    apply(rhs, solution, options(type));
  }

  template <class T1, class T2>
  void apply(const EigenBaseVector<T1, S>& rhs, EigenBaseVector<T2, S>& solution,
             const Common::Configuration& opts) const
  {
    if (!opts.has_key("type"))
      DUNE_THROW(Exceptions::configuration_error,
                 "Given options (see below) need to have at least the key 'type' set!\n\n" << opts);
    const auto type = opts.get<std::string>("type");
    SolverUtils::check_given(type, types());
    const Common::Configuration default_opts = options(type);
    // check for inf or nan
    const bool check_for_inf_nan = opts.get("check_for_inf_nan", default_opts.get<bool>("check_for_inf_nan"));
    if (check_for_inf_nan) {
      for (size_t ii = 0; ii < matrix_.rows(); ++ii) {
        for (size_t jj = 0; jj < matrix_.cols(); ++jj) {
          const S& val = matrix_.backend()(ii, jj);
          if (Common::isnan(val) || Common::isinf(val)) {
            std::stringstream msg;
            msg << "Given matrix contains inf or nan and you requested checking (see options below)!\n"
                << "If you want to disable this check, set 'check_for_inf_nan = 0' in the options.\n\n"
                << "Those were the given options:\n\n" << opts;
            if (rhs.size() <= internal::max_size_to_print)
              msg << "\nThis was the given matrix:\n\n" << matrix_ << "\n";
            DUNE_THROW(Exceptions::linear_solver_failed_bc_data_did_not_fulfill_requirements, msg.str());
          }
        }
      }
      for (size_t ii = 0; ii < rhs.size(); ++ii) {
        const S& val = rhs[ii];
        if (Common::isnan(val) || Common::isinf(val)) {
          std::stringstream msg;
          msg << "Given rhs contains inf or nan and you requested checking (see options below)!\n"
              << "If you want to disable this check, set 'check_for_inf_nan = 0' in the options.\n\n"
              << "Those were the given options:\n\n" << opts;
          if (rhs.size() <= internal::max_size_to_print)
            msg << "\nThis was the given right hand side:\n\n" << rhs << "\n";
          DUNE_THROW(Exceptions::linear_solver_failed_bc_data_did_not_fulfill_requirements, msg.str());
        }
      }
    }
    // check for symmetry (if solver needs it)
    if (type == "ldlt" || type == "llt") {
      const R pre_check_symmetry_threshhold = opts.get("pre_check_symmetry", default_opts.get<R>("pre_check_symmetry"));
      if (pre_check_symmetry_threshhold > 0) {
        const MatrixType tmp(matrix_.backend() - matrix_.backend().adjoint());
        // serialize difference to compute L^\infty error (no copy done here)
        const R error = std::max(tmp.backend().cwiseAbs().minCoeff(), tmp.backend().cwiseAbs().maxCoeff());
        if (error > pre_check_symmetry_threshhold) {
          std::stringstream msg;
          msg << "Given matrix is not symmetric and you requested checking (see options below)!\n"
              << "If you want to disable this check, set 'pre_check_symmetry = 0' in the options.\n\n"
              << "  (A - A').sup_norm() = " << error << "\n\n"
              << "Those were the given options:\n\n" << opts;
          if (rhs.size() <= internal::max_size_to_print)
            msg << "\nThis was the given matrix A:\n\n" << matrix_ << "\n";
          DUNE_THROW(Exceptions::linear_solver_failed_bc_data_did_not_fulfill_requirements, msg.str());
        }
      }
    }
    // solve
    if (type == "qr.colpivhouseholder") {
      solution.backend() = matrix_.backend().colPivHouseholderQr().solve(rhs.backend());
    } else if (type == "qr.fullpivhouseholder")
      solution.backend() = matrix_.backend().fullPivHouseholderQr().solve(rhs.backend());
    else if (type == "qr.householder")
      solution.backend() = matrix_.backend().householderQr().solve(rhs.backend());
    else if (type == "lu.fullpiv")
      solution.backend() = matrix_.backend().fullPivLu().solve(rhs.backend());
    else if (type == "llt")
      solution.backend() = matrix_.backend().llt().solve(rhs.backend());
    else if (type == "ldlt")
      solution.backend() = matrix_.backend().ldlt().solve(rhs.backend());
    else if (type == "lu.partialpiv")
      solution.backend() = matrix_.backend().partialPivLu().solve(rhs.backend());
    else
      DUNE_THROW(Exceptions::internal_error,
                 "Given type '" << type << "' is not supported, although it was reported by types()!");
    // check
    if (check_for_inf_nan)
      for (size_t ii = 0; ii < solution.size(); ++ii) {
        const S& val = solution[ii];
        if (Common::isnan(val) || Common::isinf(val)) {
          std::stringstream msg;
          msg << "The computed solution contains inf or nan and you requested checking (see options "
              << "below)!\n"
              << "If you want to disable this check, set 'check_for_inf_nan = 0' in the options.\n\n"
              << "Those were the given options:\n\n" << opts;
          if (rhs.size() <= internal::max_size_to_print)
            msg << "\nThis was the given matrix A:\n\n" << matrix_ << "\nThis was the given right hand side b:\n\n"
                << rhs << "\nThis is the computed solution:\n\n" << solution << "\n";
          DUNE_THROW(Exceptions::linear_solver_failed_bc_data_did_not_fulfill_requirements, msg.str());
        }
      }
    const R post_check_solves_system_threshold =
        opts.get("post_check_solves_system", default_opts.get<R>("post_check_solves_system"));
    if (post_check_solves_system_threshold > 0) {
      auto tmp = rhs.copy();
      tmp.backend() = matrix_.backend() * solution.backend() - rhs.backend();
      const R sup_norm = tmp.sup_norm();
      if (sup_norm > post_check_solves_system_threshold || DSC::isnan(sup_norm) || DSC::isinf(sup_norm)) {
        std::stringstream msg;
        msg << "The computed solution does not solve the system (although the eigen backend reported "
            << "'Success') and you requested checking (see options below)!\n"
            << "If you want to disable this check, set 'post_check_solves_system = 0' in the options."
            << "\n\n"
            << "  (A * x - b).sup_norm() = " << tmp.sup_norm() << "\n\n"
            << "Those were the given options:\n\n" << opts;
        if (rhs.size() <= internal::max_size_to_print)
          msg << "\nThis was the given matrix A:\n\n" << matrix_ << "\nThis was the given right hand side b:\n\n" << rhs
              << "\nThis is the computed solution:\n\n" << solution << "\n";
        DUNE_THROW(Exceptions::linear_solver_failed_bc_the_solution_does_not_solve_the_system, msg.str());
      }
    }
  } // ... apply(...)

private:
  const MatrixType& matrix_;
}; // class Solver

/**
 *  \note lu.sparse will copy the matrix to column major
 *  \note qr.sparse will copy the matrix to column major
 *  \note ldlt.simplicial will copy the matrix to column major
 *  \note llt.simplicial will copy the matrix to column major
 */
template <class S, class CommunicatorType>
class Solver<EigenRowMajorSparseMatrix<S>, CommunicatorType> : protected SolverUtils
{
  typedef ::Eigen::SparseMatrix<S, ::Eigen::ColMajor> ColMajorBackendType;

public:
  typedef EigenRowMajorSparseMatrix<S> MatrixType;
  typedef typename MatrixType::RealType R;

private:
  typedef typename MatrixType::BackendType::Index EIGEN_size_t;

public:
  Solver(const MatrixType& matrix) : matrix_(matrix) {}

  Solver(const MatrixType& matrix, const CommunicatorType& /*communicator*/) : matrix_(matrix) {}

  static std::vector<std::string> types()
  {
    return {
        "bicgstab.ilut",
        "lu.sparse",
        "llt.simplicial" // <- does only work with symmetric matrices
        ,
        "ldlt.simplicial" // <- does only work with symmetric matrices
        ,
        "bicgstab.diagonal" // <- slow for complicated matrices
        ,
        "bicgstab.identity" // <- slow for complicated matrices
        ,
        "qr.sparse" // <- produces correct results, but is painfully slow
        ,
        "cg.diagonal.lower" // <- does only work with symmetric matrices, may produce correct results
        ,
        "cg.diagonal.upper" // <- does only work with symmetric matrices, may produce correct results
        ,
        "cg.identity.lower" // <- does only work with symmetric matrices, may produce correct results
        ,
        "cg.identity.upper" // <- does only work with symmetric matrices, may produce correct results
                            //           , "spqr"                  // <- does not compile
                            //           , "llt.cholmodsupernodal" // <- does not compile
                            //#if HAVE_UMFPACK
                            //           , "lu.umfpack"            // <- untested
                            //#endif
                            //#if HAVE_SUPERLU
                            //           , "superlu"               // <- untested
                            //#endif
    };
  } // ... types()

  static Common::Configuration options(const std::string type = "")
  {
    const std::string tp = !type.empty() ? type : types()[0];
    // check
    SolverUtils::check_given(tp, types());
    // default config
    Common::Configuration default_options({"type", "post_check_solves_system", "check_for_inf_nan"}, {tp, "1e-5", "1"});
    Common::Configuration iterative_options({"max_iter", "precision"}, {"10000", "1e-10"});
    iterative_options += default_options;
    // direct solvers
    if (tp == "lu.sparse" || tp == "qr.sparse" || tp == "lu.umfpack" || tp == "spqr" || tp == "llt.cholmodsupernodal"
        || tp == "superlu")
      return default_options;
    // * for symmetric matrices
    if (tp == "ldlt.simplicial" || tp == "llt.simplicial") {
      default_options.set("pre_check_symmetry", "1e-8");
      return default_options;
    }
    // iterative solvers
    if (tp == "bicgstab.ilut") {
      iterative_options.set("preconditioner.fill_factor", "10");
      iterative_options.set("preconditioner.drop_tol", "1e-4");
    } else if (tp.substr(0, 3) == "cg.")
      iterative_options.set("pre_check_symmetry", "1e-8");
    return iterative_options;
  } // ... options(...)

  template <class T1, class T2>
  void apply(const EigenBaseVector<T1, S>& rhs, EigenBaseVector<T2, S>& solution) const
  {
    apply(rhs, solution, types()[0]);
  }

  template <class T1, class T2>
  void apply(const EigenBaseVector<T1, S>& rhs, EigenBaseVector<T2, S>& solution, const std::string& type) const
  {
    apply(rhs, solution, options(type));
  }

  template <class T1, class T2>
  void apply(const EigenBaseVector<T1, S>& rhs, EigenBaseVector<T2, S>& solution,
             const Common::Configuration& opts) const
  {
    if (!opts.has_key("type"))
      DUNE_THROW(Exceptions::configuration_error,
                 "Given options (see below) need to have at least the key 'type' set!\n\n" << opts);
    const auto type = opts.get<std::string>("type");
    SolverUtils::check_given(type, types());
    const Common::Configuration default_opts = options(type);
    // check for inf or nan
    const bool check_for_inf_nan = opts.get("check_for_inf_nan", default_opts.get<bool>("check_for_inf_nan"));
    if (check_for_inf_nan) {
      // iterates over the non-zero entries of matrix_.backend() and checks them
      typedef typename MatrixType::BackendType::InnerIterator InnerIterator;
      for (EIGEN_size_t ii = 0; ii < matrix_.backend().outerSize(); ++ii) {
        for (InnerIterator it(matrix_.backend(), ii); it; ++it) {
          if (DSC::isnan(std::real(it.value())) || DSC::isnan(std::imag(it.value()))
              || DSC::isinf(std::abs(it.value())))
            DUNE_THROW(Exceptions::linear_solver_failed_bc_data_did_not_fulfill_requirements,
                       "Given matrix contains inf or nan and you requested checking (see options below)!\n"
                           << "If you want to disable this check, set 'check_for_inf_nan = 0' in the options.\n\n"
                           << "Those were the given options:\n\n"
                           << opts);
        }
      }
      for (size_t ii = 0; ii < rhs.size(); ++ii) {
        const S& val = rhs[ii];
        if (Common::isnan(val) || Common::isinf(val))
          DUNE_THROW(Exceptions::linear_solver_failed_bc_data_did_not_fulfill_requirements,
                     "Given rhs contains inf or nan and you requested checking (see options below)!\n"
                         << "If you want to disable this check, set 'check_for_inf_nan = 0' in the options.\n\n"
                         << "Those were the given options:\n\n"
                         << opts);
      }
    }
    // check for symmetry (if solver needs it)
    if (type.substr(0, 3) == "cg." || type == "ldlt.simplicial" || type == "llt.simplicial") {
      const R pre_check_symmetry_threshhold = opts.get("pre_check_symmetry", default_opts.get<R>("pre_check_symmetry"));
      if (pre_check_symmetry_threshhold > 0) {
        ColMajorBackendType colmajor_copy(matrix_.backend());
        colmajor_copy -= matrix_.backend().adjoint();
        // iterates over non-zero entries as above
        typedef typename ColMajorBackendType::InnerIterator InnerIterator;
        for (EIGEN_size_t ii = 0; ii < colmajor_copy.outerSize(); ++ii) {
          for (InnerIterator it(colmajor_copy, ii); it; ++it) {
            if (std::max(std::abs(std::real(it.value())), std::abs(std::imag(it.value())))
                > pre_check_symmetry_threshhold)
              DUNE_THROW(Exceptions::linear_solver_failed_bc_data_did_not_fulfill_requirements,
                         "Given matrix is not symmetric/hermitian and you requested checking (see options below)!\n"
                             << "If you want to disable this check, set 'pre_check_symmetry = 0' in the options.\n\n"
                             << "Those were the given options:\n\n"
                             << opts);
          }
        }
      }
    }
    ::Eigen::ComputationInfo info;
    if (type == "cg.diagonal.lower") {
      typedef ::Eigen::ConjugateGradient<typename MatrixType::BackendType,
                                         ::Eigen::Lower,
                                         ::Eigen::DiagonalPreconditioner<S>> SolverType;
      SolverType solver(matrix_.backend());
      solver.setMaxIterations(opts.get("max_iter", default_opts.get<int>("max_iter")));
      solver.setTolerance(opts.get("precision", default_opts.get<R>("precision")));
      solution.backend() = solver.solve(rhs.backend());
      info = solver.info();
    } else if (type == "cg.diagonal.upper") {
      typedef ::Eigen::ConjugateGradient<typename MatrixType::BackendType,
                                         ::Eigen::Upper,
                                         ::Eigen::DiagonalPreconditioner<S>> SolverType;
      SolverType solver(matrix_.backend());
      solver.setMaxIterations(opts.get("max_iter", default_opts.get<int>("max_iter")));
      solver.setTolerance(opts.get("precision", default_opts.get<R>("precision")));
      solution.backend() = solver.solve(rhs.backend());
      info = solver.info();
    } else if (type == "cg.identity.lower") {
      typedef ::Eigen::ConjugateGradient<typename MatrixType::BackendType,
                                         ::Eigen::Lower,
                                         ::Eigen::IdentityPreconditioner> SolverType;
      SolverType solver(matrix_.backend());
      solver.setMaxIterations(opts.get("max_iter", default_opts.get<int>("max_iter")));
      solver.setTolerance(opts.get("precision", default_opts.get<R>("precision")));
      solution.backend() = solver.solve(rhs.backend());
      info = solver.info();
    } else if (type == "cg.identity.upper") {
      typedef ::Eigen::ConjugateGradient<typename MatrixType::BackendType,
                                         ::Eigen::Lower,
                                         ::Eigen::IdentityPreconditioner> SolverType;
      SolverType solver(matrix_.backend());
      solver.setMaxIterations(opts.get("max_iter", default_opts.get<int>("max_iter")));
      solver.setTolerance(opts.get("precision", default_opts.get<R>("precision")));
      solution.backend() = solver.solve(rhs.backend());
      info = solver.info();
    } else if (type == "bicgstab.ilut") {
      typedef ::Eigen::BiCGSTAB<typename MatrixType::BackendType, ::Eigen::IncompleteLUT<S>> SolverType;
      SolverType solver(matrix_.backend());
      solver.setMaxIterations(opts.get("max_iter", default_opts.get<int>("max_iter")));
      solver.setTolerance(opts.get("precision", default_opts.get<R>("precision")));
      solver.preconditioner().setDroptol(
          opts.get("preconditioner.drop_tol", default_opts.get<R>("preconditioner.drop_tol")));
      solver.preconditioner().setFillfactor(
          opts.get("preconditioner.fill_factor", default_opts.get<int>("preconditioner.fill_factor")));
      solution.backend() = solver.solve(rhs.backend());
      info = solver.info();
    } else if (type == "bicgstab.diagonal") {
      typedef ::Eigen::BiCGSTAB<typename MatrixType::BackendType, ::Eigen::DiagonalPreconditioner<S>> SolverType;
      SolverType solver(matrix_.backend());
      solver.setMaxIterations(opts.get("max_iter", default_opts.get<int>("max_iter")));
      solver.setTolerance(opts.get("precision", default_opts.get<R>("precision")));
      solution.backend() = solver.solve(rhs.backend());
      info = solver.info();
    } else if (type == "bicgstab.identity") {
      typedef ::Eigen::BiCGSTAB<typename MatrixType::BackendType, ::Eigen::IdentityPreconditioner> SolverType;
      SolverType solver(matrix_.backend());
      solver.setMaxIterations(opts.get("max_iter", default_opts.get<int>("max_iter")));
      solver.setTolerance(opts.get("precision", default_opts.get<R>("precision")));
      solution.backend() = solver.solve(rhs.backend());
      info = solver.info();
    } else if (type == "lu.sparse") {
      ColMajorBackendType colmajor_copy(matrix_.backend());
      colmajor_copy.makeCompressed();
      typedef ::Eigen::SparseLU<ColMajorBackendType> SolverType;
      SolverType solver;
      solver.analyzePattern(colmajor_copy);
      solver.factorize(colmajor_copy);
      solution.backend() = solver.solve(rhs.backend());
      info = solver.info();
    } else if (type == "qr.sparse") {
      ColMajorBackendType colmajor_copy(matrix_.backend());
      colmajor_copy.makeCompressed();
      typedef ::Eigen::SparseQR<ColMajorBackendType, ::Eigen::COLAMDOrdering<int>> SolverType;
      SolverType solver;
      solver.analyzePattern(colmajor_copy);
      solver.factorize(colmajor_copy);
      solution.backend() = solver.solve(rhs.backend());
      info = solver.info();
    } else if (type == "ldlt.simplicial") {
      ColMajorBackendType colmajor_copy(matrix_.backend());
      colmajor_copy.makeCompressed();
      typedef ::Eigen::SimplicialLDLT<ColMajorBackendType> SolverType;
      SolverType solver;
      solver.analyzePattern(colmajor_copy);
      solver.factorize(colmajor_copy);
      solution.backend() = solver.solve(rhs.backend());
      info = solver.info();
    } else if (type == "llt.simplicial") {
      ColMajorBackendType colmajor_copy(matrix_.backend());
      colmajor_copy.makeCompressed();
      typedef ::Eigen::SimplicialLLT<ColMajorBackendType> SolverType;
      SolverType solver;
      solver.analyzePattern(colmajor_copy);
      solver.factorize(colmajor_copy);
      solution.backend() = solver.solve(rhs.backend());
      info = solver.info();
      //#if HAVE_UMFPACK
      //    } else if (type == "lu.umfpack") {
      //      typedef ::Eigen::UmfPackLU< typename MatrixType::BackendType > SolverType;
      //      SolverType solver;
      //      solver.analyzePattern(matrix_.backend());
      //      solver.factorize(matrix_.backend());
      //      solution.backend() = solver.solve(rhs.backend());
      //      info = solver.info();
      //#endif // HAVE_UMFPACK
      //    } else if (type == "spqr") {
      //      ColMajorBackendType colmajor_copy(matrix_.backend());
      //      colmajor_copy.makeCompressed();
      //      typedef ::Eigen::SPQR< ColMajorBackendType > SolverType;
      //      SolverType solver;
      //      solver.analyzePattern(colmajor_copy);
      //      solver.factorize(colmajor_copy);
      //      solution.backend() = solver.solve(rhs.backend());
      //      if (solver.info() != ::Eigen::Success)
      //        return solver.info();
      //    } else if (type == "cholmodsupernodalllt") {
      //      typedef ::Eigen::CholmodSupernodalLLT< typename MatrixType::BackendType > SolverType;
      //      SolverType solver;
      //      solver.analyzePattern(matrix_.backend());
      //      solver.factorize(matrix_.backend());
      //      solution.backend() = solver.solve(rhs.backend());
      //      if (solver.info() != ::Eigen::Success)
      //        return solver.info();
      //#if HAVE_SUPERLU
      //    } else if (type == "superlu") {
      //      typedef ::Eigen::SuperLU< typename MatrixType::BackendType > SolverType;
      //      SolverType solver;
      //      solver.analyzePattern(matrix_.backend());
      //      solver.factorize(matrix_.backend());
      //      solution.backend() = solver.solve(rhs.backend());
      //      info = solver.info();
      //#endif // HAVE_SUPERLU
    } else
      DUNE_THROW(Exceptions::internal_error,
                 "Given type '" << type << "' is not supported, although it was reported by types()!");
    // handle eigens info
    if (info != ::Eigen::Success) {
      if (info == ::Eigen::NumericalIssue)
        DUNE_THROW(Exceptions::linear_solver_failed_bc_data_did_not_fulfill_requirements,
                   "The eigen backend reported 'NumericalIssue'!\n"
                       << "=> see http://eigen.tuxfamily.org/dox/group__enums.html#ga51bc1ac16f26ebe51eae1abb77bd037b "
                          "for eigens explanation\n"
                       << "Those were the given options:\n\n"
                       << opts);
      else if (info == ::Eigen::NoConvergence)
        DUNE_THROW(Exceptions::linear_solver_failed_bc_it_did_not_converge,
                   "The eigen backend reported 'NoConvergence'!\n"
                       << "=> see http://eigen.tuxfamily.org/dox/group__enums.html#ga51bc1ac16f26ebe51eae1abb77bd037b "
                          "for eigens explanation\n"
                       << "Those were the given options:\n\n"
                       << opts);
      else if (info == ::Eigen::InvalidInput)
        DUNE_THROW(Exceptions::linear_solver_failed_bc_it_was_not_set_up_correctly,
                   "The eigen backend reported 'InvalidInput'!\n"
                       << "=> see http://eigen.tuxfamily.org/dox/group__enums.html#ga51bc1ac16f26ebe51eae1abb77bd037b "
                          "for eigens explanation\n"
                       << "Those were the given options:\n\n"
                       << opts);
      else
        DUNE_THROW(Exceptions::internal_error,
                   "The eigen backend reported an unknown status!\n"
                       << "Please report this to the dune-stuff developers!");
    }
    // check
    if (check_for_inf_nan)
      for (size_t ii = 0; ii < solution.size(); ++ii) {
        const S& val = solution[ii];
        if (Common::isnan(val) || Common::isinf(val))
          DUNE_THROW(Exceptions::linear_solver_failed_bc_data_did_not_fulfill_requirements,
                     "The computed solution contains inf or nan and you requested checking (see options "
                         << "below)!\n"
                         << "If you want to disable this check, set 'check_for_inf_nan = 0' in the options.\n\n"
                         << "Those were the given options:\n\n"
                         << opts);
      }
    const R post_check_solves_system_threshold =
        opts.get("post_check_solves_system", default_opts.get<R>("post_check_solves_system"));
    if (post_check_solves_system_threshold > 0) {
      auto tmp = rhs.copy();
      tmp.backend() = matrix_.backend() * solution.backend() - rhs.backend();
      const R sup_norm = tmp.sup_norm();
      if (sup_norm > post_check_solves_system_threshold || DSC::isnan(sup_norm) || DSC::isinf(sup_norm))
        DUNE_THROW(Exceptions::linear_solver_failed_bc_the_solution_does_not_solve_the_system,
                   "The computed solution does not solve the system (although the eigen backend reported "
                       << "'Success') and you requested checking (see options below)!\n"
                       << "If you want to disable this check, set 'post_check_solves_system = 0' in the options."
                       << "\n\n"
                       << "  (A * x - b).sup_norm() = "
                       << tmp.sup_norm()
                       << "\n\n"
                       << "Those were the given options:\n\n"
                       << opts);
    }
  } // ... apply(...)

private:
  const MatrixType& matrix_;
}; // class Solver

#else // HAVE_EIGEN

template <class S>
class Solver<EigenDenseMatrix<S>>
{
  static_assert(Dune::AlwaysFalse<S>::value, "You are missing Eigen!");
};

template <class S>
class Solver<EigenRowMajorSparseMatrix<S>>
{
  static_assert(Dune::AlwaysFalse<S>::value, "You are missing Eigen!");
};

#endif // HAVE_EIGEN

} // namespace LA
} // namespace Stuff
} // namespace Dune

#endif // DUNE_STUFF_LA_SOLVER_EIGEN_HH