2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
37 * Implements functions in indexutil.h.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_selection
44 #include "indexutil.h"
53 #include "gromacs/topology/block.h"
54 #include "gromacs/topology/index.h"
55 #include "gromacs/topology/mtop_lookup.h"
56 #include "gromacs/topology/mtop_util.h"
57 #include "gromacs/topology/topology.h"
58 #include "gromacs/utility/exceptions.h"
59 #include "gromacs/utility/gmxassert.h"
60 #include "gromacs/utility/smalloc.h"
61 #include "gromacs/utility/stringutil.h"
62 #include "gromacs/utility/textwriter.h"
64 /********************************************************************
65 * gmx_ana_indexgrps_t functions
66 ********************************************************************/
69 * Stores a set of index groups.
71 struct gmx_ana_indexgrps_t
73 //! Initializes an empty set of groups.
74 explicit gmx_ana_indexgrps_t(int nr)
79 ~gmx_ana_indexgrps_t()
81 for (auto &indexGrp : g)
83 gmx_ana_index_deinit(&indexGrp);
88 /** Array of index groups. */
89 std::vector<gmx_ana_index_t> g;
91 std::vector<std::string> names;
95 * \param[out] g Index group structure.
96 * \param[in] top Topology structure.
97 * \param[in] fnm File name for the index file.
98 * Memory is automatically allocated.
100 * One or both of \p top or \p fnm can be NULL.
101 * If \p top is NULL, an index file is required and the groups are read
102 * from the file (uses Gromacs routine init_index()).
103 * If \p fnm is NULL, default groups are constructed based on the
104 * topology (uses Gromacs routine analyse()).
105 * If both are null, the index group structure is initialized empty.
108 gmx_ana_indexgrps_init(gmx_ana_indexgrps_t **g, gmx_mtop_t *top,
111 t_blocka *block = nullptr;
112 char **names = nullptr;
116 block = init_index(fnm, &names);
120 block = new_blocka();
121 // TODO: Propagate mtop further.
122 t_atoms atoms = gmx_mtop_global_atoms(top);
123 analyse(&atoms, block, &names, FALSE, FALSE);
128 *g = new gmx_ana_indexgrps_t(0);
134 *g = new gmx_ana_indexgrps_t(block->nr);
135 for (int i = 0; i < block->nr; ++i)
137 gmx_ana_index_t *grp = &(*g)->g[i];
139 grp->isize = block->index[i+1] - block->index[i];
140 snew(grp->index, grp->isize);
141 for (int j = 0; j < grp->isize; ++j)
143 grp->index[j] = block->a[block->index[i]+j];
145 grp->nalloc_index = grp->isize;
146 (*g)->names.emplace_back(names[i]);
151 for (int i = 0; i < block->nr; ++i)
160 for (int i = 0; i < block->nr; ++i)
170 * \param[in] g Index groups structure.
172 * The pointer \p g is invalid after the call.
175 gmx_ana_indexgrps_free(gmx_ana_indexgrps_t *g)
182 * \param[out] dest Output structure.
183 * \param[out] destName Receives the name of the group if found.
184 * \param[in] src Input index groups.
185 * \param[in] n Number of the group to extract.
186 * \returns true if \p n is a valid group in \p src, false otherwise.
189 gmx_ana_indexgrps_extract(gmx_ana_index_t *dest, std::string *destName,
190 gmx_ana_indexgrps_t *src, int n)
193 if (n < 0 || n >= gmx::index(src->g.size()))
199 if (destName != nullptr)
201 *destName = src->names[n];
203 gmx_ana_index_copy(dest, &src->g[n], true);
208 * \param[out] dest Output structure.
209 * \param[out] destName Receives the name of the group if found.
210 * \param[in] src Input index groups.
211 * \param[in] name Name (or part of the name) of the group to extract.
212 * \returns true if \p name is a valid group in \p src, false otherwise.
214 * Uses the Gromacs routine find_group() to find the actual group;
215 * the comparison is case-insensitive.
218 gmx_ana_indexgrps_find(gmx_ana_index_t *dest, std::string *destName,
219 gmx_ana_indexgrps_t *src,
225 snew(names, src->g.size());
226 for (size_t i = 0; i < src->g.size(); ++i)
228 names[i] = src->names[i].c_str();
230 int n = find_group(const_cast<char *>(name), src->g.size(),
231 const_cast<char **>(names));
239 return gmx_ana_indexgrps_extract(dest, destName, src, n);
243 * \param[in] writer Writer to use for output.
244 * \param[in] g Index groups to print.
245 * \param[in] maxn Maximum number of indices to print
246 * (-1 = print all, 0 = print only names).
249 gmx_ana_indexgrps_print(gmx::TextWriter *writer, gmx_ana_indexgrps_t *g, int maxn)
251 for (gmx::index i = 0; i < gmx::ssize(g->g); ++i)
253 writer->writeString(gmx::formatString(" Group %2zd \"%s\" ",
254 i, g->names[i].c_str()));
255 gmx_ana_index_dump(writer, &g->g[i], maxn);
259 /********************************************************************
260 * gmx_ana_index_t functions
261 ********************************************************************/
264 * \param[in,out] g Index group structure.
265 * \param[in] isize Maximum number of atoms to reserve space for.
268 gmx_ana_index_reserve(gmx_ana_index_t *g, int isize)
270 if (g->nalloc_index < isize)
272 srenew(g->index, isize);
273 g->nalloc_index = isize;
278 * \param[in,out] g Index group structure.
280 * Resizes the memory allocated for holding the indices such that the
281 * current contents fit.
284 gmx_ana_index_squeeze(gmx_ana_index_t *g)
286 srenew(g->index, g->isize);
287 g->nalloc_index = g->isize;
291 * \param[out] g Output structure.
293 * Any contents of \p g are discarded without freeing.
296 gmx_ana_index_clear(gmx_ana_index_t *g)
304 * \param[out] g Output structure.
305 * \param[in] isize Number of atoms in the new group.
306 * \param[in] index Array of \p isize atoms (can be NULL if \p isize is 0).
307 * \param[in] nalloc Number of elements allocated for \p index
308 * (if 0, \p index is not freed in gmx_ana_index_deinit())
310 * No copy if \p index is made.
313 gmx_ana_index_set(gmx_ana_index_t *g, int isize, int *index, int nalloc)
317 g->nalloc_index = nalloc;
321 * \param[out] g Output structure.
322 * \param[in] natoms Number of atoms.
325 gmx_ana_index_init_simple(gmx_ana_index_t *g, int natoms)
330 snew(g->index, natoms);
331 for (i = 0; i < natoms; ++i)
335 g->nalloc_index = natoms;
339 * \param[in] g Index group structure.
341 * The pointer \p g is not freed.
344 gmx_ana_index_deinit(gmx_ana_index_t *g)
346 if (g->nalloc_index > 0)
350 gmx_ana_index_clear(g);
354 * \param[out] dest Destination index group.
355 * \param[in] src Source index group.
356 * \param[in] bAlloc If true, memory is allocated at \p dest; otherwise,
357 * it is assumed that enough memory has been allocated for index.
360 gmx_ana_index_copy(gmx_ana_index_t *dest, gmx_ana_index_t *src, bool bAlloc)
362 dest->isize = src->isize;
365 snew(dest->index, dest->isize);
366 dest->nalloc_index = dest->isize;
370 std::memcpy(dest->index, src->index, dest->isize*sizeof(*dest->index));
375 * \param[in] writer Writer to use for output.
376 * \param[in] g Index group to print.
377 * \param[in] maxn Maximum number of indices to print (-1 = print all).
380 gmx_ana_index_dump(gmx::TextWriter *writer, gmx_ana_index_t *g, int maxn)
382 writer->writeString(gmx::formatString("(%d atoms)", g->isize));
385 writer->writeString(":");
387 if (maxn >= 0 && n > maxn)
391 for (int j = 0; j < n; ++j)
393 writer->writeString(gmx::formatString(" %d", g->index[j]+1));
397 writer->writeString(" ...");
400 writer->ensureLineBreak();
404 gmx_ana_index_get_max_index(gmx_ana_index_t *g)
412 return *std::max_element(g->index, g->index + g->isize);
417 * \param[in] g Index group to check.
418 * \returns true if the index group is sorted and has no duplicates,
422 gmx_ana_index_check_sorted(gmx_ana_index_t *g)
426 for (i = 0; i < g->isize-1; ++i)
428 if (g->index[i+1] <= g->index[i])
437 gmx_ana_index_check_range(gmx_ana_index_t *g, int natoms)
439 for (int i = 0; i < g->isize; ++i)
441 if (g->index[i] < 0 || g->index[i] >= natoms)
449 /********************************************************************
451 ********************************************************************/
454 * \param[in,out] g Index group to be sorted.
457 gmx_ana_index_sort(gmx_ana_index_t *g)
459 std::sort(g->index, g->index+g->isize);
463 gmx_ana_index_remove_duplicates(gmx_ana_index_t *g)
466 for (int i = 0; i < g->isize; ++i)
468 if (i == 0 || g->index[i-1] != g->index[i])
470 g->index[j] = g->index[i];
478 * \param[in] a Index group to check.
479 * \param[in] b Index group to check.
480 * \returns true if \p a and \p b are equal, false otherwise.
483 gmx_ana_index_equals(gmx_ana_index_t *a, gmx_ana_index_t *b)
487 if (a->isize != b->isize)
491 for (i = 0; i < a->isize; ++i)
493 if (a->index[i] != b->index[i])
502 * \param[in] a Index group to check against.
503 * \param[in] b Index group to check.
504 * \returns true if \p b is contained in \p a,
507 * If the elements are not in the same order in both groups, the function
508 * fails. However, the groups do not need to be sorted.
511 gmx_ana_index_contains(gmx_ana_index_t *a, gmx_ana_index_t *b)
515 for (i = j = 0; j < b->isize; ++i, ++j)
517 while (i < a->isize && a->index[i] != b->index[j])
530 * \param[out] dest Output index group (the intersection of \p a and \p b).
531 * \param[in] a First index group.
532 * \param[in] b Second index group.
534 * \p dest can be the same as \p a or \p b.
537 gmx_ana_index_intersection(gmx_ana_index_t *dest,
538 gmx_ana_index_t *a, gmx_ana_index_t *b)
542 for (i = j = k = 0; i < a->isize && j < b->isize; ++i)
544 while (j < b->isize && b->index[j] < a->index[i])
548 if (j < b->isize && b->index[j] == a->index[i])
550 dest->index[k++] = b->index[j++];
557 * \param[out] dest Output index group (the difference \p a - \p b).
558 * \param[in] a First index group.
559 * \param[in] b Second index group.
561 * \p dest can equal \p a, but not \p b.
564 gmx_ana_index_difference(gmx_ana_index_t *dest,
565 gmx_ana_index_t *a, gmx_ana_index_t *b)
569 for (i = j = k = 0; i < a->isize; ++i)
571 while (j < b->isize && b->index[j] < a->index[i])
575 if (j == b->isize || b->index[j] != a->index[i])
577 dest->index[k++] = a->index[i];
584 * \param[in] a First index group.
585 * \param[in] b Second index group.
586 * \returns Size of the difference \p a - \p b.
589 gmx_ana_index_difference_size(gmx_ana_index_t *a, gmx_ana_index_t *b)
593 for (i = j = k = 0; i < a->isize; ++i)
595 while (j < b->isize && b->index[j] < a->index[i])
599 if (j == b->isize || b->index[j] != a->index[i])
608 * \param[out] dest1 Output group 1 (will equal \p g).
609 * \param[out] dest2 Output group 2 (will equal \p src - \p g).
610 * \param[in] src Group to be partitioned.
611 * \param[in] g One partition.
613 * \pre \p g is a subset of \p src and both sets are sorted
614 * \pre \p dest1 has allocated storage to store \p src
615 * \post \p dest1 == \p g
616 * \post \p dest2 == \p src - \p g
618 * No storage should be allocated for \p dest2; after the call,
619 * \p dest2->index points to the memory allocated for \p dest1
620 * (to a part that is not used by \p dest1).
622 * The calculation can be performed in-place by setting \p dest1 equal to
626 gmx_ana_index_partition(gmx_ana_index_t *dest1, gmx_ana_index_t *dest2,
627 gmx_ana_index_t *src, gmx_ana_index_t *g)
631 dest2->index = dest1->index + g->isize;
632 dest2->isize = src->isize - g->isize;
633 for (i = g->isize-1, j = src->isize-1, k = dest2->isize-1; i >= 0; --i, --j)
635 while (j >= 0 && src->index[j] != g->index[i])
637 dest2->index[k--] = src->index[j--];
642 dest2->index[k--] = src->index[j--];
644 gmx_ana_index_copy(dest1, g, false);
648 * \param[out] dest Output index group (the union of \p a and \p b).
649 * \param[in] a First index group.
650 * \param[in] b Second index group.
652 * \p a and \p b can have common items.
653 * \p dest can equal \p a or \p b.
655 * \see gmx_ana_index_merge()
658 gmx_ana_index_union(gmx_ana_index_t *dest,
659 gmx_ana_index_t *a, gmx_ana_index_t *b)
664 dsize = gmx_ana_index_difference_size(b, a);
667 dest->isize = a->isize + dsize;
668 for (k = dest->isize - 1; k >= 0; k--)
670 if (i < 0 || (j >= 0 && a->index[i] < b->index[j]))
672 dest->index[k] = b->index[j--];
676 if (j >= 0 && a->index[i] == b->index[j])
680 dest->index[k] = a->index[i--];
686 gmx_ana_index_union_unsorted(gmx_ana_index_t *dest,
687 gmx_ana_index_t *a, gmx_ana_index_t *b)
689 if (gmx_ana_index_check_sorted(b))
691 gmx_ana_index_union(dest, a, b);
696 gmx_ana_index_copy(&tmp, b, true);
697 gmx_ana_index_sort(&tmp);
698 gmx_ana_index_remove_duplicates(&tmp);
699 gmx_ana_index_union(dest, a, &tmp);
700 gmx_ana_index_deinit(&tmp);
705 * \param[out] dest Output index group (the union of \p a and \p b).
706 * \param[in] a First index group.
707 * \param[in] b Second index group.
709 * \p a and \p b should not have common items.
710 * \p dest can equal \p a or \p b.
712 * \see gmx_ana_index_union()
715 gmx_ana_index_merge(gmx_ana_index_t *dest,
716 gmx_ana_index_t *a, gmx_ana_index_t *b)
722 dest->isize = a->isize + b->isize;
723 for (k = dest->isize - 1; k >= 0; k--)
725 if (i < 0 || (j >= 0 && a->index[i] < b->index[j]))
727 dest->index[k] = b->index[j--];
731 dest->index[k] = a->index[i--];
736 /********************************************************************
737 * gmx_ana_indexmap_t and related things
738 ********************************************************************/
741 * Helper for splitting a sequence of atom indices into groups.
743 * \param[in] atomIndex Index of the next atom in the sequence.
744 * \param[in] top Topology structure.
745 * \param[in] type Type of group to split into.
746 * \param[in,out] id Variable to receive the next group id.
747 * \returns `true` if \p atomIndex starts a new group in the sequence, i.e.,
748 * if \p *id was changed.
750 * \p *id should be initialized to `-1` before first call of this function, and
751 * then each atom index in the sequence passed to the function in turn.
753 * \ingroup module_selection
756 next_group_index(int atomIndex, const gmx_mtop_t *top,
757 e_index_t type, int *id)
767 int resind, molb = 0;
768 mtopGetAtomAndResidueName(top, atomIndex, &molb, nullptr, nullptr, nullptr, &resind);
775 *id = mtopGetMoleculeIndex(top, atomIndex, &molb);
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.
803 gmx_ana_index_make_block(t_blocka *t, const gmx_mtop_t *top, gmx_ana_index_t *g,
804 e_index_t type, bool bComplete)
806 if (type == INDEX_UNKNOWN)
820 // TODO: Check callers and either check these there as well, or turn these
822 GMX_RELEASE_ASSERT(top != nullptr || (type != INDEX_RES && type != INDEX_MOL),
823 "Topology must be provided for residue or molecule blocks");
824 GMX_RELEASE_ASSERT(type != INDEX_MOL || top->haveMoleculeIndices,
825 "Molecule information must be present for molecule blocks");
827 /* bComplete only does something for INDEX_RES or INDEX_MOL, so turn it
829 if (type != INDEX_RES && type != INDEX_MOL)
833 /* Allocate memory for the atom array and fill it unless we are using
838 /* We may allocate some extra memory here because we don't know in
839 * advance how much will be needed. */
840 if (t->nalloc_a < top->natoms)
842 srenew(t->a, top->natoms);
843 t->nalloc_a = top->natoms;
849 if (t->nalloc_a < g->isize)
851 srenew(t->a, g->isize);
852 t->nalloc_a = g->isize;
854 std::memcpy(t->a, g->index, g->isize*sizeof(*(t->a)));
857 /* Allocate memory for the block index. We don't know in advance
858 * how much will be needed, so we allocate some extra and free it in the
860 if (t->nalloc_index < g->isize + 1)
862 srenew(t->index, g->isize + 1);
863 t->nalloc_index = g->isize + 1;
869 for (int i = 0; i < g->isize; ++i)
871 const int ai = g->index[i];
872 /* Find the ID number of the atom/residue/molecule corresponding to
874 if (next_group_index(ai, top, type, &id))
876 /* If this is the first atom in a new block, initialize the block. */
879 /* For completion, we first set the start of the block. */
880 t->index[t->nr++] = t->nra;
881 /* And then we find all the atoms that should be included. */
887 mtopGetMolblockIndex(top, ai, &molb, &molnr, &atnr_mol);
888 const t_atoms &mol_atoms = top->moltype[top->molblock[molb].type].atoms;
889 int last_atom = atnr_mol + 1;
890 const int currentResid = mol_atoms.atom[atnr_mol].resind;
891 while (last_atom < mol_atoms.nr
892 && mol_atoms.atom[last_atom].resind == currentResid)
896 int first_atom = atnr_mol - 1;
897 while (first_atom >= 0
898 && mol_atoms.atom[first_atom].resind == currentResid)
902 const MoleculeBlockIndices &molBlock = top->moleculeBlockIndices[molb];
903 int first_mol_atom = molBlock.globalAtomStart;
904 first_mol_atom += molnr*molBlock.numAtomsPerMolecule;
905 first_atom = first_mol_atom + first_atom + 1;
906 last_atom = first_mol_atom + last_atom - 1;
907 for (int j = first_atom; j <= last_atom; ++j)
916 mtopGetMolblockIndex(top, ai, &molb, &molnr, &atnr_mol);
917 const MoleculeBlockIndices &blockIndices = top->moleculeBlockIndices[molb];
918 const int atomStart = blockIndices.globalAtomStart + (id - blockIndices.moleculeIndexStart)*blockIndices.numAtomsPerMolecule;
919 for (int j = 0; j < blockIndices.numAtomsPerMolecule; ++j)
921 t->a[t->nra++] = atomStart + j;
925 default: /* Should not be reached */
926 GMX_RELEASE_ASSERT(false, "Unreachable code was reached");
932 /* If not using completion, simply store the start of the block. */
933 t->index[t->nr++] = i;
937 /* Set the end of the last block */
938 t->index[t->nr] = t->nra;
939 /* Free any unnecessary memory */
940 srenew(t->index, t->nr+1);
941 t->nalloc_index = t->nr+1;
944 srenew(t->a, t->nra);
945 t->nalloc_a = t->nra;
950 * \param[in] g Index group to check.
951 * \param[in] b Block data to check against.
952 * \returns true if \p g consists of one or more complete blocks from \p b,
955 * The atoms in \p g are assumed to be sorted.
958 gmx_ana_index_has_full_blocks(const gmx_ana_index_t *g,
959 const gmx::RangePartitioning *b)
964 /* Each round in the loop matches one block */
967 /* Find the block that begins with the first unmatched atom */
968 while (bi < b->numBlocks() && *b->block(bi).begin() != g->index[i])
972 /* If not found, or if too large, return */
973 if (bi == b->numBlocks() || i + b->block(bi).size() > g->isize)
977 /* Check that the block matches the index */
978 for (j = *b->block(bi).begin(); j < *b->block(bi).end(); ++j, ++i)
980 if (g->index[i] != j)
985 /* Move the search to the next block */
992 * \param[in] g Index group to check.
993 * \param[in] b Block data to check against.
994 * \returns true if \p g consists of one or more complete blocks from \p b,
997 * The atoms in \p g and \p b->a are assumed to be in the same order.
1000 gmx_ana_index_has_full_ablocks(gmx_ana_index_t *g, t_blocka *b)
1005 /* Each round in the loop matches one block */
1006 while (i < g->isize)
1008 /* Find the block that begins with the first unmatched atom */
1009 while (bi < b->nr && b->a[b->index[bi]] != g->index[i])
1013 /* If not found, or if too large, return */
1014 if (bi == b->nr || i + b->index[bi+1] - b->index[bi] > g->isize)
1018 /* Check that the block matches the index */
1019 for (j = b->index[bi]; j < b->index[bi+1]; ++j, ++i)
1021 if (b->a[j] != g->index[i])
1026 /* Move the search to the next block */
1033 * \brief Returns if an atom is at a residue boundary.
1035 * \param[in] top Topology data.
1036 * \param[in] a Atom index to check, should be -1 <= \p a < top->natoms.
1037 * \param[in,out] molb The molecule block of atom a
1038 * \returns true if atoms \p a and \p a + 1 are in different residues, false otherwise.
1040 static bool is_at_residue_boundary(const gmx_mtop_t *top, int a, int *molb)
1042 if (a == -1 || a + 1 == top->natoms)
1047 mtopGetAtomAndResidueName(top, a, molb,
1048 nullptr, nullptr, nullptr, &resindA);
1050 mtopGetAtomAndResidueName(top, a + 1, molb,
1051 nullptr, nullptr, nullptr, &resindAPlusOne);
1052 return resindAPlusOne != resindA;
1056 * \param[in] g Index group to check.
1057 * \param[in] type Block data to check against.
1058 * \param[in] top Topology data.
1059 * \returns true if \p g consists of one or more complete elements of type
1060 * \p type, false otherwise.
1062 * \p g is assumed to be sorted, otherwise may return false negatives.
1064 * If \p type is \ref INDEX_ATOM, the return value is always true.
1065 * If \p type is \ref INDEX_UNKNOWN or \ref INDEX_ALL, the return value is
1069 gmx_ana_index_has_complete_elems(gmx_ana_index_t *g, e_index_t type,
1070 const gmx_mtop_t *top)
1077 // TODO: Consider whether unsorted groups need to be supported better.
1091 for (int i = 0; i < g->isize; ++i)
1093 const int a = g->index[i];
1094 // Check if a is consecutive or on a residue boundary
1097 if (!is_at_residue_boundary(top, aPrev, &molb))
1101 if (!is_at_residue_boundary(top, a - 1, &molb))
1108 GMX_ASSERT(g->isize > 0, "We return above when isize=0");
1109 const int a = g->index[g->isize - 1];
1110 if (!is_at_residue_boundary(top, a, &molb))
1119 auto molecules = gmx_mtop_molecules(*top);
1120 return gmx_ana_index_has_full_blocks(g, &molecules);
1127 * \param[out] m Output structure.
1129 * Any contents of \p m are discarded without freeing.
1132 gmx_ana_indexmap_clear(gmx_ana_indexmap_t *m)
1134 m->type = INDEX_UNKNOWN;
1138 m->mapb.index = nullptr;
1139 m->mapb.nalloc_index = 0;
1141 m->mapb.a = nullptr;
1142 m->mapb.nalloc_a = 0;
1145 m->b.index = nullptr;
1148 m->b.nalloc_index = 0;
1154 * \param[in,out] m Mapping structure.
1155 * \param[in] nr Maximum number of blocks to reserve space for.
1156 * \param[in] isize Maximum number of atoms to reserve space for.
1159 gmx_ana_indexmap_reserve(gmx_ana_indexmap_t *m, int nr, int isize)
1161 if (m->mapb.nalloc_index < nr + 1)
1163 srenew(m->refid, nr);
1164 srenew(m->mapid, nr);
1165 srenew(m->orgid, nr);
1166 srenew(m->mapb.index, nr + 1);
1167 srenew(m->b.index, nr + 1);
1168 m->mapb.nalloc_index = nr + 1;
1169 m->b.nalloc_index = nr + 1;
1171 if (m->b.nalloc_a < isize)
1173 srenew(m->b.a, isize);
1174 m->b.nalloc_a = isize;
1179 * \param[in,out] m Mapping structure to initialize.
1180 * \param[in] g Index group to map
1181 * (can be NULL if \p type is \ref INDEX_UNKNOWN).
1182 * \param[in] top Topology structure
1183 * (can be NULL if \p type is not \ref INDEX_RES or \ref INDEX_MOL).
1184 * \param[in] type Type of mapping to construct.
1186 * Initializes a new index group mapping.
1187 * The index group provided to gmx_ana_indexmap_update() should always be a
1188 * subset of the \p g given here.
1190 * \p m should have been initialized somehow (calloc() is enough).
1193 gmx_ana_indexmap_init(gmx_ana_indexmap_t *m, gmx_ana_index_t *g,
1194 const gmx_mtop_t *top, e_index_t type)
1197 gmx_ana_index_make_block(&m->b, top, g, type, false);
1198 gmx_ana_indexmap_reserve(m, m->b.nr, m->b.nra);
1200 for (int i = 0; i < m->b.nr; ++i)
1202 const int ii = (type == INDEX_UNKNOWN ? 0 : m->b.a[m->b.index[i]]);
1203 next_group_index(ii, top, type, &id);
1208 m->mapb.nr = m->b.nr;
1209 m->mapb.nra = m->b.nra;
1211 std::memcpy(m->mapb.index, m->b.index, (m->b.nr+1)*sizeof(*(m->mapb.index)));
1216 gmx_ana_indexmap_init_orgid_group(gmx_ana_indexmap_t *m, const gmx_mtop_t *top,
1219 GMX_RELEASE_ASSERT(m->bStatic,
1220 "Changing original IDs is not supported after starting "
1221 "to use the mapping");
1222 GMX_RELEASE_ASSERT(top != nullptr || (type != INDEX_RES && type != INDEX_MOL),
1223 "Topology must be provided for residue or molecule blocks");
1224 // Check that all atoms in each block belong to the same group.
1225 // This is a separate loop for better error handling (no state is modified
1226 // if there is an error.
1227 if (type == INDEX_RES || type == INDEX_MOL)
1230 for (int i = 0; i < m->b.nr; ++i)
1232 const int ii = m->b.a[m->b.index[i]];
1233 if (next_group_index(ii, top, type, &id))
1235 for (int j = m->b.index[i] + 1; j < m->b.index[i+1]; ++j)
1237 if (next_group_index(m->b.a[j], top, type, &id))
1239 std::string message("Grouping into residues/molecules is ambiguous");
1240 GMX_THROW(gmx::InconsistentInputError(message));
1246 // Do a second loop, where things are actually set.
1249 for (int i = 0; i < m->b.nr; ++i)
1251 const int ii = (type == INDEX_UNKNOWN ? 0 : m->b.a[m->b.index[i]]);
1252 if (next_group_index(ii, top, type, &id))
1256 m->mapid[i] = group;
1257 m->orgid[i] = group;
1259 // Count also the last group.
1265 * \param[in,out] m Mapping structure to initialize.
1266 * \param[in] b Block information to use for data.
1268 * Frees some memory that is not necessary for static index group mappings.
1269 * Internal pointers are set to point to data in \p b; it is the responsibility
1270 * of the caller to ensure that the block information matches the contents of
1272 * After this function has been called, the index group provided to
1273 * gmx_ana_indexmap_update() should always be the same as \p g given here.
1275 * This function breaks modularity of the index group mapping interface in an
1276 * ugly way, but allows reducing memory usage of static selections by a
1277 * significant amount.
1280 gmx_ana_indexmap_set_static(gmx_ana_indexmap_t *m, t_blocka *b)
1283 sfree(m->mapb.index);
1286 m->mapb.nalloc_index = 0;
1287 m->mapb.nalloc_a = 0;
1288 m->b.nalloc_index = 0;
1290 m->mapid = m->orgid;
1291 m->mapb.index = b->index;
1293 m->b.index = b->index;
1298 * \param[in,out] dest Destination data structure.
1299 * \param[in] src Source mapping.
1300 * \param[in] bFirst If true, memory is allocated for \p dest and a full
1301 * copy is made; otherwise, only variable parts are copied, and no memory
1304 * \p dest should have been initialized somehow (calloc() is enough).
1307 gmx_ana_indexmap_copy(gmx_ana_indexmap_t *dest, gmx_ana_indexmap_t *src, bool bFirst)
1311 gmx_ana_indexmap_reserve(dest, src->b.nr, src->b.nra);
1312 dest->type = src->type;
1313 dest->b.nr = src->b.nr;
1314 dest->b.nra = src->b.nra;
1315 std::memcpy(dest->orgid, src->orgid, dest->b.nr*sizeof(*dest->orgid));
1316 std::memcpy(dest->b.index, src->b.index, (dest->b.nr+1)*sizeof(*dest->b.index));
1317 std::memcpy(dest->b.a, src->b.a, dest->b.nra*sizeof(*dest->b.a));
1319 dest->mapb.nr = src->mapb.nr;
1320 dest->mapb.nra = src->mapb.nra;
1321 if (src->mapb.nalloc_a > 0)
1325 snew(dest->mapb.a, src->mapb.nalloc_a);
1326 dest->mapb.nalloc_a = src->mapb.nalloc_a;
1328 std::memcpy(dest->mapb.a, src->mapb.a, dest->mapb.nra*sizeof(*dest->mapb.a));
1332 dest->mapb.a = src->mapb.a;
1334 std::memcpy(dest->refid, src->refid, dest->mapb.nr*sizeof(*dest->refid));
1335 std::memcpy(dest->mapid, src->mapid, dest->mapb.nr*sizeof(*dest->mapid));
1336 std::memcpy(dest->mapb.index, src->mapb.index, (dest->mapb.nr+1)*sizeof(*dest->mapb.index));
1337 dest->bStatic = src->bStatic;
1341 * Helper function to set the source atoms in an index map.
1343 * \param[in,out] m Mapping structure.
1344 * \param[in] isize Number of atoms in the \p index array.
1345 * \param[in] index List of atoms.
1348 set_atoms(gmx_ana_indexmap_t *m, int isize, int *index)
1350 m->mapb.nra = isize;
1351 if (m->mapb.nalloc_a == 0)
1357 for (int i = 0; i < isize; ++i)
1359 m->mapb.a[i] = index[i];
1365 * \param[in,out] m Mapping structure.
1366 * \param[in] g Current index group.
1367 * \param[in] bMaskOnly true if the unused blocks should be masked with
1368 * -1 instead of removing them.
1370 * Updates the index group mapping with the new index group \p g.
1372 * \see gmx_ana_indexmap_t
1375 gmx_ana_indexmap_update(gmx_ana_indexmap_t *m, gmx_ana_index_t *g,
1380 /* Process the simple cases first */
1381 if (m->type == INDEX_UNKNOWN && m->b.nra == 0)
1385 if (m->type == INDEX_ALL)
1387 set_atoms(m, g->isize, g->index);
1390 m->mapb.index[1] = g->isize;
1394 /* Reset the reference IDs and mapping if necessary */
1395 const bool bToFull = (g->isize == m->b.nra);
1396 const bool bWasFull = (m->mapb.nra == m->b.nra);
1397 if (bToFull || bMaskOnly)
1401 for (bj = 0; bj < m->b.nr; ++bj)
1408 for (bj = 0; bj < m->b.nr; ++bj)
1410 m->mapid[bj] = m->orgid[bj];
1412 for (bj = 0; bj <= m->b.nr; ++bj)
1414 m->mapb.index[bj] = m->b.index[bj];
1417 set_atoms(m, m->b.nra, m->b.a);
1418 m->mapb.nr = m->b.nr;
1420 /* Exit immediately if the group is static */
1429 for (i = j = bj = 0; i < g->isize; ++i, ++j)
1431 /* Find the next atom in the block */
1432 while (m->b.a[j] != g->index[i])
1436 /* Mark blocks that did not contain any atoms */
1437 while (bj < m->b.nr && m->b.index[bj+1] <= j)
1439 m->refid[bj++] = -1;
1441 /* Advance the block index if we have reached the next block */
1442 if (m->b.index[bj] <= j)
1447 /* Mark the last blocks as not accessible */
1448 while (bj < m->b.nr)
1450 m->refid[bj++] = -1;
1455 set_atoms(m, g->isize, g->index);
1456 for (i = j = bi = 0, bj = -1; i < g->isize; ++i)
1458 /* Find the next atom in the block */
1459 while (m->b.a[j] != g->index[i])
1463 /* If we have reached a new block, add it */
1464 if (m->b.index[bj+1] <= j)
1466 /* Skip any blocks in between */
1467 while (bj < m->b.nr && m->b.index[bj+1] <= j)
1472 m->mapid[bi] = m->orgid[bj];
1473 m->mapb.index[bi] = i;
1477 /* Update the number of blocks */
1478 m->mapb.index[bi] = g->isize;
1485 * \param[in,out] m Mapping structure to free.
1487 * All the memory allocated for the mapping structure is freed, and
1488 * the pointers set to NULL.
1489 * The pointer \p m is not freed.
1492 gmx_ana_indexmap_deinit(gmx_ana_indexmap_t *m)
1495 if (m->mapid != m->orgid)
1499 if (m->mapb.nalloc_index > 0)
1501 sfree(m->mapb.index);
1503 if (m->mapb.nalloc_a > 0)
1508 if (m->b.nalloc_index > 0)
1512 if (m->b.nalloc_a > 0)
1516 gmx_ana_indexmap_clear(m);