/*
 parsephon.cpp

 Copyright (c) 2014, 2015, 2016 Terumasa Tadano

 This file is distributed under the terms of the MIT license.
 Please see the file 'LICENCE.txt' in the root directory
 or http://opensource.org/licenses/mit-license.php for information.
*/

#include "parsephon.h"
#include "conductivity.h"
#include "dielec.h"
#include "dynamical.h"
#include "error.h"
#include "ewald.h"
#include "fcs_phonon.h"
#include "gruneisen.h"
#include "integration.h"
#include "isotope.h"
#include "kpoint.h"
#include "memory.h"
#include "phonon_dos.h"
#include "phonon_velocity.h"
#include "anharmonic_core.h"
#include "mode_analysis.h"
#include "scph.h"
#include "symmetry_core.h"
#include "system.h"
#include "thermodynamics.h"
#include "write_phonons.h"
#include <sys/stat.h>
#include <sstream>
#include <istream>
#include <iostream>
#include <fstream>
#include <map>
#include <vector>
#include <string>
#include <algorithm>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>

using namespace PHON_NS;

Input::Input(PHON *phon) : Pointers(phon)
{
    from_stdin = false;
    job_title = "";
}

Input::~Input()
{
    if (ifs_input.is_open()) ifs_input.close();
}

void Input::parce_input(int narg,
                        char **arg)
{
    if (narg == 1) {

        from_stdin = true;

    } else {

        from_stdin = false;

        ifs_input.open(arg[1], std::ios::in);
        if (!ifs_input) {
            std::cout << "No such file or directory: " << arg[1] << std::endl;
            exit("parse_input", "could not open the file");
        }
    }

    if (!locate_tag("&general"))
        exit("parse_input",
                    "&general entry not found in the input file");
    parse_general_vars();

    if (!locate_tag("&cell"))
        exit("parse_input",
                    "&cell entry not found in the input file");
    parse_cell_parameter();

    const auto use_defaults_for_analysis = !locate_tag("&analysis");
    parse_analysis_vars(use_defaults_for_analysis);

    if (!locate_tag("&kpoint"))
        exit("parse_input",
                    "&kpoint entry not found in the input file");
    parse_kpoints();

    if (phon->mode == "SCPH") {
        if (!locate_tag("&scph"))
            exit("parse_input",
                        "&scph entry not found in the input file");
        parse_scph_vars();
    }
}

void Input::parse_general_vars()
{
    // Read input parameters in the &general-field.

    int i;
    int nkd;
    struct stat st;
    std::string str_tmp;
    const std::vector<std::string> input_list{
          "PREFIX", "MODE", "NSYM", "TOLERANCE", "PRINTSYM", "FCSXML", "FC2XML",
          "TMIN", "TMAX", "DT", "NBANDS", "NONANALYTIC", "BORNINFO", "NA_SIGMA",
          "ISMEAR", "EPSILON", "EMIN", "EMAX", "DELTA_E", "RESTART", "TREVSYM",
          "NKD", "KD", "MASS", "TRISYM", "PREC_EWALD", "CLASSICAL", "BCONNECT", "BORNSYM",
          "VERBOSITY"
    };

    std::vector<std::string> no_defaults{"PREFIX", "MODE", "FCSXML", "NKD", "KD"};
    std::vector<std::string> kdname_v, masskd_v;
    std::map<std::string, std::string> general_var_dict;

    double *masskd = nullptr;
    std::string *kdname = nullptr;

    if (from_stdin) {
        std::cin.ignore();
    } else {
        ifs_input.ignore();
    }

    get_var_dict(input_list, general_var_dict);

    for (auto &no_default: no_defaults) {
        if (general_var_dict.find(no_default) == general_var_dict.end()) {
            exit("parse_general_vars",
                        "The following variable is not found in &general input region: ",
                        no_default.c_str());
        }
    }

    const auto prefix = general_var_dict["PREFIX"];
    auto mode = general_var_dict["MODE"];
    auto file_result = prefix + ".result";

    std::transform(mode.begin(), mode.end(), mode.begin(), toupper);

    const auto fcsinfo = general_var_dict["FCSXML"];
    assign_val(nkd, "NKD", general_var_dict);

    split_str_by_space(general_var_dict["KD"], kdname_v);

    if (kdname_v.size() != nkd) {
        exit("parse_general_vars",
                    "The number of entries for KD is inconsistent with NKD");
    } else {
        allocate(kdname, nkd);
        for (i = 0; i < nkd; ++i) {
            kdname[i] = kdname_v[i];
        }
    }

    if (!general_var_dict["MASS"].empty()) {
        split_str_by_space(general_var_dict["MASS"], masskd_v);

        if (masskd_v.size() != nkd) {
            exit("parse_general_vars",
                        "The number of entries for MASS is inconsistent with NKD");
        } else {
            allocate(masskd, nkd);
            for (i = 0; i < nkd; ++i) {
                masskd[i] = my_cast<double>(masskd_v[i]);
            }
        }
    }

    // Default values

    auto Tmin = 0.0;
    auto Tmax = 1000.0;
    auto dT = 10.0;

    auto emin = 0.0;
    auto emax = 1000.0;
    auto delta_e = 10.0;

    unsigned int nonanalytic = 0;
    auto nsym = 0;
    auto tolerance = 1.0e-6;
    auto printsymmetry = false;
    auto sym_time_reversal = false;
    auto use_triplet_symmetry = true;
    auto classical = false;
    unsigned int band_connection = 0;
    unsigned int bornsym = 0;
    unsigned int verbosity = 1;

    auto prec_ewald = 1.0e-12;

    // if file_result exists in the current directory,
    // restart mode will be automatically turned on.
    auto restart = stat(file_result.c_str(), &st) == 0;

    auto nbands = -1;
    std::string borninfo;
    std::string fc2info;

    auto ismear = -1;
    auto epsilon = 10.0;
    auto na_sigma = 0.1;

    // Assign given values

    assign_val(Tmin, "TMIN", general_var_dict);
    assign_val(Tmax, "TMAX", general_var_dict);
    assign_val(dT, "DT", general_var_dict);

    assign_val(emin, "EMIN", general_var_dict);
    assign_val(emax, "EMAX", general_var_dict);
    assign_val(delta_e, "DELTA_E", general_var_dict);

    assign_val(nsym, "NSYM", general_var_dict);
    assign_val(sym_time_reversal, "TREVSYM", general_var_dict);
    assign_val(tolerance, "TOLERANCE", general_var_dict);
    assign_val(printsymmetry, "PRINTSYM", general_var_dict);

    assign_val(nonanalytic, "NONANALYTIC", general_var_dict);
    assign_val(restart, "RESTART", general_var_dict);

    assign_val(nbands, "NBANDS", general_var_dict);
    assign_val(borninfo, "BORNINFO", general_var_dict);
    assign_val(fc2info, "FC2XML", general_var_dict);

    assign_val(ismear, "ISMEAR", general_var_dict);
    assign_val(epsilon, "EPSILON", general_var_dict);
    assign_val(na_sigma, "NA_SIGMA", general_var_dict);
    assign_val(classical, "CLASSICAL", general_var_dict);
    assign_val(band_connection, "BCONNECT", general_var_dict);
    assign_val(use_triplet_symmetry, "TRISYM", general_var_dict);
    assign_val(bornsym, "BORNSYM", general_var_dict);
    assign_val(verbosity, "VERBOSITY", general_var_dict);

    if (band_connection > 2) {
        exit("parse_general_vars", "BCONNECT-tag can take 0, 1, or 2.");
    }

    if (nonanalytic == 3) {
        assign_val(prec_ewald, "PREC_EWALD", general_var_dict);
        if (prec_ewald <= 0.0 || prec_ewald >= 1.0) {
            exit("parse_general_vars",
                        "PREC_EWALD should be a small positive value.");
        }
        ewald->is_longrange = true;
        ewald->file_longrange = boost::lexical_cast<std::string>(general_var_dict["BORNINFO"]);
        ewald->prec_ewald = prec_ewald;
        ewald->rate_ab = 6.0 / pi;

    } else {
        ewald->is_longrange = false;
    }

    if (nonanalytic > 3) {
        exit("parse_general_vars",
                    "NONANALYTIC-tag can take 0, 1, 2, or 3.");
    }
    if (nonanalytic && borninfo == "") {
        exit("parse_general_vars",
                    "BORNINFO must be specified when NONANALYTIC > 0.");
    }
    // if (nonanalytic == 3) {
    //     if (mode == "SCPH") {
    //         exit("parse_general_vars",
    //                     "Sorry. NONANALYTIC=3 is not supported for MODE = SCPH.");
    //     }
    // }

    // Copy the values to appropriate classes.

    job_title = prefix;
    writes->file_result = file_result;
    phon->mode = mode;
    phon->restart_flag = restart;
    symmetry->nsym = nsym;
    symmetry->tolerance = tolerance;
    symmetry->printsymmetry = printsymmetry;
    symmetry->time_reversal_sym = sym_time_reversal;

    system->Tmin = Tmin;
    system->Tmax = Tmax;
    system->dT = dT;
    system->nkd = nkd;

    allocate(system->symbol_kd, nkd);
    for (i = 0; i < nkd; ++i) {
        system->symbol_kd[i] = kdname[i];
    }
    if (kdname) {
        deallocate(kdname);
    }

    if (!general_var_dict["MASS"].empty()) {
        allocate(system->mass_kd, nkd);
        for (i = 0; i < nkd; ++i) {
            system->mass_kd[i] = masskd[i];
        }
    }
    if (masskd) {
        deallocate(masskd);
    }

    dos->emax = emax;
    dos->emin = emin;
    dos->delta_e = delta_e;

    dynamical->nonanalytic = nonanalytic;
    dynamical->na_sigma = na_sigma;
    dynamical->symmetrize_borncharge = bornsym;
    writes->nbands = nbands;
    dynamical->file_born = borninfo;
    dynamical->band_connection = band_connection;
    integration->epsilon = epsilon;
    fcs_phonon->file_fcs = fcsinfo;
    fcs_phonon->file_fc2 = fc2info;
    fcs_phonon->update_fc2 = !fc2info.empty();
    thermodynamics->classical = classical;
    integration->ismear = ismear;
    anharmonic_core->use_triplet_symmetry = use_triplet_symmetry;

    general_var_dict.clear();
}

void Input::parse_scph_vars()
{
    // Read input parameters in the &scph-field.

    struct stat st{};
    const std::vector<std::string> input_list{
          "KMESH_SCPH", "KMESH_INTERPOLATE", "MIXALPHA", "MAXITER",
          "RESTART_SCPH", "IALGO", "SELF_OFFDIAG", "TOL_SCPH",
          "LOWER_TEMP", "WARMSTART", "BUBBLE"
    };
    std::vector<std::string> no_defaults{"KMESH_SCPH", "KMESH_INTERPOLATE"};
    std::vector<int> kmesh_v, kmesh_interpolate_v;
    std::map<std::string, std::string> scph_var_dict;

    if (from_stdin) {
        std::cin.ignore();
    } else {
        ifs_input.ignore();
    }

    get_var_dict(input_list, scph_var_dict);

    for (auto &no_default: no_defaults) {
        if (scph_var_dict.find(no_default) == scph_var_dict.end()) {
            exit("parse_general_vars",
                        "The following variable is not found in &scph input region: ",
                        no_default.c_str());
        }
    }

    auto file_dymat = this->job_title + ".scph_dymat";

    // Default values

    auto tolerance_scph = 1.0e-10;
    unsigned int maxiter = 1000;
    auto mixalpha = 0.1;
    auto selenergy_offdiagonal = true;
    unsigned int ialgo_scph = 0;
    auto lower_temp = true;
    auto warm_start = true;
    unsigned int bubble = 0;

    // if file_dymat exists in the current directory,
    // restart mode will be automatically turned on for SCPH calculations.

    auto restart_scph = stat(file_dymat.c_str(), &st) == 0;

    // Assign given values

    assign_val(restart_scph, "RESTART_SCPH", scph_var_dict);
    assign_val(maxiter, "MAXITER", scph_var_dict);
    assign_val(mixalpha, "MIXALPHA", scph_var_dict);
    assign_val(selenergy_offdiagonal, "SELF_OFFDIAG", scph_var_dict);
    assign_val(ialgo_scph, "IALGO", scph_var_dict);
    assign_val(tolerance_scph, "TOL_SCPH", scph_var_dict);
    assign_val(lower_temp, "LOWER_TEMP", scph_var_dict);
    assign_val(warm_start, "WARMSTART", scph_var_dict);
    assign_val(bubble, "BUBBLE", scph_var_dict);

    auto str_tmp = scph_var_dict["KMESH_SCPH"];

    if (!str_tmp.empty()) {

        std::istringstream is(str_tmp);

        while (true) {
            str_tmp.clear();
            is >> str_tmp;
            if (str_tmp.empty()) {
                break;
            }
            kmesh_v.push_back(my_cast<unsigned int>(str_tmp));
        }

        if (kmesh_v.size() != 3) {
            exit("parse_general_vars",
                        "The number of entries for KMESH_SCPH has to be 3.");
        }
    } else {
        exit("parse_general_vars",
                    "Please specify KMESH_SCPH for mode = SCPH");
    }

    str_tmp = scph_var_dict["KMESH_INTERPOLATE"];
    if (!str_tmp.empty()) {

        std::istringstream is(str_tmp);

        while (true) {
            str_tmp.clear();
            is >> str_tmp;
            if (str_tmp.empty()) {
                break;
            }
            kmesh_interpolate_v.push_back(my_cast<unsigned int>(str_tmp));
        }

        if (kmesh_interpolate_v.size() != 3) {
            exit("parse_general_vars",
                        "The number of entries for KMESH_INTERPOLATE has to be 3.");
        }
    } else {
        exit("parse_general_vars",
                    "Please specify KMESH_INTERPOLATE for mode = SCPH");
    }

    // Copy the values to appropriate classes.

    for (auto i = 0; i < 3; ++i) {
        scph->kmesh_scph[i] = kmesh_v[i];
        scph->kmesh_interpolate[i] = kmesh_interpolate_v[i];
    }
    scph->mixalpha = mixalpha;
    scph->maxiter = maxiter;
    scph->restart_scph = restart_scph;
    scph->selfenergy_offdiagonal = selenergy_offdiagonal;
    scph->ialgo = ialgo_scph;
    scph->tolerance_scph = tolerance_scph;
    scph->lower_temp = lower_temp;
    scph->warmstart_scph = warm_start;
    scph->bubble = bubble;

    kmesh_v.clear();
    kmesh_interpolate_v.clear();

    scph_var_dict.clear();
}

void Input::parse_analysis_vars(const bool use_default_values)
{
    // Read input parameters in the &analysis field.
    int i;

    std::vector<std::string> input_list{
          "PRINTEVEC", "PRINTXSF", "PRINTVEL", "QUARTIC", "KS_INPUT",
          "REALPART", "ISOTOPE", "ISOFACT",
          "FSTATE_W", "FSTATE_K", "PRINTMSD", "DOS", "PDOS", "TDOS",
          "GRUNEISEN", "NEWFCS", "DELTA_A", "ANIME", "ANIME_CELLSIZE",
          "ANIME_FORMAT", "ANIME_FRAMES", "SPS", "PRINTV3", "PRINTPR",
          "FC2_EWALD", "KAPPA_SPEC", "SELF_W", "UCORR", "SHIFT_UCORR",
          "KAPPA_COHERENT",
          "DIELEC", "SELF_ENERGY", "PRINTV4", "ZMODE", "PROJECTION_AXES"
    };

#ifdef _FE_BUBBLE
    input_list.push_back("FE_BUBBLE");
#endif

    unsigned int cellsize[3] = {1, 1, 1};
    int shift_ucorr[3] = {0, 0, 0};
    double anime_kpoint_double[3] = {0., 0., 0.};

    double *isotope_factor = nullptr;
    std::string ks_input, anime_format;
    std::map<std::string, std::string> analysis_var_dict;
    std::vector<std::string> isofact_v, anime_kpoint, anime_cellsize;
    std::vector<std::string> projection_axes;

    // Default values

    auto print_xsf = false;
    auto print_anime = false;

    auto print_vel = false;
    auto print_evec = false;
    auto print_msd = false;
    auto print_ucorr = false;
    auto anime_frames = 20;

    bool compute_dos = true;
    bool projected_dos = false;
    bool two_phonon_dos = false;
    int scattering_phase_space = 0;
    bool print_gruneisen = false;
    bool print_newfcs = false;
    int print_V3 = 0;
    int print_V4 = 0;
    bool participation_ratio = false;

    auto delta_a = 0.001;

    auto quartic_mode = 0;
    auto calc_realpart = false;
    auto include_isotope = 0;
    auto fstate_omega = false;
    auto fstate_k = false;
    auto bubble_omega = false;

    auto calculate_kappa_spec = 0;

    auto print_fc2_ewald = false;
    auto print_self_consistent_fc2 = false;
    auto calc_FE_bubble = false;
    auto calc_coherent = 0;

    auto calculate_dielectric_constant = 0;
    auto calc_selfenergy = 0;
    auto print_zmode = false;

    auto do_projection = false;

    // Assign values to variables

    if (!use_default_values) {
        get_var_dict(input_list, analysis_var_dict);

        assign_val(print_vel, "PRINTVEL", analysis_var_dict);
        assign_val(print_evec, "PRINTEVEC", analysis_var_dict);
        assign_val(print_msd, "PRINTMSD", analysis_var_dict);
        assign_val(print_ucorr, "UCORR", analysis_var_dict);

        assign_val(compute_dos, "DOS", analysis_var_dict);
        assign_val(projected_dos, "PDOS", analysis_var_dict);
        assign_val(two_phonon_dos, "TDOS", analysis_var_dict);
        assign_val(scattering_phase_space, "SPS", analysis_var_dict);
        assign_val(print_gruneisen, "GRUNEISEN", analysis_var_dict);
        assign_val(print_newfcs, "NEWFCS", analysis_var_dict);
        assign_val(delta_a, "DELTA_A", analysis_var_dict);

        assign_val(quartic_mode, "QUARTIC", analysis_var_dict);
        assign_val(calc_realpart, "REALPART", analysis_var_dict);
        assign_val(include_isotope, "ISOTOPE", analysis_var_dict);
        assign_val(fstate_omega, "FSTATE_W", analysis_var_dict);
        assign_val(fstate_k, "FSTATE_K", analysis_var_dict);
        assign_val(ks_input, "KS_INPUT", analysis_var_dict);
        assign_val(calculate_kappa_spec, "KAPPA_SPEC", analysis_var_dict);
        assign_val(calc_coherent, "KAPPA_COHERENT", analysis_var_dict);
        assign_val(bubble_omega, "SELF_W", analysis_var_dict);
        assign_val(calc_selfenergy, "SELF_ENERGY", analysis_var_dict);

        assign_val(print_xsf, "PRINTXSF", analysis_var_dict);
        assign_val(print_V3, "PRINTV3", analysis_var_dict);
        assign_val(print_V4, "PRINTV4", analysis_var_dict);
        assign_val(participation_ratio, "PRINTPR", analysis_var_dict);
        assign_val(print_fc2_ewald, "FC2_EWALD", analysis_var_dict);
        assign_val(calculate_dielectric_constant, "DIELEC", analysis_var_dict);
        assign_val(print_zmode, "ZMODE", analysis_var_dict);
#ifdef _FE_BUBBLE
        assign_val(calc_FE_bubble, "FE_BUBBLE", analysis_var_dict);
#endif

        if (analysis_var_dict.find("ANIME") == analysis_var_dict.end()) {
            print_anime = false;
        } else {
            print_anime = true;
        }

        if (analysis_var_dict.find("PROJECTION_AXES") != analysis_var_dict.end()) {
            do_projection = true;
        }
    }

    if (include_isotope) {

        if (!analysis_var_dict["ISOFACT"].empty()) {
            split_str_by_space(analysis_var_dict["ISOFACT"], isofact_v);

            if (isofact_v.size() != system->nkd) {
                exit("parse_analysis_vars",
                            "The number of entries for ISOFACT is inconsistent with NKD");
            } else {
                allocate(isotope_factor, system->nkd);
                for (i = 0; i < system->nkd; ++i) {
                    isotope_factor[i] = my_cast<double>(isofact_v[i]);
                }
            }
        }

    }

    if (print_anime) {
        split_str_by_space(analysis_var_dict["ANIME"], anime_kpoint);

        if (anime_kpoint.size() != 3) {
            exit("parse_analysis_vars",
                        "The number of entries for ANIME should be 3.");
        }
        for (i = 0; i < 3; ++i) {
            anime_kpoint_double[i] = my_cast<double>(anime_kpoint[i]);
        }

        split_str_by_space(analysis_var_dict["ANIME_CELLSIZE"], anime_cellsize);

        if (anime_cellsize.size() != 3) {
            exit("parse_analysis_vars",
                        "The number of entries for ANIME_CELLSIZE should be 3.");
        }

        for (i = 0; i < 3; ++i) {
            try {
                cellsize[i] = boost::lexical_cast<unsigned int>(anime_cellsize[i]);
            }
            catch (std::exception &e) {
                std::cout << e.what() << std::endl;
                exit("parse_analysis_vars",
                            "ANIME_CELLSIZE must be a set of positive integers.");
            }
            if (cellsize[i] < 1) {
                exit("parse_analysis_vars",
                            "Please give positive integers for ANIME_CELLSIZE.");
            }
        }

        assign_val(anime_format, "ANIME_FORMAT", analysis_var_dict);
        std::transform(anime_format.begin(), anime_format.end(),
                       anime_format.begin(), toupper);

        if (anime_format.empty()) anime_format = "XYZ";

        if (anime_format != "XSF" && anime_format != "AXSF" && anime_format != "XYZ") {
            exit("parse_analysis_vars", "Invalid ANIME_FORMAT");
        }

        assign_val(anime_frames, "ANIME_FRAMES", analysis_var_dict);
    }

    if (print_ucorr) {
        std::string str_shift_ucorr;
        std::vector<std::string> list_shift_ucorr;
        assign_val(str_shift_ucorr, "SHIFT_UCORR", analysis_var_dict);

        if (!str_shift_ucorr.empty()) {
            split_str_by_space(str_shift_ucorr, list_shift_ucorr);
            if (list_shift_ucorr.size() != 3) {
                exit("parse_analysis_vars",
                            "The number of entries for SHIFT_UCORR must be 3.");
            }

            for (i = 0; i < 3; ++i) {
                try {
                    shift_ucorr[i] = boost::lexical_cast<int>(list_shift_ucorr[i]);
                }
                catch (std::exception &e) {
                    std::cout << e.what() << std::endl;
                    exit("parse_analysis_vars",
                                "SHIFT_UCORR must be an array of integers.");
                }
            }
        }
    }

    if (do_projection) {
        std::string str_projection_axes;
        assign_val(str_projection_axes, "PROJECTION_AXES", analysis_var_dict);
        std::vector<double> direction(3);
        std::vector<std::vector<double>> projection_directions;
        if (!str_projection_axes.empty()) {
            std::vector<std::string> str_projection_each, str_vec;
            boost::split(str_projection_each, str_projection_axes, boost::is_any_of(","));

            if (str_projection_each.size() > 2) {
                warn("parse_analysis_vars",
                            "Too many entries for PROJECTION_AXES. Only the first two will be used.");
            }

            for (i = 0; i < str_projection_each.size(); ++i) {
                split_str_by_space(str_projection_each[i], str_vec);
                if (str_vec.size() != 3) {
                    exit("parse_analysis_vars",
                                "The number of entries for each vector in PROJECTION_AXES must be 3.");
                }
                for (auto j = 0; j < 3; ++j) {
                    try {
                        direction[j] = boost::lexical_cast<double>(str_vec[j]);
                    }
                    catch (std::exception &e) {
                        std::cout << e.what() << std::endl;
                        exit("parse_analysis_vars",
                                    "subset of PROJECTION_AXES must be an array of doubles.");
                    }
                }
                projection_directions.push_back(direction);
            }
        }

        dynamical->set_projection_directions(projection_directions);
    }

    // Copy the values to appropriate classes

    phonon_velocity->print_velocity = print_vel;
    dynamical->print_eigenvectors = print_evec;
    dynamical->participation_ratio = participation_ratio;
//    writes->print_xsf = print_xsf;
//    writes->print_anime = print_anime;
//    writes->print_ucorr = print_ucorr;

    writes->setWriteOptions(print_msd,
                            print_xsf,
                            print_anime,
                            anime_format,
                            anime_frames,
                            cellsize,
                            anime_kpoint_double,
                            print_ucorr,
                            shift_ucorr,
                            print_zmode);

//    if (print_anime) {
//        for (i = 0; i < 3; ++i) {
//            writes->anime_kpoint[i] = my_cast<double>(anime_kpoint[i]);
//            writes->anime_cellsize[i] = cellsize[i];
//        }
//        writes->anime_format = anime_format;
//    }
//
//    writes->print_msd = print_msd;

    dos->compute_dos = compute_dos;
    dos->projected_dos = projected_dos;
    dos->two_phonon_dos = two_phonon_dos;
    dos->scattering_phase_space = scattering_phase_space;

    conductivity->calc_kappa_spec = calculate_kappa_spec;
    conductivity->calc_coherent = calc_coherent;
    anharmonic_core->quartic_mode = quartic_mode;
    dielec->calc_dielectric_constant = calculate_dielectric_constant;

    mode_analysis->ks_input = ks_input;
    mode_analysis->calc_realpart = calc_realpart;
    mode_analysis->calc_fstate_omega = fstate_omega;
    mode_analysis->calc_fstate_k = fstate_k;
    mode_analysis->print_V3 = print_V3;
    mode_analysis->print_V4 = print_V4;
    mode_analysis->spectral_func = bubble_omega;
    mode_analysis->calc_selfenergy = calc_selfenergy;
    isotope->include_isotope = include_isotope;

    gruneisen->print_gruneisen = print_gruneisen;
    gruneisen->print_newfcs = print_newfcs;
    gruneisen->delta_a = delta_a;
    thermodynamics->calc_FE_bubble = calc_FE_bubble;

    ewald->print_fc2_ewald = print_fc2_ewald;

    if (include_isotope) {
        if (!analysis_var_dict["ISOFACT"].empty()) {
            allocate(isotope->isotope_factor, system->nkd);
            for (i = 0; i < system->nkd; ++i) {
                isotope->isotope_factor[i] = isotope_factor[i];
            }
        }
    }
    if (isotope_factor) {
        deallocate(isotope_factor);
    }

    if (phon->mode == "SCPH") {
        scph->print_self_consistent_fc2 = print_self_consistent_fc2;
    }

    analysis_var_dict.clear();
}

void Input::parse_cell_parameter()
{
    // Read the cell parameter

    int i, j;
    double a;
    double lavec_tmp[3][3];
    std::string line;
    std::string line_wo_comment, line_tmp;
    std::vector<std::string> line_vec, line_split;
    std::string::size_type pos_first_comment_tag;

    line_vec.clear();

    if (from_stdin) {

        while (std::getline(std::cin, line)) {

            // Ignore comment region
            pos_first_comment_tag = line.find_first_of('#');

            if (pos_first_comment_tag == std::string::npos) {
                line_wo_comment = line;
            } else {
                line_wo_comment = line.substr(0, pos_first_comment_tag);
            }

            trim_if(line_wo_comment, boost::is_any_of("\t\r\n "));

            if (line_wo_comment.empty()) continue;
            if (is_endof_entry(line_wo_comment)) break;

            line_vec.push_back(line_wo_comment);
        }

    } else {
        while (std::getline(ifs_input, line)) {

            // Ignore comment region
            pos_first_comment_tag = line.find_first_of('#');

            if (pos_first_comment_tag == std::string::npos) {
                line_wo_comment = line;
            } else {
                line_wo_comment = line.substr(0, pos_first_comment_tag);
            }

            trim_if(line_wo_comment, boost::is_any_of("\t\r\n "));

            if (line_wo_comment.empty()) continue;
            if (is_endof_entry(line_wo_comment)) break;

            line_vec.push_back(line_wo_comment);
        }
    }

    if (line_vec.size() != 4) {
        exit("parse_cell_parameter",
                    "Too few or too much lines for the &cell field.\n \
                                            The number of valid lines for the &cell field should be 4.");
    }

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

        line = line_vec[i];
        split(line_split, line,
              boost::is_any_of("\t "), boost::token_compress_on);

        if (i == 0) {
            // Lattice factor a
            if (line_split.size() == 1) {
                a = boost::lexical_cast<double>(line_split[0]);
            } else {
                exit("parse_cell_parameter",
                            "Unacceptable format for &cell field.");
            }

        } else {
            // Lattice vectors a1, a2, a3
            if (line_split.size() == 3) {
                for (j = 0; j < 3; ++j) {
                    lavec_tmp[j][i - 1] = boost::lexical_cast<double>(line_split[j]);
                }
            } else {
                exit("parse_cell_parameter",
                            "Unacceptable format for &cell field.");
            }
        }
    }

    for (i = 0; i < 3; ++i) {
        for (j = 0; j < 3; ++j) {
            system->lavec_p[i][j] = a * lavec_tmp[i][j];
        }
    }
}

void Input::parse_kpoints()
{
    // Read the settings in the &kpoint field.

    int kpmode;
    std::string line, line_wo_comment, str_tmp;
    std::vector<std::string> kpelem, line_vec;
    std::string::size_type pos_first_comment_tag;

    line_vec.clear();

    if (from_stdin) {

        while (std::getline(std::cin, line)) {

            // Ignore comment region
            pos_first_comment_tag = line.find_first_of('#');

            if (pos_first_comment_tag == std::string::npos) {
                line_wo_comment = line;
            } else {
                line_wo_comment = line.substr(0, pos_first_comment_tag);
            }

            trim_if(line_wo_comment, boost::is_any_of("\t\r\n "));

            if (line_wo_comment.empty()) continue;
            if (is_endof_entry(line_wo_comment)) break;

            line_vec.push_back(line_wo_comment);
        }

    } else {
        while (std::getline(ifs_input, line)) {

            // Ignore comment region
            pos_first_comment_tag = line.find_first_of('#');

            if (pos_first_comment_tag == std::string::npos) {
                line_wo_comment = line;
            } else {
                line_wo_comment = line.substr(0, pos_first_comment_tag);
            }

            trim_if(line_wo_comment, boost::is_any_of("\t\r\n "));

            if (line_wo_comment.empty()) continue;
            if (is_endof_entry(line_wo_comment)) break;

            line_vec.push_back(line_wo_comment);
        }
    }

    for (int i = 0; i < line_vec.size(); ++i) {
        line = line_vec[i];
        split(kpelem, line, boost::is_any_of("\t "), boost::token_compress_on);

        if (i == 0) {
            // kpmode
            if (kpelem.size() == 1) {
                try {
                    kpmode = boost::lexical_cast<int>(kpelem[0]);
                }
                catch (std::exception &e) {
                    std::cout << e.what() << std::endl;
                    exit("parse_kpoints",
                                "KPMODE must be an integer. [0, 1, or 2]");
                }

                if (!(kpmode >= 0 && kpmode <= 3)) {
                    exit("parse_kpoints",
                                "KPMODE must be 0, 1, or 2.");
                }

            } else {
                exit("parse_kpoints",
                            "Unacceptable format for the &kpoint field.");
            }

        } else {
            // Read each entry of kpoint

            if (kpmode == 0 && kpelem.size() != 3) {
                exit("parse_kpoints",
                            "The number of columns must be 3 when KPMODE = 0");
            }
            if (kpmode == 1 && kpelem.size() != 9) {
                exit("parse_kpoints",
                            "The number of columns must be 9 when KPMODE = 1");
            }
            if (kpmode == 2 && kpelem.size() != 3) {
                exit("parse_kpoints",
                            "The number of columns must be 3 when KPMODE = 2");
            }
            if (kpmode == 3 && kpelem.size() != 8) {
                exit("parse_kpoints",
                            "The number of columns must be 8 when KPMODE = 3");
            }

            kpoint->kpInp.emplace_back(kpelem);
        }
    }

    kpoint->kpoint_mode = kpmode;
}

int Input::locate_tag(const std::string &key)
{
    int ret = 0;
    std::string line, line2;

    if (from_stdin) {

        // The following two lines do nothing when MPI version is executed.
        // I don't know why this happens.
        std::cin.clear();
        std::cin.seekg(0, std::ios_base::beg);

        while (std::cin >> line) {
#ifdef _USE_BOOST
            boost::to_lower(line);
            boost::trim(line);
#else
            std::transform(line.begin(), line.end(), line.begin(), tolower);
            line2 = line;
            line = trim(line2);
#endif
            if (line == key) {
                ret = 1;
                break;
            }
        }
        return ret;

    }
    ifs_input.clear();
    ifs_input.seekg(0, std::ios_base::beg);

    while (ifs_input >> line) {
#ifdef _USE_BOOST
        boost::to_lower(line);
        boost::trim(line);
#else
        std::transform(line.begin(), line.end(), line.begin(), tolower);
        line2 = line;
        line = trim(line2);
#endif
        if (line == key) {
            ret = 1;
            break;
        }
    }
    return ret;
}

void Input::get_var_dict(const std::vector<std::string> &input_list,
                         std::map<std::string, std::string> &var_dict)
{
    std::string line, key, val;
    std::string line_wo_comment, line_tmp;
    std::string::size_type pos_first_comment_tag;
    std::vector<std::string> str_entry, str_varval;
    std::set<std::string> keyword_set;

    for (const auto &it: input_list) {
        keyword_set.insert(it);
    }

    var_dict.clear();

    if (from_stdin) {

        while (std::getline(std::cin, line)) {

            // Ignore comment region
            pos_first_comment_tag = line.find_first_of('#');

            if (pos_first_comment_tag == std::string::npos) {
                line_wo_comment = line;
            } else {
                line_wo_comment = line.substr(0, pos_first_comment_tag);
            }
#ifdef _USE_BOOST
            boost::trim_left(line_wo_comment);
#else
            line_tmp = line_wo_comment;
            line_wo_comment = ltrim(line_tmp);
#endif
            if (line_wo_comment.empty()) continue;
            if (is_endof_entry(line_wo_comment)) break;

            // Split the input line by ';'
#ifdef _USE_BOOST
            boost::split(str_entry, line_wo_comment, boost::is_any_of(";"));
#else
            str_entry = my_split(line_wo_comment, ';');
#endif

            for (auto it = str_entry.begin(); it != str_entry.end(); ++it) {

                // Split the input entry by '='
#ifdef _USE_BOOST
                std::string str_tmp = boost::trim_copy(*it);
#else
                std::string str_tmp = trim(*it);
#endif
                if (!str_tmp.empty()) {
#ifdef _USE_BOOST
                    boost::split(str_varval, str_tmp, boost::is_any_of("="));
#else
                    str_varval = my_split(str_tmp, '=');
#endif
                    if (str_varval.size() != 2) {
                        exit("get_var_dict",
                                    "Unacceptable format");
                    }
#ifdef _USE_BOOST
                    key = boost::to_upper_copy(boost::trim_copy(str_varval[0]));
                    val = boost::trim_copy(str_varval[1]);
#else
                    key = trim(str_varval[0]);
                    std::transform(key.begin(), key.end(), key.begin(), toupper);
                    val = trim(str_varval[1]);
#endif
                    if (keyword_set.find(key) == keyword_set.end()) {
                        std::cout << "Could not recognize the variable " << key << std::endl;
                        exit("get_var_dict",
                                    "Invalid variable found");
                    }

                    if (var_dict.find(key) != var_dict.end()) {
                        std::cout << "Variable " << key
                                  << " appears twice in the input file." << std::endl;
                        exit("get_var_dict",
                                    "Redundant input parameter");
                    }

                    // If everything is OK, add the variable and the corresponding value
                    // to the dictionary.

                    var_dict.insert(std::map<std::string, std::string>::value_type(key, val));
                }
            }
        }
    } else {

        while (std::getline(ifs_input, line)) {

            // Ignore comment region
            pos_first_comment_tag = line.find_first_of('#');

            if (pos_first_comment_tag == std::string::npos) {
                line_wo_comment = line;
            } else {
                line_wo_comment = line.substr(0, pos_first_comment_tag);
            }
#ifdef _USE_BOOST
            boost::trim_left(line_wo_comment);
#else
            line_tmp = line_wo_comment;
            line_wo_comment = ltrim(line_tmp);
#endif
            if (line_wo_comment.empty()) continue;
            if (is_endof_entry(line_wo_comment)) break;

            // Split the input line by ';'
#ifdef _USE_BOOST
            boost::split(str_entry, line_wo_comment, boost::is_any_of(";"));
#else
            str_entry = my_split(line_wo_comment, ';');
#endif
            for (auto &it: str_entry) {

                // Split the input entry by '='

#ifdef _USE_BOOST
                std::string str_tmp = boost::trim_copy(*it);
#else
                std::string str_tmp = trim(it);
#endif
                if (!str_tmp.empty()) {
#ifdef _USE_BOOST
                    boost::split(str_varval, str_tmp, boost::is_any_of("="));
#else
                    str_varval = my_split(str_tmp, '=');
#endif

                    if (str_varval.size() != 2) {
                        exit("get_var_dict",
                                    "Unacceptable format");
                    }

#ifdef _USE_BOOST
                    key = boost::to_upper_copy(boost::trim_copy(str_varval[0]));
                    val = boost::trim_copy(str_varval[1]);
#else
                    key = trim(str_varval[0]);
                    std::transform(key.begin(), key.end(), key.begin(), toupper);
                    val = trim(str_varval[1]);
#endif

                    if (keyword_set.find(key) == keyword_set.end()) {
                        std::cout << "Could not recognize the variable "
                                  << key << std::endl;
                        exit("get_var_dict",
                                    "Invalid variable found");
                    }

                    if (var_dict.find(key) != var_dict.end()) {
                        std::cout << "Variable " << key
                                  << " appears twice in the input file." << std::endl;
                        exit("get_var_dict",
                                    "Redundant input parameter");
                    }

                    // If everything is OK, add the variable and the corresponding value
                    // to the dictionary.

                    var_dict.insert(std::map<std::string, std::string>::value_type(key, val));
                }
            }
        }

    }
    keyword_set.clear();
}

bool Input::is_endof_entry(const std::string &str) const
{
    return str[0] == '/';
}

void Input::split_str_by_space(const std::string &str,
                               std::vector<std::string> &str_vec) const
{
    std::string str_tmp;
    std::istringstream is(str);

    str_vec.clear();

    while (1) {
        str_tmp.clear();
        is >> str_tmp;
        if (str_tmp.empty()) {
            break;
        }
        str_vec.push_back(str_tmp);
    }
    str_tmp.clear();
}

template<typename T>
void Input::assign_val(T &val,
                       const std::string &key,
                       std::map<std::string, std::string> dict)
{
    // Assign a value to the variable "key" using the boost::lexica_cast.

    if (!dict[key].empty()) {
        try {
            val = boost::lexical_cast<T>(dict[key]);
        }
        catch (std::exception &e) {
            std::cout << e.what() << std::endl;
            std::string str_tmp = "Invalid entry for the " + key + " tag.\n";
            str_tmp += " Please check the input value.";
            exit("assign_val", str_tmp.c_str());
        }
    }
}

template<typename T_to, typename T_from>
T_to Input::my_cast(T_from const &x)
{
    std::stringstream ss;
    T_to ret;

    ss << x;
    ss >> ret;

    return ret;
}


/*
===============================================================================
   This is a re-implementation of TM-align algorithm in C/C++. The code was
   is written by Jianyi Yang and later updated by Jianjie Wu at The Yang Zhang
   lab, Department of Computational Medicine and Bioinformatics, University of
   Michigan, 100 Washtenaw Avenue, Ann Arbor, MI 48109-2218. Please report bugs
   and questions to jianjiew@umich.edu or zhng@umich.edu

   DISCLAIMER:
     Permission to use, copy, modify, and distribute this program for
     any purpose, with or without fee, is hereby granted, provided that
     the notices on the head, the reference information, and this
     copyright notice appear in all copies or substantial portions of
     the Software. It is provided "as is" without express or implied
     warranty.
   *************** updating history ********************************
   2012/01/24: A C/C++ code of TM-align was constructed by Jianyi Yang
   2016/05/21: Several updates of this program were made by Jianjie Wu, including
              (1) fixed several compiling bugs
	      (2) made I/O of C/C++ version consistent with the Fortran version
	      (3) added outputs including full-atom and ligand structures
	      (4) added options of '-i', '-I' and '-m'
   2016/05/25: fixed a bug on PDB file reading
===============================================================================
*/
/* 2017/10 modified by RMR for high throughput running */

#include "basic_define.h"
#include <iomanip>	//for setprecision in double printout
#include <unistd.h>

#include <boost/algorithm/string.hpp>	//to split string

char version[20];                          //version

//global variables
double D0_MIN;                             //for d0
double Lnorm;                              //normalization length
double score_d8, d0, d0_search, dcu0;      //for TMscore search
double **score;            			       //Input score table for dynamic programming
bool   **path;                             //for dynamic programming
double **val;                              //for dynamic programming
int    xlen, ylen, minlen;                 //length of proteins
int tempxlen, tempylen, tempMinlen;
double **xa, **ya;                         //for input vectors xa[0...xlen-1][0..2], ya[0...ylen-1][0..2]
                                           //in general, ya is regarded as native structure --> superpose xa onto ya
int    *xresno, *yresno;                   //residue numbers, used in fragment gapless threading
double **xtm, **ytm;                       //for TMscore search engine
double **xt;                               //for saving the superposed version of r_1 or xtm
char   *seqx, *seqy;                       //for the protein sequence
int    *secx, *secy;                       //for the secondary structure
double **r1, **r2;                         //for Kabsch rotation
double t[3], u[3][3];                      //Kabsch translation vector and rotation matrix

int atomxlen, atomylen;        // length of atoms
int *ia1, *ia2;                // ATOM indices, used for display
char  **aa1, **aa2;           // "N", or "CA", or "C"
double **xyza1, **xyza2;      // for input vectors xa[0...xlen-1][0..2], ya[0...ylen-1][0..2], just for display
char **ra1, **ra2;           // for the protein sequence
int  *ir1, *ir2;             // residue numbers, used in fragment gapless threading

char sequence[10][MAXLEN];// get value from alignment file
double TM_ali, rmsd_ali;// TMscore and rmsd from standard_TMscore func,
int L_ali;// Aligned length from standard_TMscore func,

char *ins1, *ins2, *ains1, *ains2;// flag characters for data read from PDB file, which begins with "ATOM", and locates at s(27), usually are spaces
int **nres1, **nres2;// number of atoms, nres1(i,j): the number of atoms for ith residue, j usually is 32 for a space

//argument variables
char out_reg[MAXLEN];
double Lnorm_ass, Lnorm_d0, d0_scale, d0A, d0B, d0u, d0a;
bool o_opt, a_opt, u_opt, d_opt, v_opt;
bool i_opt;// flags for -i, with user given initial alignment file
bool m_opt;// flags for -m, output rotation matrix
bool I_opt;// flags for -I, stick to user given initial alignment file

double TM3, TM4, TM5;

using namespace std;

#include "basic_fun.h"
#include "NW.h"
#include "Kabsch.h"
#include "TMalign.h"

void print_help(char *arg)
{

	cout <<endl;
	cout << " ***************************************************************************************************" << endl
		 << " * TM-align (Version "<< version <<"): An algorithm for protein structure alignment and comparison        *" << endl
		 << " * Based on statistics:                                                                            *" << endl
		 << " *          0.0 < TM-score < 0.30, random structural similarity                                    *" << endl
		 << " *          0.5 < TM-score < 1.00, in about the same fold                                          *" << endl
		 << " * Reference: Y Zhang and J Skolnick, Nucl Acids Res 33, 2302-9 (2005)                             *" << endl
		 << " * Please email your comments and suggestions to Yang Zhang (zhng@umich.edu)                       *" << endl
		 << " ** Modified by Rostam Razban (10/2017) for high-throughput jobs                                   *" << endl
		 << " ** automatically accesses pdb files for respective organism in proteomevis_scripts repository     *" << endl
		 << " ** for unadultered version, download from Yang Zhang Group Website                                *" << endl
		 << " ***************************************************************************************************" << endl;
	cout << endl
		 << " Usage: ." << arg << " [Options] (run in valid organism directory)" << endl << endl
		 << " Options:" << endl
		 << "       -h    print this help" << endl << endl
		 << "       --all    run all pdb files in 0-identify_structure/3-length_check/'organism'/seq2struc.txt" << endl << endl
		 << "       --extra    only run for extra pdb files in extra.txt (generate extra.txt file by running setup_extra_file.py)" << endl << endl
		 << "       --EXTRA    only run for extra pdb files for yeast_ecoli (need to still run --extra as well)" << endl << endl
		 << "       (Options -u, -a, -d -o won't change the final structure alignment)" << endl << endl
		 << " Example usages:" << endl
		 << "        "<< arg <<" --all" << endl
		 << "        "<< arg <<" --extra" << endl;

  exit(EXIT_SUCCESS);

}	//worth adding option to compute list? or maybe faster way is to use GPU. do list cuz talkes time to parse initiate program and parse output

void parameter_set4search(int xlen, int ylen)
{
	//parameter initilization for searching: D0_MIN, Lnorm, d0, d0_search, score_d8
	D0_MIN=0.5;
	dcu0=4.25;                       //update 3.85-->4.25

	Lnorm=getmin(xlen, ylen);        //normaliz TMscore by this in searching
    if(Lnorm<=19)                    //update 15-->19
    {
        d0=0.168;                   //update 0.5-->0.168
    }
    else
    {
        d0=(1.24*pow((Lnorm*1.0-15), 1.0/3)-1.8);
    }
	D0_MIN=d0+0.8;              //this should be moved to above
    d0=D0_MIN;                  //update: best for search

	d0_search=d0;
	if(d0_search>8) d0_search=8;
	if(d0_search<4.5) d0_search=4.5;

    score_d8=1.5*pow(Lnorm*1.0, 0.3)+3.5; //remove pairs with dis>d8 during search & final
}

void parameter_set4final(double len)
{
	D0_MIN=0.5;

	Lnorm=len;            //normaliz TMscore by this in searching
    if(Lnorm<=21)
    {
        d0=0.5;
    }
    else
    {
        d0=(1.24*pow((Lnorm*1.0-15), 1.0/3)-1.8);
    }
    if(d0<D0_MIN) d0=D0_MIN;

	d0_search=d0;
	if(d0_search>8) d0_search=8;
	if(d0_search<4.5) d0_search=4.5;

}

void parameter_set4scale(int len, double d_s)
{

	d0=d_s;
	Lnorm=len;            //normaliz TMscore by this in searching

	d0_search=d0;
	if(d0_search>8) d0_search=8;
	if(d0_search<4.5) d0_search=4.5;

}

vector<string> get_pdb_chain_list(string xread_pdb)
{
	ifstream myfile;
	vector<string> pdb_chain_list;
	myfile.open(xread_pdb.c_str());
	string firstLine;
	getline(myfile, firstLine);
	if( !(myfile.is_open()) )
	{
	std::cout << "Error Opening File";
	std::exit(0);
	}

	std::vector<std::string> words;
	boost::split(words, firstLine, boost::is_any_of("\t"), boost::token_compress_on);

	int pdb_i;	//unable to get std:find() to work
	for( int a = 0; a < words.size(); a = a + 1 ) {
		if (words[a]=="pdb") {
			pdb_i = a;
			break;
		}
	}

	std::string line;
	while( myfile.good() )
	{
		std::getline( myfile, line);
		std::stringstream ss(line);
		std::string word;
		int count=0;
		while (std::getline(ss,word,'\t'))
		{
//			std::cout << "Word: " << word << std::endl;
//			std::cout << count << std::endl;
			if (count==pdb_i){
				pdb_chain_list.push_back(word);
				break;
			}
			count++;
		}
	}
	// remove duplicates from list
	sort( pdb_chain_list.begin(), pdb_chain_list.end() );
	pdb_chain_list.erase( unique( pdb_chain_list.begin(), pdb_chain_list.end() ), pdb_chain_list.end() );
	return pdb_chain_list;
}

int main(int argc, char *argv[])
{
	strcpy(version, "20160521");

    if (argc < 2)
    {
        print_help(argv[0]);
    }

	clock_t t1, t2;
	t1 = clock();

    /*********************************************************************************/
	/*                                get argument                                   */
    /*********************************************************************************/
    char xname[MAXLEN], yname[MAXLEN],  Lnorm_ave[MAXLEN];
	bool A_opt, B_opt, h_opt=false;
	A_opt = B_opt = o_opt = a_opt = u_opt = d_opt = v_opt = false;
	i_opt = false;// set -i flag to be false
	m_opt = false;// set -m flag to be false
	char fname_lign[MAXLEN] = "";
	char fname_matrix[MAXLEN] = "";// set names to ""
	I_opt = false;// set -I flag to be false
	bool extra_opt = false;
	bool extra_ecoli = false;

	int nameIdx = 0;	//no longer needed
	std::string xread_pdb;
	std::string yread_pdb;
	std::string pre_xname;
	std::string pre_yname;

//	char organism[MAXLEN];

	for(int i = 1; i < argc; i++)
	{
		if ( !strcmp(argv[i],"-o") && i < (argc-1) )
		{
			strcpy(out_reg, argv[i + 1]);      o_opt = true; i++;
		}
		else if ( !strcmp(argv[i],"-u") && i < (argc-1) )
		{
			Lnorm_ass = atof(argv[i + 1]); u_opt = true; i++;
		}
		else if ( !strcmp(argv[i],"-a") && i < (argc-1) )
		{
			strcpy(Lnorm_ave, argv[i + 1]);     a_opt = true; i++;
		}	//set a_opt to true always
		else if ( !strcmp(argv[i],"-d") && i < (argc-1) )
		{
			d0_scale = atof(argv[i + 1]); d_opt = true; i++;
		}
		else if ( !strcmp(argv[i],"-v") )
		{
			v_opt = true;
		}
		else if ( !strcmp(argv[i],"-h") )
		{
			h_opt = true;
		}
		else if ( !strcmp(argv[i],"-i") && i < (argc-1) )
		{
			strcpy(fname_lign, argv[i + 1]);      i_opt = true; i++;
		}
		else if (!strcmp(argv[i], "-m") && i < (argc-1) )
		{
			strcpy(fname_matrix, argv[i + 1]);      m_opt = true; i++;
		}// get filename for rotation matrix
		else if (!strcmp(argv[i], "-I") && i < (argc-1) )
		{
			strcpy(fname_lign, argv[i + 1]);      I_opt = true; i++;
		}
		else if ( !strcmp(argv[i],"--extra") || !strcmp(argv[i],"--EXTRA"))
		{
			extra_opt = true;
			if (!strcmp(argv[i],"--EXTRA")){
				extra_ecoli = true;
			}
		}
		else{}
	}

	std::string cwd = getcwd(NULL, 0);	//organism determined from the directory in which the program is called
	std::size_t found = cwd.find_last_of("/\\");
	std::string organism = cwd.substr(found+1);

	std::string dir_read = "../../../0-identify_structure/3-length_check/";
	std::string dir_pdb = "../../../0-identify_structure/2-get_pdb_chain/";
	if (organism == "yeast" || organism == "ecoli")
	{
		yread_pdb = dir_read + organism + "/seq2struc.txt" ;
		pre_xname = dir_pdb + organism + "/" ;
		pre_yname = pre_xname;
		if (extra_opt){
			xread_pdb = "extra.txt";
		}
		else {
			xread_pdb = yread_pdb;
		}
	}
	else if (organism == "yeast_ecoli")
	{
		if (not extra_opt){
			xread_pdb = dir_read + "yeast" + "/seq2struc.txt" ;
			yread_pdb = dir_read + "ecoli" + "/seq2struc.txt" ;
		}
		else if (extra_ecoli){
			xread_pdb = dir_read + "yeast" + "/seq2struc.txt" ;
			yread_pdb = "../ecoli/extra.txt";
		}
		else if (not extra_ecoli){
			xread_pdb = "../yeast/extra.txt";
			yread_pdb = "extra.txt" ;
		}
		else{}

		pre_xname = dir_pdb + "yeast/";
		pre_yname = dir_pdb + "ecoli/";
	}
	else {
		cout << "Make sure to run in valid organism directory" << endl;
		print_help(argv[0]);
	}

	if(!B_opt || !A_opt)
	{

		if( h_opt )
		{
			print_help(argv[0]);
		}

		if(v_opt)
		{
			cout <<endl;
			cout << " *****************************************************************************" << endl
				 << " * TM-align (Version "<< version <<"): A protein structural alignment algorithm     *" << endl
				 << " * Reference: Y Zhang and J Skolnick, Nucl Acids Res 33, 2302-9 (2005)       *" << endl
				 << " * Please email your comments and suggestions to Yang Zhang (zhng@umich.edu) *" << endl
				 << " *****************************************************************************" << endl;
			exit(EXIT_FAILURE);
		}
	}
/*
	if( !B_opt )
	{
		cout << "Please provide structure B" << endl;
		exit(EXIT_FAILURE);
	}
	if( !A_opt )
	{
		cout << "Please provide structure A" << endl;
		exit(EXIT_FAILURE);
	}
*/

	if( a_opt )
	{
		if(!strcmp(Lnorm_ave, "T"))
		{
		}
		else if(!strcmp(Lnorm_ave, "F"))
		{
			a_opt=false;
		}
		else
		{
			cout << "Wrong value for option -a!  It should be T or F" << endl;
			exit(EXIT_FAILURE);
		}
	}
	if( u_opt )
	{
		if(Lnorm_ass<=0)
		{
			cout << "Wrong value for option -u!  It should be >0" << endl;
			exit(EXIT_FAILURE);
		}
	}
	if( d_opt )
	{
		if(d0_scale<=0)
		{
			cout << "Wrong value for option -d!  It should be >0" << endl;
			exit(EXIT_FAILURE);
		}
	}
	////// read initial alignment file from 'alignment.txt' //////
	string basename = string(argv[0]);
	int idx = basename.find_last_of("\\");
	basename = basename.substr(0, idx + 1);
	if (i_opt || I_opt)// Ask TM-align to start with an alignment,specified in fasta file 'align.txt'
	{
		if (fname_lign == "")
		{
			cout << "Please provide a file name for option -i!" << endl;
			exit(EXIT_FAILURE);
		}
		// open alignment file
		int n_p = 0;// number of structures in alignment file
		string line;

		string fullpath = basename + fname_lign;
		char path1[1000];
		strcpy(path1, fullpath.c_str());
		ifstream fileIn(path1);
		if (fileIn.is_open())
		{
			bool bContinue = true;
			while (fileIn.good() && bContinue)
			{
				getline(fileIn, line);
				if (line.compare(0, 1, ">") == 0)// Flag for a new structure
				{
					strcpy(sequence[n_p], "");
					n_p++;
					if (n_p > 2)
						bContinue = false;
				}
				else// Read data
				{
					if (n_p > 0 && line!="")
					{
						strcat(sequence[n_p-1], line.c_str());
					}
				}
			}
			fileIn.close();
		}
		else
		{
			cout << "\nAlignment file does not exist.\n";
			exit(EXIT_FAILURE);
		}

		if (n_p < 2)
		{
			cout << "\nERROR: Fasta format is wrong, two proteins should be included.\n";
			exit(EXIT_FAILURE);
		}
		else
		{
			if (strlen(sequence[0]) != strlen(sequence[1]))
			{
				cout << "\nWarning: FASTA format may be wrong, the length in alignment should be equal respectively to the aligned proteins.\n";
				exit(EXIT_FAILURE);
			}
		}
	}

	if (m_opt)// Output TM - align rotation matrix: matrix.txt
	{
		if (fname_matrix == "")
		{
			cout << "Please provide a file name for option -m!" << endl;
			exit(EXIT_FAILURE);
		}
	}

	a_opt = true;	//always calculate avg TM

	vector<string> xpdb_chain_list = get_pdb_chain_list(xread_pdb);
	vector<string> ypdb_chain_list = get_pdb_chain_list(yread_pdb);

	ofstream outfile("output.txt");
	outfile << "pdb1\tpdb2\tTM\tSID\tTM1\tTM2\tsid\tnal\talign1\talign2\n";
	for( int a = 0; a < xpdb_chain_list.size(); a = a + 1 ) {
		strcpy(xname, pre_xname.c_str());
		strcat (xname, xpdb_chain_list[a].c_str());
		strcat (xname, ".pdb");

	for( int b = 0; b < ypdb_chain_list.size(); b = b + 1 ) {
		strcpy(yname, pre_yname.c_str());
		strcat (yname, ypdb_chain_list[b].c_str());
		strcat (yname, ".pdb");

		vector<string>::iterator it_y_in_x = std::find(xpdb_chain_list.begin(), xpdb_chain_list.end(), ypdb_chain_list[b]);
		if( it_y_in_x != xpdb_chain_list.end()) {
			vector<string>::iterator it_x_in_y = std::find(ypdb_chain_list.begin(), ypdb_chain_list.end(), xpdb_chain_list[a]);
			int pos_x_in_y = distance(ypdb_chain_list.begin(), it_x_in_y);
			if (pos_x_in_y <= b){
				continue;
			}
		}

//		cout << xname << " " << yname << endl;//	find which structure giving trouble

    /*********************************************************************************/
	/*                                load data                                      */
    /*********************************************************************************/

    load_PDB_allocate_memory(xname, yname);

    /*********************************************************************************/
	/*                                parameter set                                  */
    /*********************************************************************************/
	parameter_set4search(xlen, ylen);          //please set parameters in the function
    int simplify_step     = 40;               //for similified search engine
    int score_sum_method  = 8;                //for scoring method, whether only sum over pairs with dis<score_d8

	int i;
    int *invmap0          = new int[ylen+1];
    int *invmap           = new int[ylen+1];
    double TM, TMmax=-1;
	for(i=0; i<ylen; i++)
	{
		invmap0[i]=-1;
	}

	double ddcc=0.4;
	if(Lnorm <= 40) ddcc=0.1;   //Lnorm was setted in parameter_set4search
	double local_d0_search = d0_search;

	//*********************************************************************************//
	//                  get initial alignment from user's input:                       //
	//                  Stick to the initial alignment                                 //
	//*********************************************************************************//
	char dest[1000];
	bool bAlignStick = false;
	if (I_opt)// if input has set parameter for "-I"
	{
		for (int j = 1; j < ylen; j++)// Set aligned position to be "-1"
			invmap[j] = -1;

		int i1 = -1;// in C version, index starts from zero, not from one
		int i2 = -1;
		int L1 = strlen(sequence[0]);
		int L2 = strlen(sequence[1]);
		int L = min(L1, L2);// Get positions for aligned residues
		for (int kk1 = 0; kk1 < L; kk1++)
		{
			if (sequence[0][kk1] != '-')
				i1++;
			if (sequence[1][kk1] != '-')
			{
				i2++;
				if (i2 >= ylen || i1 >= xlen)
					kk1 = L;
				else
				{
					if (sequence[0][kk1] != '-')
					{
						invmap[i2] = i1;
					}
				}
			}
		}

		//--------------- 2. Align proteins from original alignment
		double prevD0_MIN = D0_MIN;// stored for later use
		int prevLnorm = Lnorm;
		double prevd0 = d0;
		TM_ali = standard_TMscore(xa, ya, xlen, ylen, invmap, L_ali, rmsd_ali);
		D0_MIN = prevD0_MIN;
		Lnorm = prevLnorm;
		d0 = prevd0;
		TM = detailed_search_standard(xa, ya, xlen, ylen, invmap, t, u, 40, 8, local_d0_search, true);
		if (TM > TMmax)
		{
			TMmax = TM;
			for (i = 0; i<ylen; i++)
				invmap0[i] = invmap[i];
		}
		bAlignStick = true;
	}

    /*********************************************************************************/
	/*         get initial alignment with gapless threading                          */
    /*********************************************************************************/
	if (!bAlignStick)
	{
		get_initial(xa, ya, xlen, ylen, invmap0);
		//find the max TMscore for this initial alignment with the simplified search_engin
		TM = detailed_search(xa, ya, xlen, ylen, invmap0, t, u, simplify_step, score_sum_method, local_d0_search);
		if (TM>TMmax)
		{
			TMmax = TM;
		}
		//run dynamic programing iteratively to find the best alignment
		TM = DP_iter(xa, ya, xlen, ylen, t, u, invmap, 0, 2, 30, local_d0_search);
		if (TM>TMmax)
		{
			TMmax = TM;
			for (int i = 0; i<ylen; i++)
			{
				invmap0[i] = invmap[i];
			}
		}

		/*********************************************************************************/
		/*         get initial alignment based on secondary structure                    */
		/*********************************************************************************/
		get_initial_ss(xa, ya, xlen, ylen, invmap);
		TM = detailed_search(xa, ya, xlen, ylen, invmap, t, u, simplify_step, score_sum_method, local_d0_search);
		if (TM>TMmax)
		{
			TMmax = TM;
			for (int i = 0; i<ylen; i++)
			{
				invmap0[i] = invmap[i];
			}
		}
		if (TM > TMmax*0.2)
		{
			TM = DP_iter(xa, ya, xlen, ylen, t, u, invmap, 0, 2, 30, local_d0_search);
			if (TM>TMmax)
			{
				TMmax = TM;
				for (int i = 0; i<ylen; i++)
				{
					invmap0[i] = invmap[i];
				}
			}
		}

		/*********************************************************************************/
		/*         get initial alignment based on local superposition                    */
		/*********************************************************************************/
		//=initial5 in original TM-align
		if (get_initial5(xa, ya, xlen, ylen, invmap))
		{
			TM = detailed_search(xa, ya, xlen, ylen, invmap, t, u, simplify_step, score_sum_method, local_d0_search);
			if (TM>TMmax)
			{
				TMmax = TM;
				for (int i = 0; i<ylen; i++)
				{
					invmap0[i] = invmap[i];
				}
			}
			if (TM > TMmax*ddcc)
			{
				TM = DP_iter(xa, ya, xlen, ylen, t, u, invmap, 0, 2, 2, local_d0_search);
				if (TM>TMmax)
				{
					TMmax = TM;
					for (int i = 0; i<ylen; i++)
					{
						invmap0[i] = invmap[i];
					}
				}
			}
		}
		else
		{
			cout << endl << endl << "Warning: initial alignment from local superposition fail!" << endl << endl << endl;
		}

		/*********************************************************************************/
		/*    get initial alignment based on previous alignment+secondary structure      */
		/*********************************************************************************/
		//=initial3 in original TM-align
		get_initial_ssplus(xa, ya, xlen, ylen, invmap0, invmap);
		TM = detailed_search(xa, ya, xlen, ylen, invmap, t, u, simplify_step, score_sum_method, local_d0_search);
		if (TM>TMmax)
		{
			TMmax = TM;
			for (i = 0; i<ylen; i++)
			{
				invmap0[i] = invmap[i];
			}
		}
		if (TM > TMmax*ddcc)
		{
			TM = DP_iter(xa, ya, xlen, ylen, t, u, invmap, 0, 2, 30, local_d0_search);
			if (TM>TMmax)
			{
				TMmax = TM;
				for (i = 0; i<ylen; i++)
				{
					invmap0[i] = invmap[i];
				}
			}
		}

		/*********************************************************************************/
		/*        get initial alignment based on fragment gapless threading              */
		/*********************************************************************************/
		//=initial4 in original TM-align
		get_initial_fgt(xa, ya, xlen, ylen, xresno, yresno, invmap);
		TM = detailed_search(xa, ya, xlen, ylen, invmap, t, u, simplify_step, score_sum_method, local_d0_search);
		if (TM>TMmax)
		{
			TMmax = TM;
			for (i = 0; i<ylen; i++)
			{
				invmap0[i] = invmap[i];
			}
		}
		if (TM > TMmax*ddcc)
		{
			TM = DP_iter(xa, ya, xlen, ylen, t, u, invmap, 1, 2, 2, local_d0_search);
			if (TM>TMmax)
			{
				TMmax = TM;
				for (i = 0; i<ylen; i++)
				{
					invmap0[i] = invmap[i];
				}
			}
		}

		//*********************************************************************************//
		//                  get initial alignment from user's input:                       //
		//*********************************************************************************//
		if (i_opt)// if input has set parameter for "-i"
		{
			for (int j = 0; j < ylen; j++)// Set aligned position to be "-1"
				invmap[j] = -1;

			int i1 = -1;// in C version, index starts from zero, not from one
			int i2 = -1;
			int L1 = strlen(sequence[0]);
			int L2 = strlen(sequence[1]);
			int L = min(L1, L2);// Get positions for aligned residues
			for (int kk1 = 0; kk1 < L; kk1++)
			{
				if (sequence[0][kk1] != '-')
					i1++;
				if (sequence[1][kk1] != '-')
				{
					i2++;
					if (i2 >= ylen || i1 >= xlen)
						kk1 = L;
					else
					{
						if (sequence[0][kk1] != '-')
						{
							invmap[i2] = i1;
						}
					}
				}
			}

			//--------------- 2. Align proteins from original alignment
			double prevD0_MIN = D0_MIN;// stored for later use
			int prevLnorm = Lnorm;
			double prevd0 = d0;
			TM_ali = standard_TMscore(xa, ya, xlen, ylen, invmap, L_ali, rmsd_ali);
			D0_MIN = prevD0_MIN;
			Lnorm = prevLnorm;
			d0 = prevd0;

			TM = detailed_search_standard(xa, ya, xlen, ylen, invmap, t, u, 40, 8, local_d0_search, true);
			if (TM > TMmax)
			{
				TMmax = TM;
				for (i = 0; i<ylen; i++)
					invmap0[i] = invmap[i];
			}
			TM = DP_iter(xa, ya, xlen, ylen, t, u, invmap, 0, 2, 30, local_d0_search);// Different from get_initial, get_initial_ss and get_initial_ssplus
			if (TM>TMmax)
			{
				TMmax = TM;
				for (i = 0; i<ylen; i++)
				{
					invmap0[i] = invmap[i];
				}
			}
		}
	}

    //*********************************************************************************//
    //     The alignment will not be changed any more in the following                 //
    //*********************************************************************************//
	//check if the initial alignment is generated approately
	bool flag=false;
	for(i=0; i<ylen; i++)
	{
		if(invmap0[i]>=0)
		{
			flag=true;
			break;
		}
	}
	if(!flag)
	{
		cout << "There is no alignment between the two proteins!" << endl;
		cout << "Program stop with no result!" << endl;
		return 1;
	}

    //*********************************************************************************//
    //       Detailed TMscore search engine  --> prepare for final TMscore             //
    //*********************************************************************************//
    //run detailed TMscore search engine for the best alignment, and
	//extract the best rotation matrix (t, u) for the best alginment
	simplify_step=1;
    score_sum_method=8;
	TM = detailed_search_standard(xa, ya, xlen, ylen, invmap0, t, u, simplify_step, score_sum_method, local_d0_search, false);

	//select pairs with dis<d8 for final TMscore computation and output alignment
	int n_ali8, k=0;
	int n_ali=0;
	int *m1, *m2;
	double d;
	m1=new int[xlen]; //alignd index in x
	m2=new int[ylen]; //alignd index in y
	do_rotation(xa, xt, xlen, t, u);
	k=0;
    for(int j=0; j<ylen; j++)
    {
        i=invmap0[j];
        if(i>=0)//aligned
        {
			n_ali++;
            d=sqrt(dist(&xt[i][0], &ya[j][0]));
			if (d <= score_d8 || (I_opt == true))
			{
				m1[k]=i;
				m2[k]=j;

				xtm[k][0]=xa[i][0];
                xtm[k][1]=xa[i][1];
                xtm[k][2]=xa[i][2];

                ytm[k][0]=ya[j][0];
                ytm[k][1]=ya[j][1];
                ytm[k][2]=ya[j][2];

				r1[k][0] = xt[i][0];
				r1[k][1] = xt[i][1];
				r1[k][2] = xt[i][2];
				r2[k][0] = ya[j][0];
				r2[k][1] = ya[j][1];
				r2[k][2] = ya[j][2];

				k++;
			}
		}
	}
	n_ali8=k;

	double rmsd0 = 0.0;
	Kabsch(r1, r2, n_ali8, 0, &rmsd0, t, u);// rmsd0 is used for final output, only recalculate rmsd0, not t & u
	rmsd0 = sqrt(rmsd0 / n_ali8);

    //*********************************************************************************//
    //                               Final TMscore                                     //
    //                     Please set parameters for output                            //
    //*********************************************************************************//
	double rmsd;
	double t0[3], u0[3][3];
	double TM1, TM2;
	double d0_out=5.0;
    simplify_step=1;
    score_sum_method=0;

	double d0_0, TM_0;
	double Lnorm_0=ylen;

	//normalized by length of structure A
	parameter_set4final(Lnorm_0);
	d0A=d0;
	d0_0=d0A;
	local_d0_search = d0_search;
	TM1 = TMscore8_search(xtm, ytm, n_ali8, t0, u0, simplify_step, score_sum_method, &rmsd, local_d0_search);
	TM_0 = TM1;

	//normalized by length of structure B
	parameter_set4final(xlen+0.0);
	d0B=d0;
	local_d0_search = d0_search;
	TM2 = TMscore8_search(xtm, ytm, n_ali8, t, u, simplify_step, score_sum_method, &rmsd, local_d0_search);

	if(a_opt)
	{
		//normalized by average length of structures A, B
		Lnorm_0=(xlen+ylen)*0.5;
		parameter_set4final(Lnorm_0);
		d0a=d0;
		d0_0=d0a;
		local_d0_search = d0_search;

		TM3 = TMscore8_search(xtm, ytm, n_ali8, t0, u0, simplify_step, score_sum_method, &rmsd, local_d0_search);
		TM_0=TM3;
	}
	if(u_opt)
	{
		//normalized by user assigned length
		parameter_set4final(Lnorm_ass);
		d0u=d0;
		d0_0=d0u;
		Lnorm_0=Lnorm_ass;
		local_d0_search = d0_search;
		TM4 = TMscore8_search(xtm, ytm, n_ali8, t0, u0, simplify_step, score_sum_method, &rmsd, local_d0_search);
		TM_0=TM4;
	}
	if(d_opt)
	{
		//scaled by user assigned d0
		parameter_set4scale(ylen, d0_scale);
		d0_out=d0_scale;
		d0_0=d0_scale;
		//Lnorm_0=ylen;
		Lnorm_d0=Lnorm_0;
		local_d0_search = d0_search;
		TM5 = TMscore8_search(xtm, ytm, n_ali8, t0, u0, simplify_step, score_sum_method, &rmsd, local_d0_search);
		TM_0=TM5;
	}

    int ali_len=xlen+ylen; //maximum length of alignment
      char *seqM, *seqxA, *seqyA;
      seqM=new char[ali_len];
      seqxA=new char[ali_len];
      seqyA=new char[ali_len];
	int seq_id = output_results(xname, yname, xlen, ylen, t0, u0, TM1, TM2, rmsd0, d0_out, m1, m2, n_ali8, n_ali, TM_0, Lnorm_0, d0_0, fname_matrix, seqM, seqxA, seqyA);	//this is a hack. no longer need full output fxn
	outfile << xpdb_chain_list[a] << "\t" << ypdb_chain_list[b] << "\t";
	outfile << std::setprecision(4) << TM3 << "\t" << (float)seq_id/(float)n_ali8 << "\t" << TM1 << "\t" << TM2 << "\t" << seq_id << "\t" << n_ali8 << "\t" << seqxA << "\t" << seqy << "\n";

	    //*********************************************************************************//
    //                            Done! Free memory                                    //
    //*********************************************************************************//
	free_memory();

    delete [] invmap0;
    delete [] invmap;
	delete [] m1;
	delete [] m2;

}
}
	outfile.close();

    t2 = clock();
    float diff = ((float)t2 - (float)t1)/CLOCKS_PER_SEC;
    printf("\nTotal running time is %5.2f seconds\n", diff);
 	return 0;
}


#ifndef ATM_SUBSTRINGS_HPP
#define ATM_SUBSTRINGS_HPP

#include <cmath>
#include <map>
#include <vector>
#include <boost/iterator/iterator_facade.hpp>
#include <boost/range/begin.hpp>
#include <boost/range/iterator.hpp>
#include <boost/range/sub_range.hpp>
#include <boost/range/value_type.hpp>
#include "sast/sast.hpp"

namespace atm {

template <class RandomAccessRange, class Index>
struct substrings {
    using range_type = RandomAccessRange;
    using char_type = typename boost::range_value<RandomAccessRange>::type;
    using index_type = Index;

protected:
    using sast_type = sast::sast<RandomAccessRange, index_type>;

public:
    struct substr {
        using iterator       = typename boost::range_iterator<RandomAccessRange>::type;
        using const_iterator = typename boost::range_const_iterator<RandomAccessRange>::type;

        index_type pos()           const { return i_->pos(); }
        boost::sub_range<const std::vector<index_type>> allpos() const { return i_->allpos(); }
        index_type length()        const { return i_->length() - ii_; }
        index_type frequency()     const { return i_->frequency(); }
        double     spurity()       const { return parent_->strict_purity(i_, ii_); }
        double     lpurity()       const { return parent_->loose_purity(i_, ii_); }
        double     luniversality() const { return parent_->left_universality(i_, ii_); }
        double     runiversality() const { return parent_->right_universality(i_, ii_); }

        iterator begin() { return boost::begin(parent_->input_) + pos(); }
        iterator end()   { return boost::begin(parent_->input_) + pos() + length(); }
        const_iterator begin() const { return boost::const_begin(parent_->input_) + pos(); }
        const_iterator end()   const { return boost::const_begin(parent_->input_) + pos() + length(); }

        substr(const substrings* parent, typename sast_type::const_iterator i, int ii)
            : parent_(parent), i_(i), ii_(ii)
        {}

    private:
        const substrings* parent_;
        typename sast_type::const_iterator i_;
        int ii_;
    };

private:
    template <class> struct substring_iterator;

public:
    using iterator       = substring_iterator<substr>;
    using const_iterator = substring_iterator<const substr>;

    substrings(const sast_type& sast)
        : sast_(sast),
          input_(sast_.input())
    {}

    iterator begin() { return iterator(this, sast_.begin(), 0); }
    iterator end()   { return iterator(this, sast_.begin() + (sast_.size() - 1), 0); }
    const_iterator begin() const { return const_iterator(this, sast_.begin(), 0); }
    const_iterator end()   const { return const_iterator(this, sast_.begin() + (sast_.size() - 1), 0); }

private:
    template <class Value>
    struct substring_iterator
        : public boost::iterator_facade<
            substring_iterator<Value>,
            Value,
            boost::random_access_traversal_tag,
            Value,
            int>
    {
        substring_iterator()
            : parent_(0), i_(), ii_(-1)
        {}

        substring_iterator(const substrings* parent, typename sast_type::const_iterator i, int ii)
            : parent_(parent), i_(i), ii_(ii)
        {}

        template <class OtherValue>
        substring_iterator(const substring_iterator<OtherValue>& other)
            : parent_(other.parent_), i_(other.i_), ii_(other.ii_)
        {}

        void next_branching() {
            ++i_;
            ii_ = 0;
        }

        void prev_branching() {
            --i_;
            auto j = i_.parent();
            ii_ = (i_->length() - j->length()) - 1;
        }

    private:
        friend class boost::iterator_core_access;
        template <class> friend struct substring_iterator;

        void increment() {
            auto j = i_.parent();
            if (ii_ + 1 < i_->length() - j->length()) {
                ++ii_;
            }
            else {
                next_branching();
            }
        }

        void decrement() {
            if (ii_ - 1 >= 0) {
                --ii_;
            }
            else {
                prev_branching();
            }
        }

        void advance(int n) {
            if (n >= 0) {
                auto j = i_.parent();
                while (ii_ + n >= i_->length() - j->length()) {
                    n -= (i_->length() - j->length()) - ii_;
                    next_branching();
                    j = i_.parent();
                }
                ii_ += n;
            }
            else {
                while (ii_ + n < 0) {
                    n += ii_ + 1;
                    prev_branching();
                }
                ii_ += n;
            }
        }

        int distance_to(const substring_iterator<Value>& other) const {
            int d = 0;
            if (other.i_ - this->i_ > 0 || (other.i_ == this->i_ && other.ii_ >= this->ii_)) {
                auto i = this->i_;
                int ii = this->ii_;
                while (other.i_ - i > 0) {
                    auto j = i.parent();
                    d += (i->length() - j->length()) - ii;
                    ++i;
                    ii = 0;
                }
                d += other.ii_ - ii_;
            }
            else {
                auto i = other.i_;
                int ii = other.ii_;
                while (this->i_ - i > 0) {
                    auto j = i.parent();
                    d += (i->length() - j->length()) - ii;
                    ++i;
                    ii = 0;
                }
                d += other.ii_ - ii_;
                d = -d;
            }
            return d;
        }

        template <class OtherValue>
        bool equal(const substring_iterator<OtherValue>& other) const {
            return this->parent_ == other.parent_
                && this->i_ == other.i_
                && this->ii_ == other.ii_;
        }

        Value dereference() const {
            return substr(parent_, i_, ii_);
        }

        const substrings* parent_;
        typename sast_type::const_iterator i_;
        int ii_;
    };

protected:
    double strict_purity(typename sast_type::const_iterator n, const int ii) const {
        // ここではノードi（iはpost-orderでの番号）に対応する部分文字列の末尾をii文字削ったsubstrを扱う。
        // iiが満たさなければならない条件: 0 <= ii < d[i] - d[node_to_parent_node[i]]
        const auto freq_substr = n->frequency();
        const auto len_substr  = n->length() - ii;

        // substrと同じ出現回数のsub-substrを数える。
        uint64_t count = 0;
        {
            // substrの末尾を0文字以上削って得られるsub-substrについて考える。
            // ノードiに対応する部分文字列をsubstr[i]とすると、substr[i]の末尾
            // を削って得られる部分文字列の内で、substr[i]と同じ頻度をもつもの
            // はsuffix tree上ではノードiにまとめられている
            // （分岐が無い <=> 頻度が同じ）。

            // ノードiの親ノードjを見つける。
            const auto m = n.parent();

            // substrの末尾を0文字以上削って得られるsub-substrの内で、出現
            // 回数がsubstrと同じものの数はd[i] - d[j]である。
            count += n->length() - m->length() - ii;
        }
        for (int j = 1; j < len_substr; ++j) {
            // substrの先頭をj文字削ったsub-substrを考える。
            const auto len_subsubstr = len_substr - j;

            // sub-substrに対応するノードを見つける。
            // {内部,葉}ノードに対応する部分文字列から先頭の1文字を削って得られ
            // る文字列には、必ず対応する{内部,葉}ノードが存在する。
            // ii ＞ 0 の場合、d[k] == len_subsubstr を満たす「ノード」が必ずし
            // も存在するとは限らない。よって、
            // d[node_to_parent_node[k]] < len_subsubstr <= d[k] を満たす k を
            // 見つける。
            auto m = n;
            for (int l = 0; l < j; ++l) {
                m = m.suffix();
            }
            while (m.parent()->length() >= len_subsubstr) {
                m = m.parent();
            }
            const auto kk = m->length() - len_subsubstr;  // ノードiでii文字削ると、ノードkではkk文字削ったことに相当する。

            // sub-substrの出現回数をチェックする。
            const auto freq_subsubstr = m->frequency();
            if (freq_subsubstr == freq_substr) {
                // ノードkの親ノードmを見つける。
                const auto mp = m.parent();

                // sub-substrの末尾を0文字以上削って得られる
                // sub-sub-substrの内で、出現回数がsub-substrと同じもの
                // の数はd[k] - d[m]である。
                count += m->length() - mp->length() - kk;
            }
        }

        // strict purity of substr
        const uint64_t num_subsubstrs = static_cast<uint64_t>(1 + len_substr) * len_substr / 2;
        const double spurity = static_cast<double>(count) / num_subsubstrs;

        return spurity;
    }

    double loose_purity(typename sast_type::const_iterator n, const int ii) const {
        // ここではノードi（iはpost-orderでの番号）に対応する部分文字列の末尾をii文字削ったsubstrを扱う。
        // iiが満たさなければならない条件: 0 <= ii < d[i] - d[node_to_parent_node[i]]
        const auto freq_substr = n->frequency();
        const auto len_substr  = n->length() - ii;

        double support = 0;
        {
            // substrの末尾を0文字以上削って得られるsub-substrについて考える。
            for (auto m = n, p = n.parent(); m->length() > 0; m = p, p = p.parent()) {
                const auto num_subsubstrs_of_same_frequency = m->length() - p->length() - (m == n ? ii : 0);
                const auto freq_subsubstr = m->frequency();
                const double sup = static_cast<double>(freq_substr) / freq_subsubstr;
                support += num_subsubstrs_of_same_frequency * sup;
            }
        }
        for (int j = 1; j < len_substr; ++j) {
            // substrの先頭をj文字削ったsub-substrを考える。
            const auto len_subsubstr = len_substr - j;

            // sub-substrに対応するノードを見つける。
            // ii ＞ 0 の場合、d[k] == len_subsubstr を満たす「ノード」が必ずし
            // も存在するとは限らない。よって、
            // d[node_to_parent_node[k]] < len_subsubstr <= d[k] を満たす k を
            // 見つける。
            auto m = n;
            for (int l = 0; l < j; ++l) {
                m = m.suffix();
            }
            while (m.parent()->length() >= len_subsubstr) {
                m = m.parent();
            }
            const auto kk = m->length() - len_subsubstr;  // ノードiでii文字削ると、ノードkではkk文字削ったことに相当する。

            // sub-substrの末尾を0文字以上削って得られるsub-substrについて考える。
            for (auto o = m, p = m.parent(); o->length() > 0; o = p, p = p.parent()) {
                const auto num_subsubstrs_of_same_frequency = o->length() - p->length() - (o == m ? kk : 0);
                const auto freq_subsubstr = o->frequency();
                const double sup = static_cast<double>(freq_substr) / freq_subsubstr;
                support += num_subsubstrs_of_same_frequency * sup;
            }
        }

        // loose purity of substr
        const uint64_t num_subsubstrs = static_cast<uint64_t>(1 + len_substr) * len_substr / 2;
        const double lpurity = support / num_subsubstrs;

        return lpurity;
    }

    std::map<char_type, int> left_extensions(typename sast_type::const_iterator n, const int ii) const {
        // ここではノードi（iはpost-orderでの番号）に対応する部分文字列の末尾をii文字削ったsubstrを扱う。
        // iiが満たさなければならない条件: 0 <= ii < d[i] - d[node_to_parent_node[i]]

        std::map<char_type, int> char_dist;
        for (auto pos : n->allpos()) {
            const auto& c = boost::const_begin(input_)[pos - 1];
            char_dist[c] += 1;
        }

        return char_dist;
    }

    std::map<char_type, int> right_extensions(typename sast_type::const_iterator n, const int ii) const {
        // ここではノードi（iはpost-orderでの番号）に対応する部分文字列の末尾をii文字削ったsubstrを扱う。
        // iiが満たさなければならない条件: 0 <= ii < d[i] - d[node_to_parent_node[i]]
        const auto len_substr  = n->length() - ii;

        std::map<char_type, int> char_dist;
        for (auto pos : n->allpos()) {
            const auto& c = boost::const_begin(input_)[pos + len_substr];
            char_dist[c] += 1;
        }

        return char_dist;
    }

    double left_universality(typename sast_type::const_iterator n, const int ii) const {
        // ここではノードi（iはpost-orderでの番号）に対応する部分文字列の末尾をii文字削ったsubstrを扱う。
        // iiが満たさなければならない条件: 0 <= ii < d[i] - d[node_to_parent_node[i]]
        const auto freq_substr = n->frequency();

        std::map<char_type, int> char_dist = left_extensions(n, ii);

        double e = 0;
        for (const auto& kv : char_dist) {
            const double p = static_cast<double>(kv.second) / freq_substr;
            e += -p * std::log(p);
        }
        const double u = 1 - std::exp(-e);

        return u;
    }

    double right_universality(typename sast_type::const_iterator n, const int ii) const {
        // ここではノードi（iはpost-orderでの番号）に対応する部分文字列の末尾をii文字削ったsubstrを扱う。
        // iiが満たさなければならない条件: 0 <= ii < d[i] - d[node_to_parent_node[i]]
        const auto freq_substr = n->frequency();

        std::map<char_type, int> char_dist = right_extensions(n, ii);

        double e = 0;
        for (const auto& kv : char_dist) {
            const double p = static_cast<double>(kv.second) / freq_substr;
            e += -p * std::log(p);
        }
        const double u = 1 - std::exp(-e);

        return u;
    }

    const sast_type& sast_;
    const RandomAccessRange& input_;
};

}  // namespace atm

#endif  /* ATM_SUBSTRINGS_HPP */


/*****************************************************************************
*
* Copyright (c) 2016 by The University of Queensland
* http://www.uq.edu.au
*
* Primary Business: Queensland, Australia
* Licensed under the Apache License, version 2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Development until 2012 by Earth Systems Science Computational Center (ESSCC)
* Development 2012-2013 by School of Earth Sciences
* Development from 2014 by Centre for Geoscience Computing (GeoComp)
*
*****************************************************************************/

#include <trilinoswrap/Amesos2Wrapper.h>
#include <trilinoswrap/TrilinosAdapterException.h>
#include <trilinoswrap/util.h>

#include <escript/SolverOptions.h>

#include <Amesos2.hpp>
#include <Tpetra_CrsMatrix.hpp>

#include <boost/python/dict.hpp>

using Teuchos::RCP;

namespace bp = boost::python;

namespace esys_trilinos {

template<class Matrix, class Vector>
RCP<DirectSolverType<Matrix,Vector> > createDirectSolver(
                                               const escript::SolverBuddy& sb,
                                               RCP<const Matrix> A,
                                               RCP<Vector> X,
                                               RCP<const Vector> B)
{
    using util::extractParamIfSet;

    typedef typename Matrix::scalar_type ST;

    RCP<DirectSolverType<Matrix,Vector> > solver;
    RCP<Teuchos::ParameterList> amesosParams = Teuchos::parameterList("Amesos2");
    const bp::dict& pyParams = sb.getTrilinosParameters();

    const escript::SolverOptions method = sb.getSolverMethod();
    // did user request specific direct solver or not?
    const bool dontcare = method == escript::SO_METHOD_DIRECT;

    if ((dontcare || method == escript::SO_METHOD_DIRECT_TRILINOS) &&
            Amesos2::query("klu2")) {
        solver = Amesos2::create<Matrix, Vector>("klu2", A, X, B);
        Teuchos::ParameterList solverParams(solver->name());
        // the doco says these params exist but clearly they don't :-(
        //solverParams.set("DiagPivotThresh", sb.getDiagonalDominanceThreshold());
        //solverParams.set("SymmetricMode", sb.isSymmetric());
        extractParamIfSet<std::string>("Trans", pyParams, solverParams);
        extractParamIfSet<bool>("Equil", pyParams, solverParams);
        extractParamIfSet<std::string>("IterRefine", pyParams, solverParams);
        extractParamIfSet<bool>("SymmetricMode", pyParams, solverParams);
        extractParamIfSet<ST>("DiagPivotThresh", pyParams, solverParams);
        extractParamIfSet<std::string>("ColPerm", pyParams, solverParams);
        amesosParams->set(solver->name(), solverParams);
    } else if ((dontcare || method == escript::SO_METHOD_DIRECT_MUMPS) &&
            Amesos2::query("MUMPS")) {
        solver = Amesos2::create<Matrix, Vector>("MUMPS", A, X, B);
        Teuchos::ParameterList solverParams(solver->name());
        if (sb.isVerbose()) {
            solverParams.set("ICNTL(4)", 4);
        }
        extractParamIfSet<int>("ICNTL(1)", pyParams, solverParams);
        extractParamIfSet<int>("ICNTL(2)", pyParams, solverParams);
        extractParamIfSet<int>("ICNTL(3)", pyParams, solverParams);
        extractParamIfSet<int>("ICNTL(4)", pyParams, solverParams);
        extractParamIfSet<int>("ICNTL(6)", pyParams, solverParams);
        extractParamIfSet<int>("ICNTL(9)", pyParams, solverParams);
        extractParamIfSet<int>("ICNTL(11)", pyParams, solverParams);
        amesosParams->set(solver->name(), solverParams);
    } else if ((dontcare || method == escript::SO_METHOD_DIRECT_TRILINOS) &&
            Amesos2::query("Basker")) {
        solver = Amesos2::create<Matrix, Vector>("Basker", A, X, B);
    } else if ((dontcare || method == escript::SO_METHOD_DIRECT_SUPERLU) &&
            Amesos2::query("superludist")) {
        solver = Amesos2::create<Matrix, Vector>("superludist", A, X, B);
        Teuchos::ParameterList solverParams(solver->name());
        extractParamIfSet<int>("npcol", pyParams, solverParams);
        extractParamIfSet<int>("nprow", pyParams, solverParams);
        extractParamIfSet<std::string>("ColPerm", pyParams, solverParams);
        extractParamIfSet<bool>("ReplaceTinyPivot", pyParams, solverParams);
        amesosParams->set(solver->name(), solverParams);
    } else if ((dontcare || method == escript::SO_METHOD_DIRECT_SUPERLU) &&
            Amesos2::query("superlu")) {
        solver = Amesos2::create<Matrix, Vector>("superlu", A, X, B);
        Teuchos::ParameterList solverParams(solver->name());
        solverParams.set("DiagPivotThresh", sb.getDiagonalDominanceThreshold());
        solverParams.set("ILU_DropTol", sb.getDropTolerance());
        solverParams.set("SymmetricMode", sb.isSymmetric());
        extractParamIfSet<std::string>("Trans", pyParams, solverParams);
        extractParamIfSet<bool>("Equil", pyParams, solverParams);
        extractParamIfSet<std::string>("IterRefine", pyParams, solverParams);
        extractParamIfSet<bool>("SymmetricMode", pyParams, solverParams);
        extractParamIfSet<ST>("DiagPivotThresh", pyParams, solverParams);
        extractParamIfSet<std::string>("ColPerm", pyParams, solverParams);
        extractParamIfSet<bool>("ILU_Flag", pyParams, solverParams);
        extractParamIfSet<ST>("ILU_DropTol", pyParams, solverParams);
        extractParamIfSet<ST>("ILU_FillFactor", pyParams, solverParams);
        extractParamIfSet<std::string>("ILU_Norm", pyParams, solverParams);
        extractParamIfSet<std::string>("ILU_MILU", pyParams, solverParams);
        extractParamIfSet<ST>("ILU_FillTol", pyParams, solverParams);
        amesosParams->set(solver->name(), solverParams);
    } else if ((dontcare || method == escript::SO_METHOD_DIRECT_SUPERLU) &&
            Amesos2::query("superlumt")) {
        solver = Amesos2::create<Matrix, Vector>("superlumt", A, X, B);
        Teuchos::ParameterList solverParams(solver->name());
#ifdef _OPENMP
        solverParams.set("nprocs", omp_get_max_threads());
#else
        solverParams.set("nprocs", 1);
#endif
        solverParams.set("DiagPivotThresh", sb.getDiagonalDominanceThreshold());
        solverParams.set("SymmetricMode", sb.isSymmetric());
        extractParamIfSet<int>("nprocs", pyParams, solverParams);
        extractParamIfSet<std::string>("trans", pyParams, solverParams);
        extractParamIfSet<int>("panel_size", pyParams, solverParams);
        extractParamIfSet<int>("relax", pyParams, solverParams);
        extractParamIfSet<bool>("Equil", pyParams, solverParams);
        extractParamIfSet<bool>("SymmetricMode", pyParams, solverParams);
        extractParamIfSet<ST>("DiagPivotThresh", pyParams, solverParams);
        extractParamIfSet<std::string>("ColPerm", pyParams, solverParams);
        amesosParams->set(solver->name(), solverParams);
    } else if ((dontcare || method == escript::SO_METHOD_DIRECT_PARDISO) &&
            Amesos2::query("pardiso_mkl")) {
        solver = Amesos2::create<Matrix, Vector>("pardiso_mkl", A, X, B);
        Teuchos::ParameterList solverParams(solver->name());
        extractParamIfSet<int>("IPARM(2)", pyParams, solverParams);
        extractParamIfSet<int>("IPARM(4)", pyParams, solverParams);
        extractParamIfSet<int>("IPARM(8)", pyParams, solverParams);
        extractParamIfSet<int>("IPARM(10)", pyParams, solverParams);
        extractParamIfSet<int>("IPARM(18)", pyParams, solverParams);
        extractParamIfSet<int>("IPARM(24)", pyParams, solverParams);
        extractParamIfSet<int>("IPARM(25)", pyParams, solverParams);
        extractParamIfSet<int>("IPARM(60)", pyParams, solverParams);
        amesosParams->set(solver->name(), solverParams);
    } else if (Amesos2::query("amesos2_cholmod")) {
        solver = Amesos2::create<Matrix, Vector>("amesos2_cholmod", A, X, B);
        Teuchos::ParameterList solverParams(solver->name());
        solverParams.set("DiagPivotThresh", sb.getDiagonalDominanceThreshold());
        solverParams.set("SymmetricMode", sb.isSymmetric());
        extractParamIfSet<std::string>("Trans", pyParams, solverParams);
        extractParamIfSet<bool>("Equil", pyParams, solverParams);
        extractParamIfSet<std::string>("IterRefine", pyParams, solverParams);
        extractParamIfSet<bool>("SymmetricMode", pyParams, solverParams);
        extractParamIfSet<ST>("DiagPivotThresh", pyParams, solverParams);
        extractParamIfSet<std::string>("ColPerm", pyParams, solverParams);
        amesosParams->set(solver->name(), solverParams);
    } else if (Amesos2::query("lapack")) {
        solver = Amesos2::create<Matrix, Vector>("lapack", A, X, B);
    } else {
        if (dontcare) {
            throw TrilinosAdapterException("Could not find an Amesos2 direct solver!");
        } else {
            throw TrilinosAdapterException("The requested direct solver is not available!");
        }
    }
    solver->setParameters(amesosParams);
    return solver;
}

typedef Tpetra::CrsMatrix<real_t,LO,GO,NT> RealMatrix;
typedef Tpetra::CrsMatrix<cplx_t,LO,GO,NT> ComplexMatrix;

// instantiate
template
RCP<DirectSolverType<RealMatrix, RealVector> >
createDirectSolver<RealMatrix,RealVector>(const escript::SolverBuddy& sb,
                                          RCP<const RealMatrix> A,
                                          RCP<RealVector> X,
                                          RCP<const RealVector> B);
template
RCP<DirectSolverType<ComplexMatrix, ComplexVector> >
createDirectSolver<ComplexMatrix, ComplexVector>(
                                          const escript::SolverBuddy& sb,
                                          RCP<const ComplexMatrix> A,
                                          RCP<ComplexVector> X,
                                          RCP<const ComplexVector> B);

/* Amesos2 does not currently support block matrices!
template
RCP<DirectSolverType<RealBlockMatrix, RealBlockVector> >
createDirectSolver<RealBlockMatrix,RealBlockVector>(
                                          const escript::SolverBuddy& sb,
                                          RCP<const RealBlockMatrix> A,
                                          RCP<RealBlockVector> X,
                                          RCP<const RealBlockVector> B);
template
RCP<DirectSolverType<ComplexBlockMatrix, ComplexBlockVector> >
createDirectSolver<ComplexBlockMatrix, ComplexBlockVector>(
                                          const escript::SolverBuddy& sb,
                                          RCP<const ComplexBlockMatrix> A,
                                          RCP<ComplexBlockVector> X,
                                          RCP<const ComplexBlockVector> B);
*/

}  // end of namespace