Merge branch release-2020 into master
[alexxy/gromacs.git] / src / gromacs / mdrun / minimize.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
5  * Copyright (c) 2001-2004, The GROMACS development team.
6  * Copyright (c) 2013,2014,2015,2016,2017 The GROMACS development team.
7  * Copyright (c) 2018,2019,2020, by the GROMACS development team, led by
8  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
9  * and including many others, as listed in the AUTHORS file in the
10  * top-level source directory and at http://www.gromacs.org.
11  *
12  * GROMACS is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public License
14  * as published by the Free Software Foundation; either version 2.1
15  * of the License, or (at your option) any later version.
16  *
17  * GROMACS is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with GROMACS; if not, see
24  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
26  *
27  * If you want to redistribute modifications to GROMACS, please
28  * consider that scientific software is very special. Version
29  * control is crucial - bugs must be traceable. We will be happy to
30  * consider code for inclusion in the official distribution, but
31  * derived work must not be called official GROMACS. Details are found
32  * in the README & COPYING files - if they are missing, get the
33  * official version at http://www.gromacs.org.
34  *
35  * To help us fund GROMACS development, we humbly ask that you cite
36  * the research papers on the package. Check out http://www.gromacs.org.
37  */
38 /*! \internal \file
39  *
40  * \brief This file defines integrators for energy minimization
41  *
42  * \author Berk Hess <hess@kth.se>
43  * \author Erik Lindahl <erik@kth.se>
44  * \ingroup module_mdrun
45  */
46 #include "gmxpre.h"
47
48 #include "config.h"
49
50 #include <cmath>
51 #include <cstring>
52 #include <ctime>
53
54 #include <algorithm>
55 #include <vector>
56
57 #include "gromacs/commandline/filenm.h"
58 #include "gromacs/domdec/collect.h"
59 #include "gromacs/domdec/dlbtiming.h"
60 #include "gromacs/domdec/domdec.h"
61 #include "gromacs/domdec/domdec_struct.h"
62 #include "gromacs/domdec/mdsetup.h"
63 #include "gromacs/domdec/partition.h"
64 #include "gromacs/ewald/pme_pp.h"
65 #include "gromacs/fileio/confio.h"
66 #include "gromacs/fileio/mtxio.h"
67 #include "gromacs/gmxlib/network.h"
68 #include "gromacs/gmxlib/nrnb.h"
69 #include "gromacs/imd/imd.h"
70 #include "gromacs/linearalgebra/sparsematrix.h"
71 #include "gromacs/listed_forces/manage_threading.h"
72 #include "gromacs/math/functions.h"
73 #include "gromacs/math/vec.h"
74 #include "gromacs/mdlib/constr.h"
75 #include "gromacs/mdlib/dispersioncorrection.h"
76 #include "gromacs/mdlib/ebin.h"
77 #include "gromacs/mdlib/enerdata_utils.h"
78 #include "gromacs/mdlib/energyoutput.h"
79 #include "gromacs/mdlib/force.h"
80 #include "gromacs/mdlib/force_flags.h"
81 #include "gromacs/mdlib/forcerec.h"
82 #include "gromacs/mdlib/gmx_omp_nthreads.h"
83 #include "gromacs/mdlib/md_support.h"
84 #include "gromacs/mdlib/mdatoms.h"
85 #include "gromacs/mdlib/stat.h"
86 #include "gromacs/mdlib/tgroup.h"
87 #include "gromacs/mdlib/trajectory_writing.h"
88 #include "gromacs/mdlib/update.h"
89 #include "gromacs/mdlib/vsite.h"
90 #include "gromacs/mdrunutility/handlerestart.h"
91 #include "gromacs/mdrunutility/printtime.h"
92 #include "gromacs/mdtypes/commrec.h"
93 #include "gromacs/mdtypes/forcerec.h"
94 #include "gromacs/mdtypes/inputrec.h"
95 #include "gromacs/mdtypes/interaction_const.h"
96 #include "gromacs/mdtypes/md_enums.h"
97 #include "gromacs/mdtypes/mdatom.h"
98 #include "gromacs/mdtypes/mdrunoptions.h"
99 #include "gromacs/mdtypes/state.h"
100 #include "gromacs/pbcutil/mshift.h"
101 #include "gromacs/pbcutil/pbc.h"
102 #include "gromacs/timing/wallcycle.h"
103 #include "gromacs/timing/walltime_accounting.h"
104 #include "gromacs/topology/mtop_util.h"
105 #include "gromacs/topology/topology.h"
106 #include "gromacs/utility/cstringutil.h"
107 #include "gromacs/utility/exceptions.h"
108 #include "gromacs/utility/fatalerror.h"
109 #include "gromacs/utility/logger.h"
110 #include "gromacs/utility/smalloc.h"
111
112 #include "legacysimulator.h"
113 #include "shellfc.h"
114
115 using gmx::ArrayRef;
116 using gmx::MdrunScheduleWorkload;
117 using gmx::RVec;
118
119 //! Utility structure for manipulating states during EM
120 typedef struct
121 {
122     //! Copy of the global state
123     t_state s;
124     //! Force array
125     PaddedHostVector<gmx::RVec> f;
126     //! Potential energy
127     real epot;
128     //! Norm of the force
129     real fnorm;
130     //! Maximum force
131     real fmax;
132     //! Direction
133     int a_fmax;
134 } em_state_t;
135
136 //! Print the EM starting conditions
137 static void print_em_start(FILE*                     fplog,
138                            const t_commrec*          cr,
139                            gmx_walltime_accounting_t walltime_accounting,
140                            gmx_wallcycle_t           wcycle,
141                            const char*               name)
142 {
143     walltime_accounting_start_time(walltime_accounting);
144     wallcycle_start(wcycle, ewcRUN);
145     print_start(fplog, cr, walltime_accounting, name);
146 }
147
148 //! Stop counting time for EM
149 static void em_time_end(gmx_walltime_accounting_t walltime_accounting, gmx_wallcycle_t wcycle)
150 {
151     wallcycle_stop(wcycle, ewcRUN);
152
153     walltime_accounting_end_time(walltime_accounting);
154 }
155
156 //! Printing a log file and console header
157 static void sp_header(FILE* out, const char* minimizer, real ftol, int nsteps)
158 {
159     fprintf(out, "\n");
160     fprintf(out, "%s:\n", minimizer);
161     fprintf(out, "   Tolerance (Fmax)   = %12.5e\n", ftol);
162     fprintf(out, "   Number of steps    = %12d\n", nsteps);
163 }
164
165 //! Print warning message
166 static void warn_step(FILE* fp, real ftol, real fmax, gmx_bool bLastStep, gmx_bool bConstrain)
167 {
168     constexpr bool realIsDouble = GMX_DOUBLE;
169     char           buffer[2048];
170
171     if (!std::isfinite(fmax))
172     {
173         sprintf(buffer,
174                 "\nEnergy minimization has stopped because the force "
175                 "on at least one atom is not finite. This usually means "
176                 "atoms are overlapping. Modify the input coordinates to "
177                 "remove atom overlap or use soft-core potentials with "
178                 "the free energy code to avoid infinite forces.\n%s",
179                 !realIsDouble ? "You could also be lucky that switching to double precision "
180                                 "is sufficient to obtain finite forces.\n"
181                               : "");
182     }
183     else if (bLastStep)
184     {
185         sprintf(buffer,
186                 "\nEnergy minimization reached the maximum number "
187                 "of steps before the forces reached the requested "
188                 "precision Fmax < %g.\n",
189                 ftol);
190     }
191     else
192     {
193         sprintf(buffer,
194                 "\nEnergy minimization has stopped, but the forces have "
195                 "not converged to the requested precision Fmax < %g (which "
196                 "may not be possible for your system). It stopped "
197                 "because the algorithm tried to make a new step whose size "
198                 "was too small, or there was no change in the energy since "
199                 "last step. Either way, we regard the minimization as "
200                 "converged to within the available machine precision, "
201                 "given your starting configuration and EM parameters.\n%s%s",
202                 ftol,
203                 !realIsDouble ? "\nDouble precision normally gives you higher accuracy, but "
204                                 "this is often not needed for preparing to run molecular "
205                                 "dynamics.\n"
206                               : "",
207                 bConstrain ? "You might need to increase your constraint accuracy, or turn\n"
208                              "off constraints altogether (set constraints = none in mdp file)\n"
209                            : "");
210     }
211
212     fputs(wrap_lines(buffer, 78, 0, FALSE), stderr);
213     fputs(wrap_lines(buffer, 78, 0, FALSE), fp);
214 }
215
216 //! Print message about convergence of the EM
217 static void print_converged(FILE*             fp,
218                             const char*       alg,
219                             real              ftol,
220                             int64_t           count,
221                             gmx_bool          bDone,
222                             int64_t           nsteps,
223                             const em_state_t* ems,
224                             double            sqrtNumAtoms)
225 {
226     char buf[STEPSTRSIZE];
227
228     if (bDone)
229     {
230         fprintf(fp, "\n%s converged to Fmax < %g in %s steps\n", alg, ftol, gmx_step_str(count, buf));
231     }
232     else if (count < nsteps)
233     {
234         fprintf(fp,
235                 "\n%s converged to machine precision in %s steps,\n"
236                 "but did not reach the requested Fmax < %g.\n",
237                 alg, gmx_step_str(count, buf), ftol);
238     }
239     else
240     {
241         fprintf(fp, "\n%s did not converge to Fmax < %g in %s steps.\n", alg, ftol,
242                 gmx_step_str(count, buf));
243     }
244
245 #if GMX_DOUBLE
246     fprintf(fp, "Potential Energy  = %21.14e\n", ems->epot);
247     fprintf(fp, "Maximum force     = %21.14e on atom %d\n", ems->fmax, ems->a_fmax + 1);
248     fprintf(fp, "Norm of force     = %21.14e\n", ems->fnorm / sqrtNumAtoms);
249 #else
250     fprintf(fp, "Potential Energy  = %14.7e\n", ems->epot);
251     fprintf(fp, "Maximum force     = %14.7e on atom %d\n", ems->fmax, ems->a_fmax + 1);
252     fprintf(fp, "Norm of force     = %14.7e\n", ems->fnorm / sqrtNumAtoms);
253 #endif
254 }
255
256 //! Compute the norm and max of the force array in parallel
257 static void get_f_norm_max(const t_commrec* cr,
258                            t_grpopts*       opts,
259                            t_mdatoms*       mdatoms,
260                            const rvec*      f,
261                            real*            fnorm,
262                            real*            fmax,
263                            int*             a_fmax)
264 {
265     double fnorm2, *sum;
266     real   fmax2, fam;
267     int    la_max, a_max, start, end, i, m, gf;
268
269     /* This routine finds the largest force and returns it.
270      * On parallel machines the global max is taken.
271      */
272     fnorm2 = 0;
273     fmax2  = 0;
274     la_max = -1;
275     start  = 0;
276     end    = mdatoms->homenr;
277     if (mdatoms->cFREEZE)
278     {
279         for (i = start; i < end; i++)
280         {
281             gf  = mdatoms->cFREEZE[i];
282             fam = 0;
283             for (m = 0; m < DIM; m++)
284             {
285                 if (!opts->nFreeze[gf][m])
286                 {
287                     fam += gmx::square(f[i][m]);
288                 }
289             }
290             fnorm2 += fam;
291             if (fam > fmax2)
292             {
293                 fmax2  = fam;
294                 la_max = i;
295             }
296         }
297     }
298     else
299     {
300         for (i = start; i < end; i++)
301         {
302             fam = norm2(f[i]);
303             fnorm2 += fam;
304             if (fam > fmax2)
305             {
306                 fmax2  = fam;
307                 la_max = i;
308             }
309         }
310     }
311
312     if (la_max >= 0 && DOMAINDECOMP(cr))
313     {
314         a_max = cr->dd->globalAtomIndices[la_max];
315     }
316     else
317     {
318         a_max = la_max;
319     }
320     if (PAR(cr))
321     {
322         snew(sum, 2 * cr->nnodes + 1);
323         sum[2 * cr->nodeid]     = fmax2;
324         sum[2 * cr->nodeid + 1] = a_max;
325         sum[2 * cr->nnodes]     = fnorm2;
326         gmx_sumd(2 * cr->nnodes + 1, sum, cr);
327         fnorm2 = sum[2 * cr->nnodes];
328         /* Determine the global maximum */
329         for (i = 0; i < cr->nnodes; i++)
330         {
331             if (sum[2 * i] > fmax2)
332             {
333                 fmax2 = sum[2 * i];
334                 a_max = gmx::roundToInt(sum[2 * i + 1]);
335             }
336         }
337         sfree(sum);
338     }
339
340     if (fnorm)
341     {
342         *fnorm = sqrt(fnorm2);
343     }
344     if (fmax)
345     {
346         *fmax = sqrt(fmax2);
347     }
348     if (a_fmax)
349     {
350         *a_fmax = a_max;
351     }
352 }
353
354 //! Compute the norm of the force
355 static void get_state_f_norm_max(const t_commrec* cr, t_grpopts* opts, t_mdatoms* mdatoms, em_state_t* ems)
356 {
357     get_f_norm_max(cr, opts, mdatoms, ems->f.rvec_array(), &ems->fnorm, &ems->fmax, &ems->a_fmax);
358 }
359
360 //! Initialize the energy minimization
361 static void init_em(FILE*                fplog,
362                     const gmx::MDLogger& mdlog,
363                     const char*          title,
364                     const t_commrec*     cr,
365                     t_inputrec*          ir,
366                     gmx::ImdSession*     imdSession,
367                     pull_t*              pull_work,
368                     t_state*             state_global,
369                     gmx_mtop_t*          top_global,
370                     em_state_t*          ems,
371                     gmx_localtop_t*      top,
372                     t_nrnb*              nrnb,
373                     t_forcerec*          fr,
374                     t_graph**            graph,
375                     gmx::MDAtoms*        mdAtoms,
376                     gmx_global_stat_t*   gstat,
377                     gmx_vsite_t*         vsite,
378                     gmx::Constraints*    constr,
379                     gmx_shellfc_t**      shellfc)
380 {
381     real dvdl_constr;
382
383     if (fplog)
384     {
385         fprintf(fplog, "Initiating %s\n", title);
386     }
387
388     if (MASTER(cr))
389     {
390         state_global->ngtc = 0;
391     }
392     initialize_lambdas(fplog, *ir, MASTER(cr), &(state_global->fep_state), state_global->lambda, nullptr);
393
394     if (ir->eI == eiNM)
395     {
396         GMX_ASSERT(shellfc != nullptr, "With NM we always support shells");
397
398         *shellfc = init_shell_flexcon(stdout, top_global, constr ? constr->numFlexibleConstraints() : 0,
399                                       ir->nstcalcenergy, DOMAINDECOMP(cr));
400     }
401     else
402     {
403         GMX_ASSERT(EI_ENERGY_MINIMIZATION(ir->eI),
404                    "This else currently only handles energy minimizers, consider if your algorithm "
405                    "needs shell/flexible-constraint support");
406
407         /* With energy minimization, shells and flexible constraints are
408          * automatically minimized when treated like normal DOFS.
409          */
410         if (shellfc != nullptr)
411         {
412             *shellfc = nullptr;
413         }
414     }
415
416     auto mdatoms = mdAtoms->mdatoms();
417     if (DOMAINDECOMP(cr))
418     {
419         top->useInDomainDecomp_ = true;
420         dd_init_local_top(*top_global, top);
421
422         dd_init_local_state(cr->dd, state_global, &ems->s);
423
424         /* Distribute the charge groups over the nodes from the master node */
425         dd_partition_system(fplog, mdlog, ir->init_step, cr, TRUE, 1, state_global, *top_global, ir,
426                             imdSession, pull_work, &ems->s, &ems->f, mdAtoms, top, fr, vsite,
427                             constr, nrnb, nullptr, FALSE);
428         dd_store_state(cr->dd, &ems->s);
429
430         *graph = nullptr;
431     }
432     else
433     {
434         state_change_natoms(state_global, state_global->natoms);
435         /* Just copy the state */
436         ems->s = *state_global;
437         state_change_natoms(&ems->s, ems->s.natoms);
438         ems->f.resizeWithPadding(ems->s.natoms);
439
440         mdAlgorithmsSetupAtomData(cr, ir, *top_global, top, fr, graph, mdAtoms, constr, vsite,
441                                   shellfc ? *shellfc : nullptr);
442
443         if (vsite)
444         {
445             set_vsite_top(vsite, top, mdatoms);
446         }
447     }
448
449     update_mdatoms(mdAtoms->mdatoms(), ems->s.lambda[efptMASS]);
450
451     if (constr)
452     {
453         // TODO how should this cross-module support dependency be managed?
454         if (ir->eConstrAlg == econtSHAKE && gmx_mtop_ftype_count(top_global, F_CONSTR) > 0)
455         {
456             gmx_fatal(FARGS, "Can not do energy minimization with %s, use %s\n",
457                       econstr_names[econtSHAKE], econstr_names[econtLINCS]);
458         }
459
460         if (!ir->bContinuation)
461         {
462             /* Constrain the starting coordinates */
463             dvdl_constr = 0;
464             constr->apply(TRUE, TRUE, -1, 0, 1.0, ems->s.x.arrayRefWithPadding(),
465                           ems->s.x.arrayRefWithPadding(), ArrayRef<RVec>(), ems->s.box,
466                           ems->s.lambda[efptFEP], &dvdl_constr, gmx::ArrayRefWithPadding<RVec>(),
467                           nullptr, gmx::ConstraintVariable::Positions);
468         }
469     }
470
471     if (PAR(cr))
472     {
473         *gstat = global_stat_init(ir);
474     }
475     else
476     {
477         *gstat = nullptr;
478     }
479
480     calc_shifts(ems->s.box, fr->shift_vec);
481 }
482
483 //! Finalize the minimization
484 static void finish_em(const t_commrec*          cr,
485                       gmx_mdoutf_t              outf,
486                       gmx_walltime_accounting_t walltime_accounting,
487                       gmx_wallcycle_t           wcycle)
488 {
489     if (!thisRankHasDuty(cr, DUTY_PME))
490     {
491         /* Tell the PME only node to finish */
492         gmx_pme_send_finish(cr);
493     }
494
495     done_mdoutf(outf);
496
497     em_time_end(walltime_accounting, wcycle);
498 }
499
500 //! Swap two different EM states during minimization
501 static void swap_em_state(em_state_t** ems1, em_state_t** ems2)
502 {
503     em_state_t* tmp;
504
505     tmp   = *ems1;
506     *ems1 = *ems2;
507     *ems2 = tmp;
508 }
509
510 //! Save the EM trajectory
511 static void write_em_traj(FILE*               fplog,
512                           const t_commrec*    cr,
513                           gmx_mdoutf_t        outf,
514                           gmx_bool            bX,
515                           gmx_bool            bF,
516                           const char*         confout,
517                           gmx_mtop_t*         top_global,
518                           t_inputrec*         ir,
519                           int64_t             step,
520                           em_state_t*         state,
521                           t_state*            state_global,
522                           ObservablesHistory* observablesHistory)
523 {
524     int mdof_flags = 0;
525
526     if (bX)
527     {
528         mdof_flags |= MDOF_X;
529     }
530     if (bF)
531     {
532         mdof_flags |= MDOF_F;
533     }
534
535     /* If we want IMD output, set appropriate MDOF flag */
536     if (ir->bIMD)
537     {
538         mdof_flags |= MDOF_IMD;
539     }
540
541     mdoutf_write_to_trajectory_files(fplog, cr, outf, mdof_flags, top_global->natoms, step,
542                                      static_cast<double>(step), &state->s, state_global,
543                                      observablesHistory, state->f);
544
545     if (confout != nullptr)
546     {
547         if (DOMAINDECOMP(cr))
548         {
549             /* If bX=true, x was collected to state_global in the call above */
550             if (!bX)
551             {
552                 auto globalXRef = MASTER(cr) ? state_global->x : gmx::ArrayRef<gmx::RVec>();
553                 dd_collect_vec(cr->dd, &state->s, state->s.x, globalXRef);
554             }
555         }
556         else
557         {
558             /* Copy the local state pointer */
559             state_global = &state->s;
560         }
561
562         if (MASTER(cr))
563         {
564             if (ir->pbcType != PbcType::No && !ir->bPeriodicMols && DOMAINDECOMP(cr))
565             {
566                 /* Make molecules whole only for confout writing */
567                 do_pbc_mtop(ir->pbcType, state->s.box, top_global, state_global->x.rvec_array());
568             }
569
570             write_sto_conf_mtop(confout, *top_global->name, top_global,
571                                 state_global->x.rvec_array(), nullptr, ir->pbcType, state->s.box);
572         }
573     }
574 }
575
576 //! \brief Do one minimization step
577 //
578 // \returns true when the step succeeded, false when a constraint error occurred
579 static bool do_em_step(const t_commrec*                   cr,
580                        t_inputrec*                        ir,
581                        t_mdatoms*                         md,
582                        em_state_t*                        ems1,
583                        real                               a,
584                        const PaddedHostVector<gmx::RVec>* force,
585                        em_state_t*                        ems2,
586                        gmx::Constraints*                  constr,
587                        int64_t                            count)
588
589 {
590     t_state *s1, *s2;
591     int      start, end;
592     real     dvdl_constr;
593     int nthreads gmx_unused;
594
595     bool validStep = true;
596
597     s1 = &ems1->s;
598     s2 = &ems2->s;
599
600     if (DOMAINDECOMP(cr) && s1->ddp_count != cr->dd->ddp_count)
601     {
602         gmx_incons("state mismatch in do_em_step");
603     }
604
605     s2->flags = s1->flags;
606
607     if (s2->natoms != s1->natoms)
608     {
609         state_change_natoms(s2, s1->natoms);
610         ems2->f.resizeWithPadding(s2->natoms);
611     }
612     if (DOMAINDECOMP(cr) && s2->cg_gl.size() != s1->cg_gl.size())
613     {
614         s2->cg_gl.resize(s1->cg_gl.size());
615     }
616
617     copy_mat(s1->box, s2->box);
618     /* Copy free energy state */
619     s2->lambda = s1->lambda;
620     copy_mat(s1->box, s2->box);
621
622     start = 0;
623     end   = md->homenr;
624
625     nthreads = gmx_omp_nthreads_get(emntUpdate);
626 #pragma omp parallel num_threads(nthreads)
627     {
628         const rvec* x1 = s1->x.rvec_array();
629         rvec*       x2 = s2->x.rvec_array();
630         const rvec* f  = force->rvec_array();
631
632         int gf = 0;
633 #pragma omp for schedule(static) nowait
634         for (int i = start; i < end; i++)
635         {
636             try
637             {
638                 if (md->cFREEZE)
639                 {
640                     gf = md->cFREEZE[i];
641                 }
642                 for (int m = 0; m < DIM; m++)
643                 {
644                     if (ir->opts.nFreeze[gf][m])
645                     {
646                         x2[i][m] = x1[i][m];
647                     }
648                     else
649                     {
650                         x2[i][m] = x1[i][m] + a * f[i][m];
651                     }
652                 }
653             }
654             GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR
655         }
656
657         if (s2->flags & (1 << estCGP))
658         {
659             /* Copy the CG p vector */
660             const rvec* p1 = s1->cg_p.rvec_array();
661             rvec*       p2 = s2->cg_p.rvec_array();
662 #pragma omp for schedule(static) nowait
663             for (int i = start; i < end; i++)
664             {
665                 // Trivial OpenMP block that does not throw
666                 copy_rvec(p1[i], p2[i]);
667             }
668         }
669
670         if (DOMAINDECOMP(cr))
671         {
672             /* OpenMP does not supported unsigned loop variables */
673 #pragma omp for schedule(static) nowait
674             for (gmx::index i = 0; i < gmx::ssize(s2->cg_gl); i++)
675             {
676                 s2->cg_gl[i] = s1->cg_gl[i];
677             }
678         }
679     }
680
681     if (DOMAINDECOMP(cr))
682     {
683         s2->ddp_count       = s1->ddp_count;
684         s2->ddp_count_cg_gl = s1->ddp_count_cg_gl;
685     }
686
687     if (constr)
688     {
689         dvdl_constr = 0;
690         validStep   = constr->apply(
691                 TRUE, TRUE, count, 0, 1.0, s1->x.arrayRefWithPadding(), s2->x.arrayRefWithPadding(),
692                 ArrayRef<RVec>(), s2->box, s2->lambda[efptBONDED], &dvdl_constr,
693                 gmx::ArrayRefWithPadding<RVec>(), nullptr, gmx::ConstraintVariable::Positions);
694
695         if (cr->nnodes > 1)
696         {
697             /* This global reduction will affect performance at high
698              * parallelization, but we can not really avoid it.
699              * But usually EM is not run at high parallelization.
700              */
701             int reductionBuffer = static_cast<int>(!validStep);
702             gmx_sumi(1, &reductionBuffer, cr);
703             validStep = (reductionBuffer == 0);
704         }
705
706         // We should move this check to the different minimizers
707         if (!validStep && ir->eI != eiSteep)
708         {
709             gmx_fatal(FARGS,
710                       "The coordinates could not be constrained. Minimizer '%s' can not handle "
711                       "constraint failures, use minimizer '%s' before using '%s'.",
712                       EI(ir->eI), EI(eiSteep), EI(ir->eI));
713         }
714     }
715
716     return validStep;
717 }
718
719 //! Prepare EM for using domain decomposition parallellization
720 static void em_dd_partition_system(FILE*                fplog,
721                                    const gmx::MDLogger& mdlog,
722                                    int                  step,
723                                    const t_commrec*     cr,
724                                    gmx_mtop_t*          top_global,
725                                    t_inputrec*          ir,
726                                    gmx::ImdSession*     imdSession,
727                                    pull_t*              pull_work,
728                                    em_state_t*          ems,
729                                    gmx_localtop_t*      top,
730                                    gmx::MDAtoms*        mdAtoms,
731                                    t_forcerec*          fr,
732                                    gmx_vsite_t*         vsite,
733                                    gmx::Constraints*    constr,
734                                    t_nrnb*              nrnb,
735                                    gmx_wallcycle_t      wcycle)
736 {
737     /* Repartition the domain decomposition */
738     dd_partition_system(fplog, mdlog, step, cr, FALSE, 1, nullptr, *top_global, ir, imdSession, pull_work,
739                         &ems->s, &ems->f, mdAtoms, top, fr, vsite, constr, nrnb, wcycle, FALSE);
740     dd_store_state(cr->dd, &ems->s);
741 }
742
743 namespace
744 {
745
746 /*! \brief Class to handle the work of setting and doing an energy evaluation.
747  *
748  * This class is a mere aggregate of parameters to pass to evaluate an
749  * energy, so that future changes to names and types of them consume
750  * less time when refactoring other code.
751  *
752  * Aggregate initialization is used, for which the chief risk is that
753  * if a member is added at the end and not all initializer lists are
754  * updated, then the member will be value initialized, which will
755  * typically mean initialization to zero.
756  *
757  * Use a braced initializer list to construct one of these. */
758 class EnergyEvaluator
759 {
760 public:
761     /*! \brief Evaluates an energy on the state in \c ems.
762      *
763      * \todo In practice, the same objects mu_tot, vir, and pres
764      * are always passed to this function, so we would rather have
765      * them as data members. However, their C-array types are
766      * unsuited for aggregate initialization. When the types
767      * improve, the call signature of this method can be reduced.
768      */
769     void run(em_state_t* ems, rvec mu_tot, tensor vir, tensor pres, int64_t count, gmx_bool bFirst);
770     //! Handles logging (deprecated).
771     FILE* fplog;
772     //! Handles logging.
773     const gmx::MDLogger& mdlog;
774     //! Handles communication.
775     const t_commrec* cr;
776     //! Coordinates multi-simulations.
777     const gmx_multisim_t* ms;
778     //! Holds the simulation topology.
779     gmx_mtop_t* top_global;
780     //! Holds the domain topology.
781     gmx_localtop_t* top;
782     //! User input options.
783     t_inputrec* inputrec;
784     //! The Interactive Molecular Dynamics session.
785     gmx::ImdSession* imdSession;
786     //! The pull work object.
787     pull_t* pull_work;
788     //! Manages flop accounting.
789     t_nrnb* nrnb;
790     //! Manages wall cycle accounting.
791     gmx_wallcycle_t wcycle;
792     //! Coordinates global reduction.
793     gmx_global_stat_t gstat;
794     //! Handles virtual sites.
795     gmx_vsite_t* vsite;
796     //! Handles constraints.
797     gmx::Constraints* constr;
798     //! Handles strange things.
799     t_fcdata* fcd;
800     //! Molecular graph for SHAKE.
801     t_graph* graph;
802     //! Per-atom data for this domain.
803     gmx::MDAtoms* mdAtoms;
804     //! Handles how to calculate the forces.
805     t_forcerec* fr;
806     //! Schedule of force-calculation work each step for this task.
807     MdrunScheduleWorkload* runScheduleWork;
808     //! Stores the computed energies.
809     gmx_enerdata_t* enerd;
810 };
811
812 void EnergyEvaluator::run(em_state_t* ems, rvec mu_tot, tensor vir, tensor pres, int64_t count, gmx_bool bFirst)
813 {
814     real     t;
815     gmx_bool bNS;
816     tensor   force_vir, shake_vir, ekin;
817     real     dvdl_constr;
818     real     terminate = 0;
819
820     /* Set the time to the initial time, the time does not change during EM */
821     t = inputrec->init_t;
822
823     if (bFirst || (DOMAINDECOMP(cr) && ems->s.ddp_count < cr->dd->ddp_count))
824     {
825         /* This is the first state or an old state used before the last ns */
826         bNS = TRUE;
827     }
828     else
829     {
830         bNS = FALSE;
831         if (inputrec->nstlist > 0)
832         {
833             bNS = TRUE;
834         }
835     }
836
837     if (vsite)
838     {
839         construct_vsites(vsite, ems->s.x.rvec_array(), 1, nullptr, top->idef.iparams, top->idef.il,
840                          fr->pbcType, fr->bMolPBC, cr, ems->s.box);
841     }
842
843     if (DOMAINDECOMP(cr) && bNS)
844     {
845         /* Repartition the domain decomposition */
846         em_dd_partition_system(fplog, mdlog, count, cr, top_global, inputrec, imdSession, pull_work,
847                                ems, top, mdAtoms, fr, vsite, constr, nrnb, wcycle);
848     }
849
850     /* Calc force & energy on new trial position  */
851     /* do_force always puts the charge groups in the box and shifts again
852      * We do not unshift, so molecules are always whole in congrad.c
853      */
854     do_force(fplog, cr, ms, inputrec, nullptr, nullptr, imdSession, pull_work, count, nrnb, wcycle,
855              top, ems->s.box, ems->s.x.arrayRefWithPadding(), &ems->s.hist,
856              ems->f.arrayRefWithPadding(), force_vir, mdAtoms->mdatoms(), enerd, fcd, ems->s.lambda,
857              graph, fr, runScheduleWork, vsite, mu_tot, t, nullptr,
858              GMX_FORCE_STATECHANGED | GMX_FORCE_ALLFORCES | GMX_FORCE_VIRIAL | GMX_FORCE_ENERGY
859                      | (bNS ? GMX_FORCE_NS : 0),
860              DDBalanceRegionHandler(cr));
861
862     /* Clear the unused shake virial and pressure */
863     clear_mat(shake_vir);
864     clear_mat(pres);
865
866     /* Communicate stuff when parallel */
867     if (PAR(cr) && inputrec->eI != eiNM)
868     {
869         wallcycle_start(wcycle, ewcMoveE);
870
871         global_stat(gstat, cr, enerd, force_vir, shake_vir, inputrec, nullptr, nullptr, nullptr, 1,
872                     &terminate, nullptr, FALSE, CGLO_ENERGY | CGLO_PRESSURE | CGLO_CONSTRAINT);
873
874         wallcycle_stop(wcycle, ewcMoveE);
875     }
876
877     if (fr->dispersionCorrection)
878     {
879         /* Calculate long range corrections to pressure and energy */
880         const DispersionCorrection::Correction correction =
881                 fr->dispersionCorrection->calculate(ems->s.box, ems->s.lambda[efptVDW]);
882
883         enerd->term[F_DISPCORR] = correction.energy;
884         enerd->term[F_EPOT] += correction.energy;
885         enerd->term[F_PRES] += correction.pressure;
886         enerd->term[F_DVDL] += correction.dvdl;
887     }
888     else
889     {
890         enerd->term[F_DISPCORR] = 0;
891     }
892
893     ems->epot = enerd->term[F_EPOT];
894
895     if (constr)
896     {
897         /* Project out the constraint components of the force */
898         dvdl_constr = 0;
899         auto f      = ems->f.arrayRefWithPadding();
900         constr->apply(FALSE, FALSE, count, 0, 1.0, ems->s.x.arrayRefWithPadding(), f,
901                       f.unpaddedArrayRef(), ems->s.box, ems->s.lambda[efptBONDED], &dvdl_constr,
902                       gmx::ArrayRefWithPadding<RVec>(), &shake_vir, gmx::ConstraintVariable::ForceDispl);
903         enerd->term[F_DVDL_CONSTR] += dvdl_constr;
904         m_add(force_vir, shake_vir, vir);
905     }
906     else
907     {
908         copy_mat(force_vir, vir);
909     }
910
911     clear_mat(ekin);
912     enerd->term[F_PRES] = calc_pres(fr->pbcType, inputrec->nwall, ems->s.box, ekin, vir, pres);
913
914     sum_dhdl(enerd, ems->s.lambda, *inputrec->fepvals);
915
916     if (EI_ENERGY_MINIMIZATION(inputrec->eI))
917     {
918         get_state_f_norm_max(cr, &(inputrec->opts), mdAtoms->mdatoms(), ems);
919     }
920 }
921
922 } // namespace
923
924 //! Parallel utility summing energies and forces
925 static double reorder_partsum(const t_commrec* cr,
926                               t_grpopts*       opts,
927                               gmx_mtop_t*      top_global,
928                               em_state_t*      s_min,
929                               em_state_t*      s_b)
930 {
931     if (debug)
932     {
933         fprintf(debug, "Doing reorder_partsum\n");
934     }
935
936     const rvec* fm = s_min->f.rvec_array();
937     const rvec* fb = s_b->f.rvec_array();
938
939     /* Collect fm in a global vector fmg.
940      * This conflicts with the spirit of domain decomposition,
941      * but to fully optimize this a much more complicated algorithm is required.
942      */
943     const int natoms = top_global->natoms;
944     rvec*     fmg;
945     snew(fmg, natoms);
946
947     gmx::ArrayRef<const int> indicesMin = s_min->s.cg_gl;
948     int                      i          = 0;
949     for (int a : indicesMin)
950     {
951         copy_rvec(fm[i], fmg[a]);
952         i++;
953     }
954     gmx_sum(top_global->natoms * 3, fmg[0], cr);
955
956     /* Now we will determine the part of the sum for the cgs in state s_b */
957     gmx::ArrayRef<const int> indicesB = s_b->s.cg_gl;
958
959     double partsum                  = 0;
960     i                               = 0;
961     int                          gf = 0;
962     gmx::ArrayRef<unsigned char> grpnrFREEZE =
963             top_global->groups.groupNumbers[SimulationAtomGroupType::Freeze];
964     for (int a : indicesB)
965     {
966         if (!grpnrFREEZE.empty())
967         {
968             gf = grpnrFREEZE[i];
969         }
970         for (int m = 0; m < DIM; m++)
971         {
972             if (!opts->nFreeze[gf][m])
973             {
974                 partsum += (fb[i][m] - fmg[a][m]) * fb[i][m];
975             }
976         }
977         i++;
978     }
979
980     sfree(fmg);
981
982     return partsum;
983 }
984
985 //! Print some stuff, like beta, whatever that means.
986 static real pr_beta(const t_commrec* cr,
987                     t_grpopts*       opts,
988                     t_mdatoms*       mdatoms,
989                     gmx_mtop_t*      top_global,
990                     em_state_t*      s_min,
991                     em_state_t*      s_b)
992 {
993     double sum;
994
995     /* This is just the classical Polak-Ribiere calculation of beta;
996      * it looks a bit complicated since we take freeze groups into account,
997      * and might have to sum it in parallel runs.
998      */
999
1000     if (!DOMAINDECOMP(cr)
1001         || (s_min->s.ddp_count == cr->dd->ddp_count && s_b->s.ddp_count == cr->dd->ddp_count))
1002     {
1003         const rvec* fm = s_min->f.rvec_array();
1004         const rvec* fb = s_b->f.rvec_array();
1005         sum            = 0;
1006         int gf         = 0;
1007         /* This part of code can be incorrect with DD,
1008          * since the atom ordering in s_b and s_min might differ.
1009          */
1010         for (int i = 0; i < mdatoms->homenr; i++)
1011         {
1012             if (mdatoms->cFREEZE)
1013             {
1014                 gf = mdatoms->cFREEZE[i];
1015             }
1016             for (int m = 0; m < DIM; m++)
1017             {
1018                 if (!opts->nFreeze[gf][m])
1019                 {
1020                     sum += (fb[i][m] - fm[i][m]) * fb[i][m];
1021                 }
1022             }
1023         }
1024     }
1025     else
1026     {
1027         /* We need to reorder cgs while summing */
1028         sum = reorder_partsum(cr, opts, top_global, s_min, s_b);
1029     }
1030     if (PAR(cr))
1031     {
1032         gmx_sumd(1, &sum, cr);
1033     }
1034
1035     return sum / gmx::square(s_min->fnorm);
1036 }
1037
1038 namespace gmx
1039 {
1040
1041 void LegacySimulator::do_cg()
1042 {
1043     const char* CG = "Polak-Ribiere Conjugate Gradients";
1044
1045     gmx_localtop_t    top;
1046     gmx_global_stat_t gstat;
1047     t_graph*          graph;
1048     double            tmp, minstep;
1049     real              stepsize;
1050     real              a, b, c, beta = 0.0;
1051     real              epot_repl = 0;
1052     real              pnorm;
1053     gmx_bool          converged, foundlower;
1054     rvec              mu_tot = { 0 };
1055     gmx_bool          do_log = FALSE, do_ene = FALSE, do_x, do_f;
1056     tensor            vir, pres;
1057     int               number_steps, neval = 0, nstcg = inputrec->nstcgsteep;
1058     int               m, step, nminstep;
1059     auto              mdatoms = mdAtoms->mdatoms();
1060
1061     GMX_LOG(mdlog.info)
1062             .asParagraph()
1063             .appendText(
1064                     "Note that activating conjugate gradient energy minimization via the "
1065                     "integrator .mdp option and the command gmx mdrun may "
1066                     "be available in a different form in a future version of GROMACS, "
1067                     "e.g. gmx minimize and an .mdp option.");
1068
1069     step = 0;
1070
1071     if (MASTER(cr))
1072     {
1073         // In CG, the state is extended with a search direction
1074         state_global->flags |= (1 << estCGP);
1075
1076         // Ensure the extra per-atom state array gets allocated
1077         state_change_natoms(state_global, state_global->natoms);
1078
1079         // Initialize the search direction to zero
1080         for (RVec& cg_p : state_global->cg_p)
1081         {
1082             cg_p = { 0, 0, 0 };
1083         }
1084     }
1085
1086     /* Create 4 states on the stack and extract pointers that we will swap */
1087     em_state_t  s0{}, s1{}, s2{}, s3{};
1088     em_state_t* s_min = &s0;
1089     em_state_t* s_a   = &s1;
1090     em_state_t* s_b   = &s2;
1091     em_state_t* s_c   = &s3;
1092
1093     /* Init em and store the local state in s_min */
1094     init_em(fplog, mdlog, CG, cr, inputrec, imdSession, pull_work, state_global, top_global, s_min,
1095             &top, nrnb, fr, &graph, mdAtoms, &gstat, vsite, constr, nullptr);
1096     const bool        simulationsShareState = false;
1097     gmx_mdoutf*       outf = init_mdoutf(fplog, nfile, fnm, mdrunOptions, cr, outputProvider,
1098                                    mdModulesNotifier, inputrec, top_global, nullptr, wcycle,
1099                                    StartingBehavior::NewSimulation, simulationsShareState, ms);
1100     gmx::EnergyOutput energyOutput(mdoutf_get_fp_ene(outf), top_global, inputrec, pull_work, nullptr,
1101                                    false, StartingBehavior::NewSimulation, mdModulesNotifier);
1102
1103     /* Print to log file */
1104     print_em_start(fplog, cr, walltime_accounting, wcycle, CG);
1105
1106     /* Max number of steps */
1107     number_steps = inputrec->nsteps;
1108
1109     if (MASTER(cr))
1110     {
1111         sp_header(stderr, CG, inputrec->em_tol, number_steps);
1112     }
1113     if (fplog)
1114     {
1115         sp_header(fplog, CG, inputrec->em_tol, number_steps);
1116     }
1117
1118     EnergyEvaluator energyEvaluator{
1119         fplog,      mdlog,     cr,      ms,     top_global,      &top,  inputrec,
1120         imdSession, pull_work, nrnb,    wcycle, gstat,           vsite, constr,
1121         fcd,        graph,     mdAtoms, fr,     runScheduleWork, enerd
1122     };
1123     /* Call the force routine and some auxiliary (neighboursearching etc.) */
1124     /* do_force always puts the charge groups in the box and shifts again
1125      * We do not unshift, so molecules are always whole in congrad.c
1126      */
1127     energyEvaluator.run(s_min, mu_tot, vir, pres, -1, TRUE);
1128
1129     if (MASTER(cr))
1130     {
1131         /* Copy stuff to the energy bin for easy printing etc. */
1132         matrix nullBox = {};
1133         energyOutput.addDataAtEnergyStep(false, false, static_cast<double>(step), mdatoms->tmass,
1134                                          enerd, nullptr, nullptr, nullptr, nullBox, nullptr,
1135                                          nullptr, vir, pres, nullptr, mu_tot, constr);
1136
1137         energyOutput.printHeader(fplog, step, step);
1138         energyOutput.printStepToEnergyFile(mdoutf_get_fp_ene(outf), TRUE, FALSE, FALSE, fplog, step,
1139                                            step, fcd, nullptr);
1140     }
1141
1142     /* Estimate/guess the initial stepsize */
1143     stepsize = inputrec->em_stepsize / s_min->fnorm;
1144
1145     if (MASTER(cr))
1146     {
1147         double sqrtNumAtoms = sqrt(static_cast<double>(state_global->natoms));
1148         fprintf(stderr, "   F-max             = %12.5e on atom %d\n", s_min->fmax, s_min->a_fmax + 1);
1149         fprintf(stderr, "   F-Norm            = %12.5e\n", s_min->fnorm / sqrtNumAtoms);
1150         fprintf(stderr, "\n");
1151         /* and copy to the log file too... */
1152         fprintf(fplog, "   F-max             = %12.5e on atom %d\n", s_min->fmax, s_min->a_fmax + 1);
1153         fprintf(fplog, "   F-Norm            = %12.5e\n", s_min->fnorm / sqrtNumAtoms);
1154         fprintf(fplog, "\n");
1155     }
1156     /* Start the loop over CG steps.
1157      * Each successful step is counted, and we continue until
1158      * we either converge or reach the max number of steps.
1159      */
1160     converged = FALSE;
1161     for (step = 0; (number_steps < 0 || step <= number_steps) && !converged; step++)
1162     {
1163
1164         /* start taking steps in a new direction
1165          * First time we enter the routine, beta=0, and the direction is
1166          * simply the negative gradient.
1167          */
1168
1169         /* Calculate the new direction in p, and the gradient in this direction, gpa */
1170         rvec*       pm  = s_min->s.cg_p.rvec_array();
1171         const rvec* sfm = s_min->f.rvec_array();
1172         double      gpa = 0;
1173         int         gf  = 0;
1174         for (int i = 0; i < mdatoms->homenr; i++)
1175         {
1176             if (mdatoms->cFREEZE)
1177             {
1178                 gf = mdatoms->cFREEZE[i];
1179             }
1180             for (m = 0; m < DIM; m++)
1181             {
1182                 if (!inputrec->opts.nFreeze[gf][m])
1183                 {
1184                     pm[i][m] = sfm[i][m] + beta * pm[i][m];
1185                     gpa -= pm[i][m] * sfm[i][m];
1186                     /* f is negative gradient, thus the sign */
1187                 }
1188                 else
1189                 {
1190                     pm[i][m] = 0;
1191                 }
1192             }
1193         }
1194
1195         /* Sum the gradient along the line across CPUs */
1196         if (PAR(cr))
1197         {
1198             gmx_sumd(1, &gpa, cr);
1199         }
1200
1201         /* Calculate the norm of the search vector */
1202         get_f_norm_max(cr, &(inputrec->opts), mdatoms, pm, &pnorm, nullptr, nullptr);
1203
1204         /* Just in case stepsize reaches zero due to numerical precision... */
1205         if (stepsize <= 0)
1206         {
1207             stepsize = inputrec->em_stepsize / pnorm;
1208         }
1209
1210         /*
1211          * Double check the value of the derivative in the search direction.
1212          * If it is positive it must be due to the old information in the
1213          * CG formula, so just remove that and start over with beta=0.
1214          * This corresponds to a steepest descent step.
1215          */
1216         if (gpa > 0)
1217         {
1218             beta = 0;
1219             step--;   /* Don't count this step since we are restarting */
1220             continue; /* Go back to the beginning of the big for-loop */
1221         }
1222
1223         /* Calculate minimum allowed stepsize, before the average (norm)
1224          * relative change in coordinate is smaller than precision
1225          */
1226         minstep      = 0;
1227         auto s_min_x = makeArrayRef(s_min->s.x);
1228         for (int i = 0; i < mdatoms->homenr; i++)
1229         {
1230             for (m = 0; m < DIM; m++)
1231             {
1232                 tmp = fabs(s_min_x[i][m]);
1233                 if (tmp < 1.0)
1234                 {
1235                     tmp = 1.0;
1236                 }
1237                 tmp = pm[i][m] / tmp;
1238                 minstep += tmp * tmp;
1239             }
1240         }
1241         /* Add up from all CPUs */
1242         if (PAR(cr))
1243         {
1244             gmx_sumd(1, &minstep, cr);
1245         }
1246
1247         minstep = GMX_REAL_EPS / sqrt(minstep / (3 * top_global->natoms));
1248
1249         if (stepsize < minstep)
1250         {
1251             converged = TRUE;
1252             break;
1253         }
1254
1255         /* Write coordinates if necessary */
1256         do_x = do_per_step(step, inputrec->nstxout);
1257         do_f = do_per_step(step, inputrec->nstfout);
1258
1259         write_em_traj(fplog, cr, outf, do_x, do_f, nullptr, top_global, inputrec, step, s_min,
1260                       state_global, observablesHistory);
1261
1262         /* Take a step downhill.
1263          * In theory, we should minimize the function along this direction.
1264          * That is quite possible, but it turns out to take 5-10 function evaluations
1265          * for each line. However, we dont really need to find the exact minimum -
1266          * it is much better to start a new CG step in a modified direction as soon
1267          * as we are close to it. This will save a lot of energy evaluations.
1268          *
1269          * In practice, we just try to take a single step.
1270          * If it worked (i.e. lowered the energy), we increase the stepsize but
1271          * the continue straight to the next CG step without trying to find any minimum.
1272          * If it didn't work (higher energy), there must be a minimum somewhere between
1273          * the old position and the new one.
1274          *
1275          * Due to the finite numerical accuracy, it turns out that it is a good idea
1276          * to even accept a SMALL increase in energy, if the derivative is still downhill.
1277          * This leads to lower final energies in the tests I've done. / Erik
1278          */
1279         s_a->epot = s_min->epot;
1280         a         = 0.0;
1281         c         = a + stepsize; /* reference position along line is zero */
1282
1283         if (DOMAINDECOMP(cr) && s_min->s.ddp_count < cr->dd->ddp_count)
1284         {
1285             em_dd_partition_system(fplog, mdlog, step, cr, top_global, inputrec, imdSession,
1286                                    pull_work, s_min, &top, mdAtoms, fr, vsite, constr, nrnb, wcycle);
1287         }
1288
1289         /* Take a trial step (new coords in s_c) */
1290         do_em_step(cr, inputrec, mdatoms, s_min, c, &s_min->s.cg_p, s_c, constr, -1);
1291
1292         neval++;
1293         /* Calculate energy for the trial step */
1294         energyEvaluator.run(s_c, mu_tot, vir, pres, -1, FALSE);
1295
1296         /* Calc derivative along line */
1297         const rvec* pc  = s_c->s.cg_p.rvec_array();
1298         const rvec* sfc = s_c->f.rvec_array();
1299         double      gpc = 0;
1300         for (int i = 0; i < mdatoms->homenr; i++)
1301         {
1302             for (m = 0; m < DIM; m++)
1303             {
1304                 gpc -= pc[i][m] * sfc[i][m]; /* f is negative gradient, thus the sign */
1305             }
1306         }
1307         /* Sum the gradient along the line across CPUs */
1308         if (PAR(cr))
1309         {
1310             gmx_sumd(1, &gpc, cr);
1311         }
1312
1313         /* This is the max amount of increase in energy we tolerate */
1314         tmp = std::sqrt(GMX_REAL_EPS) * fabs(s_a->epot);
1315
1316         /* Accept the step if the energy is lower, or if it is not significantly higher
1317          * and the line derivative is still negative.
1318          */
1319         if (s_c->epot < s_a->epot || (gpc < 0 && s_c->epot < (s_a->epot + tmp)))
1320         {
1321             foundlower = TRUE;
1322             /* Great, we found a better energy. Increase step for next iteration
1323              * if we are still going down, decrease it otherwise
1324              */
1325             if (gpc < 0)
1326             {
1327                 stepsize *= 1.618034; /* The golden section */
1328             }
1329             else
1330             {
1331                 stepsize *= 0.618034; /* 1/golden section */
1332             }
1333         }
1334         else
1335         {
1336             /* New energy is the same or higher. We will have to do some work
1337              * to find a smaller value in the interval. Take smaller step next time!
1338              */
1339             foundlower = FALSE;
1340             stepsize *= 0.618034;
1341         }
1342
1343
1344         /* OK, if we didn't find a lower value we will have to locate one now - there must
1345          * be one in the interval [a=0,c].
1346          * The same thing is valid here, though: Don't spend dozens of iterations to find
1347          * the line minimum. We try to interpolate based on the derivative at the endpoints,
1348          * and only continue until we find a lower value. In most cases this means 1-2 iterations.
1349          *
1350          * I also have a safeguard for potentially really pathological functions so we never
1351          * take more than 20 steps before we give up ...
1352          *
1353          * If we already found a lower value we just skip this step and continue to the update.
1354          */
1355         double gpb;
1356         if (!foundlower)
1357         {
1358             nminstep = 0;
1359
1360             do
1361             {
1362                 /* Select a new trial point.
1363                  * If the derivatives at points a & c have different sign we interpolate to zero,
1364                  * otherwise just do a bisection.
1365                  */
1366                 if (gpa < 0 && gpc > 0)
1367                 {
1368                     b = a + gpa * (a - c) / (gpc - gpa);
1369                 }
1370                 else
1371                 {
1372                     b = 0.5 * (a + c);
1373                 }
1374
1375                 /* safeguard if interpolation close to machine accuracy causes errors:
1376                  * never go outside the interval
1377                  */
1378                 if (b <= a || b >= c)
1379                 {
1380                     b = 0.5 * (a + c);
1381                 }
1382
1383                 if (DOMAINDECOMP(cr) && s_min->s.ddp_count != cr->dd->ddp_count)
1384                 {
1385                     /* Reload the old state */
1386                     em_dd_partition_system(fplog, mdlog, -1, cr, top_global, inputrec, imdSession, pull_work,
1387                                            s_min, &top, mdAtoms, fr, vsite, constr, nrnb, wcycle);
1388                 }
1389
1390                 /* Take a trial step to this new point - new coords in s_b */
1391                 do_em_step(cr, inputrec, mdatoms, s_min, b, &s_min->s.cg_p, s_b, constr, -1);
1392
1393                 neval++;
1394                 /* Calculate energy for the trial step */
1395                 energyEvaluator.run(s_b, mu_tot, vir, pres, -1, FALSE);
1396
1397                 /* p does not change within a step, but since the domain decomposition
1398                  * might change, we have to use cg_p of s_b here.
1399                  */
1400                 const rvec* pb  = s_b->s.cg_p.rvec_array();
1401                 const rvec* sfb = s_b->f.rvec_array();
1402                 gpb             = 0;
1403                 for (int i = 0; i < mdatoms->homenr; i++)
1404                 {
1405                     for (m = 0; m < DIM; m++)
1406                     {
1407                         gpb -= pb[i][m] * sfb[i][m]; /* f is negative gradient, thus the sign */
1408                     }
1409                 }
1410                 /* Sum the gradient along the line across CPUs */
1411                 if (PAR(cr))
1412                 {
1413                     gmx_sumd(1, &gpb, cr);
1414                 }
1415
1416                 if (debug)
1417                 {
1418                     fprintf(debug, "CGE: EpotA %f EpotB %f EpotC %f gpb %f\n", s_a->epot, s_b->epot,
1419                             s_c->epot, gpb);
1420                 }
1421
1422                 epot_repl = s_b->epot;
1423
1424                 /* Keep one of the intervals based on the value of the derivative at the new point */
1425                 if (gpb > 0)
1426                 {
1427                     /* Replace c endpoint with b */
1428                     swap_em_state(&s_b, &s_c);
1429                     c   = b;
1430                     gpc = gpb;
1431                 }
1432                 else
1433                 {
1434                     /* Replace a endpoint with b */
1435                     swap_em_state(&s_b, &s_a);
1436                     a   = b;
1437                     gpa = gpb;
1438                 }
1439
1440                 /*
1441                  * Stop search as soon as we find a value smaller than the endpoints.
1442                  * Never run more than 20 steps, no matter what.
1443                  */
1444                 nminstep++;
1445             } while ((epot_repl > s_a->epot || epot_repl > s_c->epot) && (nminstep < 20));
1446
1447             if (std::fabs(epot_repl - s_min->epot) < fabs(s_min->epot) * GMX_REAL_EPS || nminstep >= 20)
1448             {
1449                 /* OK. We couldn't find a significantly lower energy.
1450                  * If beta==0 this was steepest descent, and then we give up.
1451                  * If not, set beta=0 and restart with steepest descent before quitting.
1452                  */
1453                 if (beta == 0.0)
1454                 {
1455                     /* Converged */
1456                     converged = TRUE;
1457                     break;
1458                 }
1459                 else
1460                 {
1461                     /* Reset memory before giving up */
1462                     beta = 0.0;
1463                     continue;
1464                 }
1465             }
1466
1467             /* Select min energy state of A & C, put the best in B.
1468              */
1469             if (s_c->epot < s_a->epot)
1470             {
1471                 if (debug)
1472                 {
1473                     fprintf(debug, "CGE: C (%f) is lower than A (%f), moving C to B\n", s_c->epot,
1474                             s_a->epot);
1475                 }
1476                 swap_em_state(&s_b, &s_c);
1477                 gpb = gpc;
1478             }
1479             else
1480             {
1481                 if (debug)
1482                 {
1483                     fprintf(debug, "CGE: A (%f) is lower than C (%f), moving A to B\n", s_a->epot,
1484                             s_c->epot);
1485                 }
1486                 swap_em_state(&s_b, &s_a);
1487                 gpb = gpa;
1488             }
1489         }
1490         else
1491         {
1492             if (debug)
1493             {
1494                 fprintf(debug, "CGE: Found a lower energy %f, moving C to B\n", s_c->epot);
1495             }
1496             swap_em_state(&s_b, &s_c);
1497             gpb = gpc;
1498         }
1499
1500         /* new search direction */
1501         /* beta = 0 means forget all memory and restart with steepest descents. */
1502         if (nstcg && ((step % nstcg) == 0))
1503         {
1504             beta = 0.0;
1505         }
1506         else
1507         {
1508             /* s_min->fnorm cannot be zero, because then we would have converged
1509              * and broken out.
1510              */
1511
1512             /* Polak-Ribiere update.
1513              * Change to fnorm2/fnorm2_old for Fletcher-Reeves
1514              */
1515             beta = pr_beta(cr, &inputrec->opts, mdatoms, top_global, s_min, s_b);
1516         }
1517         /* Limit beta to prevent oscillations */
1518         if (fabs(beta) > 5.0)
1519         {
1520             beta = 0.0;
1521         }
1522
1523
1524         /* update positions */
1525         swap_em_state(&s_min, &s_b);
1526         gpa = gpb;
1527
1528         /* Print it if necessary */
1529         if (MASTER(cr))
1530         {
1531             if (mdrunOptions.verbose)
1532             {
1533                 double sqrtNumAtoms = sqrt(static_cast<double>(state_global->natoms));
1534                 fprintf(stderr, "\rStep %d, Epot=%12.6e, Fnorm=%9.3e, Fmax=%9.3e (atom %d)\n", step,
1535                         s_min->epot, s_min->fnorm / sqrtNumAtoms, s_min->fmax, s_min->a_fmax + 1);
1536                 fflush(stderr);
1537             }
1538             /* Store the new (lower) energies */
1539             matrix nullBox = {};
1540             energyOutput.addDataAtEnergyStep(false, false, static_cast<double>(step), mdatoms->tmass,
1541                                              enerd, nullptr, nullptr, nullptr, nullBox, nullptr,
1542                                              nullptr, vir, pres, nullptr, mu_tot, constr);
1543
1544             do_log = do_per_step(step, inputrec->nstlog);
1545             do_ene = do_per_step(step, inputrec->nstenergy);
1546
1547             imdSession->fillEnergyRecord(step, TRUE);
1548
1549             if (do_log)
1550             {
1551                 energyOutput.printHeader(fplog, step, step);
1552             }
1553             energyOutput.printStepToEnergyFile(mdoutf_get_fp_ene(outf), do_ene, FALSE, FALSE,
1554                                                do_log ? fplog : nullptr, step, step, fcd, nullptr);
1555         }
1556
1557         /* Send energies and positions to the IMD client if bIMD is TRUE. */
1558         if (MASTER(cr) && imdSession->run(step, TRUE, state_global->box, state_global->x.rvec_array(), 0))
1559         {
1560             imdSession->sendPositionsAndEnergies();
1561         }
1562
1563         /* Stop when the maximum force lies below tolerance.
1564          * If we have reached machine precision, converged is already set to true.
1565          */
1566         converged = converged || (s_min->fmax < inputrec->em_tol);
1567
1568     } /* End of the loop */
1569
1570     if (converged)
1571     {
1572         step--; /* we never took that last step in this case */
1573     }
1574     if (s_min->fmax > inputrec->em_tol)
1575     {
1576         if (MASTER(cr))
1577         {
1578             warn_step(fplog, inputrec->em_tol, s_min->fmax, step - 1 == number_steps, FALSE);
1579         }
1580         converged = FALSE;
1581     }
1582
1583     if (MASTER(cr))
1584     {
1585         /* If we printed energy and/or logfile last step (which was the last step)
1586          * we don't have to do it again, but otherwise print the final values.
1587          */
1588         if (!do_log)
1589         {
1590             /* Write final value to log since we didn't do anything the last step */
1591             energyOutput.printHeader(fplog, step, step);
1592         }
1593         if (!do_ene || !do_log)
1594         {
1595             /* Write final energy file entries */
1596             energyOutput.printStepToEnergyFile(mdoutf_get_fp_ene(outf), !do_ene, FALSE, FALSE,
1597                                                !do_log ? fplog : nullptr, step, step, fcd, nullptr);
1598         }
1599     }
1600
1601     /* Print some stuff... */
1602     if (MASTER(cr))
1603     {
1604         fprintf(stderr, "\nwriting lowest energy coordinates.\n");
1605     }
1606
1607     /* IMPORTANT!
1608      * For accurate normal mode calculation it is imperative that we
1609      * store the last conformation into the full precision binary trajectory.
1610      *
1611      * However, we should only do it if we did NOT already write this step
1612      * above (which we did if do_x or do_f was true).
1613      */
1614     /* Note that with 0 < nstfout != nstxout we can end up with two frames
1615      * in the trajectory with the same step number.
1616      */
1617     do_x = !do_per_step(step, inputrec->nstxout);
1618     do_f = (inputrec->nstfout > 0 && !do_per_step(step, inputrec->nstfout));
1619
1620     write_em_traj(fplog, cr, outf, do_x, do_f, ftp2fn(efSTO, nfile, fnm), top_global, inputrec,
1621                   step, s_min, state_global, observablesHistory);
1622
1623
1624     if (MASTER(cr))
1625     {
1626         double sqrtNumAtoms = sqrt(static_cast<double>(state_global->natoms));
1627         print_converged(stderr, CG, inputrec->em_tol, step, converged, number_steps, s_min, sqrtNumAtoms);
1628         print_converged(fplog, CG, inputrec->em_tol, step, converged, number_steps, s_min, sqrtNumAtoms);
1629
1630         fprintf(fplog, "\nPerformed %d energy evaluations in total.\n", neval);
1631     }
1632
1633     finish_em(cr, outf, walltime_accounting, wcycle);
1634
1635     /* To print the actual number of steps we needed somewhere */
1636     walltime_accounting_set_nsteps_done(walltime_accounting, step);
1637 }
1638
1639
1640 void LegacySimulator::do_lbfgs()
1641 {
1642     static const char* LBFGS = "Low-Memory BFGS Minimizer";
1643     em_state_t         ems;
1644     gmx_localtop_t     top;
1645     gmx_global_stat_t  gstat;
1646     t_graph*           graph;
1647     int                ncorr, nmaxcorr, point, cp, neval, nminstep;
1648     double             stepsize, step_taken, gpa, gpb, gpc, tmp, minstep;
1649     real *             rho, *alpha, *p, *s, **dx, **dg;
1650     real               a, b, c, maxdelta, delta;
1651     real               diag, Epot0;
1652     real               dgdx, dgdg, sq, yr, beta;
1653     gmx_bool           converged;
1654     rvec               mu_tot = { 0 };
1655     gmx_bool           do_log, do_ene, do_x, do_f, foundlower, *frozen;
1656     tensor             vir, pres;
1657     int                start, end, number_steps;
1658     int                i, k, m, n, gf, step;
1659     int                mdof_flags;
1660     auto               mdatoms = mdAtoms->mdatoms();
1661
1662     GMX_LOG(mdlog.info)
1663             .asParagraph()
1664             .appendText(
1665                     "Note that activating L-BFGS energy minimization via the "
1666                     "integrator .mdp option and the command gmx mdrun may "
1667                     "be available in a different form in a future version of GROMACS, "
1668                     "e.g. gmx minimize and an .mdp option.");
1669
1670     if (PAR(cr))
1671     {
1672         gmx_fatal(FARGS, "L-BFGS minimization only supports a single rank");
1673     }
1674
1675     if (nullptr != constr)
1676     {
1677         gmx_fatal(
1678                 FARGS,
1679                 "The combination of constraints and L-BFGS minimization is not implemented. Either "
1680                 "do not use constraints, or use another minimizer (e.g. steepest descent).");
1681     }
1682
1683     n        = 3 * state_global->natoms;
1684     nmaxcorr = inputrec->nbfgscorr;
1685
1686     snew(frozen, n);
1687
1688     snew(p, n);
1689     snew(rho, nmaxcorr);
1690     snew(alpha, nmaxcorr);
1691
1692     snew(dx, nmaxcorr);
1693     for (i = 0; i < nmaxcorr; i++)
1694     {
1695         snew(dx[i], n);
1696     }
1697
1698     snew(dg, nmaxcorr);
1699     for (i = 0; i < nmaxcorr; i++)
1700     {
1701         snew(dg[i], n);
1702     }
1703
1704     step  = 0;
1705     neval = 0;
1706
1707     /* Init em */
1708     init_em(fplog, mdlog, LBFGS, cr, inputrec, imdSession, pull_work, state_global, top_global,
1709             &ems, &top, nrnb, fr, &graph, mdAtoms, &gstat, vsite, constr, nullptr);
1710     const bool        simulationsShareState = false;
1711     gmx_mdoutf*       outf = init_mdoutf(fplog, nfile, fnm, mdrunOptions, cr, outputProvider,
1712                                    mdModulesNotifier, inputrec, top_global, nullptr, wcycle,
1713                                    StartingBehavior::NewSimulation, simulationsShareState, ms);
1714     gmx::EnergyOutput energyOutput(mdoutf_get_fp_ene(outf), top_global, inputrec, pull_work, nullptr,
1715                                    false, StartingBehavior::NewSimulation, mdModulesNotifier);
1716
1717     start = 0;
1718     end   = mdatoms->homenr;
1719
1720     /* We need 4 working states */
1721     em_state_t  s0{}, s1{}, s2{}, s3{};
1722     em_state_t* sa   = &s0;
1723     em_state_t* sb   = &s1;
1724     em_state_t* sc   = &s2;
1725     em_state_t* last = &s3;
1726     /* Initialize by copying the state from ems (we could skip x and f here) */
1727     *sa = ems;
1728     *sb = ems;
1729     *sc = ems;
1730
1731     /* Print to log file */
1732     print_em_start(fplog, cr, walltime_accounting, wcycle, LBFGS);
1733
1734     do_log = do_ene = do_x = do_f = TRUE;
1735
1736     /* Max number of steps */
1737     number_steps = inputrec->nsteps;
1738
1739     /* Create a 3*natoms index to tell whether each degree of freedom is frozen */
1740     gf = 0;
1741     for (i = start; i < end; i++)
1742     {
1743         if (mdatoms->cFREEZE)
1744         {
1745             gf = mdatoms->cFREEZE[i];
1746         }
1747         for (m = 0; m < DIM; m++)
1748         {
1749             frozen[3 * i + m] = (inputrec->opts.nFreeze[gf][m] != 0);
1750         }
1751     }
1752     if (MASTER(cr))
1753     {
1754         sp_header(stderr, LBFGS, inputrec->em_tol, number_steps);
1755     }
1756     if (fplog)
1757     {
1758         sp_header(fplog, LBFGS, inputrec->em_tol, number_steps);
1759     }
1760
1761     if (vsite)
1762     {
1763         construct_vsites(vsite, state_global->x.rvec_array(), 1, nullptr, top.idef.iparams,
1764                          top.idef.il, fr->pbcType, fr->bMolPBC, cr, state_global->box);
1765     }
1766
1767     /* Call the force routine and some auxiliary (neighboursearching etc.) */
1768     /* do_force always puts the charge groups in the box and shifts again
1769      * We do not unshift, so molecules are always whole
1770      */
1771     neval++;
1772     EnergyEvaluator energyEvaluator{
1773         fplog,      mdlog,     cr,      ms,     top_global,      &top,  inputrec,
1774         imdSession, pull_work, nrnb,    wcycle, gstat,           vsite, constr,
1775         fcd,        graph,     mdAtoms, fr,     runScheduleWork, enerd
1776     };
1777     energyEvaluator.run(&ems, mu_tot, vir, pres, -1, TRUE);
1778
1779     if (MASTER(cr))
1780     {
1781         /* Copy stuff to the energy bin for easy printing etc. */
1782         matrix nullBox = {};
1783         energyOutput.addDataAtEnergyStep(false, false, static_cast<double>(step), mdatoms->tmass,
1784                                          enerd, nullptr, nullptr, nullptr, nullBox, nullptr,
1785                                          nullptr, vir, pres, nullptr, mu_tot, constr);
1786
1787         energyOutput.printHeader(fplog, step, step);
1788         energyOutput.printStepToEnergyFile(mdoutf_get_fp_ene(outf), TRUE, FALSE, FALSE, fplog, step,
1789                                            step, fcd, nullptr);
1790     }
1791
1792     /* Set the initial step.
1793      * since it will be multiplied by the non-normalized search direction
1794      * vector (force vector the first time), we scale it by the
1795      * norm of the force.
1796      */
1797
1798     if (MASTER(cr))
1799     {
1800         double sqrtNumAtoms = sqrt(static_cast<double>(state_global->natoms));
1801         fprintf(stderr, "Using %d BFGS correction steps.\n\n", nmaxcorr);
1802         fprintf(stderr, "   F-max             = %12.5e on atom %d\n", ems.fmax, ems.a_fmax + 1);
1803         fprintf(stderr, "   F-Norm            = %12.5e\n", ems.fnorm / sqrtNumAtoms);
1804         fprintf(stderr, "\n");
1805         /* and copy to the log file too... */
1806         fprintf(fplog, "Using %d BFGS correction steps.\n\n", nmaxcorr);
1807         fprintf(fplog, "   F-max             = %12.5e on atom %d\n", ems.fmax, ems.a_fmax + 1);
1808         fprintf(fplog, "   F-Norm            = %12.5e\n", ems.fnorm / sqrtNumAtoms);
1809         fprintf(fplog, "\n");
1810     }
1811
1812     // Point is an index to the memory of search directions, where 0 is the first one.
1813     point = 0;
1814
1815     // Set initial search direction to the force (-gradient), or 0 for frozen particles.
1816     real* fInit = static_cast<real*>(ems.f.rvec_array()[0]);
1817     for (i = 0; i < n; i++)
1818     {
1819         if (!frozen[i])
1820         {
1821             dx[point][i] = fInit[i]; /* Initial search direction */
1822         }
1823         else
1824         {
1825             dx[point][i] = 0;
1826         }
1827     }
1828
1829     // Stepsize will be modified during the search, and actually it is not critical
1830     // (the main efficiency in the algorithm comes from changing directions), but
1831     // we still need an initial value, so estimate it as the inverse of the norm
1832     // so we take small steps where the potential fluctuates a lot.
1833     stepsize = 1.0 / ems.fnorm;
1834
1835     /* Start the loop over BFGS steps.
1836      * Each successful step is counted, and we continue until
1837      * we either converge or reach the max number of steps.
1838      */
1839
1840     ncorr = 0;
1841
1842     /* Set the gradient from the force */
1843     converged = FALSE;
1844     for (step = 0; (number_steps < 0 || step <= number_steps) && !converged; step++)
1845     {
1846
1847         /* Write coordinates if necessary */
1848         do_x = do_per_step(step, inputrec->nstxout);
1849         do_f = do_per_step(step, inputrec->nstfout);
1850
1851         mdof_flags = 0;
1852         if (do_x)
1853         {
1854             mdof_flags |= MDOF_X;
1855         }
1856
1857         if (do_f)
1858         {
1859             mdof_flags |= MDOF_F;
1860         }
1861
1862         if (inputrec->bIMD)
1863         {
1864             mdof_flags |= MDOF_IMD;
1865         }
1866
1867         mdoutf_write_to_trajectory_files(fplog, cr, outf, mdof_flags, top_global->natoms, step,
1868                                          static_cast<real>(step), &ems.s, state_global,
1869                                          observablesHistory, ems.f);
1870
1871         /* Do the linesearching in the direction dx[point][0..(n-1)] */
1872
1873         /* make s a pointer to current search direction - point=0 first time we get here */
1874         s = dx[point];
1875
1876         real* xx = static_cast<real*>(ems.s.x.rvec_array()[0]);
1877         real* ff = static_cast<real*>(ems.f.rvec_array()[0]);
1878
1879         // calculate line gradient in position A
1880         for (gpa = 0, i = 0; i < n; i++)
1881         {
1882             gpa -= s[i] * ff[i];
1883         }
1884
1885         /* Calculate minimum allowed stepsize along the line, before the average (norm)
1886          * relative change in coordinate is smaller than precision
1887          */
1888         for (minstep = 0, i = 0; i < n; i++)
1889         {
1890             tmp = fabs(xx[i]);
1891             if (tmp < 1.0)
1892             {
1893                 tmp = 1.0;
1894             }
1895             tmp = s[i] / tmp;
1896             minstep += tmp * tmp;
1897         }
1898         minstep = GMX_REAL_EPS / sqrt(minstep / n);
1899
1900         if (stepsize < minstep)
1901         {
1902             converged = TRUE;
1903             break;
1904         }
1905
1906         // Before taking any steps along the line, store the old position
1907         *last       = ems;
1908         real* lastx = static_cast<real*>(last->s.x.data()[0]);
1909         real* lastf = static_cast<real*>(last->f.data()[0]);
1910         Epot0       = ems.epot;
1911
1912         *sa = ems;
1913
1914         /* Take a step downhill.
1915          * In theory, we should find the actual minimum of the function in this
1916          * direction, somewhere along the line.
1917          * That is quite possible, but it turns out to take 5-10 function evaluations
1918          * for each line. However, we dont really need to find the exact minimum -
1919          * it is much better to start a new BFGS step in a modified direction as soon
1920          * as we are close to it. This will save a lot of energy evaluations.
1921          *
1922          * In practice, we just try to take a single step.
1923          * If it worked (i.e. lowered the energy), we increase the stepsize but
1924          * continue straight to the next BFGS step without trying to find any minimum,
1925          * i.e. we change the search direction too. If the line was smooth, it is
1926          * likely we are in a smooth region, and then it makes sense to take longer
1927          * steps in the modified search direction too.
1928          *
1929          * If it didn't work (higher energy), there must be a minimum somewhere between
1930          * the old position and the new one. Then we need to start by finding a lower
1931          * value before we change search direction. Since the energy was apparently
1932          * quite rough, we need to decrease the step size.
1933          *
1934          * Due to the finite numerical accuracy, it turns out that it is a good idea
1935          * to accept a SMALL increase in energy, if the derivative is still downhill.
1936          * This leads to lower final energies in the tests I've done. / Erik
1937          */
1938
1939         // State "A" is the first position along the line.
1940         // reference position along line is initially zero
1941         a = 0.0;
1942
1943         // Check stepsize first. We do not allow displacements
1944         // larger than emstep.
1945         //
1946         do
1947         {
1948             // Pick a new position C by adding stepsize to A.
1949             c = a + stepsize;
1950
1951             // Calculate what the largest change in any individual coordinate
1952             // would be (translation along line * gradient along line)
1953             maxdelta = 0;
1954             for (i = 0; i < n; i++)
1955             {
1956                 delta = c * s[i];
1957                 if (delta > maxdelta)
1958                 {
1959                     maxdelta = delta;
1960                 }
1961             }
1962             // If any displacement is larger than the stepsize limit, reduce the step
1963             if (maxdelta > inputrec->em_stepsize)
1964             {
1965                 stepsize *= 0.1;
1966             }
1967         } while (maxdelta > inputrec->em_stepsize);
1968
1969         // Take a trial step and move the coordinate array xc[] to position C
1970         real* xc = static_cast<real*>(sc->s.x.rvec_array()[0]);
1971         for (i = 0; i < n; i++)
1972         {
1973             xc[i] = lastx[i] + c * s[i];
1974         }
1975
1976         neval++;
1977         // Calculate energy for the trial step in position C
1978         energyEvaluator.run(sc, mu_tot, vir, pres, step, FALSE);
1979
1980         // Calc line gradient in position C
1981         real* fc = static_cast<real*>(sc->f.rvec_array()[0]);
1982         for (gpc = 0, i = 0; i < n; i++)
1983         {
1984             gpc -= s[i] * fc[i]; /* f is negative gradient, thus the sign */
1985         }
1986         /* Sum the gradient along the line across CPUs */
1987         if (PAR(cr))
1988         {
1989             gmx_sumd(1, &gpc, cr);
1990         }
1991
1992         // This is the max amount of increase in energy we tolerate.
1993         // By allowing VERY small changes (close to numerical precision) we
1994         // frequently find even better (lower) final energies.
1995         tmp = std::sqrt(GMX_REAL_EPS) * fabs(sa->epot);
1996
1997         // Accept the step if the energy is lower in the new position C (compared to A),
1998         // or if it is not significantly higher and the line derivative is still negative.
1999         foundlower = sc->epot < sa->epot || (gpc < 0 && sc->epot < (sa->epot + tmp));
2000         // If true, great, we found a better energy. We no longer try to alter the
2001         // stepsize, but simply accept this new better position. The we select a new
2002         // search direction instead, which will be much more efficient than continuing
2003         // to take smaller steps along a line. Set fnorm based on the new C position,
2004         // which will be used to update the stepsize to 1/fnorm further down.
2005
2006         // If false, the energy is NOT lower in point C, i.e. it will be the same
2007         // or higher than in point A. In this case it is pointless to move to point C,
2008         // so we will have to do more iterations along the same line to find a smaller
2009         // value in the interval [A=0.0,C].
2010         // Here, A is still 0.0, but that will change when we do a search in the interval
2011         // [0.0,C] below. That search we will do by interpolation or bisection rather
2012         // than with the stepsize, so no need to modify it. For the next search direction
2013         // it will be reset to 1/fnorm anyway.
2014
2015         if (!foundlower)
2016         {
2017             // OK, if we didn't find a lower value we will have to locate one now - there must
2018             // be one in the interval [a,c].
2019             // The same thing is valid here, though: Don't spend dozens of iterations to find
2020             // the line minimum. We try to interpolate based on the derivative at the endpoints,
2021             // and only continue until we find a lower value. In most cases this means 1-2 iterations.
2022             // I also have a safeguard for potentially really pathological functions so we never
2023             // take more than 20 steps before we give up.
2024             // If we already found a lower value we just skip this step and continue to the update.
2025             real fnorm = 0;
2026             nminstep   = 0;
2027             do
2028             {
2029                 // Select a new trial point B in the interval [A,C].
2030                 // If the derivatives at points a & c have different sign we interpolate to zero,
2031                 // otherwise just do a bisection since there might be multiple minima/maxima
2032                 // inside the interval.
2033                 if (gpa < 0 && gpc > 0)
2034                 {
2035                     b = a + gpa * (a - c) / (gpc - gpa);
2036                 }
2037                 else
2038                 {
2039                     b = 0.5 * (a + c);
2040                 }
2041
2042                 /* safeguard if interpolation close to machine accuracy causes errors:
2043                  * never go outside the interval
2044                  */
2045                 if (b <= a || b >= c)
2046                 {
2047                     b = 0.5 * (a + c);
2048                 }
2049
2050                 // Take a trial step to point B
2051                 real* xb = static_cast<real*>(sb->s.x.rvec_array()[0]);
2052                 for (i = 0; i < n; i++)
2053                 {
2054                     xb[i] = lastx[i] + b * s[i];
2055                 }
2056
2057                 neval++;
2058                 // Calculate energy for the trial step in point B
2059                 energyEvaluator.run(sb, mu_tot, vir, pres, step, FALSE);
2060                 fnorm = sb->fnorm;
2061
2062                 // Calculate gradient in point B
2063                 real* fb = static_cast<real*>(sb->f.rvec_array()[0]);
2064                 for (gpb = 0, i = 0; i < n; i++)
2065                 {
2066                     gpb -= s[i] * fb[i]; /* f is negative gradient, thus the sign */
2067                 }
2068                 /* Sum the gradient along the line across CPUs */
2069                 if (PAR(cr))
2070                 {
2071                     gmx_sumd(1, &gpb, cr);
2072                 }
2073
2074                 // Keep one of the intervals [A,B] or [B,C] based on the value of the derivative
2075                 // at the new point B, and rename the endpoints of this new interval A and C.
2076                 if (gpb > 0)
2077                 {
2078                     /* Replace c endpoint with b */
2079                     c = b;
2080                     /* copy state b to c */
2081                     *sc = *sb;
2082                 }
2083                 else
2084                 {
2085                     /* Replace a endpoint with b */
2086                     a = b;
2087                     /* copy state b to a */
2088                     *sa = *sb;
2089                 }
2090
2091                 /*
2092                  * Stop search as soon as we find a value smaller than the endpoints,
2093                  * or if the tolerance is below machine precision.
2094                  * Never run more than 20 steps, no matter what.
2095                  */
2096                 nminstep++;
2097             } while ((sb->epot > sa->epot || sb->epot > sc->epot) && (nminstep < 20));
2098
2099             if (std::fabs(sb->epot - Epot0) < GMX_REAL_EPS || nminstep >= 20)
2100             {
2101                 /* OK. We couldn't find a significantly lower energy.
2102                  * If ncorr==0 this was steepest descent, and then we give up.
2103                  * If not, reset memory to restart as steepest descent before quitting.
2104                  */
2105                 if (ncorr == 0)
2106                 {
2107                     /* Converged */
2108                     converged = TRUE;
2109                     break;
2110                 }
2111                 else
2112                 {
2113                     /* Reset memory */
2114                     ncorr = 0;
2115                     /* Search in gradient direction */
2116                     for (i = 0; i < n; i++)
2117                     {
2118                         dx[point][i] = ff[i];
2119                     }
2120                     /* Reset stepsize */
2121                     stepsize = 1.0 / fnorm;
2122                     continue;
2123                 }
2124             }
2125
2126             /* Select min energy state of A & C, put the best in xx/ff/Epot
2127              */
2128             if (sc->epot < sa->epot)
2129             {
2130                 /* Use state C */
2131                 ems        = *sc;
2132                 step_taken = c;
2133             }
2134             else
2135             {
2136                 /* Use state A */
2137                 ems        = *sa;
2138                 step_taken = a;
2139             }
2140         }
2141         else
2142         {
2143             /* found lower */
2144             /* Use state C */
2145             ems        = *sc;
2146             step_taken = c;
2147         }
2148
2149         /* Update the memory information, and calculate a new
2150          * approximation of the inverse hessian
2151          */
2152
2153         /* Have new data in Epot, xx, ff */
2154         if (ncorr < nmaxcorr)
2155         {
2156             ncorr++;
2157         }
2158
2159         for (i = 0; i < n; i++)
2160         {
2161             dg[point][i] = lastf[i] - ff[i];
2162             dx[point][i] *= step_taken;
2163         }
2164
2165         dgdg = 0;
2166         dgdx = 0;
2167         for (i = 0; i < n; i++)
2168         {
2169             dgdg += dg[point][i] * dg[point][i];
2170             dgdx += dg[point][i] * dx[point][i];
2171         }
2172
2173         diag = dgdx / dgdg;
2174
2175         rho[point] = 1.0 / dgdx;
2176         point++;
2177
2178         if (point >= nmaxcorr)
2179         {
2180             point = 0;
2181         }
2182
2183         /* Update */
2184         for (i = 0; i < n; i++)
2185         {
2186             p[i] = ff[i];
2187         }
2188
2189         cp = point;
2190
2191         /* Recursive update. First go back over the memory points */
2192         for (k = 0; k < ncorr; k++)
2193         {
2194             cp--;
2195             if (cp < 0)
2196             {
2197                 cp = ncorr - 1;
2198             }
2199
2200             sq = 0;
2201             for (i = 0; i < n; i++)
2202             {
2203                 sq += dx[cp][i] * p[i];
2204             }
2205
2206             alpha[cp] = rho[cp] * sq;
2207
2208             for (i = 0; i < n; i++)
2209             {
2210                 p[i] -= alpha[cp] * dg[cp][i];
2211             }
2212         }
2213
2214         for (i = 0; i < n; i++)
2215         {
2216             p[i] *= diag;
2217         }
2218
2219         /* And then go forward again */
2220         for (k = 0; k < ncorr; k++)
2221         {
2222             yr = 0;
2223             for (i = 0; i < n; i++)
2224             {
2225                 yr += p[i] * dg[cp][i];
2226             }
2227
2228             beta = rho[cp] * yr;
2229             beta = alpha[cp] - beta;
2230
2231             for (i = 0; i < n; i++)
2232             {
2233                 p[i] += beta * dx[cp][i];
2234             }
2235
2236             cp++;
2237             if (cp >= ncorr)
2238             {
2239                 cp = 0;
2240             }
2241         }
2242
2243         for (i = 0; i < n; i++)
2244         {
2245             if (!frozen[i])
2246             {
2247                 dx[point][i] = p[i];
2248             }
2249             else
2250             {
2251                 dx[point][i] = 0;
2252             }
2253         }
2254
2255         /* Print it if necessary */
2256         if (MASTER(cr))
2257         {
2258             if (mdrunOptions.verbose)
2259             {
2260                 double sqrtNumAtoms = sqrt(static_cast<double>(state_global->natoms));
2261                 fprintf(stderr, "\rStep %d, Epot=%12.6e, Fnorm=%9.3e, Fmax=%9.3e (atom %d)\n", step,
2262                         ems.epot, ems.fnorm / sqrtNumAtoms, ems.fmax, ems.a_fmax + 1);
2263                 fflush(stderr);
2264             }
2265             /* Store the new (lower) energies */
2266             matrix nullBox = {};
2267             energyOutput.addDataAtEnergyStep(false, false, static_cast<double>(step), mdatoms->tmass,
2268                                              enerd, nullptr, nullptr, nullptr, nullBox, nullptr,
2269                                              nullptr, vir, pres, nullptr, mu_tot, constr);
2270
2271             do_log = do_per_step(step, inputrec->nstlog);
2272             do_ene = do_per_step(step, inputrec->nstenergy);
2273
2274             imdSession->fillEnergyRecord(step, TRUE);
2275
2276             if (do_log)
2277             {
2278                 energyOutput.printHeader(fplog, step, step);
2279             }
2280             energyOutput.printStepToEnergyFile(mdoutf_get_fp_ene(outf), do_ene, FALSE, FALSE,
2281                                                do_log ? fplog : nullptr, step, step, fcd, nullptr);
2282         }
2283
2284         /* Send x and E to IMD client, if bIMD is TRUE. */
2285         if (imdSession->run(step, TRUE, state_global->box, state_global->x.rvec_array(), 0) && MASTER(cr))
2286         {
2287             imdSession->sendPositionsAndEnergies();
2288         }
2289
2290         // Reset stepsize in we are doing more iterations
2291         stepsize = 1.0;
2292
2293         /* Stop when the maximum force lies below tolerance.
2294          * If we have reached machine precision, converged is already set to true.
2295          */
2296         converged = converged || (ems.fmax < inputrec->em_tol);
2297
2298     } /* End of the loop */
2299
2300     if (converged)
2301     {
2302         step--; /* we never took that last step in this case */
2303     }
2304     if (ems.fmax > inputrec->em_tol)
2305     {
2306         if (MASTER(cr))
2307         {
2308             warn_step(fplog, inputrec->em_tol, ems.fmax, step - 1 == number_steps, FALSE);
2309         }
2310         converged = FALSE;
2311     }
2312
2313     /* If we printed energy and/or logfile last step (which was the last step)
2314      * we don't have to do it again, but otherwise print the final values.
2315      */
2316     if (!do_log) /* Write final value to log since we didn't do anythin last step */
2317     {
2318         energyOutput.printHeader(fplog, step, step);
2319     }
2320     if (!do_ene || !do_log) /* Write final energy file entries */
2321     {
2322         energyOutput.printStepToEnergyFile(mdoutf_get_fp_ene(outf), !do_ene, FALSE, FALSE,
2323                                            !do_log ? fplog : nullptr, step, step, fcd, nullptr);
2324     }
2325
2326     /* Print some stuff... */
2327     if (MASTER(cr))
2328     {
2329         fprintf(stderr, "\nwriting lowest energy coordinates.\n");
2330     }
2331
2332     /* IMPORTANT!
2333      * For accurate normal mode calculation it is imperative that we
2334      * store the last conformation into the full precision binary trajectory.
2335      *
2336      * However, we should only do it if we did NOT already write this step
2337      * above (which we did if do_x or do_f was true).
2338      */
2339     do_x = !do_per_step(step, inputrec->nstxout);
2340     do_f = !do_per_step(step, inputrec->nstfout);
2341     write_em_traj(fplog, cr, outf, do_x, do_f, ftp2fn(efSTO, nfile, fnm), top_global, inputrec,
2342                   step, &ems, state_global, observablesHistory);
2343
2344     if (MASTER(cr))
2345     {
2346         double sqrtNumAtoms = sqrt(static_cast<double>(state_global->natoms));
2347         print_converged(stderr, LBFGS, inputrec->em_tol, step, converged, number_steps, &ems, sqrtNumAtoms);
2348         print_converged(fplog, LBFGS, inputrec->em_tol, step, converged, number_steps, &ems, sqrtNumAtoms);
2349
2350         fprintf(fplog, "\nPerformed %d energy evaluations in total.\n", neval);
2351     }
2352
2353     finish_em(cr, outf, walltime_accounting, wcycle);
2354
2355     /* To print the actual number of steps we needed somewhere */
2356     walltime_accounting_set_nsteps_done(walltime_accounting, step);
2357 }
2358
2359 void LegacySimulator::do_steep()
2360 {
2361     const char*       SD = "Steepest Descents";
2362     gmx_localtop_t    top;
2363     gmx_global_stat_t gstat;
2364     t_graph*          graph;
2365     real              stepsize;
2366     real              ustep;
2367     gmx_bool          bDone, bAbort, do_x, do_f;
2368     tensor            vir, pres;
2369     rvec              mu_tot = { 0 };
2370     int               nsteps;
2371     int               count          = 0;
2372     int               steps_accepted = 0;
2373     auto              mdatoms        = mdAtoms->mdatoms();
2374
2375     GMX_LOG(mdlog.info)
2376             .asParagraph()
2377             .appendText(
2378                     "Note that activating steepest-descent energy minimization via the "
2379                     "integrator .mdp option and the command gmx mdrun may "
2380                     "be available in a different form in a future version of GROMACS, "
2381                     "e.g. gmx minimize and an .mdp option.");
2382
2383     /* Create 2 states on the stack and extract pointers that we will swap */
2384     em_state_t  s0{}, s1{};
2385     em_state_t* s_min = &s0;
2386     em_state_t* s_try = &s1;
2387
2388     /* Init em and store the local state in s_try */
2389     init_em(fplog, mdlog, SD, cr, inputrec, imdSession, pull_work, state_global, top_global, s_try,
2390             &top, nrnb, fr, &graph, mdAtoms, &gstat, vsite, constr, nullptr);
2391     const bool        simulationsShareState = false;
2392     gmx_mdoutf*       outf = init_mdoutf(fplog, nfile, fnm, mdrunOptions, cr, outputProvider,
2393                                    mdModulesNotifier, inputrec, top_global, nullptr, wcycle,
2394                                    StartingBehavior::NewSimulation, simulationsShareState, ms);
2395     gmx::EnergyOutput energyOutput(mdoutf_get_fp_ene(outf), top_global, inputrec, pull_work, nullptr,
2396                                    false, StartingBehavior::NewSimulation, mdModulesNotifier);
2397
2398     /* Print to log file  */
2399     print_em_start(fplog, cr, walltime_accounting, wcycle, SD);
2400
2401     /* Set variables for stepsize (in nm). This is the largest
2402      * step that we are going to make in any direction.
2403      */
2404     ustep    = inputrec->em_stepsize;
2405     stepsize = 0;
2406
2407     /* Max number of steps  */
2408     nsteps = inputrec->nsteps;
2409
2410     if (MASTER(cr))
2411     {
2412         /* Print to the screen  */
2413         sp_header(stderr, SD, inputrec->em_tol, nsteps);
2414     }
2415     if (fplog)
2416     {
2417         sp_header(fplog, SD, inputrec->em_tol, nsteps);
2418     }
2419     EnergyEvaluator energyEvaluator{
2420         fplog,      mdlog,     cr,      ms,     top_global,      &top,  inputrec,
2421         imdSession, pull_work, nrnb,    wcycle, gstat,           vsite, constr,
2422         fcd,        graph,     mdAtoms, fr,     runScheduleWork, enerd
2423     };
2424
2425     /**** HERE STARTS THE LOOP ****
2426      * count is the counter for the number of steps
2427      * bDone will be TRUE when the minimization has converged
2428      * bAbort will be TRUE when nsteps steps have been performed or when
2429      * the stepsize becomes smaller than is reasonable for machine precision
2430      */
2431     count  = 0;
2432     bDone  = FALSE;
2433     bAbort = FALSE;
2434     while (!bDone && !bAbort)
2435     {
2436         bAbort = (nsteps >= 0) && (count == nsteps);
2437
2438         /* set new coordinates, except for first step */
2439         bool validStep = true;
2440         if (count > 0)
2441         {
2442             validStep = do_em_step(cr, inputrec, mdatoms, s_min, stepsize, &s_min->f, s_try, constr, count);
2443         }
2444
2445         if (validStep)
2446         {
2447             energyEvaluator.run(s_try, mu_tot, vir, pres, count, count == 0);
2448         }
2449         else
2450         {
2451             // Signal constraint error during stepping with energy=inf
2452             s_try->epot = std::numeric_limits<real>::infinity();
2453         }
2454
2455         if (MASTER(cr))
2456         {
2457             energyOutput.printHeader(fplog, count, count);
2458         }
2459
2460         if (count == 0)
2461         {
2462             s_min->epot = s_try->epot;
2463         }
2464
2465         /* Print it if necessary  */
2466         if (MASTER(cr))
2467         {
2468             if (mdrunOptions.verbose)
2469             {
2470                 fprintf(stderr, "Step=%5d, Dmax= %6.1e nm, Epot= %12.5e Fmax= %11.5e, atom= %d%c",
2471                         count, ustep, s_try->epot, s_try->fmax, s_try->a_fmax + 1,
2472                         ((count == 0) || (s_try->epot < s_min->epot)) ? '\n' : '\r');
2473                 fflush(stderr);
2474             }
2475
2476             if ((count == 0) || (s_try->epot < s_min->epot))
2477             {
2478                 /* Store the new (lower) energies  */
2479                 matrix nullBox = {};
2480                 energyOutput.addDataAtEnergyStep(false, false, static_cast<double>(count), mdatoms->tmass,
2481                                                  enerd, nullptr, nullptr, nullptr, nullBox, nullptr,
2482                                                  nullptr, vir, pres, nullptr, mu_tot, constr);
2483
2484                 imdSession->fillEnergyRecord(count, TRUE);
2485
2486                 const bool do_dr = do_per_step(steps_accepted, inputrec->nstdisreout);
2487                 const bool do_or = do_per_step(steps_accepted, inputrec->nstorireout);
2488                 energyOutput.printStepToEnergyFile(mdoutf_get_fp_ene(outf), TRUE, do_dr, do_or,
2489                                                    fplog, count, count, fcd, nullptr);
2490                 fflush(fplog);
2491             }
2492         }
2493
2494         /* Now if the new energy is smaller than the previous...
2495          * or if this is the first step!
2496          * or if we did random steps!
2497          */
2498
2499         if ((count == 0) || (s_try->epot < s_min->epot))
2500         {
2501             steps_accepted++;
2502
2503             /* Test whether the convergence criterion is met...  */
2504             bDone = (s_try->fmax < inputrec->em_tol);
2505
2506             /* Copy the arrays for force, positions and energy  */
2507             /* The 'Min' array always holds the coords and forces of the minimal
2508                sampled energy  */
2509             swap_em_state(&s_min, &s_try);
2510             if (count > 0)
2511             {
2512                 ustep *= 1.2;
2513             }
2514
2515             /* Write to trn, if necessary */
2516             do_x = do_per_step(steps_accepted, inputrec->nstxout);
2517             do_f = do_per_step(steps_accepted, inputrec->nstfout);
2518             write_em_traj(fplog, cr, outf, do_x, do_f, nullptr, top_global, inputrec, count, s_min,
2519                           state_global, observablesHistory);
2520         }
2521         else
2522         {
2523             /* If energy is not smaller make the step smaller...  */
2524             ustep *= 0.5;
2525
2526             if (DOMAINDECOMP(cr) && s_min->s.ddp_count != cr->dd->ddp_count)
2527             {
2528                 /* Reload the old state */
2529                 em_dd_partition_system(fplog, mdlog, count, cr, top_global, inputrec, imdSession,
2530                                        pull_work, s_min, &top, mdAtoms, fr, vsite, constr, nrnb, wcycle);
2531             }
2532         }
2533
2534         // If the force is very small after finishing minimization,
2535         // we risk dividing by zero when calculating the step size.
2536         // So we check first if the minimization has stopped before
2537         // trying to obtain a new step size.
2538         if (!bDone)
2539         {
2540             /* Determine new step  */
2541             stepsize = ustep / s_min->fmax;
2542         }
2543
2544         /* Check if stepsize is too small, with 1 nm as a characteristic length */
2545 #if GMX_DOUBLE
2546         if (count == nsteps || ustep < 1e-12)
2547 #else
2548         if (count == nsteps || ustep < 1e-6)
2549 #endif
2550         {
2551             if (MASTER(cr))
2552             {
2553                 warn_step(fplog, inputrec->em_tol, s_min->fmax, count == nsteps, constr != nullptr);
2554             }
2555             bAbort = TRUE;
2556         }
2557
2558         /* Send IMD energies and positions, if bIMD is TRUE. */
2559         if (imdSession->run(count, TRUE, state_global->box,
2560                             MASTER(cr) ? state_global->x.rvec_array() : nullptr, 0)
2561             && MASTER(cr))
2562         {
2563             imdSession->sendPositionsAndEnergies();
2564         }
2565
2566         count++;
2567     } /* End of the loop  */
2568
2569     /* Print some data...  */
2570     if (MASTER(cr))
2571     {
2572         fprintf(stderr, "\nwriting lowest energy coordinates.\n");
2573     }
2574     write_em_traj(fplog, cr, outf, TRUE, inputrec->nstfout != 0, ftp2fn(efSTO, nfile, fnm),
2575                   top_global, inputrec, count, s_min, state_global, observablesHistory);
2576
2577     if (MASTER(cr))
2578     {
2579         double sqrtNumAtoms = sqrt(static_cast<double>(state_global->natoms));
2580
2581         print_converged(stderr, SD, inputrec->em_tol, count, bDone, nsteps, s_min, sqrtNumAtoms);
2582         print_converged(fplog, SD, inputrec->em_tol, count, bDone, nsteps, s_min, sqrtNumAtoms);
2583     }
2584
2585     finish_em(cr, outf, walltime_accounting, wcycle);
2586
2587     /* To print the actual number of steps we needed somewhere */
2588     inputrec->nsteps = count;
2589
2590     walltime_accounting_set_nsteps_done(walltime_accounting, count);
2591 }
2592
2593 void LegacySimulator::do_nm()
2594 {
2595     const char*         NM = "Normal Mode Analysis";
2596     int                 nnodes;
2597     gmx_localtop_t      top;
2598     gmx_global_stat_t   gstat;
2599     t_graph*            graph;
2600     tensor              vir, pres;
2601     rvec                mu_tot = { 0 };
2602     rvec*               dfdx;
2603     gmx_bool            bSparse; /* use sparse matrix storage format */
2604     size_t              sz;
2605     gmx_sparsematrix_t* sparse_matrix = nullptr;
2606     real*               full_matrix   = nullptr;
2607
2608     /* added with respect to mdrun */
2609     int  row, col;
2610     real der_range = 10.0 * std::sqrt(GMX_REAL_EPS);
2611     real x_min;
2612     bool bIsMaster = MASTER(cr);
2613     auto mdatoms   = mdAtoms->mdatoms();
2614
2615     GMX_LOG(mdlog.info)
2616             .asParagraph()
2617             .appendText(
2618                     "Note that activating normal-mode analysis via the integrator "
2619                     ".mdp option and the command gmx mdrun may "
2620                     "be available in a different form in a future version of GROMACS, "
2621                     "e.g. gmx normal-modes.");
2622
2623     if (constr != nullptr)
2624     {
2625         gmx_fatal(
2626                 FARGS,
2627                 "Constraints present with Normal Mode Analysis, this combination is not supported");
2628     }
2629
2630     gmx_shellfc_t* shellfc;
2631
2632     em_state_t state_work{};
2633
2634     /* Init em and store the local state in state_minimum */
2635     init_em(fplog, mdlog, NM, cr, inputrec, imdSession, pull_work, state_global, top_global,
2636             &state_work, &top, nrnb, fr, &graph, mdAtoms, &gstat, vsite, constr, &shellfc);
2637     const bool  simulationsShareState = false;
2638     gmx_mdoutf* outf = init_mdoutf(fplog, nfile, fnm, mdrunOptions, cr, outputProvider,
2639                                    mdModulesNotifier, inputrec, top_global, nullptr, wcycle,
2640                                    StartingBehavior::NewSimulation, simulationsShareState, ms);
2641
2642     std::vector<int>       atom_index = get_atom_index(top_global);
2643     std::vector<gmx::RVec> fneg(atom_index.size(), { 0, 0, 0 });
2644     snew(dfdx, atom_index.size());
2645
2646 #if !GMX_DOUBLE
2647     if (bIsMaster)
2648     {
2649         fprintf(stderr,
2650                 "NOTE: This version of GROMACS has been compiled in single precision,\n"
2651                 "      which MIGHT not be accurate enough for normal mode analysis.\n"
2652                 "      GROMACS now uses sparse matrix storage, so the memory requirements\n"
2653                 "      are fairly modest even if you recompile in double precision.\n\n");
2654     }
2655 #endif
2656
2657     /* Check if we can/should use sparse storage format.
2658      *
2659      * Sparse format is only useful when the Hessian itself is sparse, which it
2660      * will be when we use a cutoff.
2661      * For small systems (n<1000) it is easier to always use full matrix format, though.
2662      */
2663     if (EEL_FULL(fr->ic->eeltype) || fr->rlist == 0.0)
2664     {
2665         GMX_LOG(mdlog.warning)
2666                 .appendText("Non-cutoff electrostatics used, forcing full Hessian format.");
2667         bSparse = FALSE;
2668     }
2669     else if (atom_index.size() < 1000)
2670     {
2671         GMX_LOG(mdlog.warning)
2672                 .appendTextFormatted("Small system size (N=%zu), using full Hessian format.",
2673                                      atom_index.size());
2674         bSparse = FALSE;
2675     }
2676     else
2677     {
2678         GMX_LOG(mdlog.warning).appendText("Using compressed symmetric sparse Hessian format.");
2679         bSparse = TRUE;
2680     }
2681
2682     /* Number of dimensions, based on real atoms, that is not vsites or shell */
2683     sz = DIM * atom_index.size();
2684
2685     fprintf(stderr, "Allocating Hessian memory...\n\n");
2686
2687     if (bSparse)
2688     {
2689         sparse_matrix                       = gmx_sparsematrix_init(sz);
2690         sparse_matrix->compressed_symmetric = TRUE;
2691     }
2692     else
2693     {
2694         snew(full_matrix, sz * sz);
2695     }
2696
2697     /* Write start time and temperature */
2698     print_em_start(fplog, cr, walltime_accounting, wcycle, NM);
2699
2700     /* fudge nr of steps to nr of atoms */
2701     inputrec->nsteps = atom_index.size() * 2;
2702
2703     if (bIsMaster)
2704     {
2705         fprintf(stderr, "starting normal mode calculation '%s'\n%" PRId64 " steps.\n\n",
2706                 *(top_global->name), inputrec->nsteps);
2707     }
2708
2709     nnodes = cr->nnodes;
2710
2711     /* Make evaluate_energy do a single node force calculation */
2712     cr->nnodes = 1;
2713     EnergyEvaluator energyEvaluator{
2714         fplog,      mdlog,     cr,      ms,     top_global,      &top,  inputrec,
2715         imdSession, pull_work, nrnb,    wcycle, gstat,           vsite, constr,
2716         fcd,        graph,     mdAtoms, fr,     runScheduleWork, enerd
2717     };
2718     energyEvaluator.run(&state_work, mu_tot, vir, pres, -1, TRUE);
2719     cr->nnodes = nnodes;
2720
2721     /* if forces are not small, warn user */
2722     get_state_f_norm_max(cr, &(inputrec->opts), mdatoms, &state_work);
2723
2724     GMX_LOG(mdlog.warning).appendTextFormatted("Maximum force:%12.5e", state_work.fmax);
2725     if (state_work.fmax > 1.0e-3)
2726     {
2727         GMX_LOG(mdlog.warning)
2728                 .appendText(
2729                         "The force is probably not small enough to "
2730                         "ensure that you are at a minimum.\n"
2731                         "Be aware that negative eigenvalues may occur\n"
2732                         "when the resulting matrix is diagonalized.");
2733     }
2734
2735     /***********************************************************
2736      *
2737      *      Loop over all pairs in matrix
2738      *
2739      *      do_force called twice. Once with positive and
2740      *      once with negative displacement
2741      *
2742      ************************************************************/
2743
2744     /* Steps are divided one by one over the nodes */
2745     bool bNS          = true;
2746     auto state_work_x = makeArrayRef(state_work.s.x);
2747     auto state_work_f = makeArrayRef(state_work.f);
2748     for (index aid = cr->nodeid; aid < ssize(atom_index); aid += nnodes)
2749     {
2750         size_t atom = atom_index[aid];
2751         for (size_t d = 0; d < DIM; d++)
2752         {
2753             int64_t step        = 0;
2754             int     force_flags = GMX_FORCE_STATECHANGED | GMX_FORCE_ALLFORCES;
2755             double  t           = 0;
2756
2757             x_min = state_work_x[atom][d];
2758
2759             for (unsigned int dx = 0; (dx < 2); dx++)
2760             {
2761                 if (dx == 0)
2762                 {
2763                     state_work_x[atom][d] = x_min - der_range;
2764                 }
2765                 else
2766                 {
2767                     state_work_x[atom][d] = x_min + der_range;
2768                 }
2769
2770                 /* Make evaluate_energy do a single node force calculation */
2771                 cr->nnodes = 1;
2772                 if (shellfc)
2773                 {
2774                     /* Now is the time to relax the shells */
2775                     relax_shell_flexcon(fplog, cr, ms, mdrunOptions.verbose, nullptr, step, inputrec,
2776                                         imdSession, pull_work, bNS, force_flags, &top, constr, enerd,
2777                                         fcd, state_work.s.natoms, state_work.s.x.arrayRefWithPadding(),
2778                                         state_work.s.v.arrayRefWithPadding(), state_work.s.box,
2779                                         state_work.s.lambda, &state_work.s.hist,
2780                                         state_work.f.arrayRefWithPadding(), vir, mdatoms, nrnb,
2781                                         wcycle, graph, shellfc, fr, runScheduleWork, t, mu_tot,
2782                                         vsite, DDBalanceRegionHandler(nullptr));
2783                     bNS = false;
2784                     step++;
2785                 }
2786                 else
2787                 {
2788                     energyEvaluator.run(&state_work, mu_tot, vir, pres, aid * 2 + dx, FALSE);
2789                 }
2790
2791                 cr->nnodes = nnodes;
2792
2793                 if (dx == 0)
2794                 {
2795                     std::copy(state_work_f.begin(), state_work_f.begin() + atom_index.size(),
2796                               fneg.begin());
2797                 }
2798             }
2799
2800             /* x is restored to original */
2801             state_work_x[atom][d] = x_min;
2802
2803             for (size_t j = 0; j < atom_index.size(); j++)
2804             {
2805                 for (size_t k = 0; (k < DIM); k++)
2806                 {
2807                     dfdx[j][k] = -(state_work_f[atom_index[j]][k] - fneg[j][k]) / (2 * der_range);
2808                 }
2809             }
2810
2811             if (!bIsMaster)
2812             {
2813 #if GMX_MPI
2814 #    define mpi_type GMX_MPI_REAL
2815                 MPI_Send(dfdx[0], atom_index.size() * DIM, mpi_type, MASTER(cr), cr->nodeid,
2816                          cr->mpi_comm_mygroup);
2817 #endif
2818             }
2819             else
2820             {
2821                 for (index node = 0; (node < nnodes && aid + node < ssize(atom_index)); node++)
2822                 {
2823                     if (node > 0)
2824                     {
2825 #if GMX_MPI
2826                         MPI_Status stat;
2827                         MPI_Recv(dfdx[0], atom_index.size() * DIM, mpi_type, node, node,
2828                                  cr->mpi_comm_mygroup, &stat);
2829 #    undef mpi_type
2830 #endif
2831                     }
2832
2833                     row = (aid + node) * DIM + d;
2834
2835                     for (size_t j = 0; j < atom_index.size(); j++)
2836                     {
2837                         for (size_t k = 0; k < DIM; k++)
2838                         {
2839                             col = j * DIM + k;
2840
2841                             if (bSparse)
2842                             {
2843                                 if (col >= row && dfdx[j][k] != 0.0)
2844                                 {
2845                                     gmx_sparsematrix_increment_value(sparse_matrix, row, col, dfdx[j][k]);
2846                                 }
2847                             }
2848                             else
2849                             {
2850                                 full_matrix[row * sz + col] = dfdx[j][k];
2851                             }
2852                         }
2853                     }
2854                 }
2855             }
2856
2857             if (mdrunOptions.verbose && fplog)
2858             {
2859                 fflush(fplog);
2860             }
2861         }
2862         /* write progress */
2863         if (bIsMaster && mdrunOptions.verbose)
2864         {
2865             fprintf(stderr, "\rFinished step %d out of %td",
2866                     std::min<int>(atom + nnodes, atom_index.size()), ssize(atom_index));
2867             fflush(stderr);
2868         }
2869     }
2870
2871     if (bIsMaster)
2872     {
2873         fprintf(stderr, "\n\nWriting Hessian...\n");
2874         gmx_mtxio_write(ftp2fn(efMTX, nfile, fnm), sz, sz, full_matrix, sparse_matrix);
2875     }
2876
2877     finish_em(cr, outf, walltime_accounting, wcycle);
2878
2879     walltime_accounting_set_nsteps_done(walltime_accounting, atom_index.size() * 2);
2880 }
2881
2882 } // namespace gmx