/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*                                                                           */
/*  This file is part of the tsppd program and library for solving           */
/*  Traveling Salesman Problems with Pickup and Delivery. tsppd requires     */
/*  other commercial and open source software to build. tsppd is decribed    */
/*  in the paper "Exact Methods for Solving Traveling Salesman Problems      */
/*  with Pickup and Delivery in Real Time".                                  */
/*                                                                           */
/*  Copyright (C) 2017 Ryan J. O'Neil <roneil1@gmu.edu>                      */
/*                                                                           */
/*  tsppd is distributed under the terms of the ZIB Academic License.        */
/*  You should have received a copy of the ZIB Academic License along with   */
/*  tsppd. See the file LICENSE. If not, email roneil1@gmu.edu.              */
/*                                                                           */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <regex>
#include <string>

#include <boost/algorithm/string.hpp>

#include <tsppd/io/tsp_problem_reader.h>
#include <tsppd/util/exception.h>
#include <tsppd/util/string.h>

using namespace TSPPD::Data;
using namespace TSPPD::IO;
using namespace TSPPD::Util;
using namespace std;

TSPPDProblem TSPProblemReader::read(string filename) {
    // Defaults for problem attributes.
    string name = "unknown";
    auto type = TSP;
    string comment = "no comment";
    unsigned int dimension = 0;
    auto edge_weight_type = EUC_2D;
    vector<string> nodes{};
    vector<pair<double, double>> coordinates{};

    // Read in the input problem definition.
    ifstream is(filename);
    if (!is.is_open())
        throw TSPPDException("unable to read file '" + filename + "'");

    vector<string> lines;
    string line;
    while (getline(is, line)) {
        boost::trim_left(line);
        boost::trim_right(line);
        lines.push_back(line);
    }

    bool in_edge_weight = false;
    bool in_coordinate = false;
    bool in_precedence = false;

    vector<string> edge_weight_lines;
    vector<string> coordinate_lines;
    vector<string> precedence_lines;

    for (auto line : lines) {
        if (boost::starts_with(line, "EOF")) {
            break;

        } else if (boost::starts_with(line, "EDGE_WEIGHT_SECTION")) {
            in_edge_weight = true;
            in_coordinate = false;
            in_precedence = false;

        } else if (boost::starts_with(line, "NODE_COORD_SECTION")) {
            in_edge_weight = false;
            in_coordinate = true;
            in_precedence = false;

        } else if (boost::starts_with(line, "PRECEDENCE_SECTION")) {
            in_edge_weight = false;
            in_coordinate = false;
            in_precedence = true;

        } else if (in_edge_weight) {
            edge_weight_lines.push_back(line);

        } else if (in_coordinate) {
            coordinate_lines.push_back(line);

        } else if (in_precedence) {
            precedence_lines.push_back(line);

        } else {
            // Parse problem metadata.
            auto comps = resplit(line, regex("\\s*:\\s*"));

            if (comps.size() != 2)
                throw TSPPDException("invalid input '" + line + "'");

            auto lhs = comps[0];
            auto rhs = comps[1];

            if (lhs == "NAME") {
                name = rhs;
            } else if (lhs == "COMMENT") {
                comment = rhs;
            } else if (lhs == "TYPE") {
                if (rhs != "TSP") throw TSPPDException("invalid type '" + rhs + "'");
            } else if (lhs == "DIMENSION") {
                dimension = stoi(rhs);
            } else if (lhs == "EDGE_WEIGHT_TYPE") {
                if (rhs != "EUC_2D" && rhs != "EXPLICIT") throw TSPPDException("invalid edge weight type '" + rhs + "'");
                if (rhs == "EXPLICIT") edge_weight_type = EXPLICIT;
            } else if (lhs == "EDGE_WEIGHT_FORMAT" && edge_weight_type == EXPLICIT) {
                if (rhs != "LOWER_DIAG_ROW") throw TSPPDException("invalid edge weight format '" + rhs + "'");
            } else {
                throw TSPPDException("invalid field '" + lhs + "'");
            }
        }
    }

    is.close();

    // Parse explicit edge weights
    auto edge_weights = read_edge_weights(edge_weight_lines);

    // Parse node and coordinate section.
    auto coord_data = read_coordinate(coordinate_lines);
    nodes = coord_data.first;
    coordinates = coord_data.second;

    // Pickup and delivery pairs, if they exist.
    auto precedence = read_precedence(precedence_lines);

    // Make sure the problem can actually be solved.
    TSPPDProblem problem(name, type, comment, dimension, edge_weight_type, nodes, coordinates, precedence, edge_weights);
    problem.validate();

    return problem;
}

vector<vector<int>> TSPProblemReader::read_edge_weights(vector<string> lines) {
    vector<vector<int>> weights;
    for (auto line : lines) {
        vector<int> w;
        for (auto weight : wssplit(line))
            w.push_back(stoi(weight));
        weights.push_back(w);
    }
    return weights;
}

pair<vector<string>, vector<pair<double, double>>> TSPProblemReader::read_coordinate(vector<string> lines) {
    vector<string> nodes;
    vector<pair<double, double>> coordinates;

    for (auto line : lines) {
        boost::trim_left(line);
        boost::trim_right(line);
        auto comps = wssplit(line);
        if (comps.size() != 3)
            throw TSPPDException("invalid coordinate input '" + line + "'");

        nodes.push_back(comps[0]);
        coordinates.push_back({stod(comps[1]), stod(comps[2])});
    }

    return {nodes, coordinates};
}

vector<pair<string, string>> TSPProblemReader::read_precedence(vector<string> lines) {
    vector<pair<string, string>> pairs;

    for (auto line : lines) {
        boost::trim_left(line);
        boost::trim_right(line);
        auto comps = wssplit(line);
        if (comps.size() != 2)
            throw TSPPDException("invalid precedence input '" + line + "'");

        pairs.push_back({comps[0], comps[1]});
    }

    return pairs;
}


//=============================================================================================================
/**
* @file     fixdictmp.cpp
* @author   Martin Henfling <martin.henfling@tu-ilmenau.de>
*           Daniel Knobl <daniel.knobl@tu-ilmenau.de>
*           Sebastian Krause <sebastian.krause@tu-ilmenau.de>
*
* @version  1.0
* @date     July, 2014

* @section  LICENSE
*
* Copyright (C) 2014, Sebastian Krause,Daniel Knobl and Martin Henfling All rights reserved.
*
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that
* the following conditions are met:
*     * Redistributions of source code must retain the above copyright notice, this list of conditions and the
*       following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
*       the following disclaimer in the documentation and/or other materials provided with the distribution.
*     * Neither the name of MNE-CPP authors nor the names of its contributors may be used
*       to endorse or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* @brief    Implemetation of the Matching Pursuit Algorithm using static atom dictionaries to find best matching
*           approximation of signals.
*
*/

//*************************************************************************************************************
//=============================================================================================================
// INCLUDES
//=============================================================================================================

#include "fixdictmp.h"

//*************************************************************************************************************
//=============================================================================================================
// STL INCLUDES
//=============================================================================================================

#include <iostream>
#include <vector>
#include <math.h>

//*************************************************************************************************************
//=============================================================================================================
// Eigen INCLUDES
//=============================================================================================================

#include <Eigen/SparseCore>
#include <unsupported/Eigen/FFT>

//*************************************************************************************************************
//=============================================================================================================
// Qt INCLUDES
//=============================================================================================================

#include <QThread>
#include <QtConcurrent>
#include <QFuture>
#include <QFile>
#include <QStringList>

//*************************************************************************************************************
//=============================================================================================================
// USED NAMESPACES
//=============================================================================================================

using namespace UTILSLIB;

//*************************************************************************************************************
//=============================================================================================================
// DEFINE MEMBER METHODS
//=============================================================================================================

FixDictMp::FixDictMp()
: it(0)
, signal_energy(0)
, current_energy(0)
, epsilon(0)
, max_iterations(0)
{

}

//*************************************************************************************************************

FixDictMp::~FixDictMp()
{

}

//*************************************************************************************************************

Dictionary::Dictionary()
: type(GABORATOM)
, sample_count(0)
{

}

//*************************************************************************************************************

Dictionary::~Dictionary()
{

}

//*************************************************************************************************************

void FixDictMp::matching_pursuit(MatrixXd signal, qint32 max_iterations, qreal epsilon, qint32 boost, QString path, qreal delta)
{
    Eigen::initParallel();

    std::cout << "\nFixDict Matching Pursuit Algorithm started...\n";

    QList<Dictionary> parsed_dicts;

    qint32 sample_count = signal.rows();
    qint32 channel_count = signal.cols();

    signal_energy = 0;
    qreal residuum_energy = 0;
    qreal energy_threshold = 0;
    qreal last_energy = 0;
    bool sample_count_mismatch = false;

    this->residuum = signal;
    parsed_dicts = parse_xml_dict(path);

    //calculate signal_energy
    for(qint32 channel = 0; channel < channel_count; channel++)
    {
        for(qint32 sample = 0; sample < sample_count; sample++)
            signal_energy += (signal(sample, channel) * signal(sample, channel));

        energy_threshold = 0.01 * epsilon * signal_energy;
        residuum_energy = signal_energy;
    }

    std::cout << "absolute energy of signal: " << residuum_energy << "\n";

    while(it < max_iterations && energy_threshold < residuum_energy)
    {
        FixDictAtom global_best_matching;

        QList<find_best_matching> list_of_best;
        for(qint32 i = 0; i < parsed_dicts.length(); i++)
        {
            find_best_matching current_best_matching;
            current_best_matching.pdict = parsed_dicts.at(i);
            current_best_matching.current_resid = this->residuum;
            current_best_matching.boost = boost;
            list_of_best.append(current_best_matching);
        }

        QFuture<FixDictAtom> mapped_best_matchings = QtConcurrent::mapped(list_of_best, &find_best_matching::parallel_correlation);// parse_threads;
        mapped_best_matchings.waitForFinished();

        QFuture<FixDictAtom>::const_iterator i;
        for (i = mapped_best_matchings.constBegin(); i != mapped_best_matchings.constEnd(); i++)
        {
            //FixDictAtom current_best_matching = correlation(parsed_dicts.at(i));
            if(i == mapped_best_matchings.constBegin())
                global_best_matching = *i;

            else if(std::fabs(i->max_scalar_product) > std::fabs(global_best_matching.max_scalar_product))
                global_best_matching = *i;
        }

        global_best_matching.display_text = create_display_text(global_best_matching);

        VectorXd fitted_atom = VectorXd::Zero(this->residuum.rows());
        VectorXd resized_atom = VectorXd::Zero(this->residuum.rows());

        if(global_best_matching.atom_samples.rows() > this->residuum.rows())
            for(qint32 k = 0; k < this->residuum.rows(); k++)
                resized_atom[k] = global_best_matching.atom_samples[k + floor(global_best_matching.atom_samples.rows() / 2) - floor(this->residuum.rows() / 2)];
        else    resized_atom = global_best_matching.atom_samples;

        for(qint32 k = 0; k < resized_atom.rows(); k++)
            if((k + global_best_matching.translation - floor(resized_atom.rows() / 2)) >= 0
                    && (k + global_best_matching.translation - floor(resized_atom.rows() / 2)) < fitted_atom.rows())
                    fitted_atom[(k + global_best_matching.translation - floor(resized_atom.rows() / 2))] += resized_atom[k];

        //normalization
        qreal norm = 0;
        norm = fitted_atom.norm();
        if(norm != 0) fitted_atom /= norm;

        for(qint32 chn = 0; chn < this->residuum.cols(); chn++)
        {
            qreal scalarproduct = 0;
            for(qint32 sample = 0; sample < this->residuum.rows(); sample++)
                scalarproduct += (this->residuum(sample, chn) * fitted_atom[sample]);

            global_best_matching.max_scalar_list.append(scalarproduct);//residuum(global_best_matching.translation, chn) / fitted_atom[global_best_matching.translation]);

            for(qint32 k = 0; k < fitted_atom.rows(); k++)
            {
                this->residuum(k,chn) -= global_best_matching.max_scalar_list.at(chn) * fitted_atom[k];
                global_best_matching.energy += pow(global_best_matching.max_scalar_list.at(chn) * fitted_atom[k], 2); //  * global_best_matching.max_scalar_list.at(chn) * fitted_atom[k];
            }
        }

        global_best_matching.atom_samples = fitted_atom;

        last_energy = residuum_energy;
        /*residuum_energy = 0;
        for(qint32 channel = 0; channel < residuum.cols(); channel++)
        {
            for(qint32 sample = 0; sample < residuum.rows(); sample++)
                residuum_energy += pow(residuum(sample, channel), 2);
        }*/

        residuum_energy -= global_best_matching.energy;
        current_energy += global_best_matching.energy;

        fix_dict_list.append(global_best_matching);

        if(global_best_matching.sample_count != signal.rows() && !sample_count_mismatch)
        {
            std::cout <<  "\n=============================================\n"
                      << "  INFO\n\n(part-)dictionary does not fit the signal!\n\nResults might be better by creating dictionaries\ncontaining atoms of the same length as the signal\n"
                      << "\nsignal samples: " << signal.rows() << "\n"
                      << "atom samples: " << global_best_matching.sample_count << "\n"
                      <<  "=============================================\n";
            sample_count_mismatch = true;
        }

        std::cout << "\n" << "===============" << it + 1 <<"th atom found" << "===============" << ":\n\n"<<
                     qPrintable(global_best_matching.display_text) << "\n\n" <<  "sample_count: " << global_best_matching.sample_count <<
                     " source_dict: " << qPrintable(global_best_matching.dict_source) << " Atom ID: " << global_best_matching.id <<
                     "\nsclr_prdct: " << global_best_matching.max_scalar_product << "\n" <<
                     "\nabsolute energy of residue: " << residuum_energy << "\n";

        it++;

        emit current_result(it, max_iterations, current_energy, signal_energy, residuum, adaptive_list, fix_dict_list);

        if(((last_energy * 100 / signal_energy) - (residuum_energy * 100 / signal_energy)) < delta)
        {
            std::cout <<  "\n=============================================\n"
                      << "  ALGORITHM ABORTED\n\ndictionary excludes atoms to reduce further residual energy\n"
                      <<  "=============================================\n";
            emit send_warning(1);
            break;
        }
        if( QThread::currentThread()->isInterruptionRequested())
        {
            send_warning(10);
            break;
        }

    }//end while iterations

    std::cout << "\nFixDict Matching Pursuit Algorithm finished.\n";
    emit finished_calc();
    //return atom_list;
}

//*************************************************************************************************************

// calc scalarproduct of Atom and Signal
FixDictAtom FixDictMp::correlation(Dictionary current_pdict, MatrixXd current_resid, qint32 boost)
{
    #ifdef EIGEN_FFTW_DEFAULT
        fftw_make_planner_thread_safe();
    #endif

    qint32 channel_count = current_resid.cols() * (boost / 100.0); //reducing the number of observed channels in the algorithm to increase speed performance
    if(boost == 0 || channel_count == 0)
        channel_count = 1;

    Eigen::FFT<double> fft;
    std::ptrdiff_t max_index;
    VectorXcd fft_atom = VectorXcd::Zero(current_resid.rows());

    FixDictAtom best_matching;
    qreal max_scalar_product = 0;

    for(qint32 i = 0; i < current_pdict.atoms.length(); i++)
    {
        VectorXd fitted_atom = VectorXd::Zero(current_resid.rows());
        qint32 p = floor(current_resid.rows() / 2);//translation

        VectorXd resized_atom = VectorXd::Zero(current_resid.rows());

        if(current_pdict.atoms.at(i).atom_samples.rows() > current_resid.rows())
            for(qint32 k = 0; k < current_resid.rows(); k++)
                resized_atom[k] = current_pdict.atoms.at(i).atom_samples[k + floor(current_pdict.atoms.at(i).atom_samples.rows() / 2) - floor(current_resid.rows() / 2)];
        else resized_atom = current_pdict.atoms.at(i).atom_samples;

        if(resized_atom.rows() < current_resid.rows())
            for(qint32 k = 0; k < resized_atom.rows(); k++)
                fitted_atom[(k + p - floor(resized_atom.rows() / 2))] = resized_atom[k];
        else fitted_atom = resized_atom;

        //normalization
        qreal norm = 0;
        norm = fitted_atom.norm();
        if(norm != 0) fitted_atom /= norm;

        fft.fwd(fft_atom, fitted_atom);

        for(qint32 chn = 0; chn < channel_count; chn++)
        {
            p = floor(current_resid.rows() / 2);//translation
            VectorXd corr_coeffs = VectorXd::Zero(current_resid.rows());
            VectorXcd fft_signal = VectorXcd::Zero(current_resid.rows());
            VectorXcd fft_sig_atom = VectorXcd::Zero(current_resid.rows());

            fft.fwd(fft_signal, current_resid.col(chn));

            for( qint32 m = 0; m < current_resid.rows(); m++)
                fft_sig_atom[m] = fft_signal[m] * conj(fft_atom[m]);

            fft.inv(corr_coeffs, fft_sig_atom);

            //find index of maximum correlation-coefficient to use in translation
            max_scalar_product = corr_coeffs.maxCoeff(&max_index);

            if(i == 0)
            {
                best_matching = current_pdict.atoms.at(i);
                best_matching.max_scalar_product = max_scalar_product;

                //adapting translation p to create atomtranslation correctly
                if(max_index >= p && current_resid.rows() % (2) == 0) p = max_index - p;
                else if(max_index >= p && current_resid.rows() % (2) != 0) p = max_index - p - 1;
                else p = max_index + p;

                best_matching.translation = p;
            }

            else if(std::fabs(max_scalar_product) > std::fabs(best_matching.max_scalar_product))
            {
                best_matching = current_pdict.atoms.at(i);
                best_matching.max_scalar_product = max_scalar_product;

                //adapting translation p to create atomtranslation correctly
                if(max_index >= p && current_resid.rows() % (2) == 0) p = max_index - p;
                else if(max_index >= p && current_resid.rows() % (2) != 0) p = max_index - p - 1;
                else p = max_index + p;

                best_matching.translation = p;
            }
        }
    }
    best_matching.atom_formula = current_pdict.atom_formula;
    best_matching.dict_source = current_pdict.source;
    best_matching.type = current_pdict.type;
    best_matching.sample_count = current_pdict.sample_count;

    return best_matching;
}

//*************************************************************************************************************

QList<Dictionary> FixDictMp::parse_xml_dict(QString path)
{
    QFile current_dict(path);
    std::cout << "\nparsing dictionary, please be patient...";

    QDomDocument dictionary;
    dictionary.setContent(&current_dict);
    QList<Dictionary> parsed_dict;
    QDomElement current_element = dictionary.documentElement();
    QDomNodeList node_list = current_element.childNodes();
    QList<parse_node> pdict_nodes;
    QList<QDomNode> nodes_listed;
    bool is_emitted = false;

    for(qint32 i = 0; i < node_list.length(); i++)
    {
        parse_node temp_parse;
        temp_parse.node = node_list.at(i);
        pdict_nodes.append(temp_parse);
        nodes_listed.append(node_list.at(i));
    }

    //multithreading
    QFuture<Dictionary> mapped_dicts = QtConcurrent::mapped(pdict_nodes, &parse_node::fill_dict_in_map);// parse_threads;
    mapped_dicts.waitForFinished();

    QFuture<Dictionary>::const_iterator i;

    for (i = mapped_dicts.constBegin(); i != mapped_dicts.constEnd(); i++)
    {
        //find_best_matching current_best_matching;
        //current_best_matching.pdict = *i;
        //current_best_matching.current_resid = this->residuum;
        if(i->sample_count != this->residuum.rows() && !is_emitted)
        {
            is_emitted = true;
            emit send_warning(2);
        }

        parsed_dict.append(*i);
    }

    std::cout << "   done.\n\n";

    return parsed_dict;
}

//*************************************************************************************************************

Dictionary FixDictMp::fill_dict(const QDomNode &pdict)
{
    Dictionary current_dict;
    QDomElement atom = pdict.firstChildElement("ATOM");

    current_dict.source = pdict.toElement().attribute("source_dict");
    current_dict.atom_count();// = pdict.toElement().attribute("atom_count").toInt();
    current_dict.atom_formula = pdict.toElement().attribute("formula");
    current_dict.sample_count = pdict.toElement().attribute("sample_count").toInt();

    if(current_dict.atom_formula == QString("Gaboratom"))
    {
        current_dict.type = GABORATOM;

        while(!atom.isNull())
        {
            FixDictAtom current_atom;

            current_atom.id = atom.attribute("ID").toInt();
            current_atom.gabor_atom.scale = atom.attribute("scale").toDouble();
            current_atom.gabor_atom.modulation = atom.attribute("modu").toDouble();
            current_atom.gabor_atom.phase = atom.attribute("phase").toDouble();

            if(atom.hasChildNodes())
            {
                QString sample_string = atom.firstChild().toElement().attribute("samples");
                QStringList sample_list = sample_string.split(":",  QString::SkipEmptyParts);
                current_atom.atom_samples = VectorXd::Zero(sample_list.length());
                for(qint32 i = 0; i < sample_list.length(); i++)
                    current_atom.atom_samples[i] = sample_list.at(i).toDouble();
            }
            current_dict.atoms.append(current_atom);
            atom = atom.nextSiblingElement("ATOM");
        }
    }
    else if(current_dict.atom_formula == QString("Chirpatom"))
    {
        current_dict.type = CHIRPATOM;

        while(!atom.isNull())
        {
            FixDictAtom current_atom;

            current_atom.id = atom.attribute("id").toInt();
            current_atom.chirp_atom.scale = atom.attribute("scale").toDouble();
            current_atom.chirp_atom.modulation = atom.attribute("modu").toDouble();
            current_atom.chirp_atom.phase = atom.attribute("phase").toDouble();
            current_atom.chirp_atom.chirp = atom.attribute("chirp").toDouble();

            if(atom.hasChildNodes())
            {
                QString sample_string = atom.firstChild().toElement().attribute("samples");
                QStringList sample_list = sample_string.split(":", QString::SkipEmptyParts);
                current_atom.atom_samples = VectorXd::Zero(sample_list.length());
                for(qint32 i = 0; i < sample_list.length(); i++)
                    current_atom.atom_samples[i] = sample_list.at(i).toDouble();
            }
            current_dict.atoms.append(current_atom);
            atom = atom.nextSiblingElement("ATOM");
        }
    }
    else
    {
        current_dict.type = FORMULAATOM;

        while(!atom.isNull())
        {
            FixDictAtom current_atom;

            current_atom.id = atom.attribute("id").toInt();
            current_atom.formula_atom.a = atom.attribute("a").toDouble();
            current_atom.formula_atom.b = atom.attribute("b").toDouble();
            current_atom.formula_atom.c = atom.attribute("c").toDouble();
            current_atom.formula_atom.d = atom.attribute("d").toDouble();
            current_atom.formula_atom.e = atom.attribute("e").toDouble();
            current_atom.formula_atom.f = atom.attribute("f").toDouble();
            current_atom.formula_atom.g = atom.attribute("g").toDouble();
            current_atom.formula_atom.h = atom.attribute("h").toDouble();

            if(atom.hasChildNodes())
            {
                QString sample_string = atom.firstChild().toElement().attribute("samples");
                QStringList sample_list = sample_string.split(":", QString::SkipEmptyParts);
                current_atom.atom_samples = VectorXd::Zero(sample_list.length());
                for(qint32 i = 0; i < sample_list.length(); i++)
                    current_atom.atom_samples[i] = sample_list.at(i).toDouble();
            }
            current_dict.atoms.append(current_atom);
            atom = atom.nextSiblingElement("ATOM");
        }
    }

    return current_dict;
}

//*************************************************************************************************************

QString FixDictMp::create_display_text(const FixDictAtom& global_best_matching)
{
    QString display_text = "";
    if(global_best_matching.type == GABORATOM)
    {
        display_text = QString("Gaboratom: scale: %0, translation: %1, modulation: %2, phase: %3")
                .arg(QString::number(global_best_matching.gabor_atom.scale, 'f', 2))
                .arg(QString::number(global_best_matching.translation, 'f', 2))
                .arg(QString::number(global_best_matching.gabor_atom.modulation, 'f', 2))
                .arg(QString::number(global_best_matching.gabor_atom.phase, 'f', 2));
    }
    else if(global_best_matching.type == CHIRPATOM)
    {
        display_text = QString("Chripatom: scale: %0, translation: %1, modulation: %2, phase: %3, chirp: %4")
                .arg(QString::number(global_best_matching.chirp_atom.scale, 'f', 2))
                .arg(QString::number(global_best_matching.translation, 'f', 2))
                .arg(QString::number(global_best_matching.chirp_atom.modulation, 'f', 2))
                .arg(QString::number(global_best_matching.chirp_atom.phase, 'f', 2))
                .arg(QString::number(global_best_matching.chirp_atom.chirp, 'f', 2));
    }
    else if(global_best_matching.type == FORMULAATOM)
    {
        display_text = QString("%0:  transl: %1 a: %2, b: %3 c: %4, d: %5, e: %6, f: %7, g: %8, h: %9")
                .arg(global_best_matching.atom_formula)
                .arg(QString::number(global_best_matching.translation,    'f', 2))
                .arg(QString::number(global_best_matching.formula_atom.a, 'f', 2))
                .arg(QString::number(global_best_matching.formula_atom.b, 'f', 2))
                .arg(QString::number(global_best_matching.formula_atom.c, 'f', 2))
                .arg(QString::number(global_best_matching.formula_atom.d, 'f', 2))
                .arg(QString::number(global_best_matching.formula_atom.e, 'f', 2))
                .arg(QString::number(global_best_matching.formula_atom.f, 'f', 2))
                .arg(QString::number(global_best_matching.formula_atom.g, 'f', 2))
                .arg(QString::number(global_best_matching.formula_atom.h, 'f', 2));
    }

    return display_text;
}

//*************************************************************************************************************

void FixDictMp::recieve_input(MatrixXd signal, qint32 max_iterations, qreal epsilon, qint32 boost, QString path, qreal delta)
{
    matching_pursuit(signal, max_iterations, epsilon, boost, path, delta);
}

//*************************************************************************************************************

qint32 Dictionary::atom_count()
{
    return atoms.length();
}

//*************************************************************************************************************

 void Dictionary::clear()
 {
     this->atoms.clear();
     this->atom_formula = "";
     this->sample_count = 0;
     this->source = "";
 }

 //*************************************************************************************************************

/*
void FixDictMp::create_tree_dict(QString save_path)
{
    QFile file(save_path);
    QDomDocument atom_xml_file;
    atom_xml_file.setContent(&file);
    GaborAtom* gabor_Atom = new GaborAtom;

    qint32 sample_count = 0;
    qreal scale = 0;
    quint32 translation = 0;
    qreal modulation = 0;
    qreal phase = 0;

    QDomElement xml_element = atom_xml_file.documentElement();

    QString root_tag = xml_element.tagName();

    if(root_tag == "builtAtomsTreebasedMP")
        sample_count = xml_element.attribute("sample_count").toInt();

    VectorXd compare_atom = VectorXd::Zero(sample_count);

    QString write_save_path = save_path;
    write_save_path.replace(".tbd", ".dict");
    QFile write_file(write_save_path);
    write_file.open(QIODevice::WriteOnly);

    QXmlStreamWriter write_molecules_to_xml(&write_file);
    write_molecules_to_xml.setAutoFormatting(true);

    write_molecules_to_xml.writeStartDocument();
    write_molecules_to_xml.writeStartElement("TreebasedStructureFor_TBMP");
    write_molecules_to_xml.writeAttribute("sample_count", QString::number(sample_count));

    QDomNodeList node_list = xml_element.elementsByTagName("Atom");
    qint32 max_it = node_list.count();
    qint32 number_of_molecs = 0;//only debugout

    for(qint32 i = 0; i < max_it; i++)
    {
        QDomElement current_element = node_list.at(0).toElement();

        if(!current_element.isNull())
        {
            scale = (current_element.attribute("scale", current_element.text())).toDouble();
            translation = (current_element.attribute("translation", current_element.text())).toInt();
            modulation = (current_element.attribute("modulation", current_element.text())).toDouble();
            phase = (current_element.attribute("phase", current_element.text())).toDouble();
        }

        compare_atom = gabor_Atom->create_real(sample_count,scale,translation,modulation,phase);
        qreal threshold = 0.8 * compare_atom.dot(compare_atom); //vary this to find optimum of seperation

        QList<qint32> similar_atoms;
        VectorXd temp_atom = VectorXd::Zero(sample_count);

        //finding atoms with low differences
        for (qint32 next = 0; next < node_list.count(); next++)//;32)
        {
            //try the next atom
            current_element = node_list.at(next).toElement();

            if(!current_element.isNull())
            {
                scale = (current_element.attribute("scale", current_element.text())).toDouble();
                translation = (current_element.attribute("translation", current_element.text())).toInt();
                modulation = (current_element.attribute("modulation", current_element.text())).toDouble();
                phase = (current_element.attribute("phase", current_element.text())).toDouble();
            }

            //set atoms out of xml to compare with compare_atom
            temp_atom = gabor_Atom->create_real(sample_count,scale,translation,modulation,phase);

            //fill list of similar atoms to save as molecule
            if( compare_atom.dot(temp_atom) > threshold)
                similar_atoms.append(current_element.attribute("ID", current_element.text()).toInt());

            //if (similar_atoms.length() == 32)//vary this for testing
            //    break;
        }

        qreal molec_scale = 0;
        quint32 molec_translation = 0;
        qreal molec_modulation = 0;
        qreal molec_phase = 0;

        //calc molecule params

        for(qint32 j = 0; j < similar_atoms.length(); j++)
        {
            current_element = node_list.at(0).toElement();

            while(!current_element.isNull())
            {
                if(current_element.attribute("ID", current_element.text()).toInt() == similar_atoms.at(j))
                {
                        molec_scale += (current_element.attribute("scale", current_element.text())).toDouble();
                        molec_translation += (current_element.attribute("translation", current_element.text())).toInt();
                        molec_modulation += (current_element.attribute("modulation", current_element.text())).toDouble();
                        molec_phase += (current_element.attribute("phase", current_element.text())).toDouble();
                }
                current_element = current_element.nextSibling().toElement();
            }
        }

        molec_scale /= similar_atoms.length();
        molec_translation /= similar_atoms.length();
        molec_modulation /= similar_atoms.length();
        molec_phase /= similar_atoms.length();

        write_molecules_to_xml.writeStartElement("Molecule");
        write_molecules_to_xml.writeAttribute("level", "0");
        write_molecules_to_xml.writeAttribute("ID", QString::number(i));
        write_molecules_to_xml.writeAttribute("scale", QString::number(molec_scale));
        write_molecules_to_xml.writeAttribute("translation", QString::number(molec_translation));
        write_molecules_to_xml.writeAttribute("modulation", QString::number(molec_modulation));
        write_molecules_to_xml.writeAttribute("phase", QString::number(molec_phase));

        // write atoms to molecule
        for(qint32 k = 0; k < similar_atoms.length(); k++)
        {
            current_element = node_list.at(0).toElement();

            while(!current_element.isNull())
            {
                if(current_element.attribute("ID", current_element.text()).toInt() == similar_atoms.at(k))
                {
                    write_molecules_to_xml.writeStartElement("Atom");
                    write_molecules_to_xml.writeAttribute("ID", current_element.attribute("ID", current_element.text()));
                    write_molecules_to_xml.writeAttribute("scale", current_element.attribute("scale", current_element.text()));
                    write_molecules_to_xml.writeAttribute("translation", current_element.attribute("translation", current_element.text()));
                    write_molecules_to_xml.writeAttribute("modulation", current_element.attribute("modulation", current_element.text()));
                    write_molecules_to_xml.writeAttribute("phase", current_element.attribute("phase", current_element.text()));

                    write_molecules_to_xml.writeEndElement();//atom
                }
                current_element = current_element.nextSibling().toElement();
            }
        }
        write_molecules_to_xml.writeEndElement();//molecule

        //remove found atoms from node list
        for(qint32 n = 0; n < similar_atoms.length(); n++)
        {
            current_element = node_list.at(0).toElement();
            QDomNode to_remove = current_element.parentNode();

            while(!current_element.isNull())
            {
                if(current_element.attribute("ID", current_element.text()).toInt() == similar_atoms.at(n))
                    to_remove.removeChild(current_element);

                current_element = current_element.nextSibling().toElement();
            }
        }

        if(node_list.count() == 0)
            break;

        number_of_molecs++;
    }//for max_it of nodelist

    write_molecules_to_xml.writeEndElement();//header
    write_molecules_to_xml.writeEndDocument();

    cout << "...start to build tree recursive... number of built molecules:  " << number_of_molecs + 1 << "\n";

    //close reader and writer
    file.close();
    write_file.close();
    delete gabor_Atom;

    //ToDo: recursion
    build_molecule_xml_file(0);

    cout << "finished treebuilding\n";

}

//******************************************************************************************************************

void FixDictMp::build_molecule_xml_file(qint32 level_counter)
{
    //qint32 i = 0;
    if(level_counter < 10)
    {
        QString save_path = QString("Matching-Pursuit-Toolbox/my_first_16atoms.dict");//ToDo: make it flexible
        QFile file(save_path);
        QString temp_path("Matching-Pursuit-Toolbox/__temp.dict");
        file.copy(temp_path);
        QFile temp_file(temp_path);

        QDomDocument atom_xml_file;
        atom_xml_file.setContent(&file);
        QDomElement xml_element = atom_xml_file.documentElement();
        xml_element = atom_xml_file.documentElement();

        GaborAtom* gabor_Atom = new GaborAtom;
        qint32 sample_count = 0;
        qreal scale = 0;
        quint32 translation = 0;
        qreal modulation = 0;
        qreal phase = 0;
        //qint32 level_counter = 0;

        QString root_tag = xml_element.tagName();
        root_tag = xml_element.tagName();

        if(root_tag == "TreebasedStructureFor_TBMP")
            sample_count = xml_element.attribute("sample_count").toInt();

        VectorXd compare_molec = VectorXd::Zero(sample_count);

        QDomNodeList node_list = xml_element.elementsByTagName("Molecule");

        qint32 max_it = node_list.count();
        QDomElement current_element;

        temp_file.open(QIODevice::WriteOnly);

        QXmlStreamWriter write_molecules_to_xml(&temp_file);
        write_molecules_to_xml.setAutoFormatting(true);

        write_molecules_to_xml.writeStartDocument();
        write_molecules_to_xml.writeStartElement("TreebasedStructureFor_TBMP");
        write_molecules_to_xml.writeAttribute("sample_count", QString::number(sample_count));

        for(qint32 i = 0; i < max_it; i++)
        {
            current_element = node_list.at(0).toElement();
            QList<qint32> similar_molecs;

            if(current_element.attribute("level").toInt() == level_counter && current_element.nodeName() == "Molecule")
            {
                if(!current_element.isNull())
                {
                    scale = (current_element.attribute("scale", current_element.text())).toDouble();
                    translation = (current_element.attribute("translation", current_element.text())).toInt();
                    modulation = (current_element.attribute("modulation", current_element.text())).toDouble();
                    phase = (current_element.attribute("phase", current_element.text())).toDouble();
                }
                compare_molec = gabor_Atom->create_real(sample_count,scale,translation,modulation,phase);
                qreal threshold = 0.6 * compare_molec.dot(compare_molec); //vary this to find optimum of seperation

                VectorXd temp_molec = VectorXd::Zero(sample_count);

                //finding molecules with low differences
                for (qint32 next = 0; next < node_list.count(); next++)//;32)
                {
                    current_element = node_list.at(next).toElement();
                    //try the next molecule
                    if(current_element.attribute("level").toInt() == level_counter && current_element.nodeName() == "Molecule")
                    {    //current_element = node_list.at(next).toElement();

                        if(!current_element.isNull())
                        {
                            scale = (current_element.attribute("scale", current_element.text())).toDouble();
                            translation = (current_element.attribute("translation", current_element.text())).toInt();
                            modulation = (current_element.attribute("modulation", current_element.text())).toDouble();
                            phase = (current_element.attribute("phase", current_element.text())).toDouble();
                        }
                        //set molecules out of xml to compare with molecule
                        temp_molec = gabor_Atom->create_real(sample_count,scale,translation,modulation,phase);

                        //fill list of similar molecules to save as new molecule
                        if( compare_molec.dot(temp_molec) > threshold)
                            similar_molecs.append(current_element.attribute("ID", current_element.text()).toInt());

                        //if (similar_molecs.length() == 32)//vary this for testing
                        //    break;
                    }
                }

                qreal molec_scale = 0;
                quint32 molec_translation = 0;
                qreal molec_modulation = 0;
                qreal molec_phase = 0;

                //calc molecule params
                for(qint32 j = 0; j < similar_molecs.length(); j++)
                {
                    current_element = node_list.at(0).toElement();

                    while(!current_element.isNull())
                    {
                        if(current_element.attribute("ID", current_element.text()).toInt() == similar_molecs.at(j) && current_element.attribute("level").toInt() == level_counter)
                        {
                            molec_scale += (current_element.attribute("scale", current_element.text())).toDouble();
                            molec_translation += (current_element.attribute("translation", current_element.text())).toInt();
                            molec_modulation += (current_element.attribute("modulation", current_element.text())).toDouble();
                            molec_phase += (current_element.attribute("phase", current_element.text())).toDouble();
                        }
                        current_element = current_element.nextSiblingElement("Molecule").toElement();
                    }
                }

                molec_scale /= similar_molecs.length();
                molec_translation /= similar_molecs.length();
                molec_modulation /= similar_molecs.length();
                molec_phase /= similar_molecs.length();

                write_molecules_to_xml.writeStartElement("Molecule");
                write_molecules_to_xml.writeAttribute("level", QString::number(level_counter + 1));
                write_molecules_to_xml.writeAttribute("ID", QString::number(i));
                write_molecules_to_xml.writeAttribute("scale", QString::number(molec_scale));
                write_molecules_to_xml.writeAttribute("translation", QString::number(molec_translation));
                write_molecules_to_xml.writeAttribute("modulation", QString::number(molec_modulation));
                write_molecules_to_xml.writeAttribute("phase", QString::number(molec_phase));

                //treedepth building

                current_element = node_list.at(0).toElement();
                qint32 root_counter = 0;

                for(qint32 k = 0; k < similar_molecs.length(); k++)
                {
                    //current_element = node_list.at(current_element.attribute("ID", current_element.text()).toInt() == similar_molecs.at(k) && current_element.attribute("level").toInt() == level_counter).toElement();
                    current_element = node_list.at(0).toElement();

                    while(!current_element.isNull())
                    {
                        root_counter = 0;

                        if(current_element.attribute("ID").toInt() == similar_molecs.at(k) && current_element.attribute("level").toInt() == level_counter)
                        {
                            QDomElement temp_element = current_element;
                            qint32 end_element_counter = 0;
                            qint32 child_counter = 0;//ToDo: set right child for right position in tree to write all atoms and molecules
                            root_counter = 0;

                            while(temp_element.hasChildNodes())
                            {
                                end_element_counter = 0;
                                child_counter = 0;

                                while(!temp_element.isNull())
                                {
                                    write_molecules_to_xml.writeStartElement(temp_element.nodeName());

                                    if(temp_element.nodeName() == "Molecule")
                                        write_molecules_to_xml.writeAttribute("level", temp_element.attribute("level", temp_element.text()));

                                    write_molecules_to_xml.writeAttribute("ID", temp_element.attribute("ID", temp_element.text()));
                                    write_molecules_to_xml.writeAttribute("scale", temp_element.attribute("scale", temp_element.text()));
                                    write_molecules_to_xml.writeAttribute("translation", temp_element.attribute("translation", temp_element.text()));
                                    write_molecules_to_xml.writeAttribute("modulation", temp_element.attribute("modulation", temp_element.text()));
                                    write_molecules_to_xml.writeAttribute("phase", temp_element.attribute("phase", temp_element.text()));

                                    if(temp_element.nodeName() == "Atom")
                                        write_molecules_to_xml.writeEndElement();//close atoms immediately
                                    else
                                        end_element_counter++;//keep in mind how many molecules are opened

                                    if(temp_element.hasChildNodes())//if there are children, that means the current element is not an atom: go to next child
                                    {
                                        temp_element = temp_element.firstChild().toElement();
                                        child_counter++;//keep in mind how many children exist
                                    }
                                    else
                                        temp_element = temp_element.nextSibling().toElement();//else there are no children, there must be an atom , so take the next atom

                                }
                                root_counter++;
                                write_molecules_to_xml.writeEndElement();

                                temp_element = current_element;

                                //for(qint32 node_depth = 2; node_depth < child_counter; node_depth++)
                                //    temp_element = temp_element.firstChildElement();

                                for(qint32 node_depth = 2; node_depth < child_counter; node_depth++)
                                    temp_element = temp_element.childNodes().at(root_counter).toElement();

                            }
                            //for(qint32 close_root_molecs = end_element_counter; close_root_molecs > 1; close_root_molecs--)
                            for(qint32 close_root_molecs = level_counter; close_root_molecs > 0; close_root_molecs--)
                                write_molecules_to_xml.writeEndElement();//end elements for one submolecule in a "parentmolecule"

                        }

                        //for(qint32 close_parent_molecs = root_counter; close_parent_molecs > 1; close_parent_molecs--)
                        //    write_molecules_to_xml.writeEndElement();//close all submolecules inside a new parentmolecule

                        current_element = current_element.nextSiblingElement("Molecule").toElement();
                    }

                }//for all similar molecules

                //for(qint32 close_parent_molecs = root_counter; close_parent_molecs > 1; close_parent_molecs--)
                write_molecules_to_xml.writeEndElement();

                //remove found molecules from node list
                for(qint32 n = 0; n < similar_molecs.length(); n++)
                {
                    current_element = node_list.at(0).toElement();
                    QDomNode to_remove = current_element.parentNode();

                    while(!current_element.isNull())
                    {
                        if(current_element.attribute("ID").toInt() == similar_molecs.at(n) && current_element.attribute("level").toInt() == level_counter)
                            to_remove.removeChild(current_element);

                        current_element = current_element.nextSiblingElement("Molecule").toElement();
                    }
                }
            }//if

            cout << node_list.count() <<"\n";

            if(node_list.count() == 0)
                break;
        }//for nodelist

        write_molecules_to_xml.writeEndElement();//header
        write_molecules_to_xml.writeEndDocument();

        file.flush();
        temp_file.flush();
        file.close();
        temp_file.close();

        file.remove();

        temp_file.copy(save_path);
        temp_file.remove();

        level_counter++;

        build_molecule_xml_file(level_counter);
    }
}
*/


// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          https://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_ASTRONOMY_COORDINATE_HELIOCENTRIC_HPP
#define BOOST_ASTRONOMY_COORDINATE_HELIOCENTRIC_HPP

#include <boost/astronomy/coordinate/base_ecliptic_frame.hpp>

namespace boost { namespace astronomy { namespace coordinate {

template
<
    typename Representation, typename Differential
>
struct heliocentric : public base_ecliptic_frame<Representation, Differential>
{

public:
    //default constructor no initialization
    heliocentric() {}

    //!constructs object from another representation object
    template <typename OtherRepresentation>
    heliocentric(OtherRepresentation const& representation_data) : base_ecliptic_frame
        <Representation, Differential>(representation_data) {}

    //!constructs object from provided components of representation
    heliocentric
    (
        typename Representation::quantity1 const& lat,
        typename Representation::quantity2 const& lon,
        typename Representation::quantity3 const& distance
    ) : base_ecliptic_frame<Representation, Differential>(lat, lon, distance) {}

    //!constructs object from provided components of representation and differential
    heliocentric
    (
        typename Representation::quantity1 const& lat,
        typename Representation::quantity2 const& lon,
        typename Representation::quantity3 const& distance,
        typename Differential::quantity1 const& pm_lat,
        typename Differential::quantity2 const& pm_lon_coslat,
        typename Differential::quantity3 const& radial_velocity
    ) : base_ecliptic_frame<Representation, Differential>
            (lat, lon, distance, pm_lat, pm_lon_coslat, radial_velocity) {}

    //!constructs object from other representation and differential objects
    template <typename OtherRepresentation, typename OtherDifferential>
    heliocentric
    (
        OtherRepresentation const& representation_data,
        OtherDifferential const& differential_data
    ) : base_ecliptic_frame<Representation, Differential>
            (representation_data, differential_data) {}
};

}}} //namespace boost::astronomy::coordinate

#endif // !BOOST_ASTRONOMY_COORDINATE_HELIOCENTRIC_HPP


#include <votca/ctp/xinductor.h>
#include <boost/format.hpp>
#include <vector>
#include <boost/timer/timer.hpp>

using boost::format;

namespace votca { namespace ctp {

XInductor::~XInductor() {
    vector<InduWorker*>::iterator wit;
    for (wit = _indus.begin(); wit != _indus.end(); ++wit) {
        delete *wit;
    }
    _indus.clear();
}

void XInductor::Evaluate(XJob *job) {

    this->Configure(job);

//    _qm0.clear();
//    _mm1.clear();
//    _mm2.clear();
//    _qmm.clear();
//
//    _job = job;
//    _isConverged = !this->_induce;
//
//    // QMM = [ QM0 --- MM1 ] --- MM2 = [ MM2 ]
//    _qm0 = job->getPolarTop()->QM0();
//    _mm1 = job->getPolarTop()->MM1();
//    _mm2 = job->getPolarTop()->MM2();
//
//    _qmm.reserve(_qm0.size()+_mm1.size());
//    _qmm.insert(_qmm.end(),_qm0.begin(),_qm0.end());
//    _qmm.insert(_qmm.end(),_mm1.begin(),_mm1.end());

    // ++++++++++++++++++++++++++ //
    // (De-)polarize, charge to N //
    // ++++++++++++++++++++++++++ //

    if (job->StartFromCPT()) {
        // Permanent fields already computed, do not zero these out ...
        CTP_LOG(logDEBUG,*_log) << "Carry out partial depolarization." << flush;
        vector< PolarSeg* >   ::iterator sit;
        vector< APolarSite* > ::iterator pit;

        // Partially depolarize inner sphere
        for (sit = _qmm.begin(); sit < _qmm.end(); ++sit) {
        for (pit = (*sit)->begin(); pit < (*sit)->end(); ++pit) {
            (*pit)->ResetFieldU();
            (*pit)->ResetU1Hist();
        }}

        // Partially depolarize outer shell
        for (sit = _mm2.begin(); sit < _mm2.end(); ++sit) {
        for (pit = (*sit)->begin(); pit < (*sit)->end(); ++pit) {
            (*pit)->ResetFieldU();
            (*pit)->ResetU1Hist();
        }}
    }
    else {
        CTP_LOG(logDEBUG,*_log) << "Carry out full depolarization." << flush;
        vector< PolarSeg* >   ::iterator sit;
        vector< APolarSite* > ::iterator pit;

        // Depolarize inner sphere
        for (sit = _qmm.begin(); sit < _qmm.end(); ++sit) {
        for (pit = (*sit)->begin(); pit < (*sit)->end(); ++pit) {
            (*pit)->Depolarize();
            (*pit)->Charge(0); // <- Not necessarily neutral state
        }}

        // Depolarize outer shell
        for (sit = _mm2.begin(); sit < _mm2.end(); ++sit) {
        for (pit = (*sit)->begin(); pit < (*sit)->end(); ++pit) {
            (*pit)->Depolarize();
            (*pit)->Charge(0); // <- Not necessarily neutral state
        }}
    }

//    // +++++++++++++++++ //
//    // Induction workers //
//    // +++++++++++++++++ //
//
//    for (int id = 0; id < this->_subthreads; ++id) {
//        InduWorker *newIndu = new InduWorker(id, _top, this);
//        _indus.push_back(newIndu);
//        newIndu->InitSpheres(&_qmm, &_mm2);
//        newIndu->SetSwitch(1);
//    }
//
//    this->InitChunks();

    // ++++++++++++++++++++++++++ //
    // Compute state energy       //
    // ++++++++++++++++++++++++++ //

    //[-Wunused-but-set-variable]
    //double  E_state  = 0.0;

    int     iter     = 0;

    boost::timer::cpu_timer cpu_t;
    cpu_t.start();
    boost::timer::cpu_times t0 = cpu_t.elapsed();
    if (this->_induce) { iter      = this->Induce(job); }
    boost::timer::cpu_times t1 = cpu_t.elapsed();

    //[-Wunused-but-set-variable]
    //if (this->_induce) E_state   = this->Energy(job);
    //else               E_state   = this->EnergyStatic(job);
    if (this->_induce) this->Energy(job);
    else               this->EnergyStatic(job);
    boost::timer::cpu_times t2 = cpu_t.elapsed();

    double t_indu = (t1.wall - t0.wall)/1e9/60.;
    double t_ener = (t2.wall - t1.wall)/1e9/60.;
    CTP_LOG(logINFO,*_log) << (format("  o Total:     %1$1.2f min")
        % (t_ener+t_indu)) << flush;
    CTP_LOG(logINFO,*_log) << (format("  o Induction: %1$1.2f min")
        % (t_indu)) << flush;
    CTP_LOG(logINFO,*_log) << (format("  o Energy:    %1$1.2f min")
        % (t_ener)) << flush;

    job->setInduIter(iter);
    return;
}

void XInductor::Configure(XJob *job) {
    // SETUP MULTIPOLAR DENSITIES
    _qm0.clear();
    _mm1.clear();
    _mm2.clear();
    _qmm.clear();

    _job = job;
    _isConverged = !this->_induce;

    // QMM = [ QM0 --- MM1 ] --- MM2 = [ MM2 ]
    _qm0 = job->getPolarTop()->QM0();
    _mm1 = job->getPolarTop()->MM1();
    _mm2 = job->getPolarTop()->MM2();

    _qmm.reserve(_qm0.size()+_mm1.size());
    _qmm.insert(_qmm.end(),_qm0.begin(),_qm0.end());
    _qmm.insert(_qmm.end(),_mm1.begin(),_mm1.end());

    // INDUCTION & ENERGY WORKERS
    // Delete previous ...
    vector<InduWorker*>::iterator wit;
    for (wit = _indus.begin(); wit != _indus.end(); ++wit) {
        delete *wit;
    }
    _indus.clear();
    // ... and allocate new workers.
    for (int id = 0; id < this->_subthreads; ++id) {
        InduWorker *newIndu = new InduWorker(id, _top, this);
        _indus.push_back(newIndu);
        newIndu->InitSpheres(&_qmm, &_mm2);
        newIndu->SetSwitch(1);
    }

    this->InitChunks();

    return;
}

int XInductor::Induce(XJob *job) {

    for (int id = 0; id < this->_subthreads; ++id) {
        _indus[id]->SetSwitch(1);
    }

    vector< PolarSeg* >   ::iterator sit1;
    vector< PolarSeg* >   ::iterator sit2;
    vector< APolarSite* > ::iterator pit1;
    vector< APolarSite* > ::iterator pit2;

    // CONVERGENCE PARAMETERS
    double wSOR = this->_wSOR_N;
    double eTOL = this->_epsTol;
    int    maxI = this->_maxIter;

    // Adapt wSOR if any segment in QM region is charged
    for (sit1 = _qmm.begin(); sit1 < _qmm.end(); ++sit1) {
        double Q = (*sit1)->CalcTotQ();
        if (Q*Q > 0.5) {
            wSOR = this->_wSOR_C;
            break;
        }
    }

    CTP_LOG(logINFO,*_log) << "Inductor: Using WSOR = " << wSOR
        << ", ASHARP = " << _aDamp << flush;

    // Intra-pair induction ...
    bool   induce_intra_pair = this->_induce_intra_pair;
    // ... change this for jobs of type "site":
    if (_qmm.size() == 1) { induce_intra_pair = true; }

    // Compute CoM positions, generate coarsegrained sites
    vector<PolarFrag*>::iterator fit;
    for (sit1 = _qmm.begin(); sit1 < _qmm.end(); ++sit1) {
        (*sit1)->CalcPos();
        (*sit1)->GeneratePermInduCgSite(false);
        for (fit = (*sit1)->PolarFrags().begin();
            fit < (*sit1)->PolarFrags().end(); ++fit) {
            (*fit)->CalcPosCenterOfGeom();
            (*fit)->GeneratePermInduCgSite(false);
        }
    }

    double r_switch_cg_frag = 100.; // 2.5;
    double r_switch_cg_seg  = 100.; // 4.5;

    // ++++++++++++++++++++++++++++++++++++++++++++++ //
    // Inter-site fields (arising from perm. m'poles) //
    // ++++++++++++++++++++++++++++++++++++++++++++++ //

    boost::timer::cpu_timer cpu_t_perm;
    cpu_t_perm.start();
    boost::timer::cpu_times t_perm_0 = cpu_t_perm.elapsed();

    for (sit1 = _qmm.begin(); sit1 < _qmm.end(); ++sit1) {
    for (sit2 = sit1 + 1; sit2 < _qmm.end(); ++sit2) {

        // Intra-pair permanent induction field?
         if ( !induce_intra_pair ) {
             if ( job->isInCenter((*sit1)->getId())
               && job->isInCenter((*sit2)->getId()) ) {
                 continue;
             }
         }
//         for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
//         for (pit2 = (*sit2)->begin(); pit2 < (*sit2)->end(); ++pit2) {
//             _actor.BiasStat(*(*pit1), *(*pit2));
//             _actor.FieldPerm(*(*pit1), *(*pit2));
//         }}

         double dr = votca::tools::abs((*sit1)->getPos()-(*sit2)->getPos());
         if (dr > r_switch_cg_seg) {
            for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
                _actor.BiasStat(*(*pit1), *((*sit2)->getPermCgSite()));
                _actor.FieldPermAsPerm_At_By(*(*pit1), *((*sit2)->getPermCgSite()));
            }
            for (pit2 = (*sit2)->begin(); pit2 < (*sit2)->end(); ++pit2) {
                _actor.BiasStat(*(*pit2), *((*sit1)->getPermCgSite()));
                _actor.FieldPermAsPerm_At_By(*(*pit2), *((*sit1)->getPermCgSite()));
            }
        }
        // Interaction atom <> fragment (cg)
        else if (dr > r_switch_cg_frag) {
            for (fit = (*sit2)->PolarFrags().begin(); fit < (*sit2)->PolarFrags().end(); ++fit) {
                for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
                    _actor.BiasStat(*(*pit1), *((*fit)->getPermCgSite()));
                    _actor.FieldPermAsPerm_At_By(*(*pit1), *((*fit)->getPermCgSite()));
                }
            }
            for (fit = (*sit1)->PolarFrags().begin(); fit < (*sit1)->PolarFrags().end(); ++fit) {
                for (pit2 = (*sit2)->begin(); pit2 < (*sit2)->end(); ++pit2) {
                    _actor.BiasStat(*(*pit2), *((*fit)->getPermCgSite()));
                    _actor.FieldPermAsPerm_At_By(*(*pit2), *((*fit)->getPermCgSite()));
                }
            }
        }
        // Interaction atom <> atom
        else {
            for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
            for (pit2 = (*sit2)->begin(); pit2 < (*sit2)->end(); ++pit2) {
                _actor.BiasStat(*(*pit1),*(*pit2));
                _actor.FieldPerm(*(*pit1), *(*pit2));
            }}
        }
    }}

    // Permanent fields generated by outer shell
    // (Outer shell itself is treated as non-polarizable)
    for (sit2 = _mm2.begin();
         sit2 < _mm2.end();
         ++sit2) {
    for (sit1 = _qmm.begin();
         sit1 < _qmm.end();
         ++sit1) {
         for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
         for (pit2 = (*sit2)->begin(); pit2 < (*sit2)->end(); ++pit2) {
             _actor.BiasStat(*(*pit1), *(*pit2));
             _actor.FieldPerm(*(*pit1), *(*pit2));
         }}
    }}

    boost::timer::cpu_times t_perm_1 = cpu_t_perm.elapsed();
    double t_perm = (t_perm_1.wall-t_perm_0.wall)/1e9;
    CTP_LOG(logINFO,*_log) << (format("  PERM      |  T=%1$1.2fs ")
        % (t_perm));
    CTP_LOG(logINFO,*_log) << flush;

    // +++++++++++++++++++ //
    // 1st-order induction //
    // +++++++++++++++++++ //

    // Direct induction. Could also be restored from file (in the case of
    // iterative QM/MM being performed)
    if (true || !job->StartFromCPT()) {
        for (sit1 = _qmm.begin(); sit1 < _qmm.end(); ++sit1) {
            for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
                (*pit1)->InduceDirect();
            }
        }
    }
    else {
        CTP_LOG(logINFO,*_log) << "Job started from archive, reusing existing "
            << "state as starting configuration" << flush;
    }

    // Transition ranges atm<>atm, atm<>frag, atm<>seg
    vector<double> rcs_frag;
    vector<double> rcs_seg;
    vector<double> ets;
    //rcs_frag.push_back(1.5);
    //rcs_frag.push_back(2.0);
    rcs_frag.push_back(r_switch_cg_frag);
    //rcs_seg.push_back(3.0);
    //rcs_seg.push_back(3.75);
    rcs_seg.push_back(r_switch_cg_seg);
    //ets.push_back(100*eTOL);
    //ets.push_back(50*eTOL);
    ets.push_back(eTOL);

    int iter_cg = 0;

    for (int ridx = 0; ridx < 1; ++ridx) {
        double rc_frag = rcs_frag[ridx];
        double rc_seg = rcs_seg[ridx];
        double rc_eTOL = ets[ridx];
        iter_cg = 0;
        for ( ; iter_cg < maxI; ++iter_cg) {

            boost::timer::cpu_timer cpu_t;
            cpu_t.start();
            boost::timer::cpu_times t0 = cpu_t.elapsed();

            // Reset fields FUx, FUy, FUz
            for (sit1 = _qmm.begin(); sit1 < _qmm.end(); ++sit1) {
                for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
                    (*pit1)->ResetFieldU();
                }
            }

            boost::timer::cpu_times t1 = cpu_t.elapsed();
            // Coarsegrain polar segments & fragments
            for (sit1 = _qmm.begin(); sit1 < _qmm.end(); ++sit1) {
                (*sit1)->GeneratePermInduCgSite(false);
                for (fit = (*sit1)->PolarFrags().begin();
                    fit < (*sit1)->PolarFrags().end(); ++fit) {
                    (*fit)->GeneratePermInduCgSite(false);
                }
            }

            boost::timer::cpu_times t2 = cpu_t.elapsed();
            // Intra-site contribution to induction field
            for (sit1 = _qmm.begin(); sit1 < _qmm.end(); ++sit1) {
                for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
                for (pit2 = pit1 + 1;        pit2 < (*sit1)->end(); ++pit2) {
                    _actor.BiasIndu(*(*pit1),*(*pit2));
                    _actor.FieldIndu(*(*pit1),*(*pit2));
                }}
            }

            boost::timer::cpu_times t3 = cpu_t.elapsed();
            int count_atm_atm   = 0;
            int count_atm_frag  = 0;
            int count_atm_seg   = 0;
            // Inter-site contribution to induction field
            for (sit1 = _qmm.begin(); sit1 < _qmm.end(); ++sit1) {
            for (sit2 = sit1 + 1; sit2 < _qmm.end(); ++sit2) {
                double dr = votca::tools::abs((*sit1)->getPos()-(*sit2)->getPos());
                // Interaction atom <> segment (cg)
                if (dr > rc_seg) {
                    count_atm_seg += 1;
                    for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
                        _actor.BiasIndu(*(*pit1), *((*sit2)->getInduCgSite()));
                        _actor.FieldPermAsIndu_At_By(*(*pit1), *((*sit2)->getInduCgSite()));
                    }
                    for (pit2 = (*sit2)->begin(); pit2 < (*sit2)->end(); ++pit2) {
                        _actor.BiasIndu(*(*pit2), *((*sit1)->getInduCgSite()));
                        _actor.FieldPermAsIndu_At_By(*(*pit2), *((*sit1)->getInduCgSite()));
                    }
                }
                // Interaction atom <> fragment (cg)
                else if (dr > rc_frag) {
                    count_atm_frag += 1;
                    for (fit = (*sit2)->PolarFrags().begin(); fit < (*sit2)->PolarFrags().end(); ++fit) {
                        for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
                            _actor.BiasIndu(*(*pit1), *((*fit)->getInduCgSite()));
                            _actor.FieldPermAsIndu_At_By(*(*pit1), *((*fit)->getInduCgSite()));
                        }
                    }
                    for (fit = (*sit1)->PolarFrags().begin(); fit < (*sit1)->PolarFrags().end(); ++fit) {
                        for (pit2 = (*sit2)->begin(); pit2 < (*sit2)->end(); ++pit2) {
                            _actor.BiasIndu(*(*pit2), *((*fit)->getInduCgSite()));
                            _actor.FieldPermAsIndu_At_By(*(*pit2), *((*fit)->getInduCgSite()));
                        }
                    }
                }
                 // Interaction atom <> atom
                else {
                    count_atm_atm += 1;
                    for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
                    for (pit2 = (*sit2)->begin(); pit2 < (*sit2)->end(); ++pit2) {
                        _actor.BiasIndu(*(*pit1),*(*pit2));
                        _actor.FieldIndu(*(*pit1), *(*pit2));
                    }}
                }
            }}

            boost::timer::cpu_times t4 = cpu_t.elapsed();

            // Induce again
            for (sit1 = _qmm.begin(); sit1 < _qmm.end(); ++sit1) {
                 for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
                     (*pit1)->Induce(wSOR);
                 }
            }

            // Check for convergence
            bool    converged       = true;
            double  maxdU_U         = -1;
            double  avgdU_U         = 0.0;
            double  rmsdU           = 0.0;
            int     baseN           = 0;
            for (sit1 = _qmm.begin(); sit1 < _qmm.end(); ++sit1) {
                 for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
                     double dU_U = (*pit1)->HistdU();
                     avgdU_U += dU_U;
                     double dU2 = (*pit1)->HistdU2();
                     rmsdU += dU2;
                     ++baseN;
                     if ( dU_U > maxdU_U ) { maxdU_U = dU_U; }
                     if ( dU_U > rc_eTOL) { converged = false; }
                 }
            }
            avgdU_U /= baseN;
            rmsdU /= baseN;
            rmsdU = sqrt(rmsdU);
            if (avgdU_U < rc_eTOL/10.) { converged = true; }

            boost::timer::cpu_times t5 = cpu_t.elapsed();

            double t_total = (t5.wall-t0.wall)/1e9;
            double t_cg    = (t2.wall-t1.wall)/1e9;
            double t_intra = (t3.wall-t2.wall)/1e9;
            double t_inter = (t4.wall-t3.wall)/1e9;

            CTP_LOG(logINFO,*_log) << (boost::format(
                "  ITER %1$3d  |  AVG(dU/U) %3$1.7e  EPS %11$1.1e  |  NN(0<>%7$1.1f<>%6$1.1f) %8$04d|%9$04d|%10$04d")
                % iter_cg % maxdU_U % avgdU_U % rmsdU % baseN % rc_seg
                % rc_frag % count_atm_atm % count_atm_frag % count_atm_seg
                % eTOL).str();
            CTP_LOG(logINFO,*_log) << (format("  |  T=%1$1.2fs CG/I/O %2$2.2f%% %3$2.2f%% %4$2.2f%%")
                % (t_total) % (100*t_cg/t_total) % (100*t_intra/t_total)
                % (100*t_inter/t_total));
            CTP_LOG(logINFO,*_log) << flush;

            // Break if converged
            if      (converged) {
                _isConverged = true;
                break;
            }
            else if (iter_cg == maxI - 1) {
                _isConverged = false;
                this->setError((boost::format("Did not converge to precision "
                   "(%1$d steps, AVG(dU:U) = %2$1.3e)") % maxI % avgdU_U).str());
                break;
            }
        }
    }

    // Reset U1 history
    for (sit1 = _qmm.begin(); sit1 < _qmm.end(); ++sit1) {
        for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
            (*pit1)->ResetU1Hist();
        }
    }

    return iter_cg;
//    assert(false);
//
//
//    // ++++++++++++++++++++++ //
//    // Higher-order induction //
//    // ++++++++++++++++++++++ //
//
//
//    int iter = 0;
//    for ( ; iter < maxI; ++iter) {
//        // Reset fields FUx, FUy, FUz
//        for (sit1 = _qmm.begin(); sit1 < _qmm.end(); ++sit1) {
//            for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
//                (*pit1)->ResetFieldU();
//            }
//        }
//
//        // Intra-site contribution to induction field
//        for (sit1 = _qmm.begin(); sit1 < _qmm.end(); ++sit1) {
//            for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
//            for (pit2 = pit1 + 1;        pit2 < (*sit1)->end(); ++pit2) {
//                _actor.BiasIndu(*(*pit1),*(*pit2));
//                _actor.FieldIndu(*(*pit1),*(*pit2));
//            }}
//        }
//
//        // Inter-site contribution to induction field
//        //for (sit1 = _qmm.begin(); sit1 < _qmm.end(); ++sit1) {
//        //for (sit2 = sit1 + 1; sit2 < _qmm.end(); ++sit2) {
//        //    for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
//        //    for (pit2 = (*sit2)->begin(); pit2 < (*sit2)->end(); ++pit2) {
//        //        _actor.FieldIndu(*(*pit1), *(*pit2));
//        //    }}
//        //}}
//
//        for (int id = 0; id < this->_subthreads; ++id) {
//            _indus[id]->Start();
//        }
//
//        for (int id = 0; id < this->_subthreads; ++id) {
//            _indus[id]->WaitDone();
//        }
//
//        this->ClearTodoTable();
//
//
//
//        // Induce again
//        for (sit1 = _qmm.begin(); sit1 < _qmm.end(); ++sit1) {
//             for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
//                 (*pit1)->Induce(wSOR);
//             }
//        }
//
//        // Check for convergence
//        bool    converged       = true;
//        double  maxdU_U         = -1;
//        double  avgdU_U         = 0.0;
//        double  rmsdU           = 0.0;
//        int     baseN           = 0;
//        for (sit1 = _qmm.begin(); sit1 < _qmm.end(); ++sit1) {
//             for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
//                 double dU_U = (*pit1)->HistdU();
//                 avgdU_U += dU_U;
//                 double dU2 = (*pit1)->HistdU2();
//                 rmsdU += dU2;
//                 ++baseN;
//                 if ( dU_U > maxdU_U ) { maxdU_U = dU_U; }
//                 if ( dU_U > eTOL) { converged = false; }
//             }
//        }
//        avgdU_U /= baseN;
//        rmsdU /= baseN;
//        rmsdU = sqrt(rmsdU);
//        if (avgdU_U < eTOL/10.) { converged = true; }
//
//        CTP_LOG(logINFO,*_log) << (boost::format(
//            "  Iter %1$3d | max(dU/U) %2$1.7e  avg(dU/U) %3$1.7e  rms(dU) %4$1.7e  N %5$d")
//            % iter % maxdU_U % avgdU_U % rmsdU % baseN).str() << flush;
//
//        // Break if converged
//        if      (converged) {
//            _isConverged = true;
//            break;
//        }
//        else if (iter == maxI - 1) {
//            _isConverged = false;
//            //this->setError("Did not converge to precision ("
//            //    + boost::lexical_cast<string>(maxI) + " steps, AVG dU:U "
//            //    + boost::lexical_cast<string>(avgdU) + ")");
//            this->setError((boost::format("Did not converge to precision "
//               "(%1$d steps, AVG(dU:U) = %2$1.3e)") % maxI % avgdU_U).str());
//            break;
//        }
//    }
//
//    return iter;

}

double XInductor::Energy(XJob *job) {

    double int2eV = 1/(4*M_PI*8.854187817e-12) * 1.602176487e-19 / 1.000e-9;

    _actor.ResetEnergy();

    // Energy splittings =======================================================
    // PAIR/SITE        <->        SPH1         <->          SPH2 = OUT       //
    double E_Tot = 0.0;
    // ... 0th kind
    double E_Pair_Pair = 0.0;
    double E_Pair_Sph1 = 0.0;
    double E_Sph1_Sph1 = 0.0;
    double E_Pair_Sph2 = 0.0;
    double E_Sph1_Sph2 = 0.0;
    // ... 1st kind
    double eu_inter = 0.0;
    double eu_intra = 0.0;
    double e_perm   = 0.0;
    // ... 2nd kind
    double epp      = 0.0;
    double epu      = 0.0;
    double euu      = 0.0;
    // ... 3rd kind
    double e_f_c_c          = 0.0;
    double e_f_c_non_c      = 0.0;
    double e_f_c_out        = 0.0;
    double e_f_non_c_non_c  = 0.0;
    double e_f_non_c_out    = 0.0;
    double e_m_c            = 0.0;
    double e_m_c_out        = 0.0;
    double e_m_non_c        = 0.0;
    double e_m_non_c_out    = 0.0;
    double e_m_out          = 0.0;
    // =========================================================================

    vector< PolarSeg* >     ::iterator      sit1;
    vector< PolarSeg* >     ::iterator      sit2;
    vector< APolarSite* >   ::iterator      pit1;
    vector< APolarSite* >   ::iterator      pit2;

    // =============================================================== //
    // System Energy | QM | MM1 | MM2 |                                //
    // =============================================================== //

    for (int id = 0; id < this->_subthreads; ++id) {
        _indus[id]->SetSwitch(0);
    }

    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
    // Inter-site energy comprising central + first polarization shell //
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

    for (int id = 0; id < this->_subthreads; ++id) {
        _indus[id]->Start();
    }

    for (int id = 0; id < this->_subthreads; ++id) {
        _indus[id]->WaitDone();
    }

    for (int id = 0; id < this->_subthreads; ++id) {
        E_Pair_Pair += _indus[id]->GetEPairPair();
        E_Pair_Sph1 += _indus[id]->GetEPairSph1();
        E_Sph1_Sph1 += _indus[id]->GetESph1Sph1();

        eu_inter += _indus[id]->GetActor().getEU_INTER();
        eu_intra += _indus[id]->GetActor().getEU_INTRA();
        e_perm   += _indus[id]->GetActor().getEP();

        epp += _indus[id]->GetActor().getEPP();
        epu += _indus[id]->GetActor().getEPU();
        euu += _indus[id]->GetActor().getEUU();

        e_f_c_c             += _indus[id]->GetE_f_C_C();
        e_f_c_non_c         += _indus[id]->GetE_f_C_non_C();
        e_f_non_c_non_c     += _indus[id]->GetE_f_non_C_non_C();
        e_m_c               += _indus[id]->GetE_m_C();
        e_m_non_c           += _indus[id]->GetE_m_non_C();
    }

    this->ClearTodoTable();

    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
    // Inter-site energy resulting from interaction with static shell  //
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

    // Interaction between central and static shell
    for (sit1 = _qm0.begin(); sit1 < _qm0.end(); ++sit1) {
    for (sit2 = _mm2.begin(); sit2 < _mm2.end(); ++sit2) {
        for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
        for (pit2 = (*sit2)->begin(); pit2 < (*sit2)->end(); ++pit2) {
            _actor.BiasIndu(*(*pit1), *(*pit2));
            e_f_c_out += _actor.E_f(*(*pit1), *(*pit2));
            e_m_c_out += _actor.E_m(*(*pit1), *(*pit2));
        }}
    }}

    // Interaction between polarizable and static shell
    for (sit1 = _mm1.begin(); sit1 < _mm1.end(); ++sit1) {
    for (sit2 = _mm2.begin(); sit2 < _mm2.end(); ++sit2) {
        for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
        for (pit2 = (*sit2)->begin(); pit2 < (*sit2)->end(); ++pit2) {
            _actor.BiasIndu(*(*pit1), *(*pit2));
            e_f_non_c_out += _actor.E_f(*(*pit1), *(*pit2));
            e_m_non_c_out += _actor.E_m(*(*pit1), *(*pit2));
        }}
    }}

    // Increment energies
    // ... 0th kind
    E_Pair_Sph2 += e_f_c_out + e_m_c_out;
    E_Sph1_Sph2 += e_f_non_c_out + e_m_non_c_out;
    // ... 1st kind
    e_perm      += _actor.getEP();
    eu_inter    += _actor.getEU_INTER();
    // ... 2nd kind
    epp += _actor.getEPP();
    epu += _actor.getEPU();
    euu += _actor.getEUU();
    // ... 3rd kind
    // ... ... -> done in loop above, but need to summarize e_m_*
    e_m_c      += e_m_c_out;
    e_m_non_c  += e_m_non_c_out;

    E_Tot = E_Pair_Pair + E_Pair_Sph1 + E_Sph1_Sph1 + E_Pair_Sph2;

    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
    // Intramolecular field interaction                                //
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
    double e_f_intra_0 = 0.0;
    double e_m_intra_0 = 0.0;
    for (sit1 = _qm0.begin(); sit1 < _qm0.end(); ++sit1) {
        for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
        for (pit2 = pit1+1; pit2 < (*sit1)->end(); ++pit2) {
            _actor.BiasIndu(*(*pit1), *(*pit2));
            e_f_intra_0 += _actor.E_f_intra(*(*pit1), *(*pit2));
            e_m_intra_0 += _actor.E_m_intra(*(*pit1), *(*pit2));
            _actor.RevBias();
            e_m_intra_0 += _actor.E_m_intra(*(*pit2), *(*pit1));
        }}
    }
    double e_f_intra_1 = 0.0;
    double e_m_intra_1 = 0.0;
    for (sit1 = _mm1.begin(); sit1 < _mm1.end(); ++sit1) {
        for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
        for (pit2 = pit1+1; pit2 < (*sit1)->end(); ++pit2) {
            _actor.BiasIndu(*(*pit1), *(*pit2));
            e_f_intra_1 += _actor.E_f_intra(*(*pit1), *(*pit2));
            e_m_intra_1 += _actor.E_m_intra(*(*pit1), *(*pit2));
            _actor.RevBias();
            e_m_intra_1 += _actor.E_m_intra(*(*pit2), *(*pit1));
        }}
    }
    double e_f_intra_2 = 0.0;
    double e_m_intra_2 = 0.0;
    for (sit1 = _mm2.begin(); sit1 < _mm2.end(); ++sit1) {
        for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
        for (pit2 = pit1+1; pit2 < (*sit1)->end(); ++pit2) {
            _actor.BiasIndu(*(*pit1), *(*pit2));
            e_f_intra_2 += _actor.E_f_intra(*(*pit1), *(*pit2));
            e_m_intra_2 += _actor.E_m_intra(*(*pit1), *(*pit2));
            _actor.RevBias();
            e_m_intra_2 += _actor.E_m_intra(*(*pit2), *(*pit1));
        }}
    }

    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
    // Total ind. work (to be used with pre-generated perm. fields)    //
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
    double e_m_0 = 0.0;
    double e_m_1 = 0.0;
    double e_m_2 = 0.0;
    for (sit1 = _qm0.begin(); sit1 < _qm0.end(); ++sit1) {
        for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
            e_m_0 += (*pit1)->InductionWork();
        }
    }
    for (sit1 = _mm1.begin(); sit1 < _mm1.end(); ++sit1) {
        for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
            e_m_1 += (*pit1)->InductionWork();
        }
    }
    for (sit1 = _mm2.begin(); sit1 < _mm2.end(); ++sit1) {
        for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
            e_m_2 += (*pit1)->InductionWork();
        }
    }

//    cout << endl << "E_f_intra_0 " << e_f_intra_0*int2eV << flush;
//    cout << endl << "E_m_intra_0 " << e_m_intra_0*int2eV << flush;
//    cout << endl << "E_f_intra_1 " << e_f_intra_1*int2eV << flush;
//    cout << endl << "E_m_intra_1 " << e_m_intra_1*int2eV << flush;
//    cout << endl << "E_f_intra_2 " << e_f_intra_2*int2eV << flush;
//    cout << endl << "E_m_intra_2 " << e_m_intra_2*int2eV << flush;
//    cout << endl << "E_m_0 " << e_m_0*int2eV << flush;
//    cout << endl << "E_m_1 " << e_m_1*int2eV << flush;
//    cout << endl << "E_m_2 " << e_m_2*int2eV << flush;

    // This is important if permanent/induction fields have been applied
    // that do not originate in QM0, MM1, MM2

    e_m_c     = e_m_0 + e_f_intra_0;
    e_m_non_c = e_m_1 + e_f_intra_1;
    e_m_out   = e_m_2 + e_f_intra_2;

    //e_f_c_c += e_f_intra_0;
    //e_f_non_c_non_c += e_f_intra_1;

    // =============================================================== //
    // Energy Output                                                   //
    // =============================================================== //

    // ... 0th kind
    E_Tot = E_Pair_Pair
          + E_Pair_Sph1
          + E_Sph1_Sph1
          + E_Pair_Sph2
          + E_Sph1_Sph2;

//    CTP_LOG(logINFO,*_log)
//             << "... E(X) = " << E_Tot * int2eV << " eV "
//             << flush << "...      = (Site, Site) " << E_Pair_Pair * int2eV
//             << flush << "...      + (Site, Sph1) " << E_Pair_Sph1 * int2eV
//             << flush << "...      + (Sph1, Sph1) " << E_Sph1_Sph1 * int2eV
//             << flush << "...      + (Site, Sph2) " << E_Pair_Sph2 * int2eV
//             << flush << "...      + (Sph1, Sph2) " << E_Sph1_Sph2 * int2eV
//             << flush;

    // ... 1st kind
    double E_PPUU = epp
                  + epu
                  + euu;

//    CTP_LOG(logINFO,*_log)
//             << "... E(X) = " << E_PPUU * int2eV << " eV "
//             << flush << "...      = (PP) "    << epp  * int2eV
//             << flush << "...      + (PU) "    << epu  * int2eV
//             << flush << "...      + (UU) "    << euu  * int2eV
//             << flush;

    // ... 2nd kind
    double E_f_m = e_f_c_c
                 + e_f_c_non_c
                 + e_f_c_out
                 + e_f_non_c_non_c
                 + e_f_non_c_out
                 + e_m_c
                 + e_m_non_c
                 + e_m_out;

    CTP_LOG(logINFO,*_log)
        << (format("PP-PU-UU Splitting")).str()
        << flush << (format("  + U [Q  -  Q]       = %1$+1.7f eV") % (epp      * int2eV)).str()
        << flush << (format("  + U [Q  - dQ]       = %1$+1.7f eV") % (epu      * int2eV)).str()
        << flush << (format("  + U [dQ - dQ]       = %1$+1.7f eV") % (euu      * int2eV)).str()
        << flush << (format("  = ------------------------------")).str()
        << flush << (format("  + SUM(E)            = %1$+1.7f eV") % (E_PPUU   * int2eV)).str()
        << flush;
    CTP_LOG(logINFO,*_log)
        << (format("QM0-MM1-MM2 Splitting")).str()
        << flush << (format("  + Field-term [0-0]  = %1$+1.7f eV (%2$+1.7f)") % (e_f_c_c          * int2eV) % (e_f_intra_0*int2eV)).str()
        << flush << (format("  + Field-term [0-1]  = %1$+1.7f eV") % (e_f_c_non_c      * int2eV)).str()
        << flush << (format("  + Field-term [0-2]  = %1$+1.7f eV") % (e_f_c_out        * int2eV)).str()
        << flush << (format("  + Field-term [1-1]  = %1$+1.7f eV (%2$+1.7f)") % (e_f_non_c_non_c  * int2eV) % (e_f_intra_1*int2eV)).str()
        << flush << (format("  + Field-term [1-2]  = %1$+1.7f eV") % (e_f_non_c_out    * int2eV)).str()
        << flush << (format("    ------------------------------")).str()
        << flush << (format("  + Work-term  [-0-]  = %1$+1.7f eV") % (e_m_c            * int2eV)).str()
        << flush << (format("  + Work-term  [-1-]  = %1$+1.7f eV") % (e_m_non_c        * int2eV)).str()
        << flush << (format("  + Work-term  [-2-]  = %1$+1.7f eV") % (e_m_out          * int2eV)).str()
        << flush << (format("  = ------------------------------")).str()
        << flush << (format("    SUM(E)            = %1$+1.7f eV") % (E_f_m               *int2eV)).str()
        << flush;

//    CTP_LOG(logINFO,*_log)
//             << "... E(X) = " << E_f_m * int2eV << " eV "
//             << flush << "...      = (f,0-0) " << e_f_c_c          * int2eV
//             << flush << "...      + (f,0-1) " << e_f_c_non_c      * int2eV
//             << flush << "...      + (f,0-2) " << e_f_c_out        * int2eV
//             << flush << "...      + (f,1-1) " << e_f_non_c_non_c  * int2eV
//             << flush << "...      + (f,1-2) " << e_f_non_c_out    * int2eV
//             << flush << "...      + (m,-0-) " << e_m_c            * int2eV
//             << flush << "...      + (m,-1-) " << e_m_non_c        * int2eV
//             << flush << "...      + (m,-2-) " << e_m_out          * int2eV
//             << flush;

    // Forward results to job
    job->setEnergy(E_Tot            *int2eV,
                   E_Pair_Pair      *int2eV,
                   E_Pair_Sph1      *int2eV,
                   E_Pair_Sph2      *int2eV,
                   E_Sph1_Sph1      *int2eV,
                   E_Sph1_Sph2      *int2eV,
                   e_perm           *int2eV,
                   eu_inter         *int2eV);

    job->setEnergy_PPUU(epp         *int2eV,
                        epu         *int2eV,
                        euu         *int2eV);

    job->setEnergy_f_m(e_f_c_c         *int2eV,
                       e_f_c_non_c     *int2eV,
                       e_f_c_out       *int2eV,
                       e_f_non_c_non_c *int2eV,
                       e_f_non_c_out   *int2eV,
                       e_m_c           *int2eV,
                       e_m_non_c       *int2eV,
                       e_m_out         *int2eV);

    return E_Tot;
}

double XInductor::EnergyStatic(XJob *job) {

    double int2eV = 1/(4*M_PI*8.854187817e-12) * 1.602176487e-19 / 1.000e-9;

    _actor.ResetEnergy();

    // Energy splittings =======================================================
    // PAIR/SITE        <->        SPH1         <->          SPH2 = OUT       //
    double E_Tot = 0.0;
    // ... 0th kind
    double E_Pair_Pair = 0.0;
    double E_Pair_Sph1 = 0.0;
    double E_Sph1_Sph1 = 0.0;
    double E_Pair_Sph2 = 0.0;
    double E_Sph1_Sph2 = 0.0;
    // ... 1st kind
    double eu_inter = 0.0;

    //[-Wunused-variable]
    //double eu_intra = 0.0;

    double e_perm   = 0.0;
    // ... 2nd kind
    double epp      = 0.0;
    double epu      = 0.0;
    double euu      = 0.0;
    // ... 3rd kind
    double e_f_c_c          = 0.0;
    double e_f_c_non_c      = 0.0;
    double e_f_c_out        = 0.0;
    double e_f_non_c_non_c  = 0.0;
    double e_f_non_c_out    = 0.0;
    double e_m_c            = 0.0;

    //[-Wunused-variable]
    //double e_m_c_out        = 0.0;

    double e_m_non_c        = 0.0;

    //[-Wunused-variable]
    //double e_m_non_c_out    = 0.0;

    double e_m_out          = 0.0;
    // =========================================================================

    vector< PolarSeg* >    ::iterator      sit1;
    vector< PolarSeg* >    ::iterator      sit2;
    vector< APolarSite* >  ::iterator      pit1;
    vector< APolarSite* >  ::iterator      pit2;

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
    // Interaction pair <-> inner cut-off, without intra-pair interaction //
    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

    for (sit1 = _qm0.begin(); sit1 < _qm0.end(); ++sit1) {
    for (sit2 = _mm1.begin(); sit2 < _mm1.end(); ++sit2) {
        for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
        for (pit2 = (*sit2)->begin(); pit2 < (*sit2)->end(); ++pit2) {
            _actor.BiasIndu(*(*pit1), *(*pit2));
            e_f_c_non_c += _actor.E_f(*(*pit1), *(*pit2));
        }}
    }}

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
    // Interaction pair <-> outer cut-off                                 //
    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

    for (sit1 = _qm0.begin(); sit1 < _qm0.end(); ++sit1) {
    for (sit2 = _mm2.begin(); sit2 < _mm2.end(); ++sit2) {
        for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
        for (pit2 = (*sit2)->begin(); pit2 < (*sit2)->end(); ++pit2) {
            _actor.BiasIndu(*(*pit1), *(*pit2));
            e_f_c_out += _actor.E_f(*(*pit1), *(*pit2));
        }}
    }}

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
    // Intra-pair interaction                                             //
    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //

    for (sit1 = _qm0.begin(); sit1 < _qm0.end(); ++sit1) {
    for (sit2 = sit1 + 1; sit2 < _qm0.end(); ++sit2) {
        for (pit1 = (*sit1)->begin(); pit1 < (*sit1)->end(); ++pit1) {
        for (pit2 = (*sit2)->begin(); pit2 < (*sit2)->end(); ++pit2) {
            _actor.BiasIndu(*(*pit1), *(*pit2));
            e_f_c_c += _actor.E_f(*(*pit1), *(*pit2));
        }}
    }}

    // Increment energies
    // ... 0th kind
    E_Pair_Pair     += e_f_c_c;
    E_Pair_Sph1     += e_f_c_non_c;
    E_Pair_Sph2     += e_f_c_out;
    // ... 1st kind
    e_perm          += _actor.getEP();
    eu_inter        += _actor.getEU_INTER();
    // ... 2nd kind
    epp             += _actor.getEPP();
    epu             += _actor.getEPU();
    euu             += _actor.getEUU();
    // ... 3rd kind
    // ... ... -> done in loops above

    // =============================================================== //
    // Energy Output                                                   //
    // =============================================================== //

    // ... 0th kind
    E_Tot = E_Pair_Pair
          + E_Pair_Sph1
          + E_Sph1_Sph1
          + E_Pair_Sph2
          + E_Sph1_Sph2;

//    CTP_LOG(logINFO,*_log)
//             << "... E(X) = " << E_Tot * int2eV << " eV "
//             << flush << "...      = (Site, Site) " << E_Pair_Pair * int2eV
//             << flush << "...      + (Site, Sph1) " << E_Pair_Sph1 * int2eV
//             << flush << "...      + (Sph1, Sph1) " << E_Sph1_Sph1 * int2eV
//             << flush << "...      + (Site, Sph2) " << E_Pair_Sph2 * int2eV
//             << flush << "...      + (Sph1, Sph2) " << E_Sph1_Sph2 * int2eV
//             << flush;

    // ... 1st kind
    double E_PPUU = epp
                  + epu
                  + euu;

//    CTP_LOG(logINFO,*_log)
//             << "... E(X) = " << E_PPUU * int2eV << " eV "
//             << flush << "...      = (PP) "    << epp  * int2eV
//             << flush << "...      + (PU) "    << epu  * int2eV
//             << flush << "...      + (UU) "    << euu  * int2eV
//             << flush;

    // ... 2nd kind
    double E_f_m = e_f_c_c
                 + e_f_c_non_c
                 + e_f_c_out
                 + e_f_non_c_non_c
                 + e_f_non_c_out
                 + e_m_c
                 + e_m_non_c
                 + e_m_out;

//    CTP_LOG(logINFO,*_log)
//             << "... E(X) = " << E_f_m * int2eV << " eV "
//             << flush << "...      = (f,0-0) " << e_f_c_c          * int2eV
//             << flush << "...      + (f,0-1) " << e_f_c_non_c      * int2eV
//             << flush << "...      + (f,0-2) " << e_f_c_out        * int2eV
//             << flush << "...      + (f,1-1) " << e_f_non_c_non_c  * int2eV
//             << flush << "...      + (f,1-2) " << e_f_non_c_out    * int2eV
//             << flush << "...      + (m,-0-) " << e_m_c            * int2eV
//             << flush << "...      + (m,-1-) " << e_m_non_c        * int2eV
//             << flush << "...      + (m,-2-) " << e_m_out          * int2eV
//             << flush;

    CTP_LOG(logINFO,*_log)
        << (format("PP-PU-UU Splitting")).str()
        << flush << (format("  + U [Q  -  Q]       = %1$+1.7f eV") % (epp      * int2eV)).str()
        << flush << (format("  + U [Q  - dQ]       = %1$+1.7f eV") % (epu      * int2eV)).str()
        << flush << (format("  + U [dQ - dQ]       = %1$+1.7f eV") % (euu      * int2eV)).str()
        << flush << (format("    ------------------------------")).str()
        << flush << (format("    SUM(E)            = %1$+1.7f eV") % (E_PPUU   * int2eV)).str()
        << flush;
    CTP_LOG(logINFO,*_log)
        << (format("QM0-MM1-MM2 Splitting")).str()
        << flush << (format("  + Field-term [0-0]  = %1$+1.7f eV") % (e_f_c_c          * int2eV)).str()
        << flush << (format("  + Field-term [0-1]  = %1$+1.7f eV") % (e_f_c_non_c      * int2eV)).str()
        << flush << (format("  + Field-term [0-2]  = %1$+1.7f eV") % (e_f_c_out        * int2eV)).str()
        << flush << (format("  + Field-term [1-1]  = %1$+1.7f eV") % (e_f_non_c_non_c  * int2eV)).str()
        << flush << (format("  + Field-term [1-2]  = %1$+1.7f eV") % (e_f_non_c_out    * int2eV)).str()
        << flush << (format("    ------------------------------")).str()
        << flush << (format("  + Work-term  [-0-]  = %1$+1.7f eV") % (e_m_c            * int2eV)).str()
        << flush << (format("  + Work-term  [-1-]  = %1$+1.7f eV") % (e_m_non_c        * int2eV)).str()
        << flush << (format("  + Work-term  [-2-]  = %1$+1.7f eV") % (e_m_out          * int2eV)).str()
        << flush << (format("    ------------------------------")).str()
        << flush << (format("    SUM(E)            = %1$+1.7f eV") % (E_f_m               *int2eV)).str()
        << flush;

    // Forward results to job
    job->setEnergy(E_Tot            *int2eV,
                   E_Pair_Pair      *int2eV,
                   E_Pair_Sph1      *int2eV,
                   E_Pair_Sph2      *int2eV,
                   E_Sph1_Sph1      *int2eV,
                   E_Sph1_Sph2      *int2eV,
                   e_perm           *int2eV,
                   eu_inter         *int2eV);

    job->setEnergy_PPUU(epp         *int2eV,
                        epu         *int2eV,
                        euu         *int2eV);

    job->setEnergy_f_m(e_f_c_c         *int2eV,
                       e_f_c_non_c     *int2eV,
                       e_f_c_out       *int2eV,
                       e_f_non_c_non_c *int2eV,
                       e_f_non_c_out   *int2eV,
                       e_m_c           *int2eV,
                       e_m_non_c       *int2eV,
                       e_m_out         *int2eV);

    return E_Tot;
}

XInductor::XInductor(Topology *top, Property *opt,
                     string sfx, int nst, bool mav)
                  : _subthreads(nst), _maverick(mav) {

    string key = sfx + ".tholemodel";

        if ( opt->exists(key+".induce") ) {
            int induce = opt->get(key+".induce").as< int >();
            _induce = (induce == 0) ? false : true;
        }
        else { _induce = true; }

        if ( opt->exists(key+".induce_intra_pair") ) {
            int induce = opt->get(key+".induce_intra_pair").as< int >();
            _induce_intra_pair = (induce == 0) ? false : true;
        }
        else { _induce_intra_pair = true; }

        if ( opt->exists(key+".exp_damp") ) {
            _aDamp = opt->get(key+".exp_damp").as< double >();
        }
        else {
            cout << endl << "... ... WARNING: No sharpness parameter supplied";
            cout << endl << "... ... ... Using default a = 0.39";
            _aDamp = 0.39;
        }

    key = sfx + ".convergence";

        if ( opt->exists(key+".wSOR_N") ) {
            _wSOR_N = opt->get(key+".wSOR_N").as< float >();
        }
        else { _wSOR_N = 0.75; }
        if ( opt->exists(key+".wSOR_C") ) {
            _wSOR_C = opt->get(key+".wSOR_C").as< float >();
        }
        else { _wSOR_C = 0.75; }

        if ( opt->exists(key+".max_iter") ) {
            _maxIter = opt->get(key+".max_iter").as< int >();
        }
        else { _maxIter = 512; }

        if ( opt->exists(key+".tolerance") ) {
            _epsTol = opt->get(key+".tolerance").as< double >();
        }
        else { _epsTol = 0.001; }

    _actor = XInteractor(NULL, _aDamp);

}

}}