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

#include "QCMethod/OrbitalOptimization/BaseOrbitalOptimizer.hpp"

#include "Basis/Transformations/transform.hpp"


namespace GQCP {


/* 
 *  CONSTRUCTORS
 */

/*
*  @param convergence_threshold            the threshold used to check for convergence
*  @param maximum_number_of_iterations     the maximum number of iterations that may be used to achieve convergence
*/
BaseOrbitalOptimizer::BaseOrbitalOptimizer(const double convergence_threshold, const size_t maximum_number_of_iterations) :
    convergence_threshold {convergence_threshold},
    maximum_number_of_iterations {maximum_number_of_iterations} {}


/*
 *  PUBLIC METHODS
 */

/**
 *  Optimize the Hamiltonian by subsequently
 *      - checking for convergence (see checkForConvergence())
 *      - rotating the Hamiltonian (and spinor basis) with a newly found rotation matrix (see calculateNewRotationMatrix())
 * 
 *  @param spinor_basis         the initial spinor basis that contains the spinors to be optimized
 *  @param sq_hamiltonian       the initial (guess for the) Hamiltonian
 */
void BaseOrbitalOptimizer::optimize(RSpinOrbitalBasis<double, GTOShell>& spinor_basis, RSQHamiltonian<double>& sq_hamiltonian) {

    if (!spinor_basis.isOrthonormal()) {
        throw std::invalid_argument("BaseOrbitalOptimizer::optimize(RSQHamiltonian<double>&): The given spinor basis is not orthonormal.");
    }

    while (this->prepareConvergenceChecking(sq_hamiltonian), !this->checkForConvergence(sq_hamiltonian)) {  // result of the comma operator is the second operand, so this expression effectively means "if not converged"
        const auto U = this->calculateNewRotationMatrix(sq_hamiltonian);
        rotate(U, spinor_basis, sq_hamiltonian);

        this->number_of_iterations++;
        if (this->number_of_iterations > this->maximum_number_of_iterations) {
            throw std::runtime_error("BaseOrbitalOptimizer::optimize(RSQHamiltonian<double>&): The orbital optimization procedure did not converge in the given number of iterations.");
        }
    }

    this->is_converged = true;
}


}  // namespace GQCP
