diff --git a/doc/src/variable.rst b/doc/src/variable.rst index 483e95045c..6b143ab5ae 100644 --- a/doc/src/variable.rst +++ b/doc/src/variable.rst @@ -279,9 +279,9 @@ This means the variable can then be evaluated as many times as desired and will return those values. There are two ways to cause the next set of per-atom values from the file to be read: use the :doc:`next ` command or the next() function in an atom-style -variable, as discussed below. Unlike most variable styles -atomfile-style variables are **deleted** during a :doc:`clear ` -command. +variable, as discussed below. Unlike most variable styles, which +remain defined, atomfile-style variables are **deleted** during a +:doc:`clear ` command. The rules for formatting the file are as follows. Each time a set of per-atom values is read, a non-blank line is searched for in the file. @@ -289,23 +289,37 @@ The file is read line by line but only up to 254 characters are used. The rest are ignored. A comment character "#" can be used anywhere on a line and all text following and the "#" character are ignored; text starting with the comment character is stripped. Blank lines -are skipped. The first "word" of a non-blank line, delimited by -white-space, is read as the count N of per-atom lines to immediately -follow. N can be the total number of atoms in the system, or only a -subset. The next N lines have the following format - -.. parsed-literal:: - - ID value - -where ID is an atom ID and value is the per-atom numeric value that -will be assigned to that atom. IDs can be listed in any order. +are skipped. The first non-blank line is expected to contain a single +integer number as the count *N* of per-atom lines to follow. *N* can +be the total number of atoms in the system or less, indicating that data +for a subset is read. The next N lines must consist of two numbers, +the atom-ID of the atom for which a value is set followed by a floating +point number with the value. The atom-IDs may be listed in any order. .. note:: - Every time a set of per-atom lines is read, the value for all - atoms is first set to 0.0. Thus values for atoms whose ID does not - appear in the set, will remain 0.0. + Every time a set of per-atom lines is read, the value of the atomfile + variable for **all** atoms is first initialized to 0.0. Thus values + for atoms whose ID do not appear in the set in the file will remain + at 0.0. + +Below is a small example for the atomfile variable file format: + + .. parsed-literal:: + + # first set + 4 + # atom-ID value + 3 1 + 4 -4 + 1 0.5 + 2 -0.5 + + # second set + 2 + + 2 1.0 + 4 -1.0 ---------- diff --git a/src/variable.cpp b/src/variable.cpp index c99e4a4761..89d15d38f5 100644 --- a/src/variable.cpp +++ b/src/variable.cpp @@ -369,7 +369,8 @@ void Variable::set(int narg, char **arg) data[nvar][0] = new char[MAXLINE]; reader[nvar] = new VarReader(lmp,arg[0],arg[2],SCALARFILE); int flag = reader[nvar]->read_scalar(data[nvar][0]); - if (flag) error->all(FLERR,"File variable could not read value"); + if (flag) + error->all(FLERR,"File variable {} could not read value from {}", arg[0], arg[2]); // ATOMFILE for numbers // which = 1st value @@ -387,7 +388,8 @@ void Variable::set(int narg, char **arg) data[nvar][0] = nullptr; reader[nvar] = new VarReader(lmp,arg[0],arg[2],ATOMFILE); int flag = reader[nvar]->read_peratom(); - if (flag) error->all(FLERR,"Atomfile variable could not read values"); + if (flag) + error->all(FLERR,"Atomfile variable {} could not read values from {}", arg[0], arg[2]); // FORMAT // num = 3, which = 1st value @@ -5405,7 +5407,8 @@ VarReader::VarReader(LAMMPS *lmp, char *name, char *file, int flag) : if (me == 0) { fp = fopen(file,"r"); if (fp == nullptr) - error->one(FLERR,"Cannot open file variable file {}: {}", file, utils::getsyserror()); + error->one(FLERR,"Cannot open {} variable {} file {}: {}", (style == Variable::ATOMFILE) + ? "atomfile" : "file", name, file, utils::getsyserror()); } // if atomfile-style variable, must store per-atom values read from file @@ -5463,14 +5466,12 @@ int VarReader::read_scalar(char *str) while (true) { ptr = fgets(str,MAXLINE,fp); if (!ptr) { n=0; break; } // end of file - ptr[strcspn(ptr,"#")] = '\0'; // strip comment - ptr += strspn(ptr," \t\n\r\f"); // strip leading whitespace - ptr[strcspn(ptr," \t\n\r\f")] = '\0'; // strip trailing whitespace - n = strlen(ptr) + 1; + auto line = utils::trim(utils::trim_comment(str)); + n = line.size() + 1; if (n == 1) continue; // skip if blank line + memcpy(str, line.c_str(), n); break; } - if (n > 0) memmove(str,ptr,n); // move trimmed string back } MPI_Bcast(&n,1,MPI_INT,0,world); if (n == 0) return 1; @@ -5486,9 +5487,9 @@ int VarReader::read_scalar(char *str) int VarReader::read_peratom() { - int i,m,n,nchunk,eof; + int i,m,nchunk,eof; tagint tag; - char *ptr,*next; + char *ptr; double value; // set all per-atom values to 0.0 @@ -5502,24 +5503,22 @@ int VarReader::read_peratom() // read one string from file, convert to Nlines char str[MAXLINE]; + bigint nlines = 0; if (me == 0) { while (true) { ptr = fgets(str,MAXLINE,fp); - if (!ptr) { n=0; break; } // end of file - ptr[strcspn(ptr,"#")] = '\0'; // strip comment - ptr += strspn(ptr," \t\n\r\f"); // strip leading whitespace - ptr[strcspn(ptr," \t\n\r\f")] = '\0'; // strip trailing whitespace - n = strlen(ptr) + 1; - if (n == 1) continue; // skip if blank line + if (!ptr) { nlines = 0; break; } // end of file + Tokenizer words(utils::trim(utils::trim_comment(str))); + if (words.count() == 0) continue; // skip if blank or comment line + if (words.count() != 1) + error->one(FLERR, "Expected 1 token but found {} when parsing {}", words.count(), str); + nlines = utils::bnumeric(FLERR,words.next(),true,lmp); break; } - memmove(str,ptr,n); // move trimmed string back } + MPI_Bcast(&nlines,1,MPI_LMP_BIGINT,0,world); + if (nlines == 0) return 1; - MPI_Bcast(&n,1,MPI_INT,0,world); - if (n == 0) return 1; - MPI_Bcast(str,n,MPI_CHAR,0,world); - bigint nlines = utils::bnumeric(FLERR,str,false,lmp); tagint map_tag_max = atom->map_tag_max; bigint nread = 0; @@ -5528,24 +5527,22 @@ int VarReader::read_peratom() eof = utils::read_lines_from_file(fp,nchunk,MAXLINE,buffer,me,world); if (eof) return 1; - char *buf = buffer; - for (i = 0; i < nchunk; i++) { - next = strchr(buf,'\n'); - *next = '\0'; + for (const auto &line : utils::split_lines(buffer)) { try { - ValueTokenizer words(buf); + ValueTokenizer words(utils::trim_comment(utils::trim(line))); + if (words.count() == 0) continue; // skip comment or empty lines + if (words.count() != 2) + throw TokenizerException(fmt::format("expected 2 tokens but found {}", words.count()), ""); tag = words.next_bigint(); value = words.next_double(); + ++nread; } catch (TokenizerException &e) { - error->all(FLERR,"Invalid atomfile line '{}': {}",buf,e.what()); + error->all(FLERR,"Invalid atomfile line '{}': {}", line, e.what()); } if ((tag <= 0) || (tag > map_tag_max)) error->all(FLERR,"Invalid atom ID {} in variable file", tag); if ((m = atom->map(tag)) >= 0) vstore[m] = value; - buf = next + 1; } - - nread += nchunk; } return 0; diff --git a/unittest/commands/test_variables.cpp b/unittest/commands/test_variables.cpp index c631b69528..6748867b4e 100644 --- a/unittest/commands/test_variables.cpp +++ b/unittest/commands/test_variables.cpp @@ -216,7 +216,7 @@ TEST_F(VariableTest, CreateDelete) command("variable one internal 2");); TEST_FAILURE(".*ERROR: Cannot use atomfile-style variable unless an atom map exists.*", command("variable eleven atomfile test_variable.atomfile");); - TEST_FAILURE(".*ERROR on proc 0: Cannot open file variable file test_variable.xxx.*", + TEST_FAILURE(".*ERROR on proc 0: Cannot open file variable nine1 file test_variable.xxx.*", command("variable nine1 file test_variable.xxx");); TEST_FAILURE(".*ERROR: World variable count doesn't match # of partitions.*", command("variable ten10 world xxx xxx");); @@ -293,7 +293,7 @@ TEST_F(VariableTest, AtomicSystem) command("variable one atom x");); TEST_FAILURE(".*ERROR: Cannot redefine variable as a different style.*", command("variable id vector f_press");); - TEST_FAILURE(".*ERROR on proc 0: Cannot open file variable file test_variable.xxx.*", + TEST_FAILURE(".*ERROR on proc 0: Cannot open atomfile variable ten1 file test_variable.xxx.*", command("variable ten1 atomfile test_variable.xxx");); TEST_FAILURE(".*ERROR: Variable loop: has a circular dependency.*", variable->compute_equal("v_loop"););