Apply clang-format to source tree
[alexxy/gromacs.git] / src / gromacs / domdec / domdec_constraints.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2006,2007,2008,2009,2010,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.
8  *
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.
13  *
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.
18  *
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.
23  *
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.
31  *
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.
34  */
35
36 /*! \internal \file
37  *
38  * \brief This file implements functions for domdec to use
39  * while managing inter-atomic constraints.
40  *
41  * \author Berk Hess <hess@kth.se>
42  * \ingroup module_domdec
43  */
44
45 #include "gmxpre.h"
46
47 #include "domdec_constraints.h"
48
49 #include <cassert>
50
51 #include <algorithm>
52 #include <memory>
53
54 #include "gromacs/domdec/dlbtiming.h"
55 #include "gromacs/domdec/domdec.h"
56 #include "gromacs/domdec/domdec_struct.h"
57 #include "gromacs/domdec/ga2la.h"
58 #include "gromacs/domdec/hashedmap.h"
59 #include "gromacs/math/vec.h"
60 #include "gromacs/mdlib/constr.h"
61 #include "gromacs/mdlib/gmx_omp_nthreads.h"
62 #include "gromacs/mdtypes/commrec.h"
63 #include "gromacs/mdtypes/forcerec.h" // only for GET_CGINFO_*
64 #include "gromacs/pbcutil/ishift.h"
65 #include "gromacs/topology/ifunc.h"
66 #include "gromacs/topology/mtop_lookup.h"
67 #include "gromacs/utility/exceptions.h"
68 #include "gromacs/utility/fatalerror.h"
69 #include "gromacs/utility/gmxassert.h"
70 #include "gromacs/utility/smalloc.h"
71
72 #include "domdec_internal.h"
73 #include "domdec_specatomcomm.h"
74
75 /*! \brief Struct used during constraint setup with domain decomposition */
76 struct gmx_domdec_constraints_t
77 {
78     //! @cond Doxygen_Suppress
79     std::vector<int> molb_con_offset; /**< Offset in the constraint array for each molblock */
80     std::vector<int> molb_ncon_mol; /**< The number of constraints per molecule for each molblock */
81
82     int ncon; /**< The fully local and conneced constraints */
83     /* The global constraint number, only required for clearing gc_req */
84     std::vector<int> con_gl;     /**< Global constraint indices for local constraints */
85     std::vector<int> con_nlocat; /**< Number of local atoms (2/1/0) for each constraint */
86
87     std::vector<bool> gc_req; /**< Boolean that tells if a global constraint index has been requested; note: size global #constraints */
88
89     /* Hash table for keeping track of requests */
90     std::unique_ptr<gmx::HashedMap<int>> ga2la; /**< Global to local communicated constraint atom only index */
91
92     /* Multi-threading stuff */
93     int                  nthread; /**< Number of threads used for DD constraint setup */
94     std::vector<t_ilist> ils;     /**< Constraint ilist working arrays, size \p nthread */
95
96     /* Buffers for requesting atoms */
97     std::vector<std::vector<int>> requestedGlobalAtomIndices; /**< Buffers for requesting global atom indices, one per thread */
98
99     //! @endcond
100 };
101
102 void dd_move_x_constraints(gmx_domdec_t* dd, const matrix box, rvec* x0, rvec* x1, gmx_bool bX1IsCoord)
103 {
104     if (dd->constraint_comm)
105     {
106         dd_move_x_specat(dd, dd->constraint_comm, box, x0, x1, bX1IsCoord);
107
108         ddReopenBalanceRegionCpu(dd);
109     }
110 }
111
112 gmx::ArrayRef<const int> dd_constraints_nlocalatoms(const gmx_domdec_t* dd)
113 {
114     if (dd && dd->constraints)
115     {
116         return dd->constraints->con_nlocat;
117     }
118     else
119     {
120         return {};
121     }
122 }
123
124 void dd_clear_local_constraint_indices(gmx_domdec_t* dd)
125 {
126     gmx_domdec_constraints_t* dc = dd->constraints;
127
128     std::fill(dc->gc_req.begin(), dc->gc_req.end(), false);
129
130     if (dd->constraint_comm)
131     {
132         dc->ga2la->clear();
133     }
134 }
135
136 /*! \brief Walks over the constraints out from the local atoms into the non-local atoms and adds them to a list */
137 static void walk_out(int                       con,
138                      int                       con_offset,
139                      int                       a,
140                      int                       offset,
141                      int                       nrec,
142                      gmx::ArrayRef<const int>  ia1,
143                      gmx::ArrayRef<const int>  ia2,
144                      const t_blocka*           at2con,
145                      const gmx_ga2la_t&        ga2la,
146                      gmx_bool                  bHomeConnect,
147                      gmx_domdec_constraints_t* dc,
148                      gmx_domdec_specat_comm_t* dcc,
149                      t_ilist*                  il_local,
150                      std::vector<int>*         ireq)
151 {
152     int            a1_gl, a2_gl, i, coni, b;
153     const t_iatom* iap;
154
155     if (!dc->gc_req[con_offset + con])
156     {
157         /* Add this non-home constraint to the list */
158         dc->con_gl.push_back(con_offset + con);
159         dc->con_nlocat.push_back(bHomeConnect ? 1 : 0);
160         dc->gc_req[con_offset + con] = true;
161         if (il_local->nr + 3 > il_local->nalloc)
162         {
163             il_local->nalloc = over_alloc_dd(il_local->nr + 3);
164             srenew(il_local->iatoms, il_local->nalloc);
165         }
166         iap                              = constr_iatomptr(ia1, ia2, con);
167         il_local->iatoms[il_local->nr++] = iap[0];
168         a1_gl                            = offset + iap[1];
169         a2_gl                            = offset + iap[2];
170         /* The following indexing code can probably be optizimed */
171         if (const int* a_loc = ga2la.findHome(a1_gl))
172         {
173             il_local->iatoms[il_local->nr++] = *a_loc;
174         }
175         else
176         {
177             /* We set this index later */
178             il_local->iatoms[il_local->nr++] = -a1_gl - 1;
179         }
180         if (const int* a_loc = ga2la.findHome(a2_gl))
181         {
182             il_local->iatoms[il_local->nr++] = *a_loc;
183         }
184         else
185         {
186             /* We set this index later */
187             il_local->iatoms[il_local->nr++] = -a2_gl - 1;
188         }
189         dc->ncon++;
190     }
191     /* Check to not ask for the same atom more than once */
192     if (!dc->ga2la->find(offset + a))
193     {
194         assert(dcc);
195         /* Add this non-home atom to the list */
196         ireq->push_back(offset + a);
197         /* Temporarily mark with -2, we get the index later */
198         dc->ga2la->insert(offset + a, -2);
199     }
200
201     if (nrec > 0)
202     {
203         for (i = at2con->index[a]; i < at2con->index[a + 1]; i++)
204         {
205             coni = at2con->a[i];
206             if (coni != con)
207             {
208                 /* Walk further */
209                 iap = constr_iatomptr(ia1, ia2, coni);
210                 if (a == iap[1])
211                 {
212                     b = iap[2];
213                 }
214                 else
215                 {
216                     b = iap[1];
217                 }
218                 if (!ga2la.findHome(offset + b))
219                 {
220                     walk_out(coni, con_offset, b, offset, nrec - 1, ia1, ia2, at2con, ga2la, FALSE,
221                              dc, dcc, il_local, ireq);
222                 }
223             }
224         }
225     }
226 }
227
228 /*! \brief Looks up SETTLE constraints for a range of charge-groups */
229 static void atoms_to_settles(gmx_domdec_t*                         dd,
230                              const gmx_mtop_t*                     mtop,
231                              const int*                            cginfo,
232                              gmx::ArrayRef<const std::vector<int>> at2settle_mt,
233                              int                                   cg_start,
234                              int                                   cg_end,
235                              t_ilist*                              ils_local,
236                              std::vector<int>*                     ireq)
237 {
238     const gmx_ga2la_t& ga2la = *dd->ga2la;
239     int                nral  = NRAL(F_SETTLE);
240
241     int mb = 0;
242     for (int a = cg_start; a < cg_end; a++)
243     {
244         if (GET_CGINFO_SETTLE(cginfo[a]))
245         {
246             int a_gl = dd->globalAtomIndices[a];
247             int a_mol;
248             mtopGetMolblockIndex(mtop, a_gl, &mb, nullptr, &a_mol);
249
250             const gmx_molblock_t* molb   = &mtop->molblock[mb];
251             int                   settle = at2settle_mt[molb->type][a_mol];
252
253             if (settle >= 0)
254             {
255                 int offset = a_gl - a_mol;
256
257                 const int* ia1 = mtop->moltype[molb->type].ilist[F_SETTLE].iatoms.data();
258
259                 int      a_gls[3];
260                 gmx_bool bAssign = FALSE;
261                 int      nlocal  = 0;
262                 for (int sa = 0; sa < nral; sa++)
263                 {
264                     int a_glsa = offset + ia1[settle * (1 + nral) + 1 + sa];
265                     a_gls[sa]  = a_glsa;
266                     if (ga2la.findHome(a_glsa))
267                     {
268                         if (nlocal == 0 && a_gl == a_glsa)
269                         {
270                             bAssign = TRUE;
271                         }
272                         nlocal++;
273                     }
274                 }
275
276                 if (bAssign)
277                 {
278                     if (ils_local->nr + 1 + nral > ils_local->nalloc)
279                     {
280                         ils_local->nalloc = over_alloc_dd(ils_local->nr + 1 + nral);
281                         srenew(ils_local->iatoms, ils_local->nalloc);
282                     }
283
284                     ils_local->iatoms[ils_local->nr++] = ia1[settle * 4];
285
286                     for (int sa = 0; sa < nral; sa++)
287                     {
288                         if (const int* a_loc = ga2la.findHome(a_gls[sa]))
289                         {
290                             ils_local->iatoms[ils_local->nr++] = *a_loc;
291                         }
292                         else
293                         {
294                             ils_local->iatoms[ils_local->nr++] = -a_gls[sa] - 1;
295                             /* Add this non-home atom to the list */
296                             ireq->push_back(a_gls[sa]);
297                             /* A check on double atom requests is
298                              * not required for settle.
299                              */
300                         }
301                     }
302                 }
303             }
304         }
305     }
306 }
307
308 /*! \brief Looks up constraint for the local atoms */
309 static void atoms_to_constraints(gmx_domdec_t*                 dd,
310                                  const gmx_mtop_t*             mtop,
311                                  const int*                    cginfo,
312                                  gmx::ArrayRef<const t_blocka> at2con_mt,
313                                  int                           nrec,
314                                  t_ilist*                      ilc_local,
315                                  std::vector<int>*             ireq)
316 {
317     const t_blocka* at2con;
318     int             b_lo, offset, b_mol, i, con, con_offset;
319
320     gmx_domdec_constraints_t* dc  = dd->constraints;
321     gmx_domdec_specat_comm_t* dcc = dd->constraint_comm;
322
323     const gmx_ga2la_t& ga2la = *dd->ga2la;
324
325     dc->con_gl.clear();
326     dc->con_nlocat.clear();
327
328     int mb    = 0;
329     int nhome = 0;
330     for (int a = 0; a < dd->ncg_home; a++)
331     {
332         if (GET_CGINFO_CONSTR(cginfo[a]))
333         {
334             int a_gl = dd->globalAtomIndices[a];
335             int molnr, a_mol;
336             mtopGetMolblockIndex(mtop, a_gl, &mb, &molnr, &a_mol);
337
338             const gmx_molblock_t& molb = mtop->molblock[mb];
339
340             gmx::ArrayRef<const int> ia1 = mtop->moltype[molb.type].ilist[F_CONSTR].iatoms;
341             gmx::ArrayRef<const int> ia2 = mtop->moltype[molb.type].ilist[F_CONSTRNC].iatoms;
342
343             /* Calculate the global constraint number offset for the molecule.
344              * This is only required for the global index to make sure
345              * that we use each constraint only once.
346              */
347             con_offset = dc->molb_con_offset[mb] + molnr * dc->molb_ncon_mol[mb];
348
349             /* The global atom number offset for this molecule */
350             offset = a_gl - a_mol;
351             at2con = &at2con_mt[molb.type];
352             for (i = at2con->index[a_mol]; i < at2con->index[a_mol + 1]; i++)
353             {
354                 con            = at2con->a[i];
355                 const int* iap = constr_iatomptr(ia1, ia2, con);
356                 if (a_mol == iap[1])
357                 {
358                     b_mol = iap[2];
359                 }
360                 else
361                 {
362                     b_mol = iap[1];
363                 }
364                 if (const int* a_loc = ga2la.findHome(offset + b_mol))
365                 {
366                     /* Add this fully home constraint at the first atom */
367                     if (a_mol < b_mol)
368                     {
369                         dc->con_gl.push_back(con_offset + con);
370                         dc->con_nlocat.push_back(2);
371                         if (ilc_local->nr + 3 > ilc_local->nalloc)
372                         {
373                             ilc_local->nalloc = over_alloc_dd(ilc_local->nr + 3);
374                             srenew(ilc_local->iatoms, ilc_local->nalloc);
375                         }
376                         b_lo                               = *a_loc;
377                         ilc_local->iatoms[ilc_local->nr++] = iap[0];
378                         ilc_local->iatoms[ilc_local->nr++] = (a_gl == iap[1] ? a : b_lo);
379                         ilc_local->iatoms[ilc_local->nr++] = (a_gl == iap[1] ? b_lo : a);
380                         dc->ncon++;
381                         nhome++;
382                     }
383                 }
384                 else
385                 {
386                     /* We need the nrec constraints coupled to this constraint,
387                      * so we need to walk out of the home cell by nrec+1 atoms,
388                      * since already atom bg is not locally present.
389                      * Therefore we call walk_out with nrec recursions to go
390                      * after this first call.
391                      */
392                     walk_out(con, con_offset, b_mol, offset, nrec, ia1, ia2, at2con, ga2la, TRUE,
393                              dc, dcc, ilc_local, ireq);
394                 }
395             }
396         }
397     }
398
399     GMX_ASSERT(dc->con_gl.size() == static_cast<size_t>(dc->ncon),
400                "con_gl size should match the number of constraints");
401     GMX_ASSERT(dc->con_nlocat.size() == static_cast<size_t>(dc->ncon),
402                "con_nlocat size should match the number of constraints");
403
404     if (debug)
405     {
406         fprintf(debug, "Constraints: home %3d border %3d atoms: %3zu\n", nhome, dc->ncon - nhome,
407                 dd->constraint_comm ? ireq->size() : 0);
408     }
409 }
410
411 int dd_make_local_constraints(gmx_domdec_t*            dd,
412                               int                      at_start,
413                               const struct gmx_mtop_t* mtop,
414                               const int*               cginfo,
415                               gmx::Constraints*        constr,
416                               int                      nrec,
417                               t_ilist*                 il_local)
418 {
419     gmx_domdec_constraints_t*     dc;
420     t_ilist *                     ilc_local, *ils_local;
421     std::vector<int>*             ireq;
422     gmx::ArrayRef<const t_blocka> at2con_mt;
423     gmx::HashedMap<int>*          ga2la_specat;
424     int                           at_end, i, j;
425     t_iatom*                      iap;
426
427     // This code should not be called unless this condition is true,
428     // because that's the only time init_domdec_constraints is
429     // called...
430     GMX_RELEASE_ASSERT(dd->comm->systemInfo.haveSplitConstraints || dd->comm->systemInfo.haveSplitSettles,
431                        "dd_make_local_constraints called when there are no local constraints");
432     // ... and init_domdec_constraints always sets
433     // dd->constraint_comm...
434     GMX_RELEASE_ASSERT(
435             dd->constraint_comm,
436             "Invalid use of dd_make_local_constraints before construction of constraint_comm");
437     // ... which static analysis needs to be reassured about, because
438     // otherwise, when dd->splitSettles is
439     // true. dd->constraint_comm is unilaterally dereferenced before
440     // the call to atoms_to_settles.
441
442     dc = dd->constraints;
443
444     ilc_local = &il_local[F_CONSTR];
445     ils_local = &il_local[F_SETTLE];
446
447     dc->ncon      = 0;
448     ilc_local->nr = 0;
449     if (dd->constraint_comm)
450     {
451         // TODO Perhaps gmx_domdec_constraints_t should keep a valid constr?
452         GMX_RELEASE_ASSERT(constr != nullptr, "Must have valid constraints object");
453         at2con_mt = constr->atom2constraints_moltype();
454         ireq      = &dc->requestedGlobalAtomIndices[0];
455         ireq->clear();
456     }
457     else
458     {
459         // Currently unreachable
460         at2con_mt = {};
461         ireq      = nullptr;
462     }
463
464     gmx::ArrayRef<const std::vector<int>> at2settle_mt;
465     /* When settle works inside charge groups, we assigned them already */
466     if (dd->comm->systemInfo.haveSplitSettles)
467     {
468         // TODO Perhaps gmx_domdec_constraints_t should keep a valid constr?
469         GMX_RELEASE_ASSERT(constr != nullptr, "Must have valid constraints object");
470         at2settle_mt  = constr->atom2settle_moltype();
471         ils_local->nr = 0;
472     }
473
474     if (at2settle_mt.empty())
475     {
476         atoms_to_constraints(dd, mtop, cginfo, at2con_mt, nrec, ilc_local, ireq);
477     }
478     else
479     {
480         int t0_set;
481
482         /* Do the constraints, if present, on the first thread.
483          * Do the settles on all other threads.
484          */
485         t0_set = ((!at2con_mt.empty() && dc->nthread > 1) ? 1 : 0);
486
487 #pragma omp parallel for num_threads(dc->nthread) schedule(static)
488         for (int thread = 0; thread < dc->nthread; thread++)
489         {
490             try
491             {
492                 if (!at2con_mt.empty() && thread == 0)
493                 {
494                     atoms_to_constraints(dd, mtop, cginfo, at2con_mt, nrec, ilc_local, ireq);
495                 }
496
497                 if (thread >= t0_set)
498                 {
499                     int      cg0, cg1;
500                     t_ilist* ilst;
501
502                     /* Distribute the settle check+assignments over
503                      * dc->nthread or dc->nthread-1 threads.
504                      */
505                     cg0 = (dd->ncg_home * (thread - t0_set)) / (dc->nthread - t0_set);
506                     cg1 = (dd->ncg_home * (thread - t0_set + 1)) / (dc->nthread - t0_set);
507
508                     if (thread == t0_set)
509                     {
510                         ilst = ils_local;
511                     }
512                     else
513                     {
514                         ilst = &dc->ils[thread];
515                     }
516                     ilst->nr = 0;
517
518                     std::vector<int>& ireqt = dc->requestedGlobalAtomIndices[thread];
519                     if (thread > 0)
520                     {
521                         ireqt.clear();
522                     }
523
524                     atoms_to_settles(dd, mtop, cginfo, at2settle_mt, cg0, cg1, ilst, &ireqt);
525                 }
526             }
527             GMX_CATCH_ALL_AND_EXIT_WITH_FATAL_ERROR
528         }
529
530         /* Combine the generate settles and requested indices */
531         for (int thread = 1; thread < dc->nthread; thread++)
532         {
533             t_ilist* ilst;
534             int      ia;
535
536             if (thread > t0_set)
537             {
538                 ilst = &dc->ils[thread];
539                 if (ils_local->nr + ilst->nr > ils_local->nalloc)
540                 {
541                     ils_local->nalloc = over_alloc_large(ils_local->nr + ilst->nr);
542                     srenew(ils_local->iatoms, ils_local->nalloc);
543                 }
544                 for (ia = 0; ia < ilst->nr; ia++)
545                 {
546                     ils_local->iatoms[ils_local->nr + ia] = ilst->iatoms[ia];
547                 }
548                 ils_local->nr += ilst->nr;
549             }
550
551             const std::vector<int>& ireqt = dc->requestedGlobalAtomIndices[thread];
552             ireq->insert(ireq->end(), ireqt.begin(), ireqt.end());
553         }
554
555         if (debug)
556         {
557             fprintf(debug, "Settles: total %3d\n", ils_local->nr / 4);
558         }
559     }
560
561     if (dd->constraint_comm)
562     {
563         int nral1;
564
565         at_end = setup_specat_communication(dd, ireq, dd->constraint_comm, dd->constraints->ga2la.get(),
566                                             at_start, 2, "constraint", " or lincs-order");
567
568         /* Fill in the missing indices */
569         ga2la_specat = dd->constraints->ga2la.get();
570
571         nral1 = 1 + NRAL(F_CONSTR);
572         for (i = 0; i < ilc_local->nr; i += nral1)
573         {
574             iap = ilc_local->iatoms + i;
575             for (j = 1; j < nral1; j++)
576             {
577                 if (iap[j] < 0)
578                 {
579                     const int* a = ga2la_specat->find(-iap[j] - 1);
580                     GMX_ASSERT(a, "We have checked before that this atom index has been set");
581                     iap[j] = *a;
582                 }
583             }
584         }
585
586         nral1 = 1 + NRAL(F_SETTLE);
587         for (i = 0; i < ils_local->nr; i += nral1)
588         {
589             iap = ils_local->iatoms + i;
590             for (j = 1; j < nral1; j++)
591             {
592                 if (iap[j] < 0)
593                 {
594                     const int* a = ga2la_specat->find(-iap[j] - 1);
595                     GMX_ASSERT(a, "We have checked before that this atom index has been set");
596                     iap[j] = *a;
597                 }
598             }
599         }
600     }
601     else
602     {
603         // Currently unreachable
604         at_end = at_start;
605     }
606
607     return at_end;
608 }
609
610 void init_domdec_constraints(gmx_domdec_t* dd, const gmx_mtop_t* mtop)
611 {
612     gmx_domdec_constraints_t* dc;
613     const gmx_molblock_t*     molb;
614
615     if (debug)
616     {
617         fprintf(debug, "Begin init_domdec_constraints\n");
618     }
619
620     dd->constraints = new gmx_domdec_constraints_t;
621     dc              = dd->constraints;
622
623     dc->molb_con_offset.resize(mtop->molblock.size());
624     dc->molb_ncon_mol.resize(mtop->molblock.size());
625
626     int ncon = 0;
627     for (size_t mb = 0; mb < mtop->molblock.size(); mb++)
628     {
629         molb                    = &mtop->molblock[mb];
630         dc->molb_con_offset[mb] = ncon;
631         dc->molb_ncon_mol[mb]   = mtop->moltype[molb->type].ilist[F_CONSTR].size() / 3
632                                 + mtop->moltype[molb->type].ilist[F_CONSTRNC].size() / 3;
633         ncon += molb->nmol * dc->molb_ncon_mol[mb];
634     }
635
636     if (ncon > 0)
637     {
638         dc->gc_req.resize(ncon);
639     }
640
641     /* Use a hash table for the global to local index.
642      * The number of keys is a rough estimate, it will be optimized later.
643      */
644     int numKeysEstimate = std::min(mtop->natoms / 20, mtop->natoms / (2 * dd->nnodes));
645     dc->ga2la           = std::make_unique<gmx::HashedMap<int>>(numKeysEstimate);
646
647     dc->nthread = gmx_omp_nthreads_get(emntDomdec);
648     dc->ils.resize(dc->nthread);
649
650     dd->constraint_comm = new gmx_domdec_specat_comm_t;
651
652     dc->requestedGlobalAtomIndices.resize(dc->nthread);
653 }