使用 OOP 高效地处理和读取轨迹文件

Processing and reading a trajectory file efficiently using OOP

我正在编写读取大型 .xyz 文件的代码。这些类型的文件对于像 VMD 这样的分子动力学可视化工具很有用。所以文件格式看起来像这样

#Number of particles
#frame number
#Coordinates

举个例子:

5
0
C    1.23    2.33    4.56
C    1.23    2.33    5.56
C    1.23    2.33    6.56
C    1.23    2.33    7.56
C    1.23    2.33    8.56
5
1
C    2.23    2.33    4.56
C    2.23    3.33    5.56
C    2.23    4.33    6.56
C    2.23    5.33    7.56
C    2.23    6.33    8.56

等等。 我试图在这里 post 理解这个 https://codereview.stackexchange.com/questions/201743/processing-xyz-data-from-a-large-file,它讨论了使用运算符重载方法有效地从大型数据集中读取数据。我正在尝试编写一个 class ,它可以读取如此大的轨迹文件并给我以下输出:1)粒子数 2)帧总数 3)每个时间步的坐标集。因此,我尝试根据此 post 写下以下内容,以读取上述文件格式。到目前为止,下面的代码能够读取单个帧并在之后退出。

#include <iostream>
#include <vector>
#include <fstream>

struct Particle{

    long double x,y,z;
    char tab ='\t';
    char newline = '\n';
    char atom ;
    friend std::istream& operator>>(std::istream& in, Particle &xyz) {
        in >> xyz.atom >> xyz.x >> xyz.y >> xyz.z ;
        return in;
    }
    friend std::ostream& operator<<(std::ostream& out, Particle &xyz){
        out << xyz.x << xyz.tab << xyz.y << xyz.tab << xyz.z << xyz.newline;
        return out;
    }
};
class XYZ_frame_read
{

    int curr_frame;
    int num_particles;
    std::vector<Particle> coordinates_t;

    public:

    friend std::istream& operator>>(std::istream& in, XYZ_frame_read &traj ){

                in >> traj.num_particles;
                in >> traj.curr_frame;
                Particle p;
                while(in >> p){
                    traj.coordinates_t.push_back(p);
                }
            return in;
        }
    friend std::ostream& operator<<(std::ostream& out, XYZ_frame_read &traj){

            for(int i = 0; i< traj.num_particles ;i ++){
                out << traj.coordinates_t.at(i) ;
            }
            return out;
        }
};

int main(int argc, char *argv[]){

    std::ifstream in(argv[1]);
    XYZ_frame_read* frames = new XYZ_frame_read[3];
    in >> frames[0];
    std::cout << frames[0];

    return 0;
}

问题是我不明白我将如何实现此方法来读取下一帧并将它们附加到对象 XYZ_frame_read 的每个实例的 coordinates_t 向量中。我想我明白这是如何工作的,所以显然 while(!in.eof()) 是毫无疑问的,因为它只会一遍又一遍地读取第一帧。我是 c++ 的新手,正在从事与分子动力学相关的项目,欢迎任何 changes/suggestions !!感谢您的帮助!

编辑

我试过使用

size_t i = 0;
while(in >> frames[i]){
    std::cout << frames[i];
    if(i == 3){
        break;
    }
    i++;
}

它returns空白。它不起作用。循环甚至没有被执行。

while(!in.eof()) 是不可能的,因为 eof 不是那样工作的。

Why is iostream::eof inside a loop condition (i.e. `while (!stream.eof())`) considered wrong?

我不确定我是否看到问题,有什么问题

size_t i = 0;
while (in >> frames[i])
    ++i;

(除了数组边界错误的可能性)。

编辑

此代码不正确

 friend std::istream& operator>>(std::istream& in, XYZ_frame_read &traj) {
     in >> traj.num_particles;
     in >> traj.curr_frame;
     Particle p;
     while(in >> p){
          traj.coordinates_t.push_back(p);
     }
     return in;
 }

这表示继续读取粒子,直到读取失败。这是不正确的,你知道有多少粒子。它应该说继续阅读粒子,直到你阅读了 num_particles 个粒子(或阅读失败)。 IE。它应该说

 friend std::istream& operator>>(std::istream& in, XYZ_frame_read &traj) {
     in >> traj.num_particles;
     in >> traj.curr_frame;
     Particle p;
     for (int i = 0; i < traj.num_particles && in >> p; ++i) 
          traj.coordinates_t.push_back(p);
     }
     return in;
 }

你非常接近,你只需要在重载的运算符函数中验证你的输入,不要使用 new!,而是使用 std::vector<XYZ_frame_read> frames;

例如,对于 class XYZ_frame_readistream 重载,您只需要:

    friend std::istream& operator>>(std::istream& in, XYZ_frame_read &traj)
    {
        /* validate that num_particles and curr_frame read */
        if (in >> traj.num_particles >> traj.curr_frame) {
            int n = traj.num_particles; /* set number of particles to read */
            Particle p;

            while (n-- && (in >> p))    /* read that number of particles */
                traj.coordinates_t.push_back(p);
        }
        return in;
    }

然后在 main() 中,而不是像您在此处那样为 frames 分配 new

    XYZ_frame_read* frames = new XYZ_frame_read[3];
    in >> frames[0];
    std::cout << frames[0];

只需使用 std::vector<XYZ_frame_read> frames;,然后使用 临时 class XYZ_frame_read 在将读取添加到帧向量之前验证读取,例如

    std::vector<XYZ_frame_read> frames; /* vector of frames (NO new!) */

    for (;;) {                      /* continual loop while good input */
        XYZ_frame_read tmp;         /* temp XYZ_frame_read for read */

        if ((in >> tmp))            /* if read is good */
            frames.push_back(tmp);  /* add it to vector of frames */
        else
            break;                  /* otherwise -- bail */
    }

对于输出,只需使用 auto-ranged for 循环,例如

    for (auto & f : frames)         /* auto-ranged for loop to output frames */
        std::cout << "\nframe: " << f.get_frame() << 
                    "  particles: " << f.get_nparticles() << "\n\n" << 
                    f << '\n';

总而言之,您将拥有:

#include <iostream>
#include <vector>
#include <fstream>

struct Particle {

    long double x,y,z;
    char tab ='\t';
    char newline = '\n';
    char atom ;

    friend std::istream& operator>>(std::istream& in, Particle &xyz) {
        in >> xyz.atom >> xyz.x >> xyz.y >> xyz.z;
        return in;
    }

    friend std::ostream& operator<<(std::ostream& out, Particle &xyz) {
        out << xyz.x << xyz.tab << xyz.y << xyz.tab << xyz.z << xyz.newline;
        return out;
    }
};

class XYZ_frame_read
{
    int curr_frame;
    int num_particles;
    std::vector<Particle> coordinates_t;

  public:

    friend std::istream& operator>>(std::istream& in, XYZ_frame_read &traj)
    {
        /* validate that num_particles and curr_frame read */
        if (in >> traj.num_particles >> traj.curr_frame) {
            int n = traj.num_particles; /* set number of particles to read */
            Particle p;

            while (n-- && (in >> p))    /* read that number of particles */
                traj.coordinates_t.push_back(p);
        }
        return in;
    }
    friend std::ostream& operator<<(std::ostream& out, XYZ_frame_read &traj) {

        for(int i = 0; i< traj.num_particles ;i ++)
            out << traj.coordinates_t.at(i) ;

        return out;
    }
    int get_frame(void) { return curr_frame; }
    int get_nparticles (void) { return num_particles; }
    int getpsize(void) { return coordinates_t.size(); }
};

int main(int argc, char *argv[]) {

    std::ifstream in(argv[1]);
    std::vector<XYZ_frame_read> frames; /* vector of frames (NO new!) */

    for (;;) {                      /* continual loop while good input */
        XYZ_frame_read tmp;         /* temp XYZ_frame_read for read */

        if ((in >> tmp))            /* if read is good */
            frames.push_back(tmp);  /* add it to vector of frames */
        else
            break;                  /* otherwise -- bail */
    }

    for (auto & f : frames)         /* auto-ranged for loop to output frames */
        std::cout << "\nframe: " << f.get_frame() << 
                    "  particles: " << f.get_nparticles() << "\n\n" << 
                    f << '\n';

    return 0;
    (void)argc;     /* suppress -Wunused warning */
}

使用 vector 代替 new 分配给 class XYZ_frame_read 有很多好处。自动 memory-management 只是冰山一角。

示例输入文件

使用您的示例输入:

$ cat particles.txt
5
0
C    1.23    2.33    4.56
C    1.23    2.33    5.56
C    1.23    2.33    6.56
C    1.23    2.33    7.56
C    1.23    2.33    8.56
5
1
C    2.23    2.33    4.56
C    2.23    3.33    5.56
C    2.23    4.33    6.56
C    2.23    5.33    7.56
C    2.23    6.33    8.56

例子Use/Output

只需提供文件名,您的 std::vector<XYZ_frame_read> frames 就会自动填充,而不管您的数据文件中有多少帧(最多 virtual-memory 的限制)

$ ./bin/paticle particles.txt

frame: 0  particles: 5

1.23    2.33    4.56
1.23    2.33    5.56
1.23    2.33    6.56
1.23    2.33    7.56
1.23    2.33    8.56


frame: 1  particles: 5

2.23    2.33    4.56
2.23    3.33    5.56
2.23    4.33    6.56
2.23    5.33    7.56
2.23    6.33    8.56