SIRIUS 7.5.0
Electronic structure library and applications
periodic_optimizer.hpp
Go to the documentation of this file.
1/**
2 * @file periodic_optimizer.hpp
3 * @author Moritz Gubler (moritz.gubler@unibas.ch)
4 * @brief Implementation of the vc-sqnm method. More informations about the algorithm can be found here: https://arxiv.org/abs/2206.07339
5 * @date 2022-07-13
6 *
7 */
8
9#ifndef PERIODIC_OPTIMIZER_HPP
10#define PERIODIC_OPTIMIZER_HPP
11#include <Eigen/Dense>
12#include <iostream>
13#include "sqnm.hpp"
14
15/// Variable cell stable quasi-Newton lattice optimizer.
16namespace vcsqnm {
17
18namespace PES_optimizer{
19
21 {
22 private:
23 Eigen::Matrix3d initial_latttice;
24 Eigen::Matrix3d initial_latttice_inv;
25 Eigen::Matrix3d lattice_transformer;
26 Eigen::Matrix3d lattice_transformer_inv;
27 std::unique_ptr<sqnm_space::SQNM> opt;
28 int nat;
29 int ndim;
30 bool opt_lattice;
31 double initial_step_size = 1;
32 int n_hist_max = 10;
33 double w = 2.0;
34 double f_std_deviation = 0.0;
35
36 public:
37
38 double get_w(){
39 return w;
40 }
41
42 int get_n_hist_max(){
43 return n_hist_max;
44 }
45
46 double get_initial_step_size(){
47 return initial_step_size;
48 }
49
50 /**
51 * @brief Construct a new periodic optimizer::periodic optimizer object for free or fixed cell optimization with default parameters.
52 *
53 * @param nat number of atoms
54 */
56 {
57 this->nat = nat;
58 this->ndim = 3*nat;
59 this->opt_lattice = false;
60 this->opt = std::make_unique<sqnm_space::SQNM>(ndim, n_hist_max, initial_step_size);
61 }
62
63 /**
64 * @brief Construct a new periodic optimizer::periodic optimizer object for free or fixed cell optimization with custom parameters.
65 *
66 * @param nat number of atoms
67 * @param initial_step_size initial step size. default is 1.0. For systems with hard bonds (e.g. C-C) use a value between and 1.0 and
68 * 2.5. If a system only contains weaker bonds a value up to 5.0 may speed up the convergence.
69 * @param nhist_max Maximal number of steps that will be stored in the history list. Use a value between 3 and 20. Must be <= than 3*nat.
70 * @param alpha0 Lower limit on the step size. 1.e-2 is the default.
71 * @param eps_subsp Lower limit on linear dependencies of basis vectors in history list. Default 1.e-4.
72 */
73 periodic_optimizer(int &nat, double initial_step_size, int nhist_max, double alpha0, double eps_subsp)
74 {
75 this->nat = nat;
76 this->ndim = 3*nat;
77 this->initial_step_size = initial_step_size;
78 this->n_hist_max = nhist_max;
79 this->opt_lattice = false;
80 this->opt = std::make_unique<sqnm_space::SQNM>(ndim, n_hist_max, initial_step_size, alpha0, eps_subsp);
81 }
82
83 /**
84 * @brief Construct a new periodic optimizer::periodic optimizer object for variable cell shape optimization with default parameters.
85 *
86 * @param nat number of atoms
87 * @param lat_a first lattice vector
88 * @param lat_b second lattice vector
89 * @param lat_c third lattice vector
90 */
91 periodic_optimizer(int &nat, Eigen::Vector3d &lat_a, Eigen::Vector3d &lat_b, Eigen::Vector3d &lat_c)
92 {
93 setupInitialLattice(nat, lat_a, lat_b, lat_c);
94 this->opt = std::make_unique<sqnm_space::SQNM>(ndim, n_hist_max, initial_step_size);
95 }
96
97 /**
98 * @brief Construct a new periodic optimizer::periodic optimizer object for variable cell shape optimization with custom parameters.
99 *
100 * @param nat number of atoms
101 * @param lat_a first lattice vector
102 * @param lat_b second lattice vector
103 * @param lat_c third lattice vector
104 * @param initial_step_size initial step size. default is 1.0. For systems with hard bonds (e.g. C-C) use a value between and 1.0 and
105 * 2.5. If a system only contains weaker bonds a value up to 5.0 may speed up the convergence.
106 * @param nhist_max Maximal number of steps that will be stored in the history list. Use a value between 3 and 20. Must be <= than 3*nat + 9.
107 * @param lattice_weight weight / size of the supercell that is used to transform lattice derivatives. Use a value between 1 and 2. Default is 2.
108 * @param alpha0 Lower limit on the step size. 1.e-2 is the default.
109 * @param eps_subsp Lower limit on linear dependencies of basis vectors in history list. Default 1.e-4.
110 */
111 periodic_optimizer(int &nat, Eigen::Vector3d &lat_a, Eigen::Vector3d &lat_b, Eigen::Vector3d &lat_c, double initial_step_size, int nhist_max, double lattice_weight, double alpha0, double eps_subsp)
112 {
113 this->w = lattice_weight;
114 this->n_hist_max = nhist_max;
115 this->initial_step_size = initial_step_size;
116 setupInitialLattice(nat, lat_a, lat_b, lat_c);
117 this->opt = std::make_unique<sqnm_space::SQNM>(ndim, n_hist_max, initial_step_size, alpha0, eps_subsp);
118 }
119
120 /**
121 * @brief Calculates new atomic coordinates that are closer to the local minimum. Fixed cell optimization. This function should be used the following way:
122 * 1. calculate energies and forces at positions r.
123 * 2. call the step function to update positions r.
124 * 3. repeat.
125 *
126 * @param r Input: atomic coordinates, dimension(3, nat). Output: improved coordinates that are calculated based on forces from this and previous iterations.
127 * @param energy Potential energy of the system in it's current state
128 * @param f Forces of the system in it's current state. dimension(3, nat)
129 */
130 void step(Eigen::MatrixXd &r, double &energy, Eigen::MatrixXd &f){
131 if (opt_lattice)
132 {
133 std::cout << "The fixed cell step function was called even though the object was created for vc-relaxation. returning" << "\n";
134 return;
135 }
136 check_forces(f);
137 Eigen::VectorXd pos_all = Eigen::Map<Eigen::VectorXd>(r.data(), 3*nat);
138 Eigen::VectorXd force_all = - Eigen::Map<Eigen::VectorXd>(f.data(), 3*nat);
139 pos_all += opt->step(pos_all, energy, force_all);
140 r = Eigen::Map<Eigen::MatrixXd>(pos_all.data(), 3, nat);
141 }
142
143 /**
144 * @brief Calculates new atomic coordinates that are closer to the local minimum. Variable cell shape optimization. This function should be used the following way:
145 * 1. calculate energies, forces and stress tensor at positions r and lattice vectors a, b, c.
146 * 2. call the step function to update positions r and lattice vectors.
147 * 3. repeat.
148 *
149 * @param r Input: atomic coordinates, dimension(3, nat). Output: improved coordinates that are calculated based on forces from this and previous iterations.
150 * @param energy Potential energy of the system in it's current state
151 * @param f Forces of the system in it's current state. dimension(3, nat)
152 * @param lat_a first lattice vector
153 * @param lat_b second lattice vector
154 * @param lat_c third lattice vector
155 * @param stress stress tensor of the system in it' current state.
156 */
157 void step(Eigen::MatrixXd &r, double &energy, Eigen::MatrixXd &f, Eigen::Vector3d &lat_a, Eigen::Vector3d &lat_b, Eigen::Vector3d &lat_c, Eigen::Matrix3d &stress){
158 if (! opt_lattice)
159 {
160 std::cout << "The vc step function was called even though the object was created for fixed cell relaxation. returning" << "\n";
161 return;
162 }
163 check_forces(f);
164 Eigen::Matrix3d alat;
165 Eigen::MatrixXd alat_tilde;
166 alat.col(0) = lat_a;
167 alat.col(1) = lat_b;
168 alat.col(2) = lat_c;
169
170 // calculate transformed coordinates
171 Eigen::MatrixXd q(3, nat);
172 Eigen::MatrixXd dq(3, nat);
173 q = initial_latttice * alat.inverse() * r;
174 dq = - alat * this->initial_latttice_inv * f;
175
176 //cout << "transform lattice vectors" << endl;
177 // transform lattice vectors
178 alat_tilde = alat * lattice_transformer;
179 Eigen::MatrixXd dalat = calc_lattice_derivatices(stress, alat) * lattice_transformer_inv;
180 Eigen::VectorXd qall = combine_matrices(q, alat_tilde);
181 Eigen::VectorXd dqall = combine_matrices(dq, dalat);
182
183 //cout << "update coordinates" << endl;
184 qall += this->opt->step(qall, energy, dqall);
185
186 split_matrices(q, alat_tilde, qall);
187 alat = alat_tilde * lattice_transformer_inv;
188 r = alat * this->initial_latttice_inv * q;
189 lat_a = alat.col(0);
190 lat_b = alat.col(1);
191 lat_c = alat.col(2);
192 }
193
194 void check_forces(Eigen::MatrixXd forces)
195 {
196 double fnoise = forces.rowwise().sum().norm() / sqrt(3 * this->nat);
197 if (this->f_std_deviation == 0)
198 {
199 this->f_std_deviation = fnoise;
200 } else {
201 this->f_std_deviation = .8 * this->f_std_deviation + .2 * fnoise;
202 }
203 if (this->f_std_deviation > 0.2 * forces.cwiseAbs().maxCoeff()) {
204 std::cerr << "Noise in force is larger than 0.2 times the larges force component. Convergence cannot be guaranteed.";
205 }
206 }
207
208 /**
209 * @brief Estimates a lower bound of the energy of the local minimum
210 *
211 * @return double Lower bound estimate
212 */
213 double lower_bound()
214 {
215 return this->opt->lower_bound();
216 }
217
218 private:
219 Eigen::VectorXd combine_matrices(Eigen::MatrixXd a, Eigen::MatrixXd b){
220 Eigen::VectorXd result(this->ndim);
221 for (int i = 0; i < 3*nat; i++)
222 {
223 result(i) = a(i);
224 }
225 int j = 0;
226 for (int i = 3*nat; i < ndim; i++)
227 {
228 result(i) = b(j);
229 ++j;
230 }
231 return result;
232 }
233
234 void split_matrices(Eigen::MatrixXd &a, Eigen::MatrixXd &b, Eigen::VectorXd &c){
235 for (int i = 0; i < 3*nat; i++)
236 {
237 a(i) = c(i);
238 }
239 int j = 0;
240 for (int i = 3*nat; i < ndim; i++)
241 {
242 b(j) = c(i);
243 j++;
244 }
245 }
246
247 Eigen::Matrix3d calc_lattice_derivatices(Eigen::Matrix3d &stress, Eigen::Matrix3d &alat){
248 Eigen::Matrix3d da = - stress * alat.inverse().transpose() * alat.determinant();
249 return da;
250 }
251
252 void setupInitialLattice(int &nat, Eigen::Vector3d &lat_a, Eigen::Vector3d &lat_b, Eigen::Vector3d &lat_c)
253 {
254 this->nat = nat;
255 this->ndim = 3*nat + 9;
256 this->initial_latttice.col(0) = lat_a;
257 this->initial_latttice.col(1) = lat_b;
258 this->initial_latttice.col(2) = lat_c;
259 this->initial_latttice_inv = initial_latttice.inverse();
260 lattice_transformer.setZero();
261 for (int i = 0; i < 3; i++)
262 {
263 lattice_transformer(i, i) = 1.0 / (initial_latttice.col(i).norm());
264 }
265 lattice_transformer = lattice_transformer * (w * sqrt(nat));
266 lattice_transformer_inv = lattice_transformer.inverse();
267 this->opt_lattice = true;
268 }
269 };
270}
271
272}
273#endif
periodic_optimizer(int &nat, Eigen::Vector3d &lat_a, Eigen::Vector3d &lat_b, Eigen::Vector3d &lat_c)
Construct a new periodic optimizer::periodic optimizer object for variable cell shape optimization wi...
double lower_bound()
Estimates a lower bound of the energy of the local minimum.
void step(Eigen::MatrixXd &r, double &energy, Eigen::MatrixXd &f, Eigen::Vector3d &lat_a, Eigen::Vector3d &lat_b, Eigen::Vector3d &lat_c, Eigen::Matrix3d &stress)
Calculates new atomic coordinates that are closer to the local minimum. Variable cell shape optimizat...
periodic_optimizer(int &nat, Eigen::Vector3d &lat_a, Eigen::Vector3d &lat_b, Eigen::Vector3d &lat_c, double initial_step_size, int nhist_max, double lattice_weight, double alpha0, double eps_subsp)
Construct a new periodic optimizer::periodic optimizer object for variable cell shape optimization wi...
periodic_optimizer(int &nat, double initial_step_size, int nhist_max, double alpha0, double eps_subsp)
Construct a new periodic optimizer::periodic optimizer object for free or fixed cell optimization wit...
void step(Eigen::MatrixXd &r, double &energy, Eigen::MatrixXd &f)
Calculates new atomic coordinates that are closer to the local minimum. Fixed cell optimization....
periodic_optimizer(int &nat)
Construct a new periodic optimizer::periodic optimizer object for free or fixed cell optimization wit...
Variable cell stable quasi-Newton lattice optimizer.
Definition: historylist.hpp:13
Implementation of the SQNM method. More informations about the algorithm can be found here: https://a...