/*
 *            Copyright 2009-2018 The VOTCA Development Team
 *                       (http://www.votca.org)
 *
 *      Licensed under the Apache License, Version 2.0 (the "License")
 *
 * You may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *              http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include "nwchem.h"
#include <votca/ctp/segment.h>
#include <votca/xtp/qminterface.h>
#include <votca/xtp/basisset.h>
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#include <boost/filesystem.hpp>
#include <stdio.h>
#include <iomanip>

namespace votca {
    namespace xtp {
       using namespace std;

        void NWChem::Initialize(tools::Property &options) {

            // NWChem file names
            string fileName = "system";

            _input_file_name = fileName + ".nw";
            _log_file_name = fileName + ".log";
            _shell_file_name = fileName + ".sh";
            _orb_file_name = fileName + ".movecs";

            string key = "package";
            string _name = options.get(key + ".name").as<string> ();

            if (_name != "nwchem") {
                cerr << "Tried to use " << _name << " package. ";
                throw std::runtime_error("Wrong options file");
            }

            _executable = options.get(key + ".executable").as<string> ();
            _charge = options.get(key + ".charge").as<int> ();
            _spin = options.get(key + ".spin").as<int> ();
            _options = options.get(key + ".options").as<string> ();
            _memory = options.get(key + ".memory").as<string> ();
            _threads = options.get(key + ".threads").as<int> ();
            _scratch_dir = options.get(key + ".scratch").as<string> ();
            _cleanup = options.get(key + ".cleanup").as<string> ();

            _basisset_name = options.get(key + ".basisset").as<std::string> ();
            _write_basis_set = options.get(key + ".writebasisset").as<bool> ();
            _write_pseudopotentials = options.get(key + ".writepseudopotentials").as<bool> ();

            if ( _write_pseudopotentials )  _ecp_name = options.get(key + ".ecp").as<std::string> ();

            if (options.exists(key + ".outputVxc")) {
                _output_Vxc = options.get(key + ".outputVxc").as<bool> ();
            } else _output_Vxc = false;
            // check whether options string contains vxc output, the _outputVxc is set to true
            std::string::size_type iop_pos = _options.find(" intermediate tXC matrix");
            if (iop_pos != std::string::npos) {
                if (_output_Vxc) {
                    cout << "=== You do not have to specify outputting Vxc twice. Next time remove "
                            "the print ""intermediate tXC matrix"" part from your options string. Please continue" << endl;
                } else {
                    cout << "=== So you do not want to output Vxc but still put it in the options string? "
                            "I will assume that you want to output Vxc, be more consistent next time. " << endl;
                }
                _output_Vxc = true;
            }
            else if (_output_Vxc == true) {
                _options = _options + "\n\ndft\nprint \"intermediate tXC matrix\"\nvectors input system.movecs\nnoscf\nend\ntask dft";
            }

            // check if the optimize keyword is present, if yes, read updated coords
            iop_pos = _options.find(" optimize");
            if (iop_pos != std::string::npos) {
                _is_optimization = true;
            } else {
                _is_optimization = false;
            }

            // check if the esp keyword is present, if yes, get the charges and save them
            iop_pos = _options.find(" esp");
            if (iop_pos != std::string::npos) {
                _get_charges = true;
            } else {
                _get_charges = false;
            }

            // check if the guess should be prepared, if yes, append the guess later
            _write_guess = false;
            iop_pos = _options.find("iterations 1 ");
            if (iop_pos != std::string::npos) _write_guess = true;
            iop_pos = _options.find("iterations 1\n");
            if (iop_pos != std::string::npos) _write_guess = true;
        }

         /* For QM/MM the molecules in the MM environment are represented by
         * their atomic partial charge distributions. Triggered by the option
         * keyword "set bq background" NWChem expects them in x,y,z,q format in the
         * backround.crg file.
         */

        void NWChem::WriteChargeOption(){
              std::string::size_type iop_pos = _options.find("set bq background");
              if (iop_pos != std::string::npos) {
                _options = _options + "\n set bq background";
              }
        }

        int NWChem::WriteBackgroundCharges(ofstream& nw_file) {

            int numberofcharges=0;
            boost::format fmt("%1$+1.7f %2$+1.7f %3$+1.7f %4$+1.7f");
              for (std::shared_ptr<ctp::PolarSeg> seg:_PolarSegments) {
                for (ctp::APolarSite* site:*seg) {
                    string sitestring=boost::str(fmt % ((site->getPos().getX())*votca::tools::conv::nm2ang)
                            % (site->getPos().getY()*votca::tools::conv::nm2ang)
                            % (site->getPos().getZ()*votca::tools::conv::nm2ang)
                            % site->getQ00());
                    if (site->getQ00() != 0.0){
                      nw_file << sitestring << endl;
                      numberofcharges++;
                    }
                    if (site->getRank() > 0 || _with_polarization ) {
                        std::vector< std::vector<double> > _split_multipoles = SplitMultipoles(site);
                        for (const auto& mpoles:_split_multipoles){
                           string multipole=boost::str( fmt % mpoles[0] % mpoles[1] % mpoles[2] % mpoles[3]);
                            nw_file << multipole << endl;
                            numberofcharges++;

                        }
                    }
                }
            }
            nw_file << endl;
            return numberofcharges;
        }

        bool NWChem::WriteGuess(Orbitals& orbitals){
          ofstream orb_file;
          std::string orb_file_name_full = _run_dir + "/" + _orb_file_name;
          // get name of temporary ascii file and open it
          std::vector<std::string> results;
          boost::algorithm::split(results, _orb_file_name, boost::is_any_of("."), boost::algorithm::token_compress_on);
          std::string orb_file_name_ascii = _run_dir + "/" + results.front() + ".mos";
          orb_file.open(orb_file_name_ascii.c_str());

          // header
          orb_file << "#generated by VOTCA\nbasisum\ngeomsum\n\nscf\nFri Sep 13 00:00:00 2013\nscf\n1\n\n8\nao basis\n1\n" << flush;
          int size_of_basis = (orbitals.MOEnergies()).size();
          orb_file << size_of_basis << endl;
          orb_file << size_of_basis << endl;
          ReorderMOsBack(orbitals);
          int level = 1;
          int ncolumns = 3;
          // write occupations as double in three columns
          // occupied levels
          int column = 1;
          for (int i = 0; i < orbitals.getNumberOfElectrons(); i++) {
            orb_file << FortranFormat(2.0);
            if (column == ncolumns) {
              orb_file << endl;
              column = 0;
            }
            column++;
          }
          // unoccupied levels
          for (int i = orbitals.getNumberOfElectrons(); i < size_of_basis; i++) {
            orb_file << FortranFormat(0.0);
            if (column == ncolumns) {
              orb_file << endl;
              column = 0;
            }
            column++;
          }
          // extra endl
          if (column != 1) {
            orb_file << endl;
          }

          // write all energies in same format
          column = 1;
          for (int i=0;i<orbitals.MOEnergies().size();++i) {
            double energy = (orbitals.MOEnergies())[i];
            orb_file << FortranFormat(energy);
            if (column == ncolumns) {
              orb_file << endl;
              column = 0;
            }
            column++;
          }
          if (column != 1) orb_file << endl;

          // write coefficients in same format
          for (int i=0;i<orbitals.MOCoefficients().cols();++i) {
            Eigen::VectorXd mr=orbitals.MOCoefficients().col(i);
            column = 1;
            for (unsigned j = 0; j < mr.size(); ++j) {
              orb_file << FortranFormat(mr[j]);
              if (column == ncolumns) {
                orb_file << endl;
                column = 0;
              }
              column++;
            }
            level++;
            if (column != 1) orb_file << endl;
          }
          orb_file << " 0.0000   0.0000" << endl;
          orb_file.close();
          // now convert this ascii file to binary
          std::string command;
          command = "cd " + _run_dir + "; asc2mov 5000 system.mos system.movecs > convert.log";
          int i = std::system(command.c_str());
          if (i == 0) {
            CTP_LOG(ctp::logDEBUG, *_pLog) << "Converted MO file from ascii to binary" << flush;
          } else {
            CTP_LOG(ctp::logERROR, *_pLog) << "Conversion of binary MO file to binary failed. " << flush;
            return false;
          }
          return true;
        }

        /**
         * Prepares the *.nw file from a vector of segments
         * Appends a guess constructed from monomer orbitals if supplied
         */
        bool NWChem::WriteInputFile(Orbitals& orbitals){

            std::string temp_suffix = "/id";
            std::string scratch_dir_backup = _scratch_dir;
            std::ofstream nw_file;
            std::ofstream crg_file;

            std::string nw_file_name_full = _run_dir + "/" + _input_file_name;
            std::string crg_file_name_full = _run_dir + "/background.crg";

            nw_file.open(nw_file_name_full.c_str());
            // header
            nw_file << "geometry noautoz noautosym" << endl;

            std::vector< QMAtom* > qmatoms = orbitals.QMAtoms();

            for (const QMAtom* atom:qmatoms) {
                tools::vec pos=atom->getPos()*tools::conv::bohr2ang;
                    nw_file << setw(3) << atom->getType().c_str()
                            << setw(12) << setiosflags(ios::fixed) << setprecision(5) << pos.getX()
                            << setw(12) << setiosflags(ios::fixed) << setprecision(5) << pos.getY()
                            << setw(12) << setiosflags(ios::fixed) << setprecision(5) << pos.getZ()
                            << endl;
            }
            nw_file << "end\n";
            if (_write_charges) {
                // part for the MM charge coordinates
                crg_file.open(crg_file_name_full.c_str());
                int numberofcharges=WriteBackgroundCharges(crg_file);
                crg_file << endl;
                crg_file.close();
                nw_file<<endl;
                nw_file<<"set bq:max_nbq "<<numberofcharges<<endl;
                nw_file<<"bq background"<<endl;
                nw_file<<"load background.crg format 1 2 3 4"<<endl;
                nw_file<<"end\n"<<endl;
            }

            if(_write_basis_set){
              WriteBasisset(nw_file,qmatoms);
            }

            if(_write_pseudopotentials){
              WriteECP(nw_file,qmatoms);
            }

            // write charge of the molecule
            nw_file << "\ncharge " << _charge << "\n";

            // writing scratch_dir info
            if (_scratch_dir != "") {
                std::string _temp("scratch_dir " + _scratch_dir + temp_suffix + "\n");
                nw_file << _temp;
            }
            if(_charge!=0.0){
              std::string dft="dft";
              if(_options.find(dft) != std::string::npos){
                int dftpos=_options.find(dft);
                dftpos+=dft.size();
                std::string openshell="\nodft\n" +(boost::format("mult %1%\n") % _spin).str();
                _options.insert(dftpos,openshell,0,openshell.size());
              }else{
                throw runtime_error("NWCHEM: dft input data missing");
              }
            }
            nw_file << _options << "\n";
            if (_write_guess) {
                 bool worked=WriteGuess(orbitals);
                 if(!worked){
                    return false;
                 }
            }

            nw_file << endl;
            nw_file.close();
            // and now generate a shell script to run both jobs, if neccessary
            CTP_LOG(ctp::logDEBUG, *_pLog) << "Setting the scratch dir to " << _scratch_dir + temp_suffix << flush;

            _scratch_dir = scratch_dir_backup + temp_suffix;

            WriteShellScript();
            _scratch_dir = scratch_dir_backup;

            return true;
        }

        bool NWChem::WriteShellScript() {
            ofstream shell_file;
            std::string shell_file_name_full = _run_dir + "/" + _shell_file_name;
            shell_file.open(shell_file_name_full.c_str());
            shell_file << "#!/bin/bash" << endl;
            shell_file << "mkdir -p " << _scratch_dir << endl;
            if (_threads == 1) {
                shell_file << _executable << " " << _input_file_name << " > " << _log_file_name << " 2> run.error" << endl;
            } else {
                shell_file << "mpirun -np " << boost::lexical_cast<std::string>(_threads) << " " << _executable << " " << _input_file_name << " > " << _log_file_name << " 2> run.error" << endl;
            }
            shell_file.close();
            return true;
        }

        /**
         * Runs the NWChem job.
         */
        bool NWChem::Run( Orbitals& orbitals ) {

            CTP_LOG(ctp::logDEBUG, *_pLog) << "Running NWChem job" << flush;

            if (std::system(NULL)) {

                // NWChem overrides input information, if *.db and *.movecs files are present
                // better trash the old version
                std::string file_name = _run_dir + "/system.db";
                remove(file_name.c_str());
                file_name = _run_dir + "/" + _log_file_name;
                remove(file_name.c_str());

                std::string command = "cd " + _run_dir + "; sh " + _shell_file_name;

                int check = std::system(command.c_str());
                if (check == -1) {
                    CTP_LOG(ctp::logERROR, *_pLog) << _input_file_name << " failed to start" << flush;
                    return false;
                }

                if (CheckLogFile()) {
                    CTP_LOG(ctp::logDEBUG, *_pLog) << "Finished NWChem job" << flush;
                    return true;
                } else {
                    CTP_LOG(ctp::logDEBUG, *_pLog) << "NWChem job failed" << flush;
                    return false;
                }
            } else {
                CTP_LOG(ctp::logERROR, *_pLog) << _input_file_name << " failed to start" << flush;
                return false;
            }

            return true;
        }

        /**
         * Cleans up after the NWChem job
         */
        void NWChem::CleanUp() {

            // cleaning up the generated files
            if (_cleanup.size() != 0) {
                tools::Tokenizer tok_cleanup(_cleanup, ",");
                std::vector <std::string> cleanup_info;
                tok_cleanup.ToVector(cleanup_info);
                for (const std::string& substring:cleanup_info) {
                    if (substring== "nw") {
                        std::string file_name = _run_dir + "/" + _input_file_name;
                        remove(file_name.c_str());
                    }

                    if (substring == "db") {
                        std::string file_name = _run_dir + "/system.db";
                        remove(file_name.c_str());
                    }

                    if (substring == "log") {
                        std::string file_name = _run_dir + "/" + _log_file_name;
                        remove(file_name.c_str());
                    }

                    if (substring == "movecs") {
                        std::string file_name = _run_dir + "/" + _orb_file_name;
                        remove(file_name.c_str());
                    }

                    if (substring == "gridpts") {
                        std::string file_name = _run_dir + "/system.gridpts.*";
                        remove(file_name.c_str());
                    }
                }
            }

        }

        /**
         * Reads in the MO coefficients from a NWChem movecs file
         */
        bool NWChem::ParseOrbitalsFile(Orbitals& orbitals) {
            std::map <int, std::vector<double> > coefficients;
            std::map <int, double> energies;
            std::map <int, double> occupancy;

            std::string line;
            unsigned levels = 0;
            //unsigned _level;
            unsigned basis_size = 0;
            int number_of_electrons = 0;

            /* maybe we DO need to convert from fortran binary to ASCII first to avoid
             compiler-dependent issues */
            std::string orb_file_name_bin = _run_dir + "/" + _orb_file_name;
            std::string command;
            command = "cd " + _run_dir + "; mov2asc 10000 system.movecs system.mos > convert.log";
            int i = std::system(command.c_str());
            if (i == 0) {
                CTP_LOG(ctp::logDEBUG, *_pLog) << "Converted MO file from binary to ascii" << flush;
            } else {
                CTP_LOG(ctp::logERROR, *_pLog) << "Conversion of binary MO file to ascii failed. " << flush;
                return false;
            }

            // opening the ascii MO file
            std::string orb_file_name_full = _run_dir + "/" + "system.mos";
            std::ifstream input_file(orb_file_name_full.c_str());

            if (input_file.fail()) {
                CTP_LOG(ctp::logERROR, *_pLog) << "File " << orb_file_name_full << " with molecular orbitals is not found " << flush;
                return false;
            } else {
                CTP_LOG(ctp::logDEBUG, *_pLog) << "Reading MOs from " << orb_file_name_full << flush;
            }

            // the first 12 lines are garbage info
            for (i = 1; i < 13; i++) {
                getline(input_file, line);
            }
            // next line has basis set size
            input_file >> basis_size;
            CTP_LOG(ctp::logDEBUG, *_pLog) << "Basis set size: " << basis_size << flush;

            // next line has number of stored MOs
            input_file >> levels;
            CTP_LOG(ctp::logDEBUG, *_pLog) << "Energy levels: " << levels << flush;

            /* next lines contain information about occupation of the MOs
             *  - each line has 3 numbers
             *  - from _occ we can determine the number of electrons/2 */
            int n_lines = ((levels - 1) / 3);
            int n_rest = levels - 3 * n_lines;
            // read in the data
            int imo = 0;
            for (i = 1; i <= n_lines; i++) {
                for (int j = 0; j < 3; j++) {
                    input_file >> occupancy[ imo ];
                    if (occupancy[ imo ] == 2.0) {
                        number_of_electrons++;
                    }
                    imo++;
                }
            }
            if (n_rest != 0) {
                for (i = 0; i < n_rest; i++) {
                    input_file >> occupancy[ imo ];
                    imo++;
                }
            }
            CTP_LOG(ctp::logDEBUG, *_pLog) << "Alpha electrons: " << number_of_electrons << flush;

            int occupied_levels = number_of_electrons;
            int unoccupied_levels = levels - occupied_levels;
            CTP_LOG(ctp::logDEBUG, *_pLog) << "Occupied levels: " << occupied_levels << flush;
            CTP_LOG(ctp::logDEBUG, *_pLog) << "Unoccupied levels: " << unoccupied_levels << flush;

            // reset index and read MO energies the same way
            imo = 0;
            for (i = 1; i <= n_lines; i++) {
                for (int j = 0; j < 3; j++) {
                    input_file >> energies[ imo ];
                    imo++;
                }
            }
            if (n_rest != 0) {
                for (i = 0; i < n_rest; i++) {
                    input_file >> energies[ imo ];
                    imo++;
                }
            }

            // Now, the same for the coefficients
            double coef;
            for (unsigned imo = 0; imo < levels; imo++) {
                for (i = 1; i <= n_lines; i++) {
                    for (int j = 0; j < 3; j++) {
                        input_file >> coef;
                        coefficients[ imo ].push_back(coef);
                    }
                }
                if (n_rest != 0) {
                    for (i = 0; i < n_rest; i++) {
                        input_file >> coef;
                        coefficients[ imo ].push_back(coef);
                    }
                }
            }

            // copying information to the orbitals object
            orbitals.setBasisSetSize(basis_size);
            orbitals.setNumberOfElectrons(number_of_electrons);
            orbitals.setNumberOfLevels(occupied_levels, unoccupied_levels);
            // copying energies to a matrix
            orbitals.MOEnergies().resize(levels);
            //_level = 1;
            for (int i = 0; i < orbitals.MOEnergies().size(); i++) {
                orbitals.MOEnergies()[i] = energies[ i ];
            }

            // copying orbitals to the matrix
            (orbitals.MOCoefficients()).resize(levels, basis_size);
            for (int i = 0; i < orbitals.MOCoefficients().rows(); i++) {
                for (int j = 0; j < orbitals.MOCoefficients().cols(); j++) {
                    orbitals.MOCoefficients()(j, i) = coefficients[i][j];
                }
            }

            // when all is done, we can trash the ascii file
            std::string file_name = _run_dir + "/system.mos";
            remove(file_name.c_str());

            ReorderOutput(orbitals);
            CTP_LOG(ctp::logDEBUG, *_pLog) << "Done reading MOs" << flush;

            return true;
        }

        bool NWChem::CheckLogFile() {

            // check if the log file exists

            ifstream input_file((_run_dir + "/" + _log_file_name).c_str());

            if (input_file.fail()) {
                CTP_LOG(ctp::logERROR, *_pLog) << "NWChem LOG is not found" << flush;
                return false;
            };

            if (input_file.peek() == std::ifstream::traits_type::eof()) {
                CTP_LOG(ctp::logERROR, *_pLog) << "NWChem run failed. Check OpenMPI version!" << flush;
                return false;
            };

            /* Checking the log file is a pain in the *** since NWChem throws an error
             * for our 'iterations 1'  runs (even though it outputs the required data
             * correctly. The only way that works for both scf and noscf runs is to
             * check for "Total DFT energy" near the end of the log file.
             */

            input_file.seekg(0, ios_base::end); // go to the EOF
            char ch;
            std::string::size_type total_energy_pos = std::string::npos;
            std::string::size_type diis_pos = std::string::npos;
            do {
                // get the beginning of the line
                do {
                    input_file.seekg(-2, ios_base::cur);
                    input_file.get(ch);
                    //cout << "\nNext Char: " << ch << " TELL G " <<  (int)_input_file.tellg() << endl;
                } while (ch != '\n' || (int) input_file.tellg() == -1);

                std::string line;
                getline(input_file, line); // Read the current line
                total_energy_pos = line.find("Total DFT energy");
                diis_pos = line.find("diis");
                // whatever is found first, determines the completeness of the file
                if (total_energy_pos != std::string::npos) {
                    return true;
                } else if (diis_pos != std::string::npos) {
                    CTP_LOG(ctp::logERROR, *_pLog) << "NWChem LOG is incomplete" << flush;
                    return false;
                } else {
                    // go to previous line
                    //_input_file.get(ch);
                    do {
                        input_file.seekg(-2, ios_base::cur);
                        input_file.get(ch);
                        //cout << "\nChar: " << ch << endl;
                    } while (ch != '\n' || (int) input_file.tellg() == -1);
                }
            } while (total_energy_pos == std::string::npos && diis_pos == std::string::npos);

            input_file.close();
            return true;
        }

        /**
         * Parses the Gaussian Log file and stores data in the Orbitals object
         */
        bool NWChem::ParseLogFile(Orbitals& orbitals) {

            double conv_Hrt_eV = tools::conv::hrt2ev;

            std::string line;
            std::vector<std::string> results;

            bool has_overlap_matrix = false;
            bool has_charges = false;
            bool has_qm_energy = false;
            bool has_self_energy = false;
            bool has_basis_set_size = false;

            bool found_optimization = false;
            int basis_set_size = 0;

            CTP_LOG(ctp::logDEBUG, *_pLog) << "Parsing " << _log_file_name << flush;
            std::string log_file_name_full = _run_dir + "/" + _log_file_name;
            // check if LOG file is complete
            if (!CheckLogFile()) return false;

            // save qmpackage name
            orbitals.setQMpackage("nwchem");
            orbitals.setDFTbasis(_basisset_name);

            if (_write_pseudopotentials) {
                orbitals.setECP(_ecp_name);
            }
            // set _found_optimization to true if this is a run without optimization
            if (!_is_optimization) {
                found_optimization = true;
            }

            // Start parsing the file line by line
            ifstream input_file(log_file_name_full.c_str());
            while (input_file) {

                getline(input_file, line);
                boost::trim(line);

                /*
                 * basis set size (is only required for overlap matrix reading, rest is
                 * in orbitals file and could be skipped
                 */
                std::string::size_type basis_pos = line.find("number of functions");
                if (basis_pos != std::string::npos) {
                    //cout << _line << endl;
                    boost::algorithm::split(results, line, boost::is_any_of(":"), boost::algorithm::token_compress_on);
                    has_basis_set_size = true;
                    std::string bf = results.back();
                    boost::trim(bf);
                    basis_set_size = boost::lexical_cast<int>(bf);
                    orbitals.setBasisSetSize(basis_set_size);
                    CTP_LOG(ctp::logDEBUG, *_pLog) << "Basis functions: " << basis_set_size << flush;
                }

                /*
                 * Total DFT energy
                 */
                std::string::size_type energy_pos = line.find("Total DFT energy");
                if (energy_pos != std::string::npos) {
                    boost::algorithm::split(results, line, boost::is_any_of("="), boost::algorithm::token_compress_on);
                    std::string energy = results.back();
                    boost::trim(energy);
                    orbitals.setQMEnergy(conv_Hrt_eV * boost::lexical_cast<double>(energy));
                    CTP_LOG(ctp::logDEBUG, *_pLog) << (boost::format("QM energy[eV]: %4.6f ") % orbitals.getQMEnergy()).str() << flush;
                    has_qm_energy = true;
                    // _orbitals._has_qm_energy = true;

                }

                /*
                 *  Partial charges from the input file
                 */
                std::string::size_type charge_pos = line.find("ESP");
                if (charge_pos != std::string::npos && _get_charges) {
                    CTP_LOG(ctp::logDEBUG, *_pLog) << "Getting charges" << flush;
                    has_charges = true;
                    // two empty lines
                    getline(input_file, line);
                    getline(input_file, line);

                    // now starts the data in format
                    // _id type x y z q

                    std::vector<std::string> row=GetLineAndSplit(input_file, "\t ");
                    int nfields = row.size();

                    while (nfields == 6) {
                        int atom_id = boost::lexical_cast< int >(row.at(0));
                        std::string atom_type = row.at(1);
                        double atom_charge = boost::lexical_cast< double >(row.at(5));
                        row=GetLineAndSplit(input_file, "\t ");
                        nfields = row.size();
                        QMAtom* pAtom;
                        if (orbitals.hasQMAtoms() == false) {
                            pAtom =orbitals.AddAtom(atom_id - 1,atom_type,tools::vec(0.0));
                        } else {
                            pAtom = orbitals.QMAtoms().at(atom_id - 1);
                        }
                        pAtom->setPartialcharge(atom_charge);
                        }
                }

                /*
                 * Coordinates of the final configuration
                 * depending on whether it is an optimization or not
                 */

                if (_is_optimization) {
                    std::string::size_type optimize_pos = line.find("Optimization converged");
                    if (optimize_pos != std::string::npos) {
                        found_optimization = true;
                    }
                }

                std::string::size_type coordinates_pos = line.find("Output coordinates");

                if (found_optimization && coordinates_pos != std::string::npos) {
                    CTP_LOG(ctp::logDEBUG, *_pLog) << "Getting the coordinates" << flush;

                    //_has_coordinates = true;
                    bool has_QMAtoms = orbitals.hasQMAtoms();

                    // three garbage lines
                    getline(input_file, line);
                    getline(input_file, line);
                    getline(input_file, line);
                    // now starts the data in format
                    // _id type Qnuc x y z
                     std::vector<std::string> row=GetLineAndSplit(input_file, "\t ");
                    int nfields = row.size();

                    while (nfields == 6) {
                        int atom_id = boost::lexical_cast< int >(row.at(0))-1;
                        std::string _atom_type = row.at(1);
                        double x = boost::lexical_cast<double>(row.at(3));
                        double y = boost::lexical_cast<double>(row.at(4));
                        double z = boost::lexical_cast<double>(row.at(5));
                        tools::vec pos=tools::vec(x,y,z);
                        pos*=tools::conv::ang2bohr;
                        if (has_QMAtoms == false) {
                            orbitals.AddAtom(atom_id,_atom_type, pos);
                        } else{
                            QMAtom* pAtom = orbitals.QMAtoms().at(atom_id);
                            pAtom->setPos(pos);
                        }
                        atom_id++;
                        row=GetLineAndSplit(input_file, "\t ");
                        nfields = row.size();
                    }
                }

                /*
                 * Vxc matrix
                 * stored after the global array: g vxc
                 */
                if(_output_Vxc){
                std::string::size_type vxc_pos = line.find("global array: g vxc");
                if (vxc_pos != std::string::npos) {

                    // prepare the container
                    Eigen::MatrixXd vxc = orbitals.AOVxc();
                    vxc.resize(basis_set_size,basis_set_size);

                    //_has_vxc_matrix = true;
                    std::vector<int> j_indeces;

                    int n_blocks = 1 + ((basis_set_size - 1) / 6);
                    //cout << _n_blocks;

                    for (int block = 0; block < n_blocks; block++) {
                        // first line is garbage
                        getline(input_file, line);
                        // second line gives the j index in the matrix
                        getline(input_file, line);
                        boost::tokenizer<> tok(line);

                        /// COMPILATION IS BROKEN DUE TO A BUG IN BOOST 1.53
                        std::transform(tok.begin(), tok.end(), std::back_inserter(j_indeces), &boost::lexical_cast<int, std::string>);

                        // third line is garbage again
                        getline(input_file, line);

                        // read the block of max _basis_size lines + the following header
                        for (int i = 0; i < basis_set_size; i++) {
                            std::vector<std::string> row=GetLineAndSplit(input_file, "\t ");
                            int i_index = boost::lexical_cast<int>(row.front());
                            row.erase(row.begin());

                            std::vector<int>::iterator j_iter = j_indeces.begin();

                            for (std::string& coefficient: row) {

                                int j_index = *j_iter;
                                vxc(i_index - 1, j_index - 1) = boost::lexical_cast<double>(coefficient);
                                vxc(j_index - 1, i_index - 1) = boost::lexical_cast<double>(coefficient);
                                j_iter++;

                            }

                        }

                        // clear the index for the next block
                        j_indeces.clear();
                    } // end of the blocks

                    CTP_LOG(ctp::logDEBUG, *_pLog) << "Read the Vxc matrix" << flush;

                }
                }

                /* Check for ScaHFX = factor of HF exchange included in functional */
                std::string::size_type HFX_pos = line.find("Hartree-Fock (Exact) Exchange");
                if (HFX_pos != std::string::npos) {
                    boost::algorithm::split(results, line, boost::is_any_of("\t "), boost::algorithm::token_compress_on);
                    double ScaHFX = boost::lexical_cast<double>(results.back());
                    orbitals.setScaHFX(ScaHFX);
                    CTP_LOG(ctp::logDEBUG, *_pLog) << "DFT with " << ScaHFX << " of HF exchange!" << flush;
                }

                /*
                 * overlap matrix
                 * stored after the global array: Temp Over line
                 */
                std::string::size_type overlap_pos = line.find("global array: Temp Over");
                if (overlap_pos != std::string::npos) {

                    // prepare the container
                    (orbitals.AOOverlap()).resize(basis_set_size,basis_set_size);

                    has_overlap_matrix = true;
                    std::vector<int> j_indeces;

                    int n_blocks = 1 + ((basis_set_size - 1) / 6);

                    for (int block = 0; block < n_blocks; block++) {
                        // first line is garbage
                        getline(input_file, line);
                        // second line gives the j index in the matrix
                        getline(input_file, line);
                        boost::tokenizer<> tok(line);

                        /// COMPILATION IS BROKEN DUE TO A BUG IN BOOST 1.53
                        std::transform(tok.begin(), tok.end(), std::back_inserter(j_indeces), &boost::lexical_cast<int, std::string>);

                        // third line is garbage again
                        getline(input_file, line);

                        // read the block of max _basis_size lines + the following header
                        for (int i = 0; i < basis_set_size; i++) {
                            std::vector<std::string> row=GetLineAndSplit(input_file, "\t ");

                            int i_index = boost::lexical_cast<int>(row.front());
                            row.erase(row.begin());

                            std::vector<int>::iterator j_iter = j_indeces.begin();

                           for (std::string& coefficient: row) {
                                int j_index = *j_iter;
                                orbitals.AOOverlap()(i_index - 1, j_index - 1) = boost::lexical_cast<double>(coefficient);
                                orbitals.AOOverlap()(j_index - 1, i_index - 1) = boost::lexical_cast<double>(coefficient);
                                j_iter++;
                            }

                        }

                        // clear the index for the next block
                        j_indeces.clear();
                    } // end of the blocks

                    CTP_LOG(ctp::logDEBUG, *_pLog) << "Read the overlap matrix" << flush;
                } // end of the if "Overlap" found

                /*
                 * TODO Self-energy of external charges
                 */
                std::string::size_type self_energy_pos = line.find("Self energy of the charges");

                if (self_energy_pos != std::string::npos) {
                    CTP_LOG(ctp::logDEBUG, *_pLog) << "Getting the self energy\n";
                    std::vector<std::string> block;
                    std::vector<std::string> energy;
                    boost::algorithm::split(block, line, boost::is_any_of("="), boost::algorithm::token_compress_on);
                    boost::algorithm::split(energy, block[1], boost::is_any_of("\t "), boost::algorithm::token_compress_on);

                    orbitals.setSelfEnergy(conv_Hrt_eV * boost::lexical_cast<double> (energy[1]));

                    CTP_LOG(ctp::logDEBUG, *_pLog) << "Self energy " << orbitals.getSelfEnergy() << flush;

                }

                // check if all information has been accumulated and quit
                if (has_basis_set_size &&
                        has_overlap_matrix &&
                        has_charges &&
                        has_qm_energy &&
                        has_self_energy
                        ) break;

            } // end of reading the file line-by-line

            CTP_LOG(ctp::logDEBUG, *_pLog) << "Done parsing" << flush;
            return true;
        }

        void NWChem::WriteBasisset(ofstream& nw_file, std::vector<QMAtom*>& qmatoms) {

      std::vector<std::string> UniqueElements = FindUniqueElements(qmatoms);
      BasisSet bs;
      bs.LoadBasisSet(_basisset_name);
      CTP_LOG(ctp::logDEBUG, *_pLog) << "Loaded Basis Set " << _basisset_name << flush;
      nw_file << "basis spherical" << endl;
      for (const std::string& element_name : UniqueElements) {
        const Element& element = bs.getElement(element_name);
        for (const Shell& shell : element) {
          //nwchem can only use S,P,SP,D,F,G shells so we split them up if not SP
          if (!shell.isCombined()) {
            // shell type, number primitives, scale factor
            nw_file << element_name << " " << boost::algorithm::to_lower_copy(shell.getType()) << endl;
            for (const GaussianPrimitive& gaussian : shell) {
              for (unsigned _icontr = 0; _icontr < gaussian._contraction.size(); _icontr++) {
                if (gaussian._contraction[_icontr] != 0.0) {
                  nw_file << FortranFormat(gaussian._decay) << " " << FortranFormat(gaussian._contraction[_icontr]) << endl;
                }
              }
            }
          } else {
            string type = shell.getType();
            for (unsigned i = 0; i < type.size(); ++i) {
              string subtype = string(type, i, 1);
              nw_file << element_name << " " << boost::algorithm::to_lower_copy(subtype) << endl;

              for (const GaussianPrimitive& gaussian : shell) {
                nw_file << FortranFormat(gaussian._decay) << " " << FortranFormat(gaussian._contraction[FindLmax(subtype)]) << endl;
              }
            }
          }
        }
      }
      nw_file << "end\n";
      nw_file << endl;

      return;
    }

    void NWChem::WriteECP(ofstream& nw_file, std::vector<QMAtom*>& qmatoms) {

      std::vector<std::string> UniqueElements = FindUniqueElements(qmatoms);

      BasisSet ecp;
      ecp.LoadPseudopotentialSet(_ecp_name);

      CTP_LOG(ctp::logDEBUG, *_pLog) << "Loaded Pseudopotentials " << _ecp_name << flush;

      for (const std::string& element_name : UniqueElements) {
        try {
          ecp.getElement(element_name);
        } catch (std::runtime_error& error) {
          CTP_LOG(ctp::logDEBUG, *_pLog) << "No pseudopotential for " << element_name << " available" << flush;
          continue;
        }
        const Element& element = ecp.getElement(element_name);
        // element name, [possibly indeces of centers], zero to indicate the end
        nw_file << element_name << " nelec " << element.getNcore() << endl;
        for (const Shell& shell : element) {
          string shelltype = shell.getType();
          if (shell.getLmax() == element.getLmax()) {
            shelltype = "ul";
          }
          nw_file << element_name << " " << shelltype << endl;
          for (const GaussianPrimitive& gaussian : shell) {
            nw_file << "    " << gaussian._power << " " << FortranFormat(gaussian._decay) << " " << FortranFormat(gaussian._contraction[0]) << endl;
          }
        }
      }
      nw_file << "end\n";
      nw_file << endl;
      return;
    }

        std::string NWChem::FortranFormat(const double &number) {
            std::stringstream ssnumber;
            if (number >= 0) {
                ssnumber << "    ";
            } else {
                ssnumber << "   ";
            }
            ssnumber << setiosflags(ios::fixed) << setprecision(15) << std::scientific << number;
            std::string snumber = ssnumber.str();
            return snumber;
        }

    }
}


// Boost.Geometry (aka GGL, Generic Geometry Library)
// This file is manually converted from PROJ4

// Copyright (c) 2008-2012 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017, 2018, 2019.
// Modifications copyright (c) 2017-2019, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Geometry Library by Barend Gehrels (Geodan, Amsterdam)

// Original copyright notice:

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

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

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#ifndef BOOST_GEOMETRY_PROJECTIONS_IMPL_PJ_INIT_HPP
#define BOOST_GEOMETRY_PROJECTIONS_IMPL_PJ_INIT_HPP

#include <cstdlib>
#include <string>
#include <vector>

#include <boost/algorithm/string.hpp>
#include <boost/mpl/find.hpp>
#include <boost/mpl/if.hpp>
#include <boost/range.hpp>
#include <boost/type_traits/is_same.hpp>

#include <boost/geometry/util/math.hpp>
#include <boost/geometry/util/condition.hpp>

#include <boost/geometry/srs/projections/dpar.hpp>
#include <boost/geometry/srs/projections/impl/dms_parser.hpp>
#include <boost/geometry/srs/projections/impl/pj_datum_set.hpp>
#include <boost/geometry/srs/projections/impl/pj_datums.hpp>
#include <boost/geometry/srs/projections/impl/pj_ell_set.hpp>
#include <boost/geometry/srs/projections/impl/pj_param.hpp>
#include <boost/geometry/srs/projections/impl/pj_units.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>
#include <boost/geometry/srs/projections/proj4.hpp>
#include <boost/geometry/srs/projections/spar.hpp>

namespace boost { namespace geometry { namespace projections
{

namespace detail
{

/************************************************************************/
/*                           pj_init_proj()                             */
/************************************************************************/

template <typename T>
inline void pj_init_proj(srs::detail::proj4_parameters const& params,
                         parameters<T> & par)
{
    par.id = pj_get_param_s(params, "proj");
}

template <typename T>
inline void pj_init_proj(srs::dpar::parameters<T> const& params,
                         parameters<T> & par)
{
    typename srs::dpar::parameters<T>::const_iterator it = pj_param_find(params, srs::dpar::proj);
    if (it != params.end())
    {
        par.id = static_cast<srs::dpar::value_proj>(it->template get_value<int>());
    }
}

template <typename T, BOOST_GEOMETRY_PROJECTIONS_DETAIL_TYPENAME_PX>
inline void pj_init_proj(srs::spar::parameters<BOOST_GEOMETRY_PROJECTIONS_DETAIL_PX> const& ,
                         parameters<T> & par)
{
    typedef srs::spar::parameters<BOOST_GEOMETRY_PROJECTIONS_DETAIL_PX> params_type;
    typedef typename geometry::tuples::find_if
        <
            params_type,
            srs::spar::detail::is_param_tr<srs::spar::detail::proj_traits>::pred
        >::type proj_type;

    static const bool is_found = geometry::tuples::is_found<proj_type>::value;

    BOOST_MPL_ASSERT_MSG((is_found), PROJECTION_NOT_NAMED, (params_type));

    par.id = srs::spar::detail::proj_traits<proj_type>::id;
}

/************************************************************************/
/*                           pj_init_units()                            */
/************************************************************************/

template <typename T, bool Vertical>
inline void pj_init_units(srs::detail::proj4_parameters const& params,
                          T & to_meter,
                          T & fr_meter,
                          T const& default_to_meter,
                          T const& default_fr_meter)
{
    std::string s;
    std::string units = pj_get_param_s(params, Vertical ? "vunits" : "units");
    if (! units.empty())
    {
        static const int n = sizeof(pj_units) / sizeof(pj_units[0]);
        int index = -1;
        for (int i = 0; i < n && index == -1; i++)
        {
            if(pj_units[i].id == units)
            {
                index = i;
            }
        }

        if (index == -1) {
            BOOST_THROW_EXCEPTION( projection_exception(error_unknow_unit_id) );
        }
        s = pj_units[index].to_meter;
    }

    if (s.empty())
    {
        s = pj_get_param_s(params, Vertical ? "vto_meter" : "to_meter");
    }

    // TODO: numerator and denominator could be taken from pj_units
    if (! s.empty())
    {
        std::size_t const pos = s.find('/');
        if (pos == std::string::npos)
        {
            to_meter = geometry::str_cast<T>(s);
        }
        else
        {
            T const numerator = geometry::str_cast<T>(s.substr(0, pos));
            T const denominator = geometry::str_cast<T>(s.substr(pos + 1));
            if (numerator == 0.0 || denominator == 0.0)
            {
                BOOST_THROW_EXCEPTION( projection_exception(error_unit_factor_less_than_0) );
            }
            to_meter = numerator / denominator;
        }
        if (to_meter == 0.0)
        {
            BOOST_THROW_EXCEPTION( projection_exception(error_unit_factor_less_than_0) );
        }
        fr_meter = 1. / to_meter;
    }
    else
    {
        to_meter = default_to_meter;
        fr_meter = default_fr_meter;
    }
}

template <typename T, bool Vertical>
inline void pj_init_units(srs::dpar::parameters<T> const& params,
                          T & to_meter,
                          T & fr_meter,
                          T const& default_to_meter,
                          T const& default_fr_meter)
{
    typename srs::dpar::parameters<T>::const_iterator
        it = pj_param_find(params, Vertical ? srs::dpar::vunits : srs::dpar::units);
    if (it != params.end())
    {
        static const int n = sizeof(pj_units) / sizeof(pj_units[0]);
        const int i = it->template get_value<int>();
        if (i >= 0 && i < n)
        {
            T const numerator = pj_units[i].numerator;
            T const denominator = pj_units[i].denominator;
            if (numerator == 0.0 || denominator == 0.0)
            {
                BOOST_THROW_EXCEPTION( projection_exception(error_unit_factor_less_than_0) );
            }
            to_meter = numerator / denominator;
            fr_meter = 1. / to_meter;
        }
        else
        {
            BOOST_THROW_EXCEPTION( projection_exception(error_unknow_unit_id) );
        }
    }
    else
    {
        it = pj_param_find(params, Vertical ? srs::dpar::vto_meter : srs::dpar::to_meter);
        if (it != params.end())
        {
            to_meter = it->template get_value<T>();
            fr_meter = 1. / to_meter;
        }
        else
        {
            to_meter = default_to_meter;
            fr_meter = default_fr_meter;
        }
    }
}

template
<
    typename Params,
    bool Vertical,
    int UnitsI = geometry::tuples::find_index_if
        <
            Params,
            boost::mpl::if_c
                <
                    Vertical,
                    srs::spar::detail::is_param_t<srs::spar::vunits>,
                    srs::spar::detail::is_param_tr<srs::spar::detail::units_traits>
                >::type::template pred
        >::value,
    int ToMeterI = geometry::tuples::find_index_if
        <
            Params,
            boost::mpl::if_c
                <
                    Vertical,
                    srs::spar::detail::is_param_t<srs::spar::vto_meter>,
                    srs::spar::detail::is_param_t<srs::spar::to_meter>
                >::type::template pred
        >::value,
    int N = boost::tuples::length<Params>::value
>
struct pj_init_units_static
    : pj_init_units_static<Params, Vertical, UnitsI, N, N>
{};

template <typename Params, bool Vertical, int UnitsI, int N>
struct pj_init_units_static<Params, Vertical, UnitsI, N, N>
{
    static const int n = sizeof(pj_units) / sizeof(pj_units[0]);
    static const int i = srs::spar::detail::units_traits
                    <
                        typename boost::tuples::element<UnitsI, Params>::type
                    >::id;
    static const bool is_valid = i >= 0 && i < n;

    BOOST_MPL_ASSERT_MSG((is_valid), UNKNOWN_UNIT_ID, (Params));

    template <typename T>
    static void apply(Params const& ,
                      T & to_meter, T & fr_meter,
                      T const& , T const& )
    {
        T const numerator = pj_units[i].numerator;
        T const denominator = pj_units[i].denominator;
        if (numerator == 0.0 || denominator == 0.0)
        {
            BOOST_THROW_EXCEPTION( projection_exception(error_unit_factor_less_than_0) );
        }
        to_meter = numerator / denominator;
        fr_meter = 1. / to_meter;
    }
};

template <typename Params, bool Vertical, int ToMeterI, int N>
struct pj_init_units_static<Params, Vertical, N, ToMeterI, N>
{
    template <typename T>
    static void apply(Params const& params,
                      T & to_meter, T & fr_meter,
                      T const& , T const& )
    {
        to_meter = boost::tuples::get<ToMeterI>(params).value;
        fr_meter = 1. / to_meter;
    }
};

template <typename Params, bool Vertical, int N>
struct pj_init_units_static<Params, Vertical, N, N, N>
{
    template <typename T>
    static void apply(Params const& ,
                      T & to_meter, T & fr_meter,
                      T const& default_to_meter, T const& default_fr_meter)
    {
        to_meter = default_to_meter;
        fr_meter = default_fr_meter;
    }
};

template <typename T, bool Vertical, BOOST_GEOMETRY_PROJECTIONS_DETAIL_TYPENAME_PX>
inline void pj_init_units(srs::spar::parameters<BOOST_GEOMETRY_PROJECTIONS_DETAIL_PX> const& params,
                          T & to_meter,
                          T & fr_meter,
                          T const& default_to_meter,
                          T const& default_fr_meter)
{
    pj_init_units_static
        <
            srs::spar::parameters<BOOST_GEOMETRY_PROJECTIONS_DETAIL_PX>,
            Vertical
        >::apply(params, to_meter, fr_meter, default_to_meter, default_fr_meter);
}

/************************************************************************/
/*                           pj_init_pm()                               */
/************************************************************************/

template <typename T>
inline void pj_init_pm(srs::detail::proj4_parameters const& params, T& val)
{
    std::string pm = pj_get_param_s(params, "pm");
    if (! pm.empty())
    {
        int n = sizeof(pj_prime_meridians) / sizeof(pj_prime_meridians[0]);
        for (int i = 0; i < n ; i++)
        {
            if(pj_prime_meridians[i].id == pm)
            {
                val = pj_prime_meridians[i].deg * math::d2r<T>();
                return;
            }
        }

        // TODO: Is this try-catch needed?
        // In other cases the bad_str_cast exception is simply thrown
        BOOST_TRY
        {
            val = dms_parser<T, true>::apply(pm).angle();
            return;
        }
        BOOST_CATCH(geometry::bad_str_cast const&)
        {
            BOOST_THROW_EXCEPTION( projection_exception(error_unknown_prime_meridian) );
        }
        BOOST_CATCH_END
    }

    val = 0.0;
}

template <typename T>
inline void pj_init_pm(srs::dpar::parameters<T> const& params, T& val)
{
    typename srs::dpar::parameters<T>::const_iterator it = pj_param_find(params, srs::dpar::pm);
    if (it != params.end())
    {
        if (it->template is_value_set<int>())
        {
            int n = sizeof(pj_prime_meridians) / sizeof(pj_prime_meridians[0]);
            int i = it->template get_value<int>();
            if (i >= 0 && i < n)
            {
                val = pj_prime_meridians[i].deg * math::d2r<T>();
                return;
            }
            else
            {
                BOOST_THROW_EXCEPTION( projection_exception(error_unknown_prime_meridian) );
            }
        }
        else if (it->template is_value_set<T>())
        {
            val = it->template get_value<T>() * math::d2r<T>();
            return;
        }
    }

    val = 0.0;
}

template
<
    typename Params,
    int I = geometry::tuples::find_index_if
        <
            Params,
            srs::spar::detail::is_param_tr<srs::spar::detail::pm_traits>::pred
        >::value,
    int N = boost::tuples::length<Params>::value
>
struct pj_init_pm_static
{
    template <typename T>
    static void apply(Params const& params, T & val)
    {
        typedef typename boost::tuples::element<I, Params>::type param_type;

        val = srs::spar::detail::pm_traits<param_type>::value(boost::tuples::get<I>(params));
    }
};
template <typename Params, int N>
struct pj_init_pm_static<Params, N, N>
{
    template <typename T>
    static void apply(Params const& , T & val)
    {
        val = 0;
    }
};

template <typename T, BOOST_GEOMETRY_PROJECTIONS_DETAIL_TYPENAME_PX>
inline void pj_init_pm(srs::spar::parameters<BOOST_GEOMETRY_PROJECTIONS_DETAIL_PX> const& params, T& val)
{
    pj_init_pm_static
        <
            srs::spar::parameters<BOOST_GEOMETRY_PROJECTIONS_DETAIL_PX>
        >::apply(params, val);
}

/************************************************************************/
/*                              pj_init()                               */
/*                                                                      */
/*      Main entry point for initialing a PJ projections                */
/*      definition.                                                     */
/************************************************************************/
template <typename T, typename Params>
inline parameters<T> pj_init(Params const& params)
{
    parameters<T> pin;

    // find projection -> implemented in projection factory
    pj_init_proj(params, pin);
    // exception thrown in projection<>
    // TODO: consider throwing here both projection_unknown_id_exception and
    // projection_not_named_exception in order to throw before other exceptions
    //if (pin.name.empty())
    //{ BOOST_THROW_EXCEPTION( projection_not_named_exception() ); }

    // NOTE: proj4 gets defaults from "proj_def.dat".
    // In Boost.Geometry this is emulated by manually setting them in
    // pj_ell_init and projections aea, lcc and lagrng

    /* set datum parameters */
    pj_datum_init(params, pin);

    /* set ellipsoid/sphere parameters */
    pj_ell_init(params, pin.a, pin.es);

    pin.a_orig = pin.a;
    pin.es_orig = pin.es;

    pin.e = sqrt(pin.es);
    pin.ra = 1. / pin.a;
    pin.one_es = 1. - pin.es;
    if (pin.one_es == 0.) {
        BOOST_THROW_EXCEPTION( projection_exception(error_eccentricity_is_one) );
    }
    pin.rone_es = 1./pin.one_es;

    /* Now that we have ellipse information check for WGS84 datum */
    if( pin.datum_type == datum_3param
        && pin.datum_params[0] == 0.0
        && pin.datum_params[1] == 0.0
        && pin.datum_params[2] == 0.0
        && pin.a == 6378137.0
        && geometry::math::abs(pin.es - 0.006694379990) < 0.000000000050 )/*WGS84/GRS80*/
    {
        pin.datum_type = datum_wgs84;
    }

    /* set pin.geoc coordinate system */
    pin.geoc = (pin.es && pj_get_param_b<srs::spar::geoc>(params, "geoc", srs::dpar::geoc));

    /* over-ranging flag */
    pin.over = pj_get_param_b<srs::spar::over>(params, "over", srs::dpar::over);

    /* longitude center for wrapping */
    pin.is_long_wrap_set = pj_param_r<srs::spar::lon_wrap>(params, "lon_wrap", srs::dpar::lon_wrap, pin.long_wrap_center);

    /* central meridian */
    pin.lam0 = pj_get_param_r<T, srs::spar::lon_0>(params, "lon_0", srs::dpar::lon_0);

    /* central latitude */
    pin.phi0 = pj_get_param_r<T, srs::spar::lat_0>(params, "lat_0", srs::dpar::lat_0);

    /* false easting and northing */
    pin.x0 = pj_get_param_f<T, srs::spar::x_0>(params, "x_0", srs::dpar::x_0);
    pin.y0 = pj_get_param_f<T, srs::spar::y_0>(params, "y_0", srs::dpar::y_0);

    /* general scaling factor */
    if (pj_param_f<srs::spar::k_0>(params, "k_0", srs::dpar::k_0, pin.k0)) {
        /* empty */
    } else if (pj_param_f<srs::spar::k>(params, "k", srs::dpar::k, pin.k0)) {
        /* empty */
    } else
        pin.k0 = 1.;
    if (pin.k0 <= 0.) {
        BOOST_THROW_EXCEPTION( projection_exception(error_k_less_than_zero) );
    }

    /* set units */
    pj_init_units<T, false>(params, pin.to_meter, pin.fr_meter, 1., 1.);
    pj_init_units<T, true>(params, pin.vto_meter, pin.vfr_meter, pin.to_meter, pin.fr_meter);

    /* prime meridian */
    pj_init_pm(params, pin.from_greenwich);

    return pin;
}

} // namespace detail
}}} // namespace boost::geometry::projections

#endif // BOOST_GEOMETRY_PROJECTIONS_IMPL_PJ_INIT_HPP


#ifndef CMDSTAN_COMMAND_HPP
#define CMDSTAN_COMMAND_HPP

#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/math/special_functions/fpclassify.hpp>
#include <boost/random/additive_combine.hpp>  // L'Ecuyer RNG
#include <boost/random/uniform_real_distribution.hpp>

#include <stan/version.hpp>
#include <stan/io/cmd_line.hpp>
#include <stan/io/dump.hpp>
#include <stan/io/json/json_data.hpp>
#include <stan/io/json/json_data_handler.hpp>
#include <stan/io/json/json_error.hpp>
#include <stan/io/json/json_handler.hpp>
#include <stan/io/json/json_parser.hpp>

#include <stan/services/arguments/arg_adapt.hpp>
#include <stan/services/arguments/arg_adapt_delta.hpp>
#include <stan/services/arguments/arg_adapt_engaged.hpp>
#include <stan/services/arguments/arg_adapt_gamma.hpp>
#include <stan/services/arguments/arg_adapt_init_buffer.hpp>
#include <stan/services/arguments/arg_adapt_kappa.hpp>
#include <stan/services/arguments/arg_adapt_t0.hpp>
#include <stan/services/arguments/arg_adapt_term_buffer.hpp>
#include <stan/services/arguments/arg_adapt_window.hpp>
#include <stan/services/arguments/arg_bfgs.hpp>
#include <stan/services/arguments/arg_data.hpp>
#include <stan/services/arguments/arg_data_file.hpp>
#include <stan/services/arguments/arg_dense_e.hpp>
#include <stan/services/arguments/arg_diag_e.hpp>
#include <stan/services/arguments/arg_diagnose.hpp>
#include <stan/services/arguments/arg_diagnostic_file.hpp>
#include <stan/services/arguments/arg_engine.hpp>
#include <stan/services/arguments/arg_fail.hpp>
#include <stan/services/arguments/arg_fixed_param.hpp>
#include <stan/services/arguments/arg_history_size.hpp>
#include <stan/services/arguments/arg_hmc.hpp>
#include <stan/services/arguments/arg_id.hpp>
#include <stan/services/arguments/arg_init.hpp>
#include <stan/services/arguments/arg_init_alpha.hpp>
#include <stan/services/arguments/arg_int_time.hpp>
#include <stan/services/arguments/arg_iter.hpp>
#include <stan/services/arguments/arg_lbfgs.hpp>
#include <stan/services/arguments/arg_max_depth.hpp>
#include <stan/services/arguments/arg_method.hpp>
#include <stan/services/arguments/arg_metric.hpp>
#include <stan/services/arguments/arg_newton.hpp>
#include <stan/services/arguments/arg_num_samples.hpp>
#include <stan/services/arguments/arg_num_warmup.hpp>
#include <stan/services/arguments/arg_nuts.hpp>
#include <stan/services/arguments/arg_optimize.hpp>
#include <stan/services/arguments/arg_optimize_algo.hpp>
#include <stan/services/arguments/arg_output.hpp>
#include <stan/services/arguments/arg_output_file.hpp>
#include <stan/services/arguments/arg_random.hpp>
#include <stan/services/arguments/arg_refresh.hpp>
#include <stan/services/arguments/arg_rwm.hpp>
#include <stan/services/arguments/arg_sample.hpp>
#include <stan/services/arguments/arg_sample_algo.hpp>
#include <stan/services/arguments/arg_save_iterations.hpp>
#include <stan/services/arguments/arg_save_warmup.hpp>
#include <stan/services/arguments/arg_seed.hpp>
#include <stan/services/arguments/arg_static.hpp>
#include <stan/services/arguments/arg_stepsize.hpp>
#include <stan/services/arguments/arg_stepsize_jitter.hpp>
#include <stan/services/arguments/arg_test.hpp>
#include <stan/services/arguments/arg_test_grad_eps.hpp>
#include <stan/services/arguments/arg_test_grad_err.hpp>
#include <stan/services/arguments/arg_test_gradient.hpp>
#include <stan/services/arguments/arg_thin.hpp>
#include <stan/services/arguments/arg_tolerance.hpp>
#include <stan/services/arguments/arg_unit_e.hpp>
#include <stan/services/arguments/argument.hpp>
#include <stan/services/arguments/argument_parser.hpp>
#include <stan/services/arguments/argument_probe.hpp>
#include <stan/services/arguments/categorical_argument.hpp>
#include <stan/services/arguments/list_argument.hpp>
#include <stan/services/arguments/singleton_argument.hpp>
#include <stan/services/arguments/unvalued_argument.hpp>
#include <stan/services/arguments/valued_argument.hpp>
#include <stan/services/sample/mcmc_writer.hpp>
#include <stan/mcmc/fixed_param_sampler.hpp>
#include <stan/mcmc/hmc/static/adapt_unit_e_static_hmc.hpp>
#include <stan/mcmc/hmc/static/adapt_diag_e_static_hmc.hpp>
#include <stan/mcmc/hmc/static/adapt_dense_e_static_hmc.hpp>
#include <stan/mcmc/hmc/nuts/adapt_unit_e_nuts.hpp>
#include <stan/mcmc/hmc/nuts/adapt_diag_e_nuts.hpp>
#include <stan/mcmc/hmc/nuts/adapt_dense_e_nuts.hpp>

#include <stan/model/gradient.hpp>
#include <stan/model/test_gradients.hpp>

#include <stan/optimization/newton.hpp>
#include <stan/optimization/bfgs.hpp>

#include <stan/variational/advi.hpp>

#include <stan/services/init/initialize_state.hpp>
#include <stan/services/io/do_print.hpp>
#include <stan/services/io/write_error_msg.hpp>
#include <stan/services/io/write_iteration.hpp>
#include <stan/services/io/write_model.hpp>
#include <stan/services/io/write_stan.hpp>
#include <stan/services/mcmc/sample.hpp>
#include <stan/services/mcmc/warmup.hpp>
#include <stan/services/optimize/do_bfgs_optimize.hpp>
#include <stan/services/sample/init_adapt.hpp>
#include <stan/services/sample/init_nuts.hpp>
#include <stan/services/sample/init_static_hmc.hpp>
#include <stan/services/sample/init_windowed_adapt.hpp>
#include <stan/services/sample/generate_transitions.hpp>
#include <stan/services/sample/progress.hpp>

#include <stan/interface_callbacks/interrupt/noop.hpp>
#include <stan/interface_callbacks/var_context_factory/dump_factory.hpp>
#include <stan/interface_callbacks/writer/base_writer.hpp>
#include <stan/interface_callbacks/writer/stream_writer.hpp>

#include <fstream>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <string>
#include <vector>

namespace stan {
  namespace services {

    class null_fstream : public std::fstream {
    public:
      null_fstream() {}
    };

    template <class Model>
    int command(int argc, const char* argv[]) {
      stan::interface_callbacks::writer::stream_writer info(std::cout);
      stan::interface_callbacks::writer::stream_writer err(std::cout);

      std::vector<stan::services::argument*> valid_arguments;
      valid_arguments.push_back(new stan::services::arg_id());
      valid_arguments.push_back(new stan::services::arg_data());
      valid_arguments.push_back(new stan::services::arg_init());
      valid_arguments.push_back(new stan::services::arg_random());
      valid_arguments.push_back(new stan::services::arg_output());

      stan::services::argument_parser parser(valid_arguments);
      int err_code = parser.parse_args(argc, argv, info, err);
      if (err_code != 0) {
        std::cout << "Failed to parse arguments, terminating Stan" << std::endl;
        return err_code;
      }

      if (parser.help_printed())
        return err_code;

      //////////////////////////////////////////////////
      //                  Input/Output                //
      //////////////////////////////////////////////////

      // Data input
      std::string data_file
        = dynamic_cast<stan::services::string_argument*>
        (parser.arg("data")->arg("file"))->value();

      std::fstream data_stream(data_file.c_str(),
                               std::fstream::in);
      if (data_file != "" && (data_stream.rdstate() & std::ifstream::failbit)) {
        std::cout << std::endl;
        std::cout << "Can't open specified data file, \"" << data_file << "\"" << std::endl;
        return stan::services::error_codes::NOINPUT;
      }
      stan::io::dump data_var_context(data_stream);
      data_stream.close();

      // Sample output
      std::string output_file = dynamic_cast<stan::services::string_argument*>(
                                parser.arg("output")->arg("file"))->value();
      std::fstream* output_stream = 0;
      if (output_file != "") {
        output_stream = new std::fstream(output_file.c_str(),
                                         std::fstream::out);
      } else {
        output_stream = new null_fstream();
      }

      // Diagnostic output
      std::string diagnostic_file
        = dynamic_cast<stan::services::string_argument*>
          (parser.arg("output")->arg("diagnostic_file"))->value();

      std::fstream* diagnostic_stream = 0;
      if (diagnostic_file != "") {
        diagnostic_stream = new std::fstream(diagnostic_file.c_str(),
                                             std::fstream::out);
      } else {
        diagnostic_stream = new null_fstream();
      }

      // Refresh rate
      int refresh = dynamic_cast<stan::services::int_argument*>(
                    parser.arg("output")->arg("refresh"))->value();

      // Identification
      unsigned int id = dynamic_cast<stan::services::int_argument*>
        (parser.arg("id"))->value();

      stan::interface_callbacks::writer::stream_writer
        sample_writer(*output_stream, "# "),
        diagnostic_writer(*diagnostic_stream, "# ");

      //////////////////////////////////////////////////
      //            Random number generator           //
      //////////////////////////////////////////////////

      unsigned int random_seed = 0;

      stan::services::u_int_argument* random_arg
        = dynamic_cast<stan::services::u_int_argument*>
        (parser.arg("random")->arg("seed"));

      if (random_arg->is_default()) {
        random_seed
          = (boost::posix_time::microsec_clock::universal_time() -
             boost::posix_time::ptime(boost::posix_time::min_date_time))
          .total_milliseconds();

        random_arg->set_value(random_seed);

      } else {
        random_seed = random_arg->value();
      }

      typedef boost::ecuyer1988 rng_t;  // (2**50 = 1T samples, 1000 chains)
      rng_t base_rng(random_seed);

      // Advance generator to avoid process conflicts
      static boost::uintmax_t DISCARD_STRIDE
        = static_cast<boost::uintmax_t>(1) << 50;
      base_rng.discard(DISCARD_STRIDE * (id - 1));

      //////////////////////////////////////////////////
      //                Initialize Model              //
      //////////////////////////////////////////////////

      Model model(data_var_context, &std::cout);

      Eigen::VectorXd cont_params = Eigen::VectorXd::Zero(model.num_params_r());

      parser.print(info);
      info();

      if (output_stream) {
        io::write_stan(sample_writer);
        io::write_model(sample_writer, model.model_name());
        parser.print(sample_writer);
      }

      if (diagnostic_stream) {
        io::write_stan(diagnostic_writer);
        io::write_model(diagnostic_writer, model.model_name());
        parser.print(diagnostic_writer);
      }

      std::string init = dynamic_cast<stan::services::string_argument*>(
                         parser.arg("init"))->value();

      interface_callbacks::var_context_factory::dump_factory var_context_factory;
      if (!init::initialize_state<interface_callbacks::var_context_factory::dump_factory>
          (init, cont_params, model, base_rng, info,
           var_context_factory))
        return stan::services::error_codes::SOFTWARE;

      //////////////////////////////////////////////////
      //               Model Diagnostics              //
      //////////////////////////////////////////////////

      if (parser.arg("method")->arg("diagnose")) {
        std::vector<double> cont_vector(cont_params.size());
        for (int i = 0; i < cont_params.size(); ++i)
          cont_vector.at(i) = cont_params(i);
        std::vector<int> disc_vector;

        stan::services::list_argument* test = dynamic_cast<stan::services::list_argument*>
                              (parser.arg("method")->arg("diagnose")->arg("test"));

        if (test->value() == "gradient") {
          std::cout << std::endl << "TEST GRADIENT MODE" << std::endl;

          double epsilon = dynamic_cast<stan::services::real_argument*>
                           (test->arg("gradient")->arg("epsilon"))->value();

          double error = dynamic_cast<stan::services::real_argument*>
                         (test->arg("gradient")->arg("error"))->value();

          int num_failed
            = stan::model::test_gradients<true, true>
            (model, cont_vector, disc_vector,
             epsilon, error, info);

          if (output_stream) {
            num_failed
              = stan::model::test_gradients<true, true>
              (model, cont_vector, disc_vector,
               epsilon, error, sample_writer);
          }

          if (diagnostic_stream) {
            num_failed
              = stan::model::test_gradients<true, true>
              (model, cont_vector, disc_vector,
               epsilon, error, diagnostic_writer);
          }

          (void) num_failed; // FIXME: do something with the number failed

          return stan::services::error_codes::OK;

        }
      }

      //////////////////////////////////////////////////
      //           Optimization Algorithms            //
      //////////////////////////////////////////////////

      if (parser.arg("method")->arg("optimize")) {
        std::vector<double> cont_vector(cont_params.size());
        for (int i = 0; i < cont_params.size(); ++i)
          cont_vector.at(i) = cont_params(i);
        std::vector<int> disc_vector;

        stan::services::list_argument* algo = dynamic_cast<stan::services::list_argument*>
                              (parser.arg("method")->arg("optimize")->arg("algorithm"));

        int num_iterations = dynamic_cast<stan::services::int_argument*>(
                             parser.arg("method")->arg("optimize")->arg("iter"))->value();

        bool save_iterations
          = dynamic_cast<stan::services::bool_argument*>(parser.arg("method")
                                         ->arg("optimize")
                                         ->arg("save_iterations"))->value();
        if (output_stream) {
          std::vector<std::string> names;
          names.push_back("lp__");
          model.constrained_param_names(names, true, true);

          (*output_stream) << names.at(0);
          for (size_t i = 1; i < names.size(); ++i) {
            (*output_stream) << "," << names.at(i);
          }
          (*output_stream) << std::endl;
        }

        double lp(0);
        int return_code = stan::services::error_codes::CONFIG;
        if (algo->value() == "newton") {
          std::vector<double> gradient;
          try {
            lp = model.template log_prob<false, false>
              (cont_vector, disc_vector, &std::cout);
          } catch (const std::exception& e) {
            io::write_error_msg(err, e);
            lp = -std::numeric_limits<double>::infinity();
          }

          std::cout << "initial log joint probability = " << lp << std::endl;
          if (save_iterations) {
            io::write_iteration(model, base_rng,
                                lp, cont_vector, disc_vector,
                                info, sample_writer);
          }

          double lastlp = lp * 1.1;
          int m = 0;
          std::cout << "(lp - lastlp) / lp > 1e-8: "
                    << ((lp - lastlp) / fabs(lp)) << std::endl;
          while ((lp - lastlp) / fabs(lp) > 1e-8) {
            lastlp = lp;
            lp = stan::optimization::newton_step
              (model, cont_vector, disc_vector);
            std::cout << "Iteration ";
            std::cout << std::setw(2) << (m + 1) << ". ";
            std::cout << "Log joint probability = " << std::setw(10) << lp;
            std::cout << ". Improved by " << (lp - lastlp) << ".";
            std::cout << std::endl;
            std::cout.flush();
            m++;

            if (save_iterations) {
              io::write_iteration(model, base_rng,
                                  lp, cont_vector, disc_vector,
                                  info, sample_writer);
            }
          }
          return_code = stan::services::error_codes::OK;
        } else if (algo->value() == "bfgs") {
          interface_callbacks::interrupt::noop callback;
          typedef stan::optimization::BFGSLineSearch
            <Model,stan::optimization::BFGSUpdate_HInv<> > Optimizer;
          Optimizer bfgs(model, cont_vector, disc_vector, &std::cout);

          bfgs._ls_opts.alpha0 = dynamic_cast<stan::services::real_argument*>(
                         algo->arg("bfgs")->arg("init_alpha"))->value();
          bfgs._conv_opts.tolAbsF = dynamic_cast<stan::services::real_argument*>(
                         algo->arg("bfgs")->arg("tol_obj"))->value();
          bfgs._conv_opts.tolRelF = dynamic_cast<stan::services::real_argument*>(
                         algo->arg("bfgs")->arg("tol_rel_obj"))->value();
          bfgs._conv_opts.tolAbsGrad = dynamic_cast<stan::services::real_argument*>(
                         algo->arg("bfgs")->arg("tol_grad"))->value();
          bfgs._conv_opts.tolRelGrad = dynamic_cast<stan::services::real_argument*>(
                         algo->arg("bfgs")->arg("tol_rel_grad"))->value();
          bfgs._conv_opts.tolAbsX = dynamic_cast<stan::services::real_argument*>(
                         algo->arg("bfgs")->arg("tol_param"))->value();
          bfgs._conv_opts.maxIts = num_iterations;

          return_code = optimize::do_bfgs_optimize(model,bfgs, base_rng,
                                                   lp, cont_vector, disc_vector,
                                                   sample_writer, info,
                                                   save_iterations, refresh,
                                                   callback);
        } else if (algo->value() == "lbfgs") {
          interface_callbacks::interrupt::noop callback;
          typedef stan::optimization::BFGSLineSearch
            <Model,stan::optimization::LBFGSUpdate<> > Optimizer;

          Optimizer bfgs(model, cont_vector, disc_vector, &std::cout);

          bfgs.get_qnupdate().set_history_size(dynamic_cast<services::int_argument*>(
                         algo->arg("lbfgs")->arg("history_size"))->value());
          bfgs._ls_opts.alpha0 = dynamic_cast<services::real_argument*>(
                         algo->arg("lbfgs")->arg("init_alpha"))->value();
          bfgs._conv_opts.tolAbsF = dynamic_cast<services::real_argument*>(
                         algo->arg("lbfgs")->arg("tol_obj"))->value();
          bfgs._conv_opts.tolRelF = dynamic_cast<services::real_argument*>(
                         algo->arg("lbfgs")->arg("tol_rel_obj"))->value();
          bfgs._conv_opts.tolAbsGrad = dynamic_cast<services::real_argument*>(
                         algo->arg("lbfgs")->arg("tol_grad"))->value();
          bfgs._conv_opts.tolRelGrad = dynamic_cast<services::real_argument*>(
                         algo->arg("lbfgs")->arg("tol_rel_grad"))->value();
          bfgs._conv_opts.tolAbsX = dynamic_cast<services::real_argument*>(
                         algo->arg("lbfgs")->arg("tol_param"))->value();
          bfgs._conv_opts.maxIts = num_iterations;

          return_code = optimize::do_bfgs_optimize(model, bfgs, base_rng,
                                                   lp, cont_vector, disc_vector,
                                                   sample_writer, info,
                                                   save_iterations, refresh,
                                                   callback);
        } else {
          return_code = stan::services::error_codes::CONFIG;
        }

        io::write_iteration(model, base_rng,
                            lp, cont_vector, disc_vector,
                            info, sample_writer);
        output_stream->close();
        diagnostic_stream->close();
        delete output_stream;
        delete diagnostic_stream;
        return return_code;
      }

      //////////////////////////////////////////////////
      //              Sampling Algorithms             //
      //////////////////////////////////////////////////

      if (parser.arg("method")->arg("sample")) {
        // Check timing
        clock_t start_check = clock();

        double init_log_prob;
        Eigen::VectorXd init_grad = Eigen::VectorXd::Zero(model.num_params_r());

        stan::model::gradient(model, cont_params, init_log_prob,
                              init_grad, &std::cout);

        clock_t end_check = clock();
        double deltaT
          = static_cast<double>(end_check - start_check) / CLOCKS_PER_SEC;

        std::cout << std::endl;
        std::cout << "Gradient evaluation took " << deltaT
                  << " seconds" << std::endl;
        std::cout << "1000 transitions using 10 leapfrog steps "
                  << "per transition would take "
                  << 1e4 * deltaT << " seconds." << std::endl;
        std::cout << "Adjust your expectations accordingly!"
                  << std::endl << std::endl;
        std::cout << std::endl;

        stan::services::sample::mcmc_writer<Model,
                                            interface_callbacks::writer::stream_writer,
                                            interface_callbacks::writer::stream_writer,
                                            interface_callbacks::writer::stream_writer>
          writer(sample_writer, diagnostic_writer, info);

        // Sampling parameters
        int num_warmup = dynamic_cast<stan::services::int_argument*>(
                          parser.arg("method")->arg("sample")->arg("num_warmup"))->value();

        int num_samples = dynamic_cast<stan::services::int_argument*>(
                          parser.arg("method")->arg("sample")->arg("num_samples"))->value();

        int num_thin = dynamic_cast<stan::services::int_argument*>(
                       parser.arg("method")->arg("sample")->arg("thin"))->value();

        bool save_warmup = dynamic_cast<stan::services::bool_argument*>(
                           parser.arg("method")->arg("sample")->arg("save_warmup"))->value();

        stan::mcmc::sample s(cont_params, 0, 0);

        double warmDeltaT;
        double sampleDeltaT;

        // Sampler
        stan::mcmc::base_mcmc* sampler_ptr = 0;

        stan::services::list_argument* algo
          = dynamic_cast<stan::services::list_argument*>
            (parser.arg("method")->arg("sample")->arg("algorithm"));

        stan::services::categorical_argument* adapt
          = dynamic_cast<stan::services::categorical_argument*>
            (parser.arg("method")->arg("sample")->arg("adapt"));
        bool adapt_engaged
          = dynamic_cast<stan::services::bool_argument*>(adapt->arg("engaged"))
            ->value();

        if (model.num_params_r() == 0 && algo->value() != "fixed_param") {
          std::cout
            << "Must use algorithm=fixed_param for "
            << "model that has no parameters."
            << std::endl;
          return -1;
        }

        if (algo->value() == "fixed_param") {
          sampler_ptr = new stan::mcmc::fixed_param_sampler();

          adapt_engaged = false;

          if (num_warmup != 0) {
            std::cout << "Warning: warmup will be skipped "
                      << "for the fixed parameter sampler!"
                      << std::endl;
            num_warmup = 0;
          }

        } else if (algo->value() == "rwm") {
          std::cout << algo->arg("rwm")->description() << std::endl;
          return 0;

        } else if (algo->value() == "hmc") {
          int engine_index = 0;

          stan::services::list_argument* engine
            = dynamic_cast<stan::services::list_argument*>
              (algo->arg("hmc")->arg("engine"));

          if (engine->value() == "static") {
            engine_index = 0;
          } else if (engine->value() == "nuts") {
            engine_index = 1;
          }

          int metric_index = 0;
          stan::services::list_argument* metric
            = dynamic_cast<stan::services::list_argument*>
              (algo->arg("hmc")->arg("metric"));
          if (metric->value() == "unit_e") {
            metric_index = 0;
          } else if (metric->value() == "diag_e") {
            metric_index = 1;
          } else if (metric->value() == "dense_e") {
            metric_index = 2;
          }

          int sampler_select = engine_index
            + 10 * metric_index
            + 100 * static_cast<int>(adapt_engaged);

          switch (sampler_select) {
            case 0: {
              typedef stan::mcmc::unit_e_static_hmc<Model, rng_t> sampler;
              sampler_ptr = new sampler(model, base_rng);
              if (!sample::init_static_hmc<sampler>(sampler_ptr, algo))
                return 0;
              break;
            }

            case 1: {
              typedef stan::mcmc::unit_e_nuts<Model, rng_t> sampler;
              sampler_ptr = new sampler(model, base_rng);
              if (!sample::init_nuts<sampler>(sampler_ptr, algo))
                return 0;
              break;
            }

            case 10: {
              typedef stan::mcmc::diag_e_static_hmc<Model, rng_t> sampler;
              sampler_ptr = new sampler(model, base_rng);
              if (!sample::init_static_hmc<sampler>(sampler_ptr, algo))
                return 0;
              break;
            }

            case 11: {
              typedef stan::mcmc::diag_e_nuts<Model, rng_t> sampler;
              sampler_ptr = new sampler(model, base_rng);
              if (!sample::init_nuts<sampler>(sampler_ptr, algo))
                return 0;
              break;
            }

            case 20: {
              typedef stan::mcmc::dense_e_static_hmc<Model, rng_t> sampler;
              sampler_ptr = new sampler(model, base_rng);
              if (!sample::init_static_hmc<sampler>(sampler_ptr, algo))
                return 0;
              break;
            }

            case 21: {
              typedef stan::mcmc::dense_e_nuts<Model, rng_t> sampler;
              sampler_ptr = new sampler(model, base_rng);
              if (!sample::init_nuts<sampler>(sampler_ptr, algo))
                return 0;
              break;
            }

            case 100: {
              typedef stan::mcmc::adapt_unit_e_static_hmc<Model, rng_t> sampler;
              sampler_ptr = new sampler(model, base_rng);
              if (!sample::init_static_hmc<sampler>(sampler_ptr, algo))
                return 0;
              if (!sample::init_adapt<sampler>(sampler_ptr,
                                               adapt, cont_params,
                                               info, err))
                return 0;
              break;
            }

            case 101: {
              typedef stan::mcmc::adapt_unit_e_nuts<Model, rng_t> sampler;
              sampler_ptr = new sampler(model, base_rng);
              if (!sample::init_nuts<sampler>(sampler_ptr, algo))
                return 0;
              if (!sample::init_adapt<sampler>(sampler_ptr,
                                               adapt, cont_params,
                                               info, err))
                return 0;
              break;
            }

            case 110: {
              typedef stan::mcmc::adapt_diag_e_static_hmc<Model, rng_t> sampler;
              sampler_ptr = new sampler(model, base_rng);
              if (!sample::init_static_hmc<sampler>(sampler_ptr, algo))
                return 0;
              if (!sample::init_windowed_adapt<sampler>(sampler_ptr, adapt, num_warmup,
                                                        cont_params, info, err))
                return 0;
              break;
            }

            case 111: {
              typedef stan::mcmc::adapt_diag_e_nuts<Model, rng_t> sampler;
              sampler_ptr = new sampler(model, base_rng);
              if (!sample::init_nuts<sampler>(sampler_ptr, algo))
                return 0;
              if (!sample::init_windowed_adapt<sampler>(sampler_ptr, adapt, num_warmup,
                                                        cont_params, info, err))
                return 0;
              break;
            }

            case 120: {
              typedef stan::mcmc::adapt_dense_e_static_hmc<Model, rng_t>
                sampler;
              sampler_ptr = new sampler(model, base_rng);
              if (!sample::init_static_hmc<sampler>(sampler_ptr, algo))
                return 0;
              if (!sample::init_windowed_adapt<sampler>(sampler_ptr, adapt, num_warmup,
                                                        cont_params, info, err))
                return 0;
              break;
            }

            case 121: {
              typedef stan::mcmc::adapt_dense_e_nuts<Model, rng_t> sampler;
              sampler_ptr = new sampler(model, base_rng);
              if (!sample::init_nuts<sampler>(sampler_ptr, algo))
                return 0;
              if (!sample::init_windowed_adapt<sampler>(sampler_ptr, adapt, num_warmup,
                                                        cont_params, info, err))
                return 0;
              break;
            }

            default:
              std::cout << "No sampler matching HMC specification!"
                        << std::endl;
              return 0;
          }
        }

        // Headers
        writer.write_sample_names(s, sampler_ptr, model);
        writer.write_diagnostic_names(s, sampler_ptr, model);

        std::string prefix = "";
        std::string suffix = "\n";
        interface_callbacks::interrupt::noop startTransitionCallback;

        // Warm-Up
        clock_t start = clock();

        mcmc::warmup<Model, rng_t>(sampler_ptr, num_warmup, num_samples, num_thin,
                                   refresh, save_warmup,
                                   writer,
                                   s, model, base_rng,
                                   prefix, suffix, std::cout,
                                   startTransitionCallback,
                                   info, err);

        clock_t end = clock();
        warmDeltaT = static_cast<double>(end - start) / CLOCKS_PER_SEC;

        if (adapt_engaged) {
          dynamic_cast<stan::mcmc::base_adapter*>(sampler_ptr)
            ->disengage_adaptation();
          writer.write_adapt_finish(sampler_ptr);
        }

        // Sampling
        start = clock();

        mcmc::sample<Model, rng_t>
          (sampler_ptr, num_warmup, num_samples, num_thin,
           refresh, true,
           writer,
           s, model, base_rng,
           prefix, suffix, std::cout,
           startTransitionCallback,
           info, err);

        end = clock();
        sampleDeltaT = static_cast<double>(end - start) / CLOCKS_PER_SEC;

        writer.write_timing(warmDeltaT, sampleDeltaT);

        if (sampler_ptr)
          delete sampler_ptr;
      }

      //////////////////////////////////////////////////
      //           VARIATIONAL Algorithms             //
      //////////////////////////////////////////////////

      if (parser.arg("method")->arg("variational")) {
        stan::services::list_argument* algo
          = dynamic_cast<stan::services::list_argument*>(parser.arg("method")
            ->arg("variational")->arg("algorithm"));

        int grad_samples = dynamic_cast<stan::services::int_argument*>
          (parser.arg("method")->arg("variational")
                               ->arg("grad_samples"))->value();

        int elbo_samples = dynamic_cast<stan::services::int_argument*>
          (parser.arg("method")->arg("variational")
                               ->arg("elbo_samples"))->value();

        int max_iterations = dynamic_cast<stan::services::int_argument*>
          (parser.arg("method")->arg("variational")
                               ->arg("iter"))->value();

        double tol_rel_obj = dynamic_cast<stan::services::real_argument*>
          (parser.arg("method")->arg("variational")
                               ->arg("tol_rel_obj"))->value();

        double eta = dynamic_cast<stan::services::real_argument*>
          (parser.arg("method")->arg("variational")
                               ->arg("eta"))->value();

        bool adapt_engaged = dynamic_cast<stan::services::bool_argument*>
          (parser.arg("method")->arg("variational")->arg("adapt")
                                                   ->arg("engaged"))->value();

        int adapt_iterations = dynamic_cast<stan::services::int_argument*>
          (parser.arg("method")->arg("variational")->arg("adapt")
                                                   ->arg("iter"))->value();

        int eval_elbo = dynamic_cast<stan::services::int_argument*>
          (parser.arg("method")->arg("variational")
                               ->arg("eval_elbo"))->value();

        int output_samples = dynamic_cast<stan::services::int_argument*>
          (parser.arg("method")->arg("variational")
                               ->arg("output_samples"))->value();

        // Check timing
        clock_t start_check = clock();

        double init_log_prob;
        Eigen::VectorXd init_grad
          = Eigen::VectorXd::Zero(model.num_params_r());

        stan::model::gradient(model, cont_params, init_log_prob,
                              init_grad, &std::cout);

        clock_t end_check = clock();
        double deltaT
          = static_cast<double>(end_check - start_check) / CLOCKS_PER_SEC;

        std::cout << std::endl;
        std::cout << "This is Automatic Differentiation Variational Inference.";
        std::cout << std::endl;

        std::cout << std::endl;
        std::cout << "(EXPERIMENTAL ALGORITHM: expect frequent updates to the"
                  << " procedure.)";
        std::cout << std::endl;

        std::cout << std::endl;
        std::cout << "Gradient evaluation took " << deltaT
                  << " seconds" << std::endl;
        std::cout << "1000 iterations under these settings should take "
                  << 1e3 * grad_samples * deltaT << " seconds." << std::endl;
        std::cout << "Adjust your expectations accordingly!";
        std::cout << std::endl;
        std::cout << std::endl;

        if (algo->value() == "fullrank") {
          std::vector<std::string> names;
          names.push_back("lp__");
          model.constrained_param_names(names, true, true);

          sample_writer(names);

          stan::variational::advi<Model,
                                  stan::variational::normal_fullrank,
                                  rng_t>
            cmd_advi(model,
                     cont_params,
                     base_rng,
                     grad_samples,
                     elbo_samples,
                     eval_elbo,
                     output_samples);
          cmd_advi.run(eta, adapt_engaged, adapt_iterations,
                       tol_rel_obj, max_iterations,
                       info, sample_writer, diagnostic_writer);
        }

        if (algo->value() == "meanfield") {
          std::vector<std::string> names;
          names.push_back("lp__");
          model.constrained_param_names(names, true, true);
          sample_writer(names);

          stan::variational::advi<Model,
                                  stan::variational::normal_meanfield,
                                  rng_t>
            cmd_advi(model,
                     cont_params,
                     base_rng,
                     grad_samples,
                     elbo_samples,
                     eval_elbo,
                     output_samples);
          cmd_advi.run(eta, adapt_engaged, adapt_iterations,
                       tol_rel_obj, max_iterations,
                       info, sample_writer, diagnostic_writer);
        }
      }

      if (output_stream) {
        output_stream->close();
        delete output_stream;
      }

      if (diagnostic_stream) {
        diagnostic_stream->close();
        delete diagnostic_stream;
      }

      for (size_t i = 0; i < valid_arguments.size(); ++i)
        delete valid_arguments.at(i);

      return 0;
    }

  }  // namespace services
}  // namespace stan

#endif