You have two primary problems that are plaguing your attempt to successfully read from your data file, one is specific, and the other more general, but both equally fatal to the attempt.
The technical problem with your read can be solved by reviewing Why !.eof() inside a loop condition is always wrong.. The equally important more general problem you are facings is the sprawling list of vectors
, strings
, and doubles
you are attempting to duct-tape and bailing-wire together. When you overly complicate your data handling using superfluous or ill-fitting containers, trying to make them work together is like trying to put toothpaste back into the tube -- it ain't gonna work....
Take a bit of time and sort out what it is you need to store for each chunk of data, and then create a data structure that holds that. It's fine to use a few temporary variables to facilitate reading the data, but the final storage for your data should be straight-forward and as simple as allowable.
In your case you are storing the atomic-symbol as a string
and three double
values for the position. You base data structure should be able to capture all three as a single object. While there are a couple of options, the plain-old struct
for coordinating between the differing types of data will work fine. Then you simply create a vector of struct as your final storage solution to allow access to your data.
In keeping with the simple-as-required, you can use a struct
containing a string
and three double
values. For example:
struct atom {
std::string sym;
double x, y, z;
};
That is all that is needed to capture symbol and positional coordinates. The you simply declare a vector of atom as your final storage solution, e.g.
std::vector<atom> atoms; /* your final storage container */
A running theme throughout your code that will invite Undefined Behavior is failing to validate EVERY input. If you simply read from a stream without validating whether the read succeeded or failed, you are simply playing Russian-Roulette. If your read fails, and you just blindly continue forward using the uninitialized variable you assume was properly filled with data, game-over.
So validate every read. For example, reading the charge
and multiplicity
, you can do:
if (!(fs >> charge >> multiplicity)) { /* validate EVERY input */
std::cerr << "error: invalid format: charge, multiplicity\n";
return 1;
}
(note: I've shortened your variable names, e.g. inputfile
is now fs
, typing is not a strong-suit)
For reading each atom from every subsequent line in the file, you can do:
/* read each remaining line until you run out */
while (fs >> atmSymbol >> bposX >> bposY >> bposZ) {
/* add the values read to your temporary struct */
atom tmp = { atmSymbol, bposX, bposY, bposZ };
atoms.push_back(tmp); /* push tmp struct onto storage vector */
}
The key is to validate the success or failure of every read so you know you are processing valid data in your code.
Putting the rest of it together in a short example to read your data file, you could do something like the following:
#include <iostream>
#include <iomanip>
#include <fstream>
#include <array>
#include <string>
#include <vector>
struct atom {
std::string sym;
double x, y, z;
};
int main (int argc, char *argv[]) {
if (argc < 2) { /* validate at least one argument given for filename */
std::cout << "error: insuffient input.\n"
"usage: " << argv[0] << " <fn>\n";
return 1;
}
std::cout << "The input file is: " << argv[1] << '\n';
std::ifstream fs (argv[1]); /* file stream, just use argv[1] */
int charge, multiplicity, natom; /* temporary variables for filling */
double bposX, bposY, bposZ;
std::string atmSymbol;
std::vector<atom> atoms; /* your final storage container */
if (!(fs >> charge >> multiplicity)) { /* validate EVERY input */
std::cerr << "error: invalid format: charge, multiplicity\n";
return 1;
}
/* read each remaining line until you run out */
while (fs >> atmSymbol >> bposX >> bposY >> bposZ) {
/* add the values read to your temporary struct */
atom tmp = { atmSymbol, bposX, bposY, bposZ };
atoms.push_back(tmp); /* push tmp struct onto storage vector */
}
fs.close(); /* close stream -- you are done reading */
natom = atoms.size(); /* get an output size */
std::cout << "\nThere are " << natom << " atoms.\n\n";
for (auto& a : atoms) { /* loop over each atom in vector */
std::cout << a.sym /* output atomic symbol */
<< " " << std::setw(8) << a.x /* each coordinate, and */
<< " " << std::setw(8) << a.y
<< " " << std::setw(8) << a.z << '\n';/* tidy up with \n */
}
}
Example Use/Output
$ ./bin/atoms_read dat/atoms.txt
The input file is: dat/atoms.txt
There are 9 atoms.
C 1.11988 -0.11356 -0.04893
C -0.22149 0.53742 0.1539
N -1.36703 -0.23693 -0.0457
O -0.39583 1.70537 0.48392
H 1.93813 0.59458 0.13709
H 1.23188 -0.48457 -1.07645
H 1.25795 -0.96373 0.63239
H -2.27205 0.14808 0.07622
H -1.29145 -1.18667 -0.31244
Look things over and let me know if you have further questions.
Update Based on Request To Handle Empty-Lines in File
If you have additional blank-line separated blocks of atoms to read in your datafile, all you need to do is rearrange your read slightly to use getline
to read a line at a time from the file. You then create a stringstream
from the line and read from the stringstream just as we originally read from the file. If you can validly read into your atomic-symbol and positional coordinates from the stringstream, you have a valid line.
A quick edit to read with getline
and removing the temporary variables that are no longer needed (we can read directly into the temporary struct now), you could do:
std::ifstream fs (argv[1]); /* file stream, just use argv[1] */
int charge, multiplicity, natom; /* temporary variables for filling */
std::string line;
std::vector<atom> atoms; /* your final storage container */
if (!(fs >> charge >> multiplicity)) { /* validate EVERY input */
std::cerr << "error: invalid format: charge, multiplicity\n";
return 1;
}
/* read each remaining line until you run out with getline */
while (getline (fs, line)) {
std::stringstream ss (line); /* create stringstream from line */
atom tmp; /* declare temporary struct */
/* read from stringstream into temporary struct */
if (ss >> tmp.sym >> tmp.x >> tmp.y >> tmp.z)
atoms.push_back(tmp); /* push_back atmp struct on success */
}
fs.close(); /* close stream -- you are done reading */
Now, beginning with the second line, the code will read all atom data that matches your line format into your atoms
vector regardless of blank or other non-conforming lines in your file.