2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2009,2010,2011,2012,2013 by the GROMACS development team.
5 * Copyright (c) 2014,2015,2016,2017,2018 by the GROMACS development team.
6 * Copyright (c) 2019,2020, by the GROMACS development team, led by
7 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
8 * and including many others, as listed in the AUTHORS file in the
9 * top-level source directory and at http://www.gromacs.org.
11 * GROMACS is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public License
13 * as published by the Free Software Foundation; either version 2.1
14 * of the License, or (at your option) any later version.
16 * GROMACS is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public
22 * License along with GROMACS; if not, see
23 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
24 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 * If you want to redistribute modifications to GROMACS, please
27 * consider that scientific software is very special. Version
28 * control is crucial - bugs must be traceable. We will be happy to
29 * consider code for inclusion in the official distribution, but
30 * derived work must not be called official GROMACS. Details are found
31 * in the README & COPYING files - if they are missing, get the
32 * official version at http://www.gromacs.org.
34 * To help us fund GROMACS development, we humbly ask that you cite
35 * the research papers on the package. Check out http://www.gromacs.org.
39 * Implements functions in indexutil.h.
41 * \author Teemu Murtola <teemu.murtola@gmail.com>
42 * \ingroup module_selection
46 #include "indexutil.h"
56 #include "gromacs/topology/block.h"
57 #include "gromacs/topology/index.h"
58 #include "gromacs/topology/mtop_lookup.h"
59 #include "gromacs/topology/mtop_util.h"
60 #include "gromacs/topology/topology.h"
61 #include "gromacs/utility/exceptions.h"
62 #include "gromacs/utility/gmxassert.h"
63 #include "gromacs/utility/smalloc.h"
64 #include "gromacs/utility/stringutil.h"
65 #include "gromacs/utility/textwriter.h"
70 IndexGroupsAndNames::IndexGroupsAndNames(const t_blocka
& indexGroup
, ArrayRef
<char const* const> groupNames
) :
71 indexGroup_
{ indexGroup
}
73 std::copy(groupNames
.begin(), groupNames
.end(), std::back_inserter(groupNames_
));
74 GMX_ASSERT(indexGroup_
.nr
== ssize(groupNames
),
75 "Number of groups must match number of group names.");
78 bool IndexGroupsAndNames::containsGroupName(const std::string
& groupName
) const
81 std::begin(groupNames_
), std::end(groupNames_
),
82 [&groupName
](const std::string
& name
) { return equalCaseInsensitive(groupName
, name
); });
85 std::vector
<index
> IndexGroupsAndNames::indices(const std::string
& groupName
) const
87 if (!containsGroupName(groupName
))
90 InconsistentInputError(
91 std::string("Group ") + groupName
92 + " referenced in the .mdp file was not found in the index file.\n"
93 "Group names must match either [moleculetype] names or custom index "
95 "names, in which case you must supply an index file to the '-n' option\n"
98 const auto groupNamePosition
= std::find_if(
99 std::begin(groupNames_
), std::end(groupNames_
),
100 [&groupName
](const std::string
& name
) { return equalCaseInsensitive(groupName
, name
); });
101 const auto groupIndex
= std::distance(std::begin(groupNames_
), groupNamePosition
);
102 const auto groupSize
= indexGroup_
.index
[groupIndex
+ 1] - indexGroup_
.index
[groupIndex
];
103 std::vector
<index
> groupIndices(groupSize
);
104 const auto startingIndex
= indexGroup_
.index
[groupIndex
];
105 std::iota(std::begin(groupIndices
), std::end(groupIndices
), startingIndex
);
106 std::transform(std::begin(groupIndices
), std::end(groupIndices
), std::begin(groupIndices
),
107 [blockLookup
= indexGroup_
.a
](auto i
) { return blockLookup
[i
]; });
113 /********************************************************************
114 * gmx_ana_indexgrps_t functions
115 ********************************************************************/
118 * Stores a set of index groups.
120 struct gmx_ana_indexgrps_t
122 //! Initializes an empty set of groups.
123 explicit gmx_ana_indexgrps_t(int nr
) : g(nr
) { names
.reserve(nr
); }
124 ~gmx_ana_indexgrps_t()
126 for (auto& indexGrp
: g
)
128 gmx_ana_index_deinit(&indexGrp
);
133 /** Array of index groups. */
134 std::vector
<gmx_ana_index_t
> g
;
136 std::vector
<std::string
> names
;
140 * \param[out] g Index group structure.
141 * \param[in] top Topology structure.
142 * \param[in] fnm File name for the index file.
143 * Memory is automatically allocated.
145 * One or both of \p top or \p fnm can be NULL.
146 * If \p top is NULL, an index file is required and the groups are read
147 * from the file (uses Gromacs routine init_index()).
148 * If \p fnm is NULL, default groups are constructed based on the
149 * topology (uses Gromacs routine analyse()).
150 * If both are null, the index group structure is initialized empty.
152 void gmx_ana_indexgrps_init(gmx_ana_indexgrps_t
** g
, gmx_mtop_t
* top
, const char* fnm
)
154 t_blocka
* block
= nullptr;
155 char** names
= nullptr;
159 block
= init_index(fnm
, &names
);
163 block
= new_blocka();
164 // TODO: Propagate mtop further.
165 t_atoms atoms
= gmx_mtop_global_atoms(top
);
166 analyse(&atoms
, block
, &names
, FALSE
, FALSE
);
171 *g
= new gmx_ana_indexgrps_t(0);
177 *g
= new gmx_ana_indexgrps_t(block
->nr
);
178 for (int i
= 0; i
< block
->nr
; ++i
)
180 gmx_ana_index_t
* grp
= &(*g
)->g
[i
];
182 grp
->isize
= block
->index
[i
+ 1] - block
->index
[i
];
183 snew(grp
->index
, grp
->isize
);
184 for (int j
= 0; j
< grp
->isize
; ++j
)
186 grp
->index
[j
] = block
->a
[block
->index
[i
] + j
];
188 grp
->nalloc_index
= grp
->isize
;
189 (*g
)->names
.emplace_back(names
[i
]);
194 for (int i
= 0; i
< block
->nr
; ++i
)
203 for (int i
= 0; i
< block
->nr
; ++i
)
213 * \param[in] g Index groups structure.
215 * The pointer \p g is invalid after the call.
217 void gmx_ana_indexgrps_free(gmx_ana_indexgrps_t
* g
)
224 * \param[out] dest Output structure.
225 * \param[out] destName Receives the name of the group if found.
226 * \param[in] src Input index groups.
227 * \param[in] n Number of the group to extract.
228 * \returns true if \p n is a valid group in \p src, false otherwise.
230 bool gmx_ana_indexgrps_extract(gmx_ana_index_t
* dest
, std::string
* destName
, gmx_ana_indexgrps_t
* src
, int n
)
233 if (n
< 0 || n
>= gmx::index(src
->g
.size()))
239 if (destName
!= nullptr)
241 *destName
= src
->names
[n
];
243 gmx_ana_index_copy(dest
, &src
->g
[n
], true);
248 * \param[out] dest Output structure.
249 * \param[out] destName Receives the name of the group if found.
250 * \param[in] src Input index groups.
251 * \param[in] name Name (or part of the name) of the group to extract.
252 * \returns true if \p name is a valid group in \p src, false otherwise.
254 * Uses the Gromacs routine find_group() to find the actual group;
255 * the comparison is case-insensitive.
257 bool gmx_ana_indexgrps_find(gmx_ana_index_t
* dest
, std::string
* destName
, gmx_ana_indexgrps_t
* src
, const char* name
)
262 snew(names
, src
->g
.size());
263 for (size_t i
= 0; i
< src
->g
.size(); ++i
)
265 names
[i
] = src
->names
[i
].c_str();
267 int n
= find_group(const_cast<char*>(name
), src
->g
.size(), const_cast<char**>(names
));
275 return gmx_ana_indexgrps_extract(dest
, destName
, src
, n
);
279 * \param[in] writer Writer to use for output.
280 * \param[in] g Index groups to print.
281 * \param[in] maxn Maximum number of indices to print
282 * (-1 = print all, 0 = print only names).
284 void gmx_ana_indexgrps_print(gmx::TextWriter
* writer
, gmx_ana_indexgrps_t
* g
, int maxn
)
286 for (gmx::index i
= 0; i
< gmx::ssize(g
->g
); ++i
)
288 writer
->writeString(gmx::formatString(" Group %2zd \"%s\" ", i
, g
->names
[i
].c_str()));
289 gmx_ana_index_dump(writer
, &g
->g
[i
], maxn
);
293 /********************************************************************
294 * gmx_ana_index_t functions
295 ********************************************************************/
298 * \param[in,out] g Index group structure.
299 * \param[in] isize Maximum number of atoms to reserve space for.
301 void gmx_ana_index_reserve(gmx_ana_index_t
* g
, int isize
)
303 if (g
->nalloc_index
< isize
)
305 srenew(g
->index
, isize
);
306 g
->nalloc_index
= isize
;
311 * \param[in,out] g Index group structure.
313 * Resizes the memory allocated for holding the indices such that the
314 * current contents fit.
316 void gmx_ana_index_squeeze(gmx_ana_index_t
* g
)
318 srenew(g
->index
, g
->isize
);
319 g
->nalloc_index
= g
->isize
;
323 * \param[out] g Output structure.
325 * Any contents of \p g are discarded without freeing.
327 void gmx_ana_index_clear(gmx_ana_index_t
* g
)
335 * \param[out] g Output structure.
336 * \param[in] isize Number of atoms in the new group.
337 * \param[in] index Array of \p isize atoms (can be NULL if \p isize is 0).
338 * \param[in] nalloc Number of elements allocated for \p index
339 * (if 0, \p index is not freed in gmx_ana_index_deinit())
341 * No copy if \p index is made.
343 void gmx_ana_index_set(gmx_ana_index_t
* g
, int isize
, int* index
, int nalloc
)
347 g
->nalloc_index
= nalloc
;
351 * \param[out] g Output structure.
352 * \param[in] natoms Number of atoms.
354 void gmx_ana_index_init_simple(gmx_ana_index_t
* g
, int natoms
)
359 snew(g
->index
, natoms
);
360 for (i
= 0; i
< natoms
; ++i
)
364 g
->nalloc_index
= natoms
;
368 * \param[in] g Index group structure.
370 * The pointer \p g is not freed.
372 void gmx_ana_index_deinit(gmx_ana_index_t
* g
)
374 if (g
->nalloc_index
> 0)
378 gmx_ana_index_clear(g
);
382 * \param[out] dest Destination index group.
383 * \param[in] src Source index group.
384 * \param[in] bAlloc If true, memory is allocated at \p dest; otherwise,
385 * it is assumed that enough memory has been allocated for index.
387 void gmx_ana_index_copy(gmx_ana_index_t
* dest
, gmx_ana_index_t
* src
, bool bAlloc
)
389 dest
->isize
= src
->isize
;
392 snew(dest
->index
, dest
->isize
);
393 dest
->nalloc_index
= dest
->isize
;
397 std::memcpy(dest
->index
, src
->index
, dest
->isize
* sizeof(*dest
->index
));
402 * \param[in] writer Writer to use for output.
403 * \param[in] g Index group to print.
404 * \param[in] maxn Maximum number of indices to print (-1 = print all).
406 void gmx_ana_index_dump(gmx::TextWriter
* writer
, gmx_ana_index_t
* g
, int maxn
)
408 writer
->writeString(gmx::formatString("(%d atoms)", g
->isize
));
411 writer
->writeString(":");
413 if (maxn
>= 0 && n
> maxn
)
417 for (int j
= 0; j
< n
; ++j
)
419 writer
->writeString(gmx::formatString(" %d", g
->index
[j
] + 1));
423 writer
->writeString(" ...");
426 writer
->ensureLineBreak();
429 int gmx_ana_index_get_max_index(gmx_ana_index_t
* g
)
437 return *std::max_element(g
->index
, g
->index
+ g
->isize
);
442 * \param[in] g Index group to check.
443 * \returns true if the index group is sorted and has no duplicates,
446 bool gmx_ana_index_check_sorted(gmx_ana_index_t
* g
)
450 for (i
= 0; i
< g
->isize
- 1; ++i
)
452 if (g
->index
[i
+ 1] <= g
->index
[i
])
460 bool gmx_ana_index_check_range(gmx_ana_index_t
* g
, int natoms
)
462 for (int i
= 0; i
< g
->isize
; ++i
)
464 if (g
->index
[i
] < 0 || g
->index
[i
] >= natoms
)
472 /********************************************************************
474 ********************************************************************/
477 * \param[in,out] g Index group to be sorted.
479 void gmx_ana_index_sort(gmx_ana_index_t
* g
)
481 std::sort(g
->index
, g
->index
+ g
->isize
);
484 void gmx_ana_index_remove_duplicates(gmx_ana_index_t
* g
)
487 for (int i
= 0; i
< g
->isize
; ++i
)
489 if (i
== 0 || g
->index
[i
- 1] != g
->index
[i
])
491 g
->index
[j
] = g
->index
[i
];
499 * \param[in] a Index group to check.
500 * \param[in] b Index group to check.
501 * \returns true if \p a and \p b are equal, false otherwise.
503 bool gmx_ana_index_equals(gmx_ana_index_t
* a
, gmx_ana_index_t
* b
)
507 if (a
->isize
!= b
->isize
)
511 for (i
= 0; i
< a
->isize
; ++i
)
513 if (a
->index
[i
] != b
->index
[i
])
522 * \param[in] a Index group to check against.
523 * \param[in] b Index group to check.
524 * \returns true if \p b is contained in \p a,
527 * If the elements are not in the same order in both groups, the function
528 * fails. However, the groups do not need to be sorted.
530 bool gmx_ana_index_contains(gmx_ana_index_t
* a
, gmx_ana_index_t
* b
)
534 for (i
= j
= 0; j
< b
->isize
; ++i
, ++j
)
536 while (i
< a
->isize
&& a
->index
[i
] != b
->index
[j
])
549 * \param[out] dest Output index group (the intersection of \p a and \p b).
550 * \param[in] a First index group.
551 * \param[in] b Second index group.
553 * \p dest can be the same as \p a or \p b.
555 void gmx_ana_index_intersection(gmx_ana_index_t
* dest
, gmx_ana_index_t
* a
, gmx_ana_index_t
* b
)
559 for (i
= j
= k
= 0; i
< a
->isize
&& j
< b
->isize
; ++i
)
561 while (j
< b
->isize
&& b
->index
[j
] < a
->index
[i
])
565 if (j
< b
->isize
&& b
->index
[j
] == a
->index
[i
])
567 dest
->index
[k
++] = b
->index
[j
++];
574 * \param[out] dest Output index group (the difference \p a - \p b).
575 * \param[in] a First index group.
576 * \param[in] b Second index group.
578 * \p dest can equal \p a, but not \p b.
580 void gmx_ana_index_difference(gmx_ana_index_t
* dest
, gmx_ana_index_t
* a
, gmx_ana_index_t
* b
)
584 for (i
= j
= k
= 0; i
< a
->isize
; ++i
)
586 while (j
< b
->isize
&& b
->index
[j
] < a
->index
[i
])
590 if (j
== b
->isize
|| b
->index
[j
] != a
->index
[i
])
592 dest
->index
[k
++] = a
->index
[i
];
599 * \param[in] a First index group.
600 * \param[in] b Second index group.
601 * \returns Size of the difference \p a - \p b.
603 int gmx_ana_index_difference_size(gmx_ana_index_t
* a
, gmx_ana_index_t
* b
)
607 for (i
= j
= k
= 0; i
< a
->isize
; ++i
)
609 while (j
< b
->isize
&& b
->index
[j
] < a
->index
[i
])
613 if (j
== b
->isize
|| b
->index
[j
] != a
->index
[i
])
622 * \param[out] dest1 Output group 1 (will equal \p g).
623 * \param[out] dest2 Output group 2 (will equal \p src - \p g).
624 * \param[in] src Group to be partitioned.
625 * \param[in] g One partition.
627 * \pre \p g is a subset of \p src and both sets are sorted
628 * \pre \p dest1 has allocated storage to store \p src
629 * \post \p dest1 == \p g
630 * \post \p dest2 == \p src - \p g
632 * No storage should be allocated for \p dest2; after the call,
633 * \p dest2->index points to the memory allocated for \p dest1
634 * (to a part that is not used by \p dest1).
636 * The calculation can be performed in-place by setting \p dest1 equal to
639 void gmx_ana_index_partition(gmx_ana_index_t
* dest1
, gmx_ana_index_t
* dest2
, gmx_ana_index_t
* src
, gmx_ana_index_t
* g
)
643 dest2
->index
= dest1
->index
+ g
->isize
;
644 dest2
->isize
= src
->isize
- g
->isize
;
645 for (i
= g
->isize
- 1, j
= src
->isize
- 1, k
= dest2
->isize
- 1; i
>= 0; --i
, --j
)
647 while (j
>= 0 && src
->index
[j
] != g
->index
[i
])
649 dest2
->index
[k
--] = src
->index
[j
--];
654 dest2
->index
[k
--] = src
->index
[j
--];
656 gmx_ana_index_copy(dest1
, g
, false);
660 * \param[out] dest Output index group (the union of \p a and \p b).
661 * \param[in] a First index group.
662 * \param[in] b Second index group.
664 * \p a and \p b can have common items.
665 * \p dest can equal \p a or \p b.
667 * \see gmx_ana_index_merge()
669 void gmx_ana_index_union(gmx_ana_index_t
* dest
, gmx_ana_index_t
* a
, gmx_ana_index_t
* b
)
674 dsize
= gmx_ana_index_difference_size(b
, a
);
677 dest
->isize
= a
->isize
+ dsize
;
678 for (k
= dest
->isize
- 1; k
>= 0; k
--)
680 if (i
< 0 || (j
>= 0 && a
->index
[i
] < b
->index
[j
]))
682 dest
->index
[k
] = b
->index
[j
--];
686 if (j
>= 0 && a
->index
[i
] == b
->index
[j
])
690 dest
->index
[k
] = a
->index
[i
--];
695 void gmx_ana_index_union_unsorted(gmx_ana_index_t
* dest
, gmx_ana_index_t
* a
, gmx_ana_index_t
* b
)
697 if (gmx_ana_index_check_sorted(b
))
699 gmx_ana_index_union(dest
, a
, b
);
704 gmx_ana_index_copy(&tmp
, b
, true);
705 gmx_ana_index_sort(&tmp
);
706 gmx_ana_index_remove_duplicates(&tmp
);
707 gmx_ana_index_union(dest
, a
, &tmp
);
708 gmx_ana_index_deinit(&tmp
);
713 * \param[out] dest Output index group (the union of \p a and \p b).
714 * \param[in] a First index group.
715 * \param[in] b Second index group.
717 * \p a and \p b should not have common items.
718 * \p dest can equal \p a or \p b.
720 * \see gmx_ana_index_union()
722 void gmx_ana_index_merge(gmx_ana_index_t
* dest
, gmx_ana_index_t
* a
, gmx_ana_index_t
* b
)
728 dest
->isize
= a
->isize
+ b
->isize
;
729 for (k
= dest
->isize
- 1; k
>= 0; k
--)
731 if (i
< 0 || (j
>= 0 && a
->index
[i
] < b
->index
[j
]))
733 dest
->index
[k
] = b
->index
[j
--];
737 dest
->index
[k
] = a
->index
[i
--];
742 /********************************************************************
743 * gmx_ana_indexmap_t and related things
744 ********************************************************************/
747 * Helper for splitting a sequence of atom indices into groups.
749 * \param[in] atomIndex Index of the next atom in the sequence.
750 * \param[in] top Topology structure.
751 * \param[in] type Type of group to split into.
752 * \param[in,out] id Variable to receive the next group id.
753 * \returns `true` if \p atomIndex starts a new group in the sequence, i.e.,
754 * if \p *id was changed.
756 * \p *id should be initialized to `-1` before first call of this function, and
757 * then each atom index in the sequence passed to the function in turn.
759 * \ingroup module_selection
761 static bool next_group_index(int atomIndex
, const gmx_mtop_t
* top
, e_index_t type
, int* id
)
766 case INDEX_ATOM
: *id
= atomIndex
; break;
769 int resind
, molb
= 0;
770 mtopGetAtomAndResidueName(top
, atomIndex
, &molb
, nullptr, nullptr, nullptr, &resind
);
777 *id
= mtopGetMoleculeIndex(top
, atomIndex
, &molb
);
781 case INDEX_ALL
: *id
= 0; break;
787 * \param[in,out] t Output block.
788 * \param[in] top Topology structure
789 * (only used if \p type is \ref INDEX_RES or \ref INDEX_MOL, can be NULL
791 * \param[in] g Index group
792 * (can be NULL if \p type is \ref INDEX_UNKNOWN).
793 * \param[in] type Type of partitioning to make.
794 * \param[in] bComplete
795 * If true, the index group is expanded to include any residue/molecule
796 * (depending on \p type) that is partially contained in the group.
797 * If \p type is not INDEX_RES or INDEX_MOL, this has no effect.
799 * \p m should have been initialized somehow (calloc() is enough).
800 * \p g should be sorted.
802 void gmx_ana_index_make_block(t_blocka
* t
, const gmx_mtop_t
* top
, gmx_ana_index_t
* g
, e_index_t type
, bool bComplete
)
804 if (type
== INDEX_UNKNOWN
)
818 // TODO: Check callers and either check these there as well, or turn these
820 GMX_RELEASE_ASSERT(top
!= nullptr || (type
!= INDEX_RES
&& type
!= INDEX_MOL
),
821 "Topology must be provided for residue or molecule blocks");
822 GMX_RELEASE_ASSERT(type
!= INDEX_MOL
|| top
->haveMoleculeIndices
,
823 "Molecule information must be present for molecule blocks");
825 /* bComplete only does something for INDEX_RES or INDEX_MOL, so turn it
827 if (type
!= INDEX_RES
&& type
!= INDEX_MOL
)
831 /* Allocate memory for the atom array and fill it unless we are using
836 /* We may allocate some extra memory here because we don't know in
837 * advance how much will be needed. */
838 if (t
->nalloc_a
< top
->natoms
)
840 srenew(t
->a
, top
->natoms
);
841 t
->nalloc_a
= top
->natoms
;
847 if (t
->nalloc_a
< g
->isize
)
849 srenew(t
->a
, g
->isize
);
850 t
->nalloc_a
= g
->isize
;
854 std::memcpy(t
->a
, g
->index
, g
->isize
* sizeof(*(t
->a
)));
858 /* Allocate memory for the block index. We don't know in advance
859 * how much will be needed, so we allocate some extra and free it in the
861 if (t
->nalloc_index
< g
->isize
+ 1)
863 srenew(t
->index
, g
->isize
+ 1);
864 t
->nalloc_index
= g
->isize
+ 1;
870 for (int i
= 0; i
< g
->isize
; ++i
)
872 const int ai
= g
->index
[i
];
873 /* Find the ID number of the atom/residue/molecule corresponding to
875 if (next_group_index(ai
, top
, type
, &id
))
877 /* If this is the first atom in a new block, initialize the block. */
880 /* For completion, we first set the start of the block. */
881 t
->index
[t
->nr
++] = t
->nra
;
882 /* And then we find all the atoms that should be included. */
888 mtopGetMolblockIndex(top
, ai
, &molb
, &molnr
, &atnr_mol
);
889 const t_atoms
& mol_atoms
= top
->moltype
[top
->molblock
[molb
].type
].atoms
;
890 int last_atom
= atnr_mol
+ 1;
891 const int currentResid
= mol_atoms
.atom
[atnr_mol
].resind
;
892 while (last_atom
< mol_atoms
.nr
&& mol_atoms
.atom
[last_atom
].resind
== currentResid
)
896 int first_atom
= atnr_mol
- 1;
897 while (first_atom
>= 0 && mol_atoms
.atom
[first_atom
].resind
== currentResid
)
901 const MoleculeBlockIndices
& molBlock
= top
->moleculeBlockIndices
[molb
];
902 int first_mol_atom
= molBlock
.globalAtomStart
;
903 first_mol_atom
+= molnr
* molBlock
.numAtomsPerMolecule
;
904 first_atom
= first_mol_atom
+ first_atom
+ 1;
905 last_atom
= first_mol_atom
+ last_atom
- 1;
906 for (int j
= first_atom
; j
<= last_atom
; ++j
)
915 mtopGetMolblockIndex(top
, ai
, &molb
, &molnr
, &atnr_mol
);
916 const MoleculeBlockIndices
& blockIndices
= top
->moleculeBlockIndices
[molb
];
917 const int atomStart
= blockIndices
.globalAtomStart
918 + (id
- blockIndices
.moleculeIndexStart
)
919 * blockIndices
.numAtomsPerMolecule
;
920 for (int j
= 0; j
< blockIndices
.numAtomsPerMolecule
; ++j
)
922 t
->a
[t
->nra
++] = atomStart
+ j
;
926 default: /* Should not be reached */
927 GMX_RELEASE_ASSERT(false, "Unreachable code was reached");
933 /* If not using completion, simply store the start of the block. */
934 t
->index
[t
->nr
++] = i
;
938 /* Set the end of the last block */
939 t
->index
[t
->nr
] = t
->nra
;
940 /* Free any unnecessary memory */
941 srenew(t
->index
, t
->nr
+ 1);
942 t
->nalloc_index
= t
->nr
+ 1;
945 srenew(t
->a
, t
->nra
);
946 t
->nalloc_a
= t
->nra
;
951 * \param[in] g Index group to check.
952 * \param[in] b Block data to check against.
953 * \returns true if \p g consists of one or more complete blocks from \p b,
956 * The atoms in \p g are assumed to be sorted.
958 bool gmx_ana_index_has_full_blocks(const gmx_ana_index_t
* g
, const gmx::RangePartitioning
* b
)
963 /* Each round in the loop matches one block */
966 /* Find the block that begins with the first unmatched atom */
967 while (bi
< b
->numBlocks() && *b
->block(bi
).begin() != g
->index
[i
])
971 /* If not found, or if too large, return */
972 if (bi
== b
->numBlocks() || i
+ b
->block(bi
).size() > g
->isize
)
976 /* Check that the block matches the index */
977 for (j
= *b
->block(bi
).begin(); j
< *b
->block(bi
).end(); ++j
, ++i
)
979 if (g
->index
[i
] != j
)
984 /* Move the search to the next block */
991 * \param[in] g Index group to check.
992 * \param[in] b Block data to check against.
993 * \returns true if \p g consists of one or more complete blocks from \p b,
996 * The atoms in \p g and \p b->a are assumed to be in the same order.
998 bool gmx_ana_index_has_full_ablocks(gmx_ana_index_t
* g
, t_blocka
* b
)
1003 /* Each round in the loop matches one block */
1004 while (i
< g
->isize
)
1006 /* Find the block that begins with the first unmatched atom */
1007 while (bi
< b
->nr
&& b
->a
[b
->index
[bi
]] != g
->index
[i
])
1011 /* If not found, or if too large, return */
1012 if (bi
== b
->nr
|| i
+ b
->index
[bi
+ 1] - b
->index
[bi
] > g
->isize
)
1016 /* Check that the block matches the index */
1017 for (j
= b
->index
[bi
]; j
< b
->index
[bi
+ 1]; ++j
, ++i
)
1019 if (b
->a
[j
] != g
->index
[i
])
1024 /* Move the search to the next block */
1031 * \brief Returns if an atom is at a residue boundary.
1033 * \param[in] top Topology data.
1034 * \param[in] a Atom index to check, should be -1 <= \p a < top->natoms.
1035 * \param[in,out] molb The molecule block of atom a
1036 * \returns true if atoms \p a and \p a + 1 are in different residues, false otherwise.
1038 static bool is_at_residue_boundary(const gmx_mtop_t
* top
, int a
, int* molb
)
1040 if (a
== -1 || a
+ 1 == top
->natoms
)
1045 mtopGetAtomAndResidueName(top
, a
, molb
, nullptr, nullptr, nullptr, &resindA
);
1047 mtopGetAtomAndResidueName(top
, a
+ 1, molb
, nullptr, nullptr, nullptr, &resindAPlusOne
);
1048 return resindAPlusOne
!= resindA
;
1052 * \param[in] g Index group to check.
1053 * \param[in] type Block data to check against.
1054 * \param[in] top Topology data.
1055 * \returns true if \p g consists of one or more complete elements of type
1056 * \p type, false otherwise.
1058 * \p g is assumed to be sorted, otherwise may return false negatives.
1060 * If \p type is \ref INDEX_ATOM, the return value is always true.
1061 * If \p type is \ref INDEX_UNKNOWN or \ref INDEX_ALL, the return value is
1064 bool gmx_ana_index_has_complete_elems(gmx_ana_index_t
* g
, e_index_t type
, const gmx_mtop_t
* top
)
1071 // TODO: Consider whether unsorted groups need to be supported better.
1075 case INDEX_ALL
: return false;
1077 case INDEX_ATOM
: return true;
1083 for (int i
= 0; i
< g
->isize
; ++i
)
1085 const int a
= g
->index
[i
];
1086 // Check if a is consecutive or on a residue boundary
1089 if (!is_at_residue_boundary(top
, aPrev
, &molb
))
1093 if (!is_at_residue_boundary(top
, a
- 1, &molb
))
1100 GMX_ASSERT(g
->isize
> 0, "We return above when isize=0");
1101 const int a
= g
->index
[g
->isize
- 1];
1102 if (!is_at_residue_boundary(top
, a
, &molb
))
1111 auto molecules
= gmx_mtop_molecules(*top
);
1112 return gmx_ana_index_has_full_blocks(g
, &molecules
);
1119 * \param[out] m Output structure.
1121 * Any contents of \p m are discarded without freeing.
1123 void gmx_ana_indexmap_clear(gmx_ana_indexmap_t
* m
)
1125 m
->type
= INDEX_UNKNOWN
;
1129 m
->mapb
.index
= nullptr;
1130 m
->mapb
.nalloc_index
= 0;
1132 m
->mapb
.a
= nullptr;
1133 m
->mapb
.nalloc_a
= 0;
1136 m
->b
.index
= nullptr;
1139 m
->b
.nalloc_index
= 0;
1145 * \param[in,out] m Mapping structure.
1146 * \param[in] nr Maximum number of blocks to reserve space for.
1147 * \param[in] isize Maximum number of atoms to reserve space for.
1149 void gmx_ana_indexmap_reserve(gmx_ana_indexmap_t
* m
, int nr
, int isize
)
1151 if (m
->mapb
.nalloc_index
< nr
+ 1)
1153 srenew(m
->refid
, nr
);
1154 srenew(m
->mapid
, nr
);
1155 srenew(m
->orgid
, nr
);
1156 srenew(m
->mapb
.index
, nr
+ 1);
1157 srenew(m
->b
.index
, nr
+ 1);
1158 m
->mapb
.nalloc_index
= nr
+ 1;
1159 m
->b
.nalloc_index
= nr
+ 1;
1161 if (m
->b
.nalloc_a
< isize
)
1163 srenew(m
->b
.a
, isize
);
1164 m
->b
.nalloc_a
= isize
;
1169 * \param[in,out] m Mapping structure to initialize.
1170 * \param[in] g Index group to map
1171 * (can be NULL if \p type is \ref INDEX_UNKNOWN).
1172 * \param[in] top Topology structure
1173 * (can be NULL if \p type is not \ref INDEX_RES or \ref INDEX_MOL).
1174 * \param[in] type Type of mapping to construct.
1176 * Initializes a new index group mapping.
1177 * The index group provided to gmx_ana_indexmap_update() should always be a
1178 * subset of the \p g given here.
1180 * \p m should have been initialized somehow (calloc() is enough).
1182 void gmx_ana_indexmap_init(gmx_ana_indexmap_t
* m
, gmx_ana_index_t
* g
, const gmx_mtop_t
* top
, e_index_t type
)
1185 gmx_ana_index_make_block(&m
->b
, top
, g
, type
, false);
1186 gmx_ana_indexmap_reserve(m
, m
->b
.nr
, m
->b
.nra
);
1188 for (int i
= 0; i
< m
->b
.nr
; ++i
)
1190 const int ii
= (type
== INDEX_UNKNOWN
? 0 : m
->b
.a
[m
->b
.index
[i
]]);
1191 next_group_index(ii
, top
, type
, &id
);
1196 m
->mapb
.nr
= m
->b
.nr
;
1197 m
->mapb
.nra
= m
->b
.nra
;
1199 std::memcpy(m
->mapb
.index
, m
->b
.index
, (m
->b
.nr
+ 1) * sizeof(*(m
->mapb
.index
)));
1203 int gmx_ana_indexmap_init_orgid_group(gmx_ana_indexmap_t
* m
, const gmx_mtop_t
* top
, e_index_t type
)
1205 GMX_RELEASE_ASSERT(m
->bStatic
,
1206 "Changing original IDs is not supported after starting "
1207 "to use the mapping");
1208 GMX_RELEASE_ASSERT(top
!= nullptr || (type
!= INDEX_RES
&& type
!= INDEX_MOL
),
1209 "Topology must be provided for residue or molecule blocks");
1210 // Check that all atoms in each block belong to the same group.
1211 // This is a separate loop for better error handling (no state is modified
1212 // if there is an error.
1213 if (type
== INDEX_RES
|| type
== INDEX_MOL
)
1216 for (int i
= 0; i
< m
->b
.nr
; ++i
)
1218 const int ii
= m
->b
.a
[m
->b
.index
[i
]];
1219 if (next_group_index(ii
, top
, type
, &id
))
1221 for (int j
= m
->b
.index
[i
] + 1; j
< m
->b
.index
[i
+ 1]; ++j
)
1223 if (next_group_index(m
->b
.a
[j
], top
, type
, &id
))
1225 std::string
message("Grouping into residues/molecules is ambiguous");
1226 GMX_THROW(gmx::InconsistentInputError(message
));
1232 // Do a second loop, where things are actually set.
1235 for (int i
= 0; i
< m
->b
.nr
; ++i
)
1237 const int ii
= (type
== INDEX_UNKNOWN
? 0 : m
->b
.a
[m
->b
.index
[i
]]);
1238 if (next_group_index(ii
, top
, type
, &id
))
1242 m
->mapid
[i
] = group
;
1243 m
->orgid
[i
] = group
;
1245 // Count also the last group.
1251 * \param[in,out] m Mapping structure to initialize.
1252 * \param[in] b Block information to use for data.
1254 * Frees some memory that is not necessary for static index group mappings.
1255 * Internal pointers are set to point to data in \p b; it is the responsibility
1256 * of the caller to ensure that the block information matches the contents of
1258 * After this function has been called, the index group provided to
1259 * gmx_ana_indexmap_update() should always be the same as \p g given here.
1261 * This function breaks modularity of the index group mapping interface in an
1262 * ugly way, but allows reducing memory usage of static selections by a
1263 * significant amount.
1265 void gmx_ana_indexmap_set_static(gmx_ana_indexmap_t
* m
, t_blocka
* b
)
1268 sfree(m
->mapb
.index
);
1271 m
->mapb
.nalloc_index
= 0;
1272 m
->mapb
.nalloc_a
= 0;
1273 m
->b
.nalloc_index
= 0;
1275 m
->mapid
= m
->orgid
;
1276 m
->mapb
.index
= b
->index
;
1278 m
->b
.index
= b
->index
;
1283 * \param[in,out] dest Destination data structure.
1284 * \param[in] src Source mapping.
1285 * \param[in] bFirst If true, memory is allocated for \p dest and a full
1286 * copy is made; otherwise, only variable parts are copied, and no memory
1289 * \p dest should have been initialized somehow (calloc() is enough).
1291 void gmx_ana_indexmap_copy(gmx_ana_indexmap_t
* dest
, gmx_ana_indexmap_t
* src
, bool bFirst
)
1295 gmx_ana_indexmap_reserve(dest
, src
->b
.nr
, src
->b
.nra
);
1296 dest
->type
= src
->type
;
1297 dest
->b
.nr
= src
->b
.nr
;
1298 dest
->b
.nra
= src
->b
.nra
;
1299 std::memcpy(dest
->orgid
, src
->orgid
, dest
->b
.nr
* sizeof(*dest
->orgid
));
1300 std::memcpy(dest
->b
.index
, src
->b
.index
, (dest
->b
.nr
+ 1) * sizeof(*dest
->b
.index
));
1301 if (dest
->b
.nra
> 0)
1303 std::memcpy(dest
->b
.a
, src
->b
.a
, dest
->b
.nra
* sizeof(*dest
->b
.a
));
1306 dest
->mapb
.nr
= src
->mapb
.nr
;
1307 dest
->mapb
.nra
= src
->mapb
.nra
;
1308 if (src
->mapb
.nalloc_a
> 0)
1312 snew(dest
->mapb
.a
, src
->mapb
.nalloc_a
);
1313 dest
->mapb
.nalloc_a
= src
->mapb
.nalloc_a
;
1315 std::memcpy(dest
->mapb
.a
, src
->mapb
.a
, dest
->mapb
.nra
* sizeof(*dest
->mapb
.a
));
1319 dest
->mapb
.a
= src
->mapb
.a
;
1321 std::memcpy(dest
->refid
, src
->refid
, dest
->mapb
.nr
* sizeof(*dest
->refid
));
1322 std::memcpy(dest
->mapid
, src
->mapid
, dest
->mapb
.nr
* sizeof(*dest
->mapid
));
1323 std::memcpy(dest
->mapb
.index
, src
->mapb
.index
, (dest
->mapb
.nr
+ 1) * sizeof(*dest
->mapb
.index
));
1324 dest
->bStatic
= src
->bStatic
;
1328 * Helper function to set the source atoms in an index map.
1330 * \param[in,out] m Mapping structure.
1331 * \param[in] isize Number of atoms in the \p index array.
1332 * \param[in] index List of atoms.
1334 static void set_atoms(gmx_ana_indexmap_t
* m
, int isize
, int* index
)
1336 m
->mapb
.nra
= isize
;
1337 if (m
->mapb
.nalloc_a
== 0)
1343 for (int i
= 0; i
< isize
; ++i
)
1345 m
->mapb
.a
[i
] = index
[i
];
1351 * \param[in,out] m Mapping structure.
1352 * \param[in] g Current index group.
1353 * \param[in] bMaskOnly true if the unused blocks should be masked with
1354 * -1 instead of removing them.
1356 * Updates the index group mapping with the new index group \p g.
1358 * \see gmx_ana_indexmap_t
1360 void gmx_ana_indexmap_update(gmx_ana_indexmap_t
* m
, gmx_ana_index_t
* g
, bool bMaskOnly
)
1364 /* Process the simple cases first */
1365 if (m
->type
== INDEX_UNKNOWN
&& m
->b
.nra
== 0)
1369 if (m
->type
== INDEX_ALL
)
1371 set_atoms(m
, g
->isize
, g
->index
);
1374 m
->mapb
.index
[1] = g
->isize
;
1378 /* Reset the reference IDs and mapping if necessary */
1379 const bool bToFull
= (g
->isize
== m
->b
.nra
);
1380 const bool bWasFull
= (m
->mapb
.nra
== m
->b
.nra
);
1381 if (bToFull
|| bMaskOnly
)
1385 for (bj
= 0; bj
< m
->b
.nr
; ++bj
)
1392 for (bj
= 0; bj
< m
->b
.nr
; ++bj
)
1394 m
->mapid
[bj
] = m
->orgid
[bj
];
1396 for (bj
= 0; bj
<= m
->b
.nr
; ++bj
)
1398 m
->mapb
.index
[bj
] = m
->b
.index
[bj
];
1401 set_atoms(m
, m
->b
.nra
, m
->b
.a
);
1402 m
->mapb
.nr
= m
->b
.nr
;
1404 /* Exit immediately if the group is static */
1413 for (i
= j
= bj
= 0; i
< g
->isize
; ++i
, ++j
)
1415 /* Find the next atom in the block */
1416 while (m
->b
.a
[j
] != g
->index
[i
])
1420 /* Mark blocks that did not contain any atoms */
1421 while (bj
< m
->b
.nr
&& m
->b
.index
[bj
+ 1] <= j
)
1423 m
->refid
[bj
++] = -1;
1425 /* Advance the block index if we have reached the next block */
1426 if (m
->b
.index
[bj
] <= j
)
1431 /* Mark the last blocks as not accessible */
1432 while (bj
< m
->b
.nr
)
1434 m
->refid
[bj
++] = -1;
1439 set_atoms(m
, g
->isize
, g
->index
);
1440 for (i
= j
= bi
= 0, bj
= -1; i
< g
->isize
; ++i
)
1442 /* Find the next atom in the block */
1443 while (m
->b
.a
[j
] != g
->index
[i
])
1447 /* If we have reached a new block, add it */
1448 if (m
->b
.index
[bj
+ 1] <= j
)
1450 /* Skip any blocks in between */
1451 while (bj
< m
->b
.nr
&& m
->b
.index
[bj
+ 1] <= j
)
1456 m
->mapid
[bi
] = m
->orgid
[bj
];
1457 m
->mapb
.index
[bi
] = i
;
1461 /* Update the number of blocks */
1462 m
->mapb
.index
[bi
] = g
->isize
;
1469 * \param[in,out] m Mapping structure to free.
1471 * All the memory allocated for the mapping structure is freed, and
1472 * the pointers set to NULL.
1473 * The pointer \p m is not freed.
1475 void gmx_ana_indexmap_deinit(gmx_ana_indexmap_t
* m
)
1478 if (m
->mapid
!= m
->orgid
)
1482 if (m
->mapb
.nalloc_index
> 0)
1484 sfree(m
->mapb
.index
);
1486 if (m
->mapb
.nalloc_a
> 0)
1491 if (m
->b
.nalloc_index
> 0)
1495 if (m
->b
.nalloc_a
> 0)
1499 gmx_ana_indexmap_clear(m
);