63 const int dim =
static_cast<int>(3 * matter->numberOfAtoms());
64 const VectorXd freeMask = matter->getFreeV();
73 if (freeMask.norm() < 1e-14 || matter->numberOfFreeAtoms() == 0) {
74 QUILL_LOG_WARNING(
log,
"[LOR] no free atoms; skipping rotation");
78 VectorXd N = VectorXd::Map(initialDirectionAtomMatrix.data(), dim);
79 N = N.array() * freeMask.array();
80 if (N.norm() < 1e-10) {
82 N = N.array() * freeMask.array();
84 if (N.norm() < 1e-14) {
85 QUILL_LOG_WARNING(
log,
"[LOR] free mask yields zero direction; skip");
92 const VectorXd x0_r =
x0->getPositionsV();
93 const double delta =
params.main_options.finiteDifference;
96 const long rotBudget =
params.dimer_options.rotations_max > 0
97 ?
params.dimer_options.rotations_max
99 const int rotmax =
static_cast<int>(std::max<long>(1, rotBudget));
102 const double residualTol =
103 std::max(1e-3,
params.dimer_options.lor_residual_tol);
104 auto relativeResidual = [](
double fnorm,
double cn) {
105 return fnorm / (std::abs(cn) + 1.0);
108 double bestCN = std::numeric_limits<double>::infinity();
110 VectorXd bestHN = VectorXd::Zero(dim);
113 const VectorXd F0 =
x0->getForcesV();
116 auto applyMask = [&](VectorXd &v) { v = v.array() * freeMask.array(); };
118 auto unitize = [&](VectorXd &v) ->
double {
120 const double n = v.norm();
128 auto hessianAlong = [&](
const VectorXd &v) -> VectorXd {
129 VectorXd dir = v.array() * freeMask.array();
130 const double nrm = dir.norm();
132 return VectorXd::Zero(dim);
135 x1->setPositionsV(x0_r + delta * dir);
136 const VectorXd F1 =
x1->getForcesV();
138 VectorXd Hv = -(F1 - F0) / delta;
143 auto trackBest = [&](
double cn,
const VectorXd &nVec,
const VectorXd &hnVec) {
153 auto appendHistory = [&](
double cn) ->
bool {
162 VectorXd HN = hessianAlong(N);
164 double CN = N.dot(HN);
165 if (appendHistory(CN)) {
166 trackBest(CN, N, HN);
169 VectorXd F = HN - CN * N;
171 double Fnorm = F.norm();
174 log,
"[LOR] iter=0 ||F_perp||={:.6e} C_N={:.6f} (Algorithm I start)",
177 if (relativeResidual(Fnorm, CN) < residualTol) {
181 QUILL_LOG_INFO(
log,
"[LOR] converged on residual at start");
185 VectorXd Theta = F / Fnorm;
186 VectorXd HTheta = hessianAlong(Theta);
191 A2(0, 0) = N.dot(HN);
192 A2(0, 1) = N.dot(HTheta);
194 A2(1, 1) = Theta.dot(HTheta);
195 Eigen::SelfAdjointEigenSolver<Eigen::Matrix2d> es2(A2);
196 Eigen::Vector2d coeffs = es2.eigenvectors().col(0);
197 double a = coeffs(0);
198 double b = coeffs(1);
200 VectorXd Nlin = a * N + b * Theta;
201 const double nN = unitize(Nlin);
202 VectorXd HNlin = a * HN + b * HTheta;
210 VectorXd HP = HTheta;
213 if (appendHistory(CN)) {
214 trackBest(CN, N, HN);
222 log,
"[LOR] iter=1 (2x2) ||F_perp||={:.6e} C_N={:.6f} a={:.4f} b={:.4f}",
225 for (
int k = 2; k <= rotmax; ++k) {
226 if (relativeResidual(Fnorm, CN) < residualTol) {
229 "[LOR] converged residual iter={} ||F||={:.6e} rel={:.6e}",
230 k, Fnorm, relativeResidual(Fnorm, CN));
236 HTheta = hessianAlong(Theta);
240 VectorXd P3 = P - N.dot(P) * N - Theta.dot(P) * Theta;
242 const double pNrm = P3.norm();
244 auto applyRitz2 = [&]() {
246 A2b(0, 0) = N.dot(HN);
247 A2b(0, 1) = N.dot(HTheta);
248 A2b(1, 0) = A2b(0, 1);
249 A2b(1, 1) = Theta.dot(HTheta);
250 Eigen::SelfAdjointEigenSolver<Eigen::Matrix2d> es2b(A2b);
251 Eigen::Vector2d c2 = es2b.eigenvectors().col(0);
254 VectorXd Nnew = a * N + b * Theta;
255 VectorXd HNnew = a * HN + b * HTheta;
256 const double nn = unitize(Nnew);
260 const double cNew = Nnew.dot(HNnew);
263 "[LOR] curvature stall (2x2 fallback) iter={} "
264 "C_prev={:.6f} C_new={:.6f} (reject)",
280 if (appendHistory(CN)) {
281 trackBest(CN, N, HN);
301 if (HP3.norm() < 1e-14) {
308 Eigen::Matrix3d A3 = Eigen::Matrix3d::Zero();
309 const VectorXd basis[3] = {N, Theta, P3};
310 const VectorXd Hbasis[3] = {HN, HTheta, HP3};
311 for (
int i = 0; i < 3; ++i) {
312 for (
int j = i; j < 3; ++j) {
313 A3(i, j) = basis[i].dot(Hbasis[j]);
317 A3 = 0.5 * (A3 + A3.transpose());
318 Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> ges(A3);
319 if (ges.info() != Eigen::Success) {
320 QUILL_LOG_WARNING(
log,
"[LOR] 3x3 eigen failed at iter={}; stop", k);
323 const Eigen::Vector3d c3 = ges.eigenvectors().col(0);
326 const double c = c3(2);
329 const VectorXd Nprev = N;
330 const VectorXd HNprev = HN;
331 const double CNprev = CN;
333 VectorXd Nnew = a * N + b * Theta + c * P3;
334 VectorXd Pnew = b * Theta + c * P3;
335 VectorXd HNnew = a * HN + b * HTheta + c * HP3;
336 VectorXd HPnew = b * HTheta + c * HP3;
338 const double nNew = unitize(Nnew);
342 const double pNew = unitize(Pnew);
347 const double CNnew = Nnew.dot(HNnew);
350 "[LOR] curvature stall iter={} C_prev={:.6f} C_new={:.6f} "
351 "(reject update; keep prior mode, no extra FD)",
368 if (appendHistory(CN)) {
369 trackBest(CN, N, HN);
381 "[LOR] iter={} (3x3) ||F_perp||={:.6e} C_N={:.6f} a={:.4f} "
383 k, Fnorm, CN, a, b, c);
385 if (relativeResidual(Fnorm, CN) < residualTol) {
398 "[LOR] done rotations={} force_calls={} C_N={:.6f} "
399 "converged_residual={}",