/*
* Copyright 2009-2019 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 "molpol.h"
#include "votca/xtp/polarregion.h"
#include "votca/xtp/qmpackage.h"
#include <votca/xtp/qmpackagefactory.h>
namespace votca {
namespace xtp {
void MolPol::Initialize(tools::Property& options) {
std::string key = "options." + Identify();
std::string mps_input =
options.ifExistsReturnElseThrowRuntimeError<std::string>(key +
".mpsinput");
_input.LoadFromFile(mps_input);
_mps_output = options.ifExistsReturnElseThrowRuntimeError<std::string>(
key + ".mpsoutput");
_polar_options = options.get(key);
bool target_exists = options.exists(key + ".target");
bool qmpackage_exists = options.exists(key + ".qmpackage");
if (target_exists && qmpackage_exists) {
throw std::runtime_error(
"Can only read either from target or qmpackage logfile");
}
if (!target_exists && !qmpackage_exists) {
throw std::runtime_error(
"You have to define a polar targer <target> or a or qmpackage logfile");
}
if (target_exists) {
Eigen::VectorXd target_vec =
options.ifExistsReturnElseThrowRuntimeError<Eigen::VectorXd>(key +
".target");
if (target_vec.size() != 6) {
throw std::runtime_error(
"ERROR <options.molpol.target> "
" should have this format: pxx pxy pxz pyy pyz pzz");
}
target_vec *= std::pow(tools::conv::ang2bohr, 3);
_polarisation_target(0, 0) = target_vec(0);
_polarisation_target(1, 0) = target_vec(1);
_polarisation_target(0, 1) = target_vec(1);
_polarisation_target(2, 0) = target_vec(2);
_polarisation_target(0, 2) = target_vec(2);
_polarisation_target(1, 1) = target_vec(3);
_polarisation_target(2, 1) = target_vec(4);
_polarisation_target(1, 2) = target_vec(4);
_polarisation_target(2, 2) = target_vec(5);
} else {
std::string qm_package =
options.ifExistsReturnElseThrowRuntimeError<std::string>(key +
".qmpackage");
std::string log_file =
options.ifExistsReturnElseThrowRuntimeError<std::string>(key +
".logfile");
Logger log;
log.setPreface(Log::info, "\n ...");
log.setPreface(Log::error, "\n ...");
log.setPreface(Log::warning, "\n ...");
log.setPreface(Log::debug, "\n ...");
log.setReportLevel(Log::current_level);
log.setMultithreading(true);
// Set-up QM package
XTP_LOG(Log::error, log)
<< "Using package <" << qm_package << ">" << std::flush;
QMPackageFactory::RegisterAll();
std::unique_ptr<QMPackage> qmpack =
std::unique_ptr<QMPackage>(QMPackages().Create(qm_package));
qmpack->setLog(&log);
qmpack->setRunDir(".");
qmpack->setLogFileName(log_file);
_polarisation_target = qmpack->GetPolarizability();
}
Eigen::VectorXd default_weights = Eigen::VectorXd::Ones(_input.size());
_weights = options.ifExistsReturnElseReturnDefault<Eigen::VectorXd>(
key + ".weights", default_weights);
_tolerance_pol = options.ifExistsReturnElseReturnDefault<double>(
key + ".tolerance", _tolerance_pol);
_max_iter = options.ifExistsReturnElseReturnDefault<Index>(
key + ".iterations", _max_iter);
}
Eigen::Vector3d MolPol::Polarize(const PolarSegment& input,
const Eigen::Vector3d& ext_field) const {
Logger log;
log.setMultithreading(false);
log.setCommonPreface("\n ...");
log.setReportLevel(Log::current_level);
PolarRegion pol(0, log);
pol.Initialize(_polar_options);
pol.push_back(input);
std::vector<std::unique_ptr<Region>> empty;
PolarSegment& workmol = pol[0];
for (PolarSite& site : workmol) {
site.V() = ext_field;
}
pol.Evaluate(empty);
Eigen::Vector3d induced_dipole = Eigen::Vector3d::Zero();
for (const PolarSite& site : workmol) {
induced_dipole += site.Induced_Dipole();
}
std::cout << log;
return induced_dipole;
}
Eigen::Matrix3d MolPol::CalcClassicalPol(const PolarSegment& input) const {
double eVnm_2_hrtbohr = tools::conv::ev2hrt / tools::conv::nm2bohr;
double fieldstrength = (0.1 * eVnm_2_hrtbohr);
Eigen::Matrix3d polarisation = Eigen::Matrix3d::Zero();
Eigen::Vector3d ext_field = fieldstrength * Eigen::Vector3d::UnitX();
// central differences scheme
Eigen::Vector3d xplus = Polarize(input, ext_field);
Eigen::Vector3d xminus = Polarize(input, -ext_field);
polarisation.col(0) = xplus - xminus;
ext_field = fieldstrength * Eigen::Vector3d::UnitY();
Eigen::Vector3d yplus = Polarize(input, ext_field);
Eigen::Vector3d yminus = Polarize(input, -ext_field);
polarisation.col(1) = yplus - yminus;
ext_field = fieldstrength * Eigen::Vector3d::UnitZ();
Eigen::Vector3d zplus = Polarize(input, ext_field);
Eigen::Vector3d zminus = Polarize(input, -ext_field);
polarisation.col(2) = zplus - zminus;
return -polarisation / (2 * fieldstrength);
}
void MolPol::PrintPolarisation(const Eigen::Matrix3d& result) const {
std::cout << std::endl << "First principle polarisation [A^3]" << std::flush;
double conversion = std::pow(tools::conv::bohr2ang, 3);
std::cout << std::endl << _polarisation_target * conversion << std::flush;
std::cout << std::endl << "Scaled classical polarisation [A^3]" << std::flush;
std::cout << std::endl << result * conversion << std::flush;
std::cout << std::endl
<< "EigenValues classical polarisation [A^3]" << std::flush;
Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> es2;
es2.computeDirect(result, Eigen::EigenvaluesOnly);
Eigen::Matrix3d diag = es2.eigenvalues().asDiagonal();
std::cout << std::endl << diag * conversion << std::flush;
}
bool MolPol::Evaluate() {
OPENMP::setMaxThreads(_nThreads);
PolarSegment polar = _input;
Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> es;
es.computeDirect(_polarisation_target, Eigen::EigenvaluesOnly);
const double pol_volume_target = std::pow(es.eigenvalues().prod(), 1.0 / 3.0);
for (Index iter = 0; iter < _max_iter; iter++) {
Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> es2;
Eigen::Matrix3d pol = CalcClassicalPol(polar);
es2.computeDirect(pol, Eigen::EigenvaluesOnly);
const double pol_volume_iter =
std::pow(es2.eigenvalues().prod(), 1.0 / 3.0);
double scale = pol_volume_target / pol_volume_iter - 1;
std::cout << "\nIteration " << iter + 1 << " of " << _max_iter
<< " target:" << pol_volume_target
<< " current:" << pol_volume_iter << std::endl;
if (std::abs(scale) < _tolerance_pol) {
std::cout << std::endl
<< "... ... Iterative refinement : *CONVERGED*" << std::flush;
std::cout << std::endl
<< "... ... Scaling coefficient : " << scale << std::flush;
polar.WriteMPS(_mps_output, "MOLPOL (OPTIMIZED)");
PrintPolarisation(pol);
break;
} else if (iter == (_max_iter - 1)) {
std::cout << std::endl
<< "... ... Iterative refinement : *FAILED*" << std::flush;
std::cout << std::endl
<< "... ... ERROR Convergence not achieved. "
<< "Check your input mps-file, target polarizability <target> "
<< std::flush;
PrintPolarisation(pol);
}
for (Index i = 0; i < polar.size(); i++) {
PolarSite& site = polar[i];
Eigen::Matrix3d local_pol = site.getPolarisation();
site.setPolarisation(local_pol * std::pow(1 + scale * _weights[i], 2));
}
}
return true;
}
} // namespace xtp
} // namespace votca