ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
TuningAlgorithm.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2010-2022 The ESPResSo project
3 * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009,2010
4 * Max-Planck-Institute for Polymer Research, Theory Group
5 *
6 * This file is part of ESPResSo.
7 *
8 * ESPResSo is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * ESPResSo is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#include "config/config.hpp"
23
24#if defined(ESPRESSO_P3M) || defined(ESPRESSO_DP3M)
25
27#include "p3m/common.hpp"
28#include "p3m/math.hpp"
29
30#include "tuning.hpp"
31
32#include "BoxGeometry.hpp"
33#include "LocalBox.hpp"
35#include "communication.hpp"
36#include "system/System.hpp"
37
38#include <algorithm>
39#include <cassert>
40#include <cmath>
41#include <string>
42#include <tuple>
43#include <utility>
44
45/** @name Error codes for tuning. */
46/**@{*/
47/** charge assignment order too large for mesh size */
48static auto constexpr P3M_TUNE_CAO_TOO_LARGE = 1.;
49/** conflict with ELC gap size */
50static auto constexpr P3M_TUNE_ELC_GAP_SIZE = 2.;
51/** could not achieve target accuracy */
52static auto constexpr P3M_TUNE_ACCURACY_TOO_LARGE = 3.;
53/** conflict with FFT domain decomposition */
54static auto constexpr P3M_TUNE_FFT_MESH_SIZE = 4.;
55/**@}*/
56
57/** @brief Precision threshold for a non-zero real-space cutoff. */
58static auto constexpr P3M_RCUT_PREC = 1e-3;
59
61 auto const &box_geo = *m_system.box_geo;
62 auto const &local_geo = *m_system.local_geo;
63 auto const verlet_skin = m_system.cell_structure->get_verlet_skin();
64 auto const r_cut_iL = get_params().r_cut_iL;
65 if (r_cut_iL == 0.) {
66 auto const min_box_l = std::ranges::min(box_geo.length());
67 auto const min_local_box_l = std::ranges::min(local_geo.length());
68 m_r_cut_iL_min = 0.;
69 m_r_cut_iL_max = std::min(min_local_box_l, min_box_l / 2.) - verlet_skin;
70 m_r_cut_iL_min *= box_geo.length_inv()[0];
71 m_r_cut_iL_max *= box_geo.length_inv()[0];
72 } else {
73 m_r_cut_iL_min = m_r_cut_iL_max = r_cut_iL;
74 m_logger->report_fixed_r_cut_iL(r_cut_iL);
75 }
76}
77
79 assert(initial_cao >= p3m_min_cao and initial_cao <= p3m_max_cao);
80 auto const cao = get_params().cao;
81 if (cao == -1) {
84 cao_best = initial_cao;
85 } else {
86 cao_min = cao_max = cao_best = cao;
87 m_logger->report_fixed_cao(cao);
88 }
89}
90
91void TuningAlgorithm::commit(Utils::Vector3i const &mesh, int cao,
92 double r_cut_iL, double alpha_L) {
93 auto const &box_geo = *m_system.box_geo;
94 auto &p3m_params = get_params();
95 p3m_params.r_cut = r_cut_iL * box_geo.length()[0];
96 p3m_params.r_cut_iL = r_cut_iL;
97 p3m_params.cao = cao;
98 p3m_params.alpha_L = alpha_L;
99 p3m_params.alpha = alpha_L * box_geo.length_inv()[0];
100 p3m_params.mesh = mesh;
101}
102
103/**
104 * @brief Get the optimal alpha and the corresponding computation time
105 * for a fixed @p mesh and @p cao.
106 *
107 * The @p tuned_r_cut_iL is determined via a simple bisection.
108 *
109 * @param[in] mesh @copybrief P3MParameters::mesh
110 * @param[in] cao @copybrief P3MParameters::cao
111 * @param[in,out] tuned_r_cut_iL @copybrief P3MParameters::r_cut_iL
112 * @param[in,out] tuned_alpha_L @copybrief P3MParameters::alpha_L
113 * @param[in,out] tuned_accuracy @copybrief P3MParameters::accuracy
114 *
115 * @returns The integration time in case of success, otherwise
116 * -@ref P3M_TUNE_ACCURACY_TOO_LARGE, -@ref P3M_TUNE_FFT_MESH_SIZE,
117 * -@ref P3M_TUNE_CAO_TOO_LARGE, or -@ref P3M_TUNE_ELC_GAP_SIZE
118 */
120 double &tuned_r_cut_iL,
121 double &tuned_alpha_L,
122 double &tuned_accuracy) {
123 auto const &box_geo = *m_system.box_geo;
124 auto const &local_geo = *m_system.local_geo;
125 auto const verlet_skin = m_system.cell_structure->get_verlet_skin();
126 auto const target_accuracy = get_params().accuracy;
127 double rs_err, ks_err;
128 double r_cut_iL_min = m_r_cut_iL_min;
129 double r_cut_iL_max = m_r_cut_iL_max;
130
131 /* initial checks. */
132 auto const k_cut_per_dir = (static_cast<double>(cao) / 2.) *
133 Utils::hadamard_division(box_geo.length(), mesh);
134 auto const k_cut = std::ranges::min(k_cut_per_dir);
135 auto const min_box_l = std::ranges::min(box_geo.length());
136 auto const min_local_box_l = std::ranges::min(local_geo.length());
137 auto const k_cut_max = std::min(min_box_l, min_local_box_l) - verlet_skin;
138
139 if (cao >= std::ranges::min(mesh) or k_cut >= k_cut_max) {
140 m_logger->log_cao_too_large(mesh[0], cao);
142 }
143
144 std::tie(tuned_accuracy, rs_err, ks_err, tuned_alpha_L) =
145 calculate_accuracy(mesh, cao, r_cut_iL_max);
146
147 /* Either low and high boundary are equal (for fixed cut), or the low border
148 is initially 0 and therefore
149 has infinite error estimate, as required. Therefore if the high boundary
150 fails, there is no possible r_cut */
151 if (tuned_accuracy > target_accuracy) {
152 m_logger->log_skip("accuracy not achieved", mesh[0], cao, r_cut_iL_max,
153 tuned_alpha_L, tuned_accuracy, rs_err, ks_err);
155 }
156
157 double r_cut_iL, accuracy;
158 for (;;) {
159 r_cut_iL = 0.5 * (r_cut_iL_min + r_cut_iL_max);
160
161 if (r_cut_iL_max - r_cut_iL_min < P3M_RCUT_PREC)
162 break;
163
164 /* bisection */
165 std::tie(accuracy, rs_err, ks_err, tuned_alpha_L) =
166 calculate_accuracy(mesh, cao, r_cut_iL);
167 if (accuracy > target_accuracy)
168 r_cut_iL_min = r_cut_iL;
169 else
170 r_cut_iL_max = r_cut_iL;
171 }
172
173 /* final result is always the upper interval boundary, since only there
174 * we know that the desired minimal accuracy is obtained */
175 tuned_r_cut_iL = r_cut_iL = r_cut_iL_max;
176
177 auto const report_veto = [&](auto const &veto) {
178 if (veto) {
179 m_logger->log_skip(*veto, mesh[0], cao, r_cut_iL, tuned_alpha_L,
180 tuned_accuracy, rs_err, ks_err);
181 }
182 return static_cast<bool>(veto);
183 };
184
185 /* if we are running P3M+ELC, check that r_cut is compatible */
186 auto const r_cut = r_cut_iL * box_geo.length()[0];
187 if (report_veto(layer_correction_veto_r_cut(r_cut))) {
188 return -P3M_TUNE_ELC_GAP_SIZE;
189 }
190
191 commit(mesh, cao, r_cut_iL, tuned_alpha_L);
193 if (report_veto(fft_decomposition_veto(mesh))) {
195 }
196 auto const int_time = benchmark_integration_step(m_system, m_timings);
197
198 std::tie(tuned_accuracy, rs_err, ks_err, tuned_alpha_L) =
199 calculate_accuracy(mesh, cao, r_cut_iL);
200
201 m_logger->log_success(int_time, mesh[0], cao, r_cut_iL, tuned_alpha_L,
202 tuned_accuracy, rs_err, ks_err);
204 return int_time;
205}
206
207/**
208 * @brief Get the optimal alpha and the corresponding computation time
209 * for a fixed @p mesh.
210 *
211 * @p _cao should contain an initial guess, which is then adapted by stepping
212 * up and down.
213 *
214 * @param[in] mesh @copybrief P3MParameters::mesh
215 * @param[in,out] tuned_cao initial guess for the
216 * @copybrief P3MParameters::cao
217 * @param[out] tuned_r_cut_iL @copybrief P3MParameters::r_cut_iL
218 * @param[out] tuned_alpha_L @copybrief P3MParameters::alpha_L
219 * @param[out] tuned_accuracy @copybrief P3MParameters::accuracy
220 *
221 * @returns The integration time in case of success, otherwise
222 * -@ref P3M_TUNE_CAO_TOO_LARGE
223 */
224double TuningAlgorithm::get_m_time(Utils::Vector3i const &mesh, int &tuned_cao,
225 double &tuned_r_cut_iL,
226 double &tuned_alpha_L,
227 double &tuned_accuracy) {
228 double best_time = -1., tmp_r_cut_iL = 0., tmp_alpha_L = 0.,
229 tmp_accuracy = 0.;
230 /* in which direction improvement is possible. Initially, we don't know it
231 * yet. */
232 int final_dir = 0;
233 int cao = tuned_cao;
234
235 /* the initial step sets a timing mark. If there is no valid r_cut, we can
236 * only try to increase cao to increase the obtainable precision of the far
237 * formula. */
238 double tmp_time;
239 do {
240 tmp_time = get_mc_time(mesh, cao, tmp_r_cut_iL, tmp_alpha_L, tmp_accuracy);
241 /* cao is too large for this grid, but still the accuracy cannot be
242 * achieved, give up */
243 if (tmp_time == -P3M_TUNE_CAO_TOO_LARGE) {
244 return tmp_time;
245 }
246 /* we have a valid time, start optimising from there */
247 if (tmp_time >= 0.) {
248 best_time = tmp_time;
249 tuned_r_cut_iL = tmp_r_cut_iL;
250 tuned_alpha_L = tmp_alpha_L;
251 tuned_accuracy = tmp_accuracy;
252 tuned_cao = cao;
253 break;
254 }
255 /* the required accuracy could not be obtained, try higher caos */
256 cao++;
257 final_dir = 1;
258 } while (cao <= cao_max);
259 /* with this mesh, the required accuracy cannot be obtained. */
260 if (cao > cao_max)
262
263 /* at the boundaries, only the opposite direction can be used for
264 * optimisation
265 */
266 if (cao == cao_min)
267 final_dir = 1;
268 else if (cao == cao_max)
269 final_dir = -1;
270
271 if (final_dir == 0) {
272 /* check in which direction we can optimise. Both directions are possible */
273 double dir_times[3];
274 for (final_dir = -1; final_dir <= 1; final_dir += 2) {
275 dir_times[final_dir + 1] = tmp_time = get_mc_time(
276 mesh, cao + final_dir, tmp_r_cut_iL, tmp_alpha_L, tmp_accuracy);
277 /* in this direction, we cannot optimise, since we get into precision
278 * trouble */
279 if (tmp_time < 0.)
280 continue;
281
282 if (tmp_time < best_time) {
283 best_time = tmp_time;
284 tuned_r_cut_iL = tmp_r_cut_iL;
285 tuned_alpha_L = tmp_alpha_L;
286 tuned_accuracy = tmp_accuracy;
287 tuned_cao = cao + final_dir;
288 }
289 }
290 /* choose the direction which was optimal, if any of the two */
291 if (dir_times[0] == best_time) {
292 final_dir = -1;
293 } else if (dir_times[2] == best_time) {
294 final_dir = 1;
295 } else {
296 /* no improvement in either direction, however if one is only marginally
297 * worse, we can still try; down is possible and not much worse, while
298 * up is either illegal or even worse */
299 if ((dir_times[0] >= 0 && dir_times[0] < best_time + time_granularity) &&
300 (dir_times[2] < 0 || dir_times[2] > dir_times[0]))
301 final_dir = -1;
302 /* same for up */
303 else if ((dir_times[2] >= 0 &&
304 dir_times[2] < best_time + time_granularity) &&
305 (dir_times[0] < 0 || dir_times[0] > dir_times[2]))
306 final_dir = 1;
307 else {
308 /* really no chance for optimisation */
309 return best_time;
310 }
311 }
312 /* we already checked the initial cao and its neighbor */
313 cao += 2 * final_dir;
314 } else {
315 /* here some constraint is active, and we only checked the initial cao
316 * itself */
317 cao += final_dir;
318 }
319
320 /* move cao into the optimisation direction until we do not gain anymore. */
321 for (; cao >= cao_min && cao <= cao_max; cao += final_dir) {
322 tmp_time = get_mc_time(mesh, cao, tmp_r_cut_iL, tmp_alpha_L, tmp_accuracy);
323 /* if we cannot meet the precision anymore, give up */
324 if (tmp_time < 0.)
325 break;
326
327 if (tmp_time < best_time) {
328 best_time = tmp_time;
329 tuned_r_cut_iL = tmp_r_cut_iL;
330 tuned_alpha_L = tmp_alpha_L;
331 tuned_accuracy = tmp_accuracy;
332 tuned_cao = cao;
333 } else if (tmp_time > best_time + time_granularity) {
334 /* no hope of further optimisation */
335 break;
336 }
337 }
338 return best_time;
339}
340
341#endif // ESPRESSO_P3M or ESPRESSO_DP3M
static auto constexpr P3M_TUNE_ACCURACY_TOO_LARGE
could not achieve target accuracy
static auto constexpr P3M_RCUT_PREC
Precision threshold for a non-zero real-space cutoff.
static auto constexpr P3M_TUNE_ELC_GAP_SIZE
conflict with ELC gap size
static auto constexpr P3M_TUNE_FFT_MESH_SIZE
conflict with FFT domain decomposition
static auto constexpr P3M_TUNE_CAO_TOO_LARGE
charge assignment order too large for mesh size
std::shared_ptr< LocalBox > local_geo
std::shared_ptr< CellStructure > cell_structure
std::shared_ptr< BoxGeometry > box_geo
double get_m_time(Utils::Vector3i const &mesh, int &tuned_cao, double &tuned_r_cut_iL, double &tuned_alpha_L, double &tuned_accuracy)
Get the optimal alpha and the corresponding computation time for a fixed mesh.
virtual std::optional< std::string > fft_decomposition_veto(Utils::Vector3i const &) const
Veto FFT decomposition in non-cubic boxes.
System::System & m_system
virtual void on_solver_change() const =0
Re-initialize the currently active solver.
double get_mc_time(Utils::Vector3i const &mesh, int cao, double &tuned_r_cut_iL, double &tuned_alpha_L, double &tuned_accuracy)
Get the optimal alpha and the corresponding computation time for a fixed mesh and cao.
virtual std::tuple< double, double, double, double > calculate_accuracy(Utils::Vector3i const &mesh, int cao, double r_cut_iL) const =0
Get the minimal error for this combination of parameters.
virtual std::optional< std::string > layer_correction_veto_r_cut(double r_cut) const =0
Veto real-space cutoffs larger than the layer correction gap.
void commit(Utils::Vector3i const &mesh, int cao, double r_cut_iL, double alpha_L)
Write tuned parameters to the P3M parameter struct.
void determine_cao_limits(int initial_cao)
Determine a sensible range for the charge assignment order.
void determine_r_cut_limits()
Determine a sensible range for the real-space cutoff.
virtual P3MParameters & get_params()=0
Get the P3M parameters.
std::unique_ptr< TuningLogger > m_logger
static auto constexpr time_granularity
Granularity of the time measurement (milliseconds).
constexpr int p3m_min_cao
Minimal charge assignment order.
Definition math.hpp:48
constexpr int p3m_max_cao
Maximal charge assignment order.
Definition math.hpp:50
auto hadamard_division(Vector< T, N > const &a, Vector< U, N > const &b)
Definition Vector.hpp:395
Common functions for dipolar and charge P3M.
double r_cut_iL
cutoff radius for real space electrostatics (>0), rescaled to r_cut_iL = r_cut * box_l_i.
int cao
charge assignment order ([0,7]).
double accuracy
accuracy of the actual parameter set.
double benchmark_integration_step(System::System &system, int int_steps)
Benchmark the integration loop.
Definition tuning.cpp:73