From 6751f3508af0f7da61bf836ce77f1a0e86409ddc Mon Sep 17 00:00:00 2001 From: sjplimp Date: Mon, 28 Jul 2014 18:46:35 +0000 Subject: [PATCH 01/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12198 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- doc/variable.html | 5 +++-- doc/variable.txt | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/variable.html b/doc/variable.html index 51daea0bf2..5222302cbe 100644 --- a/doc/variable.html +++ b/doc/variable.html @@ -50,7 +50,7 @@ constants = PI thermo keywords = vol, ke, press, etc from thermo_style math operators = (), -x, x+y, x-y, x*y, x/y, x^y, x%y, - x==y, x!=y, xy, x>=y, x&&y, x||y, !x + x == y, x != y, x < y, x <= y, x > y, x >= y, x && y, x || y, !x math functions = sqrt(x), exp(x), ln(x), log(x), abs(x), sin(x), cos(x), tan(x), asin(x), acos(x), atan(x), atan2(y,x), random(x,y,z), normal(x,y,z), ceil(x), floor(x), round(x) @@ -368,7 +368,8 @@ references to other variables. Number 0.2, 100, 1.0e20, -15.4, etc Constant PI Thermo keywords vol, pe, ebond, etc -Math operators (), -x, x+y, x-y, x*y, x/y, x^y, x%y, x==y, x!=y, xy, x>=y, x&&y, x||y, !x +Math operators (), -x, x+y, x-y, x*y, x/y, x^y, x%y, +Math operators (), -x, x+y, x-y, x*y, x/y, x^y, x%y, x == y, x != y, x < y, x <= y, x > y, x >= y, x && y, x || y, !x Math functions sqrt(x), exp(x), ln(x), log(x), abs(x), sin(x), cos(x), tan(x), asin(x), acos(x), atan(x), atan2(y,x), random(x,y,z), normal(x,y,z), ceil(x), floor(x), round(x), ramp(x,y), stagger(x,y), logfreq(x,y,z), stride(x,y,z), vdisplace(x,y), swiggle(x,y,z), cwiggle(x,y,z) Group functions count(ID), mass(ID), charge(ID), xcm(ID,dim), vcm(ID,dim), fcm(ID,dim), bound(ID,dir), gyration(ID), ke(ID), angmom(ID,dim), torque(ID,dim), inertia(ID,dimdim), omega(ID,dim) Region functions count(ID,IDR), mass(ID,IDR), charge(ID,IDR), xcm(ID,dim,IDR), vcm(ID,dim,IDR), fcm(ID,dim,IDR), bound(ID,dir,IDR), gyration(ID,IDR), ke(ID,IDR), angmom(ID,dim,IDR), torque(ID,dim,IDR), inertia(ID,dimdim,IDR), omega(ID,dim,IDR) diff --git a/doc/variable.txt b/doc/variable.txt index e9362de392..d7323b3efe 100644 --- a/doc/variable.txt +++ b/doc/variable.txt @@ -45,7 +45,7 @@ style = {delete} or {index} or {loop} or {world} or {universe} or {uloop} or {st constants = PI thermo keywords = vol, ke, press, etc from "thermo_style"_thermo_style.html math operators = (), -x, x+y, x-y, x*y, x/y, x^y, x%y, - x==y, x!=y, xy, x>=y, x&&y, x||y, !x + x == y, x != y, x < y, x <= y, x > y, x >= y, x && y, x || y, !x math functions = sqrt(x), exp(x), ln(x), log(x), abs(x), sin(x), cos(x), tan(x), asin(x), acos(x), atan(x), atan2(y,x), random(x,y,z), normal(x,y,z), ceil(x), floor(x), round(x) @@ -361,7 +361,8 @@ references to other variables. Number: 0.2, 100, 1.0e20, -15.4, etc Constant: PI Thermo keywords: vol, pe, ebond, etc -Math operators: (), -x, x+y, x-y, x*y, x/y, x^y, x%y, x==y, x!=y, xy, x>=y, x&&y, x||y, !x +Math operators: (), -x, x+y, x-y, x*y, x/y, x^y, x%y, +Math operators: (), -x, x+y, x-y, x*y, x/y, x^y, x%y, x == y, x != y, x < y, x <= y, x > y, x >= y, x && y, x || y, !x Math functions: sqrt(x), exp(x), ln(x), log(x), abs(x), sin(x), cos(x), tan(x), asin(x), acos(x), atan(x), atan2(y,x), random(x,y,z), normal(x,y,z), ceil(x), floor(x), round(x), ramp(x,y), stagger(x,y), logfreq(x,y,z), stride(x,y,z), vdisplace(x,y), swiggle(x,y,z), cwiggle(x,y,z) Group functions: count(ID), mass(ID), charge(ID), xcm(ID,dim), \ vcm(ID,dim), fcm(ID,dim), bound(ID,dir), \ From 16dc820a58b4907b4fe00ac9bb9576f6320f7faa Mon Sep 17 00:00:00 2001 From: sjplimp Date: Mon, 28 Jul 2014 18:46:39 +0000 Subject: [PATCH 02/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12199 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- doc/create_atoms.html | 46 ++++++++++++++++++++++++++++++++++++++++- doc/create_atoms.txt | 48 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/doc/create_atoms.html b/doc/create_atoms.html index 6f06935377..eff378a418 100644 --- a/doc/create_atoms.html +++ b/doc/create_atoms.html @@ -31,7 +31,7 @@
  • zero or more keyword/value pairs may be appended -
  • keyword = mol or basis or remap or units +
  • keyword = mol or basis or remap or var or set or units
      mol value = template-ID seed
         template-ID = ID of molecule template specified in a separate molecule command
    @@ -40,6 +40,10 @@
         M = which basis atom
         itype = atom type (1-N) to assign to this basis atom
       remap value = yes or no
    +  var value = name = variable name to evaluate for test of atom creation
    +  set values = dim vname
    +    dim = x or y or z
    +    name = name of variable to set with x,y,z atom position
       units value = lattice or box
         lattice = the geometry is defined in lattice units
         box = the geometry is defined in simulation box units 
    @@ -51,6 +55,7 @@
     
    create_atoms 1 box
     create_atoms 3 region regsphere basis 2 3
     create_atoms 3 single 0 0 5 
    +create_atoms 1 box var v set x xpos set y ypos 
     

    Description:

    @@ -189,6 +194,45 @@ box, it will mapped back into the box, assuming the relevant dimensions are periodic. If it is set to no, no remapping is done and no particle is created if its position is outside the box.

    +

    The var and set keywords can be used to provide a criterion for +accepting or rejecting the addition of an individual atom, based on +its coordinates. The vname specified for the var keyword is the +name of an equal-style variable which should evaluate +to a zero or non-zero value based on one or two or three variables +which will store the x, y, or z coordinates of an atom (one variable +per coordinate). These other variables must be equal-style +variables defined in the input script, but their +formula can by anything. The set keyword is used to identify the +names of these other variables, one variable for the x-coordinate of a +created atom, one for y, and one for z. +

    +

    When an atom is created, its x, y, or z coordinates override the +formula for any set variable that is defined. The var variable is +then evaluated. If the returned value is 0.0, the atom is not +created. If it is non-zero, the atom is created. +

    +

    As an example, these commands can be used in a 2d simulation, to +create a sinusoidal surface. Note that the surface is "rough" due to +individual lattice points being "above" or "below" the mathematical +expression for the sinusoidal curve. If a finer lattice were used, +the sinusoid would appear to be "smoother". Also note the use of the +"xlat" and "ylat" thermo_style keywords which +converts lattice spacings to distance. Click on the image for a +larger version. +

    +
    variable        x equal 100
    +variable        y equal 25
    +lattice		hex 0.8442
    +region		box block 0 $x 0 $y -0.5 0.5
    +create_box	1 box 
    +
    +
    variable        xx equal 0.0
    +variable        yy equal 0.0
    +variable        v equal "(0.2*v_y*ylat * cos(v_xx/xlat * 2.0*PI*4.0/v_x) + 0.5*v_y*ylat - v_yy) > 0.0"
    +create_atoms	1 box var v set x xx set y yy 
    +
    +
    +

    The units keyword determines the meaning of the distance units used to specify the coordinates of the one particle created by the single style. A box value selects standard distance units as defined by diff --git a/doc/create_atoms.txt b/doc/create_atoms.txt index 4552d43e9a..f395b87b32 100644 --- a/doc/create_atoms.txt +++ b/doc/create_atoms.txt @@ -24,7 +24,7 @@ style = {box} or {region} or {single} or {random} :l seed = random # seed (positive integer) region-ID = create atoms within this region, use NULL for entire simulation box :pre zero or more keyword/value pairs may be appended :l -keyword = {mol} or {basis} or {remap} or {units} :l +keyword = {mol} or {basis} or {remap} or {var} or {set} or {units} :l {mol} value = template-ID seed template-ID = ID of molecule template specified in a separate "molecule"_molecule.html command seed = random # seed (positive integer) @@ -32,6 +32,10 @@ keyword = {mol} or {basis} or {remap} or {units} :l M = which basis atom itype = atom type (1-N) to assign to this basis atom {remap} value = {yes} or {no} + {var} value = name = variable name to evaluate for test of atom creation + {set} values = dim vname + dim = {x} or {y} or {z} + name = name of variable to set with x,y,z atom position {units} value = {lattice} or {box} {lattice} = the geometry is defined in lattice units {box} = the geometry is defined in simulation box units :pre @@ -41,7 +45,8 @@ keyword = {mol} or {basis} or {remap} or {units} :l create_atoms 1 box create_atoms 3 region regsphere basis 2 3 -create_atoms 3 single 0 0 5 :pre +create_atoms 3 single 0 0 5 +create_atoms 1 box var v set x xpos set y ypos :pre [Description:] @@ -180,6 +185,45 @@ box, it will mapped back into the box, assuming the relevant dimensions are periodic. If it is set to {no}, no remapping is done and no particle is created if its position is outside the box. +The {var} and {set} keywords can be used to provide a criterion for +accepting or rejecting the addition of an individual atom, based on +its coordinates. The {vname} specified for the {var} keyword is the +name of an "equal-style variable"_variable.html which should evaluate +to a zero or non-zero value based on one or two or three variables +which will store the x, y, or z coordinates of an atom (one variable +per coordinate). These other variables must be "equal-style +variables"_variable.html defined in the input script, but their +formula can by anything. The {set} keyword is used to identify the +names of these other variables, one variable for the x-coordinate of a +created atom, one for y, and one for z. + +When an atom is created, its x, y, or z coordinates override the +formula for any {set} variable that is defined. The {var} variable is +then evaluated. If the returned value is 0.0, the atom is not +created. If it is non-zero, the atom is created. + +As an example, these commands can be used in a 2d simulation, to +create a sinusoidal surface. Note that the surface is "rough" due to +individual lattice points being "above" or "below" the mathematical +expression for the sinusoidal curve. If a finer lattice were used, +the sinusoid would appear to be "smoother". Also note the use of the +"xlat" and "ylat" "thermo_style"_thermo_style.html keywords which +converts lattice spacings to distance. Click on the image for a +larger version. + +variable x equal 100 +variable y equal 25 +lattice hex 0.8442 +region box block 0 $x 0 $y -0.5 0.5 +create_box 1 box :pre + +variable xx equal 0.0 +variable yy equal 0.0 +variable v equal "(0.2*v_y*ylat * cos(v_xx/xlat * 2.0*PI*4.0/v_x) + 0.5*v_y*ylat - v_yy) > 0.0" +create_atoms 1 box var v set x xx set y yy :pre + +:c,image(JPG/sinusoid_small.jpg,JPG/sinusoid.jpg) + The {units} keyword determines the meaning of the distance units used to specify the coordinates of the one particle created by the {single} style. A {box} value selects standard distance units as defined by From cf005551eafb033bfdc37abb18e0d66c46e7411f Mon Sep 17 00:00:00 2001 From: sjplimp Date: Mon, 28 Jul 2014 18:46:50 +0000 Subject: [PATCH 03/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12200 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/create_atoms.cpp | 145 +++++++++++++++++++++++++++++++++++++++---- src/create_atoms.h | 5 ++ 2 files changed, 138 insertions(+), 12 deletions(-) diff --git a/src/create_atoms.cpp b/src/create_atoms.cpp index be5c479360..9777f23334 100644 --- a/src/create_atoms.cpp +++ b/src/create_atoms.cpp @@ -27,6 +27,8 @@ #include "domain.h" #include "lattice.h" #include "region.h" +#include "input.h" +#include "variable.h" #include "random_park.h" #include "random_mars.h" #include "math_extra.h" @@ -41,6 +43,7 @@ using namespace MathConst; enum{BOX,REGION,SINGLE,RANDOM}; enum{ATOM,MOLECULE}; +enum{LAYOUT_UNIFORM,LAYOUT_NONUNIFORM,LAYOUT_TILED}; // several files /* ---------------------------------------------------------------------- */ @@ -103,6 +106,8 @@ void CreateAtoms::command(int narg, char **arg) remapflag = 0; mode = ATOM; int molseed; + varflag = 0; + vstr = xstr = ystr = zstr = NULL; nbasis = domain->lattice->nbasis; basistype = new int[nbasis]; @@ -141,6 +146,33 @@ void CreateAtoms::command(int narg, char **arg) else if (strcmp(arg[iarg+1],"lattice") == 0) scaleflag = 1; else error->all(FLERR,"Illegal create_atoms command"); iarg += 2; + } else if (strcmp(arg[iarg],"var") == 0) { + if (iarg+2 > narg) error->all(FLERR,"Illegal create_atoms command"); + delete [] vstr; + int n = strlen(arg[iarg+1]) + 1; + vstr = new char[n]; + strcpy(vstr,arg[iarg+1]); + varflag = 1; + iarg += 2; + } else if (strcmp(arg[iarg],"set") == 0) { + if (iarg+3 > narg) error->all(FLERR,"Illegal create_atoms command"); + if (strcmp(arg[iarg+1],"x") == 0) { + delete [] xstr; + int n = strlen(arg[iarg+2]) + 1; + xstr = new char[n]; + strcpy(xstr,arg[iarg+2]); + } else if (strcmp(arg[iarg+1],"y") == 0) { + delete [] ystr; + int n = strlen(arg[iarg+2]) + 1; + ystr = new char[n]; + strcpy(ystr,arg[iarg+2]); + } else if (strcmp(arg[iarg+1],"z") == 0) { + delete [] zstr; + int n = strlen(arg[iarg+2]) + 1; + zstr = new char[n]; + strcpy(zstr,arg[iarg+2]); + } else error->all(FLERR,"Illegal create_atoms command"); + iarg += 3; } else error->all(FLERR,"Illegal create_atoms command"); } @@ -178,6 +210,47 @@ void CreateAtoms::command(int narg, char **arg) ranmol = new RanMars(lmp,molseed+comm->me); } + // error check and further setup for variable test + // save local copy of each equal variable string so can restore at end + + if (!vstr && (xstr || ystr || zstr)) + error->all(FLERR,"Incomplete use of variables in create_atoms command"); + if (vstr && (!xstr && !ystr && !zstr)) + error->all(FLERR,"Incomplete use of variables in create_atoms command"); + + if (varflag) { + vvar = input->variable->find(vstr); + if (vvar < 0) + error->all(FLERR,"Variable name for create_atoms does not exist"); + if (!input->variable->equalstyle(vvar)) + error->all(FLERR,"Variable for create_atoms is invalid style"); + + if (xstr) { + xvar = input->variable->find(xstr); + if (xvar < 0) + error->all(FLERR,"Variable name for create_atoms does not exist"); + if (!input->variable->equalstyle(xvar)) + error->all(FLERR,"Variable for create_atoms is invalid style"); + input->variable->equal_save(xvar,xstr_copy); + } + if (ystr) { + yvar = input->variable->find(ystr); + if (yvar < 0) + error->all(FLERR,"Variable name for create_atoms does not exist"); + if (!input->variable->equalstyle(yvar)) + error->all(FLERR,"Variable for create_atoms is invalid style"); + input->variable->equal_save(yvar,ystr_copy); + } + if (zstr) { + zvar = input->variable->find(zstr); + if (zvar < 0) + error->all(FLERR,"Variable name for create_atoms does not exist"); + if (!input->variable->equalstyle(zvar)) + error->all(FLERR,"Variable for create_atoms is invalid style"); + input->variable->equal_save(zvar,zstr_copy); + } + } + // demand non-none lattice be defined for BOX and REGION // else setup scaling for SINGLE and RANDOM // could use domain->lattice->lattice2box() to do conversion of @@ -226,17 +299,32 @@ void CreateAtoms::command(int narg, char **arg) } if (style == BOX || style == REGION) { - if (domain->xperiodic) { - if (comm->myloc[0] == 0) sublo[0] -= epsilon[0]; - if (comm->myloc[0] == comm->procgrid[0]-1) subhi[0] -= 2.0*epsilon[0]; - } - if (domain->yperiodic) { - if (comm->myloc[1] == 0) sublo[1] -= epsilon[1]; - if (comm->myloc[1] == comm->procgrid[1]-1) subhi[1] -= 2.0*epsilon[1]; - } - if (domain->zperiodic) { - if (comm->myloc[2] == 0) sublo[2] -= epsilon[2]; - if (comm->myloc[2] == comm->procgrid[2]-1) subhi[2] -= 2.0*epsilon[2]; + if (comm->layout != LAYOUT_TILED) { + if (domain->xperiodic) { + if (comm->myloc[0] == 0) sublo[0] -= epsilon[0]; + if (comm->myloc[0] == comm->procgrid[0]-1) subhi[0] -= 2.0*epsilon[0]; + } + if (domain->yperiodic) { + if (comm->myloc[1] == 0) sublo[1] -= epsilon[1]; + if (comm->myloc[1] == comm->procgrid[1]-1) subhi[1] -= 2.0*epsilon[1]; + } + if (domain->zperiodic) { + if (comm->myloc[2] == 0) sublo[2] -= epsilon[2]; + if (comm->myloc[2] == comm->procgrid[2]-1) subhi[2] -= 2.0*epsilon[2]; + } + } else { + if (domain->xperiodic) { + if (comm->mysplit[0][0] == 0.0) sublo[0] -= epsilon[0]; + if (comm->mysplit[0][1] == 1.0) subhi[0] -= 2.0*epsilon[0]; + } + if (domain->yperiodic) { + if (comm->mysplit[1][0] == 0.0) sublo[1] -= epsilon[1]; + if (comm->mysplit[1][1] == 1.0) subhi[1] -= 2.0*epsilon[1]; + } + if (domain->zperiodic) { + if (comm->mysplit[2][0] == 0.0) sublo[2] -= epsilon[2]; + if (comm->mysplit[2][1] == 1.0) subhi[2] -= 2.0*epsilon[2]; + } } } @@ -259,6 +347,14 @@ void CreateAtoms::command(int narg, char **arg) fix->set_arrays(i); } + // restore each equal variable string previously saved + + if (varflag) { + if (xstr) input->variable->equal_restore(xvar,xstr_copy); + if (ystr) input->variable->equal_restore(yvar,ystr_copy); + if (zstr) input->variable->equal_restore(zvar,zstr_copy); + } + // set new total # of atoms and error check bigint nblocal = atom->nlocal; @@ -409,6 +505,10 @@ void CreateAtoms::command(int narg, char **arg) delete ranmol; if (domain->lattice) delete [] basistype; + delete [] vstr; + delete [] xstr; + delete [] ystr; + delete [] zstr; // print status @@ -509,7 +609,7 @@ void CreateAtoms::add_random() } // generate random positions for each new atom/molecule within bounding box - // iterate until atom is within region and triclinic simulation box + // iterate until atom is within region, variable, and triclinic simulation box // if final atom position is in my subbox, create it if (xlo > xhi || ylo > yhi || zlo > zhi) @@ -527,6 +627,7 @@ void CreateAtoms::add_random() if (nregion >= 0 && domain->regions[nregion]->match(xone[0],xone[1],xone[2]) == 0) valid = 0; + if (varflag && vartest(xone) == 0) valid = 0; if (triclinic) { domain->x2lamda(xone,lamda); coord = lamda; @@ -642,6 +743,10 @@ void CreateAtoms::add_lattice() if (style == REGION) if (!domain->regions[nregion]->match(x[0],x[1],x[2])) continue; + // if variable test specified, eval variable + + if (varflag && vartest(x) == 0) continue; + // test if atom/molecule position is in my subbox if (triclinic) { @@ -696,3 +801,19 @@ void CreateAtoms::add_molecule(double *center) atom->add_molecule_atom(onemol,m,n,0); } } + +/* ---------------------------------------------------------------------- + test a generated atom position against variable evaluation + first plug in x,y,z values as requested +------------------------------------------------------------------------- */ + +int CreateAtoms::vartest(double *x) +{ + if (xstr) input->variable->equal_override(xvar,x[0]); + if (ystr) input->variable->equal_override(yvar,x[1]); + if (zstr) input->variable->equal_override(zvar,x[2]); + + double value = input->variable->compute_equal(vvar); + if (value == 0.0) return 0; + return 1; +} diff --git a/src/create_atoms.h b/src/create_atoms.h index f20eff66f1..1d9593cdf7 100644 --- a/src/create_atoms.h +++ b/src/create_atoms.h @@ -35,6 +35,10 @@ class CreateAtoms : protected Pointers { double xone[3]; int remapflag; + int varflag,vvar,xvar,yvar,zvar; + char *vstr,*xstr,*ystr,*zstr; + char *xstr_copy,*ystr_copy,*zstr_copy; + class Molecule *onemol; class RanMars *ranmol; @@ -45,6 +49,7 @@ class CreateAtoms : protected Pointers { void add_random(); void add_lattice(); void add_molecule(double *); + int vartest(double *); // evaluate a variable with new atom position }; } From 33d9ff4bf4453f22f4817311a942b8f6daa86ef7 Mon Sep 17 00:00:00 2001 From: sjplimp Date: Mon, 28 Jul 2014 18:46:56 +0000 Subject: [PATCH 04/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12201 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/variable.cpp | 69 +++++++++++++++++++++++++++++++++++------------- src/variable.h | 6 ++++- 2 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/variable.cpp b/src/variable.cpp index fe11239e9f..b468ca6a74 100644 --- a/src/variable.cpp +++ b/src/variable.cpp @@ -44,6 +44,7 @@ using namespace MathConst; #define MAXLEVEL 4 #define MAXLINE 256 #define CHUNK 1024 +#define VALUELENGTH 64 #define MYROUND(a) (( a-floor(a) ) >= .5) ? ceil(a) : floor(a) @@ -306,7 +307,7 @@ void Variable::set(int narg, char **arg) pad[nvar] = 0; data[nvar] = new char*[num[nvar]]; copy(1,&arg[2],data[nvar]); - data[nvar][1] = NULL; + data[nvar][1] = new char[VALUELENGTH]; // SCALARFILE for strings or numbers // which = 1st value @@ -360,7 +361,7 @@ void Variable::set(int narg, char **arg) pad[nvar] = 0; data[nvar] = new char*[num[nvar]]; copy(2,&arg[2],data[nvar]); - data[nvar][2] = NULL; + data[nvar][2] = new char[VALUELENGTH]; // EQUAL // replace pre-existing var if also style EQUAL (allows it to be reset) @@ -376,7 +377,6 @@ void Variable::set(int narg, char **arg) delete [] data[ivar][0]; if (data[ivar][1]) delete [] data[ivar][1]; copy(1,&arg[2],data[ivar]); - data[ivar][1] = NULL; replaceflag = 1; } else { if (nvar == maxvar) grow(); @@ -386,7 +386,7 @@ void Variable::set(int narg, char **arg) pad[nvar] = 0; data[nvar] = new char*[num[nvar]]; copy(1,&arg[2],data[nvar]); - data[nvar][1] = NULL; + data[nvar][1] = new char[VALUELENGTH]; } // ATOM @@ -625,32 +625,24 @@ char *Variable::retrieve(char *name) strcpy(data[ivar][0],result); str = data[ivar][0]; } else if (style[ivar] == EQUAL) { - char result[64]; double answer = evaluate(data[ivar][0],NULL); - sprintf(result,"%.15g",answer); - int n = strlen(result) + 1; - if (data[ivar][1]) delete [] data[ivar][1]; - data[ivar][1] = new char[n]; - strcpy(data[ivar][1],result); + sprintf(data[ivar][1],"%.15g",answer); str = data[ivar][1]; } else if (style[ivar] == FORMAT) { - char result[64]; int jvar = find(data[ivar][0]); if (jvar == -1) return NULL; if (!equalstyle(jvar)) return NULL; double answer = evaluate(data[jvar][0],NULL); - sprintf(result,data[ivar][1],answer); - int n = strlen(result) + 1; - if (data[ivar][2]) delete [] data[ivar][2]; - data[ivar][2] = new char[n]; - strcpy(data[ivar][2],result); + sprintf(data[ivar][2],data[ivar][1],answer); str = data[ivar][2]; } else if (style[ivar] == GETENV) { const char *result = getenv(data[ivar][0]); - if (data[ivar][1]) delete [] data[ivar][1]; - if (result == NULL) result = (const char *)""; + if (result == NULL) result = (const char *) ""; int n = strlen(result) + 1; - data[ivar][1] = new char[n]; + if (n > VALUELENGTH) { + delete [] data[ivar][1]; + data[ivar][1] = new char[n]; + } strcpy(data[ivar][1],result); str = data[ivar][1]; } else if (style[ivar] == ATOM || style[ivar] == ATOMFILE) return NULL; @@ -782,6 +774,45 @@ int Variable::atomstyle(int ivar) return 0; } +/* ---------------------------------------------------------------------- + save copy of EQUAL style ivar formula in copy + allocate copy here, later equal_restore() call will free it + insure data[ivar][0] is of VALUELENGTH since will be overridden +------------------------------------------------------------------------- */ + +void Variable::equal_save(int ivar, char *©) +{ + int n = strlen(data[ivar][0]) + 1; + copy = new char[n]; + strcpy(copy,data[ivar][0]); + delete [] data[ivar][0]; + data[ivar][0] = new char[VALUELENGTH]; +} + +/* ---------------------------------------------------------------------- + restore formula string of EQUAL style ivar from copy + then free copy, allocated in equal_save() +------------------------------------------------------------------------- */ + +void Variable::equal_restore(int ivar, char *copy) +{ + delete [] data[ivar][0]; + int n = strlen(copy) + 1; + data[ivar][0] = new char[n]; + strcpy(data[ivar][0],copy); + delete [] copy; +} + +/* ---------------------------------------------------------------------- + override EQUAL style ivar formula with value converted to string + data[ivar][0] was set to length 64 in equal_save() +------------------------------------------------------------------------- */ + +void Variable::equal_override(int ivar, double value) +{ + sprintf(data[ivar][0],"%.15g",value); +} + /* ---------------------------------------------------------------------- remove Nth variable from list and compact list delete reader explicitly if it exists diff --git a/src/variable.h b/src/variable.h index cf4e76e33e..4870b5e5d9 100644 --- a/src/variable.h +++ b/src/variable.h @@ -36,10 +36,15 @@ class Variable : protected Pointers { int int_between_brackets(char *&, int); double evaluate_boolean(char *); + void equal_save(int, char *&); + void equal_restore(int, char *); + void equal_override(int, double); + unsigned int data_mask(int ivar); unsigned int data_mask(char *str); private: + int me; int nvar; // # of defined variables int maxvar; // max # of variables following lists can hold char **names; // name of each variable @@ -57,7 +62,6 @@ class Variable : protected Pointers { int precedence[17]; // precedence level of math operators // set length to include up to OR in enum - int me; struct Tree { // parse tree for atom-style variables double value; // single scalar From d03dccfa0783b439a77570c6a66cb561333a5bfa Mon Sep 17 00:00:00 2001 From: sjplimp Date: Mon, 28 Jul 2014 18:47:18 +0000 Subject: [PATCH 05/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12202 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- doc/JPG/sinusoid.jpg | Bin 0 -> 128596 bytes doc/JPG/sinusoid_small.jpg | Bin 0 -> 29397 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/JPG/sinusoid.jpg create mode 100644 doc/JPG/sinusoid_small.jpg diff --git a/doc/JPG/sinusoid.jpg b/doc/JPG/sinusoid.jpg new file mode 100644 index 0000000000000000000000000000000000000000..00969281a1ba2bc91ae2c038eb9279b4cc8dd79a GIT binary patch literal 128596 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%0D<$E6d$D55!FUuIv(hUTnsqzCUV1U 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 literal 0 HcmV?d00001 diff --git a/doc/JPG/sinusoid_small.jpg b/doc/JPG/sinusoid_small.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1643d8169890f699ce6cbe18477994d0ace75f0c GIT binary patch literal 29397 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 Date: Mon, 28 Jul 2014 18:50:33 +0000 Subject: [PATCH 06/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12203 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h b/src/version.h index 25d90d9612..9abc4b939e 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define LAMMPS_VERSION "22 Jul 2014" +#define LAMMPS_VERSION "28 Jul 2014" From 032632ee338b97be0f5b3c4fa5e6a4d37a7bc308 Mon Sep 17 00:00:00 2001 From: sjplimp Date: Mon, 28 Jul 2014 18:50:33 +0000 Subject: [PATCH 07/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12204 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- doc/Manual.html | 4 ++-- doc/Manual.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/Manual.html b/doc/Manual.html index cbb0a34c7b..193368826d 100644 --- a/doc/Manual.html +++ b/doc/Manual.html @@ -1,7 +1,7 @@ LAMMPS Users Manual - + @@ -22,7 +22,7 @@

    LAMMPS Documentation

    -

    22 Jul 2014 version +

    28 Jul 2014 version

    Version info:

    diff --git a/doc/Manual.txt b/doc/Manual.txt index a05b429b9a..2c01ce13c3 100644 --- a/doc/Manual.txt +++ b/doc/Manual.txt @@ -1,6 +1,6 @@ LAMMPS Users Manual - + @@ -18,7 +18,7 @@

    LAMMPS Documentation :c,h3 -22 Jul 2014 version :c,h4 +28 Jul 2014 version :c,h4 Version info: :h4 From 3cc23720e97b5be514c5fe3e420060876eab1660 Mon Sep 17 00:00:00 2001 From: sjplimp Date: Mon, 28 Jul 2014 19:58:18 +0000 Subject: [PATCH 08/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12206 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- doc/pair_table.html | 4 ++-- doc/pair_table.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/pair_table.html b/doc/pair_table.html index 21d9fc9d1f..1919b40002 100644 --- a/doc/pair_table.html +++ b/doc/pair_table.html @@ -119,8 +119,8 @@ to very steep parts of the potential.
    -

    The format of a tabulated file is as follows (without the -parenthesized comments): +

    The format of a tabulated file is a series of one or more sections, +defined as follows (without the parenthesized comments):

    # Morse potential for Fe   (one or more comment or blank lines) 
     
    diff --git a/doc/pair_table.txt b/doc/pair_table.txt index 4b83d2a8f5..8938de20b7 100644 --- a/doc/pair_table.txt +++ b/doc/pair_table.txt @@ -113,8 +113,8 @@ to very steep parts of the potential. :l,ule :line -The format of a tabulated file is as follows (without the -parenthesized comments): +The format of a tabulated file is a series of one or more sections, +defined as follows (without the parenthesized comments): # Morse potential for Fe (one or more comment or blank lines) :pre From 4455b66003ce133bf2daa6feb9b4d9d4c53720fa Mon Sep 17 00:00:00 2001 From: sjplimp Date: Mon, 28 Jul 2014 19:58:51 +0000 Subject: [PATCH 09/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12207 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/USER-CUDA/pppm_cuda.cpp | 3 + src/USER-LB/fix_lb_fluid.cpp | 9 +- src/atom.cpp | 40 +- src/balance.cpp | 214 +++++--- src/balance.h | 7 +- src/comm.cpp | 74 ++- src/comm.h | 30 +- src/comm_brick.cpp | 127 ++--- src/comm_brick.h | 2 + src/comm_tiled.cpp | 691 ++++++++++++++++++++------ src/comm_tiled.h | 35 +- src/domain.cpp | 72 ++- src/fix_balance.cpp | 40 +- src/fix_deform.cpp | 11 + src/fix_deform.h | 1 + src/fix_nh.cpp | 11 + src/fix_nh.h | 1 + src/input.cpp | 20 +- src/irregular.cpp | 557 +++++++++++---------- src/irregular.h | 76 +-- src/rcb.cpp | 926 +++++++++++++++++++++++++++++++++++ src/rcb.h | 131 +++++ src/replicate.cpp | 40 +- 23 files changed, 2418 insertions(+), 700 deletions(-) create mode 100644 src/rcb.cpp create mode 100644 src/rcb.h diff --git a/src/USER-CUDA/pppm_cuda.cpp b/src/USER-CUDA/pppm_cuda.cpp index 9754dd1f94..2a2973c900 100644 --- a/src/USER-CUDA/pppm_cuda.cpp +++ b/src/USER-CUDA/pppm_cuda.cpp @@ -211,6 +211,9 @@ void PPPMCuda::init() // error check if (domain->dimension == 2) error->all(FLERR,"Cannot use PPPMCuda with 2d simulation"); + if (comm->style != 0) + error->universe_all(FLERR,"PPPMCuda can only currently be used with " + "comm_style brick"); if (!atom->q_flag) error->all(FLERR,"Kspace style requires atom attribute q"); diff --git a/src/USER-LB/fix_lb_fluid.cpp b/src/USER-LB/fix_lb_fluid.cpp index 72a1b412c0..b76f5cc620 100755 --- a/src/USER-LB/fix_lb_fluid.cpp +++ b/src/USER-LB/fix_lb_fluid.cpp @@ -88,6 +88,10 @@ FixLbFluid::FixLbFluid(LAMMPS *lmp, int narg, char **arg) : if(narg <7) error->all(FLERR,"Illegal fix lb/fluid command"); + if (comm->style != 0) + error->universe_all(FLERR,"Fix lb/fluid can only currently be used with " + "comm_style brick"); + MPI_Comm_rank(world,&me); MPI_Comm_size(world,&nprocs); @@ -567,9 +571,12 @@ int FixLbFluid::setmask() void FixLbFluid::init(void) { - int i,j; + if (comm->style != 0) + error->universe_all(FLERR,"Fix lb/fluid can only currently be used with " + "comm_style brick"); + //-------------------------------------------------------------------------- // Check to see if the MD timestep has changed between runs. //-------------------------------------------------------------------------- diff --git a/src/atom.cpp b/src/atom.cpp index 1d47b11148..7efbf4740f 100644 --- a/src/atom.cpp +++ b/src/atom.cpp @@ -47,6 +47,8 @@ using namespace MathConst; #define CUDA_CHUNK 3000 #define MAXBODY 20 // max # of lines in one body, also in ReadData class +enum{LAYOUT_UNIFORM,LAYOUT_NONUNIFORM,LAYOUT_TILED}; // several files + /* ---------------------------------------------------------------------- */ Atom::Atom(LAMMPS *lmp) : Pointers(lmp) @@ -753,17 +755,33 @@ void Atom::data_atoms(int n, char *buf) sublo[2] = domain->sublo_lamda[2]; subhi[2] = domain->subhi_lamda[2]; } - if (domain->xperiodic) { - if (comm->myloc[0] == 0) sublo[0] -= epsilon[0]; - if (comm->myloc[0] == comm->procgrid[0]-1) subhi[0] += epsilon[0]; - } - if (domain->yperiodic) { - if (comm->myloc[1] == 0) sublo[1] -= epsilon[1]; - if (comm->myloc[1] == comm->procgrid[1]-1) subhi[1] += epsilon[1]; - } - if (domain->zperiodic) { - if (comm->myloc[2] == 0) sublo[2] -= epsilon[2]; - if (comm->myloc[2] == comm->procgrid[2]-1) subhi[2] += epsilon[2]; + if (comm->layout != LAYOUT_TILED) { + if (domain->xperiodic) { + if (comm->myloc[0] == 0) sublo[0] -= epsilon[0]; + if (comm->myloc[0] == comm->procgrid[0]-1) subhi[0] += epsilon[0]; + } + if (domain->yperiodic) { + if (comm->myloc[1] == 0) sublo[1] -= epsilon[1]; + if (comm->myloc[1] == comm->procgrid[1]-1) subhi[1] += epsilon[1]; + } + if (domain->zperiodic) { + if (comm->myloc[2] == 0) sublo[2] -= epsilon[2]; + if (comm->myloc[2] == comm->procgrid[2]-1) subhi[2] += epsilon[2]; + } + + } else { + if (domain->xperiodic) { + if (comm->mysplit[0][0] == 0.0) sublo[0] -= epsilon[0]; + if (comm->mysplit[0][1] == 1.0) subhi[0] += epsilon[0]; + } + if (domain->yperiodic) { + if (comm->mysplit[1][0] == 0.0) sublo[1] -= epsilon[1]; + if (comm->mysplit[1][1] == 1.0) subhi[1] += epsilon[1]; + } + if (domain->zperiodic) { + if (comm->mysplit[2][0] == 0.0) sublo[2] -= epsilon[2]; + if (comm->mysplit[2][1] == 1.0) subhi[2] += epsilon[2]; + } } // xptr = which word in line starts xyz coords diff --git a/src/balance.cpp b/src/balance.cpp index a76a7ce364..89f0ccb5cc 100644 --- a/src/balance.cpp +++ b/src/balance.cpp @@ -21,6 +21,7 @@ #include "balance.h" #include "atom.h" #include "comm.h" +#include "rcb.h" #include "irregular.h" #include "domain.h" #include "force.h" @@ -30,9 +31,10 @@ using namespace LAMMPS_NS; -enum{XYZ,SHIFT,RCB}; +enum{XYZ,SHIFT,BISECTION}; enum{NONE,UNIFORM,USER}; enum{X,Y,Z}; +enum{LAYOUT_UNIFORM,LAYOUT_NONUNIFORM,LAYOUT_TILED}; // several files /* ---------------------------------------------------------------------- */ @@ -47,6 +49,8 @@ Balance::Balance(LAMMPS *lmp) : Pointers(lmp) user_xsplit = user_ysplit = user_zsplit = NULL; shift_allocate = 0; + rcb = NULL; + fp = NULL; firststep = 1; } @@ -74,6 +78,8 @@ Balance::~Balance() delete [] hisum; } + delete rcb; + if (fp) fclose(fp); } @@ -176,7 +182,7 @@ void Balance::command(int narg, char **arg) } else if (strcmp(arg[iarg],"rcb") == 0) { if (style != -1) error->all(FLERR,"Illegal balance command"); - style = RCB; + style = BISECTION; iarg++; } else break; @@ -232,6 +238,9 @@ void Balance::command(int narg, char **arg) } } + if (style == BISECTION && comm->style == 0) + error->all(FLERR,"Balance rcb cannot be used with comm_style brick"); + // insure atoms are in current box & update box via shrink-wrap // init entire system since comm->setup is done // comm::init needs neighbor::init needs pair::init needs kspace::init, etc @@ -251,8 +260,10 @@ void Balance::command(int narg, char **arg) double imbinit = imbalance_nlocal(maxinit); // no load-balance if imbalance doesn't exceed threshhold + // unless switching from tiled to non tiled layout, then force rebalance - if (imbinit < thresh) return; + if (comm->layout == LAYOUT_TILED && style != BISECTION) { + } else if (imbinit < thresh) return; // debug output of initial state @@ -262,80 +273,65 @@ void Balance::command(int narg, char **arg) int niter = 0; - // NOTE: if using XYZ or SHIFT and current partition is TILING, - // then need to create initial BRICK partition before performing LB - // perform load-balance // style XYZ = explicit setting of cutting planes of logical 3d grid if (style == XYZ) { + if (comm->layout == LAYOUT_UNIFORM) { + if (xflag == USER || yflag == USER || zflag == USER) + comm->layout == LAYOUT_NONUNIFORM; + } else if (comm->style == LAYOUT_NONUNIFORM) { + if (xflag == UNIFORM && yflag == UNIFORM && zflag == UNIFORM) + comm->layout == LAYOUT_UNIFORM; + } else if (comm->style == LAYOUT_TILED) { + if (xflag == UNIFORM && yflag == UNIFORM && zflag == UNIFORM) + comm->layout == LAYOUT_UNIFORM; + else comm->layout == LAYOUT_NONUNIFORM; + } + if (xflag == UNIFORM) { for (int i = 0; i < procgrid[0]; i++) comm->xsplit[i] = i * 1.0/procgrid[0]; comm->xsplit[procgrid[0]] = 1.0; - } + } else if (xflag == USER) + for (int i = 0; i <= procgrid[0]; i++) comm->xsplit[i] = user_xsplit[i]; if (yflag == UNIFORM) { for (int i = 0; i < procgrid[1]; i++) comm->ysplit[i] = i * 1.0/procgrid[1]; comm->ysplit[procgrid[1]] = 1.0; - } + } else if (yflag == USER) + for (int i = 0; i <= procgrid[1]; i++) comm->ysplit[i] = user_ysplit[i]; if (zflag == UNIFORM) { for (int i = 0; i < procgrid[2]; i++) comm->zsplit[i] = i * 1.0/procgrid[2]; comm->zsplit[procgrid[2]] = 1.0; - } - - if (xflag == USER) - for (int i = 0; i <= procgrid[0]; i++) comm->xsplit[i] = user_xsplit[i]; - - if (yflag == USER) - for (int i = 0; i <= procgrid[1]; i++) comm->ysplit[i] = user_ysplit[i]; - - if (zflag == USER) + } else if (zflag == USER) for (int i = 0; i <= procgrid[2]; i++) comm->zsplit[i] = user_zsplit[i]; } // style SHIFT = adjust cutting planes of logical 3d grid if (style == SHIFT) { - static_setup(bstr); + comm->layout = LAYOUT_NONUNIFORM; + shift_setup_static(bstr); niter = shift(); } - // style RCB = + // style BISECTION = recursive coordinate bisectioning - if (style == RCB) { - error->all(FLERR,"Balance rcb is not yet supported"); - - if (comm->style == 0) - error->all(FLERR,"Cannot use balance rcb with comm_style brick"); + if (style == BISECTION) { + comm->layout = LAYOUT_TILED; + bisection(1); } // output of final result if (outflag && me == 0) dumpout(update->ntimestep,fp); - // reset comm->uniform flag if necessary - - if (comm->uniform) { - if (style == SHIFT) comm->uniform = 0; - if (style == XYZ && xflag == USER) comm->uniform = 0; - if (style == XYZ && yflag == USER) comm->uniform = 0; - if (style == XYZ && zflag == USER) comm->uniform = 0; - } else { - if (dimension == 3) { - if (style == XYZ && - xflag == UNIFORM && yflag == UNIFORM && zflag == UNIFORM) - comm->uniform = 1; - } else { - if (style == XYZ && xflag == UNIFORM && yflag == UNIFORM) - comm->uniform = 1; - } - } - // reset proc sub-domains + // for either brick or tiled comm style if (domain->triclinic) domain->set_lamda_box(); domain->set_local_box(); @@ -344,7 +340,8 @@ void Balance::command(int narg, char **arg) if (domain->triclinic) domain->x2lamda(atom->nlocal); Irregular *irregular = new Irregular(lmp); - irregular->migrate_atoms(1); + if (style == BISECTION) irregular->migrate_atoms(1,rcb->sendproc); + else irregular->migrate_atoms(1); delete irregular; if (domain->triclinic) domain->lamda2x(atom->nlocal); @@ -382,34 +379,36 @@ void Balance::command(int narg, char **arg) } } - if (me == 0) { - if (screen) { - fprintf(screen," x cuts:"); - for (int i = 0; i <= comm->procgrid[0]; i++) - fprintf(screen," %g",comm->xsplit[i]); - fprintf(screen,"\n"); - fprintf(screen," y cuts:"); - for (int i = 0; i <= comm->procgrid[1]; i++) - fprintf(screen," %g",comm->ysplit[i]); - fprintf(screen,"\n"); - fprintf(screen," z cuts:"); - for (int i = 0; i <= comm->procgrid[2]; i++) - fprintf(screen," %g",comm->zsplit[i]); - fprintf(screen,"\n"); - } - if (logfile) { - fprintf(logfile," x cuts:"); - for (int i = 0; i <= comm->procgrid[0]; i++) - fprintf(logfile," %g",comm->xsplit[i]); - fprintf(logfile,"\n"); - fprintf(logfile," y cuts:"); - for (int i = 0; i <= comm->procgrid[1]; i++) - fprintf(logfile," %g",comm->ysplit[i]); - fprintf(logfile,"\n"); - fprintf(logfile," z cuts:"); - for (int i = 0; i <= comm->procgrid[2]; i++) - fprintf(logfile," %g",comm->zsplit[i]); - fprintf(logfile,"\n"); + if (style != BISECTION) { + if (me == 0) { + if (screen) { + fprintf(screen," x cuts:"); + for (int i = 0; i <= comm->procgrid[0]; i++) + fprintf(screen," %g",comm->xsplit[i]); + fprintf(screen,"\n"); + fprintf(screen," y cuts:"); + for (int i = 0; i <= comm->procgrid[1]; i++) + fprintf(screen," %g",comm->ysplit[i]); + fprintf(screen,"\n"); + fprintf(screen," z cuts:"); + for (int i = 0; i <= comm->procgrid[2]; i++) + fprintf(screen," %g",comm->zsplit[i]); + fprintf(screen,"\n"); + } + if (logfile) { + fprintf(logfile," x cuts:"); + for (int i = 0; i <= comm->procgrid[0]; i++) + fprintf(logfile," %g",comm->xsplit[i]); + fprintf(logfile,"\n"); + fprintf(logfile," y cuts:"); + for (int i = 0; i <= comm->procgrid[1]; i++) + fprintf(logfile," %g",comm->ysplit[i]); + fprintf(logfile,"\n"); + fprintf(logfile," z cuts:"); + for (int i = 0; i <= comm->procgrid[2]; i++) + fprintf(logfile," %g",comm->zsplit[i]); + fprintf(logfile,"\n"); + } } } } @@ -467,13 +466,52 @@ double Balance::imbalance_splits(int &max) return imbalance; } +/* ---------------------------------------------------------------------- + perform balancing via RCB class + sortflag = flag for sorting order of received messages by proc ID +------------------------------------------------------------------------- */ + +int *Balance::bisection(int sortflag) +{ + if (!rcb) rcb = new RCB(lmp); + + // NOTE: lo/hi args could be simulation box or particle bounding box + // NOTE: triclinic needs to be in lamda coords + + int dim = domain->dimension; + double *boxlo = domain->boxlo; + double *boxhi = domain->boxhi; + double *prd = domain->prd; + + rcb->compute(dim,atom->nlocal,atom->x,NULL,boxlo,boxhi); + rcb->invert(sortflag); + + // NOTE: this logic is specific to orthogonal boxes, not triclinic + + double (*mysplit)[2] = comm->mysplit; + + mysplit[0][0] = (rcb->lo[0] - boxlo[0]) / prd[0]; + if (rcb->hi[0] == boxhi[0]) mysplit[0][1] = 1.0; + else mysplit[0][1] = (rcb->hi[0] - boxlo[0]) / prd[0]; + + mysplit[1][0] = (rcb->lo[1] - boxlo[1]) / prd[1]; + if (rcb->hi[1] == boxhi[1]) mysplit[1][1] = 1.0; + else mysplit[1][1] = (rcb->hi[1] - boxlo[1]) / prd[1]; + + mysplit[2][0] = (rcb->lo[2] - boxlo[2]) / prd[2]; + if (rcb->hi[2] == boxhi[2]) mysplit[2][1] = 1.0; + else mysplit[2][1] = (rcb->hi[2] - boxlo[2]) / prd[2]; + + return rcb->sendproc; +} + /* ---------------------------------------------------------------------- setup static load balance operations - called from command + called from command and indirectly initially from fix balance set rho = 0 for static balancing ------------------------------------------------------------------------- */ -void Balance::static_setup(char *str) +void Balance::shift_setup_static(char *str) { shift_allocate = 1; @@ -498,21 +536,35 @@ void Balance::static_setup(char *str) losum = new bigint[max+1]; hisum = new bigint[max+1]; + // if current layout is TILED, set initial uniform splits in Comm + // this gives starting point to subsequent shift balancing + + if (comm->layout == LAYOUT_TILED) { + int *procgrid = comm->procgrid; + double *xsplit = comm->xsplit; + double *ysplit = comm->ysplit; + double *zsplit = comm->zsplit; + + for (int i = 0; i < procgrid[0]; i++) xsplit[i] = i * 1.0/procgrid[0]; + for (int i = 0; i < procgrid[1]; i++) ysplit[i] = i * 1.0/procgrid[1]; + for (int i = 0; i < procgrid[2]; i++) zsplit[i] = i * 1.0/procgrid[2]; + xsplit[procgrid[0]] = ysplit[procgrid[1]] = zsplit[procgrid[2]] = 1.0; + } + rho = 0; } /* ---------------------------------------------------------------------- setup shift load balance operations called from fix balance - set rho = 1 for shift balancing after call to shift_setup() + set rho = 1 to do dynamic balancing after call to shift_setup_static() ------------------------------------------------------------------------- */ void Balance::shift_setup(char *str, int nitermax_in, double thresh_in) { - static_setup(str); + shift_setup_static(str); nitermax = nitermax_in; stopthresh = thresh_in; - rho = 1; } @@ -525,7 +577,7 @@ void Balance::shift_setup(char *str, int nitermax_in, double thresh_in) int Balance::shift() { int i,j,k,m,np,max; - double *split = NULL; + double *split; // no balancing if no atoms @@ -590,7 +642,7 @@ int Balance::shift() // iterate until balanced #ifdef BALANCE_DEBUG - if (me == 0) debug_output(idim,0,np,split); + if (me == 0) debug_shift_output(idim,0,np,split); #endif int doneflag; @@ -601,7 +653,7 @@ int Balance::shift() niter++; #ifdef BALANCE_DEBUG - if (me == 0) debug_output(idim,m+1,np,split); + if (me == 0) debug_shift_output(idim,m+1,np,split); if (me == 0 && fp) dumpout(update->ntimestep,fp); #endif @@ -827,7 +879,7 @@ void Balance::dumpout(bigint tstep, FILE *bfp) int nx = comm->procgrid[0] + 1; int ny = comm->procgrid[1] + 1; - //int nz = comm->procgrid[2] + 1; + int nz = comm->procgrid[2] + 1; if (dimension == 2) { int m = 0; @@ -914,7 +966,7 @@ void Balance::dumpout(bigint tstep, FILE *bfp) ------------------------------------------------------------------------- */ #ifdef BALANCE_DEBUG -void Balance::debug_output(int idim, int m, int np, double *split) +void Balance::debug_shift_output(int idim, int m, int np, double *split) { int i; const char *dim = NULL; diff --git a/src/balance.h b/src/balance.h index 66ad6db721..69ff41abaf 100644 --- a/src/balance.h +++ b/src/balance.h @@ -27,11 +27,14 @@ namespace LAMMPS_NS { class Balance : protected Pointers { public: + class RCB *rcb; + Balance(class LAMMPS *); ~Balance(); void command(int, char **); void shift_setup(char *, int, double); int shift(); + int *bisection(int sortflag = 0); double imbalance_nlocal(int &); void dumpout(bigint, FILE *); @@ -66,13 +69,13 @@ class Balance : protected Pointers { FILE *fp; int firststep; - void static_setup(char *); double imbalance_splits(int &); + void shift_setup_static(char *); void tally(int, int, double *); int adjust(int, double *); int binary(double, int, double *); #ifdef BALANCE_DEBUG - void debug_output(int, int, int, double *); + void debug_shift_output(int, int, int, double *); #endif }; diff --git a/src/comm.cpp b/src/comm.cpp index a80db36d95..2d0758fc8e 100644 --- a/src/comm.cpp +++ b/src/comm.cpp @@ -47,25 +47,95 @@ Comm::Comm(LAMMPS *lmp) : Pointers(lmp) gridflag = ONELEVEL; mapflag = CART; customfile = NULL; + outfile = NULL; recv_from_partition = send_to_partition = -1; otherflag = 0; - outfile = NULL; + maxexchange_atom = maxexchange_fix = 0; + grid2proc = NULL; xsplit = ysplit = zsplit = NULL; + + // use of OpenMP threads + // query OpenMP for number of threads/process set by user at run-time + // if the OMP_NUM_THREADS environment variable is not set, we default + // to using 1 thread. This follows the principle of the least surprise, + // while practically all OpenMP implementations violate it by using + // as many threads as there are (virtual) CPU cores by default. + + nthreads = 1; +#ifdef _OPENMP + if (lmp->kokkos) { + nthreads = lmp->kokkos->num_threads * lmp->kokkos->numa; + } else if (getenv("OMP_NUM_THREADS") == NULL) { + nthreads = 1; + if (me == 0) + error->warning(FLERR,"OMP_NUM_THREADS environment is not set."); + } else { + nthreads = omp_get_max_threads(); + } + + // enforce consistent number of threads across all MPI tasks + + MPI_Bcast(&nthreads,1,MPI_INT,0,world); + if (!lmp->kokkos) omp_set_num_threads(nthreads); + + if (me == 0) { + if (screen) + fprintf(screen," using %d OpenMP thread(s) per MPI task\n",nthreads); + if (logfile) + fprintf(logfile," using %d OpenMP thread(s) per MPI task\n",nthreads); + } +#endif + } /* ---------------------------------------------------------------------- */ Comm::~Comm() { + memory->destroy(grid2proc); memory->destroy(xsplit); memory->destroy(ysplit); memory->destroy(zsplit); - delete [] customfile; delete [] outfile; } +/* ---------------------------------------------------------------------- + deep copy of arrays from old Comm class to new one + all public/protected vectors/arrays in parent Comm class must be copied + called from alternate constructor of child classes + when new comm style is created from Input +------------------------------------------------------------------------- */ + +void Comm::copy_arrays(Comm *oldcomm) +{ + if (oldcomm->grid2proc) { + memory->create(grid2proc,procgrid[0],procgrid[1],procgrid[2], + "comm:grid2proc"); + memcpy(&grid2proc[0][0][0],&oldcomm->grid2proc[0][0][0], + (procgrid[0]*procgrid[1]*procgrid[2])*sizeof(int)); + + memory->create(xsplit,procgrid[0]+1,"comm:xsplit"); + memory->create(ysplit,procgrid[1]+1,"comm:ysplit"); + memory->create(zsplit,procgrid[2]+1,"comm:zsplit"); + memcpy(xsplit,oldcomm->xsplit,(procgrid[0]+1)*sizeof(double)); + memcpy(ysplit,oldcomm->ysplit,(procgrid[1]+1)*sizeof(double)); + memcpy(zsplit,oldcomm->zsplit,(procgrid[2]+1)*sizeof(double)); + } + + if (customfile) { + int n = strlen(oldcomm->customfile) + 1; + customfile = new char[n]; + strcpy(customfile,oldcomm->customfile); + } + if (outfile) { + int n = strlen(oldcomm->outfile) + 1; + outfile = new char[n]; + strcpy(outfile,oldcomm->outfile); + } +} + /* ---------------------------------------------------------------------- modify communication params invoked from input script by comm_modify command diff --git a/src/comm.h b/src/comm.h index f5d2742c9a..1aceb20c2c 100644 --- a/src/comm.h +++ b/src/comm.h @@ -21,21 +21,15 @@ namespace LAMMPS_NS { class Comm : protected Pointers { public: int style; // comm pattern: 0 = 6-way stencil, 1 = irregular tiling - int layout; // current proc domains: 0 = logical bricks, 1 = general tiling - // can do style=1 on layout=0, but not vice versa - // NOTE: uniform needs to be subsumed into layout - int uniform; // 1 = equal subdomains, 0 = load-balanced + int layout; // LAYOUT_UNIFORM = logical equal-sized bricks + // LAYOUT_NONUNIFORM = logical bricks, + // but different sizes due to LB + // LAYOUT_TILED = general tiling, due to RCB LB int me,nprocs; // proc info - int procgrid[3]; // procs assigned in each dim of 3d grid - int user_procgrid[3]; // user request for procs in each dim - int myloc[3]; // which proc I am in each dim - int procneigh[3][2]; // my 6 neighboring procs, 0/1 = left/right int ghost_velocity; // 1 if ghost atoms have velocity, 0 if not - double *xsplit,*ysplit,*zsplit; // fractional (0-1) sub-domain sizes double cutghost[3]; // cutoffs used for acquiring ghost atoms double cutghostuser; // user-specified ghost cutoff - int ***grid2proc; // which proc owns i,j,k loc in 3d grid int recv_from_partition; // recv proc layout from this partition int send_to_partition; // send my proc layout to this partition // -1 if no recv or send @@ -45,8 +39,24 @@ class Comm : protected Pointers { int maxexchange_fix; // max contribution to exchange from Fixes int nthreads; // OpenMP threads per MPI process + // public settings specific to layout = UNIFORM, NONUNIFORM + + int procgrid[3]; // procs assigned in each dim of 3d grid + int user_procgrid[3]; // user request for procs in each dim + int myloc[3]; // which proc I am in each dim + int procneigh[3][2]; // my 6 neighboring procs, 0/1 = left/right + double *xsplit,*ysplit,*zsplit; // fractional (0-1) sub-domain sizes + int ***grid2proc; // which proc owns i,j,k loc in 3d grid + + // public settings specific to layout = TILED + + double mysplit[3][2]; // fractional (0-1) bounds of my sub-domain + + // methods + Comm(class LAMMPS *); virtual ~Comm(); + void copy_arrays(class Comm *); void modify_params(int, char **); void set_processors(int, char **); // set 3d processor grid attributes diff --git a/src/comm_brick.cpp b/src/comm_brick.cpp index 93a81c3ad2..03ff8effd0 100644 --- a/src/comm_brick.cpp +++ b/src/comm_brick.cpp @@ -22,6 +22,7 @@ #include "stdio.h" #include "stdlib.h" #include "comm_brick.h" +#include "comm_tiled.h" #include "universe.h" #include "atom.h" #include "atom_vec.h" @@ -52,58 +53,57 @@ using namespace LAMMPS_NS; #define BIG 1.0e20 enum{SINGLE,MULTI}; // same as in Comm +enum{LAYOUT_UNIFORM,LAYOUT_NONUNIFORM,LAYOUT_TILED}; // several files /* ---------------------------------------------------------------------- */ CommBrick::CommBrick(LAMMPS *lmp) : Comm(lmp) { - style = layout = 0; + style = 0; + layout = LAYOUT_UNIFORM; + init_buffers(); +} - recv_from_partition = send_to_partition = -1; - otherflag = 0; +/* ---------------------------------------------------------------------- */ - grid2proc = NULL; +CommBrick::~CommBrick() +{ + free_swap(); + if (mode == MULTI) { + free_multi(); + memory->destroy(cutghostmulti); + } - uniform = 1; + if (sendlist) for (int i = 0; i < maxswap; i++) memory->destroy(sendlist[i]); + memory->sfree(sendlist); + memory->destroy(maxsendlist); + + memory->destroy(buf_send); + memory->destroy(buf_recv); +} + +/* ---------------------------------------------------------------------- */ + +CommBrick::CommBrick(LAMMPS *lmp, Comm *oldcomm) : Comm(*oldcomm) +{ + if (oldcomm->layout == LAYOUT_TILED) + error->all(FLERR,"Cannot change to comm_style brick from tiled layout"); + + style = 0; + layout = oldcomm->layout; + copy_arrays(oldcomm); + init_buffers(); +} + +/* ---------------------------------------------------------------------- + initialize comm buffers and other data structs local to CommBrick +------------------------------------------------------------------------- */ + +void CommBrick::init_buffers() +{ multilo = multihi = NULL; cutghostmulti = NULL; - // use of OpenMP threads - // query OpenMP for number of threads/process set by user at run-time - // if the OMP_NUM_THREADS environment variable is not set, we default - // to using 1 thread. This follows the principle of the least surprise, - // while practically all OpenMP implementations violate it by using - // as many threads as there are (virtual) CPU cores by default. - - nthreads = 1; -#ifdef _OPENMP - if (lmp->kokkos) { - nthreads = lmp->kokkos->num_threads * lmp->kokkos->numa; - } else if (getenv("OMP_NUM_THREADS") == NULL) { - nthreads = 1; - if (me == 0) - error->warning(FLERR,"OMP_NUM_THREADS environment is not set."); - } else { - nthreads = omp_get_max_threads(); - } - - // enforce consistent number of threads across all MPI tasks - - MPI_Bcast(&nthreads,1,MPI_INT,0,world); - if (!lmp->kokkos) omp_set_num_threads(nthreads); - - if (me == 0) { - if (screen) - fprintf(screen," using %d OpenMP thread(s) per MPI task\n",nthreads); - if (logfile) - fprintf(logfile," using %d OpenMP thread(s) per MPI task\n",nthreads); - } -#endif - - // initialize comm buffers & exchange memory - // NOTE: allow for AtomVec to set maxexchange_atom, e.g. for atom_style body - - maxexchange_atom = maxexchange_fix = 0; maxexchange = maxexchange_atom + maxexchange_fix; bufextra = maxexchange + BUFEXTRA; @@ -125,26 +125,6 @@ CommBrick::CommBrick(LAMMPS *lmp) : Comm(lmp) /* ---------------------------------------------------------------------- */ -CommBrick::~CommBrick() -{ - memory->destroy(grid2proc); - - free_swap(); - if (mode == MULTI) { - free_multi(); - memory->destroy(cutghostmulti); - } - - if (sendlist) for (int i = 0; i < maxswap; i++) memory->destroy(sendlist[i]); - memory->sfree(sendlist); - memory->destroy(maxsendlist); - - memory->destroy(buf_send); - memory->destroy(buf_recv); -} - -/* ---------------------------------------------------------------------- */ - void CommBrick::init() { triclinic = domain->triclinic; @@ -280,12 +260,12 @@ void CommBrick::setup() // 0 = to left, 1 = to right // set equal to recvneed[idim][1/0] of neighbor proc // maxneed[idim] = max procs away any proc recvs atoms in either direction - // uniform = 1 = uniform sized sub-domains: + // layout = UNIFORM = uniform sized sub-domains: // maxneed is directly computable from sub-domain size // limit to procgrid-1 for non-PBC // recvneed = maxneed except for procs near non-PBC // sendneed = recvneed of neighbor on each side - // uniform = 0 = non-uniform sized sub-domains: + // layout = NONUNIFORM = non-uniform sized sub-domains: // compute recvneed via updown() which accounts for non-PBC // sendneed = recvneed of neighbor on each side // maxneed via Allreduce() of recvneed @@ -293,7 +273,7 @@ void CommBrick::setup() int *periodicity = domain->periodicity; int left,right; - if (uniform) { + if (layout == LAYOUT_UNIFORM) { maxneed[0] = static_cast (cutghost[0] * procgrid[0] / prd[0]) + 1; maxneed[1] = static_cast (cutghost[1] * procgrid[1] / prd[1]) + 1; maxneed[2] = static_cast (cutghost[2] * procgrid[2] / prd[2]) + 1; @@ -561,16 +541,15 @@ void CommBrick::forward_comm(int dummy) } else { if (comm_x_only) { if (sendnum[iswap]) - n = avec->pack_comm(sendnum[iswap],sendlist[iswap], - x[firstrecv[iswap]],pbc_flag[iswap], - pbc[iswap]); + avec->pack_comm(sendnum[iswap],sendlist[iswap], + x[firstrecv[iswap]],pbc_flag[iswap],pbc[iswap]); } else if (ghost_velocity) { - n = avec->pack_comm_vel(sendnum[iswap],sendlist[iswap], - buf_send,pbc_flag[iswap],pbc[iswap]); + avec->pack_comm_vel(sendnum[iswap],sendlist[iswap], + buf_send,pbc_flag[iswap],pbc[iswap]); avec->unpack_comm_vel(recvnum[iswap],firstrecv[iswap],buf_send); } else { - n = avec->pack_comm(sendnum[iswap],sendlist[iswap], - buf_send,pbc_flag[iswap],pbc[iswap]); + avec->pack_comm(sendnum[iswap],sendlist[iswap], + buf_send,pbc_flag[iswap],pbc[iswap]); avec->unpack_comm(recvnum[iswap],firstrecv[iswap],buf_send); } } @@ -620,10 +599,10 @@ void CommBrick::reverse_comm() } else { if (comm_f_only) { if (sendnum[iswap]) - avec->unpack_reverse(sendnum[iswap],sendlist[iswap], - f[firstrecv[iswap]]); + avec->unpack_reverse(sendnum[iswap],sendlist[iswap], + f[firstrecv[iswap]]); } else { - n = avec->pack_reverse(recvnum[iswap],firstrecv[iswap],buf_send); + avec->pack_reverse(recvnum[iswap],firstrecv[iswap],buf_send); avec->unpack_reverse(sendnum[iswap],sendlist[iswap],buf_send); } } diff --git a/src/comm_brick.h b/src/comm_brick.h index 20592accab..8dafecfbe7 100644 --- a/src/comm_brick.h +++ b/src/comm_brick.h @@ -21,6 +21,7 @@ namespace LAMMPS_NS { class CommBrick : public Comm { public: CommBrick(class LAMMPS *); + CommBrick(class LAMMPS *, class Comm *); virtual ~CommBrick(); virtual void init(); @@ -80,6 +81,7 @@ class CommBrick : public Comm { int maxexchange; // max # of datums/atom in exchange comm int bufextra; // extra space beyond maxsend in send buffer + void init_buffers(); int updown(int, int, int, double, int, double *); // compare cutoff to procs virtual void grow_send(int, int); // reallocate send buffer diff --git a/src/comm_tiled.cpp b/src/comm_tiled.cpp index 9f111b999a..3160a5d60a 100644 --- a/src/comm_tiled.cpp +++ b/src/comm_tiled.cpp @@ -11,15 +11,20 @@ See the README file in the top-level LAMMPS directory. ------------------------------------------------------------------------- */ +#include "string.h" #include "lmptype.h" #include "comm_tiled.h" +#include "comm_brick.h" #include "atom.h" #include "atom_vec.h" #include "domain.h" +#include "force.h" #include "pair.h" +#include "neighbor.h" #include "modify.h" #include "fix.h" #include "compute.h" +#include "output.h" #include "dump.h" #include "memory.h" #include "error.h" @@ -27,32 +32,93 @@ using namespace LAMMPS_NS; #define BUFFACTOR 1.5 +#define BUFFACTOR 1.5 +#define BUFMIN 1000 +#define BUFEXTRA 1000 enum{SINGLE,MULTI}; // same as in Comm +enum{LAYOUT_UNIFORM,LAYOUT_NONUNIFORM,LAYOUT_TILED}; // several files /* ---------------------------------------------------------------------- */ CommTiled::CommTiled(LAMMPS *lmp) : Comm(lmp) { - style = 1; - layout = 0; - error->all(FLERR,"Comm_style tiled is not yet supported"); + + style = 1; + layout = LAYOUT_UNIFORM; + init_buffers(); +} + +/* ---------------------------------------------------------------------- */ + +CommTiled::CommTiled(LAMMPS *lmp, Comm *oldcomm) : Comm(*oldcomm) +{ + style = 1; + layout = oldcomm->layout; + copy_arrays(oldcomm); + init_buffers(); } /* ---------------------------------------------------------------------- */ CommTiled::~CommTiled() { + free_swap(); + + if (sendlist) for (int i = 0; i < nswap; i++) memory->destroy(sendlist[i]); + memory->sfree(sendlist); + memory->destroy(maxsendlist); + + memory->destroy(buf_send); + memory->destroy(buf_recv); } -/* ---------------------------------------------------------------------- */ +/* ---------------------------------------------------------------------- + initialize comm buffers and other data structs local to CommTiled + NOTE: if this is identical to CommBrick, put it into Comm ?? +------------------------------------------------------------------------- */ + +void CommTiled::init_buffers() +{ + maxexchange = maxexchange_atom + maxexchange_fix; + bufextra = maxexchange + BUFEXTRA; + + maxsend = BUFMIN; + memory->create(buf_send,maxsend+bufextra,"comm:buf_send"); + maxrecv = BUFMIN; + memory->create(buf_recv,maxrecv,"comm:buf_recv"); + + nswap = 2 * domain->dimension; + allocate_swap(nswap); + + //sendlist = (int **) memory->smalloc(nswap*sizeof(int *),"comm:sendlist"); + //memory->create(maxsendlist,nswap,"comm:maxsendlist"); + //for (int i = 0; i < nswap; i++) { + // maxsendlist[i] = BUFMIN; + // memory->create(sendlist[i],BUFMIN,"comm:sendlist[i]"); + //} +} + +/* ---------------------------------------------------------------------- + NOTE: if this is nearly identical to CommBrick, put it into Comm ?? +------------------------------------------------------------------------- */ void CommTiled::init() { triclinic = domain->triclinic; map_style = atom->map_style; + // temporary restrictions + + if (triclinic) + error->all(FLERR,"Cannot yet use comm_style tiled with triclinic box"); + if (domain->xperiodic || domain->yperiodic || + (domain->dimension == 2 && domain->zperiodic)) + error->all(FLERR,"Cannot yet use comm_style tiled with periodic box"); + if (mode == MULTI) + error->all(FLERR,"Cannot yet use comm_style tiled with multi-mode comm"); + // comm_only = 1 if only x,f are exchanged in forward/reverse comm // comm_x_only = 0 if ghost_velocity since velocities are added @@ -72,51 +138,210 @@ void CommTiled::init() for (int i = 0; i < modify->nfix; i++) size_border += modify->fix[i]->comm_border; + + // maxexchange = max # of datums/atom in exchange communication + // maxforward = # of datums in largest forward communication + // maxreverse = # of datums in largest reverse communication + // query pair,fix,compute,dump for their requirements + // pair style can force reverse comm even if newton off + + maxexchange = BUFMIN + maxexchange_fix; + maxforward = MAX(size_forward,size_border); + maxreverse = size_reverse; + + if (force->pair) maxforward = MAX(maxforward,force->pair->comm_forward); + if (force->pair) maxreverse = MAX(maxreverse,force->pair->comm_reverse); + + for (int i = 0; i < modify->nfix; i++) { + maxforward = MAX(maxforward,modify->fix[i]->comm_forward); + maxreverse = MAX(maxreverse,modify->fix[i]->comm_reverse); + } + + for (int i = 0; i < modify->ncompute; i++) { + maxforward = MAX(maxforward,modify->compute[i]->comm_forward); + maxreverse = MAX(maxreverse,modify->compute[i]->comm_reverse); + } + + for (int i = 0; i < output->ndump; i++) { + maxforward = MAX(maxforward,output->dump[i]->comm_forward); + maxreverse = MAX(maxreverse,output->dump[i]->comm_reverse); + } + + if (force->newton == 0) maxreverse = 0; + if (force->pair) maxreverse = MAX(maxreverse,force->pair->comm_reverse_off); } /* ---------------------------------------------------------------------- setup spatial-decomposition communication patterns - function of neighbor cutoff(s) & cutghostuser & current box size - single mode sets slab boundaries (slablo,slabhi) based on max cutoff - multi mode sets type-dependent slab boundaries (multilo,multihi) + function of neighbor cutoff(s) & cutghostuser & current box size and tiling ------------------------------------------------------------------------- */ void CommTiled::setup() { - // error on triclinic or multi? - // set nswap = 2*dim - // setup neighbor proc info for exchange() - // setup nsendproc and nrecvproc bounts - // setup sendproc and recvproc lists - // setup sendboxes - // reallocate requests and statuses + int i; - // check that cutoff is <= 1/2 of periodic box len? + int dimension; + int *periodicity; + double *prd,*sublo,*subhi,*boxlo,*boxhi; - // loop over dims - // left: - // construct ghost boxes - // differnet in x,y,z - // account for ghost borders in y,z - // account for PBC by shifting - // split into multiple boxes if straddles PBC - // drop boxes down RCB tree - // count unique procs they cover - // what about self if crosses PBC - // for each proc they cover: - // compute box I send it to left - // is a message I will recv from right (don't care about box) - // for ghost-extended boxes - // do not count procs that do not overlap my owned box at all - // only touching edge of my owned box does not count - // in this case list I send to and recv from may be different? - // same thing to right + double cut = MAX(neighbor->cutneighmax,cutghostuser); + + dimension = domain->dimension; + periodicity = domain->periodicity; + prd = domain->prd; + sublo = domain->sublo; + subhi = domain->subhi; + boxlo = domain->boxlo; + boxhi = domain->boxhi; + cutghost[0] = cutghost[1] = cutghost[2] = cut; + + if ((periodicity[0] && cut > prd[0]) || + (periodicity[1] && cut > prd[1]) || + (dimension == 3 && periodicity[2] && cut > prd[2])) + error->all(FLERR,"Communication cutoff for comm_style tiled " + "cannot exceed periodic box length"); + + // allocate overlap + int *overlap; + int noverlap,noverlap1,indexme; + double lo1[3],hi1[3],lo2[3],hi2[3]; + int one,two; + + nswap = 0; + for (int idim = 0; idim < dimension; idim++) { + + // ghost box in lower direction + + one = 1; + lo1[0] = sublo[0]; lo1[1] = sublo[1]; lo1[2] = sublo[2]; + hi1[0] = subhi[0]; hi1[1] = subhi[1]; hi1[2] = subhi[2]; + lo1[idim] = sublo[idim] - cut; + hi1[idim] = sublo[idim]; + + two = 0; + if (periodicity[idim] && lo1[idim] < boxlo[idim]) { + two = 1; + lo2[0] = sublo[0]; lo2[1] = sublo[1]; lo2[2] = sublo[2]; + hi2[0] = subhi[0]; hi2[1] = subhi[1]; hi2[2] = subhi[2]; + lo2[idim] = lo1[idim] + prd[idim]; + hi2[idim] = hi1[idim] + prd[idim]; + if (sublo[idim] == boxlo[idim]) { + one = 0; + hi2[idim] = boxhi[idim]; + } + } + + indexme = -1; + noverlap = 0; + if (one) { + if (layout == LAYOUT_UNIFORM) + box_drop_uniform(idim,lo1,hi1,noverlap,overlap,indexme); + else if (layout == LAYOUT_NONUNIFORM) + box_drop_nonuniform(idim,lo1,hi1,noverlap,overlap,indexme); + else + box_drop_tiled(lo1,hi1,0,nprocs-1,noverlap,overlap,indexme); + } + + noverlap1 = noverlap; + if (two) { + if (layout == LAYOUT_UNIFORM) + box_drop_uniform(idim,lo2,hi2,noverlap,overlap,indexme); + else if (layout == LAYOUT_NONUNIFORM) + box_drop_nonuniform(idim,lo2,hi2,noverlap,overlap,indexme); + else + box_drop_tiled(lo2,hi2,0,nprocs-1,noverlap,overlap,indexme); + } + + // if this (self) proc is in overlap list, move it to end of list + + if (indexme >= 0) { + int tmp = overlap[noverlap-1]; + overlap[noverlap-1] = overlap[indexme]; + overlap[indexme] = tmp; + } + + // overlap how has list of noverlap procs + // includes PBC effects + + if (overlap[noverlap-1] == me) sendself[nswap] = 1; + else sendself[nswap] = 0; + if (noverlap-sendself[nswap]) sendother[nswap] = 1; + else sendother[nswap] = 0; + + nsendproc[nswap] = noverlap; + for (i = 0; i < noverlap; i++) sendproc[nswap][i] = overlap[i]; + nrecvproc[nswap+1] = noverlap; + for (i = 0; i < noverlap; i++) recvproc[nswap+1][i] = overlap[i]; + + // compute sendbox for each of my sends + // ibox = intersection of ghostbox with other proc's sub-domain + // sendbox = ibox displaced by cutoff in dim + + // NOTE: need to extend send box in lower dims by cutoff + // NOTE: this logic for overlapping boxes is not correct for sending + + double oboxlo[3],oboxhi[3],sbox[6]; + + for (i = 0; i < noverlap; i++) { + pbc_flag[nswap][i] = 0; + pbc[nswap][i][0] = pbc[nswap][i][1] = pbc[nswap][i][2] = + pbc[nswap][i][3] = pbc[nswap][i][4] = pbc[nswap][i][5] = 0; + + if (layout == LAYOUT_UNIFORM) + box_other_uniform(overlap[i],oboxlo,oboxhi); + else if (layout == LAYOUT_NONUNIFORM) + box_other_nonuniform(overlap[i],oboxlo,oboxhi); + else + box_other_tiled(overlap[i],oboxlo,oboxhi); + + if (i < noverlap1) { + sbox[0] = MAX(oboxlo[0],lo1[0]); + sbox[1] = MAX(oboxlo[1],lo1[1]); + sbox[2] = MAX(oboxlo[2],lo1[2]); + sbox[3] = MIN(oboxhi[0],hi1[0]); + sbox[4] = MIN(oboxhi[1],hi1[1]); + sbox[5] = MIN(oboxhi[2],hi1[2]); + sbox[idim] += cut; + sbox[3+idim] += cut; + if (sbox[idim] == lo1[idim]) sbox[idim] = sublo[idim]; + } else { + pbc_flag[nswap][i] = 1; + pbc[nswap][i][idim] = 1; + sbox[0] = MAX(oboxlo[0],lo2[0]); + sbox[1] = MAX(oboxlo[1],lo2[1]); + sbox[2] = MAX(oboxlo[2],lo2[2]); + sbox[3] = MIN(oboxhi[0],hi2[0]); + sbox[4] = MIN(oboxhi[1],hi2[1]); + sbox[5] = MIN(oboxhi[2],hi2[2]); + sbox[idim] -= prd[idim] - cut; + sbox[3+idim] -= prd[idim] + cut; + if (sbox[idim] == lo1[idim]) sbox[idim] = sublo[idim]; + } + + if (idim >= 1) { + if (sbox[0] == sublo[0]) sbox[0] -= cut; + if (sbox[4] == subhi[0]) sbox[4] += cut; + } + if (idim == 2) { + if (sbox[1] == sublo[1]) sbox[1] -= cut; + if (sbox[5] == subhi[1]) sbox[5] += cut; + } + + memcpy(sendbox[nswap][i],sbox,6*sizeof(double)); + } + + // ghost box in upper direction + + + + + + nswap += 2; + } + + + // reallocate requests and statuses to max of any swap - // what need from decomp (RCB): - // dropbox: return list of procs with overlap and overlapping boxes - // return n, proclist, boxlist - // otherbox: bbox of another proc - // dropatom: return what proc owns the atom coord } /* ---------------------------------------------------------------------- @@ -126,46 +351,73 @@ void CommTiled::setup() void CommTiled::forward_comm(int dummy) { - int i,irecv,n; + int i,irecv,n,nsend,nrecv; MPI_Status status; AtomVec *avec = atom->avec; double **x = atom->x; // exchange data with another set of procs in each swap - // if first proc in set is self, then is just self across PBC, just copy + // post recvs from all procs except self + // send data to all procs except self + // copy data to self if sendself is set + // wait on all procs except self and unpack received data // if comm_x_only set, exchange or copy directly to x, don't unpack for (int iswap = 0; iswap < nswap; iswap++) { - if (sendproc[iswap][0] != me) { - if (comm_x_only) { - for (i = 0; i < nrecvproc[iswap]; i++) + nsend = nsendproc[iswap] - sendself[iswap]; + nrecv = nrecvproc[iswap] - sendself[iswap]; + + if (comm_x_only) { + if (sendother[iswap]) { + for (i = 0; i < nrecv; i++) MPI_Irecv(x[firstrecv[iswap][i]],size_forward_recv[iswap][i], MPI_DOUBLE,recvproc[iswap][i],0,world,&requests[i]); - for (i = 0; i < nsendproc[iswap]; i++) { + for (i = 0; i < nsend; i++) { n = avec->pack_comm(sendnum[iswap][i],sendlist[iswap][i], buf_send,pbc_flag[iswap][i],pbc[iswap][i]); MPI_Send(buf_send,n,MPI_DOUBLE,sendproc[iswap][i],0,world); } - MPI_Waitall(nrecvproc[iswap],requests,statuses); + } - } else if (ghost_velocity) { - for (i = 0; i < nrecvproc[iswap]; i++) + if (sendself[iswap]) { + avec->pack_comm(sendnum[iswap][nsend],sendlist[iswap][nsend], + x[firstrecv[iswap][nrecv]],pbc_flag[iswap][nsend], + pbc[iswap][nsend]); + } + + if (sendother[iswap]) MPI_Waitall(nrecv,requests,statuses); + + } else if (ghost_velocity) { + if (sendother[iswap]) { + for (i = 0; i < nrecv; i++) MPI_Irecv(&buf_recv[forward_recv_offset[iswap][i]], size_forward_recv[iswap][i], MPI_DOUBLE,recvproc[iswap][i],0,world,&requests[i]); - for (i = 0; i < nsendproc[iswap]; i++) { + for (i = 0; i < nsend; i++) { n = avec->pack_comm_vel(sendnum[iswap][i],sendlist[iswap][i], buf_send,pbc_flag[iswap][i],pbc[iswap][i]); MPI_Send(buf_send,n,MPI_DOUBLE,sendproc[iswap][i],0,world); } - for (i = 0; i < nrecvproc[iswap]; i++) { - MPI_Waitany(nrecvproc[iswap],requests,&irecv,&status); - avec->unpack_comm_vel(recvnum[iswap][i],firstrecv[iswap][irecv], + } + + if (sendself[iswap]) { + avec->pack_comm_vel(sendnum[iswap][nsend],sendlist[iswap][nsend], + buf_send,pbc_flag[iswap][nsend],pbc[iswap][nsend]); + avec->unpack_comm_vel(recvnum[iswap][nrecv],firstrecv[iswap][nrecv], + buf_send); + } + + if (sendother[iswap]) { + for (i = 0; i < nrecv; i++) { + MPI_Waitany(nrecv,requests,&irecv,&status); + avec->unpack_comm_vel(recvnum[iswap][irecv],firstrecv[iswap][irecv], &buf_recv[forward_recv_offset[iswap][irecv]]); } + } - } else { - for (i = 0; i < nrecvproc[iswap]; i++) + } else { + if (sendother[iswap]) { + for (i = 0; i < nrecv; i++) MPI_Irecv(&buf_recv[forward_recv_offset[iswap][i]], size_forward_recv[iswap][i], MPI_DOUBLE,recvproc[iswap][i],0,world,&requests[i]); @@ -174,27 +426,21 @@ void CommTiled::forward_comm(int dummy) buf_send,pbc_flag[iswap][i],pbc[iswap][i]); MPI_Send(buf_send,n,MPI_DOUBLE,sendproc[iswap][i],0,world); } - for (i = 0; i < nrecvproc[iswap]; i++) { - MPI_Waitany(nrecvproc[iswap],requests,&irecv,&status); - avec->unpack_comm(recvnum[iswap][i],firstrecv[iswap][irecv], - &buf_recv[forward_recv_offset[iswap][irecv]]); - } } - } else { - if (comm_x_only) { - if (sendnum[iswap][0]) - n = avec->pack_comm(sendnum[iswap][0],sendlist[iswap][0], - x[firstrecv[iswap][0]],pbc_flag[iswap][0], - pbc[iswap][0]); - } else if (ghost_velocity) { - n = avec->pack_comm_vel(sendnum[iswap][0],sendlist[iswap][0], - buf_send,pbc_flag[iswap][0],pbc[iswap][0]); - avec->unpack_comm_vel(recvnum[iswap][0],firstrecv[iswap][0],buf_send); - } else { - n = avec->pack_comm(sendnum[iswap][0],sendlist[iswap][0], - buf_send,pbc_flag[iswap][0],pbc[iswap][0]); - avec->unpack_comm(recvnum[iswap][0],firstrecv[iswap][0],buf_send); + if (sendself[iswap]) { + avec->pack_comm(sendnum[iswap][nsend],sendlist[iswap][nsend], + buf_send,pbc_flag[iswap][nsend],pbc[iswap][nsend]); + avec->unpack_comm(recvnum[iswap][nrecv],firstrecv[iswap][nrecv], + buf_send); + } + + if (sendother[iswap]) { + for (i = 0; i < nrecv; i++) { + MPI_Waitany(nrecv,requests,&irecv,&status); + avec->unpack_comm(recvnum[iswap][irecv],firstrecv[iswap][irecv], + &buf_recv[forward_recv_offset[iswap][irecv]]); + } } } } @@ -207,57 +453,70 @@ void CommTiled::forward_comm(int dummy) void CommTiled::reverse_comm() { - int i,irecv,n; + int i,irecv,n,nsend,nrecv; MPI_Request request; MPI_Status status; AtomVec *avec = atom->avec; double **f = atom->f; // exchange data with another set of procs in each swap - // if first proc in set is self, then is just self across PBC, just copy + // post recvs from all procs except self + // send data to all procs except self + // copy data to self if sendself is set + // wait on all procs except self and unpack received data // if comm_f_only set, exchange or copy directly from f, don't pack for (int iswap = nswap-1; iswap >= 0; iswap--) { - if (sendproc[iswap][0] != me) { - if (comm_f_only) { - for (i = 0; i < nsendproc[iswap]; i++) + if (comm_f_only) { + if (sendother[iswap]) { + for (i = 0; i < nsend; i++) MPI_Irecv(&buf_recv[reverse_recv_offset[iswap][i]], size_reverse_recv[iswap][i],MPI_DOUBLE, sendproc[iswap][i],0,world,&requests[i]); - for (i = 0; i < nrecvproc[iswap]; i++) + for (i = 0; i < nrecv; i++) MPI_Send(f[firstrecv[iswap][i]],size_reverse_send[iswap][i], MPI_DOUBLE,recvproc[iswap][i],0,world); - for (i = 0; i < nsendproc[iswap]; i++) { - MPI_Waitany(nsendproc[iswap],requests,&irecv,&status); - avec->unpack_reverse(sendnum[iswap][irecv],sendlist[iswap][irecv], - &buf_recv[reverse_recv_offset[iswap][irecv]]); - } + } - } else { - for (i = 0; i < nsendproc[iswap]; i++) - MPI_Irecv(&buf_recv[reverse_recv_offset[iswap][i]], - size_reverse_recv[iswap][i],MPI_DOUBLE, - sendproc[iswap][i],0,world,&requests[i]); - for (i = 0; i < nrecvproc[iswap]; i++) { - n = avec->pack_reverse(recvnum[iswap][i],firstrecv[iswap][i], - buf_send); - MPI_Send(buf_send,n,MPI_DOUBLE,recvproc[iswap][i],0,world); - } - for (i = 0; i < nsendproc[iswap]; i++) { - MPI_Waitany(nsendproc[iswap],requests,&irecv,&status); + if (sendself[iswap]) { + avec->unpack_reverse(sendnum[iswap][nsend],sendlist[iswap][nsend], + f[firstrecv[iswap][nrecv]]); + } + + if (sendother[iswap]) { + for (i = 0; i < nsend; i++) { + MPI_Waitany(nsend,requests,&irecv,&status); avec->unpack_reverse(sendnum[iswap][irecv],sendlist[iswap][irecv], &buf_recv[reverse_recv_offset[iswap][irecv]]); } } - + } else { - if (comm_f_only) { - if (sendnum[iswap][0]) - avec->unpack_reverse(sendnum[iswap][0],sendlist[iswap][0], - f[firstrecv[iswap][0]]); - } else { - n = avec->pack_reverse(recvnum[iswap][0],firstrecv[iswap][0],buf_send); - avec->unpack_reverse(sendnum[iswap][0],sendlist[iswap][0],buf_send); + if (sendother[iswap]) { + for (i = 0; i < nsend; i++) + MPI_Irecv(&buf_recv[reverse_recv_offset[iswap][i]], + size_reverse_recv[iswap][i],MPI_DOUBLE, + sendproc[iswap][i],0,world,&requests[i]); + for (i = 0; i < nrecv; i++) { + n = avec->pack_reverse(recvnum[iswap][i],firstrecv[iswap][i], + buf_send); + MPI_Send(buf_send,n,MPI_DOUBLE,recvproc[iswap][i],0,world); + } + } + + if (sendself[iswap]) { + avec->pack_reverse(recvnum[iswap][nrecv],firstrecv[iswap][nrecv], + buf_send); + avec->unpack_reverse(sendnum[iswap][nsend],sendlist[iswap][nsend], + buf_send); + } + + if (sendother[iswap]) { + for (i = 0; i < nsend; i++) { + MPI_Waitany(nsend,requests,&irecv,&status); + avec->unpack_reverse(sendnum[iswap][irecv],sendlist[iswap][irecv], + &buf_recv[reverse_recv_offset[iswap][irecv]]); + } } } } @@ -298,7 +557,7 @@ void CommTiled::exchange() void CommTiled::borders() { - int i,n,irecv,ngroup,nlast,nsend,rmaxswap; + int i,n,irecv,ngroup,nlast,nsend,nrecv,ncount,rmaxswap; double xlo,xhi,ylo,yhi,zlo,zhi; double *bbox; double **x; @@ -333,36 +592,40 @@ void CommTiled::borders() if (iswap < 2) nlast = atom->nlocal; else nlast = atom->nlocal + atom->nghost; - nsend = 0; + ncount = 0; for (i = 0; i < ngroup; i++) if (x[i][0] >= xlo && x[i][0] <= xhi && x[i][1] >= ylo && x[i][1] <= yhi && x[i][2] >= zlo && x[i][2] <= zhi) { - if (nsend == maxsendlist[iswap][i]) grow_list(iswap,i,nsend); - sendlist[iswap][i][nsend++] = i; + if (ncount == maxsendlist[iswap][i]) grow_list(iswap,i,ncount); + sendlist[iswap][i][ncount++] = i; } for (i = atom->nlocal; i < nlast; i++) if (x[i][0] >= xlo && x[i][0] <= xhi && x[i][1] >= ylo && x[i][1] <= yhi && x[i][2] >= zlo && x[i][2] <= zhi) { - if (nsend == maxsendlist[iswap][i]) grow_list(iswap,i,nsend); - sendlist[iswap][i][nsend++] = i; + if (ncount == maxsendlist[iswap][i]) grow_list(iswap,i,ncount); + sendlist[iswap][i][ncount++] = i; } - sendnum[iswap][i] = nsend; - smax = MAX(smax,nsend); + sendnum[iswap][i] = ncount; + smax = MAX(smax,ncount); } - // send sendnum counts to procs who recv from me + // send sendnum counts to procs who recv from me except self + // copy data to self if sendself is set - if (sendproc[iswap][0] != me) { - for (i = 0; i < nrecvproc[iswap]; i++) + nsend = nsendproc[iswap] - sendself[iswap]; + nrecv = nrecvproc[iswap] - sendself[iswap]; + + if (sendother[iswap]) { + for (i = 0; i < nrecv; i++) MPI_Irecv(&recvnum[iswap][i],1,MPI_INT, recvproc[iswap][i],0,world,&requests[i]); - for (i = 0; i < nsendproc[iswap]; i++) + for (i = 0; i < nsend; i++) MPI_Send(&sendnum[iswap][i],1,MPI_INT,sendproc[iswap][i],0,world); - MPI_Waitall(nrecvproc[iswap],requests,statuses); - - } else recvnum[iswap][0] = sendnum[iswap][0]; + } + if (sendself[iswap]) recvnum[iswap][nrecv] = sendnum[iswap][nsend]; + if (sendother[iswap]) MPI_Waitall(nrecv,requests,statuses); // setup other per swap/proc values from sendnum and recvnum @@ -390,54 +653,64 @@ void CommTiled::borders() // swap atoms with other procs using pack_border(), unpack_border() - if (sendproc[iswap][0] != me) { - for (i = 0; i < nsendproc[iswap]; i++) { - if (ghost_velocity) { - for (i = 0; i < nrecvproc[iswap]; i++) - MPI_Irecv(&buf_recv[forward_recv_offset[iswap][i]], - recvnum[iswap][i]*size_border, - MPI_DOUBLE,recvproc[iswap][i],0,world,&requests[i]); - for (i = 0; i < nsendproc[iswap]; i++) { - n = avec->pack_border_vel(sendnum[iswap][i],sendlist[iswap][i], - buf_send,pbc_flag[iswap][i], - pbc[iswap][i]); - MPI_Send(buf_send,n*size_border,MPI_DOUBLE, - sendproc[iswap][i],0,world); - } - for (i = 0; i < nrecvproc[iswap]; i++) { - MPI_Waitany(nrecvproc[iswap],requests,&irecv,&status); - avec->unpack_border(recvnum[iswap][i],firstrecv[iswap][irecv], - &buf_recv[forward_recv_offset[iswap][irecv]]); - } - - } else { - for (i = 0; i < nrecvproc[iswap]; i++) - MPI_Irecv(&buf_recv[forward_recv_offset[iswap][i]], - recvnum[iswap][i]*size_border, - MPI_DOUBLE,recvproc[iswap][i],0,world,&requests[i]); - for (i = 0; i < nsendproc[iswap]; i++) { - n = avec->pack_border(sendnum[iswap][i],sendlist[iswap][i], - buf_send,pbc_flag[iswap][i],pbc[iswap][i]); - MPI_Send(buf_send,n*size_border,MPI_DOUBLE, - sendproc[iswap][i],0,world); - } - for (i = 0; i < nrecvproc[iswap]; i++) { - MPI_Waitany(nrecvproc[iswap],requests,&irecv,&status); - avec->unpack_border(recvnum[iswap][i],firstrecv[iswap][irecv], - &buf_recv[forward_recv_offset[iswap][irecv]]); - } + if (ghost_velocity) { + if (sendother[iswap]) { + for (i = 0; i < nrecv; i++) + MPI_Irecv(&buf_recv[forward_recv_offset[iswap][i]], + recvnum[iswap][i]*size_border, + MPI_DOUBLE,recvproc[iswap][i],0,world,&requests[i]); + for (i = 0; i < nsend; i++) { + n = avec->pack_border_vel(sendnum[iswap][i],sendlist[iswap][i], + buf_send,pbc_flag[iswap][i],pbc[iswap][i]); + MPI_Send(buf_send,n*size_border,MPI_DOUBLE, + sendproc[iswap][i],0,world); + } + } + + if (sendself[iswap]) { + n = avec->pack_border_vel(sendnum[iswap][nsend],sendlist[iswap][nsend], + buf_send,pbc_flag[iswap][nsend], + pbc[iswap][nsend]); + avec->unpack_border_vel(recvnum[iswap][nrecv],firstrecv[iswap][nrecv], + buf_send); + } + + if (sendother[iswap]) { + for (i = 0; i < nrecv; i++) { + MPI_Waitany(nrecv,requests,&irecv,&status); + avec->unpack_border(recvnum[iswap][irecv],firstrecv[iswap][irecv], + &buf_recv[forward_recv_offset[iswap][irecv]]); } } } else { - if (ghost_velocity) { - n = avec->pack_border_vel(sendnum[iswap][0],sendlist[iswap][0], - buf_send,pbc_flag[iswap][0],pbc[iswap][0]); - avec->unpack_border_vel(recvnum[iswap][0],firstrecv[iswap][0],buf_send); - } else { - n = avec->pack_border(sendnum[iswap][0],sendlist[iswap][0], - buf_send,pbc_flag[iswap][0],pbc[iswap][0]); - avec->unpack_border(recvnum[iswap][0],firstrecv[iswap][0],buf_send); + if (sendother[iswap]) { + for (i = 0; i < nrecv; i++) + MPI_Irecv(&buf_recv[forward_recv_offset[iswap][i]], + recvnum[iswap][i]*size_border, + MPI_DOUBLE,recvproc[iswap][i],0,world,&requests[i]); + for (i = 0; i < nsend; i++) { + n = avec->pack_border(sendnum[iswap][i],sendlist[iswap][i], + buf_send,pbc_flag[iswap][i],pbc[iswap][i]); + MPI_Send(buf_send,n*size_border,MPI_DOUBLE, + sendproc[iswap][i],0,world); + } + } + + if (sendself[iswap]) { + n = avec->pack_border(sendnum[iswap][nsend],sendlist[iswap][nsend], + buf_send,pbc_flag[iswap][nsend], + pbc[iswap][nsend]); + avec->unpack_border(recvnum[iswap][nsend],firstrecv[iswap][nsend], + buf_send); + } + + if (sendother[iswap]) { + for (i = 0; i < nrecv; i++) { + MPI_Waitany(nrecv,requests,&irecv,&status); + avec->unpack_border(recvnum[iswap][irecv],firstrecv[iswap][irecv], + &buf_recv[forward_recv_offset[iswap][irecv]]); + } } } @@ -786,6 +1059,64 @@ int CommTiled::exchange_variable(int n, double *inbuf, double *&outbuf) return nrecv; } +/* ---------------------------------------------------------------------- + determine overlap list of Noverlap procs the lo/hi box overlaps + overlap = non-zero area in common between box and proc sub-domain + box is onwed by me and extends in dim +------------------------------------------------------------------------- */ + +void CommTiled::box_drop_uniform(int dim, double *lo, double *hi, + int &noverlap, int *overlap, int &indexme) +{ + +} + +/* ---------------------------------------------------------------------- + determine overlap list of Noverlap procs the lo/hi box overlaps + overlap = non-zero area in common between box and proc sub-domain +------------------------------------------------------------------------- */ + +void CommTiled::box_drop_nonuniform(int dim, double *lo, double *hi, + int &noverlap, int *overlap, int &indexme) +{ +} + +/* ---------------------------------------------------------------------- + determine overlap list of Noverlap procs the lo/hi box overlaps + overlap = non-zero area in common between box and proc sub-domain + recursive routine for traversing an RCB tree of cuts +------------------------------------------------------------------------- */ + +void CommTiled::box_drop_tiled(double *lo, double *hi, + int proclower, int procupper, + int &noverlap, int *overlap, int &indexme) +{ + // end recursion when partition is a single proc + // add proc to overlap list + + if (proclower == procupper) { + if (proclower == me) indexme = noverlap; + overlap[noverlap++] = proclower; + return; + } + + // drop box on each side of cut it extends beyond + // use > and < criteria to not include procs it only touches + // procmid = 1st processor in upper half of partition + // = location in tree that stores this cut + // dim = 0,1,2 dimension of cut + // cut = position of cut + + int procmid = proclower + (procupper - proclower) / 2 + 1; + double cut = tree[procmid].cut; + int dim = tree[procmid].dim; + + if (lo[dim] < cut) + box_drop_tiled(lo,hi,proclower,procmid-1,noverlap,overlap,indexme); + if (hi[dim] > cut) + box_drop_tiled(lo,hi,procmid,procupper,noverlap,overlap,indexme); +} + /* ---------------------------------------------------------------------- realloc the size of the send buffer as needed with BUFFACTOR and bufextra if flag = 1, realloc @@ -825,6 +1156,42 @@ void CommTiled::grow_list(int iswap, int iwhich, int n) "comm:sendlist[iswap]"); } +/* ---------------------------------------------------------------------- + allocation of swap info +------------------------------------------------------------------------- */ + +void CommTiled::allocate_swap(int n) +{ + memory->create(sendnum,n,"comm:sendnum"); + memory->create(recvnum,n,"comm:recvnum"); + memory->create(sendproc,n,"comm:sendproc"); + memory->create(recvproc,n,"comm:recvproc"); + memory->create(size_forward_recv,n,"comm:size"); + memory->create(size_reverse_send,n,"comm:size"); + memory->create(size_reverse_recv,n,"comm:size"); + memory->create(firstrecv,n,"comm:firstrecv"); + memory->create(pbc_flag,n,"comm:pbc_flag"); + memory->create(pbc,n,6,"comm:pbc"); +} + +/* ---------------------------------------------------------------------- + free memory for swaps +------------------------------------------------------------------------- */ + +void CommTiled::free_swap() +{ + memory->destroy(sendnum); + memory->destroy(recvnum); + memory->destroy(sendproc); + memory->destroy(recvproc); + memory->destroy(size_forward_recv); + memory->destroy(size_reverse_send); + memory->destroy(size_reverse_recv); + memory->destroy(firstrecv); + memory->destroy(pbc_flag); + memory->destroy(pbc); +} + /* ---------------------------------------------------------------------- return # of bytes of allocated memory ------------------------------------------------------------------------- */ diff --git a/src/comm_tiled.h b/src/comm_tiled.h index 2c7a6e62df..b992242476 100644 --- a/src/comm_tiled.h +++ b/src/comm_tiled.h @@ -21,6 +21,7 @@ namespace LAMMPS_NS { class CommTiled : public Comm { public: CommTiled(class LAMMPS *); + CommTiled(class LAMMPS *, class Comm *); virtual ~CommTiled(); void init(); @@ -46,15 +47,16 @@ class CommTiled : public Comm { bigint memory_usage(); private: - int nswap; // # of swaps to perform = 2*dim - int *nsendproc,*nrecvproc; // # of procs to send/recv to/from in each swap - int triclinic; // 0 if domain is orthog, 1 if triclinic int map_style; // non-0 if global->local mapping is done int size_forward; // # of per-atom datums in forward comm int size_reverse; // # of datums in reverse comm int size_border; // # of datums in forward border comm + int nswap; // # of swaps to perform = 2*dim + int *nsendproc,*nrecvproc; // # of procs to send/recv to/from in each swap + int *sendother; // 1 if send to any other proc in each swap + int *sendself; // 1 if send to self in each swap int **sendnum,**recvnum; // # of atoms to send/recv per swap/proc int **sendproc,**recvproc; // proc to send/recv to/from per swap/proc int **size_forward_recv; // # of values to recv in each forward swap/proc @@ -77,6 +79,7 @@ class CommTiled : public Comm { int maxsend,maxrecv; // current size of send/recv buffer int maxforward,maxreverse; // max # of datums in forward/reverse comm + int maxexchange; // max # of datums/atom in exchange comm int bufextra; // extra space beyond maxsend in send buffer MPI_Request *requests; @@ -84,9 +87,35 @@ class CommTiled : public Comm { int comm_x_only,comm_f_only; // 1 if only exchange x,f in for/rev comm + struct Tree { + double cut; + int dim; + }; + + Tree *tree; + + // info from RCB decomp + + double rcbcut; + int rcbcutdim; + double rcblo[3]; + double rcbhi[3]; + + void init_buffers(); + + void box_drop_uniform(int, double *, double *, int &, int *, int &); + void box_drop_nonuniform(int, double *, double *, int &, int *, int &); + void box_drop_tiled(double *, double *, int, int, int &, int *, int &); + + void box_other_uniform(int, double *, double *) {} + void box_other_nonuniform(int, double *, double *) {} + void box_other_tiled(int, double *, double *) {} + void grow_send(int, int); // reallocate send buffer void grow_recv(int); // free/allocate recv buffer void grow_list(int, int, int); // reallocate sendlist for one swap/proc + void allocate_swap(int); // allocate swap arrays + void free_swap(); // free swap arrays }; } diff --git a/src/domain.cpp b/src/domain.cpp index 35d4461864..83fafd7261 100644 --- a/src/domain.cpp +++ b/src/domain.cpp @@ -44,14 +44,15 @@ using namespace LAMMPS_NS; using namespace MathConst; +enum{NO_REMAP,X_REMAP,V_REMAP}; // same as fix_deform.cpp +enum{IGNORE,WARN,ERROR}; // same as thermo.cpp +enum{LAYOUT_UNIFORM,LAYOUT_NONUNIFORM,LAYOUT_TILED}; // several files + #define BIG 1.0e20 #define SMALL 1.0e-4 #define DELTAREGION 4 #define BONDSTRETCH 1.1 -enum{NO_REMAP,X_REMAP,V_REMAP}; // same as fix_deform.cpp -enum{IGNORE,WARN,ERROR}; // same as thermo.cpp - /* ---------------------------------------------------------------------- default is periodic ------------------------------------------------------------------------- */ @@ -249,42 +250,56 @@ void Domain::set_global_box() /* ---------------------------------------------------------------------- set lamda box params assumes global box is defined and proc assignment has been made - uses comm->xyz_split to define subbox boundaries in consistent manner + uses comm->xyz_split or comm->mysplit + to define subbox boundaries in consistent manner ------------------------------------------------------------------------- */ void Domain::set_lamda_box() { - int *myloc = comm->myloc; - double *xsplit = comm->xsplit; - double *ysplit = comm->ysplit; - double *zsplit = comm->zsplit; + if (comm->layout != LAYOUT_TILED) { + int *myloc = comm->myloc; + double *xsplit = comm->xsplit; + double *ysplit = comm->ysplit; + double *zsplit = comm->zsplit; - sublo_lamda[0] = xsplit[myloc[0]]; - subhi_lamda[0] = xsplit[myloc[0]+1]; + sublo_lamda[0] = xsplit[myloc[0]]; + subhi_lamda[0] = xsplit[myloc[0]+1]; + sublo_lamda[1] = ysplit[myloc[1]]; + subhi_lamda[1] = ysplit[myloc[1]+1]; + sublo_lamda[2] = zsplit[myloc[2]]; + subhi_lamda[2] = zsplit[myloc[2]+1]; - sublo_lamda[1] = ysplit[myloc[1]]; - subhi_lamda[1] = ysplit[myloc[1]+1]; + } else { + double (*mysplit)[2] = comm->mysplit; - sublo_lamda[2] = zsplit[myloc[2]]; - subhi_lamda[2] = zsplit[myloc[2]+1]; + sublo_lamda[0] = mysplit[0][0]; + subhi_lamda[0] = mysplit[0][1]; + sublo_lamda[1] = mysplit[1][0]; + subhi_lamda[1] = mysplit[1][1]; + sublo_lamda[2] = mysplit[2][0]; + subhi_lamda[2] = mysplit[2][1]; + } } /* ---------------------------------------------------------------------- set local subbox params for orthogonal boxes assumes global box is defined and proc assignment has been made - uses comm->xyz_split to define subbox boundaries in consistent manner + uses comm->xyz_split or comm->mysplit + to define subbox boundaries in consistent manner insure subhi[max] = boxhi ------------------------------------------------------------------------- */ void Domain::set_local_box() { - int *myloc = comm->myloc; - int *procgrid = comm->procgrid; - double *xsplit = comm->xsplit; - double *ysplit = comm->ysplit; - double *zsplit = comm->zsplit; + if (triclinic) return; + + if (comm->layout != LAYOUT_TILED) { + int *myloc = comm->myloc; + int *procgrid = comm->procgrid; + double *xsplit = comm->xsplit; + double *ysplit = comm->ysplit; + double *zsplit = comm->zsplit; - if (triclinic == 0) { sublo[0] = boxlo[0] + xprd*xsplit[myloc[0]]; if (myloc[0] < procgrid[0]-1) subhi[0] = boxlo[0] + xprd*xsplit[myloc[0]+1]; @@ -299,6 +314,21 @@ void Domain::set_local_box() if (myloc[2] < procgrid[2]-1) subhi[2] = boxlo[2] + zprd*zsplit[myloc[2]+1]; else subhi[2] = boxhi[2]; + + } else { + double (*mysplit)[2] = comm->mysplit; + + sublo[0] = boxlo[0] + xprd*mysplit[0][0]; + subhi[0] = boxlo[0] + xprd*mysplit[0][1]; + if (mysplit[0][1] == 1.0) subhi[0] = boxhi[0]; + + sublo[1] = boxlo[1] + yprd*mysplit[1][0]; + subhi[1] = boxlo[1] + yprd*mysplit[1][1]; + if (mysplit[1][1] == 1.0) subhi[1] = boxhi[1]; + + sublo[2] = boxlo[2] + zprd*mysplit[2][0]; + subhi[2] = boxlo[2] + zprd*mysplit[2][1]; + if (mysplit[2][1] == 1.0) subhi[2] = boxhi[2]; } } diff --git a/src/fix_balance.cpp b/src/fix_balance.cpp index e4b7b28b7d..c617d5d042 100644 --- a/src/fix_balance.cpp +++ b/src/fix_balance.cpp @@ -22,12 +22,14 @@ #include "irregular.h" #include "force.h" #include "kspace.h" +#include "rcb.h" #include "error.h" using namespace LAMMPS_NS; using namespace FixConst; -enum{SHIFT,RCB}; +enum{SHIFT,BISECTION}; +enum{LAYOUT_UNIFORM,LAYOUT_NONUNIFORM,LAYOUT_TILED}; // several files /* ---------------------------------------------------------------------- */ @@ -53,7 +55,7 @@ FixBalance::FixBalance(LAMMPS *lmp, int narg, char **arg) : thresh = force->numeric(FLERR,arg[4]); if (strcmp(arg[5],"shift") == 0) lbstyle = SHIFT; - else if (strcmp(arg[5],"rcb") == 0) lbstyle = RCB; + else if (strcmp(arg[5],"rcb") == 0) lbstyle = BISECTION; else error->all(FLERR,"Illegal fix balance command"); int iarg = 5; @@ -65,7 +67,7 @@ FixBalance::FixBalance(LAMMPS *lmp, int narg, char **arg) : stopthresh = force->numeric(FLERR,arg[iarg+3]); if (stopthresh < 1.0) error->all(FLERR,"Illegal fix balance command"); iarg += 4; - } else if (lbstyle == RCB) { + } else if (lbstyle == BISECTION) { iarg++; } @@ -97,14 +99,16 @@ FixBalance::FixBalance(LAMMPS *lmp, int narg, char **arg) : } } - // create instance of Balance class and initialize it with params - // NOTE: do I need Balance instance if RCB? - // create instance of Irregular class + if (lbstyle == BISECTION && comm->style == 0) + error->all(FLERR,"Fix balance rcb cannot be used with comm_style brick"); + + // create instance of Balance class + // if SHIFT, initialize it with params balance = new Balance(lmp); - if (lbstyle == SHIFT) balance->shift_setup(bstr,nitermax,thresh); - if (lbstyle == RCB) error->all(FLERR,"Fix balance rcb is not yet supported"); + + // create instance of Irregular class irregular = new Irregular(lmp); @@ -238,32 +242,33 @@ void FixBalance::rebalance() { imbprev = imbnow; + // invoke balancer and reset comm->uniform flag + + int *sendproc; if (lbstyle == SHIFT) { itercount = balance->shift(); - } else if (lbstyle == RCB) { + comm->layout = LAYOUT_NONUNIFORM; + } else if (lbstyle == BISECTION) { + sendproc = balance->bisection(); + comm->layout = LAYOUT_TILED; } // output of final result if (fp) balance->dumpout(update->ntimestep,fp); - // reset comm->uniform flag - // NOTE: this needs to change with RCB - - comm->uniform = 0; - // reset proc sub-domains if (domain->triclinic) domain->set_lamda_box(); domain->set_local_box(); - // if splits moved further than neighboring processor // move atoms to new processors via irregular() - // only needed if migrate_check() says an atom moves to far, + // only needed if migrate_check() says an atom moves to far // else allow caller's comm->exchange() to do it if (domain->triclinic) domain->x2lamda(atom->nlocal); - if (irregular->migrate_check()) irregular->migrate_atoms(); + if (lbstyle == BISECTION) irregular->migrate_atoms(0,sendproc); + else if (irregular->migrate_check()) irregular->migrate_atoms(); if (domain->triclinic) domain->lamda2x(atom->nlocal); // invoke KSpace setup_grid() to adjust to new proc sub-domains @@ -303,5 +308,6 @@ double FixBalance::compute_vector(int i) double FixBalance::memory_usage() { double bytes = irregular->memory_usage(); + if (balance->rcb) bytes += balance->rcb->memory_usage(); return bytes; } diff --git a/src/fix_deform.cpp b/src/fix_deform.cpp index 3401c61a67..3d350c569a 100644 --- a/src/fix_deform.cpp +++ b/src/fix_deform.cpp @@ -987,3 +987,14 @@ void FixDeform::options(int narg, char **arg) } else error->all(FLERR,"Illegal fix deform command"); } } + +/* ---------------------------------------------------------------------- + memory usage of Irregular +------------------------------------------------------------------------- */ + +double FixDeform::memory_usage() +{ + double bytes = 0.0; + if (irregular) bytes += irregular->memory_usage(); + return bytes; +} diff --git a/src/fix_deform.h b/src/fix_deform.h index c8f7d8a829..e0bc3a1a6e 100644 --- a/src/fix_deform.h +++ b/src/fix_deform.h @@ -35,6 +35,7 @@ class FixDeform : public Fix { void init(); void pre_exchange(); void end_of_step(); + double memory_usage(); private: int triclinic,scaleflag,flipflag; diff --git a/src/fix_nh.cpp b/src/fix_nh.cpp index 1abc05c739..b254636111 100644 --- a/src/fix_nh.cpp +++ b/src/fix_nh.cpp @@ -2271,3 +2271,14 @@ void FixNH::pre_exchange() domain->lamda2x(atom->nlocal); } } + +/* ---------------------------------------------------------------------- + memory usage of Irregular +------------------------------------------------------------------------- */ + +double FixNH::memory_usage() +{ + double bytes = 0.0; + if (irregular) bytes += irregular->memory_usage(); + return bytes; +} diff --git a/src/fix_nh.h b/src/fix_nh.h index e32d9c2669..71ea86eebc 100644 --- a/src/fix_nh.h +++ b/src/fix_nh.h @@ -39,6 +39,7 @@ class FixNH : public Fix { void reset_target(double); void reset_dt(); virtual void *extract(const char*,int &); + double memory_usage(); protected: int dimension,which; diff --git a/src/input.cpp b/src/input.cpp index c3b265180b..65f8d6c19a 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -1159,19 +1159,15 @@ void Input::comm_style() { if (narg < 1) error->all(FLERR,"Illegal comm_style command"); if (strcmp(arg[0],"brick") == 0) { - if (comm->layout) - error->all(FLERR, - "Cannot switch to comm style brick from " - "irregular tiling of proc domains"); - comm = new CommBrick(lmp); - // NOTE: this will lose load balancing info in old CommBrick - if (domain->box_exist) { - comm->set_proc_grid(); - domain->set_local_box(); - } + if (comm->style == 0) return; + Comm *oldcomm = comm; + comm = new CommBrick(lmp,oldcomm); + delete oldcomm; } else if (strcmp(arg[0],"tiled") == 0) { - error->all(FLERR,"Comm_style tiled not yet supported"); - comm = new CommTiled(lmp); + if (comm->style == 1) return; + Comm *oldcomm = comm; + comm = new CommTiled(lmp,oldcomm); + delete oldcomm; } else error->all(FLERR,"Illegal comm_style command"); } diff --git a/src/irregular.cpp b/src/irregular.cpp index b55d964327..9fd478a9da 100644 --- a/src/irregular.cpp +++ b/src/irregular.cpp @@ -30,6 +30,8 @@ using namespace LAMMPS_NS; int *Irregular::proc_recv_copy; int compare_standalone(const void *, const void *); +enum{LAYOUT_UNIFORM,LAYOUT_NONUNIFORM,LAYOUT_TILED}; // several files + #define BUFFACTOR 1.5 #define BUFMIN 1000 #define BUFEXTRA 1000 @@ -43,13 +45,26 @@ Irregular::Irregular(LAMMPS *lmp) : Pointers(lmp) triclinic = domain->triclinic; map_style = atom->map_style; - procgrid = comm->procgrid; - grid2proc = comm->grid2proc; - aplan = NULL; - dplan = NULL; + // migrate work vectors - // initialize buffers for atom comm, not used for datum comm + maxlocal = 0; + mproclist = NULL; + msizes = NULL; + + // send buffers + + maxdbuf = 0; + dbuf = NULL; + maxbuf = 0; + buf = NULL; + + // universal work vectors + + memory->create(work1,nprocs,"irregular:work1"); + memory->create(work2,nprocs,"irregular:work2"); + + // initialize buffers for migrate atoms, not used for datum comm // these can persist for multiple irregular operations maxsend = BUFMIN; @@ -62,9 +77,12 @@ Irregular::Irregular(LAMMPS *lmp) : Pointers(lmp) Irregular::~Irregular() { - if (aplan) destroy_atom(); - if (dplan) destroy_data(); - + memory->destroy(mproclist); + memory->destroy(msizes); + memory->destroy(dbuf); + memory->destroy(buf); + memory->destroy(work1); + memory->destroy(work2); memory->destroy(buf_send); memory->destroy(buf_recv); } @@ -74,11 +92,13 @@ Irregular::~Irregular() can be used in place of comm->exchange() unlike exchange(), allows atoms to have moved arbitrarily long distances sets up irregular plan, invokes it, destroys it + sortflag = flag for sorting order of received messages by proc ID + procassign = non-NULL if already know procs atoms are assigned to (from RCB) atoms MUST be remapped to be inside simulation box before this is called for triclinic: atoms must be in lamda coords (0-1) before this is called ------------------------------------------------------------------------- */ -void Irregular::migrate_atoms(int sortflag) +void Irregular::migrate_atoms(int sortflag, int *procassign) { // clear global->local map since atoms move to new procs // clear old ghosts so map_set() at end will operate only on local atoms @@ -101,13 +121,16 @@ void Irregular::migrate_atoms(int sortflag) subhi = domain->subhi_lamda; } - uniform = comm->uniform; + layout = comm->layout; xsplit = comm->xsplit; ysplit = comm->ysplit; zsplit = comm->zsplit; boxlo = domain->boxlo; prd = domain->prd; + procgrid = comm->procgrid; + grid2proc = comm->grid2proc; + // loop over atoms, flag any that are not in my sub-box // fill buffer with atoms leaving my box, using < and >= // assign which proc it belongs to via coord2proc() @@ -119,41 +142,63 @@ void Irregular::migrate_atoms(int sortflag) double **x = atom->x; int nlocal = atom->nlocal; + if (nlocal > maxlocal) { + maxlocal = nlocal; + memory->destroy(mproclist); + memory->destroy(msizes); + memory->create(mproclist,maxlocal,"irregular:mproclist"); + memory->create(msizes,maxlocal,"irregular:msizes"); + } + + int igx,igy,igz; int nsend = 0; int nsendatom = 0; - int *sizes = new int[nlocal]; - int *proclist = new int[nlocal]; - int igx,igy,igz; - int i = 0; - while (i < nlocal) { - if (x[i][0] < sublo[0] || x[i][0] >= subhi[0] || - x[i][1] < sublo[1] || x[i][1] >= subhi[1] || - x[i][2] < sublo[2] || x[i][2] >= subhi[2]) { - proclist[nsendatom] = coord2proc(x[i],igx,igy,igz); - if (proclist[nsendatom] != me) { + + if (procassign) { + while (i < nlocal) { + if (procassign[i] == me) i++; + else { + mproclist[nsendatom] = procassign[i]; if (nsend > maxsend) grow_send(nsend,1); - sizes[nsendatom] = avec->pack_exchange(i,&buf_send[nsend]); - nsend += sizes[nsendatom]; + msizes[nsendatom] = avec->pack_exchange(i,&buf_send[nsend]); + nsend += msizes[nsendatom]; nsendatom++; avec->copy(nlocal-1,i,1); + procassign[i] = procassign[nlocal-1]; nlocal--; + } + } + + } else { + while (i < nlocal) { + if (x[i][0] < sublo[0] || x[i][0] >= subhi[0] || + x[i][1] < sublo[1] || x[i][1] >= subhi[1] || + x[i][2] < sublo[2] || x[i][2] >= subhi[2]) { + mproclist[nsendatom] = coord2proc(x[i],igx,igy,igz); + if (mproclist[nsendatom] == me) i++; + else { + if (nsend > maxsend) grow_send(nsend,1); + msizes[nsendatom] = avec->pack_exchange(i,&buf_send[nsend]); + nsend += msizes[nsendatom]; + nsendatom++; + avec->copy(nlocal-1,i,1); + nlocal--; + } } else i++; - } else i++; + } } + atom->nlocal = nlocal; // create irregular communication plan, perform comm, destroy plan // returned nrecv = size of buffer needed for incoming atoms - int nrecv = create_atom(nsendatom,sizes,proclist,sortflag); + int nrecv = create_atom(nsendatom,msizes,mproclist,sortflag); if (nrecv > maxrecv) grow_recv(nrecv); - exchange_atom(buf_send,sizes,buf_recv); + exchange_atom(buf_send,msizes,buf_recv); destroy_atom(); - delete [] sizes; - delete [] proclist; - // add received atoms to my list int m = 0; @@ -174,6 +219,11 @@ void Irregular::migrate_atoms(int sortflag) int Irregular::migrate_check() { + // migrate required if comm layout is tiled + // cannot use myloc[] logic below + + if (comm->layout == LAYOUT_TILED) return 1; + // subbox bounds for orthogonal or triclinic box // other comm/domain data used by coord2proc() @@ -186,13 +236,16 @@ int Irregular::migrate_check() subhi = domain->subhi_lamda; } - uniform = comm->uniform; + layout = comm->layout; xsplit = comm->xsplit; ysplit = comm->ysplit; zsplit = comm->zsplit; boxlo = domain->boxlo; prd = domain->prd; + procgrid = comm->procgrid; + grid2proc = comm->grid2proc; + // loop over atoms, check for any that are not in my sub-box // assign which proc it belongs to via coord2proc() // if logical igx,igy,igz of newproc > one away from myloc, set flag = 1 @@ -250,6 +303,7 @@ int Irregular::migrate_check() n = # of atoms to send sizes = # of doubles for each atom proclist = proc to send each atom to (not including self) + sortflag = flag for sorting order of received messages by proc ID return total # of doubles I will recv (not including self) ------------------------------------------------------------------------- */ @@ -257,51 +311,60 @@ int Irregular::create_atom(int n, int *sizes, int *proclist, int sortflag) { int i; - // allocate plan and work vectors - - if (aplan) destroy_atom(); - aplan = (PlanAtom *) memory->smalloc(sizeof(PlanAtom),"irregular:aplan"); - int *list = new int[nprocs]; - int *count = new int[nprocs]; - - // nrecv = # of messages I receive + // setup for collective comm + // work1 = 1 for procs I send a message to, not including self + // work2 = 1 for all procs, used for ReduceScatter for (i = 0; i < nprocs; i++) { - list[i] = 0; - count[i] = 1; + work1[i] = 0; + work2[i] = 1; } - for (i = 0; i < n; i++) list[proclist[i]] = 1; + for (i = 0; i < n; i++) work1[proclist[i]] = 1; + work1[me] = 0; - int nrecv; - MPI_Reduce_scatter(list,&nrecv,count,MPI_INT,MPI_SUM,world); + // nrecv_proc = # of procs I receive messages from, not including self + // options for performing ReduceScatter operation + // some are more efficient on some machines at big sizes + +#ifdef LAMMPS_RS_ALLREDUCE_INPLACE + MPI_Allreduce(MPI_IN_PLACE,work1,nprocs,MPI_INT,MPI_SUM,world); + nrecv_proc = work1[me]; +#else +#ifdef LAMMPS_RS_ALLREDUCE + MPI_Allreduce(work1,work2,nprocs,MPI_INT,MPI_SUM,world); + nrecv_proc = work2[me]; +#else + MPI_Reduce_scatter(work1,&nrecv_proc,work2,MPI_INT,MPI_SUM,world); +#endif +#endif // allocate receive arrays - int *proc_recv = new int[nrecv]; - int *length_recv = new int[nrecv]; - MPI_Request *request = new MPI_Request[nrecv]; - MPI_Status *status = new MPI_Status[nrecv]; + proc_recv = new int[nrecv_proc]; + length_recv = new int[nrecv_proc]; + request = new MPI_Request[nrecv_proc]; + status = new MPI_Status[nrecv_proc]; - // nsend = # of messages I send + // nsend_proc = # of messages I send - for (i = 0; i < nprocs; i++) list[i] = 0; - for (i = 0; i < n; i++) list[proclist[i]] += sizes[i]; + for (i = 0; i < nprocs; i++) work1[i] = 0; + for (i = 0; i < n; i++) work1[proclist[i]] += sizes[i]; - int nsend = 0; + nsend_proc = 0; for (i = 0; i < nprocs; i++) - if (list[i]) nsend++; + if (work1[i]) nsend_proc++; // allocate send arrays - int *proc_send = new int[nsend]; - int *length_send = new int[nsend]; - int *num_send = new int[nsend]; - int *index_send = new int[n]; - int *offset_send = new int[n]; + proc_send = new int[nsend_proc]; + length_send = new int[nsend_proc]; + num_send = new int[nsend_proc]; + index_send = new int[n]; + offset_send = new int[n]; // list still stores size of message for procs I send to // proc_send = procs I send to - // length_send = total size of message I send to each proc + // length_send = # of doubles I send to each proc // to balance pattern of send messages: // each proc begins with iproc > me, continues until iproc = me // reset list to store which send message each proc corresponds to @@ -311,81 +374,81 @@ int Irregular::create_atom(int n, int *sizes, int *proclist, int sortflag) for (i = 0; i < nprocs; i++) { iproc++; if (iproc == nprocs) iproc = 0; - if (list[iproc] > 0) { + if (work1[iproc] > 0) { proc_send[isend] = iproc; - length_send[isend] = list[iproc]; - list[iproc] = isend; + length_send[isend] = work1[iproc]; + work1[iproc] = isend; isend++; } } // num_send = # of atoms I send to each proc - for (i = 0; i < nsend; i++) num_send[i] = 0; + for (i = 0; i < nsend_proc; i++) num_send[i] = 0; for (i = 0; i < n; i++) { - isend = list[proclist[i]]; + isend = work1[proclist[i]]; num_send[isend]++; } - // count = offsets into index_send for each proc I send to + // work2 = offsets into index_send for each proc I send to // index_send = list of which atoms to send to each proc // 1st N1 values are atom indices for 1st proc, // next N2 values are atom indices for 2nd proc, etc // offset_send = where each atom starts in send buffer - count[0] = 0; - for (i = 1; i < nsend; i++) count[i] = count[i-1] + num_send[i-1]; + work2[0] = 0; + for (i = 1; i < nsend_proc; i++) work2[i] = work2[i-1] + num_send[i-1]; for (i = 0; i < n; i++) { - isend = list[proclist[i]]; - index_send[count[isend]++] = i; + isend = work1[proclist[i]]; + index_send[work2[isend]++] = i; if (i) offset_send[i] = offset_send[i-1] + sizes[i-1]; else offset_send[i] = 0; } // tell receivers how much data I send - // sendmax = largest # of doubles I send in a single message + // sendmax_proc = # of doubles I send in largest single message - int sendmax = 0; - for (i = 0; i < nsend; i++) { + sendmax_proc = 0; + for (i = 0; i < nsend_proc; i++) { MPI_Send(&length_send[i],1,MPI_INT,proc_send[i],0,world); - sendmax = MAX(sendmax,length_send[i]); + sendmax_proc = MAX(sendmax_proc,length_send[i]); } // receive incoming messages // proc_recv = procs I recv from - // length_recv = total size of message each proc sends me - // nrecvsize = total size of data I recv + // length_recv = # of doubles each proc sends me + // nrecvsize = total size of atom data I recv int nrecvsize = 0; - for (i = 0; i < nrecv; i++) { + for (i = 0; i < nrecv_proc; i++) { MPI_Recv(&length_recv[i],1,MPI_INT,MPI_ANY_SOURCE,0,world,status); proc_recv[i] = status->MPI_SOURCE; nrecvsize += length_recv[i]; } - // sort proc_recv and num_recv by proc ID if requested + // sort proc_recv and length_recv by proc ID if requested // useful for debugging to insure reproducible ordering of received atoms // invoke by adding final arg = 1 to create_atom() call in migrate_atoms() if (sortflag) { - int *order = new int[nrecv]; - int *proc_recv_ordered = new int[nrecv]; - int *length_recv_ordered = new int[nrecv]; + int *order = new int[nrecv_proc]; + int *proc_recv_ordered = new int[nrecv_proc]; + int *length_recv_ordered = new int[nrecv_proc]; - for (i = 0; i < nrecv; i++) order[i] = i; + for (i = 0; i < nrecv_proc; i++) order[i] = i; proc_recv_copy = proc_recv; - qsort(order,nrecv,sizeof(int),compare_standalone); + qsort(order,nrecv_proc,sizeof(int),compare_standalone); int j; - for (i = 0; i < nrecv; i++) { + for (i = 0; i < nrecv_proc; i++) { j = order[i]; proc_recv_ordered[i] = proc_recv[j]; length_recv_ordered[i] = length_recv[j]; } - memcpy(proc_recv,proc_recv_ordered,nrecv*sizeof(int)); - memcpy(length_recv,length_recv_ordered,nrecv*sizeof(int)); + memcpy(proc_recv,proc_recv_ordered,nrecv_proc*sizeof(int)); + memcpy(length_recv,length_recv_ordered,nrecv_proc*sizeof(int)); delete [] order; delete [] proc_recv_ordered; delete [] length_recv_ordered; @@ -396,27 +459,7 @@ int Irregular::create_atom(int n, int *sizes, int *proclist, int sortflag) MPI_Barrier(world); - // free work vectors - - delete [] count; - delete [] list; - - // initialize plan - - aplan->nsend = nsend; - aplan->nrecv = nrecv; - aplan->sendmax = sendmax; - - aplan->proc_send = proc_send; - aplan->length_send = length_send; - aplan->num_send = num_send; - aplan->index_send = index_send; - aplan->offset_send = offset_send; - aplan->proc_recv = proc_recv; - aplan->length_recv = length_recv; - - aplan->request = request; - aplan->status = status; + // return size of atom data I will receive return nrecvsize; } @@ -445,217 +488,226 @@ int compare_standalone(const void *iptr, const void *jptr) void Irregular::exchange_atom(double *sendbuf, int *sizes, double *recvbuf) { - int i,m,n,offset,num_send; + int i,m,n,offset,count; // post all receives offset = 0; - for (int irecv = 0; irecv < aplan->nrecv; irecv++) { - MPI_Irecv(&recvbuf[offset],aplan->length_recv[irecv],MPI_DOUBLE, - aplan->proc_recv[irecv],0,world,&aplan->request[irecv]); - offset += aplan->length_recv[irecv]; + for (int irecv = 0; irecv < nrecv_proc; irecv++) { + MPI_Irecv(&recvbuf[offset],length_recv[irecv],MPI_DOUBLE, + proc_recv[irecv],0,world,&request[irecv]); + offset += length_recv[irecv]; } - // allocate buf for largest send + // reallocate buf for largest send if necessary - double *buf; - memory->create(buf,aplan->sendmax,"irregular:buf"); + if (sendmax_proc > maxdbuf) { + memory->destroy(dbuf); + maxdbuf = sendmax_proc; + memory->create(dbuf,maxdbuf,"irregular:dbuf"); + } // send each message // pack buf with list of atoms // m = index of atom in sendbuf - int *index_send = aplan->index_send; - int nsend = aplan->nsend; n = 0; - - for (int isend = 0; isend < nsend; isend++) { + for (int isend = 0; isend < nsend_proc; isend++) { offset = 0; - num_send = aplan->num_send[isend]; - for (i = 0; i < num_send; i++) { + count = num_send[isend]; + for (i = 0; i < count; i++) { m = index_send[n++]; - memcpy(&buf[offset],&sendbuf[aplan->offset_send[m]], - sizes[m]*sizeof(double)); + memcpy(&dbuf[offset],&sendbuf[offset_send[m]],sizes[m]*sizeof(double)); offset += sizes[m]; } - MPI_Send(buf,aplan->length_send[isend],MPI_DOUBLE, - aplan->proc_send[isend],0,world); + MPI_Send(dbuf,length_send[isend],MPI_DOUBLE,proc_send[isend],0,world); } - // free temporary send buffer - - memory->destroy(buf); - // wait on all incoming messages - if (aplan->nrecv) MPI_Waitall(aplan->nrecv,aplan->request,aplan->status); + if (nrecv_proc) MPI_Waitall(nrecv_proc,request,status); } /* ---------------------------------------------------------------------- - destroy communication plan for atoms + destroy vectors in communication plan for atoms ------------------------------------------------------------------------- */ void Irregular::destroy_atom() { - delete [] aplan->proc_send; - delete [] aplan->length_send; - delete [] aplan->num_send; - delete [] aplan->index_send; - delete [] aplan->offset_send; - delete [] aplan->proc_recv; - delete [] aplan->length_recv; - delete [] aplan->request; - delete [] aplan->status; - memory->sfree(aplan); - aplan = NULL; + delete [] proc_send; + delete [] length_send; + delete [] num_send; + delete [] index_send; + delete [] offset_send; + delete [] proc_recv; + delete [] length_recv; + delete [] request; + delete [] status; } /* ---------------------------------------------------------------------- - create a communication plan for datums + create communication plan based on list of datums of uniform size n = # of datums to send - proclist = proc to send each datum to (including self) - return total # of datums I will recv (including self) + proclist = proc to send each datum to, can include self + sortflag = flag for sorting order of received messages by proc ID + return total # of datums I will recv, including any to self ------------------------------------------------------------------------- */ -int Irregular::create_data(int n, int *proclist) +int Irregular::create_data(int n, int *proclist, int sortflag) { int i,m; - // allocate plan and work vectors - - dplan = (PlanData *) memory->smalloc(sizeof(PlanData),"irregular:dplan"); - int *list = new int[nprocs]; - int *count = new int[nprocs]; - - // nrecv = # of messages I receive + // setup for collective comm + // work1 = 1 for procs I send a message to, not including self + // work2 = 1 for all procs, used for ReduceScatter for (i = 0; i < nprocs; i++) { - list[i] = 0; - count[i] = 1; + work1[i] = 0; + work2[i] = 1; } - for (i = 0; i < n; i++) list[proclist[i]] = 1; + for (i = 0; i < n; i++) work1[proclist[i]] = 1; + work1[me] = 0; - int nrecv; - MPI_Reduce_scatter(list,&nrecv,count,MPI_INT,MPI_SUM,world); - if (list[me]) nrecv--; + // nrecv_proc = # of procs I receive messages from, not including self + // options for performing ReduceScatter operation + // some are more efficient on some machines at big sizes + +#ifdef LAMMPS_RS_ALLREDUCE_INPLACE + MPI_Allreduce(MPI_IN_PLACE,work1,nprocs,MPI_INT,MPI_SUM,world); + nrecv_proc = work1[me]; +#else +#ifdef LAMMPS_RS_ALLREDUCE + MPI_Allreduce(work1,work2,nprocs,MPI_INT,MPI_SUM,world); + nrecv_proc = work2[me]; +#else + MPI_Reduce_scatter(work1,&nrecv_proc,work2,MPI_INT,MPI_SUM,world); +#endif +#endif // allocate receive arrays - int *proc_recv = new int[nrecv]; - int *num_recv = new int[nrecv]; - MPI_Request *request = new MPI_Request[nrecv]; - MPI_Status *status = new MPI_Status[nrecv]; + proc_recv = new int[nrecv_proc]; + num_recv = new int[nrecv_proc]; + request = new MPI_Request[nrecv_proc]; + status = new MPI_Status[nrecv_proc]; - // nsend = # of messages I send + // work1 = # of datums I send to each proc, including self + // nsend_proc = # of procs I send messages to, not including self - for (i = 0; i < nprocs; i++) list[i] = 0; - for (i = 0; i < n; i++) list[proclist[i]]++; + for (i = 0; i < nprocs; i++) work1[i] = 0; + for (i = 0; i < n; i++) work1[proclist[i]]++; - int nsend = 0; + nsend_proc = 0; for (i = 0; i < nprocs; i++) - if (list[i]) nsend++; - if (list[me]) nsend--; + if (work1[i]) nsend_proc++; + if (work1[me]) nsend_proc--; // allocate send and self arrays - int *proc_send = new int[nsend]; - int *num_send = new int[nsend]; - int *index_send = new int[n-list[me]]; - int *index_self = new int[list[me]]; + proc_send = new int[nsend_proc]; + num_send = new int[nsend_proc]; + index_send = new int[n-work1[me]]; + index_self = new int[work1[me]]; // proc_send = procs I send to // num_send = # of datums I send to each proc // num_self = # of datums I copy to self // to balance pattern of send messages: // each proc begins with iproc > me, continues until iproc = me - // reset list to store which send message each proc corresponds to - - int num_self; + // reset work1 to store which send message each proc corresponds to int iproc = me; int isend = 0; for (i = 0; i < nprocs; i++) { iproc++; if (iproc == nprocs) iproc = 0; - if (iproc == me) num_self = list[iproc]; - else if (list[iproc] > 0) { + if (iproc == me) { + num_self = work1[iproc]; + work1[iproc] = 0; + } else if (work1[iproc] > 0) { proc_send[isend] = iproc; - num_send[isend] = list[iproc]; - list[iproc] = isend; + num_send[isend] = work1[iproc]; + work1[iproc] = isend; isend++; } } - list[me] = 0; - // count = offsets into index_send for each proc I send to + // work2 = offsets into index_send for each proc I send to // m = ptr into index_self // index_send = list of which datums to send to each proc // 1st N1 values are datum indices for 1st proc, // next N2 values are datum indices for 2nd proc, etc + // index_self = list of which datums to copy to self - count[0] = 0; - for (i = 1; i < nsend; i++) count[i] = count[i-1] + num_send[i-1]; + work2[0] = 0; + for (i = 1; i < nsend_proc; i++) work2[i] = work2[i-1] + num_send[i-1]; m = 0; for (i = 0; i < n; i++) { iproc = proclist[i]; if (iproc == me) index_self[m++] = i; else { - isend = list[iproc]; - index_send[count[isend]++] = i; + isend = work1[iproc]; + index_send[work2[isend]++] = i; } } // tell receivers how much data I send - // sendmax = largest # of datums I send in a single message + // sendmax_proc = largest # of datums I send in a single message - int sendmax = 0; - for (i = 0; i < nsend; i++) { + sendmax_proc = 0; + for (i = 0; i < nsend_proc; i++) { MPI_Send(&num_send[i],1,MPI_INT,proc_send[i],0,world); - sendmax = MAX(sendmax,num_send[i]); + sendmax_proc = MAX(sendmax_proc,num_send[i]); } // receive incoming messages // proc_recv = procs I recv from // num_recv = total size of message each proc sends me - // nrecvsize = total size of data I recv + // nrecvdatum = total size of data I recv - int nrecvsize = 0; - for (i = 0; i < nrecv; i++) { + int nrecvdatum = 0; + for (i = 0; i < nrecv_proc; i++) { MPI_Recv(&num_recv[i],1,MPI_INT,MPI_ANY_SOURCE,0,world,status); proc_recv[i] = status->MPI_SOURCE; - nrecvsize += num_recv[i]; + nrecvdatum += num_recv[i]; + } + nrecvdatum += num_self; + + // sort proc_recv and num_recv by proc ID if requested + // useful for debugging to insure reproducible ordering of received datums + + if (sortflag) { + int *order = new int[nrecv_proc]; + int *proc_recv_ordered = new int[nrecv_proc]; + int *num_recv_ordered = new int[nrecv_proc]; + + for (i = 0; i < nrecv_proc; i++) order[i] = i; + proc_recv_copy = proc_recv; + qsort(order,nrecv_proc,sizeof(int),compare_standalone); + + int j; + for (i = 0; i < nrecv_proc; i++) { + j = order[i]; + proc_recv_ordered[i] = proc_recv[j]; + num_recv_ordered[i] = num_recv[j]; + } + + memcpy(proc_recv,proc_recv_ordered,nrecv_proc*sizeof(int)); + memcpy(num_recv,num_recv_ordered,nrecv_proc*sizeof(int)); + delete [] order; + delete [] proc_recv_ordered; + delete [] num_recv_ordered; } - nrecvsize += num_self; // barrier to insure all MPI_ANY_SOURCE messages are received // else another proc could proceed to exchange_data() and send to me MPI_Barrier(world); - // free work vectors + // return # of datums I will receive - delete [] count; - delete [] list; - - // initialize plan and return it - - dplan->nsend = nsend; - dplan->nrecv = nrecv; - dplan->sendmax = sendmax; - - dplan->proc_send = proc_send; - dplan->num_send = num_send; - dplan->index_send = index_send; - dplan->proc_recv = proc_recv; - dplan->num_recv = num_recv; - dplan->num_self = num_self; - dplan->index_self = index_self; - - dplan->request = request; - dplan->status = status; - - return nrecvsize; + return nrecvdatum; } /* ---------------------------------------------------------------------- @@ -667,49 +719,41 @@ int Irregular::create_data(int n, int *proclist) void Irregular::exchange_data(char *sendbuf, int nbytes, char *recvbuf) { - int i,m,n,offset,num_send; + int i,m,n,offset,count; // post all receives, starting after self copies - offset = dplan->num_self*nbytes; - for (int irecv = 0; irecv < dplan->nrecv; irecv++) { - MPI_Irecv(&recvbuf[offset],dplan->num_recv[irecv]*nbytes,MPI_CHAR, - dplan->proc_recv[irecv],0,world,&dplan->request[irecv]); - offset += dplan->num_recv[irecv]*nbytes; + offset = num_self*nbytes; + for (int irecv = 0; irecv < nrecv_proc; irecv++) { + MPI_Irecv(&recvbuf[offset],num_recv[irecv]*nbytes,MPI_CHAR, + proc_recv[irecv],0,world,&request[irecv]); + offset += num_recv[irecv]*nbytes; } - // allocate buf for largest send + // reallocate buf for largest send if necessary - char *buf; - memory->create(buf,dplan->sendmax*nbytes,"irregular:buf"); + if (sendmax_proc*nbytes > maxbuf) { + memory->destroy(buf); + maxbuf = sendmax_proc*nbytes; + memory->create(buf,maxbuf,"irregular:buf"); + } // send each message // pack buf with list of datums // m = index of datum in sendbuf - int *index_send = dplan->index_send; - int nsend = dplan->nsend; n = 0; - - for (int isend = 0; isend < nsend; isend++) { - num_send = dplan->num_send[isend]; - for (i = 0; i < num_send; i++) { + for (int isend = 0; isend < nsend_proc; isend++) { + count = num_send[isend]; + for (i = 0; i < count; i++) { m = index_send[n++]; memcpy(&buf[i*nbytes],&sendbuf[m*nbytes],nbytes); } - MPI_Send(buf,dplan->num_send[isend]*nbytes,MPI_CHAR, - dplan->proc_send[isend],0,world); + MPI_Send(buf,count*nbytes,MPI_CHAR,proc_send[isend],0,world); } - // free temporary send buffer - - memory->destroy(buf); - // copy datums to self, put at beginning of recvbuf - int *index_self = dplan->index_self; - int num_self = dplan->num_self; - for (i = 0; i < num_self; i++) { m = index_self[i]; memcpy(&recvbuf[i*nbytes],&sendbuf[m*nbytes],nbytes); @@ -717,39 +761,37 @@ void Irregular::exchange_data(char *sendbuf, int nbytes, char *recvbuf) // wait on all incoming messages - if (dplan->nrecv) MPI_Waitall(dplan->nrecv,dplan->request,dplan->status); + if (nrecv_proc) MPI_Waitall(nrecv_proc,request,status); } /* ---------------------------------------------------------------------- - destroy communication plan for datums + destroy vectors in communication plan for datums ------------------------------------------------------------------------- */ void Irregular::destroy_data() { - delete [] dplan->proc_send; - delete [] dplan->num_send; - delete [] dplan->index_send; - delete [] dplan->proc_recv; - delete [] dplan->num_recv; - delete [] dplan->index_self; - delete [] dplan->request; - delete [] dplan->status; - memory->sfree(dplan); - dplan = NULL; + delete [] proc_send; + delete [] num_send; + delete [] index_send; + delete [] proc_recv; + delete [] num_recv; + delete [] index_self; + delete [] request; + delete [] status; } /* ---------------------------------------------------------------------- determine which proc owns atom with coord x[3] x will be in box (orthogonal) or lamda coords (triclinic) - for uniform = 1, directly calculate owning proc - for non-uniform, iteratively find owning proc via binary search + if layout = UNIFORM, calculate owning proc directly + else layout = NONUNIFORM, iteratively find owning proc via binary search return owning proc ID via grid2proc return igx,igy,igz = logical grid loc of owing proc within 3d grid of procs ------------------------------------------------------------------------- */ int Irregular::coord2proc(double *x, int &igx, int &igy, int &igz) { - if (uniform) { + if (layout == 0) { if (triclinic == 0) { igx = static_cast (procgrid[0] * (x[0]-boxlo[0]) / prd[0]); igy = static_cast (procgrid[1] * (x[1]-boxlo[1]) / prd[1]); @@ -846,7 +888,12 @@ void Irregular::grow_recv(int n) bigint Irregular::memory_usage() { - bigint bytes = memory->usage(buf_send,maxsend); - bytes += memory->usage(buf_recv,maxrecv); + bigint bytes = 0; + bytes += maxsend*sizeof(double); // buf_send + bytes += maxrecv*sizeof(double); // buf_recv + bytes += maxdbuf*sizeof(double); // dbuf + bytes += maxbuf; // buf + bytes += 2*maxlocal*sizeof(int); // mproclist,msizes + bytes += 2*nprocs*sizeof(int); // work1,work2 return bytes; } diff --git a/src/irregular.h b/src/irregular.h index eba889c767..051b0d4df5 100644 --- a/src/irregular.h +++ b/src/irregular.h @@ -27,9 +27,9 @@ class Irregular : protected Pointers { Irregular(class LAMMPS *); ~Irregular(); - void migrate_atoms(int sortflag = 0); + void migrate_atoms(int sortflag = 0, int *procassign = NULL); int migrate_check(); - int create_data(int, int *); + int create_data(int, int *, int sortflag = 0); void exchange_data(char *, int, char *); void destroy_data(); bigint memory_usage(); @@ -38,58 +38,58 @@ class Irregular : protected Pointers { int me,nprocs; int triclinic; int map_style; - int uniform; + int layout; double *xsplit,*ysplit,*zsplit; // ptrs to comm int *procgrid; // ptr to comm int ***grid2proc; // ptr to comm double *boxlo; // ptr to domain double *prd; // ptr to domain - int maxsend,maxrecv; // size of buffers in # of doubles - double *buf_send,*buf_recv; + int maxsend,maxrecv; // size of buf send/recv in # of doubles + double *buf_send,*buf_recv; // bufs used in migrate_atoms + int maxdbuf; // size of double buf in bytes + double *dbuf; // double buf for largest single atom send + int maxbuf; // size of char buf in bytes + char *buf; // char buf for largest single data send - // plan for irregular communication of atoms + int *mproclist,*msizes; // persistent vectors in migrate_atoms + int maxlocal; // allocated size of mproclist and msizes + + int *work1,*work2; // work vectors + + // plan params for irregular communication of atoms or datums + // no params refer to atoms/data copied to self + + int nsend_proc; // # of messages to send + int nrecv_proc; // # of messages to recv + int sendmax_proc; // # of doubles/datums in largest send message + int *proc_send; // list of procs to send to + int *num_send; // # of atoms/datums to send to each proc + int *index_send; // list of which atoms/datums to send to each proc + int *proc_recv; // list of procs to recv from + MPI_Request *request; // MPI requests for posted recvs + MPI_Status *status; // MPI statuses for WaitAll + + // extra plan params plan for irregular communication of atoms // no params refer to atoms copied to self - struct PlanAtom { - int nsend; // # of messages to send - int nrecv; // # of messages to recv - int sendmax; // # of doubles in largest send message - int *proc_send; // procs to send to - int *length_send; // # of doubles to send to each proc - int *num_send; // # of atoms to send to each proc - int *index_send; // list of which atoms to send to each proc - int *offset_send; // where each atom starts in send buffer - int *proc_recv; // procs to recv from - int *length_recv; // # of doubles to recv from each proc - MPI_Request *request; // MPI requests for posted recvs - MPI_Status *status; // MPI statuses for WaitAll - }; + int *length_send; // # of doubles to send to each proc + int *length_recv; // # of doubles to recv from each proc + int *offset_send; // where each atom starts in send buffer - // plan for irregular communication of datums - // only 2 self params refer to atoms copied to self + // extra plan params plan for irregular communication of datums + // 2 self params refer to data copied to self - struct PlanData { // plan for irregular communication of data - int nsend; // # of messages to send - int nrecv; // # of messages to recv - int sendmax; // # of datums in largest send message - int *proc_send; // procs to send to - int *num_send; // # of datums to send to each proc - int *index_send; // list of which datums to send to each proc - int *proc_recv; // procs to recv from - int *num_recv; // # of datums to recv from each proc - int num_self; // # of datums to copy to self - int *index_self; // list of which datums to copy to self - MPI_Request *request; // MPI requests for posted recvs - MPI_Status *status; // MPI statuses for WaitAll - }; + int *num_recv; // # of datums to recv from each proc + int num_self; // # of datums to copy to self + int *index_self; // list of which datums to copy to self - PlanAtom *aplan; - PlanData *dplan; + // private methods int create_atom(int, int *, int *, int); void exchange_atom(double *, int *, double *); void destroy_atom(); + int coord2proc(double *, int &, int &, int &); int binary(double, int, double *); diff --git a/src/rcb.cpp b/src/rcb.cpp new file mode 100644 index 0000000000..cf4e12d213 --- /dev/null +++ b/src/rcb.cpp @@ -0,0 +1,926 @@ +/* ---------------------------------------------------------------------- + LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator + http://lammps.sandia.gov, Sandia National Laboratories + Steve Plimpton, sjplimp@sandia.gov + + Copyright (2003) Sandia Corporation. Under the terms of Contract + DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + certain rights in this software. This software is distributed under + the GNU General Public License. + + See the README file in the top-level LAMMPS directory. +------------------------------------------------------------------------- */ + +#include "mpi.h" +#include "string.h" +#include "rcb.h" +#include "irregular.h" +#include "memory.h" +#include "error.h" + +using namespace LAMMPS_NS; + +#define MYHUGE 1.0e30 +#define TINY 1.0e-6 + +// set this to bigger number after debugging + +#define DELTA 10 + +// prototypes for non-class functions + +void box_merge(void *, void *, int *, MPI_Datatype *); +void median_merge(void *, void *, int *, MPI_Datatype *); + +// NOTE: if want to have reuse flag, need to sum Tree across procs + +/* ---------------------------------------------------------------------- */ + +RCB::RCB(LAMMPS *lmp) : Pointers(lmp) +{ + MPI_Comm_rank(world,&me); + MPI_Comm_size(world,&nprocs); + + ndot = maxdot = 0; + dots = NULL; + + nlist = maxlist = 0; + dotlist = dotmark = NULL; + + maxbuf = 0; + buf = NULL; + + maxrecv = maxsend = 0; + recvproc = recvindex = sendproc = sendindex = NULL; + + tree = NULL; + irregular = NULL; + + // create MPI data and function types for box and median AllReduce ops + + MPI_Type_contiguous(6,MPI_DOUBLE,&box_type); + MPI_Type_commit(&box_type); + MPI_Type_contiguous(sizeof(Median),MPI_CHAR,&med_type); + MPI_Type_commit(&med_type); + + MPI_Op_create(box_merge,1,&box_op); + MPI_Op_create(median_merge,1,&med_op); + + reuse = 0; +} + +/* ---------------------------------------------------------------------- */ + +RCB::~RCB() +{ + memory->sfree(dots); + memory->destroy(dotlist); + memory->destroy(dotmark); + memory->sfree(buf); + + memory->destroy(recvproc); + memory->destroy(recvindex); + memory->destroy(sendproc); + memory->destroy(sendindex); + + memory->sfree(tree); + delete irregular; + + MPI_Type_free(&med_type); + MPI_Type_free(&box_type); + MPI_Op_free(&box_op); + MPI_Op_free(&med_op); +} + +/* ---------------------------------------------------------------------- + perform RCB balancing of N particles at coords X in bounding box LO/HI + if wt = NULL, ignore per-particle weights + if wt defined, per-particle weights > 0.0 + dimension = 2 or 3 + as documented in rcb.h: + sets noriginal,nfinal,nkeep,recvproc,recvindex,lo,hi + all proc particles will be inside or on surface of 3-d box + defined by final lo/hi + // NOTE: worry about re-use of data structs for fix balance + // NOTE: should I get rid of wt all together, will it be used? +------------------------------------------------------------------------- */ + +void RCB::compute(int dimension, int n, double **x, double *wt, + double *bboxlo, double *bboxhi) +{ + int i,j,k; + int keep,outgoing,incoming,incoming2; + int dim,markactive; + int indexlo,indexhi; + int first_iteration,breakflag; + double wttot,wtlo,wthi,wtsum,wtok,wtupto,wtmax; + double targetlo,targethi; + double valuemin,valuemax,valuehalf; + double tolerance; + MPI_Comm comm,comm_half; + MPI_Request request,request2; + MPI_Status status; + Median med,medme; + + // create list of my Dots + + ndot = nkeep = noriginal = n; + + if (ndot > maxdot) { + maxdot = ndot; + memory->sfree(dots); + dots = (Dot *) memory->smalloc(ndot*sizeof(Dot),"RCB:dots"); + } + + for (i = 0; i < ndot; i++) { + dots[i].x[0] = x[i][0]; + dots[i].x[1] = x[i][1]; + dots[i].x[2] = x[i][2]; + dots[i].proc = me; + dots[i].index = i; + } + + if (wt) + for (i = 0; i < ndot; i++) dots[i].wt = wt[i]; + else + for (i = 0; i < ndot; i++) dots[i].wt = 1.0; + + // initial bounding box = simulation box + // includes periodic or shrink-wrapped boundaries + + lo = bbox.lo; + hi = bbox.hi; + + lo[0] = bboxlo[0]; + lo[1] = bboxlo[1]; + lo[2] = bboxlo[2]; + hi[0] = bboxhi[0]; + hi[1] = bboxhi[1]; + hi[2] = bboxhi[2]; + + // initialize counters + + counters[0] = 0; + counters[1] = 0; + counters[2] = 0; + counters[3] = ndot; + counters[4] = maxdot; + counters[5] = 0; + counters[6] = 0; + + // create communicator for use in recursion + + MPI_Comm_dup(world,&comm); + + // recurse until partition is a single proc = me + // proclower,procupper = lower,upper procs in partition + // procmid = 1st proc in upper half of partition + + int procpartner,procpartner2; + int readnumber; + + int procmid; + int proclower = 0; + int procupper = nprocs - 1; + + while (proclower != procupper) { + + // if odd # of procs, lower partition gets extra one + + procmid = proclower + (procupper - proclower) / 2 + 1; + + // determine communication partner(s) + // readnumber = # of proc partners to read from + + if (me < procmid) + procpartner = me + (procmid - proclower); + else + procpartner = me - (procmid - proclower); + + int readnumber = 1; + if (procpartner > procupper) { + readnumber = 0; + procpartner--; + } + if (me == procupper && procpartner != procmid - 1) { + readnumber = 2; + procpartner2 = procpartner + 1; + } + + // wttot = summed weight of entire partition + // search tolerance = largest single weight (plus epsilon) + // targetlo = desired weight in lower half of partition + // targethi = desired weight in upper half of partition + + wtmax = wtsum = 0.0; + + if (wt) { + for (i = 0; i < ndot; i++) { + wtsum += dots[i].wt; + if (dots[i].wt > wtmax) wtmax = dots[i].wt; + } + } else { + for (i = 0; i < ndot; i++) wtsum += dots[i].wt; + wtmax = 1.0; + } + + MPI_Allreduce(&wtsum,&wttot,1,MPI_DOUBLE,MPI_SUM,comm); + if (wt) MPI_Allreduce(&wtmax,&tolerance,1,MPI_DOUBLE,MPI_MAX,comm); + else tolerance = 1.0; + + tolerance *= 1.0 + TINY; + targetlo = wttot * (procmid - proclower) / (procupper + 1 - proclower); + targethi = wttot - targetlo; + + // dim = dimension to bisect on + // do not allow choice of z dimension for 2d system + + dim = 0; + if (hi[1]-lo[1] > hi[0]-lo[0]) dim = 1; + if (dimension == 3) { + if (dim == 0 && hi[2]-lo[2] > hi[0]-lo[0]) dim = 2; + if (dim == 1 && hi[2]-lo[2] > hi[1]-lo[1]) dim = 2; + } + + // create active list and mark array for dots + // initialize active list to all dots + + if (ndot > maxlist) { + memory->destroy(dotlist); + memory->destroy(dotmark); + maxlist = maxdot; + memory->create(dotlist,maxlist,"RCB:dotlist"); + memory->create(dotmark,maxlist,"RCB:dotmark"); + } + + nlist = ndot; + for (i = 0; i < nlist; i++) dotlist[i] = i; + + // median iteration + // zoom in on bisector until correct # of dots in each half of partition + // as each iteration of median-loop begins, require: + // all non-active dots are marked with 0/1 in dotmark + // valuemin <= every active dot <= valuemax + // wtlo, wthi = total wt of non-active dots + // when leave median-loop, require only: + // valuehalf = correct cut position + // all dots <= valuehalf are marked with 0 in dotmark + // all dots >= valuehalf are marked with 1 in dotmark + // markactive = which side of cut is active = 0/1 + // indexlo,indexhi = indices of dot closest to median + + wtlo = wthi = 0.0; + valuemin = lo[dim]; + valuemax = hi[dim]; + first_iteration = 1; + + while (1) { + + // choose bisector value + // use old value on 1st iteration if old cut dimension is the same + // on 2nd option: could push valuehalf towards geometric center + // with "1.0-factor" to force overshoot + + if (first_iteration && reuse && dim == tree[procmid].dim) { + counters[5]++; + valuehalf = tree[procmid].cut; + if (valuehalf < valuemin || valuehalf > valuemax) + valuehalf = 0.5 * (valuemin + valuemax); + } else if (wt) + valuehalf = valuemin + (targetlo - wtlo) / + (wttot - wtlo - wthi) * (valuemax - valuemin); + else + valuehalf = 0.5 * (valuemin + valuemax); + + first_iteration = 0; + + // initialize local median data structure + + medme.totallo = medme.totalhi = 0.0; + medme.valuelo = -MYHUGE; + medme.valuehi = MYHUGE; + medme.wtlo = medme.wthi = 0.0; + medme.countlo = medme.counthi = 0; + medme.proclo = medme.prochi = me; + + // mark all active dots on one side or other of bisector + // also set all fields in median data struct + // save indices of closest dots on either side + + for (j = 0; j < nlist; j++) { + i = dotlist[j]; + if (dots[i].x[dim] <= valuehalf) { // in lower part + medme.totallo += dots[i].wt; + dotmark[i] = 0; + if (dots[i].x[dim] > medme.valuelo) { // my closest dot + medme.valuelo = dots[i].x[dim]; + medme.wtlo = dots[i].wt; + medme.countlo = 1; + indexlo = i; + } else if (dots[i].x[dim] == medme.valuelo) { // tied for closest + medme.wtlo += dots[i].wt; + medme.countlo++; + } + } + else { // in upper part + medme.totalhi += dots[i].wt; + dotmark[i] = 1; + if (dots[i].x[dim] < medme.valuehi) { // my closest dot + medme.valuehi = dots[i].x[dim]; + medme.wthi = dots[i].wt; + medme.counthi = 1; + indexhi = i; + } else if (dots[i].x[dim] == medme.valuehi) { // tied for closest + medme.wthi += dots[i].wt; + medme.counthi++; + } + } + } + + // combine median data struct across current subset of procs + + counters[0]++; + MPI_Allreduce(&medme,&med,1,med_type,med_op,comm); + + // test median guess for convergence + // move additional dots that are next to cut across it + + if (wtlo + med.totallo < targetlo) { // lower half TOO SMALL + + wtlo += med.totallo; + valuehalf = med.valuehi; + + if (med.counthi == 1) { // only one dot to move + if (wtlo + med.wthi < targetlo) { // move it, keep iterating + if (me == med.prochi) dotmark[indexhi] = 0; + } + else { // only move if beneficial + if (wtlo + med.wthi - targetlo < targetlo - wtlo) + if (me == med.prochi) dotmark[indexhi] = 0; + break; // all done + } + } + else { // multiple dots to move + breakflag = 0; + wtok = 0.0; + if (medme.valuehi == med.valuehi) wtok = medme.wthi; + if (wtlo + med.wthi >= targetlo) { // all done + MPI_Scan(&wtok,&wtupto,1,MPI_DOUBLE,MPI_SUM,comm); + wtmax = targetlo - wtlo; + if (wtupto > wtmax) wtok = wtok - (wtupto - wtmax); + breakflag = 1; + } // wtok = most I can move + for (j = 0, wtsum = 0.0; j < nlist && wtsum < wtok; j++) { + i = dotlist[j]; + if (dots[i].x[dim] == med.valuehi) { // only move if better + if (wtsum + dots[i].wt - wtok < wtok - wtsum) + dotmark[i] = 0; + wtsum += dots[i].wt; + } + } + if (breakflag) break; // done if moved enough + } + + wtlo += med.wthi; + if (targetlo-wtlo <= tolerance) break; // close enough + + valuemin = med.valuehi; // iterate again + markactive = 1; + } + + else if (wthi + med.totalhi < targethi) { // upper half TOO SMALL + + wthi += med.totalhi; + valuehalf = med.valuelo; + + if (med.countlo == 1) { // only one dot to move + if (wthi + med.wtlo < targethi) { // move it, keep iterating + if (me == med.proclo) dotmark[indexlo] = 1; + } + else { // only move if beneficial + if (wthi + med.wtlo - targethi < targethi - wthi) + if (me == med.proclo) dotmark[indexlo] = 1; + break; // all done + } + } + else { // multiple dots to move + breakflag = 0; + wtok = 0.0; + if (medme.valuelo == med.valuelo) wtok = medme.wtlo; + if (wthi + med.wtlo >= targethi) { // all done + MPI_Scan(&wtok,&wtupto,1,MPI_DOUBLE,MPI_SUM,comm); + wtmax = targethi - wthi; + if (wtupto > wtmax) wtok = wtok - (wtupto - wtmax); + breakflag = 1; + } // wtok = most I can move + for (j = 0, wtsum = 0.0; j < nlist && wtsum < wtok; j++) { + i = dotlist[j]; + if (dots[i].x[dim] == med.valuelo) { // only move if better + if (wtsum + dots[i].wt - wtok < wtok - wtsum) + dotmark[i] = 1; + wtsum += dots[i].wt; + } + } + if (breakflag) break; // done if moved enough + } + + wthi += med.wtlo; + if (targethi-wthi <= tolerance) break; // close enough + + valuemax = med.valuelo; // iterate again + markactive = 0; + } + + else // Goldilocks result: both partitions just right + break; + + // shrink the active list + + k = 0; + for (j = 0; j < nlist; j++) { + i = dotlist[j]; + if (dotmark[i] == markactive) dotlist[k++] = i; + } + nlist = k; + } + + // found median + // store cut info only if I am procmid + + if (me == procmid) { + cut = valuehalf; + cutdim = dim; + } + + // use cut to shrink my RCB bounding box + + if (me < procmid) hi[dim] = valuehalf; + else lo[dim] = valuehalf; + + // outgoing = number of dots to ship to partner + // nkeep = number of dots that have never migrated + + markactive = (me < procpartner); + for (i = 0, keep = 0, outgoing = 0; i < ndot; i++) + if (dotmark[i] == markactive) outgoing++; + else if (i < nkeep) keep++; + nkeep = keep; + + // alert partner how many dots I'll send, read how many I'll recv + + MPI_Send(&outgoing,1,MPI_INT,procpartner,0,world); + incoming = 0; + if (readnumber) { + MPI_Recv(&incoming,1,MPI_INT,procpartner,0,world,&status); + if (readnumber == 2) { + MPI_Recv(&incoming2,1,MPI_INT,procpartner2,0,world,&status); + incoming += incoming2; + } + } + + // check if need to alloc more space + + int ndotnew = ndot - outgoing + incoming; + if (ndotnew > maxdot) { + while (maxdot < ndotnew) maxdot += DELTA; + dots = (Dot *) memory->srealloc(dots,maxdot*sizeof(Dot),"RCB::dots"); + counters[6]++; + } + + counters[1] += outgoing; + counters[2] += incoming; + if (ndotnew > counters[3]) counters[3] = ndotnew; + if (maxdot > counters[4]) counters[4] = maxdot; + + // malloc comm send buffer + + if (outgoing > maxbuf) { + memory->sfree(buf); + maxbuf = outgoing; + buf = (Dot *) memory->smalloc(maxbuf*sizeof(Dot),"RCB:buf"); + } + + // fill buffer with dots that are marked for sending + // pack down the unmarked ones + + keep = outgoing = 0; + for (i = 0; i < ndot; i++) { + if (dotmark[i] == markactive) + memcpy(&buf[outgoing++],&dots[i],sizeof(Dot)); + else + memcpy(&dots[keep++],&dots[i],sizeof(Dot)); + } + + // post receives for dots + + if (readnumber > 0) { + MPI_Irecv(&dots[keep],incoming*sizeof(Dot),MPI_CHAR, + procpartner,1,world,&request); + if (readnumber == 2) { + keep += incoming - incoming2; + MPI_Irecv(&dots[keep],incoming2*sizeof(Dot),MPI_CHAR, + procpartner2,1,world,&request2); + } + } + + // handshake before sending dots to insure recvs have been posted + + if (readnumber > 0) { + MPI_Send(NULL,0,MPI_INT,procpartner,0,world); + if (readnumber == 2) MPI_Send(NULL,0,MPI_INT,procpartner2,0,world); + } + MPI_Recv(NULL,0,MPI_INT,procpartner,0,world,&status); + + // send dots to partner + + MPI_Rsend(buf,outgoing*sizeof(Dot),MPI_CHAR,procpartner,1,world); + + // wait until all dots are received + + if (readnumber > 0) { + MPI_Wait(&request,&status); + if (readnumber == 2) MPI_Wait(&request2,&status); + } + + ndot = ndotnew; + + // cut partition in half, create new communicators of 1/2 size + + int split; + if (me < procmid) { + procupper = procmid - 1; + split = 0; + } else { + proclower = procmid; + split = 1; + } + + MPI_Comm_split(comm,split,me,&comm_half); + MPI_Comm_free(&comm); + comm = comm_half; + } + + // clean up + + MPI_Comm_free(&comm); + + // set public variables with results of rebalance + + nfinal = ndot; + + if (nfinal > maxrecv) { + memory->destroy(recvproc); + memory->destroy(recvindex); + maxrecv = nfinal; + memory->create(recvproc,maxrecv,"RCB:recvproc"); + memory->create(recvindex,maxrecv,"RCB:recvindex"); + } + + for (i = 0; i < nfinal; i++) { + recvproc[i] = dots[i].proc; + recvindex[i] = dots[i].index; + } +} + +/* ---------------------------------------------------------------------- + custom MPI reduce operation + merge of each component of an RCB bounding box +------------------------------------------------------------------------- */ + +void box_merge(void *in, void *inout, int *len, MPI_Datatype *dptr) + +{ + RCB::BBox *box1 = (RCB::BBox *) in; + RCB::BBox *box2 = (RCB::BBox *) inout; + + for (int i = 0; i < 3; i++) { + if (box1->lo[i] < box2->lo[i]) box2->lo[i] = box1->lo[i]; + if (box1->hi[i] > box2->hi[i]) box2->hi[i] = box1->hi[i]; + } +} + +/* ---------------------------------------------------------------------- + custom MPI reduce operation + merge median data structure + on input: + in,inout->totallo, totalhi = weight in both partitions on this proc + valuelo, valuehi = pos of nearest dot(s) to cut on this proc + wtlo, wthi = total wt of dot(s) at that pos on this proc + countlo, counthi = # of dot(s) nearest to cut on this proc + proclo, prochi = not used + on exit: + inout-> totallo, totalhi = total # of active dots in both partitions + valuelo, valuehi = pos of nearest dot(s) to cut + wtlo, wthi = total wt of dot(s) at that position + countlo, counthi = total # of dot(s) nearest to cut + proclo, prochi = one unique proc who owns a nearest dot + all procs must get same proclo,prochi +------------------------------------------------------------------------- */ + +void median_merge(void *in, void *inout, int *len, MPI_Datatype *dptr) + +{ + RCB::Median *med1 = (RCB::Median *) in; + RCB::Median *med2 = (RCB::Median *) inout; + + med2->totallo += med1->totallo; + if (med1->valuelo > med2->valuelo) { + med2->valuelo = med1->valuelo; + med2->wtlo = med1->wtlo; + med2->countlo = med1->countlo; + med2->proclo = med1->proclo; + } + else if (med1->valuelo == med2->valuelo) { + med2->wtlo += med1->wtlo; + med2->countlo += med1->countlo; + if (med1->proclo < med2->proclo) med2->proclo = med1->proclo; + } + + med2->totalhi += med1->totalhi; + if (med1->valuehi < med2->valuehi) { + med2->valuehi = med1->valuehi; + med2->wthi = med1->wthi; + med2->counthi = med1->counthi; + med2->prochi = med1->prochi; + } + else if (med1->valuehi == med2->valuehi) { + med2->wthi += med1->wthi; + med2->counthi += med1->counthi; + if (med1->prochi < med2->prochi) med2->prochi = med1->prochi; + } +} + +/* ---------------------------------------------------------------------- + invert the RCB rebalance result to convert receive info into send info + sortflag = flag for sorting order of received messages by proc ID +------------------------------------------------------------------------- */ + +void RCB::invert(int sortflag) +{ + Invert *sbuf,*rbuf; + + // only create Irregular if not previously created + // allows Irregular to persist for multiple RCB calls by fix balance + + if (!irregular) irregular = new Irregular(lmp); + + // nsend = # of dots to request from other procs + + int nsend = nfinal-nkeep; + + int *proclist; + memory->create(proclist,nsend,"RCB:proclist"); + + Invert *sinvert = + (Invert *) memory->smalloc(nsend*sizeof(Invert),"RCB:sinvert"); + + int m = 0; + for (int i = nkeep; i < nfinal; i++) { + proclist[m] = recvproc[i]; + sinvert[m].rindex = recvindex[i]; + sinvert[m].sproc = me; + sinvert[m].sindex = i; + m++; + } + + // perform inversion via irregular comm + // nrecv = # of my dots to send to other procs + + int nrecv = irregular->create_data(nsend,proclist,sortflag); + Invert *rinvert = + (Invert *) memory->smalloc(nrecv*sizeof(Invert),"RCB:rinvert"); + irregular->exchange_data((char *) sinvert,sizeof(Invert),(char *) rinvert); + irregular->destroy_data(); + + // set public variables from requests to send my dots + + if (noriginal > maxsend) { + memory->destroy(sendproc); + memory->destroy(sendindex); + maxsend = noriginal; + memory->create(sendproc,maxsend,"RCB:sendproc"); + memory->create(sendindex,maxsend,"RCB:sendindex"); + } + + for (int i = 0; i < nkeep; i++) { + sendproc[recvindex[i]] = me; + sendindex[recvindex[i]] = i; + } + + for (int i = 0; i < nrecv; i++) { + m = rinvert[i].rindex; + sendproc[m] = rinvert[i].sproc; + sendindex[m] = rinvert[i].sindex; + } + + // clean-up + + memory->destroy(proclist); + memory->destroy(sinvert); + memory->destroy(rinvert); +} + +/* ---------------------------------------------------------------------- + memory use of Irregular +------------------------------------------------------------------------- */ + +bigint RCB::memory_usage() +{ + bigint bytes = 0; + if (irregular) bytes += irregular->memory_usage(); + return bytes; +} + +// ----------------------------------------------------------------------- +// DEBUG methods +// ----------------------------------------------------------------------- +/* +// consistency checks on RCB results + +void RCB::check() +{ + int i,iflag,total1,total2; + double weight,wtmax,wtmin,wtone,tolerance; + + // check that total # of dots remained the same + + MPI_Allreduce(&ndotorig,&total1,1,MPI_INT,MPI_SUM,world); + MPI_Allreduce(&ndot,&total2,1,MPI_INT,MPI_SUM,world); + if (total1 != total2) { + if (me == 0) + printf("ERROR: Points before RCB = %d, Points after RCB = %d\n", + total1,total2); + } + + // check that result is load-balanced within log2(P)*max-wt + + weight = wtone = 0.0; + for (i = 0; i < ndot; i++) { + weight += dots[i].wt; + if (dots[i].wt > wtone) wtone = dots[i].wt; + } + + MPI_Allreduce(&weight,&wtmin,1,MPI_DOUBLE,MPI_MIN,world); + MPI_Allreduce(&weight,&wtmax,1,MPI_DOUBLE,MPI_MAX,world); + MPI_Allreduce(&wtone,&tolerance,1,MPI_DOUBLE,MPI_MAX,world); + + // i = smallest power-of-2 >= nprocs + // tolerance = largest-single-weight*log2(nprocs) + + for (i = 0; (nprocs >> i) != 0; i++); + tolerance = tolerance * i * (1.0 + TINY); + + if (wtmax - wtmin > tolerance) { + if (me == 0) + printf("ERROR: Load-imbalance > tolerance of %g\n",tolerance); + MPI_Barrier(world); + if (weight == wtmin) printf(" Proc %d has weight = %g\n",me,weight); + if (weight == wtmax) printf(" Proc %d has weight = %g\n",me,weight); + } + + MPI_Barrier(world); + + // check that final set of points is inside RCB box of each proc + + iflag = 0; + for (i = 0; i < ndot; i++) { + if (dots[i].x[0] < lo[0] || dots[i].x[0] > hi[0] || + dots[i].x[1] < lo[1] || dots[i].x[1] > hi[1] || + dots[i].x[2] < lo[2] || dots[i].x[2] > hi[2]) + iflag++; + } + if (iflag > 0) + printf("ERROR: %d points are out-of-box on proc %d\n",iflag,me); +} + +// stats for RCB decomposition + +void RCB::stats(int flag) +{ + int i,iflag,sum,min,max; + double ave,rsum,rmin,rmax; + double weight,wttot,wtmin,wtmax; + + if (me == 0) printf("RCB Statistics:\n"); + + // distribution info + + for (i = 0, weight = 0.0; i < ndot; i++) weight += dots[i].wt; + MPI_Allreduce(&weight,&wttot,1,MPI_DOUBLE,MPI_SUM,world); + MPI_Allreduce(&weight,&wtmin,1,MPI_DOUBLE,MPI_MIN,world); + MPI_Allreduce(&weight,&wtmax,1,MPI_DOUBLE,MPI_MAX,world); + + if (me == 0) { + printf(" Total weight of dots = %g\n",wttot); + printf(" Weight on each proc: ave = %g, max = %g, min = %g\n", + wttot/nprocs,wtmax,wtmin); + } + if (flag) { + MPI_Barrier(world); + printf(" Proc %d has weight = %g\n",me,weight); + } + + for (i = 0, weight = 0.0; i < ndot; i++) + if (dots[i].wt > weight) weight = dots[i].wt; + MPI_Allreduce(&weight,&wtmax,1,MPI_DOUBLE,MPI_MAX,world); + + if (me == 0) printf(" Maximum weight of single dot = %g\n",wtmax); + if (flag) { + MPI_Barrier(world); + printf(" Proc %d max weight = %g\n",me,weight); + } + + // counter info + + MPI_Allreduce(&counters[0],&sum,1,MPI_INT,MPI_SUM,world); + MPI_Allreduce(&counters[0],&min,1,MPI_INT,MPI_MIN,world); + MPI_Allreduce(&counters[0],&max,1,MPI_INT,MPI_MAX,world); + ave = ((double) sum)/nprocs; + if (me == 0) + printf(" Median iter: ave = %g, min = %d, max = %d\n",ave,min,max); + if (flag) { + MPI_Barrier(world); + printf(" Proc %d median count = %d\n",me,counters[0]); + } + + MPI_Allreduce(&counters[1],&sum,1,MPI_INT,MPI_SUM,world); + MPI_Allreduce(&counters[1],&min,1,MPI_INT,MPI_MIN,world); + MPI_Allreduce(&counters[1],&max,1,MPI_INT,MPI_MAX,world); + ave = ((double) sum)/nprocs; + if (me == 0) + printf(" Send count: ave = %g, min = %d, max = %d\n",ave,min,max); + if (flag) { + MPI_Barrier(world); + printf(" Proc %d send count = %d\n",me,counters[1]); + } + + MPI_Allreduce(&counters[2],&sum,1,MPI_INT,MPI_SUM,world); + MPI_Allreduce(&counters[2],&min,1,MPI_INT,MPI_MIN,world); + MPI_Allreduce(&counters[2],&max,1,MPI_INT,MPI_MAX,world); + ave = ((double) sum)/nprocs; + if (me == 0) + printf(" Recv count: ave = %g, min = %d, max = %d\n",ave,min,max); + if (flag) { + MPI_Barrier(world); + printf(" Proc %d recv count = %d\n",me,counters[2]); + } + + MPI_Allreduce(&counters[3],&sum,1,MPI_INT,MPI_SUM,world); + MPI_Allreduce(&counters[3],&min,1,MPI_INT,MPI_MIN,world); + MPI_Allreduce(&counters[3],&max,1,MPI_INT,MPI_MAX,world); + ave = ((double) sum)/nprocs; + if (me == 0) + printf(" Max dots: ave = %g, min = %d, max = %d\n",ave,min,max); + if (flag) { + MPI_Barrier(world); + printf(" Proc %d max dots = %d\n",me,counters[3]); + } + + MPI_Allreduce(&counters[4],&sum,1,MPI_INT,MPI_SUM,world); + MPI_Allreduce(&counters[4],&min,1,MPI_INT,MPI_MIN,world); + MPI_Allreduce(&counters[4],&max,1,MPI_INT,MPI_MAX,world); + ave = ((double) sum)/nprocs; + if (me == 0) + printf(" Max memory: ave = %g, min = %d, max = %d\n",ave,min,max); + if (flag) { + MPI_Barrier(world); + printf(" Proc %d max memory = %d\n",me,counters[4]); + } + + if (reuse) { + MPI_Allreduce(&counters[5],&sum,1,MPI_INT,MPI_SUM,world); + MPI_Allreduce(&counters[5],&min,1,MPI_INT,MPI_MIN,world); + MPI_Allreduce(&counters[5],&max,1,MPI_INT,MPI_MAX,world); + ave = ((double) sum)/nprocs; + if (me == 0) + printf(" # of Reuse: ave = %g, min = %d, max = %d\n",ave,min,max); + if (flag) { + MPI_Barrier(world); + printf(" Proc %d # of Reuse = %d\n",me,counters[5]); + } + } + + MPI_Allreduce(&counters[6],&sum,1,MPI_INT,MPI_SUM,world); + MPI_Allreduce(&counters[6],&min,1,MPI_INT,MPI_MIN,world); + MPI_Allreduce(&counters[6],&max,1,MPI_INT,MPI_MAX,world); + ave = ((double) sum)/nprocs; + if (me == 0) + printf(" # of OverAlloc: ave = %g, min = %d, max = %d\n",ave,min,max); + if (flag) { + MPI_Barrier(world); + printf(" Proc %d # of OverAlloc = %d\n",me,counters[6]); + } + + // RCB boxes for each proc + + if (flag) { + if (me == 0) printf(" RCB sub-domain boxes:\n"); + for (i = 0; i < 3; i++) { + MPI_Barrier(world); + if (me == 0) printf(" Dimension %d\n",i+1); + MPI_Barrier(world); + printf(" Proc = %d: Box = %g %g\n",me,lo[i],hi[i]); + } + } +} +*/ diff --git a/src/rcb.h b/src/rcb.h new file mode 100644 index 0000000000..4c2e3809e9 --- /dev/null +++ b/src/rcb.h @@ -0,0 +1,131 @@ +/* ---------------------------------------------------------------------- + LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator + http://lammps.sandia.gov, Sandia National Laboratories + Steve Plimpton, sjplimp@sandia.gov + + Copyright (2003) Sandia Corporation. Under the terms of Contract + DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + certain rights in this software. This software is distributed under + the GNU General Public License. + + See the README file in the top-level LAMMPS directory. +------------------------------------------------------------------------- */ + +#ifndef LAMMPS_RCB_H +#define LAMMPS_RCB_H + +#include "mpi.h" +#include "pointers.h" + +namespace LAMMPS_NS { + +class RCB : protected Pointers { + public: + // set by compute() + + int noriginal; // # of dots I own before balancing + int nfinal; // # of dots I own after balancing + int nkeep; // how many dots of noriginal I still own + // will be first nkept of nfinal list + int *recvproc; // proc IDs of nfinal dots + int *recvindex; // index of nfinal dots on owning procs + // based on input list for compute() + double *lo,*hi; // final bounding box of my RCB sub-domain + double cut; // single cut (in Tree) owned by this proc + int cutdim; // dimension (0,1,2) of the cut + + // set by invert() + + int *sendproc; // proc to send each of my noriginal dots to + int *sendindex; // index of dot in receiver's nfinal list + + RCB(class LAMMPS *); + ~RCB(); + void compute(int, int, double **, double *, double *, double *); + void invert(int sortflag = 0); + bigint memory_usage(); + + // DEBUG methods + //void check(); + //void stats(int); + + // RCB cut info + + struct Median { + double totallo,totalhi; // weight in each half of active partition + double valuelo,valuehi; // position of dot(s) nearest to cut + double wtlo,wthi; // total weight of dot(s) at that position + int countlo,counthi; // # of dots at that position + int proclo,prochi; // unique proc who owns a nearest dot + }; + + struct BBox { + double lo[3],hi[3]; // corner points of a bounding box + }; + + private: + int me,nprocs; + + // point to balance on + + struct Dot { + double x[3]; // coord of point + double wt; // weight of point + int proc; // owning proc + int index; // index on owning proc + }; + + // tree of RCB cuts + + struct Tree { + double cut; // position of cut + int dim; // dimension = 0/1/2 of cut + }; + + // inversion message + + struct Invert { + int rindex; // index on receiving proc + int sproc; // sending proc + int sindex; // index on sending proc + }; + + Dot *dots; // dots on this proc + int ndot; // # of dots on this proc + int maxdot; // allocated size of dots + int ndotorig; + + int nlist; + int maxlist; + int *dotlist; + int *dotmark; + + int maxbuf; + Dot *buf; + + int maxrecv,maxsend; + + BBox bbox; + class Irregular *irregular; + + MPI_Op box_op,med_op; + MPI_Datatype box_type,med_type; + + int reuse; // 1/0 to use/not use previous cuts + int dottop; // dots >= this index are new + double bboxlo[3]; // bounding box of final RCB sub-domain + double bboxhi[3]; + Tree *tree; // tree of RCB cuts, used by reuse() + int counters[7]; // diagnostic counts + // 0 = # of median iterations + // 1 = # of points sent + // 2 = # of points received + // 3 = most points this proc ever owns + // 4 = most point memory this proc ever allocs + // 5 = # of times a previous cut is re-used + // 6 = # of reallocs of point vector +}; + +} + +#endif diff --git a/src/replicate.cpp b/src/replicate.cpp index b759c47fa3..26f3fca7ed 100644 --- a/src/replicate.cpp +++ b/src/replicate.cpp @@ -29,6 +29,8 @@ using namespace LAMMPS_NS; #define LB_FACTOR 1.1 #define EPSILON 1.0e-6 +enum{LAYOUT_UNIFORM,LAYOUT_NONUNIFORM,LAYOUT_TILED}; // several files + /* ---------------------------------------------------------------------- */ Replicate::Replicate(LAMMPS *lmp) : Pointers(lmp) {} @@ -220,17 +222,33 @@ void Replicate::command(int narg, char **arg) sublo[2] = domain->sublo_lamda[2]; subhi[2] = domain->subhi_lamda[2]; } - if (domain->xperiodic) { - if (comm->myloc[0] == 0) sublo[0] -= epsilon[0]; - if (comm->myloc[0] == comm->procgrid[0]-1) subhi[0] += epsilon[0]; - } - if (domain->yperiodic) { - if (comm->myloc[1] == 0) sublo[1] -= epsilon[1]; - if (comm->myloc[1] == comm->procgrid[1]-1) subhi[1] += epsilon[1]; - } - if (domain->zperiodic) { - if (comm->myloc[2] == 0) sublo[2] -= epsilon[2]; - if (comm->myloc[2] == comm->procgrid[2]-1) subhi[2] += epsilon[2]; + if (comm->layout != LAYOUT_TILED) { + if (domain->xperiodic) { + if (comm->myloc[0] == 0) sublo[0] -= epsilon[0]; + if (comm->myloc[0] == comm->procgrid[0]-1) subhi[0] += epsilon[0]; + } + if (domain->yperiodic) { + if (comm->myloc[1] == 0) sublo[1] -= epsilon[1]; + if (comm->myloc[1] == comm->procgrid[1]-1) subhi[1] += epsilon[1]; + } + if (domain->zperiodic) { + if (comm->myloc[2] == 0) sublo[2] -= epsilon[2]; + if (comm->myloc[2] == comm->procgrid[2]-1) subhi[2] += epsilon[2]; + } + + } else { + if (domain->xperiodic) { + if (comm->mysplit[0][0] == 0.0) sublo[0] -= epsilon[0]; + if (comm->mysplit[0][1] == 1.0) subhi[0] += epsilon[0]; + } + if (domain->yperiodic) { + if (comm->mysplit[1][0] == 0.0) sublo[1] -= epsilon[1]; + if (comm->mysplit[1][1] == 1.0) subhi[1] += epsilon[1]; + } + if (domain->zperiodic) { + if (comm->mysplit[2][0] == 0.0) sublo[2] -= epsilon[2]; + if (comm->mysplit[2][1] == 1.0) subhi[2] += epsilon[2]; + } } // loop over all procs From 65a76deb45b15714c70103682cfa8cedab372844 Mon Sep 17 00:00:00 2001 From: sjplimp Date: Mon, 28 Jul 2014 20:13:44 +0000 Subject: [PATCH 10/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12208 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/GRANULAR/fix_pour.cpp | 31 ++++++++++++++++++++++--------- src/KSPACE/msm.cpp | 3 +++ src/KSPACE/pppm.cpp | 3 +++ src/KSPACE/pppm_disp.cpp | 3 +++ src/MISC/fix_deposit.cpp | 27 ++++++++++++++++++++------- src/REPLICA/verlet_split.cpp | 6 ++++++ src/SHOCK/fix_append_atoms.cpp | 12 +++++++++++- src/SRD/fix_srd.cpp | 5 ++++- 8 files changed, 72 insertions(+), 18 deletions(-) diff --git a/src/GRANULAR/fix_pour.cpp b/src/GRANULAR/fix_pour.cpp index a6284d1d5e..27bf4fc707 100644 --- a/src/GRANULAR/fix_pour.cpp +++ b/src/GRANULAR/fix_pour.cpp @@ -37,10 +37,11 @@ using namespace LAMMPS_NS; using namespace FixConst; using namespace MathConst; -#define EPSILON 0.001 - enum{ATOM,MOLECULE}; enum{ONE,RANGE,POLY}; +enum{LAYOUT_UNIFORM,LAYOUT_NONUNIFORM,LAYOUT_TILED}; // several files + +#define EPSILON 0.001 /* ---------------------------------------------------------------------- */ @@ -598,13 +599,25 @@ void FixPour::pre_exchange() if (newcoord[0] >= sublo[0] && newcoord[0] < subhi[0] && newcoord[1] >= sublo[1] && newcoord[1] < subhi[1] && newcoord[2] >= sublo[2] && newcoord[2] < subhi[2]) flag = 1; - else if (dimension == 3 && newcoord[2] >= domain->boxhi[2] && - comm->myloc[2] == comm->procgrid[2]-1 && - newcoord[0] >= sublo[0] && newcoord[0] < subhi[0] && - newcoord[1] >= sublo[1] && newcoord[1] < subhi[1]) flag = 1; - else if (dimension == 2 && newcoord[1] >= domain->boxhi[1] && - comm->myloc[1] == comm->procgrid[1]-1 && - newcoord[0] >= sublo[0] && newcoord[0] < subhi[0]) flag = 1; + else if (dimension == 3 && newcoord[2] >= domain->boxhi[2]) { + if (comm->layout != LAYOUT_TILED) { + if (comm->myloc[2] == comm->procgrid[2]-1 && + newcoord[0] >= sublo[0] && newcoord[0] < subhi[0] && + newcoord[1] >= sublo[1] && newcoord[1] < subhi[1]) flag = 1; + } else { + if (comm->mysplit[2][1] == 1.0 && + newcoord[0] >= sublo[0] && newcoord[0] < subhi[0] && + newcoord[1] >= sublo[1] && newcoord[1] < subhi[1]) flag = 1; + } + } else if (dimension == 2 && newcoord[1] >= domain->boxhi[1]) { + if (comm->layout != LAYOUT_TILED) { + if (comm->myloc[1] == comm->procgrid[1]-1 && + newcoord[0] >= sublo[0] && newcoord[0] < subhi[0]) flag = 1; + } else { + if (comm->mysplit[1][1] == 1.0 && + newcoord[0] >= sublo[0] && newcoord[0] < subhi[0]) flag = 1; + } + } if (flag) { if (mode == ATOM) atom->avec->create_atom(ntype,coords[m]); diff --git a/src/KSPACE/msm.cpp b/src/KSPACE/msm.cpp index 5ce73ee41f..81dbc98b47 100644 --- a/src/KSPACE/msm.cpp +++ b/src/KSPACE/msm.cpp @@ -148,6 +148,9 @@ void MSM::init() triclinic_check(); if (domain->dimension == 2) error->all(FLERR,"Cannot (yet) use MSM with 2d simulation"); + if (comm->style != 0) + error->universe_all(FLERR,"MSM can only currently be used with " + "comm_style brick"); if (!atom->q_flag) error->all(FLERR,"Kspace style requires atom attribute q"); diff --git a/src/KSPACE/pppm.cpp b/src/KSPACE/pppm.cpp index 5f3f8ce2db..095d70b7a2 100644 --- a/src/KSPACE/pppm.cpp +++ b/src/KSPACE/pppm.cpp @@ -183,6 +183,9 @@ void PPPM::init() "slab correction"); if (domain->dimension == 2) error->all(FLERR, "Cannot use PPPM with 2d simulation"); + if (comm->style != 0) + error->universe_all(FLERR,"PPPM can only currently be used with " + "comm_style brick"); if (!atom->q_flag) error->all(FLERR,"Kspace style requires atom attribute q"); diff --git a/src/KSPACE/pppm_disp.cpp b/src/KSPACE/pppm_disp.cpp index 195aa47af3..1dfea3bf77 100755 --- a/src/KSPACE/pppm_disp.cpp +++ b/src/KSPACE/pppm_disp.cpp @@ -213,6 +213,9 @@ void PPPMDisp::init() triclinic_check(); if (domain->dimension == 2) error->all(FLERR,"Cannot use PPPMDisp with 2d simulation"); + if (comm->style != 0) + error->universe_all(FLERR,"PPPMDisp can only currently be used with " + "comm_style brick"); if (slabflag == 0 && domain->nonperiodic > 0) error->all(FLERR,"Cannot use nonperiodic boundaries with PPPMDisp"); diff --git a/src/MISC/fix_deposit.cpp b/src/MISC/fix_deposit.cpp index a804028397..213d39329b 100644 --- a/src/MISC/fix_deposit.cpp +++ b/src/MISC/fix_deposit.cpp @@ -37,6 +37,7 @@ using namespace FixConst; using namespace MathConst; enum{ATOM,MOLECULE}; +enum{LAYOUT_UNIFORM,LAYOUT_NONUNIFORM,LAYOUT_TILED}; // several files #define EPSILON 1.0e6 @@ -452,13 +453,25 @@ void FixDeposit::pre_exchange() if (newcoord[0] >= sublo[0] && newcoord[0] < subhi[0] && newcoord[1] >= sublo[1] && newcoord[1] < subhi[1] && newcoord[2] >= sublo[2] && newcoord[2] < subhi[2]) flag = 1; - else if (dimension == 3 && newcoord[2] >= domain->boxhi[2] && - comm->myloc[2] == comm->procgrid[2]-1 && - newcoord[0] >= sublo[0] && newcoord[0] < subhi[0] && - newcoord[1] >= sublo[1] && newcoord[1] < subhi[1]) flag = 1; - else if (dimension == 2 && newcoord[1] >= domain->boxhi[1] && - comm->myloc[1] == comm->procgrid[1]-1 && - newcoord[0] >= sublo[0] && newcoord[0] < subhi[0]) flag = 1; + else if (dimension == 3 && newcoord[2] >= domain->boxhi[2]) { + if (comm->layout != LAYOUT_TILED) { + if (comm->myloc[2] == comm->procgrid[2]-1 && + newcoord[0] >= sublo[0] && newcoord[0] < subhi[0] && + newcoord[1] >= sublo[1] && newcoord[1] < subhi[1]) flag = 1; + } else { + if (comm->mysplit[2][1] == 1.0 && + newcoord[0] >= sublo[0] && newcoord[0] < subhi[0] && + newcoord[1] >= sublo[1] && newcoord[1] < subhi[1]) flag = 1; + } + } else if (dimension == 2 && newcoord[1] >= domain->boxhi[1]) { + if (comm->layout != LAYOUT_TILED) { + if (comm->myloc[1] == comm->procgrid[1]-1 && + newcoord[0] >= sublo[0] && newcoord[0] < subhi[0]) flag = 1; + } else { + if (comm->mysplit[1][1] == 1.0 && + newcoord[0] >= sublo[0] && newcoord[0] < subhi[0]) flag = 1; + } + } if (flag) { if (mode == ATOM) atom->avec->create_atom(ntype,coords[m]); diff --git a/src/REPLICA/verlet_split.cpp b/src/REPLICA/verlet_split.cpp index 1ede274a76..a2808251db 100644 --- a/src/REPLICA/verlet_split.cpp +++ b/src/REPLICA/verlet_split.cpp @@ -52,6 +52,9 @@ VerletSplit::VerletSplit(LAMMPS *lmp, int narg, char **arg) : if (universe->procs_per_world[0] % universe->procs_per_world[1]) error->universe_all(FLERR,"Verlet/split requires Rspace partition " "size be multiple of Kspace partition size"); + if (comm->style != 0) + error->universe_all(FLERR,"Verlet/split can only currently be used with " + "comm_style brick"); // master = 1 for Rspace procs, 0 for Kspace procs @@ -214,6 +217,9 @@ VerletSplit::~VerletSplit() void VerletSplit::init() { + if (comm->style != 0) + error->universe_all(FLERR,"Verlet/split can only currently be used with " + "comm_style brick"); if (!force->kspace && comm->me == 0) error->warning(FLERR,"No Kspace calculation with verlet/split"); diff --git a/src/SHOCK/fix_append_atoms.cpp b/src/SHOCK/fix_append_atoms.cpp index 5e7af01f6e..571e77b129 100644 --- a/src/SHOCK/fix_append_atoms.cpp +++ b/src/SHOCK/fix_append_atoms.cpp @@ -29,6 +29,8 @@ using namespace LAMMPS_NS; using namespace FixConst; +enum{LAYOUT_UNIFORM,LAYOUT_NONUNIFORM,LAYOUT_TILED}; // several files + #define BIG 1.0e30 #define EPSILON 1.0e-6 @@ -410,7 +412,15 @@ void FixAppendAtoms::pre_exchange() if (ntimestep % freq == 0) { if (spatflag==1) if (get_spatial()==0) return; - if (comm->myloc[2] == comm->procgrid[2]-1) { + + int addflag = 0; + if (comm->layout != LAYOUT_TILED) { + if (comm->myloc[2] == comm->procgrid[2]-1) addflag = 1; + } else { + if (comm->mysplit[2][1] == 1.0) addflag = 1; + } + + if (addflag) { double bboxlo[3],bboxhi[3]; bboxlo[0] = domain->sublo[0]; bboxhi[0] = domain->subhi[0]; diff --git a/src/SRD/fix_srd.cpp b/src/SRD/fix_srd.cpp index 3db73bab51..37dc068797 100644 --- a/src/SRD/fix_srd.cpp +++ b/src/SRD/fix_srd.cpp @@ -334,9 +334,12 @@ void FixSRD::init() if (bigexist && comm->ghost_velocity == 0) error->all(FLERR,"Fix srd requires ghost atoms store velocity"); if (bigexist && collidestyle == NOSLIP && !atom->torque_flag) - error->all(FLERR,"Fix SRD no-slip requires atom attribute torque"); + error->all(FLERR,"Fix srd no-slip requires atom attribute torque"); if (initflag && update->dt != dt_big) error->all(FLERR,"Cannot change timestep once fix srd is setup"); + if (comm->style != 0) + error->universe_all(FLERR,"Fix srd can only currently be used with " + "comm_style brick"); // orthogonal vs triclinic simulation box // could be static or shearing box From bab8ac74a00195f5a8a8d7ccf0070482850d7c3e Mon Sep 17 00:00:00 2001 From: sjplimp Date: Mon, 28 Jul 2014 20:23:48 +0000 Subject: [PATCH 11/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12209 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/comm.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/comm.cpp b/src/comm.cpp index 2d0758fc8e..f4a20c785d 100644 --- a/src/comm.cpp +++ b/src/comm.cpp @@ -20,6 +20,7 @@ #include "domain.h" #include "group.h" #include "procmap.h" +#include "accelerator_kokkos.h" #include "memory.h" #include "error.h" From f1052a189a2301054fa00b4957462d2fe64e8163 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 28 Jul 2014 16:30:02 -0400 Subject: [PATCH 12/31] small documentation updates for stable release --- doc/Section_errors.txt | 8 -------- doc/dump.txt | 21 +++++++++++---------- doc/fix_store_state.txt | 4 ++-- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/doc/Section_errors.txt b/doc/Section_errors.txt index 39c73b8831..91878bc777 100644 --- a/doc/Section_errors.txt +++ b/doc/Section_errors.txt @@ -988,14 +988,6 @@ will assign the restart file info to actual atoms. :dd This is a restriction due to the way atoms are organized in a list to enable the atom_modify first command. :dd -{Support for writing images in JPEG format not included} :dt - -LAMMPS was not built with the -DLAMMPS_JPEG switch in the Makefile. :dd - -{Support for writing images in PNG format not included} :dt - -LAMMPS was not built with the -DLAMMPS_PNG switch in the Makefile. :dd - {Cannot dump sort on atom IDs with no atom IDs defined} :dt Self-explanatory. :dd diff --git a/doc/dump.txt b/doc/dump.txt index 3f83f217ca..84d7010b04 100644 --- a/doc/dump.txt +++ b/doc/dump.txt @@ -78,8 +78,8 @@ args = list of arguments for a particular style :l f_ID = per-atom vector calculated by a fix with ID f_ID\[N\] = Nth column of per-atom array calculated by a fix with ID v_name = per-atom vector calculated by an atom-style variable with name - d_name = per-atom floating point vector with name managed by fix property/atom - i_name = per-atom integer vector with name managed by fix property/atom :pre + d_name = per-atom floating point vector with name, managed by fix property/atom + i_name = per-atom integer vector with name, managed by fix property/atom :pre :ule [Examples:] @@ -112,7 +112,7 @@ options; see details below. As described below, the filename determines the kind of output (text or binary or gzipped, one big file or one per timestep, one big file -or one per processor). +or multiple smaller files). IMPORTANT NOTE: Because periodic boundary conditions are enforced only on timesteps when neighbor lists are rebuilt, the coordinates of an @@ -127,7 +127,7 @@ if the "atom_modify sort"_atom_modify.html option is on, which it is by default. In this case atoms are re-ordered periodically during a simulation, due to spatial sorting. It is also true when running in parallel, because data for a single snapshot is collected from -multiple processors. +multiple processors, each of which owns a subset of the atoms. For the {atom}, {custom}, {cfg}, and {local} styles, sorting is off by default. For the {dcd}, {xtc}, {xyz}, and {molfile} styles, sorting by @@ -295,11 +295,12 @@ from using the (numerical) atom type to an element name (or some other label). This will help many visualization programs to guess bonds and colors. -Note that {atom}, {custom}, {dcd}, {xtc}, and {xyz} style dump files can -be read directly by "VMD"_http://www.ks.uiuc.edu/Research/vmd (a popular -molecular viewing program). See "Section tools"_Section_tools.html#vmd -of the manual and the tools/lmp2vmd/README.txt file for more information -about support in VMD for reading and visualizing LAMMPS dump files. +Note that {atom}, {custom}, {dcd}, {xtc}, and {xyz} style dump files +can be read directly by "VMD"_http://www.ks.uiuc.edu/Research/vmd, a +popular molecular viewing program. See "Section +tools"_Section_tools.html#vmd of the manual and the +tools/lmp2vmd/README.txt file for more information about support in +VMD for reading and visualizing LAMMPS dump files. :line @@ -332,7 +333,7 @@ tmp.dump.20000, etc. This option is not available for the {dcd} and {xtc} styles. Note that the "dump_modify pad"_dump_modify.html command can be used to insure all timestep numbers are the same length (e.g. 00010), which can make it easier to read a series of dump files -in order by some post-processing tools. +in order with some post-processing tools. If a "%" character appears in the filename, then each of P processors writes a portion of the dump file, and the "%" character is replaced diff --git a/doc/fix_store_state.txt b/doc/fix_store_state.txt index e40a0f49cc..01f73a3bbe 100644 --- a/doc/fix_store_state.txt +++ b/doc/fix_store_state.txt @@ -46,8 +46,8 @@ input = one or more atom attributes :l f_ID = per-atom vector calculated by a fix with ID f_ID\[I\] = Ith column of per-atom array calculated by a fix with ID v_name = per-atom vector calculated by an atom-style variable with name - d_name = per-atom floating point vector managed by fix property/atom - i_name = per-atom integer vector managed by fix property/atom :pre + d_name = per-atom floating point vector name, managed by fix property/atom + i_name = per-atom integer vector name, managed by fix property/atom :pre zero or more keyword/value pairs may be appended :l keyword = {com} :l From 47de24ac4c5d9c263da24be7a19482646ca72234 Mon Sep 17 00:00:00 2001 From: sjplimp Date: Mon, 28 Jul 2014 22:48:38 +0000 Subject: [PATCH 13/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12210 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/comm.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/comm.cpp b/src/comm.cpp index f4a20c785d..dbc5114151 100644 --- a/src/comm.cpp +++ b/src/comm.cpp @@ -24,6 +24,10 @@ #include "memory.h" #include "error.h" +#ifdef _OPENMP +#include "omp.h" +#endif + using namespace LAMMPS_NS; enum{SINGLE,MULTI}; // same as in Comm sub-styles From 0198cc71a958a5f69ead68b2a700051ad16a1ef7 Mon Sep 17 00:00:00 2001 From: sjplimp Date: Mon, 28 Jul 2014 23:00:22 +0000 Subject: [PATCH 14/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12211 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/variable.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variable.cpp b/src/variable.cpp index b468ca6a74..f018243d15 100644 --- a/src/variable.cpp +++ b/src/variable.cpp @@ -375,7 +375,6 @@ void Variable::set(int narg, char **arg) if (style[ivar] != EQUAL) error->all(FLERR,"Cannot redefine variable as a different style"); delete [] data[ivar][0]; - if (data[ivar][1]) delete [] data[ivar][1]; copy(1,&arg[2],data[ivar]); replaceflag = 1; } else { @@ -387,6 +386,7 @@ void Variable::set(int narg, char **arg) data[nvar] = new char*[num[nvar]]; copy(1,&arg[2],data[nvar]); data[nvar][1] = new char[VALUELENGTH]; + printf("AAA %d %s %p\n",nvar,data[nvar][0],data[nvar][1]); } // ATOM From 7bce80b07e2aab7ee0ec0ae89239a70241f9c570 Mon Sep 17 00:00:00 2001 From: sjplimp Date: Mon, 28 Jul 2014 23:01:00 +0000 Subject: [PATCH 15/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12212 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/comm.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/comm.cpp b/src/comm.cpp index dbc5114151..522fef6f1c 100644 --- a/src/comm.cpp +++ b/src/comm.cpp @@ -12,6 +12,7 @@ ------------------------------------------------------------------------- */ #include "mpi.h" +#include "stdlib.h" #include "string.h" #include "comm.h" #include "universe.h" From 33fe209a823b0e7a6f09c64323bba81a26d0449a Mon Sep 17 00:00:00 2001 From: sjplimp Date: Mon, 28 Jul 2014 23:02:13 +0000 Subject: [PATCH 16/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12213 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/comm_tiled.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/comm_tiled.cpp b/src/comm_tiled.cpp index 3160a5d60a..37a7052edb 100644 --- a/src/comm_tiled.cpp +++ b/src/comm_tiled.cpp @@ -54,6 +54,8 @@ CommTiled::CommTiled(LAMMPS *lmp) : Comm(lmp) CommTiled::CommTiled(LAMMPS *lmp, Comm *oldcomm) : Comm(*oldcomm) { + error->all(FLERR,"Comm_style tiled is not yet supported"); + style = 1; layout = oldcomm->layout; copy_arrays(oldcomm); From d44b041d625fb0b0a0c0e9f8e3f719f4fff94e2d Mon Sep 17 00:00:00 2001 From: sjplimp Date: Mon, 28 Jul 2014 23:07:27 +0000 Subject: [PATCH 17/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12214 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/comm_brick.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/comm_brick.cpp b/src/comm_brick.cpp index 03ff8effd0..70997725b0 100644 --- a/src/comm_brick.cpp +++ b/src/comm_brick.cpp @@ -36,15 +36,10 @@ #include "compute.h" #include "output.h" #include "dump.h" -#include "accelerator_kokkos.h" #include "math_extra.h" #include "error.h" #include "memory.h" -#ifdef _OPENMP -#include "omp.h" -#endif - using namespace LAMMPS_NS; #define BUFFACTOR 1.5 From 2b9a3e843784b87cb37e17b91b50efcf36e8e68a Mon Sep 17 00:00:00 2001 From: sjplimp Date: Mon, 28 Jul 2014 23:10:39 +0000 Subject: [PATCH 18/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12215 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/variable.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/variable.cpp b/src/variable.cpp index f018243d15..9c8c04d0dc 100644 --- a/src/variable.cpp +++ b/src/variable.cpp @@ -386,7 +386,6 @@ void Variable::set(int narg, char **arg) data[nvar] = new char*[num[nvar]]; copy(1,&arg[2],data[nvar]); data[nvar][1] = new char[VALUELENGTH]; - printf("AAA %d %s %p\n",nvar,data[nvar][0],data[nvar][1]); } // ATOM From 3a8d10eb2f55bed47b12ae522fb7b84ac49cd4b5 Mon Sep 17 00:00:00 2001 From: sjplimp Date: Tue, 29 Jul 2014 00:03:35 +0000 Subject: [PATCH 19/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12217 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h b/src/version.h index 9abc4b939e..b4d684523a 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define LAMMPS_VERSION "28 Jul 2014" +#define LAMMPS_VERSION "29 Jul 2014" From 24f79216b1d2668fd08ce1e2f5720d65fc8722a3 Mon Sep 17 00:00:00 2001 From: sjplimp Date: Tue, 29 Jul 2014 00:03:36 +0000 Subject: [PATCH 20/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12218 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- doc/Manual.html | 4 ++-- doc/Manual.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/Manual.html b/doc/Manual.html index 193368826d..afc0051e88 100644 --- a/doc/Manual.html +++ b/doc/Manual.html @@ -1,7 +1,7 @@ LAMMPS Users Manual - + @@ -22,7 +22,7 @@

    LAMMPS Documentation

    -

    28 Jul 2014 version +

    29 Jul 2014 version

    Version info:

    diff --git a/doc/Manual.txt b/doc/Manual.txt index 2c01ce13c3..0e7c9df027 100644 --- a/doc/Manual.txt +++ b/doc/Manual.txt @@ -1,6 +1,6 @@ LAMMPS Users Manual - + @@ -18,7 +18,7 @@

    LAMMPS Documentation :c,h3 -28 Jul 2014 version :c,h4 +29 Jul 2014 version :c,h4 Version info: :h4 From e4915201dfcc9e5ccd588b52e1ac86ae0cc4cf0a Mon Sep 17 00:00:00 2001 From: sjplimp Date: Tue, 29 Jul 2014 00:05:17 +0000 Subject: [PATCH 21/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12219 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/comm_tiled.cpp | 277 +++++++++++++++++++++++++-------------------- 1 file changed, 153 insertions(+), 124 deletions(-) diff --git a/src/comm_tiled.cpp b/src/comm_tiled.cpp index 37a7052edb..039adfc200 100644 --- a/src/comm_tiled.cpp +++ b/src/comm_tiled.cpp @@ -176,6 +176,8 @@ void CommTiled::init() /* ---------------------------------------------------------------------- setup spatial-decomposition communication patterns function of neighbor cutoff(s) & cutghostuser & current box size and tiling + sets nsendproc, nrecvproc, sendproc, recvproc + sets sendother, sendself, pbc_flag, pbc, sendbox ------------------------------------------------------------------------- */ void CommTiled::setup() @@ -203,7 +205,12 @@ void CommTiled::setup() error->all(FLERR,"Communication cutoff for comm_style tiled " "cannot exceed periodic box length"); - // allocate overlap + // NOTE: allocate overlap (to Nprocs?) + // NOTE: allocate 2nd dim of sendproc, recvproc, sendbox + // NOTE: func pointers for box_drop and box_other + // NOTE: write box_drop and box_other methods + // NOTE: for tiled, must do one-time gather of RCB cuts and proc boxes + int *overlap; int noverlap,noverlap1,indexme; double lo1[3],hi1[3],lo2[3],hi2[3]; @@ -211,134 +218,155 @@ void CommTiled::setup() nswap = 0; for (int idim = 0; idim < dimension; idim++) { + for (int iswap = 0; iswap < 2; iswap++) { - // ghost box in lower direction + // ghost box in lower direction - one = 1; - lo1[0] = sublo[0]; lo1[1] = sublo[1]; lo1[2] = sublo[2]; - hi1[0] = subhi[0]; hi1[1] = subhi[1]; hi1[2] = subhi[2]; - lo1[idim] = sublo[idim] - cut; - hi1[idim] = sublo[idim]; - - two = 0; - if (periodicity[idim] && lo1[idim] < boxlo[idim]) { - two = 1; - lo2[0] = sublo[0]; lo2[1] = sublo[1]; lo2[2] = sublo[2]; - hi2[0] = subhi[0]; hi2[1] = subhi[1]; hi2[2] = subhi[2]; - lo2[idim] = lo1[idim] + prd[idim]; - hi2[idim] = hi1[idim] + prd[idim]; - if (sublo[idim] == boxlo[idim]) { - one = 0; - hi2[idim] = boxhi[idim]; - } - } - - indexme = -1; - noverlap = 0; - if (one) { - if (layout == LAYOUT_UNIFORM) - box_drop_uniform(idim,lo1,hi1,noverlap,overlap,indexme); - else if (layout == LAYOUT_NONUNIFORM) - box_drop_nonuniform(idim,lo1,hi1,noverlap,overlap,indexme); - else - box_drop_tiled(lo1,hi1,0,nprocs-1,noverlap,overlap,indexme); - } - - noverlap1 = noverlap; - if (two) { - if (layout == LAYOUT_UNIFORM) - box_drop_uniform(idim,lo2,hi2,noverlap,overlap,indexme); - else if (layout == LAYOUT_NONUNIFORM) - box_drop_nonuniform(idim,lo2,hi2,noverlap,overlap,indexme); - else - box_drop_tiled(lo2,hi2,0,nprocs-1,noverlap,overlap,indexme); - } - - // if this (self) proc is in overlap list, move it to end of list - - if (indexme >= 0) { - int tmp = overlap[noverlap-1]; - overlap[noverlap-1] = overlap[indexme]; - overlap[indexme] = tmp; - } - - // overlap how has list of noverlap procs - // includes PBC effects - - if (overlap[noverlap-1] == me) sendself[nswap] = 1; - else sendself[nswap] = 0; - if (noverlap-sendself[nswap]) sendother[nswap] = 1; - else sendother[nswap] = 0; - - nsendproc[nswap] = noverlap; - for (i = 0; i < noverlap; i++) sendproc[nswap][i] = overlap[i]; - nrecvproc[nswap+1] = noverlap; - for (i = 0; i < noverlap; i++) recvproc[nswap+1][i] = overlap[i]; - - // compute sendbox for each of my sends - // ibox = intersection of ghostbox with other proc's sub-domain - // sendbox = ibox displaced by cutoff in dim - - // NOTE: need to extend send box in lower dims by cutoff - // NOTE: this logic for overlapping boxes is not correct for sending - - double oboxlo[3],oboxhi[3],sbox[6]; - - for (i = 0; i < noverlap; i++) { - pbc_flag[nswap][i] = 0; - pbc[nswap][i][0] = pbc[nswap][i][1] = pbc[nswap][i][2] = - pbc[nswap][i][3] = pbc[nswap][i][4] = pbc[nswap][i][5] = 0; - - if (layout == LAYOUT_UNIFORM) - box_other_uniform(overlap[i],oboxlo,oboxhi); - else if (layout == LAYOUT_NONUNIFORM) - box_other_nonuniform(overlap[i],oboxlo,oboxhi); - else - box_other_tiled(overlap[i],oboxlo,oboxhi); - - if (i < noverlap1) { - sbox[0] = MAX(oboxlo[0],lo1[0]); - sbox[1] = MAX(oboxlo[1],lo1[1]); - sbox[2] = MAX(oboxlo[2],lo1[2]); - sbox[3] = MIN(oboxhi[0],hi1[0]); - sbox[4] = MIN(oboxhi[1],hi1[1]); - sbox[5] = MIN(oboxhi[2],hi1[2]); - sbox[idim] += cut; - sbox[3+idim] += cut; - if (sbox[idim] == lo1[idim]) sbox[idim] = sublo[idim]; + one = 1; + lo1[0] = sublo[0]; lo1[1] = sublo[1]; lo1[2] = sublo[2]; + hi1[0] = subhi[0]; hi1[1] = subhi[1]; hi1[2] = subhi[2]; + if (iswap == 0) { + lo1[idim] = sublo[idim] - cut; + hi1[idim] = sublo[idim]; } else { - pbc_flag[nswap][i] = 1; - pbc[nswap][i][idim] = 1; - sbox[0] = MAX(oboxlo[0],lo2[0]); - sbox[1] = MAX(oboxlo[1],lo2[1]); - sbox[2] = MAX(oboxlo[2],lo2[2]); - sbox[3] = MIN(oboxhi[0],hi2[0]); - sbox[4] = MIN(oboxhi[1],hi2[1]); - sbox[5] = MIN(oboxhi[2],hi2[2]); - sbox[idim] -= prd[idim] - cut; - sbox[3+idim] -= prd[idim] + cut; - if (sbox[idim] == lo1[idim]) sbox[idim] = sublo[idim]; + lo1[idim] = subhi[idim]; + hi1[idim] = subhi[idim] + cut; + } + + two = 0; + if (iswap == 0 && periodicity[idim] && lo1[idim] < boxlo[idim]) two = 1; + if (iswap == 1 && periodicity[idim] && hi1[idim] > boxhi[idim]) two = 1; + + if (two) { + lo2[0] = sublo[0]; lo2[1] = sublo[1]; lo2[2] = sublo[2]; + hi2[0] = subhi[0]; hi2[1] = subhi[1]; hi2[2] = subhi[2]; + if (iswap == 0) { + lo2[idim] = lo1[idim] + prd[idim]; + hi2[idim] = hi1[idim] + prd[idim]; + if (sublo[idim] == boxlo[idim]) { + one = 0; + hi2[idim] = boxhi[idim]; + } + } else { + lo2[idim] = lo1[idim] - prd[idim]; + hi2[idim] = hi1[idim] - prd[idim]; + if (subhi[idim] == boxhi[idim]) { + one = 0; + lo2[idim] = boxlo[idim]; + } + } } - if (idim >= 1) { - if (sbox[0] == sublo[0]) sbox[0] -= cut; - if (sbox[4] == subhi[0]) sbox[4] += cut; - } - if (idim == 2) { - if (sbox[1] == sublo[1]) sbox[1] -= cut; - if (sbox[5] == subhi[1]) sbox[5] += cut; + indexme = -1; + noverlap = 0; + if (one) { + if (layout == LAYOUT_UNIFORM) + box_drop_uniform(idim,lo1,hi1,noverlap,overlap,indexme); + else if (layout == LAYOUT_NONUNIFORM) + box_drop_nonuniform(idim,lo1,hi1,noverlap,overlap,indexme); + else + box_drop_tiled(lo1,hi1,0,nprocs-1,noverlap,overlap,indexme); } - memcpy(sendbox[nswap][i],sbox,6*sizeof(double)); + noverlap1 = noverlap; + if (two) { + if (layout == LAYOUT_UNIFORM) + box_drop_uniform(idim,lo2,hi2,noverlap,overlap,indexme); + else if (layout == LAYOUT_NONUNIFORM) + box_drop_nonuniform(idim,lo2,hi2,noverlap,overlap,indexme); + else + box_drop_tiled(lo2,hi2,0,nprocs-1,noverlap,overlap,indexme); + } + + // if this (self) proc is in overlap list, move it to end of list + + if (indexme >= 0) { + int tmp = overlap[noverlap-1]; + overlap[noverlap-1] = overlap[indexme]; + overlap[indexme] = tmp; + } + + // overlap how has list of noverlap procs + // includes PBC effects + + if (overlap[noverlap-1] == me) sendself[nswap] = 1; + else sendself[nswap] = 0; + if (noverlap-sendself[nswap]) sendother[nswap] = 1; + else sendother[nswap] = 0; + + nsendproc[nswap] = noverlap; + for (i = 0; i < noverlap; i++) sendproc[nswap][i] = overlap[i]; + if (iswap == 0) { + nrecvproc[nswap+1] = noverlap; + for (i = 0; i < noverlap; i++) recvproc[nswap+1][i] = overlap[i]; + } else { + nrecvproc[nswap-1] = noverlap; + for (i = 0; i < noverlap; i++) recvproc[nswap-1][i] = overlap[i]; + } + + // compute sendbox for each of my sends + // obox = intersection of ghostbox with other proc's sub-domain + // sbox = what I need to send to other proc + // = sublo to MIN(sublo+cut,subhi) in idim, for iswap = 0 + // = MIN(subhi-cut,sublo) to subhi in idim, for iswap = 1 + // = obox in other 2 dims + // if sbox touches sub-box boundaries in lower dims, + // extend sbox in those lower dims to include ghost atoms + + double oboxlo[3],oboxhi[3],sbox[6]; + + for (i = 0; i < noverlap; i++) { + pbc_flag[nswap][i] = 0; + pbc[nswap][i][0] = pbc[nswap][i][1] = pbc[nswap][i][2] = + pbc[nswap][i][3] = pbc[nswap][i][4] = pbc[nswap][i][5] = 0; + + if (layout == LAYOUT_UNIFORM) + box_other_uniform(overlap[i],oboxlo,oboxhi); + else if (layout == LAYOUT_NONUNIFORM) + box_other_nonuniform(overlap[i],oboxlo,oboxhi); + else + box_other_tiled(overlap[i],oboxlo,oboxhi); + + if (i < noverlap1) { + sbox[0] = MAX(oboxlo[0],lo1[0]); + sbox[1] = MAX(oboxlo[1],lo1[1]); + sbox[2] = MAX(oboxlo[2],lo1[2]); + sbox[3] = MIN(oboxhi[0],hi1[0]); + sbox[4] = MIN(oboxhi[1],hi1[1]); + sbox[5] = MIN(oboxhi[2],hi1[2]); + } else { + pbc_flag[nswap][i] = 1; + pbc[nswap][i][idim] = 1; + sbox[0] = MAX(oboxlo[0],lo2[0]); + sbox[1] = MAX(oboxlo[1],lo2[1]); + sbox[2] = MAX(oboxlo[2],lo2[2]); + sbox[3] = MIN(oboxhi[0],hi2[0]); + sbox[4] = MIN(oboxhi[1],hi2[1]); + sbox[5] = MIN(oboxhi[2],hi2[2]); + } + + if (iswap == 0) { + sbox[idim] = sublo[idim]; + sbox[3+idim] = MIN(sublo[idim]+cut,subhi[idim]); + } else { + sbox[idim] = MAX(subhi[idim]-cut,sublo[idim]); + sbox[3+idim] = subhi[idim]; + } + + if (idim >= 1) { + if (sbox[0] == sublo[0]) sbox[0] -= cut; + if (sbox[4] == subhi[0]) sbox[4] += cut; + } + if (idim == 2) { + if (sbox[1] == sublo[1]) sbox[1] -= cut; + if (sbox[5] == subhi[1]) sbox[5] += cut; + } + + memcpy(sendbox[nswap][i],sbox,6*sizeof(double)); + } + + nswap++; } - - // ghost box in upper direction - - - - - - nswap += 2; } @@ -1064,7 +1092,7 @@ int CommTiled::exchange_variable(int n, double *inbuf, double *&outbuf) /* ---------------------------------------------------------------------- determine overlap list of Noverlap procs the lo/hi box overlaps overlap = non-zero area in common between box and proc sub-domain - box is onwed by me and extends in dim + box is owned by me and extends in dim ------------------------------------------------------------------------- */ void CommTiled::box_drop_uniform(int dim, double *lo, double *hi, @@ -1086,7 +1114,8 @@ void CommTiled::box_drop_nonuniform(int dim, double *lo, double *hi, /* ---------------------------------------------------------------------- determine overlap list of Noverlap procs the lo/hi box overlaps overlap = non-zero area in common between box and proc sub-domain - recursive routine for traversing an RCB tree of cuts + recursive method for traversing an RCB tree of cuts + no need to split lo/hi box as recurse b/c OK if box extends outside RCB box ------------------------------------------------------------------------- */ void CommTiled::box_drop_tiled(double *lo, double *hi, @@ -1103,7 +1132,7 @@ void CommTiled::box_drop_tiled(double *lo, double *hi, } // drop box on each side of cut it extends beyond - // use > and < criteria to not include procs it only touches + // use > and < criteria so does not include a box it only touches // procmid = 1st processor in upper half of partition // = location in tree that stores this cut // dim = 0,1,2 dimension of cut From 7cd0d58e85616e9bfb4d0db6d644dc15d174fe9e Mon Sep 17 00:00:00 2001 From: sjplimp Date: Tue, 29 Jul 2014 15:29:09 +0000 Subject: [PATCH 22/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12221 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/STUBS/mpi.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++- src/STUBS/mpi.h | 10 +++++++ src/rcb.cpp | 4 +-- 3 files changed, 90 insertions(+), 3 deletions(-) diff --git a/src/STUBS/mpi.c b/src/STUBS/mpi.c index 5f865c4031..891fd80ce7 100644 --- a/src/STUBS/mpi.c +++ b/src/STUBS/mpi.c @@ -20,7 +20,7 @@ #include #include "mpi.h" -/* lo-level data structure */ +/* data structure for double/int */ struct _mpi_double_int { double value; @@ -28,6 +28,15 @@ struct _mpi_double_int { }; typedef struct _mpi_double_int double_int; +/* extra MPI_Datatypes registered by MPI_Type_contiguous */ + +#define MAXEXTRA_DATATYPE 16 + +int nextra_datatype; +MPI_Datatype *ptr_datatype[MAXEXTRA_DATATYPE]; +int index_datatype[MAXEXTRA_DATATYPE]; +int size_datatype[MAXEXTRA_DATATYPE]; + /* ---------------------------------------------------------------------- */ /* MPI Functions */ /* ---------------------------------------------------------------------- */ @@ -101,6 +110,8 @@ double MPI_Wtime() /* ---------------------------------------------------------------------- */ +/* include sizes of user defined datatypes, stored in extra lists */ + static int stubtypesize(MPI_Datatype datatype) { if (datatype == MPI_INT) return sizeof(int); @@ -111,6 +122,12 @@ static int stubtypesize(MPI_Datatype datatype) else if (datatype == MPI_LONG) return sizeof(long); else if (datatype == MPI_LONG_LONG) return sizeof(uint64_t); else if (datatype == MPI_DOUBLE_INT) return sizeof(double_int); + else { + int i; + for (i = 0; i < nextra_datatype; i++) + if (datatype == index_datatype[i]) return size_datatype[i]; + } + return 0; } /* ---------------------------------------------------------------------- */ @@ -305,6 +322,66 @@ int MPI_Cart_rank(MPI_Comm comm, int *coords, int *rank) /* ---------------------------------------------------------------------- */ +/* store size of user datatype in extra lists */ + +int MPI_Type_contiguous(int count, MPI_Datatype oldtype, + MPI_Datatype *newtype) +{ + if (nextra_datatype = MAXEXTRA_DATATYPE) return -1; + ptr_datatype[nextra_datatype] = newtype; + index_datatype[nextra_datatype] = -(nextra_datatype + 1); + size_datatype[nextra_datatype] = count * stubtypesize(oldtype); + nextra_datatype++; + return 0; +} + +/* ---------------------------------------------------------------------- */ + +/* set value of user datatype to internal negative index, + based on match of ptr */ + +int MPI_Type_commit(MPI_Datatype *datatype) +{ + int i; + for (i = 0; i < nextra_datatype; i++) + if (datatype == ptr_datatype[i]) *datatype = index_datatype[i]; + return 0; +} + +/* ---------------------------------------------------------------------- */ + +/* remove user datatype from extra lists */ + +int MPI_Type_free(MPI_Datatype *datatype) +{ + int i; + for (i = 0; i < nextra_datatype; i++) + if (datatype == ptr_datatype[i]) { + ptr_datatype[i] = ptr_datatype[nextra_datatype-1]; + index_datatype[i] = index_datatype[nextra_datatype-1]; + size_datatype[i] = size_datatype[nextra_datatype-1]; + nextra_datatype--; + break; + } + return 0; +} + +/* ---------------------------------------------------------------------- */ + +int MPI_Op_create(MPI_User_function *function, int commute, MPI_Op *op) +{ + return 0; +} + +/* ---------------------------------------------------------------------- */ + +int MPI_Op_free(MPI_Op *op) +{ + return 0; +} + +/* ---------------------------------------------------------------------- */ + int MPI_Barrier(MPI_Comm comm) {return 0;} /* ---------------------------------------------------------------------- */ diff --git a/src/STUBS/mpi.h b/src/STUBS/mpi.h index bbd1186ea8..86bd6db341 100644 --- a/src/STUBS/mpi.h +++ b/src/STUBS/mpi.h @@ -64,6 +64,9 @@ extern "C" { #define MPI_MAX_PROCESSOR_NAME 128 +typedef void MPI_User_function(void *invec, void *inoutvec, + int *len, MPI_Datatype *datatype); + /* MPI data structs */ struct _MPI_Status { @@ -122,6 +125,13 @@ int MPI_Cart_shift(MPI_Comm comm, int direction, int displ, int *source, int *dest); int MPI_Cart_rank(MPI_Comm comm, int *coords, int *rank); +int MPI_Type_contiguous(int count, MPI_Datatype oldtype, + MPI_Datatype *newtype); +int MPI_Type_commit(MPI_Datatype *datatype); +int MPI_Type_free(MPI_Datatype *datatype); + +int MPI_Op_create(MPI_User_function *function, int commute, MPI_Op *op); +int MPI_Op_free(MPI_Op *op); int MPI_Barrier(MPI_Comm comm); int MPI_Bcast(void *buf, int count, MPI_Datatype datatype, diff --git a/src/rcb.cpp b/src/rcb.cpp index cf4e12d213..de3f9f3ba6 100644 --- a/src/rcb.cpp +++ b/src/rcb.cpp @@ -101,8 +101,8 @@ RCB::~RCB() sets noriginal,nfinal,nkeep,recvproc,recvindex,lo,hi all proc particles will be inside or on surface of 3-d box defined by final lo/hi - // NOTE: worry about re-use of data structs for fix balance - // NOTE: should I get rid of wt all together, will it be used? + // NOTE: worry about re-use of data structs for fix balance? + // NOTE: could get rid of wt all together, will it be used? ------------------------------------------------------------------------- */ void RCB::compute(int dimension, int n, double **x, double *wt, From 526895f7efecb127b4bc388bf61c0b50d7b021da Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Tue, 29 Jul 2014 16:33:58 -0400 Subject: [PATCH 23/31] fix issues flagged by compiler warnings. --- src/balance.cpp | 8 ++++---- src/fix_adapt.cpp | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/balance.cpp b/src/balance.cpp index 89f0ccb5cc..71b8eeabc5 100644 --- a/src/balance.cpp +++ b/src/balance.cpp @@ -279,14 +279,14 @@ void Balance::command(int narg, char **arg) if (style == XYZ) { if (comm->layout == LAYOUT_UNIFORM) { if (xflag == USER || yflag == USER || zflag == USER) - comm->layout == LAYOUT_NONUNIFORM; + comm->layout = LAYOUT_NONUNIFORM; } else if (comm->style == LAYOUT_NONUNIFORM) { if (xflag == UNIFORM && yflag == UNIFORM && zflag == UNIFORM) - comm->layout == LAYOUT_UNIFORM; + comm->layout = LAYOUT_UNIFORM; } else if (comm->style == LAYOUT_TILED) { if (xflag == UNIFORM && yflag == UNIFORM && zflag == UNIFORM) - comm->layout == LAYOUT_UNIFORM; - else comm->layout == LAYOUT_NONUNIFORM; + comm->layout = LAYOUT_UNIFORM; + else comm->layout = LAYOUT_NONUNIFORM; } if (xflag == UNIFORM) { diff --git a/src/fix_adapt.cpp b/src/fix_adapt.cpp index 22e718dd76..246d828e7d 100644 --- a/src/fix_adapt.cpp +++ b/src/fix_adapt.cpp @@ -498,8 +498,6 @@ void FixAdapt::restore_settings() } else if (ad->which == ATOM) { if (diamflag) { - int mflag = 0; - if (atom->rmass_flag) mflag = 1; double density; double *vec = fix_diam->vstore; From e4abe8ee03ae5cc53b663afe9cf7420165ced62d Mon Sep 17 00:00:00 2001 From: sjplimp Date: Tue, 29 Jul 2014 20:51:46 +0000 Subject: [PATCH 24/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12222 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/fix_adapt.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/fix_adapt.cpp b/src/fix_adapt.cpp index 22e718dd76..246d828e7d 100644 --- a/src/fix_adapt.cpp +++ b/src/fix_adapt.cpp @@ -498,8 +498,6 @@ void FixAdapt::restore_settings() } else if (ad->which == ATOM) { if (diamflag) { - int mflag = 0; - if (atom->rmass_flag) mflag = 1; double density; double *vec = fix_diam->vstore; From 4666da3f481baf87df3bac993938a628efdd0b1f Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Tue, 29 Jul 2014 17:28:21 -0400 Subject: [PATCH 25/31] anticipate changes from upstream to fix compilation error for MPI STUBS --- src/STUBS/mpi.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++++- src/STUBS/mpi.h | 10 +++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/STUBS/mpi.c b/src/STUBS/mpi.c index 718e5fb475..987479bb7b 100644 --- a/src/STUBS/mpi.c +++ b/src/STUBS/mpi.c @@ -20,7 +20,7 @@ #include #include "mpi.h" -/* lo-level data structure */ +/* data structure for double/int */ struct _mpi_double_int { double value; @@ -28,6 +28,15 @@ struct _mpi_double_int { }; typedef struct _mpi_double_int double_int; +/* extra MPI_Datatypes registered by MPI_Type_contiguous */ + +#define MAXEXTRA_DATATYPE 16 + +int nextra_datatype; +MPI_Datatype *ptr_datatype[MAXEXTRA_DATATYPE]; +int index_datatype[MAXEXTRA_DATATYPE]; +int size_datatype[MAXEXTRA_DATATYPE]; + static int _mpi_is_initialized=0; /* ---------------------------------------------------------------------- */ @@ -135,6 +144,8 @@ double MPI_Wtime() /* ---------------------------------------------------------------------- */ +/* include sizes of user defined datatypes, stored in extra lists */ + static int stubtypesize(MPI_Datatype datatype) { if (datatype == MPI_INT) return sizeof(int); @@ -145,6 +156,12 @@ static int stubtypesize(MPI_Datatype datatype) else if (datatype == MPI_LONG) return sizeof(long); else if (datatype == MPI_LONG_LONG) return sizeof(uint64_t); else if (datatype == MPI_DOUBLE_INT) return sizeof(double_int); + else { + int i; + for (i = 0; i < nextra_datatype; i++) + if (datatype == index_datatype[i]) return size_datatype[i]; + } + return 0; } /* ---------------------------------------------------------------------- */ @@ -339,6 +356,66 @@ int MPI_Cart_rank(MPI_Comm comm, int *coords, int *rank) /* ---------------------------------------------------------------------- */ +/* store size of user datatype in extra lists */ + +int MPI_Type_contiguous(int count, MPI_Datatype oldtype, + MPI_Datatype *newtype) +{ + if (nextra_datatype = MAXEXTRA_DATATYPE) return -1; + ptr_datatype[nextra_datatype] = newtype; + index_datatype[nextra_datatype] = -(nextra_datatype + 1); + size_datatype[nextra_datatype] = count * stubtypesize(oldtype); + nextra_datatype++; + return 0; +} + +/* ---------------------------------------------------------------------- */ + +/* set value of user datatype to internal negative index, + based on match of ptr */ + +int MPI_Type_commit(MPI_Datatype *datatype) +{ + int i; + for (i = 0; i < nextra_datatype; i++) + if (datatype == ptr_datatype[i]) *datatype = index_datatype[i]; + return 0; +} + +/* ---------------------------------------------------------------------- */ + +/* remove user datatype from extra lists */ + +int MPI_Type_free(MPI_Datatype *datatype) +{ + int i; + for (i = 0; i < nextra_datatype; i++) + if (datatype == ptr_datatype[i]) { + ptr_datatype[i] = ptr_datatype[nextra_datatype-1]; + index_datatype[i] = index_datatype[nextra_datatype-1]; + size_datatype[i] = size_datatype[nextra_datatype-1]; + nextra_datatype--; + break; + } + return 0; +} + +/* ---------------------------------------------------------------------- */ + +int MPI_Op_create(MPI_User_function *function, int commute, MPI_Op *op) +{ + return 0; +} + +/* ---------------------------------------------------------------------- */ + +int MPI_Op_free(MPI_Op *op) +{ + return 0; +} + +/* ---------------------------------------------------------------------- */ + int MPI_Barrier(MPI_Comm comm) {return 0;} /* ---------------------------------------------------------------------- */ diff --git a/src/STUBS/mpi.h b/src/STUBS/mpi.h index 0d93326b45..07ea1027eb 100644 --- a/src/STUBS/mpi.h +++ b/src/STUBS/mpi.h @@ -64,6 +64,9 @@ extern "C" { #define MPI_MAX_PROCESSOR_NAME 128 +typedef void MPI_User_function(void *invec, void *inoutvec, + int *len, MPI_Datatype *datatype); + /* MPI data structs */ struct _MPI_Status { @@ -123,6 +126,13 @@ int MPI_Cart_shift(MPI_Comm comm, int direction, int displ, int *source, int *dest); int MPI_Cart_rank(MPI_Comm comm, int *coords, int *rank); +int MPI_Type_contiguous(int count, MPI_Datatype oldtype, + MPI_Datatype *newtype); +int MPI_Type_commit(MPI_Datatype *datatype); +int MPI_Type_free(MPI_Datatype *datatype); + +int MPI_Op_create(MPI_User_function *function, int commute, MPI_Op *op); +int MPI_Op_free(MPI_Op *op); int MPI_Barrier(MPI_Comm comm); int MPI_Bcast(void *buf, int count, MPI_Datatype datatype, From 8047d6058eb40ad4301c40b6ae77a18ec796985b Mon Sep 17 00:00:00 2001 From: sjplimp Date: Tue, 29 Jul 2014 21:46:50 +0000 Subject: [PATCH 26/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12223 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/balance.cpp | 15 +- src/comm.cpp | 1 + src/comm.h | 3 + src/comm_tiled.cpp | 574 +++++++++++++++++++++++++++++++++------------ src/comm_tiled.h | 51 ++-- src/domain.cpp | 21 +- 6 files changed, 473 insertions(+), 192 deletions(-) diff --git a/src/balance.cpp b/src/balance.cpp index 89f0ccb5cc..8675d8568e 100644 --- a/src/balance.cpp +++ b/src/balance.cpp @@ -279,14 +279,14 @@ void Balance::command(int narg, char **arg) if (style == XYZ) { if (comm->layout == LAYOUT_UNIFORM) { if (xflag == USER || yflag == USER || zflag == USER) - comm->layout == LAYOUT_NONUNIFORM; + comm->layout = LAYOUT_NONUNIFORM; } else if (comm->style == LAYOUT_NONUNIFORM) { if (xflag == UNIFORM && yflag == UNIFORM && zflag == UNIFORM) - comm->layout == LAYOUT_UNIFORM; + comm->layout = LAYOUT_UNIFORM; } else if (comm->style == LAYOUT_TILED) { if (xflag == UNIFORM && yflag == UNIFORM && zflag == UNIFORM) - comm->layout == LAYOUT_UNIFORM; - else comm->layout == LAYOUT_NONUNIFORM; + comm->layout = LAYOUT_UNIFORM; + else comm->layout = LAYOUT_NONUNIFORM; } if (xflag == UNIFORM) { @@ -476,6 +476,7 @@ int *Balance::bisection(int sortflag) if (!rcb) rcb = new RCB(lmp); // NOTE: lo/hi args could be simulation box or particle bounding box + // if particle bbox, then mysplit needs to be reset to sim box // NOTE: triclinic needs to be in lamda coords int dim = domain->dimension; @@ -488,6 +489,10 @@ int *Balance::bisection(int sortflag) // NOTE: this logic is specific to orthogonal boxes, not triclinic + comm->rcbnew = 1; + comm->rcbcut = rcb->cut; + comm->rcbcutdim = rcb->cutdim; + double (*mysplit)[2] = comm->mysplit; mysplit[0][0] = (rcb->lo[0] - boxlo[0]) / prd[0]; @@ -502,6 +507,8 @@ int *Balance::bisection(int sortflag) if (rcb->hi[2] == boxhi[2]) mysplit[2][1] = 1.0; else mysplit[2][1] = (rcb->hi[2] - boxlo[2]) / prd[2]; + // return list of procs to send my atoms to + return rcb->sendproc; } diff --git a/src/comm.cpp b/src/comm.cpp index 522fef6f1c..ad4b18786e 100644 --- a/src/comm.cpp +++ b/src/comm.cpp @@ -60,6 +60,7 @@ Comm::Comm(LAMMPS *lmp) : Pointers(lmp) grid2proc = NULL; xsplit = ysplit = zsplit = NULL; + rcbnew = 0; // use of OpenMP threads // query OpenMP for number of threads/process set by user at run-time diff --git a/src/comm.h b/src/comm.h index 1aceb20c2c..c1376e14b1 100644 --- a/src/comm.h +++ b/src/comm.h @@ -50,7 +50,10 @@ class Comm : protected Pointers { // public settings specific to layout = TILED + int rcbnew; // 1 if just reset by rebalance, else 0 double mysplit[3][2]; // fractional (0-1) bounds of my sub-domain + double rcbcut; // RCB cut by this proc + int rcbcutdim; // dimension of RCB cut // methods diff --git a/src/comm_tiled.cpp b/src/comm_tiled.cpp index 039adfc200..2bd089e35d 100644 --- a/src/comm_tiled.cpp +++ b/src/comm_tiled.cpp @@ -36,6 +36,10 @@ using namespace LAMMPS_NS; #define BUFMIN 1000 #define BUFEXTRA 1000 +// NOTE: change this to 16 after debugged + +#define DELTA_PROCS 1 + enum{SINGLE,MULTI}; // same as in Comm enum{LAYOUT_UNIFORM,LAYOUT_NONUNIFORM,LAYOUT_TILED}; // several files @@ -66,14 +70,10 @@ CommTiled::CommTiled(LAMMPS *lmp, Comm *oldcomm) : Comm(*oldcomm) CommTiled::~CommTiled() { - free_swap(); - - if (sendlist) for (int i = 0; i < nswap; i++) memory->destroy(sendlist[i]); - memory->sfree(sendlist); - memory->destroy(maxsendlist); - + memory->destroy(overlap); memory->destroy(buf_send); memory->destroy(buf_recv); + deallocate_swap(nswap); } /* ---------------------------------------------------------------------- @@ -83,6 +83,8 @@ CommTiled::~CommTiled() void CommTiled::init_buffers() { + memory->create(overlap,nprocs,"comm:overlap"); + maxexchange = maxexchange_atom + maxexchange_fix; bufextra = maxexchange + BUFEXTRA; @@ -93,13 +95,6 @@ void CommTiled::init_buffers() nswap = 2 * domain->dimension; allocate_swap(nswap); - - //sendlist = (int **) memory->smalloc(nswap*sizeof(int *),"comm:sendlist"); - //memory->create(maxsendlist,nswap,"comm:maxsendlist"); - //for (int i = 0; i < nswap; i++) { - // maxsendlist[i] = BUFMIN; - // memory->create(sendlist[i],BUFMIN,"comm:sendlist[i]"); - //} } /* ---------------------------------------------------------------------- @@ -115,9 +110,6 @@ void CommTiled::init() if (triclinic) error->all(FLERR,"Cannot yet use comm_style tiled with triclinic box"); - if (domain->xperiodic || domain->yperiodic || - (domain->dimension == 2 && domain->zperiodic)) - error->all(FLERR,"Cannot yet use comm_style tiled with periodic box"); if (mode == MULTI) error->all(FLERR,"Cannot yet use comm_style tiled with multi-mode comm"); @@ -176,27 +168,44 @@ void CommTiled::init() /* ---------------------------------------------------------------------- setup spatial-decomposition communication patterns function of neighbor cutoff(s) & cutghostuser & current box size and tiling - sets nsendproc, nrecvproc, sendproc, recvproc - sets sendother, sendself, pbc_flag, pbc, sendbox ------------------------------------------------------------------------- */ void CommTiled::setup() { int i; - int dimension; - int *periodicity; - double *prd,*sublo,*subhi,*boxlo,*boxhi; + int dimension = domain->dimension; + int *periodicity = domain->periodicity; + double *prd = domain->prd; + double *boxlo = domain->boxlo; + double *boxhi = domain->boxhi; + double *sublo = domain->sublo; + double *subhi = domain->subhi; + + // set function pointers + + if (layout != LAYOUT_TILED) { + box_drop = &CommTiled::box_drop_brick; + box_other = &CommTiled::box_other_brick; + } else { + box_drop = &CommTiled::box_drop_tiled; + box_other = &CommTiled::box_other_tiled; + } + + // if RCB decomp has just changed, gather needed global RCB info + + if (rcbnew) { + rcbnew = 0; + memcpy(&rcbinfo[me].mysplit[0][0],&mysplit[0][0],6*sizeof(double)); + rcbinfo[me].cut = rcbcut; + rcbinfo[me].dim = rcbcutdim; + MPI_Allgather(&rcbinfo[me],sizeof(RCBinfo),MPI_CHAR, + rcbinfo,sizeof(RCBinfo),MPI_CHAR,world); + } + + // check that cutoff < any periodic box length double cut = MAX(neighbor->cutneighmax,cutghostuser); - - dimension = domain->dimension; - periodicity = domain->periodicity; - prd = domain->prd; - sublo = domain->sublo; - subhi = domain->subhi; - boxlo = domain->boxlo; - boxhi = domain->boxhi; cutghost[0] = cutghost[1] = cutghost[2] = cut; if ((periodicity[0] && cut > prd[0]) || @@ -205,13 +214,12 @@ void CommTiled::setup() error->all(FLERR,"Communication cutoff for comm_style tiled " "cannot exceed periodic box length"); - // NOTE: allocate overlap (to Nprocs?) - // NOTE: allocate 2nd dim of sendproc, recvproc, sendbox - // NOTE: func pointers for box_drop and box_other - // NOTE: write box_drop and box_other methods - // NOTE: for tiled, must do one-time gather of RCB cuts and proc boxes + // loop over 6 swap directions + // determine which procs I will send to and receive from in each swap + // done by intersecting ghost box with all proc sub-boxes it overlaps + // sets nsendproc, nrecvproc, sendproc, recvproc + // sets sendother, sendself, pbc_flag, pbc, sendbox - int *overlap; int noverlap,noverlap1,indexme; double lo1[3],hi1[3],lo2[3],hi2[3]; int one,two; @@ -220,7 +228,9 @@ void CommTiled::setup() for (int idim = 0; idim < dimension; idim++) { for (int iswap = 0; iswap < 2; iswap++) { - // ghost box in lower direction + // one = first ghost box in same periodic image + // two = second ghost box wrapped across periodic boundary + // either may not exist one = 1; lo1[0] = sublo[0]; lo1[1] = sublo[1]; lo1[2] = sublo[2]; @@ -257,35 +267,35 @@ void CommTiled::setup() } } + // noverlap = # of overlaps of box1/2 with procs via box_drop() + // overlap = list of overlapping procs + // if overlap with self, indexme = index of me in list + indexme = -1; noverlap = 0; - if (one) { - if (layout == LAYOUT_UNIFORM) - box_drop_uniform(idim,lo1,hi1,noverlap,overlap,indexme); - else if (layout == LAYOUT_NONUNIFORM) - box_drop_nonuniform(idim,lo1,hi1,noverlap,overlap,indexme); - else - box_drop_tiled(lo1,hi1,0,nprocs-1,noverlap,overlap,indexme); - } - + if (one) (this->*box_drop)(idim,lo1,hi1,noverlap,indexme); noverlap1 = noverlap; - if (two) { - if (layout == LAYOUT_UNIFORM) - box_drop_uniform(idim,lo2,hi2,noverlap,overlap,indexme); - else if (layout == LAYOUT_NONUNIFORM) - box_drop_nonuniform(idim,lo2,hi2,noverlap,overlap,indexme); - else - box_drop_tiled(lo2,hi2,0,nprocs-1,noverlap,overlap,indexme); - } + if (two) (this->*box_drop)(idim,lo2,hi2,noverlap,indexme); + + // if self is in overlap list, move it to end of list - // if this (self) proc is in overlap list, move it to end of list - if (indexme >= 0) { int tmp = overlap[noverlap-1]; overlap[noverlap-1] = overlap[indexme]; overlap[indexme] = tmp; } + // reallocate 2nd dimensions of all send/recv arrays, based on noverlap + // # of sends of this swap = # of recvs of nswap +/- 1 + + if (noverlap > nprocmax[iswap]) { + int oldmax = nprocmax[iswap]; + while (nprocmax[iswap] < noverlap) nprocmax[iswap] += DELTA_PROCS; + grow_swap_send(iswap,nprocmax[iswap],oldmax); + if (iswap == 0) grow_swap_recv(iswap+1,nprocmax[iswap]); + else grow_swap_recv(iswap-1,nprocmax[iswap]); + } + // overlap how has list of noverlap procs // includes PBC effects @@ -320,12 +330,7 @@ void CommTiled::setup() pbc[nswap][i][0] = pbc[nswap][i][1] = pbc[nswap][i][2] = pbc[nswap][i][3] = pbc[nswap][i][4] = pbc[nswap][i][5] = 0; - if (layout == LAYOUT_UNIFORM) - box_other_uniform(overlap[i],oboxlo,oboxhi); - else if (layout == LAYOUT_NONUNIFORM) - box_other_nonuniform(overlap[i],oboxlo,oboxhi); - else - box_other_tiled(overlap[i],oboxlo,oboxhi); + (this->*box_other)(idim,iswap,overlap[i],oboxlo,oboxhi); if (i < noverlap1) { sbox[0] = MAX(oboxlo[0],lo1[0]); @@ -368,10 +373,6 @@ void CommTiled::setup() nswap++; } } - - - // reallocate requests and statuses to max of any swap - } /* ---------------------------------------------------------------------- @@ -497,6 +498,9 @@ void CommTiled::reverse_comm() // if comm_f_only set, exchange or copy directly from f, don't pack for (int iswap = nswap-1; iswap >= 0; iswap--) { + nsend = nsendproc[iswap] - sendself[iswap]; + nrecv = nrecvproc[iswap] - sendself[iswap]; + if (comm_f_only) { if (sendother[iswap]) { for (i = 0; i < nsend; i++) @@ -587,7 +591,7 @@ void CommTiled::exchange() void CommTiled::borders() { - int i,n,irecv,ngroup,nlast,nsend,nrecv,ncount,rmaxswap; + int i,m,n,irecv,nlocal,nlast,nsend,nrecv,ncount,rmaxswap; double xlo,xhi,ylo,yhi,zlo,zhi; double *bbox; double **x; @@ -609,35 +613,35 @@ void CommTiled::borders() // for yz-dim swaps, check owned and ghost atoms // store sent atom indices in list for use in future timesteps // NOTE: assume SINGLE mode, add back in logic for MULTI mode later + // and for ngroup when bordergroup is set x = atom->x; - for (i = 0; i < nsendproc[iswap]; i++) { - bbox = sendbox[iswap][i]; - xlo = bbox[0]; xhi = bbox[1]; - ylo = bbox[2]; yhi = bbox[3]; - zlo = bbox[4]; zhi = bbox[5]; + for (m = 0; m < nsendproc[iswap]; m++) { + bbox = sendbox[iswap][m]; + xlo = bbox[0]; ylo = bbox[1]; zlo = bbox[2]; + xhi = bbox[3]; yhi = bbox[4]; zhi = bbox[5]; - ngroup = atom->nfirst; + nlocal = atom->nlocal; if (iswap < 2) nlast = atom->nlocal; else nlast = atom->nlocal + atom->nghost; ncount = 0; - for (i = 0; i < ngroup; i++) + for (i = 0; i < nlocal; i++) if (x[i][0] >= xlo && x[i][0] <= xhi && x[i][1] >= ylo && x[i][1] <= yhi && x[i][2] >= zlo && x[i][2] <= zhi) { - if (ncount == maxsendlist[iswap][i]) grow_list(iswap,i,ncount); - sendlist[iswap][i][ncount++] = i; + if (ncount == maxsendlist[iswap][m]) grow_list(iswap,i,ncount); + sendlist[iswap][m][ncount++] = i; } for (i = atom->nlocal; i < nlast; i++) if (x[i][0] >= xlo && x[i][0] <= xhi && x[i][1] >= ylo && x[i][1] <= yhi && x[i][2] >= zlo && x[i][2] <= zhi) { - if (ncount == maxsendlist[iswap][i]) grow_list(iswap,i,ncount); - sendlist[iswap][i][ncount++] = i; + if (ncount == maxsendlist[iswap][m]) grow_list(iswap,i,ncount); + sendlist[iswap][m][ncount++] = i; } - sendnum[iswap][i] = ncount; + sendnum[iswap][m] = ncount; smax = MAX(smax,ncount); } @@ -648,11 +652,11 @@ void CommTiled::borders() nrecv = nrecvproc[iswap] - sendself[iswap]; if (sendother[iswap]) { - for (i = 0; i < nrecv; i++) - MPI_Irecv(&recvnum[iswap][i],1,MPI_INT, - recvproc[iswap][i],0,world,&requests[i]); - for (i = 0; i < nsend; i++) - MPI_Send(&sendnum[iswap][i],1,MPI_INT,sendproc[iswap][i],0,world); + for (m = 0; m < nrecv; m++) + MPI_Irecv(&recvnum[iswap][m],1,MPI_INT, + recvproc[iswap][m],0,world,&requests[m]); + for (m = 0; m < nsend; m++) + MPI_Send(&sendnum[iswap][m],1,MPI_INT,sendproc[iswap][m],0,world); } if (sendself[iswap]) recvnum[iswap][nrecv] = sendnum[iswap][nsend]; if (sendother[iswap]) MPI_Waitall(nrecv,requests,statuses); @@ -660,18 +664,18 @@ void CommTiled::borders() // setup other per swap/proc values from sendnum and recvnum rmaxswap = 0; - for (i = 0; i < nrecvproc[iswap]; i++) { - rmaxswap += recvnum[iswap][i]; - size_forward_recv[iswap][i] = recvnum[iswap][i]*size_forward; - size_reverse_send[iswap][i] = recvnum[iswap][i]*size_reverse; - size_reverse_recv[iswap][i] = sendnum[iswap][i]*size_reverse; - if (i == 0) { + for (m = 0; m < nrecvproc[iswap]; m++) { + rmaxswap += recvnum[iswap][m]; + size_forward_recv[iswap][m] = recvnum[iswap][m]*size_forward; + size_reverse_send[iswap][m] = recvnum[iswap][m]*size_reverse; + size_reverse_recv[iswap][m] = sendnum[iswap][m]*size_reverse; + if (m == 0) { firstrecv[iswap][0] = atom->nlocal + atom->nghost; forward_recv_offset[iswap][0] = 0; } else { - firstrecv[iswap][i] = firstrecv[iswap][i-1] + recvnum[iswap][i-1]; - forward_recv_offset[iswap][i] = - forward_recv_offset[iswap][i-1] + recvnum[iswap][i-1]; + firstrecv[iswap][m] = firstrecv[iswap][m-1] + recvnum[iswap][m-1]; + forward_recv_offset[iswap][m] = + forward_recv_offset[iswap][m-1] + recvnum[iswap][m-1]; } } rmax = MAX(rmax,rmaxswap); @@ -685,15 +689,15 @@ void CommTiled::borders() if (ghost_velocity) { if (sendother[iswap]) { - for (i = 0; i < nrecv; i++) - MPI_Irecv(&buf_recv[forward_recv_offset[iswap][i]], - recvnum[iswap][i]*size_border, - MPI_DOUBLE,recvproc[iswap][i],0,world,&requests[i]); - for (i = 0; i < nsend; i++) { - n = avec->pack_border_vel(sendnum[iswap][i],sendlist[iswap][i], - buf_send,pbc_flag[iswap][i],pbc[iswap][i]); + for (m = 0; m < nrecv; m++) + MPI_Irecv(&buf_recv[forward_recv_offset[iswap][m]], + recvnum[iswap][m]*size_border, + MPI_DOUBLE,recvproc[iswap][m],0,world,&requests[m]); + for (m = 0; m < nsend; m++) { + n = avec->pack_border_vel(sendnum[iswap][m],sendlist[iswap][m], + buf_send,pbc_flag[iswap][m],pbc[iswap][m]); MPI_Send(buf_send,n*size_border,MPI_DOUBLE, - sendproc[iswap][i],0,world); + sendproc[iswap][m],0,world); } } @@ -706,7 +710,7 @@ void CommTiled::borders() } if (sendother[iswap]) { - for (i = 0; i < nrecv; i++) { + for (m = 0; m < nrecv; m++) { MPI_Waitany(nrecv,requests,&irecv,&status); avec->unpack_border(recvnum[iswap][irecv],firstrecv[iswap][irecv], &buf_recv[forward_recv_offset[iswap][irecv]]); @@ -715,15 +719,15 @@ void CommTiled::borders() } else { if (sendother[iswap]) { - for (i = 0; i < nrecv; i++) - MPI_Irecv(&buf_recv[forward_recv_offset[iswap][i]], - recvnum[iswap][i]*size_border, - MPI_DOUBLE,recvproc[iswap][i],0,world,&requests[i]); - for (i = 0; i < nsend; i++) { - n = avec->pack_border(sendnum[iswap][i],sendlist[iswap][i], - buf_send,pbc_flag[iswap][i],pbc[iswap][i]); + for (m = 0; m < nrecv; m++) + MPI_Irecv(&buf_recv[forward_recv_offset[iswap][m]], + recvnum[iswap][m]*size_border, + MPI_DOUBLE,recvproc[iswap][m],0,world,&requests[m]); + for (m = 0; m < nsend; m++) { + n = avec->pack_border(sendnum[iswap][m],sendlist[iswap][m], + buf_send,pbc_flag[iswap][m],pbc[iswap][m]); MPI_Send(buf_send,n*size_border,MPI_DOUBLE, - sendproc[iswap][i],0,world); + sendproc[iswap][m],0,world); } } @@ -736,7 +740,7 @@ void CommTiled::borders() } if (sendother[iswap]) { - for (i = 0; i < nrecv; i++) { + for (m = 0; m < nrecv; m++) { MPI_Waitany(nrecv,requests,&irecv,&status); avec->unpack_border(recvnum[iswap][irecv],firstrecv[iswap][irecv], &buf_recv[forward_recv_offset[iswap][irecv]]); @@ -762,6 +766,8 @@ void CommTiled::borders() if (map_style) atom->map_set(); } +// NOTE: remaining forward/reverse methods still need to be updated + /* ---------------------------------------------------------------------- forward communication invoked by a Pair n = constant number of datums per atom @@ -1095,20 +1101,63 @@ int CommTiled::exchange_variable(int n, double *inbuf, double *&outbuf) box is owned by me and extends in dim ------------------------------------------------------------------------- */ -void CommTiled::box_drop_uniform(int dim, double *lo, double *hi, - int &noverlap, int *overlap, int &indexme) +void CommTiled::box_drop_brick(int idim, double *lo, double *hi, + int &noverlap, int &indexme) { + // NOTE: this is not triclinic compatible -} + double *prd = domain->prd; + double *boxlo = domain->boxlo; + double *boxhi = domain->boxhi; + double *sublo = domain->sublo; + double *subhi = domain->subhi; -/* ---------------------------------------------------------------------- - determine overlap list of Noverlap procs the lo/hi box overlaps - overlap = non-zero area in common between box and proc sub-domain -------------------------------------------------------------------------- */ + int index,dir; + if (hi[idim] == sublo[idim]) { + index = myloc[idim] - 1; + dir = -1; + } else if (lo[idim] == subhi[idim]) { + index = myloc[idim] + 1; + dir = 1; + } else if (hi[idim] == boxhi[idim]) { + index = procgrid[idim] - 1; + dir = -1; + } else if (lo[idim] == boxlo[idim]) { + index = 0; + dir = 1; + } -void CommTiled::box_drop_nonuniform(int dim, double *lo, double *hi, - int &noverlap, int *overlap, int &indexme) -{ + int other1,other2,proc; + double lower,upper; + double *split; + + if (idim == 0) { + other1 = myloc[1]; other2 = myloc[2]; + split = xsplit; + } else if (idim == 1) { + other1 = myloc[0]; other2 = myloc[2]; + split = ysplit; + } else { + other1 = myloc[0]; other2 = myloc[1]; + split = zsplit; + } + + while (1) { + lower = boxlo[idim] + prd[idim]*split[index]; + if (index < procgrid[idim]-1) + upper = boxlo[idim] + prd[idim]*split[index+1]; + else upper = boxhi[idim]; + if (lower >= hi[idim] || upper <= lo[idim]) break; + + if (idim == 0) proc = grid2proc[index][other1][other2]; + else if (idim == 1) proc = grid2proc[other1][index][other2]; + else proc = grid2proc[other1][other2][idim]; + + if (proc == me) indexme = noverlap; + overlap[noverlap++] = proc; + index += dir; + if (index < 0 || index >= procgrid[idim]) break; + } } /* ---------------------------------------------------------------------- @@ -1118,9 +1167,15 @@ void CommTiled::box_drop_nonuniform(int dim, double *lo, double *hi, no need to split lo/hi box as recurse b/c OK if box extends outside RCB box ------------------------------------------------------------------------- */ -void CommTiled::box_drop_tiled(double *lo, double *hi, - int proclower, int procupper, - int &noverlap, int *overlap, int &indexme) +void CommTiled::box_drop_tiled(int idim, double *lo, double *hi, + int &noverlap, int &indexme) +{ + box_drop_tiled_recurse(lo,hi,0,nprocs-1,noverlap,indexme); +} + +void CommTiled::box_drop_tiled_recurse(double *lo, double *hi, + int proclower, int procupper, + int &noverlap, int &indexme) { // end recursion when partition is a single proc // add proc to overlap list @@ -1139,13 +1194,93 @@ void CommTiled::box_drop_tiled(double *lo, double *hi, // cut = position of cut int procmid = proclower + (procupper - proclower) / 2 + 1; - double cut = tree[procmid].cut; - int dim = tree[procmid].dim; + double cut = rcbinfo[procmid].cut; + int idim = rcbinfo[procmid].dim; - if (lo[dim] < cut) - box_drop_tiled(lo,hi,proclower,procmid-1,noverlap,overlap,indexme); - if (hi[dim] > cut) - box_drop_tiled(lo,hi,procmid,procupper,noverlap,overlap,indexme); + if (lo[idim] < cut) + box_drop_tiled_recurse(lo,hi,proclower,procmid-1,noverlap,indexme); + if (hi[idim] > cut) + box_drop_tiled_recurse(lo,hi,procmid,procupper,noverlap,indexme); +} + +/* ---------------------------------------------------------------------- + return other box owned by proc as lo/hi corner pts +------------------------------------------------------------------------- */ + +void CommTiled::box_other_brick(int idim, int iswap, + int proc, double *lo, double *hi) +{ + double *prd = domain->prd; + double *boxlo = domain->boxlo; + double *boxhi = domain->boxhi; + double *sublo = domain->sublo; + double *subhi = domain->subhi; + + lo[0] = sublo[0]; lo[1] = sublo[1]; lo[2] = sublo[2]; + hi[0] = subhi[0]; hi[1] = subhi[1]; hi[2] = subhi[2]; + + int other1,other2,oproc; + double *split; + + if (idim == 0) { + other1 = myloc[1]; other2 = myloc[2]; + split = xsplit; + } else if (idim == 1) { + other1 = myloc[0]; other2 = myloc[2]; + split = ysplit; + } else { + other1 = myloc[0]; other2 = myloc[1]; + split = zsplit; + } + + int dir = -1; + if (iswap) dir = 1; + int index = myloc[idim]; + int n = procgrid[idim]; + + for (int i = 0; i < n; i++) { + index += dir; + if (index < 0) index = n-1; + else if (index >= n) index = 0; + + if (idim == 0) oproc = grid2proc[index][other1][other2]; + else if (idim == 1) oproc = grid2proc[other1][index][other2]; + else oproc = grid2proc[other1][other2][idim]; + + if (proc == oproc) { + lo[idim] = boxlo[idim] + prd[idim]*split[index]; + if (split[index+1] < 1.0) + hi[idim] = boxlo[idim] + prd[idim]*split[index+1]; + else hi[idim] = boxhi[idim]; + return; + } + } +} + +/* ---------------------------------------------------------------------- + return other box owned by proc as lo/hi corner pts +------------------------------------------------------------------------- */ + +void CommTiled::box_other_tiled(int idim, int iswap, + int proc, double *lo, double *hi) +{ + double *prd = domain->prd; + double *boxlo = domain->boxlo; + double *boxhi = domain->boxhi; + + double (*split)[2] = rcbinfo[proc].mysplit; + + lo[0] = boxlo[0] + prd[0]*split[0][0]; + if (split[0][1] < 1.0) hi[0] = boxlo[0] + prd[0]*split[0][1]; + else hi[0] = boxhi[0]; + + lo[1] = boxlo[1] + prd[1]*split[1][0]; + if (split[1][1] < 1.0) hi[1] = boxlo[1] + prd[1]*split[1][1]; + else hi[1] = boxhi[1]; + + lo[2] = boxlo[2] + prd[2]*split[2][0]; + if (split[2][1] < 1.0) hi[2] = boxlo[2] + prd[2]*split[2][1]; + else hi[2] = boxhi[2]; } /* ---------------------------------------------------------------------- @@ -1184,7 +1319,7 @@ void CommTiled::grow_list(int iswap, int iwhich, int n) { maxsendlist[iswap][iwhich] = static_cast (BUFFACTOR * n); memory->grow(sendlist[iswap][iwhich],maxsendlist[iswap][iwhich], - "comm:sendlist[iswap]"); + "comm:sendlist[i][j]"); } /* ---------------------------------------------------------------------- @@ -1193,34 +1328,169 @@ void CommTiled::grow_list(int iswap, int iwhich, int n) void CommTiled::allocate_swap(int n) { - memory->create(sendnum,n,"comm:sendnum"); - memory->create(recvnum,n,"comm:recvnum"); - memory->create(sendproc,n,"comm:sendproc"); - memory->create(recvproc,n,"comm:recvproc"); - memory->create(size_forward_recv,n,"comm:size"); - memory->create(size_reverse_send,n,"comm:size"); - memory->create(size_reverse_recv,n,"comm:size"); - memory->create(firstrecv,n,"comm:firstrecv"); - memory->create(pbc_flag,n,"comm:pbc_flag"); - memory->create(pbc,n,6,"comm:pbc"); + nsendproc = new int[n]; + nrecvproc = new int[n]; + sendother = new int[n]; + sendself = new int[n]; + nprocmax = new int[n]; + + sendproc = new int*[n]; + recvproc = new int*[n]; + sendnum = new int*[n]; + recvnum = new int*[n]; + size_forward_recv = new int*[n]; + firstrecv = new int*[n]; + size_reverse_send = new int*[n]; + size_reverse_recv = new int*[n]; + forward_recv_offset = new int*[n]; + reverse_recv_offset = new int*[n]; + + pbc_flag = new int*[n]; + pbc = new int**[n]; + sendbox = new double**[n]; + maxsendlist = new int*[n]; + sendlist = new int**[n]; + + for (int i = 0; i < n; i++) { + sendproc[i] = recvproc[i] = NULL; + sendnum[i] = recvnum[i] = NULL; + size_forward_recv[i] = firstrecv[i] = NULL; + size_reverse_send[i] = size_reverse_recv[i] = NULL; + forward_recv_offset[i] = reverse_recv_offset[i] = NULL; + + pbc_flag[i] = NULL; + pbc[i] = NULL; + sendbox[i] = NULL; + maxsendlist[i] = NULL; + sendlist[i] = NULL; + } + + maxreqstat = 0; + requests = NULL; + statuses = NULL; + + for (int i = 0; i < n; i++) { + nprocmax[i] = DELTA_PROCS; + grow_swap_send(i,DELTA_PROCS,0); + grow_swap_recv(i,DELTA_PROCS); + } } /* ---------------------------------------------------------------------- - free memory for swaps + grow info for swap I, to allow for N procs to communicate with + ditto for complementary recv for swap I+1 or I-1, as invoked by caller ------------------------------------------------------------------------- */ -void CommTiled::free_swap() +void CommTiled::grow_swap_send(int i, int n, int nold) { - memory->destroy(sendnum); - memory->destroy(recvnum); - memory->destroy(sendproc); - memory->destroy(recvproc); - memory->destroy(size_forward_recv); - memory->destroy(size_reverse_send); - memory->destroy(size_reverse_recv); - memory->destroy(firstrecv); - memory->destroy(pbc_flag); - memory->destroy(pbc); + delete [] sendproc[i]; + sendproc[i] = new int[n]; + delete [] sendnum[i]; + sendnum[i] = new int[n]; + + delete [] size_reverse_recv[i]; + size_reverse_recv[i] = new int[n]; + delete [] reverse_recv_offset[i]; + reverse_recv_offset[i] = new int[n]; + + delete [] pbc_flag[i]; + pbc_flag[i] = new int[n]; + memory->destroy(pbc[i]); + memory->create(pbc[i],n,6,"comm:pbc_flag"); + memory->destroy(sendbox[i]); + memory->create(sendbox[i],n,6,"comm:sendbox"); + + delete [] maxsendlist[i]; + maxsendlist[i] = new int[n]; + + for (int j = 0; j < nold; j++) memory->destroy(sendlist[i][j]); + delete [] sendlist[i]; + sendlist[i] = new int*[n]; + for (int j = 0; j < n; j++) { + maxsendlist[i][j] = BUFMIN; + memory->create(sendlist[i][j],BUFMIN,"comm:sendlist[i][j]"); + } +} + +void CommTiled::grow_swap_recv(int i, int n) +{ + delete [] recvproc[i]; + recvproc[i] = new int[n]; + delete [] recvnum[i]; + recvnum[i] = new int[n]; + + delete [] size_forward_recv[i]; + size_forward_recv[i] = new int[n]; + delete [] firstrecv[i]; + firstrecv[i] = new int[n]; + delete [] forward_recv_offset[i]; + forward_recv_offset[i] = new int[n]; + + delete [] size_reverse_send[i]; + size_reverse_send[i] = new int[n]; + + if (n > maxreqstat) { + maxreqstat = n; + delete [] requests; + delete [] statuses; + requests = new MPI_Request[n]; + statuses = new MPI_Status[n]; + } +} + +/* ---------------------------------------------------------------------- + deallocate swap info +------------------------------------------------------------------------- */ + +void CommTiled::deallocate_swap(int n) +{ + delete [] nsendproc; + delete [] nrecvproc; + delete [] sendother; + delete [] sendself; + + for (int i = 0; i < n; i++) { + delete [] sendproc[i]; + delete [] recvproc[i]; + delete [] sendnum[i]; + delete [] recvnum[i]; + delete [] size_forward_recv[i]; + delete [] firstrecv[i]; + delete [] size_reverse_send[i]; + delete [] size_reverse_recv[i]; + delete [] forward_recv_offset[i]; + delete [] reverse_recv_offset[i]; + + delete [] pbc_flag[i]; + memory->destroy(pbc[i]); + memory->destroy(sendbox[i]); + delete [] maxsendlist[i]; + + for (int j = 0; j < nprocmax[i]; j++) memory->destroy(sendlist[i][j]); + delete [] sendlist[i]; + } + + delete [] sendproc; + delete [] recvproc; + delete [] sendnum; + delete [] recvnum; + delete [] size_forward_recv; + delete [] firstrecv; + delete [] size_reverse_send; + delete [] size_reverse_recv; + delete [] forward_recv_offset; + delete [] reverse_recv_offset; + + delete [] pbc_flag; + delete [] pbc; + delete [] sendbox; + delete [] maxsendlist; + delete [] sendlist; + + delete [] requests; + delete [] statuses; + + delete [] nprocmax; } /* ---------------------------------------------------------------------- diff --git a/src/comm_tiled.h b/src/comm_tiled.h index b992242476..f3f8f65e74 100644 --- a/src/comm_tiled.h +++ b/src/comm_tiled.h @@ -57,13 +57,13 @@ class CommTiled : public Comm { int *nsendproc,*nrecvproc; // # of procs to send/recv to/from in each swap int *sendother; // 1 if send to any other proc in each swap int *sendself; // 1 if send to self in each swap - int **sendnum,**recvnum; // # of atoms to send/recv per swap/proc + int *nprocmax; // current max # of send procs for each swap int **sendproc,**recvproc; // proc to send/recv to/from per swap/proc + int **sendnum,**recvnum; // # of atoms to send/recv per swap/proc int **size_forward_recv; // # of values to recv in each forward swap/proc int **firstrecv; // where to put 1st recv atom per swap/proc int **size_reverse_send; // # to send in each reverse comm per swap/proc int **size_reverse_recv; // # to recv in each reverse comm per swap/proc - int **forward_recv_offset; // forward comm offsets in buf_recv per swap/proc int **reverse_recv_offset; // reverse comm offsets in buf_recv per swap/proc @@ -82,40 +82,43 @@ class CommTiled : public Comm { int maxexchange; // max # of datums/atom in exchange comm int bufextra; // extra space beyond maxsend in send buffer + int maxreqstat; // max size of Request and Status vectors MPI_Request *requests; MPI_Status *statuses; int comm_x_only,comm_f_only; // 1 if only exchange x,f in for/rev comm - struct Tree { - double cut; - int dim; + struct RCBinfo { + double mysplit[3][2]; // fractional RCB bounding box for one proc + double cut; // position of cut this proc owns + int dim; // dimension = 0/1/2 of cut }; - Tree *tree; - - // info from RCB decomp - - double rcbcut; - int rcbcutdim; - double rcblo[3]; - double rcbhi[3]; + int *overlap; + RCBinfo *rcbinfo; // list of RCB info for all procs void init_buffers(); - void box_drop_uniform(int, double *, double *, int &, int *, int &); - void box_drop_nonuniform(int, double *, double *, int &, int *, int &); - void box_drop_tiled(double *, double *, int, int, int &, int *, int &); + // box drop and other functions - void box_other_uniform(int, double *, double *) {} - void box_other_nonuniform(int, double *, double *) {} - void box_other_tiled(int, double *, double *) {} + typedef void (CommTiled::*BoxDropPtr)(int, double *, double *, int &, int &); + BoxDropPtr box_drop; + void box_drop_brick(int, double *, double *, int &, int &); + void box_drop_tiled(int, double *, double *, int &, int &); + void box_drop_tiled_recurse(double *, double *, int, int, int &, int &); - void grow_send(int, int); // reallocate send buffer - void grow_recv(int); // free/allocate recv buffer - void grow_list(int, int, int); // reallocate sendlist for one swap/proc - void allocate_swap(int); // allocate swap arrays - void free_swap(); // free swap arrays + typedef void (CommTiled::*BoxOtherPtr)(int, int, int, double *, double *); + BoxOtherPtr box_other; + void box_other_brick(int, int, int, double *, double *); + void box_other_tiled(int, int, int, double *, double *); + + void grow_send(int, int); // reallocate send buffer + void grow_recv(int); // free/allocate recv buffer + void grow_list(int, int, int); // reallocate sendlist for one swap/proc + void allocate_swap(int); // allocate swap arrays + void grow_swap_send(int, int, int); // grow swap arrays for send and recv + void grow_swap_recv(int, int); + void deallocate_swap(int); // deallocate swap arrays }; } diff --git a/src/domain.cpp b/src/domain.cpp index 83fafd7261..13e8a190dc 100644 --- a/src/domain.cpp +++ b/src/domain.cpp @@ -301,34 +301,31 @@ void Domain::set_local_box() double *zsplit = comm->zsplit; sublo[0] = boxlo[0] + xprd*xsplit[myloc[0]]; - if (myloc[0] < procgrid[0]-1) - subhi[0] = boxlo[0] + xprd*xsplit[myloc[0]+1]; + if (myloc[0] < procgrid[0]-1) subhi[0] = boxlo[0] + xprd*xsplit[myloc[0]+1]; else subhi[0] = boxhi[0]; sublo[1] = boxlo[1] + yprd*ysplit[myloc[1]]; - if (myloc[1] < procgrid[1]-1) - subhi[1] = boxlo[1] + yprd*ysplit[myloc[1]+1]; + if (myloc[1] < procgrid[1]-1) subhi[1] = boxlo[1] + yprd*ysplit[myloc[1]+1]; else subhi[1] = boxhi[1]; sublo[2] = boxlo[2] + zprd*zsplit[myloc[2]]; - if (myloc[2] < procgrid[2]-1) - subhi[2] = boxlo[2] + zprd*zsplit[myloc[2]+1]; + if (myloc[2] < procgrid[2]-1) subhi[2] = boxlo[2] + zprd*zsplit[myloc[2]+1]; else subhi[2] = boxhi[2]; } else { double (*mysplit)[2] = comm->mysplit; sublo[0] = boxlo[0] + xprd*mysplit[0][0]; - subhi[0] = boxlo[0] + xprd*mysplit[0][1]; - if (mysplit[0][1] == 1.0) subhi[0] = boxhi[0]; + if (mysplit[0][1] < 1.0) subhi[0] = boxlo[0] + xprd*mysplit[0][1]; + else subhi[0] = boxhi[0]; sublo[1] = boxlo[1] + yprd*mysplit[1][0]; - subhi[1] = boxlo[1] + yprd*mysplit[1][1]; - if (mysplit[1][1] == 1.0) subhi[1] = boxhi[1]; + if (mysplit[1][1] < 1.0) subhi[1] = boxlo[1] + yprd*mysplit[1][1]; + else subhi[1] = boxhi[1]; sublo[2] = boxlo[2] + zprd*mysplit[2][0]; - subhi[2] = boxlo[2] + zprd*mysplit[2][1]; - if (mysplit[2][1] == 1.0) subhi[2] = boxhi[2]; + if (mysplit[2][1] < 1.0) subhi[2] = boxlo[2] + zprd*mysplit[2][1]; + else subhi[2] = boxhi[2]; } } From 22cecea979fd4344bb215d273952e890bf9b5c89 Mon Sep 17 00:00:00 2001 From: sjplimp Date: Tue, 29 Jul 2014 23:26:56 +0000 Subject: [PATCH 27/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12224 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/dump_custom.cpp | 20 +++++++++++++++++++- src/dump_custom.h | 1 + 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/dump_custom.cpp b/src/dump_custom.cpp index a65485883b..3cef1beff9 100644 --- a/src/dump_custom.cpp +++ b/src/dump_custom.cpp @@ -34,7 +34,7 @@ using namespace LAMMPS_NS; // customize by adding keyword // also customize compute_atom_property.cpp -enum{ID,MOL,PROC,TYPE,ELEMENT,MASS, +enum{ID,MOL,PROC,PROCP1,TYPE,ELEMENT,MASS, X,Y,Z,XS,YS,ZS,XSTRI,YSTRI,ZSTRI,XU,YU,ZU,XUTRI,YUTRI,ZUTRI, XSU,YSU,ZSU,XSUTRI,YSUTRI,ZSUTRI, IX,IY,IZ, @@ -455,6 +455,10 @@ int DumpCustom::count() for (i = 0; i < nlocal; i++) dchoose[i] = me; ptr = dchoose; nstride = 1; + } else if (thresh_array[ithresh] == PROCP1) { + for (i = 0; i < nlocal; i++) dchoose[i] = me; + ptr = dchoose; + nstride = 1; } else if (thresh_array[ithresh] == TYPE) { int *type = atom->type; for (i = 0; i < nlocal; i++) dchoose[i] = type[i]; @@ -985,6 +989,9 @@ int DumpCustom::parse_fields(int narg, char **arg) } else if (strcmp(arg[iarg],"proc") == 0) { pack_choice[i] = &DumpCustom::pack_proc; vtype[i] = INT; + } else if (strcmp(arg[iarg],"procp1") == 0) { + pack_choice[i] = &DumpCustom::pack_procp1; + vtype[i] = INT; } else if (strcmp(arg[iarg],"type") == 0) { pack_choice[i] = &DumpCustom::pack_type; vtype[i] = INT; @@ -1392,6 +1399,7 @@ int DumpCustom::modify_param(int narg, char **arg) if (strcmp(arg[1],"id") == 0) thresh_array[nthresh] = ID; else if (strcmp(arg[1],"mol") == 0) thresh_array[nthresh] = MOL; else if (strcmp(arg[1],"proc") == 0) thresh_array[nthresh] = PROC; + else if (strcmp(arg[1],"procp1") == 0) thresh_array[nthresh] = PROCP1; else if (strcmp(arg[1],"type") == 0) thresh_array[nthresh] = TYPE; else if (strcmp(arg[1],"mass") == 0) thresh_array[nthresh] = MASS; @@ -1701,6 +1709,16 @@ void DumpCustom::pack_proc(int n) /* ---------------------------------------------------------------------- */ +void DumpCustom::pack_procp1(int n) +{ + for (int i = 0; i < nchoose; i++) { + buf[n] = me+1; + n += size_one; + } +} + +/* ---------------------------------------------------------------------- */ + void DumpCustom::pack_type(int n) { int *type = atom->type; diff --git a/src/dump_custom.h b/src/dump_custom.h index 794349515a..1fa820ab50 100644 --- a/src/dump_custom.h +++ b/src/dump_custom.h @@ -118,6 +118,7 @@ class DumpCustom : public Dump { void pack_id(int); void pack_molecule(int); void pack_proc(int); + void pack_procp1(int); void pack_type(int); void pack_mass(int); From e1adfe9db5c8fd690cd85c2ce8c612ce8d95c049 Mon Sep 17 00:00:00 2001 From: sjplimp Date: Tue, 29 Jul 2014 23:27:01 +0000 Subject: [PATCH 28/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12225 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/comm_tiled.cpp | 66 ++++++++++++++++++++++------------------------ src/comm_tiled.h | 19 ++++++++----- 2 files changed, 44 insertions(+), 41 deletions(-) diff --git a/src/comm_tiled.cpp b/src/comm_tiled.cpp index 2bd089e35d..42f6efdb6c 100644 --- a/src/comm_tiled.cpp +++ b/src/comm_tiled.cpp @@ -70,9 +70,9 @@ CommTiled::CommTiled(LAMMPS *lmp, Comm *oldcomm) : Comm(*oldcomm) CommTiled::~CommTiled() { - memory->destroy(overlap); memory->destroy(buf_send); memory->destroy(buf_recv); + memory->destroy(overlap); deallocate_swap(nswap); } @@ -83,8 +83,6 @@ CommTiled::~CommTiled() void CommTiled::init_buffers() { - memory->create(overlap,nprocs,"comm:overlap"); - maxexchange = maxexchange_atom + maxexchange_fix; bufextra = maxexchange + BUFEXTRA; @@ -93,6 +91,9 @@ void CommTiled::init_buffers() maxrecv = BUFMIN; memory->create(buf_recv,maxrecv,"comm:buf_recv"); + maxoverlap = 0; + overlap = NULL; + nswap = 2 * domain->dimension; allocate_swap(nswap); } @@ -174,13 +175,16 @@ void CommTiled::setup() { int i; + // domain properties used in setup and methods it calls + + prd = domain->prd; + boxlo = domain->boxlo; + boxhi = domain->boxhi; + sublo = domain->sublo; + subhi = domain->subhi; + int dimension = domain->dimension; int *periodicity = domain->periodicity; - double *prd = domain->prd; - double *boxlo = domain->boxlo; - double *boxhi = domain->boxhi; - double *sublo = domain->sublo; - double *subhi = domain->subhi; // set function pointers @@ -220,7 +224,7 @@ void CommTiled::setup() // sets nsendproc, nrecvproc, sendproc, recvproc // sets sendother, sendself, pbc_flag, pbc, sendbox - int noverlap,noverlap1,indexme; + int noverlap1,indexme; double lo1[3],hi1[3],lo2[3],hi2[3]; int one,two; @@ -273,9 +277,9 @@ void CommTiled::setup() indexme = -1; noverlap = 0; - if (one) (this->*box_drop)(idim,lo1,hi1,noverlap,indexme); + if (one) (this->*box_drop)(idim,lo1,hi1,indexme); noverlap1 = noverlap; - if (two) (this->*box_drop)(idim,lo2,hi2,noverlap,indexme); + if (two) (this->*box_drop)(idim,lo2,hi2,indexme); // if self is in overlap list, move it to end of list @@ -1101,17 +1105,10 @@ int CommTiled::exchange_variable(int n, double *inbuf, double *&outbuf) box is owned by me and extends in dim ------------------------------------------------------------------------- */ -void CommTiled::box_drop_brick(int idim, double *lo, double *hi, - int &noverlap, int &indexme) +void CommTiled::box_drop_brick(int idim, double *lo, double *hi, int &indexme) { // NOTE: this is not triclinic compatible - double *prd = domain->prd; - double *boxlo = domain->boxlo; - double *boxhi = domain->boxhi; - double *sublo = domain->sublo; - double *subhi = domain->subhi; - int index,dir; if (hi[idim] == sublo[idim]) { index = myloc[idim] - 1; @@ -1153,6 +1150,11 @@ void CommTiled::box_drop_brick(int idim, double *lo, double *hi, else if (idim == 1) proc = grid2proc[other1][index][other2]; else proc = grid2proc[other1][other2][idim]; + if (noverlap == maxoverlap) { + maxoverlap += DELTA_PROCS; + memory->grow(overlap,maxoverlap,"comm:overlap"); + } + if (proc == me) indexme = noverlap; overlap[noverlap++] = proc; index += dir; @@ -1167,20 +1169,24 @@ void CommTiled::box_drop_brick(int idim, double *lo, double *hi, no need to split lo/hi box as recurse b/c OK if box extends outside RCB box ------------------------------------------------------------------------- */ -void CommTiled::box_drop_tiled(int idim, double *lo, double *hi, - int &noverlap, int &indexme) +void CommTiled::box_drop_tiled(int idim, double *lo, double *hi, int &indexme) { - box_drop_tiled_recurse(lo,hi,0,nprocs-1,noverlap,indexme); + box_drop_tiled_recurse(lo,hi,0,nprocs-1,indexme); } void CommTiled::box_drop_tiled_recurse(double *lo, double *hi, int proclower, int procupper, - int &noverlap, int &indexme) + int &indexme) { // end recursion when partition is a single proc // add proc to overlap list if (proclower == procupper) { + if (noverlap == maxoverlap) { + maxoverlap += DELTA_PROCS; + memory->grow(overlap,maxoverlap,"comm:overlap"); + } + if (proclower == me) indexme = noverlap; overlap[noverlap++] = proclower; return; @@ -1198,9 +1204,9 @@ void CommTiled::box_drop_tiled_recurse(double *lo, double *hi, int idim = rcbinfo[procmid].dim; if (lo[idim] < cut) - box_drop_tiled_recurse(lo,hi,proclower,procmid-1,noverlap,indexme); + box_drop_tiled_recurse(lo,hi,proclower,procmid-1,indexme); if (hi[idim] > cut) - box_drop_tiled_recurse(lo,hi,procmid,procupper,noverlap,indexme); + box_drop_tiled_recurse(lo,hi,procmid,procupper,indexme); } /* ---------------------------------------------------------------------- @@ -1210,12 +1216,6 @@ void CommTiled::box_drop_tiled_recurse(double *lo, double *hi, void CommTiled::box_other_brick(int idim, int iswap, int proc, double *lo, double *hi) { - double *prd = domain->prd; - double *boxlo = domain->boxlo; - double *boxhi = domain->boxhi; - double *sublo = domain->sublo; - double *subhi = domain->subhi; - lo[0] = sublo[0]; lo[1] = sublo[1]; lo[2] = sublo[2]; hi[0] = subhi[0]; hi[1] = subhi[1]; hi[2] = subhi[2]; @@ -1264,10 +1264,6 @@ void CommTiled::box_other_brick(int idim, int iswap, void CommTiled::box_other_tiled(int idim, int iswap, int proc, double *lo, double *hi) { - double *prd = domain->prd; - double *boxlo = domain->boxlo; - double *boxhi = domain->boxhi; - double (*split)[2] = rcbinfo[proc].mysplit; lo[0] = boxlo[0] + prd[0]*split[0][0]; diff --git a/src/comm_tiled.h b/src/comm_tiled.h index f3f8f65e74..fd27c4de41 100644 --- a/src/comm_tiled.h +++ b/src/comm_tiled.h @@ -94,18 +94,25 @@ class CommTiled : public Comm { int dim; // dimension = 0/1/2 of cut }; - int *overlap; - RCBinfo *rcbinfo; // list of RCB info for all procs + int noverlap; // # of overlapping procs + int maxoverlap; // current max length of overlap + int *overlap; // list of overlapping procs + + RCBinfo *rcbinfo; // list of RCB info for all procs + + double *prd; // local ptrs to Domain attributes + double *boxlo,*boxhi; + double *sublo,*subhi; void init_buffers(); // box drop and other functions - typedef void (CommTiled::*BoxDropPtr)(int, double *, double *, int &, int &); + typedef void (CommTiled::*BoxDropPtr)(int, double *, double *, int &); BoxDropPtr box_drop; - void box_drop_brick(int, double *, double *, int &, int &); - void box_drop_tiled(int, double *, double *, int &, int &); - void box_drop_tiled_recurse(double *, double *, int, int, int &, int &); + void box_drop_brick(int, double *, double *, int &); + void box_drop_tiled(int, double *, double *, int &); + void box_drop_tiled_recurse(double *, double *, int, int, int &); typedef void (CommTiled::*BoxOtherPtr)(int, int, int, double *, double *); BoxOtherPtr box_other; From cca9c603ea94a86b492c0295cc80c68fddcbc25d Mon Sep 17 00:00:00 2001 From: sjplimp Date: Tue, 29 Jul 2014 23:28:49 +0000 Subject: [PATCH 29/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12226 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- doc/dump.html | 23 +++++++++++++---------- doc/dump.txt | 23 +++++++++++++---------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/doc/dump.html b/doc/dump.html index e83d9f5c5f..c13777fd3e 100644 --- a/doc/dump.html +++ b/doc/dump.html @@ -57,7 +57,7 @@ f_ID[N] = Nth column of local array calculated by a fix with ID
      custom of custom/mpiio args = list of atom attributes
    -    possible attributes = id, mol, type, element, mass,
    +    possible attributes = id, mol, proc, procp1, type, element, mass,
     			  x, y, z, xs, ys, zs, xu, yu, zu, 
     			  xsu, ysu, zsu, ix, iy, iz,
     			  vx, vy, vz, fx, fy, fz,
    @@ -69,6 +69,7 @@
     
          id = atom ID
           mol = molecule ID
           proc = ID of processor that owns atom
    +      procp1 = ID+1 of processor that owns atom
           type = atom type
           element = name of atom element, as defined by dump_modify command
           mass = atom mass
    @@ -460,18 +461,20 @@ dump 1 all local 1000 tmp.dump index c_1[1] c_1[2] c_1[3] c_2[1] c_2[2]
     

    This section explains the atom attributes that can be specified as part of the custom and cfg styles.

    -

    The id, mol, proc, type, element, mass, vx, vy, vz, -fx, fy, fz, q attributes are self-explanatory. +

    The id, mol, proc, procp1, type, element, mass, vx, +vy, vz, fx, fy, fz, q attributes are self-explanatory.

    Id is the atom ID. Mol is the molecule ID, included in the data file for molecular systems. Proc is the ID of the processor (0 to -Nprocs-1) that currently owns the atom. Type is the atom type. -Element is typically the chemical name of an element, which you must -assign to each type via the dump_modify element -command. More generally, it can be any string you wish to associated -with an atom type. Mass is the atom mass. Vx, vy, vz, fx, -fy, fz, and q are components of atom velocity and force and -atomic charge. +Nprocs-1) that currently owns the atom. Procp1 is the proc ID+1, +which can be convenient in place of a type attribute (1 to Ntypes) +for coloring atoms in a visualization program. Type is the atom +type (1 to Ntypes). Element is typically the chemical name of an +element, which you must assign to each type via the dump_modify +element command. More generally, it can be any +string you wish to associated with an atom type. Mass is the atom +mass. Vx, vy, vz, fx, fy, fz, and q are components of +atom velocity and force and atomic charge.

    There are several options for outputting atom coordinates. The x, y, z attributes write atom coordinates "unscaled", in the diff --git a/doc/dump.txt b/doc/dump.txt index cfd0884c9c..b41ddaa473 100644 --- a/doc/dump.txt +++ b/doc/dump.txt @@ -44,7 +44,7 @@ args = list of arguments for a particular style :l f_ID\[N\] = Nth column of local array calculated by a fix with ID :pre {custom} of {custom/mpiio} args = list of atom attributes - possible attributes = id, mol, type, element, mass, + possible attributes = id, mol, proc, procp1, type, element, mass, x, y, z, xs, ys, zs, xu, yu, zu, xsu, ysu, zsu, ix, iy, iz, vx, vy, vz, fx, fy, fz, @@ -56,6 +56,7 @@ args = list of arguments for a particular style :l id = atom ID mol = molecule ID proc = ID of processor that owns atom + procp1 = ID+1 of processor that owns atom type = atom type element = name of atom element, as defined by "dump_modify"_dump_modify.html command mass = atom mass @@ -446,18 +447,20 @@ dump 1 all local 1000 tmp.dump index c_1\[1\] c_1\[2\] c_1\[3\] c_2\[1\] c_2\[2\ This section explains the atom attributes that can be specified as part of the {custom} and {cfg} styles. -The {id}, {mol}, {proc}, {type}, {element}, {mass}, {vx}, {vy}, {vz}, -{fx}, {fy}, {fz}, {q} attributes are self-explanatory. +The {id}, {mol}, {proc}, {procp1}, {type}, {element}, {mass}, {vx}, +{vy}, {vz}, {fx}, {fy}, {fz}, {q} attributes are self-explanatory. {Id} is the atom ID. {Mol} is the molecule ID, included in the data file for molecular systems. {Proc} is the ID of the processor (0 to -Nprocs-1) that currently owns the atom. {Type} is the atom type. -{Element} is typically the chemical name of an element, which you must -assign to each type via the "dump_modify element"_dump_modify.html -command. More generally, it can be any string you wish to associated -with an atom type. {Mass} is the atom mass. {Vx}, {vy}, {vz}, {fx}, -{fy}, {fz}, and {q} are components of atom velocity and force and -atomic charge. +Nprocs-1) that currently owns the atom. {Procp1} is the proc ID+1, +which can be convenient in place of a {type} attribute (1 to Ntypes) +for coloring atoms in a visualization program. {Type} is the atom +type (1 to Ntypes). {Element} is typically the chemical name of an +element, which you must assign to each type via the "dump_modify +element"_dump_modify.html command. More generally, it can be any +string you wish to associated with an atom type. {Mass} is the atom +mass. {Vx}, {vy}, {vz}, {fx}, {fy}, {fz}, and {q} are components of +atom velocity and force and atomic charge. There are several options for outputting atom coordinates. The {x}, {y}, {z} attributes write atom coordinates "unscaled", in the From 842ba3a22bcd29df5117ce4698b0b991753b8d19 Mon Sep 17 00:00:00 2001 From: sjplimp Date: Tue, 29 Jul 2014 23:29:10 +0000 Subject: [PATCH 30/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12227 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/comm_tiled.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/comm_tiled.cpp b/src/comm_tiled.cpp index 42f6efdb6c..fee84a4db6 100644 --- a/src/comm_tiled.cpp +++ b/src/comm_tiled.cpp @@ -47,7 +47,7 @@ enum{LAYOUT_UNIFORM,LAYOUT_NONUNIFORM,LAYOUT_TILED}; // several files CommTiled::CommTiled(LAMMPS *lmp) : Comm(lmp) { - error->all(FLERR,"Comm_style tiled is not yet supported"); + //error->all(FLERR,"Comm_style tiled is not yet supported"); style = 1; layout = LAYOUT_UNIFORM; @@ -58,7 +58,7 @@ CommTiled::CommTiled(LAMMPS *lmp) : Comm(lmp) CommTiled::CommTiled(LAMMPS *lmp, Comm *oldcomm) : Comm(*oldcomm) { - error->all(FLERR,"Comm_style tiled is not yet supported"); + //error->all(FLERR,"Comm_style tiled is not yet supported"); style = 1; layout = oldcomm->layout; From c5b40aaa90f8b9c0a0a232fb03a79b1631b8da6b Mon Sep 17 00:00:00 2001 From: sjplimp Date: Tue, 29 Jul 2014 23:29:20 +0000 Subject: [PATCH 31/31] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@12228 f3b2605a-c512-4ea7-a41b-209d697bcdaa --- src/comm_tiled.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/comm_tiled.cpp b/src/comm_tiled.cpp index fee84a4db6..42f6efdb6c 100644 --- a/src/comm_tiled.cpp +++ b/src/comm_tiled.cpp @@ -47,7 +47,7 @@ enum{LAYOUT_UNIFORM,LAYOUT_NONUNIFORM,LAYOUT_TILED}; // several files CommTiled::CommTiled(LAMMPS *lmp) : Comm(lmp) { - //error->all(FLERR,"Comm_style tiled is not yet supported"); + error->all(FLERR,"Comm_style tiled is not yet supported"); style = 1; layout = LAYOUT_UNIFORM; @@ -58,7 +58,7 @@ CommTiled::CommTiled(LAMMPS *lmp) : Comm(lmp) CommTiled::CommTiled(LAMMPS *lmp, Comm *oldcomm) : Comm(*oldcomm) { - //error->all(FLERR,"Comm_style tiled is not yet supported"); + error->all(FLERR,"Comm_style tiled is not yet supported"); style = 1; layout = oldcomm->layout;