2 * This file is part of the GROMACS molecular simulation package.
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 by the GROMACS development team.
7 * Copyright (c) 2018,2019,2020,2021, 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.
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.
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.
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.
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.
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.
53 #include "gromacs/commandline/filenm.h"
54 #include "gromacs/domdec/domdec_struct.h"
55 #include "gromacs/domdec/localatomset.h"
56 #include "gromacs/domdec/localatomsetmanager.h"
57 #include "gromacs/fileio/gmxfio.h"
58 #include "gromacs/gmxlib/network.h"
59 #include "gromacs/math/functions.h"
60 #include "gromacs/math/units.h"
61 #include "gromacs/math/utilities.h"
62 #include "gromacs/math/vec.h"
63 #include "gromacs/math/vectypes.h"
64 #include "gromacs/mdlib/gmx_omp_nthreads.h"
65 #include "gromacs/mdtypes/commrec.h"
66 #include "gromacs/mdtypes/forceoutput.h"
67 #include "gromacs/mdtypes/inputrec.h"
68 #include "gromacs/mdtypes/md_enums.h"
69 #include "gromacs/mdtypes/state.h"
70 #include "gromacs/pbcutil/pbc.h"
71 #include "gromacs/topology/mtop_lookup.h"
72 #include "gromacs/topology/topology.h"
73 #include "gromacs/utility/arrayref.h"
74 #include "gromacs/utility/cstringutil.h"
75 #include "gromacs/utility/exceptions.h"
76 #include "gromacs/utility/fatalerror.h"
77 #include "gromacs/utility/futil.h"
78 #include "gromacs/utility/gmxassert.h"
79 #include "gromacs/utility/pleasecite.h"
80 #include "gromacs/utility/real.h"
81 #include "gromacs/utility/smalloc.h"
82 #include "gromacs/utility/strconvert.h"
83 #include "gromacs/utility/stringutil.h"
85 #include "pull_internal.h"
89 extern template LocalAtomSet LocalAtomSetManager::add<void, void>(ArrayRef<const int> globalAtomIndex);
94 static int groupPbcFromParams(const t_pull_group& params, bool setPbcRefToPrevStepCOM)
96 if (params.ind.size() <= 1)
101 else if (params.pbcatom >= 0)
103 if (setPbcRefToPrevStepCOM)
105 return epgrppbcPREVSTEPCOM;
109 return epgrppbcREFAT;
118 /* NOTE: The params initialization currently copies pointers.
119 * So the lifetime of the source, currently always inputrec,
120 * should not end before that of this object.
121 * This will be fixed when the pointers are replacted by std::vector.
123 pull_group_work_t::pull_group_work_t(const t_pull_group& params,
124 gmx::LocalAtomSet atomSet,
125 bool bSetPbcRefToPrevStepCOM,
128 epgrppbc(groupPbcFromParams(params, bSetPbcRefToPrevStepCOM)),
129 maxNumThreads(maxNumThreads),
130 needToCalcCom(false),
140 bool pull_coordinate_is_angletype(const t_pull_coord* pcrd)
142 return (pcrd->eGeom == PullGroupGeometry::Angle || pcrd->eGeom == PullGroupGeometry::Dihedral
143 || pcrd->eGeom == PullGroupGeometry::AngleAxis);
146 static bool pull_coordinate_is_directional(const t_pull_coord* pcrd)
148 return (pcrd->eGeom == PullGroupGeometry::Direction || pcrd->eGeom == PullGroupGeometry::DirectionPBC
149 || pcrd->eGeom == PullGroupGeometry::DirectionRelative
150 || pcrd->eGeom == PullGroupGeometry::Cylinder);
153 const char* pull_coordinate_units(const t_pull_coord* pcrd)
155 return pull_coordinate_is_angletype(pcrd) ? "deg" : "nm";
158 double pull_conversion_factor_userinput2internal(const t_pull_coord* pcrd)
160 if (pull_coordinate_is_angletype(pcrd))
162 return gmx::c_deg2Rad;
170 double pull_conversion_factor_internal2userinput(const t_pull_coord* pcrd)
172 if (pull_coordinate_is_angletype(pcrd))
174 return gmx::c_rad2Deg;
182 /* Apply forces in a mass weighted fashion for part of the pull group */
183 static void apply_forces_grp_part(const pull_group_work_t& pgrp,
186 gmx::ArrayRef<const real> masses,
191 const double inv_wm = pgrp.mwscale;
193 auto localAtomIndices = pgrp.atomSet.localIndex();
194 for (int i = ind_start; i < ind_end; i++)
196 int ii = localAtomIndices[i];
197 double wmass = masses[ii];
198 if (!pgrp.localWeights.empty())
200 wmass *= pgrp.localWeights[i];
203 for (int d = 0; d < DIM; d++)
205 f[ii][d] += sign * wmass * f_pull[d] * inv_wm;
210 /* Apply forces in a mass weighted fashion */
211 static void apply_forces_grp(const pull_group_work_t& pgrp,
212 gmx::ArrayRef<const real> masses,
217 auto localAtomIndices = pgrp.atomSet.localIndex();
219 if (pgrp.params.ind.size() == 1 && pgrp.atomSet.numAtomsLocal() == 1)
221 /* Only one atom and our rank has this atom: we can skip
222 * the mass weighting, which means that this code also works
223 * for mass=0, e.g. with a virtual site.
225 for (int d = 0; d < DIM; d++)
227 f[localAtomIndices[0]][d] += sign * f_pull[d];
232 const int numThreads = pgrp.numThreads();
235 apply_forces_grp_part(pgrp, 0, localAtomIndices.size(), masses, f_pull, sign, f);
239 #pragma omp parallel for num_threads(numThreads) schedule(static)
240 for (int th = 0; th < numThreads; th++)
242 int ind_start = (localAtomIndices.size() * (th + 0)) / numThreads;
243 int ind_end = (localAtomIndices.size() * (th + 1)) / numThreads;
244 apply_forces_grp_part(pgrp, ind_start, ind_end, masses, f_pull, sign, f);
250 /* Apply forces in a mass weighted fashion to a cylinder group */
251 static void apply_forces_cyl_grp(const pull_group_work_t& pgrp,
252 const double dv_corr,
253 gmx::ArrayRef<const real> masses,
259 const double inv_wm = pgrp.mwscale;
261 auto localAtomIndices = pgrp.atomSet.localIndex();
263 /* The cylinder group is always a slab in the system, thus large.
264 * Therefore we always thread-parallelize this group.
266 const int numAtomsLocal = localAtomIndices.size();
267 const int gmx_unused numThreads = pgrp.numThreads();
268 #pragma omp parallel for num_threads(numThreads) schedule(static)
269 for (int i = 0; i < numAtomsLocal; i++)
271 double weight = pgrp.localWeights[i];
276 int ii = localAtomIndices[i];
277 double mass = masses[ii];
278 /* The stored axial distance from the cylinder center (dv) needs
279 * to be corrected for an offset (dv_corr), which was unknown when
282 double dv_com = pgrp.dv[i] + dv_corr;
284 /* Here we not only add the pull force working along vec (f_pull),
285 * but also a radial component, due to the dependence of the weights
286 * on the radial distance.
288 for (int m = 0; m < DIM; m++)
290 f[ii][m] += sign * inv_wm * (mass * weight * f_pull[m] + pgrp.mdw[i][m] * dv_com * f_scal);
295 /* Apply torque forces in a mass weighted fashion to the groups that define
296 * the pull vector direction for pull coordinate pcrd.
298 static void apply_forces_vec_torque(const pull_coord_work_t& pcrd,
299 ArrayRef<const pull_group_work_t> pullGroups,
300 gmx::ArrayRef<const real> masses,
303 const PullCoordSpatialData& spatialData = pcrd.spatialData;
305 /* The component inpr along the pull vector is accounted for in the usual
306 * way. Here we account for the component perpendicular to vec.
309 for (int m = 0; m < DIM; m++)
311 inpr += spatialData.dr01[m] * spatialData.vec[m];
313 /* The torque force works along the component of the distance vector
314 * of between the two "usual" pull groups that is perpendicular to
315 * the pull vector. The magnitude of this force is the "usual" scale force
316 * multiplied with the ratio of the distance between the two "usual" pull
317 * groups and the distance between the two groups that define the vector.
320 for (int m = 0; m < DIM; m++)
322 f_perp[m] = (spatialData.dr01[m] - inpr * spatialData.vec[m]) / spatialData.vec_len * pcrd.scalarForce;
325 /* Apply the force to the groups defining the vector using opposite signs */
326 apply_forces_grp(pullGroups[pcrd.params.group[2]], masses, f_perp, -1, f);
327 apply_forces_grp(pullGroups[pcrd.params.group[3]], masses, f_perp, 1, f);
330 /* Apply forces in a mass weighted fashion */
331 static void apply_forces_coord(const pull_coord_work_t& pcrd,
332 ArrayRef<const pull_group_work_t> pullGroups,
333 const PullCoordVectorForces& forces,
334 gmx::ArrayRef<const real> masses,
337 /* Here it would be more efficient to use one large thread-parallel
338 * region instead of potential parallel regions within apply_forces_grp.
339 * But there could be overlap between pull groups and this would lead
343 if (pcrd.params.eGeom == PullGroupGeometry::Cylinder)
345 apply_forces_cyl_grp(
346 *pcrd.dynamicGroup0, pcrd.spatialData.cyl_dev, masses, forces.force01, pcrd.scalarForce, -1, f);
348 /* Sum the force along the vector and the radial force */
350 for (int m = 0; m < DIM; m++)
352 f_tot[m] = forces.force01[m] + pcrd.scalarForce * pcrd.spatialData.ffrad[m];
354 apply_forces_grp(pullGroups[pcrd.params.group[1]], masses, f_tot, 1, f);
358 if (pcrd.params.eGeom == PullGroupGeometry::DirectionRelative)
360 /* We need to apply the torque forces to the pull groups
361 * that define the pull vector.
363 apply_forces_vec_torque(pcrd, pullGroups, masses, f);
366 if (!pullGroups[pcrd.params.group[0]].params.ind.empty())
368 apply_forces_grp(pullGroups[pcrd.params.group[0]], masses, forces.force01, -1, f);
370 apply_forces_grp(pullGroups[pcrd.params.group[1]], masses, forces.force01, 1, f);
372 if (pcrd.params.ngroup >= 4)
374 apply_forces_grp(pullGroups[pcrd.params.group[2]], masses, forces.force23, -1, f);
375 apply_forces_grp(pullGroups[pcrd.params.group[3]], masses, forces.force23, 1, f);
377 if (pcrd.params.ngroup >= 6)
379 apply_forces_grp(pullGroups[pcrd.params.group[4]], masses, forces.force45, -1, f);
380 apply_forces_grp(pullGroups[pcrd.params.group[5]], masses, forces.force45, 1, f);
385 real max_pull_distance2(const pull_coord_work_t* pcrd, const t_pbc* pbc)
387 /* Note that this maximum distance calculation is more complex than
388 * most other cases in GROMACS, since here we have to take care of
389 * distance calculations that don't involve all three dimensions.
390 * For example, we can use distances that are larger than the
391 * box X and Y dimensions for a box that is elongated along Z.
394 real max_d2 = GMX_REAL_MAX;
396 if (pull_coordinate_is_directional(&pcrd->params))
398 /* Directional pulling along along direction pcrd->vec.
399 * Calculating the exact maximum distance is complex and bug-prone.
400 * So we take a safe approach by not allowing distances that
401 * are larger than half the distance between unit cell faces
402 * along dimensions involved in pcrd->vec.
404 for (int m = 0; m < DIM; m++)
406 if (m < pbc->ndim_ePBC && pcrd->spatialData.vec[m] != 0)
408 real imageDistance2 = gmx::square(pbc->box[m][m]);
409 for (int d = m + 1; d < DIM; d++)
411 imageDistance2 -= gmx::square(pbc->box[d][m]);
413 max_d2 = std::min(max_d2, imageDistance2);
419 /* Distance pulling along dimensions with pcrd->params.dim[d]==1.
420 * We use half the minimum box vector length of the dimensions involved.
421 * This is correct for all cases, except for corner cases with
422 * triclinic boxes where e.g. dim[XX]=1 and dim[YY]=0 with
423 * box[YY][XX]!=0 and box[YY][YY] < box[XX][XX]. But since even
424 * in such corner cases the user could get correct results,
425 * depending on the details of the setup, we avoid further
426 * code complications.
428 for (int m = 0; m < DIM; m++)
430 if (m < pbc->ndim_ePBC && pcrd->params.dim[m] != 0)
432 real imageDistance2 = gmx::square(pbc->box[m][m]);
433 for (int d = 0; d < m; d++)
435 if (pcrd->params.dim[d] != 0)
437 imageDistance2 += gmx::square(pbc->box[m][d]);
440 max_d2 = std::min(max_d2, imageDistance2);
445 return 0.25 * max_d2;
448 /* This function returns the distance based on coordinates xg and xref.
449 * Note that the pull coordinate struct pcrd is not modified.
451 * \param[in] pull The pull struct
452 * \param[in] pcrd The pull coordinate to compute a distance for
453 * \param[in] pbc The periodic boundary conditions
454 * \param[in] xg The coordinate of group 1
455 * \param[in] xref The coordinate of group 0
456 * \param[in] groupIndex0 The index of group 0 in the pcrd->params.group
457 * \param[in] groupIndex1 The index of group 1 in the pcrd->params.group
458 * \param[in] max_dist2 The maximum distance squared
459 * \param[out] dr The distance vector
461 static void low_get_pull_coord_dr(const pull_t& pull,
462 const pull_coord_work_t* pcrd,
466 const int groupIndex0,
467 const int groupIndex1,
468 const double max_dist2,
471 const pull_group_work_t& pgrp0 = pull.group[pcrd->params.group[0]];
473 // Group coordinate, to be updated with the reference position
476 /* Only the first group can be an absolute reference, in that case nat=0 */
477 if (pgrp0.params.ind.empty())
479 for (int m = 0; m < DIM; m++)
481 xrefr[m] = pcrd->params.origin[m];
486 copy_dvec(xref, xrefr);
489 dvec dref = { 0, 0, 0 };
490 if (pcrd->params.eGeom == PullGroupGeometry::DirectionPBC)
492 for (int m = 0; m < DIM; m++)
494 dref[m] = pcrd->value_ref * pcrd->spatialData.vec[m];
496 /* Add the reference position, so we use the correct periodic image */
497 dvec_inc(xrefr, dref);
500 pbc_dx_d(pbc, xg, xrefr, dr);
502 bool directional = pull_coordinate_is_directional(&pcrd->params);
504 for (int m = 0; m < DIM; m++)
506 dr[m] *= pcrd->params.dim[m];
507 if (pcrd->params.dim[m] && !(directional && pcrd->spatialData.vec[m] == 0))
509 dr2 += dr[m] * dr[m];
512 /* Check if we are close to switching to another periodic image */
513 if (max_dist2 > 0 && dr2 > 0.98 * 0.98 * max_dist2)
515 /* Note that technically there is no issue with switching periodic
516 * image, as pbc_dx_d returns the distance to the closest periodic
517 * image. However in all cases where periodic image switches occur,
518 * the pull results will be useless in practice.
521 "Distance between pull groups %d and %d (%f nm) is larger than 0.49 times the "
522 "box size (%f).\n%s",
523 pcrd->params.group[groupIndex0],
524 pcrd->params.group[groupIndex1],
526 sqrt(0.98 * 0.98 * max_dist2),
527 pcrd->params.eGeom == PullGroupGeometry::Direction
528 ? "You might want to consider using \"pull-geometry = "
529 "direction-periodic\" instead.\n"
533 if (pcrd->params.eGeom == PullGroupGeometry::DirectionPBC)
539 /* This function returns the distance based on the contents of the pull struct.
540 * pull->coord[coord_ind].dr, and potentially vector, are updated.
542 static void get_pull_coord_dr(const pull_t& pull, pull_coord_work_t* pcrd, const t_pbc* pbc)
544 const int coord_ind = pcrd->params.coordIndex;
546 PullCoordSpatialData& spatialData = pcrd->spatialData;
549 /* With AWH pulling we allow for periodic pulling with geometry=direction.
550 * TODO: Store a periodicity flag instead of checking for external pull provider.
552 if (pcrd->params.eGeom == PullGroupGeometry::DirectionPBC
553 || (pcrd->params.eGeom == PullGroupGeometry::Direction
554 && pcrd->params.eType == PullingAlgorithm::External))
560 md2 = static_cast<double>(max_pull_distance2(pcrd, pbc));
563 if (pcrd->params.eGeom == PullGroupGeometry::DirectionRelative)
565 /* We need to determine the pull vector */
569 const pull_group_work_t& pgrp2 = pull.group[pcrd->params.group[2]];
570 const pull_group_work_t& pgrp3 = pull.group[pcrd->params.group[3]];
572 pbc_dx_d(pbc, pgrp3.x, pgrp2.x, vec);
574 for (m = 0; m < DIM; m++)
576 vec[m] *= pcrd->params.dim[m];
578 spatialData.vec_len = dnorm(vec);
579 for (m = 0; m < DIM; m++)
581 spatialData.vec[m] = vec[m] / spatialData.vec_len;
586 "pull coord %d vector: %6.3f %6.3f %6.3f normalized: %6.3f %6.3f %6.3f\n",
593 spatialData.vec[ZZ]);
597 const pull_group_work_t& pgrp0 = pull.group[pcrd->params.group[0]];
598 const pull_group_work_t& pgrp1 = pull.group[pcrd->params.group[1]];
600 low_get_pull_coord_dr(
605 pcrd->params.eGeom == PullGroupGeometry::Cylinder ? pcrd->dynamicGroup0->x : pgrp0.x,
611 if (pcrd->params.ngroup >= 4)
613 const pull_group_work_t& pgrp2 = pull.group[pcrd->params.group[2]];
614 const pull_group_work_t& pgrp3 = pull.group[pcrd->params.group[3]];
616 low_get_pull_coord_dr(pull, pcrd, pbc, pgrp3.x, pgrp2.x, 2, 3, md2, spatialData.dr23);
618 if (pcrd->params.ngroup >= 6)
620 const pull_group_work_t& pgrp4 = pull.group[pcrd->params.group[4]];
621 const pull_group_work_t& pgrp5 = pull.group[pcrd->params.group[5]];
623 low_get_pull_coord_dr(pull, pcrd, pbc, pgrp5.x, pgrp4.x, 4, 5, md2, spatialData.dr45);
627 /* Modify x so that it is periodic in [-pi, pi)
628 * It is assumed that x is in [-3pi, 3pi) so that x
629 * needs to be shifted by at most one period.
631 static void make_periodic_2pi(double* x)
643 /* This function should always be used to get values for setting value_ref in pull_coord_work_t */
644 static double sanitizePullCoordReferenceValue(const t_pull_coord& pcrdParams, double value_ref)
646 GMX_ASSERT(pcrdParams.eType != PullingAlgorithm::External,
647 "The pull coord reference value should not be used with type external-potential");
649 if (pcrdParams.eGeom == PullGroupGeometry::Distance)
654 "Pull reference distance for coordinate %d (%f) needs to be non-negative",
655 pcrdParams.coordIndex + 1,
659 else if (pcrdParams.eGeom == PullGroupGeometry::Angle || pcrdParams.eGeom == PullGroupGeometry::AngleAxis)
661 if (value_ref < 0 || value_ref > M_PI)
664 "Pull reference angle for coordinate %d (%f) needs to be in the allowed "
665 "interval [0,180] deg",
666 pcrdParams.coordIndex + 1,
667 value_ref * pull_conversion_factor_internal2userinput(&pcrdParams));
670 else if (pcrdParams.eGeom == PullGroupGeometry::Dihedral)
672 /* Allow pulling to be periodic for dihedral angles by remapping the reference value to the interval [-pi, pi). */
673 make_periodic_2pi(&value_ref);
679 static void updatePullCoordReferenceValue(double* referenceValue, const t_pull_coord& pcrdParams, double t)
681 GMX_ASSERT(referenceValue, "Need a valid reference value object");
683 /* With zero rate the reference value is set initially and doesn't change */
684 if (pcrdParams.rate != 0)
686 const double inputValue = (pcrdParams.init + pcrdParams.rate * t)
687 * pull_conversion_factor_userinput2internal(&pcrdParams);
688 *referenceValue = sanitizePullCoordReferenceValue(pcrdParams, inputValue);
692 /* Returns the dihedral angle. Updates the plane normal vectors m, n. */
693 static double get_dihedral_angle_coord(PullCoordSpatialData* spatialData)
696 dvec dr32; /* store instead of dr23? */
698 dsvmul(-1, spatialData->dr23, dr32);
699 dcprod(spatialData->dr01, dr32, spatialData->planevec_m); /* Normal of first plane */
700 dcprod(dr32, spatialData->dr45, spatialData->planevec_n); /* Normal of second plane */
701 phi = gmx_angle_between_dvecs(spatialData->planevec_m, spatialData->planevec_n);
703 /* Note 1: the sign below is opposite of that in the bondeds or Bekker 1994
704 * because there r_ij = ri - rj, while here dr01 = r_1 - r_0
705 * Note 2: the angle between the plane normal vectors equals pi only when
706 * both planes coincide. Thus, when phi = pi dr01 will lie in both planes and
707 * we get a positive sign below. Thus, the range of the dihedral angle is (-180, 180].
709 sign = (diprod(spatialData->dr01, spatialData->planevec_n) < 0.0) ? 1.0 : -1.0;
713 /* Calculates pull->coord[coord_ind].value.
714 * This function also updates pull->coord[coord_ind].dr.
716 static void get_pull_coord_distance(const pull_t& pull, pull_coord_work_t* pcrd, const t_pbc* pbc)
718 get_pull_coord_dr(pull, pcrd, pbc);
720 PullCoordSpatialData& spatialData = pcrd->spatialData;
722 switch (pcrd->params.eGeom)
724 case PullGroupGeometry::Distance:
725 /* Pull along the vector between the com's */
726 spatialData.value = dnorm(spatialData.dr01);
728 case PullGroupGeometry::Direction:
729 case PullGroupGeometry::DirectionPBC:
730 case PullGroupGeometry::DirectionRelative:
731 case PullGroupGeometry::Cylinder:
733 spatialData.value = 0;
734 for (int m = 0; m < DIM; m++)
736 spatialData.value += spatialData.vec[m] * spatialData.dr01[m];
739 case PullGroupGeometry::Angle:
740 spatialData.value = gmx_angle_between_dvecs(spatialData.dr01, spatialData.dr23);
742 case PullGroupGeometry::Dihedral:
743 spatialData.value = get_dihedral_angle_coord(&spatialData);
745 case PullGroupGeometry::AngleAxis:
746 spatialData.value = gmx_angle_between_dvecs(spatialData.dr01, spatialData.vec);
748 default: gmx_incons("Unsupported pull type in get_pull_coord_distance");
752 /* Returns the deviation from the reference value.
753 * Updates pull->coord[coord_ind].dr, .value and .value_ref.
755 static double get_pull_coord_deviation(const pull_t& pull, pull_coord_work_t* pcrd, const t_pbc* pbc, double t)
759 /* Update the reference value before computing the distance,
760 * since it is used in the distance computation with periodic pulling.
762 updatePullCoordReferenceValue(&pcrd->value_ref, pcrd->params, t);
764 get_pull_coord_distance(pull, pcrd, pbc);
766 get_pull_coord_distance(pull, pcrd, pbc);
768 /* Determine the deviation */
769 dev = pcrd->spatialData.value - pcrd->value_ref;
771 /* Check that values are allowed */
772 if (pcrd->params.eGeom == PullGroupGeometry::Distance && pcrd->spatialData.value == 0)
774 /* With no vector we can not determine the direction for the force,
775 * so we set the force to zero.
779 else if (pcrd->params.eGeom == PullGroupGeometry::Dihedral)
781 /* The reference value is in [-pi, pi). The coordinate value is in (-pi, pi].
782 Thus, the unwrapped deviation is here in (-2pi, 2pi].
783 After making it periodic, the deviation will be in [-pi, pi). */
784 make_periodic_2pi(&dev);
790 double get_pull_coord_value(pull_t* pull, int coord_ind, const t_pbc* pbc)
792 get_pull_coord_distance(*pull, &pull->coord[coord_ind], pbc);
794 return pull->coord[coord_ind].spatialData.value;
797 void clear_pull_forces(pull_t* pull)
799 /* Zeroing the forces is only required for constraint pulling.
800 * It can happen that multiple constraint steps need to be applied
801 * and therefore the constraint forces need to be accumulated.
803 for (pull_coord_work_t& coord : pull->coord)
805 coord.scalarForce = 0;
809 /* Apply constraint using SHAKE */
810 static void do_constraint(struct pull_t* pull,
812 gmx::ArrayRef<gmx::RVec> x,
813 gmx::ArrayRef<gmx::RVec> v,
820 dvec* r_ij; /* x[i] com of i in prev. step. Obeys constr. -> r_ij[i] */
821 dvec unc_ij; /* xp[i] com of i this step, before constr. -> unc_ij */
822 dvec* rnew; /* current 'new' positions of the groups */
823 double* dr_tot; /* the total update of the coords */
826 double lambda, rm, invdt = 0;
827 gmx_bool bConverged_all, bConverged = FALSE;
828 int niter = 0, ii, j, m, max_iter = 100;
832 snew(r_ij, pull->coord.size());
833 snew(dr_tot, pull->coord.size());
835 snew(rnew, pull->group.size());
837 /* copy the current unconstrained positions for use in iterations. We
838 iterate until rinew[i] and rjnew[j] obey the constraints. Then
839 rinew - pull.x_unc[i] is the correction dr to group i */
840 for (size_t g = 0; g < pull->group.size(); g++)
842 copy_dvec(pull->group[g].xp, rnew[g]);
845 /* Determine the constraint directions from the old positions */
846 for (size_t c = 0; c < pull->coord.size(); c++)
848 pull_coord_work_t* pcrd;
850 pcrd = &pull->coord[c];
852 if (pcrd->params.eType != PullingAlgorithm::Constraint)
857 /* Note that get_pull_coord_distance also sets pcrd->dr and pcrd->value.
858 * We don't modify dr and value anymore, so these values are also used
861 get_pull_coord_distance(*pull, pcrd, pbc);
863 const PullCoordSpatialData& spatialData = pcrd->spatialData;
867 "Pull coord %zu dr %f %f %f\n",
869 spatialData.dr01[XX],
870 spatialData.dr01[YY],
871 spatialData.dr01[ZZ]);
874 if (pcrd->params.eGeom == PullGroupGeometry::Direction
875 || pcrd->params.eGeom == PullGroupGeometry::DirectionPBC)
877 /* Select the component along vec */
879 for (m = 0; m < DIM; m++)
881 a += spatialData.vec[m] * spatialData.dr01[m];
883 for (m = 0; m < DIM; m++)
885 r_ij[c][m] = a * spatialData.vec[m];
890 copy_dvec(spatialData.dr01, r_ij[c]);
893 if (dnorm2(r_ij[c]) == 0)
896 "Distance for pull coordinate %zu is zero with constraint pulling, which is "
902 bConverged_all = FALSE;
903 while (!bConverged_all && niter < max_iter)
905 bConverged_all = TRUE;
907 /* loop over all constraints */
908 for (size_t c = 0; c < pull->coord.size(); c++)
910 pull_coord_work_t* pcrd;
911 pull_group_work_t *pgrp0, *pgrp1;
914 pcrd = &pull->coord[c];
916 if (pcrd->params.eType != PullingAlgorithm::Constraint)
921 updatePullCoordReferenceValue(&pcrd->value_ref, pcrd->params, t);
923 pgrp0 = &pull->group[pcrd->params.group[0]];
924 pgrp1 = &pull->group[pcrd->params.group[1]];
926 /* Get the current difference vector */
927 low_get_pull_coord_dr(
928 *pull, pcrd, pbc, rnew[pcrd->params.group[1]], rnew[pcrd->params.group[0]], 0, 1, -1, unc_ij);
932 fprintf(debug, "Pull coord %zu, iteration %d\n", c, niter);
935 rm = 1.0 / (pgrp0->invtm + pgrp1->invtm);
937 switch (pcrd->params.eGeom)
939 case PullGroupGeometry::Distance:
940 if (pcrd->value_ref <= 0)
944 "The pull constraint reference distance for group %zu is <= 0 (%f)",
950 double q, c_a, c_b, c_c;
952 c_a = diprod(r_ij[c], r_ij[c]);
953 c_b = diprod(unc_ij, r_ij[c]) * 2;
954 c_c = diprod(unc_ij, unc_ij) - gmx::square(pcrd->value_ref);
958 q = -0.5 * (c_b - std::sqrt(c_b * c_b - 4 * c_a * c_c));
963 q = -0.5 * (c_b + std::sqrt(c_b * c_b - 4 * c_a * c_c));
969 fprintf(debug, "Pull ax^2+bx+c=0: a=%e b=%e c=%e lambda=%e\n", c_a, c_b, c_c, lambda);
973 /* The position corrections dr due to the constraints */
974 dsvmul(-lambda * rm * pgrp1->invtm, r_ij[c], dr1);
975 dsvmul(lambda * rm * pgrp0->invtm, r_ij[c], dr0);
976 dr_tot[c] += -lambda * dnorm(r_ij[c]);
978 case PullGroupGeometry::Direction:
979 case PullGroupGeometry::DirectionPBC:
980 case PullGroupGeometry::Cylinder:
981 /* A 1-dimensional constraint along a vector */
983 for (m = 0; m < DIM; m++)
985 vec[m] = pcrd->spatialData.vec[m];
986 a += unc_ij[m] * vec[m];
988 /* Select only the component along the vector */
989 dsvmul(a, vec, unc_ij);
990 lambda = a - pcrd->value_ref;
993 fprintf(debug, "Pull inpr %e lambda: %e\n", a, lambda);
996 /* The position corrections dr due to the constraints */
997 dsvmul(-lambda * rm * pgrp1->invtm, vec, dr1);
998 dsvmul(lambda * rm * pgrp0->invtm, vec, dr0);
999 dr_tot[c] += -lambda;
1001 default: gmx_incons("Invalid enumeration value for eGeom");
1009 g0 = pcrd->params.group[0];
1010 g1 = pcrd->params.group[1];
1011 low_get_pull_coord_dr(*pull, pcrd, pbc, rnew[g1], rnew[g0], 0, 1, -1, tmp);
1012 low_get_pull_coord_dr(*pull, pcrd, pbc, dr1, dr0, 0, 1, -1, tmp3);
1014 "Pull cur %8.5f %8.5f %8.5f j:%8.5f %8.5f %8.5f d: %8.5f\n",
1023 "Pull ref %8s %8s %8s %8s %8s %8s d: %8.5f\n",
1032 "Pull cor %8.5f %8.5f %8.5f j:%8.5f %8.5f %8.5f d: %8.5f\n",
1042 /* Update the COMs with dr */
1043 dvec_inc(rnew[pcrd->params.group[1]], dr1);
1044 dvec_inc(rnew[pcrd->params.group[0]], dr0);
1047 /* Check if all constraints are fullfilled now */
1048 for (pull_coord_work_t& coord : pull->coord)
1050 if (coord.params.eType != PullingAlgorithm::Constraint)
1055 low_get_pull_coord_dr(
1056 *pull, &coord, pbc, rnew[coord.params.group[1]], rnew[coord.params.group[0]], 0, 1, -1, unc_ij);
1058 switch (coord.params.eGeom)
1060 case PullGroupGeometry::Distance:
1061 bConverged = fabs(dnorm(unc_ij) - coord.value_ref) < pull->params.constr_tol;
1063 case PullGroupGeometry::Direction:
1064 case PullGroupGeometry::DirectionPBC:
1065 case PullGroupGeometry::Cylinder:
1066 for (m = 0; m < DIM; m++)
1068 vec[m] = coord.spatialData.vec[m];
1070 inpr = diprod(unc_ij, vec);
1071 dsvmul(inpr, vec, unc_ij);
1072 bConverged = fabs(diprod(unc_ij, vec) - coord.value_ref) < pull->params.constr_tol;
1076 gmx::formatString("Geometry %s not handled here",
1077 enumValueToString(coord.params.eGeom))
1086 "Pull constraint not converged: "
1088 "d_ref = %f, current d = %f\n",
1089 coord.params.group[0],
1090 coord.params.group[1],
1095 bConverged_all = FALSE;
1100 /* if after all constraints are dealt with and bConverged is still TRUE
1101 we're finished, if not we do another iteration */
1103 if (niter > max_iter)
1105 gmx_fatal(FARGS, "Too many iterations for constraint run: %d", niter);
1108 /* DONE ITERATING, NOW UPDATE COORDINATES AND CALC. CONSTRAINT FORCES */
1115 /* update atoms in the groups */
1116 for (size_t g = 0; g < pull->group.size(); g++)
1118 const pull_group_work_t* pgrp;
1121 pgrp = &pull->group[g];
1123 /* get the final constraint displacement dr for group g */
1124 dvec_sub(rnew[g], pgrp->xp, dr);
1126 if (dnorm2(dr) == 0)
1128 /* No displacement, no update necessary */
1132 /* update the atom positions */
1133 auto localAtomIndices = pgrp->atomSet.localIndex();
1135 for (gmx::index j = 0; j < localAtomIndices.ssize(); j++)
1137 ii = localAtomIndices[j];
1138 if (!pgrp->localWeights.empty())
1140 dsvmul(pgrp->wscale * pgrp->localWeights[j], dr, tmp);
1142 for (m = 0; m < DIM; m++)
1148 for (m = 0; m < DIM; m++)
1150 v[ii][m] += invdt * tmp[m];
1156 /* calculate the constraint forces, used for output and virial only */
1157 for (size_t c = 0; c < pull->coord.size(); c++)
1159 pull_coord_work_t* pcrd;
1161 pcrd = &pull->coord[c];
1163 if (pcrd->params.eType != PullingAlgorithm::Constraint)
1168 /* Accumulate the forces, in case we have multiple constraint steps */
1171 / ((pull->group[pcrd->params.group[0]].invtm + pull->group[pcrd->params.group[1]].invtm)
1173 pcrd->scalarForce += force;
1175 if (vir != nullptr && pcrd->params.eGeom != PullGroupGeometry::DirectionPBC && bMaster)
1179 /* Add the pull contribution to the virial */
1180 /* We have already checked above that r_ij[c] != 0 */
1181 f_invr = pcrd->scalarForce / dnorm(r_ij[c]);
1183 for (j = 0; j < DIM; j++)
1185 for (m = 0; m < DIM; m++)
1187 vir[j][m] -= 0.5 * f_invr * r_ij[c][j] * r_ij[c][m];
1193 /* finished! I hope. Give back some memory */
1199 static void add_virial_coord_dr(tensor vir, const dvec dr, const dvec f)
1201 for (int j = 0; j < DIM; j++)
1203 for (int m = 0; m < DIM; m++)
1205 vir[j][m] -= 0.5 * f[j] * dr[m];
1210 /* Adds the pull contribution to the virial */
1211 static void add_virial_coord(tensor vir, const pull_coord_work_t& pcrd, const PullCoordVectorForces& forces)
1213 if (vir != nullptr && pcrd.params.eGeom != PullGroupGeometry::DirectionPBC)
1215 /* Add the pull contribution for each distance vector to the virial. */
1216 add_virial_coord_dr(vir, pcrd.spatialData.dr01, forces.force01);
1217 if (pcrd.params.ngroup >= 4)
1219 add_virial_coord_dr(vir, pcrd.spatialData.dr23, forces.force23);
1221 if (pcrd.params.ngroup >= 6)
1223 add_virial_coord_dr(vir, pcrd.spatialData.dr45, forces.force45);
1228 static void calc_pull_coord_scalar_force_and_potential(pull_coord_work_t* pcrd,
1236 k = (1.0 - lambda) * pcrd->params.k + lambda * pcrd->params.kB;
1237 dkdl = pcrd->params.kB - pcrd->params.k;
1239 switch (pcrd->params.eType)
1241 case PullingAlgorithm::Umbrella:
1242 case PullingAlgorithm::FlatBottom:
1243 case PullingAlgorithm::FlatBottomHigh:
1244 /* The only difference between an umbrella and a flat-bottom
1245 * potential is that a flat-bottom is zero above or below
1246 the reference value.
1248 if ((pcrd->params.eType == PullingAlgorithm::FlatBottom && dev < 0)
1249 || (pcrd->params.eType == PullingAlgorithm::FlatBottomHigh && dev > 0))
1254 pcrd->scalarForce = -k * dev;
1255 *V += 0.5 * k * gmx::square(dev);
1256 *dVdl += 0.5 * dkdl * gmx::square(dev);
1258 case PullingAlgorithm::ConstantForce:
1259 pcrd->scalarForce = -k;
1260 *V += k * pcrd->spatialData.value;
1261 *dVdl += dkdl * pcrd->spatialData.value;
1263 case PullingAlgorithm::External:
1265 "the scalar pull force should not be calculated internally for pull type "
1267 default: gmx_incons("Unsupported pull type in do_pull_pot");
1271 static PullCoordVectorForces calculateVectorForces(const pull_coord_work_t& pcrd)
1273 const t_pull_coord& params = pcrd.params;
1274 const PullCoordSpatialData& spatialData = pcrd.spatialData;
1276 /* The geometry of the coordinate determines how the scalar force relates to the force on each group */
1277 PullCoordVectorForces forces;
1279 if (params.eGeom == PullGroupGeometry::Distance)
1281 double invdr01 = spatialData.value > 0 ? 1. / spatialData.value : 0.;
1282 for (int m = 0; m < DIM; m++)
1284 forces.force01[m] = pcrd.scalarForce * spatialData.dr01[m] * invdr01;
1287 else if (params.eGeom == PullGroupGeometry::Angle)
1290 double cos_theta, cos_theta2;
1292 cos_theta = cos(spatialData.value);
1293 cos_theta2 = gmx::square(cos_theta);
1295 /* The force at theta = 0, pi is undefined so we should not apply any force.
1296 * cos(theta) = 1 for theta = 0, pi so this is what we check for below.
1297 * Note that dr01 = 0 or dr23 = 0 evaluates to theta = 0 so we do not
1298 * have to check for this before dividing by their norm below.
1302 /* The forces f01 and f23 can be decomposed into one vector parallel to dr01
1303 * and another vector parallel to dr23:
1304 * f01 = -dV/dtheta*(a*dr23/|dr23| - b*dr01*|dr01|)/|dr01|,
1305 * f23 = -dV/dtheta*(a*dr01/|dr01| - b*dr23*|dr23|)/|dr23|,
1307 double a = -gmx::invsqrt(1 - cos_theta2); /* comes from d/dx acos(x) */
1308 double b = a * cos_theta;
1309 double invdr01 = 1. / dnorm(spatialData.dr01);
1310 double invdr23 = 1. / dnorm(spatialData.dr23);
1311 dvec normalized_dr01, normalized_dr23;
1312 dsvmul(invdr01, spatialData.dr01, normalized_dr01);
1313 dsvmul(invdr23, spatialData.dr23, normalized_dr23);
1315 for (int m = 0; m < DIM; m++)
1317 /* Here, f_scal is -dV/dtheta */
1319 pcrd.scalarForce * invdr01 * (a * normalized_dr23[m] - b * normalized_dr01[m]);
1321 pcrd.scalarForce * invdr23 * (a * normalized_dr01[m] - b * normalized_dr23[m]);
1326 /* No forces to apply for ill-defined cases*/
1327 clear_dvec(forces.force01);
1328 clear_dvec(forces.force23);
1331 else if (params.eGeom == PullGroupGeometry::AngleAxis)
1333 double cos_theta, cos_theta2;
1335 /* The angle-axis force is exactly the same as the angle force (above) except that in
1336 this case the second vector (dr23) is replaced by the pull vector. */
1337 cos_theta = cos(spatialData.value);
1338 cos_theta2 = gmx::square(cos_theta);
1344 dvec normalized_dr01;
1346 invdr01 = 1. / dnorm(spatialData.dr01);
1347 dsvmul(invdr01, spatialData.dr01, normalized_dr01);
1348 a = -gmx::invsqrt(1 - cos_theta2); /* comes from d/dx acos(x) */
1351 for (int m = 0; m < DIM; m++)
1354 pcrd.scalarForce * invdr01 * (a * spatialData.vec[m] - b * normalized_dr01[m]);
1359 clear_dvec(forces.force01);
1362 else if (params.eGeom == PullGroupGeometry::Dihedral)
1364 double m2, n2, tol, sqrdist_32;
1366 /* Note: there is a small difference here compared to the
1367 dihedral force calculations in the bondeds (ref: Bekker 1994).
1368 There rij = ri - rj, while here dr01 = r1 - r0.
1369 However, all distance vectors occur in form of cross or inner products
1370 so that two signs cancel and we end up with the same expressions.
1371 Also, we treat the more general case of 6 groups (0..5) instead of 4 (i, j, k, l).
1373 m2 = diprod(spatialData.planevec_m, spatialData.planevec_m);
1374 n2 = diprod(spatialData.planevec_n, spatialData.planevec_n);
1375 dsvmul(-1, spatialData.dr23, dr32);
1376 sqrdist_32 = diprod(dr32, dr32);
1377 tol = sqrdist_32 * GMX_REAL_EPS; /* Avoid tiny angles */
1378 if ((m2 > tol) && (n2 > tol))
1380 double a_01, a_23_01, a_23_45, a_45;
1381 double inv_dist_32, inv_sqrdist_32, dist_32;
1383 inv_dist_32 = gmx::invsqrt(sqrdist_32);
1384 inv_sqrdist_32 = inv_dist_32 * inv_dist_32;
1385 dist_32 = sqrdist_32 * inv_dist_32;
1387 /* Forces on groups 0, 1 */
1388 a_01 = pcrd.scalarForce * dist_32 / m2; /* scalarForce is -dV/dphi */
1389 dsvmul(-a_01, spatialData.planevec_m, forces.force01); /* added sign to get force on group 1, not 0 */
1391 /* Forces on groups 4, 5 */
1392 a_45 = -pcrd.scalarForce * dist_32 / n2;
1393 dsvmul(a_45, spatialData.planevec_n, forces.force45); /* force on group 5 */
1395 /* Force on groups 2, 3 (defining the axis) */
1396 a_23_01 = -diprod(spatialData.dr01, dr32) * inv_sqrdist_32;
1397 a_23_45 = -diprod(spatialData.dr45, dr32) * inv_sqrdist_32;
1398 dsvmul(-a_23_01, forces.force01, u); /* added sign to get force from group 0, not 1 */
1399 dsvmul(a_23_45, forces.force45, v);
1400 dvec_sub(u, v, forces.force23); /* force on group 3 */
1404 /* No force to apply for ill-defined cases */
1405 clear_dvec(forces.force01);
1406 clear_dvec(forces.force23);
1407 clear_dvec(forces.force45);
1412 for (int m = 0; m < DIM; m++)
1414 forces.force01[m] = pcrd.scalarForce * spatialData.vec[m];
1422 /* We use a global mutex for locking access to the pull data structure
1423 * during registration of external pull potential providers.
1424 * We could use a different, local mutex for each pull object, but the overhead
1425 * is extremely small here and registration is only done during initialization.
1427 static std::mutex registrationMutex;
1429 using Lock = std::lock_guard<std::mutex>;
1431 void register_external_pull_potential(struct pull_t* pull, int coord_index, const char* provider)
1433 GMX_RELEASE_ASSERT(pull != nullptr, "register_external_pull_potential called before init_pull");
1434 GMX_RELEASE_ASSERT(provider != nullptr,
1435 "register_external_pull_potential called with NULL as provider name");
1437 if (coord_index < 0 || coord_index >= gmx::ssize(pull->coord))
1440 "Module '%s' attempted to register an external potential for pull coordinate %d "
1441 "which is out of the pull coordinate range %d - %zu\n",
1445 pull->coord.size());
1448 pull_coord_work_t* pcrd = &pull->coord[coord_index];
1450 if (pcrd->params.eType != PullingAlgorithm::External)
1454 "Module '%s' attempted to register an external potential for pull coordinate %d "
1455 "which of type '%s', whereas external potentials are only supported with type '%s'",
1458 enumValueToString(pcrd->params.eType),
1459 enumValueToString(PullingAlgorithm::External));
1462 GMX_RELEASE_ASSERT(!pcrd->params.externalPotentialProvider.empty(),
1463 "The external potential provider string for a pull coordinate is NULL");
1465 if (gmx_strcasecmp(provider, pcrd->params.externalPotentialProvider.c_str()) != 0)
1468 "Module '%s' attempted to register an external potential for pull coordinate %d "
1469 "which expects the external potential to be provided by a module named '%s'",
1472 pcrd->params.externalPotentialProvider.c_str());
1475 /* Lock to avoid (extremely unlikely) simultaneous reading and writing of
1476 * pcrd->bExternalPotentialProviderHasBeenRegistered and
1477 * pull->numUnregisteredExternalPotentials.
1479 Lock registrationLock(registrationMutex);
1481 if (pcrd->bExternalPotentialProviderHasBeenRegistered)
1484 "Module '%s' attempted to register an external potential for pull coordinate %d "
1490 pcrd->bExternalPotentialProviderHasBeenRegistered = true;
1491 pull->numUnregisteredExternalPotentials--;
1493 GMX_RELEASE_ASSERT(pull->numUnregisteredExternalPotentials >= 0,
1494 "Negative unregistered potentials, the pull code is inconsistent");
1498 static void check_external_potential_registration(const struct pull_t* pull)
1500 if (pull->numUnregisteredExternalPotentials > 0)
1503 for (c = 0; c < pull->coord.size(); c++)
1505 if (pull->coord[c].params.eType == PullingAlgorithm::External
1506 && !pull->coord[c].bExternalPotentialProviderHasBeenRegistered)
1512 GMX_RELEASE_ASSERT(c < pull->coord.size(),
1513 "Internal inconsistency in the pull potential provider counting");
1516 "No external provider for external pull potentials have been provided for %d "
1517 "pull coordinates. The first coordinate without provider is number %zu, which "
1518 "expects a module named '%s' to provide the external potential.",
1519 pull->numUnregisteredExternalPotentials,
1521 pull->coord[c].params.externalPotentialProvider.c_str());
1525 /* Pull takes care of adding the forces of the external potential.
1526 * The external potential module has to make sure that the corresponding
1527 * potential energy is added either to the pull term or to a term
1528 * specific to the external module.
1530 void apply_external_pull_coord_force(struct pull_t* pull,
1533 gmx::ArrayRef<const real> masses,
1534 gmx::ForceWithVirial* forceWithVirial)
1536 pull_coord_work_t* pcrd;
1538 GMX_ASSERT(coord_index >= 0 && coord_index < gmx::ssize(pull->coord),
1539 "apply_external_pull_coord_force called with coord_index out of range");
1541 if (pull->comm.bParticipate)
1543 pcrd = &pull->coord[coord_index];
1546 pcrd->params.eType == PullingAlgorithm::External,
1547 "The pull force can only be set externally on pull coordinates of external type");
1549 GMX_ASSERT(pcrd->bExternalPotentialProviderHasBeenRegistered,
1550 "apply_external_pull_coord_force called for an unregistered pull coordinate");
1553 pcrd->scalarForce = coord_force;
1555 /* Calculate the forces on the pull groups */
1556 PullCoordVectorForces pullCoordForces = calculateVectorForces(*pcrd);
1558 /* Add the forces for this coordinate to the total virial and force */
1559 if (forceWithVirial->computeVirial_ && pull->comm.isMasterRank)
1561 matrix virial = { { 0 } };
1562 add_virial_coord(virial, *pcrd, pullCoordForces);
1563 forceWithVirial->addVirialContribution(virial);
1567 *pcrd, pull->group, pullCoordForces, masses, as_rvec_array(forceWithVirial->force_.data()));
1570 pull->numExternalPotentialsStillToBeAppliedThisStep--;
1573 /* Calculate the pull potential and scalar force for a pull coordinate.
1574 * Returns the vector forces for the pull coordinate.
1576 static PullCoordVectorForces do_pull_pot_coord(const pull_t& pull,
1577 pull_coord_work_t* pcrd,
1585 assert(pcrd->params.eType != PullingAlgorithm::Constraint);
1587 double dev = get_pull_coord_deviation(pull, pcrd, pbc, t);
1589 calc_pull_coord_scalar_force_and_potential(pcrd, dev, lambda, V, dVdl);
1591 PullCoordVectorForces pullCoordForces = calculateVectorForces(*pcrd);
1593 add_virial_coord(vir, *pcrd, pullCoordForces);
1595 return pullCoordForces;
1598 real pull_potential(struct pull_t* pull,
1599 gmx::ArrayRef<const real> masses,
1601 const t_commrec* cr,
1604 gmx::ArrayRef<const gmx::RVec> x,
1605 gmx::ForceWithVirial* force,
1610 assert(pull != nullptr);
1612 /* Ideally we should check external potential registration only during
1613 * the initialization phase, but that requires another function call
1614 * that should be done exactly in the right order. So we check here.
1616 check_external_potential_registration(pull);
1618 if (pull->comm.bParticipate)
1622 pull_calc_coms(cr, pull, masses, pbc, t, x, {});
1624 rvec* f = as_rvec_array(force->force_.data());
1625 matrix virial = { { 0 } };
1626 const bool computeVirial = (force->computeVirial_ && MASTER(cr));
1627 for (size_t c = 0; c < pull->coord.size(); c++)
1629 pull_coord_work_t* pcrd;
1630 pcrd = &pull->coord[c];
1632 /* For external potential the force is assumed to be given by an external module by a
1633 call to apply_pull_coord_external_force */
1634 if (pcrd->params.eType == PullingAlgorithm::Constraint
1635 || pcrd->params.eType == PullingAlgorithm::External)
1640 PullCoordVectorForces pullCoordForces = do_pull_pot_coord(
1641 *pull, pcrd, pbc, t, lambda, &V, computeVirial ? virial : nullptr, &dVdl);
1643 /* Distribute the force over the atoms in the pulled groups */
1644 apply_forces_coord(*pcrd, pull->group, pullCoordForces, masses, f);
1649 force->addVirialContribution(virial);
1654 GMX_ASSERT(pull->numExternalPotentialsStillToBeAppliedThisStep == 0,
1655 "Too few or too many external pull potentials have been applied the previous step");
1656 /* All external pull potentials still need to be applied */
1657 pull->numExternalPotentialsStillToBeAppliedThisStep = pull->numCoordinatesWithExternalPotential;
1659 return (MASTER(cr) ? V : 0.0);
1662 void pull_constraint(struct pull_t* pull,
1663 gmx::ArrayRef<const real> masses,
1665 const t_commrec* cr,
1668 gmx::ArrayRef<gmx::RVec> x,
1669 gmx::ArrayRef<gmx::RVec> xp,
1670 gmx::ArrayRef<gmx::RVec> v,
1673 assert(pull != nullptr);
1675 if (pull->comm.bParticipate)
1677 pull_calc_coms(cr, pull, masses, pbc, t, x, xp);
1679 do_constraint(pull, pbc, xp, v, MASTER(cr), vir, dt, t);
1683 void dd_make_local_pull_groups(const t_commrec* cr, struct pull_t* pull)
1687 gmx_bool bMustParticipate;
1693 /* We always make the master node participate, such that it can do i/o,
1694 * add the virial and to simplify MC type extensions people might have.
1696 bMustParticipate = (comm->bParticipateAll || comm->isMasterRank);
1698 for (pull_group_work_t& group : pull->group)
1700 if (!group.globalWeights.empty())
1702 group.localWeights.resize(group.atomSet.numAtomsLocal());
1703 for (size_t i = 0; i < group.atomSet.numAtomsLocal(); ++i)
1705 group.localWeights[i] = group.globalWeights[group.atomSet.collectiveIndex()[i]];
1709 GMX_ASSERT(bMustParticipate || dd != nullptr,
1710 "Either all ranks (including this rank) participate, or we use DD and need to "
1711 "have access to dd here");
1713 /* We should participate if we have pull or pbc atoms */
1714 if (!bMustParticipate
1715 && (group.atomSet.numAtomsLocal() > 0
1716 || (group.epgrppbc == epgrppbcREFAT && group.pbcAtomSet->numAtomsLocal() > 0)))
1718 bMustParticipate = TRUE;
1722 if (!comm->bParticipateAll)
1724 /* Keep currently not required ranks in the communicator
1725 * if they needed to participate up to 20 decompositions ago.
1726 * This avoids frequent rebuilds due to atoms jumping back and forth.
1728 const int64_t history_count = 20;
1729 gmx_bool bWillParticipate;
1732 /* Increase the decomposition counter for the current call */
1733 comm->setup_count++;
1735 if (bMustParticipate)
1737 comm->must_count = comm->setup_count;
1742 || (comm->bParticipate && comm->must_count >= comm->setup_count - history_count);
1744 if (debug && dd != nullptr)
1747 "Our DD rank (%3d) pull #atoms>0 or master: %s, will be part %s\n",
1749 gmx::boolToString(bMustParticipate),
1750 gmx::boolToString(bWillParticipate));
1753 if (bWillParticipate)
1755 /* Count the number of ranks that we want to have participating */
1757 /* Count the number of ranks that need to be added */
1758 count[1] = comm->bParticipate ? 0 : 1;
1766 /* The cost of this global operation will be less that the cost
1767 * of the extra MPI_Comm_split calls that we can avoid.
1769 gmx_sumi(2, count, cr);
1771 /* If we are missing ranks or if we have 20% more ranks than needed
1772 * we make a new sub-communicator.
1774 if (count[1] > 0 || 6 * count[0] < 5 * comm->nparticipate)
1778 fprintf(debug, "Creating new pull subcommunicator of size %d\n", count[0]);
1782 if (comm->mpi_comm_com != MPI_COMM_NULL)
1784 MPI_Comm_free(&comm->mpi_comm_com);
1787 /* This might be an extremely expensive operation, so we try
1788 * to avoid this splitting as much as possible.
1790 assert(dd != nullptr);
1791 MPI_Comm_split(dd->mpi_comm_all, bWillParticipate ? 0 : 1, dd->rank, &comm->mpi_comm_com);
1794 comm->bParticipate = bWillParticipate;
1795 comm->nparticipate = count[0];
1797 /* When we use the previous COM for PBC, we need to broadcast
1798 * the previous COM to ranks that have joined the communicator.
1800 for (pull_group_work_t& group : pull->group)
1802 if (group.epgrppbc == epgrppbcPREVSTEPCOM)
1804 GMX_ASSERT(comm->bParticipate || !MASTER(cr),
1805 "The master rank has to participate, as it should pass an up to "
1807 "to bcast here as well as to e.g. checkpointing");
1809 gmx_bcast(sizeof(dvec), group.x_prev_step, cr->mpi_comm_mygroup);
1815 /* Since the PBC of atoms might have changed, we need to update the PBC */
1816 pull->bSetPBCatoms = TRUE;
1819 static void init_pull_group_index(FILE* fplog,
1820 const t_commrec* cr,
1822 pull_group_work_t* pg,
1823 gmx_bool bConstraint,
1824 const ivec pulldim_con,
1825 const gmx_mtop_t& mtop,
1826 const t_inputrec* ir,
1829 /* With EM and BD there are no masses in the integrator.
1830 * But we still want to have the correct mass-weighted COMs.
1831 * So we store the real masses in the weights.
1833 const bool setWeights = (!pg->params.weight.empty() || EI_ENERGY_MINIMIZATION(ir->eI)
1834 || ir->eI == IntegrationAlgorithm::BD);
1836 /* In parallel, store we need to extract localWeights from weights at DD time */
1837 std::vector<real>& weights = ((cr && PAR(cr)) ? pg->globalWeights : pg->localWeights);
1839 const SimulationGroups& groups = mtop.groups;
1841 /* Count frozen dimensions and (weighted) mass */
1847 for (int i = 0; i < int(pg->params.ind.size()); i++)
1849 int ii = pg->params.ind[i];
1850 if (bConstraint && ir->opts.nFreeze)
1852 for (int d = 0; d < DIM; d++)
1854 if (pulldim_con[d] == 1
1855 && ir->opts.nFreeze[getGroupType(groups, SimulationAtomGroupType::Freeze, ii)][d])
1861 const t_atom& atom = mtopGetAtomParameters(mtop, ii, &molb);
1863 if (ir->efep == FreeEnergyPerturbationType::No)
1869 m = (1 - lambda) * atom.m + lambda * atom.mB;
1872 if (!pg->params.weight.empty())
1874 w = pg->params.weight[i];
1880 if (EI_ENERGY_MINIMIZATION(ir->eI))
1882 /* Move the mass to the weight */
1886 else if (ir->eI == IntegrationAlgorithm::BD)
1889 if (ir->bd_fric != 0.0F)
1891 mbd = ir->bd_fric * ir->delta_t;
1895 if (groups.groupNumbers[SimulationAtomGroupType::TemperatureCoupling].empty())
1897 mbd = ir->delta_t / ir->opts.tau_t[0];
1902 / ir->opts.tau_t[groups.groupNumbers[SimulationAtomGroupType::TemperatureCoupling][ii]];
1910 weights.push_back(w);
1914 wwmass += m * w * w;
1919 /* We can have single atom groups with zero mass with potential pulling
1920 * without cosine weighting.
1922 if (pg->params.ind.size() == 1 && !bConstraint && pg->epgrppbc != epgrppbcCOS)
1924 /* With one atom the mass doesn't matter */
1930 "The total%s mass of pull group %d is zero",
1931 !pg->params.weight.empty() ? " weighted" : "",
1937 fprintf(fplog, "Pull group %d: %5zu atoms, mass %9.3f", g, pg->params.ind.size(), tmass);
1938 if (!pg->params.weight.empty() || EI_ENERGY_MINIMIZATION(ir->eI)
1939 || ir->eI == IntegrationAlgorithm::BD)
1941 fprintf(fplog, ", weighted mass %9.3f", wmass * wmass / wwmass);
1943 if (pg->epgrppbc == epgrppbcCOS)
1945 fprintf(fplog, ", cosine weighting will be used");
1947 fprintf(fplog, "\n");
1952 /* A value != 0 signals not frozen, it is updated later */
1958 for (int d = 0; d < DIM; d++)
1960 ndim += pulldim_con[d] * pg->params.ind.size();
1962 if (fplog && nfrozen > 0 && nfrozen < ndim)
1965 "\nWARNING: In pull group %d some, but not all of the degrees of freedom\n"
1966 " that are subject to pulling are frozen.\n"
1967 " For constraint pulling the whole group will be frozen.\n\n",
1975 struct pull_t* init_pull(FILE* fplog,
1976 const pull_params_t* pull_params,
1977 const t_inputrec* ir,
1978 const gmx_mtop_t& mtop,
1979 const t_commrec* cr,
1980 gmx::LocalAtomSetManager* atomSets,
1983 struct pull_t* pull;
1988 /* Copy the pull parameters */
1989 pull->params = *pull_params;
1991 /* The gmx_omp_nthreads module might not be initialized here, so max(1,) */
1992 const int maxNumThreads = std::max(1, gmx_omp_nthreads_get(emntDefault));
1994 for (int i = 0; i < pull_params->ngroup; ++i)
1996 pull->group.emplace_back(pull_params->group[i],
1997 atomSets->add(pull_params->group[i].ind),
1998 pull_params->bSetPbcRefToPrevStepCOM,
2002 if (cr != nullptr && DOMAINDECOMP(cr))
2004 /* Set up the global to local atom mapping for PBC atoms */
2005 for (pull_group_work_t& group : pull->group)
2007 if (group.epgrppbc == epgrppbcREFAT || group.epgrppbc == epgrppbcPREVSTEPCOM)
2009 /* pbcAtomSet consists of a single atom */
2010 group.pbcAtomSet = std::make_unique<gmx::LocalAtomSet>(
2011 atomSets->add({ &group.params.pbcatom, &group.params.pbcatom + 1 }));
2016 pull->bPotential = FALSE;
2017 pull->bConstraint = FALSE;
2018 pull->bCylinder = FALSE;
2019 pull->bAngle = FALSE;
2020 pull->bXOutAverage = pull_params->bXOutAverage;
2021 pull->bFOutAverage = pull_params->bFOutAverage;
2023 GMX_RELEASE_ASSERT(pull->group[0].params.ind.empty(),
2024 "pull group 0 is an absolute reference group and should not contain atoms");
2026 pull->numCoordinatesWithExternalPotential = 0;
2028 for (int c = 0; c < pull->params.ncoord; c++)
2030 GMX_RELEASE_ASSERT(pull_params->coord[c].coordIndex == c,
2031 "The stored index should match the position in the vector");
2033 /* Construct a pull coordinate, copying all coordinate parameters */
2034 pull->coord.emplace_back(pull_params->coord[c]);
2036 pull_coord_work_t* pcrd = &pull->coord.back();
2038 switch (pcrd->params.eGeom)
2040 case PullGroupGeometry::Distance:
2041 case PullGroupGeometry::DirectionRelative: /* Direction vector is determined at each step */
2042 case PullGroupGeometry::Angle:
2043 case PullGroupGeometry::Dihedral: break;
2044 case PullGroupGeometry::Direction:
2045 case PullGroupGeometry::DirectionPBC:
2046 case PullGroupGeometry::Cylinder:
2047 case PullGroupGeometry::AngleAxis:
2048 copy_rvec_to_dvec(pull_params->coord[c].vec, pcrd->spatialData.vec);
2051 /* We allow reading of newer tpx files with new pull geometries,
2052 * but with the same tpx format, with old code. A new geometry
2053 * only adds a new enum value, which will be out of range for
2054 * old code. The first place we need to generate an error is
2055 * here, since the pull code can't handle this.
2056 * The enum can be used before arriving here only for printing
2057 * the string corresponding to the geometry, which will result
2058 * in printing "UNDEFINED".
2061 "Pull geometry not supported for pull coordinate %d. The geometry enum "
2062 "%s in the input is larger than that supported by the code (up to %d). "
2063 "You are probably reading a tpr file generated with a newer version of "
2064 "GROMACS with an binary from an older version of Gromacs.",
2066 enumValueToString(pcrd->params.eGeom),
2067 static_cast<int>(PullGroupGeometry::Count) - 1);
2070 if (pcrd->params.eType == PullingAlgorithm::Constraint)
2072 /* Check restrictions of the constraint pull code */
2073 if (pcrd->params.eGeom == PullGroupGeometry::Cylinder
2074 || pcrd->params.eGeom == PullGroupGeometry::DirectionRelative
2075 || pcrd->params.eGeom == PullGroupGeometry::Angle
2076 || pcrd->params.eGeom == PullGroupGeometry::Dihedral
2077 || pcrd->params.eGeom == PullGroupGeometry::AngleAxis)
2080 "Pulling of type %s can not be combined with geometry %s. Consider using "
2082 enumValueToString(pcrd->params.eType),
2083 enumValueToString(pcrd->params.eGeom),
2084 enumValueToString(PullingAlgorithm::Umbrella));
2089 "Constraint pulling can not be combined with multiple time stepping");
2091 pull->bConstraint = TRUE;
2095 pull->bPotential = TRUE;
2098 if (pcrd->params.eGeom == PullGroupGeometry::Cylinder)
2100 pull->bCylinder = TRUE;
2102 else if (pcrd->params.eGeom == PullGroupGeometry::Angle
2103 || pcrd->params.eGeom == PullGroupGeometry::Dihedral
2104 || pcrd->params.eGeom == PullGroupGeometry::AngleAxis)
2106 pull->bAngle = TRUE;
2109 /* We only need to calculate the plain COM of a group
2110 * when it is not only used as a cylinder group.
2111 * Also the absolute reference group 0 needs no COM computation.
2113 for (int i = 0; i < pcrd->params.ngroup; i++)
2115 int groupIndex = pcrd->params.group[i];
2116 if (groupIndex > 0 && !(pcrd->params.eGeom == PullGroupGeometry::Cylinder && i == 0))
2118 pull->group[groupIndex].needToCalcCom = true;
2122 /* With non-zero rate the reference value is set at every step */
2123 if (pcrd->params.rate == 0)
2125 /* Initialize the constant reference value */
2126 if (pcrd->params.eType != PullingAlgorithm::External)
2128 pcrd->value_ref = sanitizePullCoordReferenceValue(
2130 pcrd->params.init * pull_conversion_factor_userinput2internal(&pcrd->params));
2134 /* With an external pull potential, the reference value loses
2135 * it's meaning and should not be used. Setting it to zero
2136 * makes any terms dependent on it disappear.
2137 * The only issue this causes is that with cylinder pulling
2138 * no atoms of the cylinder group within the cylinder radius
2139 * should be more than half a box length away from the COM of
2140 * the pull group along the axial direction.
2142 pcrd->value_ref = 0.0;
2146 if (pcrd->params.eType == PullingAlgorithm::External)
2149 pcrd->params.rate == 0,
2150 "With an external potential, a pull coordinate should have rate = 0");
2152 /* This external potential needs to be registered later */
2153 pull->numCoordinatesWithExternalPotential++;
2155 pcrd->bExternalPotentialProviderHasBeenRegistered = false;
2158 pull->numUnregisteredExternalPotentials = pull->numCoordinatesWithExternalPotential;
2159 pull->numExternalPotentialsStillToBeAppliedThisStep = 0;
2161 pull->pbcType = ir->pbcType;
2162 switch (pull->pbcType)
2164 case PbcType::No: pull->npbcdim = 0; break;
2165 case PbcType::XY: pull->npbcdim = 2; break;
2166 default: pull->npbcdim = 3; break;
2171 gmx_bool bAbs, bCos;
2174 for (const pull_coord_work_t& coord : pull->coord)
2176 if (pull->group[coord.params.group[0]].params.ind.empty()
2177 || pull->group[coord.params.group[1]].params.ind.empty())
2183 fprintf(fplog, "\n");
2184 if (pull->bPotential)
2186 fprintf(fplog, "Will apply potential COM pulling\n");
2188 if (pull->bConstraint)
2190 fprintf(fplog, "Will apply constraint COM pulling\n");
2192 // Don't include the reference group 0 in output, so we report ngroup-1
2193 int numRealGroups = pull->group.size() - 1;
2194 GMX_RELEASE_ASSERT(numRealGroups > 0,
2195 "The reference absolute position pull group should always be present");
2197 "with %zu pull coordinate%s and %d group%s\n",
2199 pull->coord.size() == 1 ? "" : "s",
2201 numRealGroups == 1 ? "" : "s");
2204 fprintf(fplog, "with an absolute reference\n");
2207 // Don't include the reference group 0 in loop
2208 for (size_t g = 1; g < pull->group.size(); g++)
2210 if (pull->group[g].params.ind.size() > 1 && pull->group[g].params.pbcatom < 0)
2212 /* We are using cosine weighting */
2213 fprintf(fplog, "Cosine weighting is used for group %zu\n", g);
2219 please_cite(fplog, "Engin2010");
2223 pull->bRefAt = FALSE;
2225 for (size_t g = 0; g < pull->group.size(); g++)
2227 pull_group_work_t* pgrp;
2229 pgrp = &pull->group[g];
2230 if (!pgrp->params.ind.empty())
2232 /* There is an issue when a group is used in multiple coordinates
2233 * and constraints are applied in different dimensions with atoms
2234 * frozen in some, but not all dimensions.
2235 * Since there is only one mass array per group, we can't have
2236 * frozen/non-frozen atoms for different coords at the same time.
2237 * But since this is a very exotic case, we don't check for this.
2238 * A warning is printed in init_pull_group_index.
2241 gmx_bool bConstraint;
2242 ivec pulldim, pulldim_con;
2244 /* Loop over all pull coordinates to see along which dimensions
2245 * this group is pulled and if it is involved in constraints.
2247 bConstraint = FALSE;
2248 clear_ivec(pulldim);
2249 clear_ivec(pulldim_con);
2250 for (const pull_coord_work_t& coord : pull->coord)
2252 gmx_bool bGroupUsed = FALSE;
2253 for (int gi = 0; gi < coord.params.ngroup; gi++)
2255 if (coord.params.group[gi] == static_cast<int>(g))
2263 for (int m = 0; m < DIM; m++)
2265 if (coord.params.dim[m] == 1)
2269 if (coord.params.eType == PullingAlgorithm::Constraint)
2279 /* Determine if we need to take PBC into account for calculating
2280 * the COM's of the pull groups.
2282 switch (pgrp->epgrppbc)
2284 case epgrppbcREFAT: pull->bRefAt = TRUE; break;
2286 if (!pgrp->params.weight.empty())
2289 "Pull groups can not have relative weights and cosine weighting "
2292 for (int m = 0; m < DIM; m++)
2294 if (m < pull->npbcdim && pulldim[m] == 1)
2296 if (pull->cosdim >= 0 && pull->cosdim != m)
2299 "Can only use cosine weighting with pulling in one "
2300 "dimension (use mdp option pull-coord?-dim)");
2306 case epgrppbcNONE: break;
2309 /* Set the indices */
2310 init_pull_group_index(fplog, cr, g, pgrp, bConstraint, pulldim_con, mtop, ir, lambda);
2314 /* Absolute reference, set the inverse mass to zero.
2315 * This is only relevant (and used) with constraint pulling.
2322 /* If we use cylinder coordinates, do some initialising for them */
2323 if (pull->bCylinder)
2325 for (pull_coord_work_t& coord : pull->coord)
2327 if (coord.params.eGeom == PullGroupGeometry::Cylinder)
2329 if (pull->group[coord.params.group[0]].params.ind.empty())
2332 "A cylinder pull group is not supported when using absolute "
2336 const auto& group0 = pull->group[coord.params.group[0]];
2337 coord.dynamicGroup0 = std::make_unique<pull_group_work_t>(
2338 group0.params, group0.atomSet, pull->params.bSetPbcRefToPrevStepCOM, maxNumThreads);
2342 pull->comSums.resize(maxNumThreads);
2347 /* Use a sub-communicator when we have more than 32 ranks, but not
2348 * when we have an external pull potential, since then the external
2349 * potential provider expects each rank to have the coordinate.
2351 comm->bParticipateAll = (cr == nullptr || !DOMAINDECOMP(cr) || cr->dd->nnodes <= 32
2352 || pull->numCoordinatesWithExternalPotential > 0
2353 || getenv("GMX_PULL_PARTICIPATE_ALL") != nullptr);
2354 /* This sub-commicator is not used with comm->bParticipateAll,
2355 * so we can always initialize it to NULL.
2357 comm->mpi_comm_com = MPI_COMM_NULL;
2358 comm->nparticipate = 0;
2359 comm->isMasterRank = (cr == nullptr || MASTER(cr));
2361 /* No MPI: 1 rank: all ranks pull */
2362 comm->bParticipateAll = TRUE;
2363 comm->isMasterRank = true;
2365 comm->bParticipate = comm->bParticipateAll;
2366 comm->setup_count = 0;
2367 comm->must_count = 0;
2369 if (!comm->bParticipateAll && fplog != nullptr)
2371 fprintf(fplog, "Will use a sub-communicator for pull communication\n");
2374 comm->pbcAtomBuffer.resize(pull->group.size());
2375 comm->comBuffer.resize(pull->group.size() * c_comBufferStride);
2376 if (pull->bCylinder)
2378 comm->cylinderBuffer.resize(pull->coord.size() * c_cylinderBufferStride);
2381 /* We still need to initialize the PBC reference coordinates */
2382 pull->bSetPBCatoms = TRUE;
2384 pull->out_x = nullptr;
2385 pull->out_f = nullptr;
2390 static void destroy_pull(struct pull_t* pull)
2393 if (pull->comm.mpi_comm_com != MPI_COMM_NULL)
2395 MPI_Comm_free(&pull->comm.mpi_comm_com);
2402 void preparePrevStepPullCom(const t_inputrec* ir,
2404 gmx::ArrayRef<const real> masses,
2406 const t_state* state_global,
2407 const t_commrec* cr,
2408 bool startingFromCheckpoint)
2410 if (!ir->pull || !ir->pull->bSetPbcRefToPrevStepCOM)
2414 allocStatePrevStepPullCom(state, pull_work);
2415 if (startingFromCheckpoint)
2419 state->pull_com_prev_step = state_global->pull_com_prev_step;
2423 /* Only the master rank has the checkpointed COM from the previous step */
2424 gmx_bcast(sizeof(double) * state->pull_com_prev_step.size(),
2425 &state->pull_com_prev_step[0],
2426 cr->mpi_comm_mygroup);
2428 setPrevStepPullComFromState(pull_work, state);
2433 set_pbc(&pbc, ir->pbcType, state->box);
2434 initPullComFromPrevStep(
2435 cr, pull_work, masses, &pbc, state->x.arrayRefWithPadding().unpaddedArrayRef());
2436 updatePrevStepPullCom(pull_work, state);
2440 void finish_pull(struct pull_t* pull)
2442 check_external_potential_registration(pull);
2446 gmx_fio_fclose(pull->out_x);
2450 gmx_fio_fclose(pull->out_f);
2456 bool pull_have_potential(const pull_t& pull)
2458 return pull.bPotential;
2461 bool pull_have_constraint(const pull_t& pull)
2463 return pull.bConstraint;
2466 bool pull_have_constraint(const pull_params_t& pullParameters)
2468 for (int c = 0; c < pullParameters.ncoord; c++)
2470 if (pullParameters.coord[c].eType == PullingAlgorithm::Constraint)