brucefan1983 / GPUMD

Graphics Processing Units Molecular Dynamics
https://gpumd.org/dev
GNU General Public License v3.0
476 stars 117 forks source link

Cell info in netcdf output #550

Open bananenpampe opened 10 months ago

bananenpampe commented 10 months ago

Dear Developers,

I have encountered a strange issue when writing trajectories from a non-orthogonal box in the netcdf format. I find spurious correlations in the rdf functions I calculate from my trajectories, which make me believe that the cell info is not read correctly. I could reproduce the issue using both OVITO and MDanalysis. Please find attached the trajectory in the .extyz format written by GPUMD which I believe is correct and the .netcdf format. I had to rename the file extensions into .txt to be accepted from github, (dump.txt is the extxyz and movie_4.txt the netcdf) dump.txt movie_4.txt

brucefan1983 commented 10 months ago

Thanks for reporting this issue. I think the implementation of the netcdf cell in GPUMD is incorrect. If I understand it correctly, the angles are defined to be between the planes according to the manual of netcdf:

https://ambermd.org/netcdf/nctraj.xhtml

But in our code, it was implemented as angles between the cell vectors (https://github.com/brucefan1983/GPUMD/blob/master/src/measure/dump_netcdf.cu):

    const double* t = box.cpu_h;
    double cosgamma, cosbeta, cosalpha;
    cell_lengths[0] = sqrt(t[0] * t[0] + t[3] * t[3] + t[6] * t[6]); // a-side
    cell_lengths[1] = sqrt(t[1] * t[1] + t[4] * t[4] + t[7] * t[7]); // b-side
    cell_lengths[2] = sqrt(t[2] * t[2] + t[5] * t[5] + t[8] * t[8]); // c-side

    cosgamma = (t[0] * t[1] + t[3] * t[4] + t[6] * t[7]) / (cell_lengths[0] * cell_lengths[1]);
    cosbeta = (t[0] * t[2] + t[3] * t[5] + t[6] * t[8]) / (cell_lengths[0] * cell_lengths[2]);
    cosalpha = (t[1] * t[2] + t[4] * t[5] + t[7] * t[8]) / (cell_lengths[1] * cell_lengths[2]);

    cell_angles[0] = acos(cosalpha) * 180.0 / PI;
    cell_angles[1] = acos(cosbeta) * 180.0 / PI;
    cell_angles[2] = acos(cosgamma) * 180.0 / PI;

However, I myself do not use netcdf at all, so I am not in a position to test and correct it. Could you try it out? Otherwise, I can try to get help from some collaborators to check it.

bananenpampe commented 9 months ago

Hello, thank you for the suggestions, yes, I have given it a look: This is what I tried:

   const double* t = box.cpu_h;
    double cosgamma, cosbeta, cosalpha;
    double normal_ab[3];
    double normal_ac[3];
    double normal_bc[3];
    double mag_normal_ab, mag_normal_ac, mag_normal_bc;

    cell_lengths[0] = sqrt(t[0] * t[0] + t[3] * t[3] + t[6] * t[6]); // a-side
    cell_lengths[1] = sqrt(t[1] * t[1] + t[4] * t[4] + t[7] * t[7]); // b-side
    cell_lengths[2] = sqrt(t[2] * t[2] + t[5] * t[5] + t[8] * t[8]); // c-side

    // from the AMBER docs:
    // alpha defines the angle between the a-b and a-c planes
    // beta defines the angle between the a-b and b-c planes
    // gamma defines the angle between the a-c and b-c planes.

    normal_ab[0] = t[3] * t[7] - t[6] * t[4];
    normal_ab[1] = t[6] * t[1] - t[0] * t[7];
    normal_ab[2] = t[0] * t[4] - t[3] * t[1];

    normal_ac[0] = t[3] * t[8] - t[6] * t[5];
    normal_ac[1] = t[6] * t[2] - t[0] * t[8];
    normal_ac[2] = t[0] * t[5] - t[3] * t[2];

    normal_bc[0] = t[4] * t[8] - t[7] * t[5];
    normal_bc[1] = t[7] * t[2] - t[1] * t[8];
    normal_bc[2] = t[1] * t[5] - t[4] * t[2];

    mag_normal_bc = sqrt(normal_bc[0] * normal_bc[0] + normal_bc[1] * normal_bc[1] + normal_bc[2] * normal_bc[2]);
    mag_normal_ac = sqrt(normal_ac[0] * normal_ac[0] + normal_ac[1] * normal_ac[1] + normal_ac[2] * normal_ac[2]);
    mag_normal_ab = sqrt(normal_ab[0] * normal_ab[0] + normal_ab[1] * normal_ab[1] + normal_ab[2] * normal_ab[2]);

    cosgamma = (normal_bc[0] * normal_ac[0] + normal_bc[1] * normal_ac[1] + normal_bc[2] * normal_ac[2]) / (mag_normal_bc * mag_normal_ac);
    cosbeta = (normal_bc[0] * normal_ab[0] + normal_bc[1] * normal_ab[1] + normal_bc[2] * normal_ab[2]) / (mag_normal_bc * mag_normal_ab);
    cosalpha = (normal_ac[0] * normal_ab[0] + normal_ac[1] * normal_ab[1] + normal_ac[2] * normal_ab[2]) / (mag_normal_ac * mag_normal_ab);

    cell_angles[0] = acos(cosalpha) * 180.0 / PI;
    cell_angles[1] = acos(cosbeta) * 180.0 / PI;
    cell_angles[2] = acos(cosgamma) * 180.0 / PI;

Unfortunately, this does not fix the issue, is there maybe some convention in how the cell is defined that I am missing?

brucefan1983 commented 9 months ago

OK, the major problem is to figure out what conventions AMBER NetCDF uses. The manual is not very clear to me. At least I don't understand why it uses such complicated angles. Is there any reference source code?

brucefan1983 commented 9 months ago

Could you try this out:

const double* t = box.cpu_h;

const double a[3] = {t[0], t[3], t[6]};
const double b[3] = {t[1], t[4], t[7]};
const double c[3] = {t[2], t[5], t[8]};

const double axb = {a[1]*b[2]-a[2]*b[1], a[2]*b[0]-a[0]*b[2], a[0]*b[1]-a[1]*b[0]};
const double axc = {a[1]*c[2]-a[2]*c[1], a[2]*c[0]-a[0]*c[2], a[0]*c[1]-a[1]*c[0]};
const double bxc = {b[1]*c[2]-b[2]*c[1], b[2]*c[0]-b[0]*c[2], b[0]*c[1]-b[1]*c[0]};

const double len_axb = sqrt(axb[0]*axb[0] +  axb[1]*axb[1]  + axb[2]*axb[2]);
const double len_axc = sqrt(axc[0]*axc[0] +  axc[1]*axc[1]  + axc[2]*axc[2]);
const double len_bxc = sqrt(bxc[0]*bxc[0] +  bxc[1]*bxc[1]  + bxc[2]*bxc[2]);

const double cos_alpha = (axb[0] * axc[0] + axb[1] * axc[1] + axb[2] * axc[2]) / (len_axb * len_axc);
const double cos_beta = -(axb[0] * bxc[0] + axb[1] * bxc[1] + axb[2] * bxc[2]) / (len_axb * len_bxc);
const double cos_gamma = (axc[0] * bxc[0] + axc[1] * bxc[1] + axc[2] * bxc[2]) / (len_axc * len_bxc);

cell_lengths[0] = sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]);
cell_lengths[1] = sqrt(b[0] * b[0] + b[1] * b[1] + b[2] * b[2]);
cell_lengths[2] = sqrt(c[0] * c[0] + c[1] * c[1] + c[2] * c[2]);

cell_angles[0] = acos(cos_alpha) * 180.0 / PI;
cell_angles[1] = acos(cos_beta) * 180.0 / PI;
cell_angles[2] = acos(cos_gamma) * 180.0 / PI;