Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Surface forces #488

Open
wants to merge 102 commits into
base: master
Choose a base branch
from
Open

Conversation

moritzgubler
Copy link
Contributor

@moritzgubler moritzgubler commented Jun 12, 2024

This is my implementation of the idea I had of calculating forces with surface integrals. It works quite well and the forces are more accurate. Also, mrchem struggles to compute forces when the world precision is 1e-7 or smaller. Then it requires a lot of memory (more than 32 GB for a H2 molecule). This is not the case with my approach . Geometry optimizations seem to be reliable if the stopping criterion is chosen 10 * world_prec.

I have already put all the parameters into the parser, so it is quite simple to test and run my code (look for the section "- name: Forces" in the template.yaml input parser file).

At the moment, there is two things that need to be improved:

  1. I did not figure out a way to get access to the exchange correlation density and potential, so I had to change the xc_operator classes a bit in order to get access to them. It works now, but I did it a bit hacky.
  2. I need the charge density, the electrostatic potential and the exchange correlation density and potential (the total one in case of an unrestricted calculation) to calculate the forces. A lot of the time when calculating the forces is spent recalculating these objects because I did not figure out a clean way to reuse them from the last SCF iteration. The time savings will probably not impact the entire SCF calculation significantly.
  3. At the moment the method works only for LDA (this is also why some tests are failing). Extending the method to GGA would be super easy, the XC stress for GGAs is given here: Evaluation of exchange-correlation energy, potential, and stress Phys. Rev. B 64, 165110 – Published 4 October 2001
    I am not sure how to calculate the exchange stress for hartree fock exchange. It shouldn't be too complicated but in a quick search I did not find a reference that derives it.

@stigrj Do you think my changes in the xc operator classes are fine.

Copy link

codecov bot commented Jul 31, 2024

Codecov Report

Attention: Patch coverage is 2.54735% with 1492 lines in your changes missing coverage. Please review.

Project coverage is 62.59%. Comparing base (df39ee2) to head (13b89d8).
Report is 2 commits behind head on master.

Files Patch % Lines
src/surface_forces/SurfaceForce.cpp 0.00% 220 Missing ⚠️
src/surface_forces/xcStress.cpp 0.00% 125 Missing ⚠️
src/surface_forces/detail/lebedev_utils.cpp 0.00% 107 Missing ⚠️
src/surface_forces/LebedevData.cpp 0.00% 75 Missing ⚠️
src/surface_forces/lebedev.cpp 0.00% 36 Missing ⚠️
src/surface_forces/detail/Lebedev_021_0170.hpp 0.00% 33 Missing ⚠️
src/surface_forces/detail/Lebedev_023_0194.hpp 0.00% 33 Missing ⚠️
src/surface_forces/detail/Lebedev_027_0266.hpp 0.00% 33 Missing ⚠️
src/surface_forces/detail/Lebedev_035_0434.hpp 0.00% 33 Missing ⚠️
src/surface_forces/detail/Lebedev_047_0770.hpp 0.00% 33 Missing ⚠️
... and 31 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #488      +/-   ##
==========================================
- Coverage   68.66%   62.59%   -6.08%     
==========================================
  Files         194      233      +39     
  Lines       15287    16790    +1503     
==========================================
+ Hits        10497    10509      +12     
- Misses       4790     6281    +1491     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@moritzgubler moritzgubler marked this pull request as ready for review August 1, 2024 14:36
@moritzgubler
Copy link
Contributor Author

Hi @robertodr @ilfreddy
I implemented now everything I wanted to, cleaned up to code and added a bit of documentation. The new method to calculate the forces now works for LDA and GGA functionals for both restricted and unrestricted calculations. The pulll request is now ready for review.
Best, Moritz

@moritzgubler
Copy link
Contributor Author

I previously tested GGA calculations only for H2 where the forces where the forces were correct. In more complicated systems this is not the case. (LDA is always correct). I will look into it and let you know once I have solved the problem.

@moritzgubler
Copy link
Contributor Author

The GGA bug is fixed now, I forgot to add the divergence terms in the xc potential for the GGA case. They have been added now.
MPI does not work anymore, it segfaults in the calculation of the kinetic stress. I suspect, that I try to access orbitals in an orbitalvector that are stored on another rank. @gitpeterwind Do you know what could be the problem?

Comment on lines +192 to +194
n1 = nablaPhi[iOrb][0].real().evalf(pos);
n2 = nablaPhi[iOrb][1].real().evalf(pos);
n3 = nablaPhi[iOrb][2].real().evalf(pos);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gitpeterwind These are the lines that cause the segfault

Copy link
Member

@gitpeterwind gitpeterwind Aug 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an OrbitalVector, not all orbitals are directly available to all MPI processes. You should do something like:

     if (mrcpp::mpi::my_orb(phi_i)) {
           pos[0] = gridPos(i, 0);
            pos[1] = gridPos(i, 1);
            pos[2] = gridPos(i, 2);
            n1 = nablaPhi[iOrb][0].real().evalf(pos);
            n2 = nablaPhi[iOrb][1].real().evalf(pos);
            n3 = nablaPhi[iOrb][2].real().evalf(pos);
            stress[i](0, 0) -= occ * n1 * n1;
            stress[i](1, 1) -= occ * n2 * n2;
            stress[i](2, 2) -= occ * n3 * n3;
            stress[i](0, 1) -= occ * n1 * n2;
            stress[i](0, 2) -= occ * n1 * n3;
            stress[i](1, 2) -= occ * n2 * n3;
           }
         }
    }
And then use      mrcpp::mpi::allreduce_vector to collect all the results. It is a bit difficult with the way you have defined the stress vector. But if you cannot easily define it as a DoubleVector, or DoubleMatrix , I can try to give a detailed way to do it (one way is simply to copy all the values into a DoubleVector, the do the allreduce operation, and copy back). 

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, thanks for your quick answer. I will use arrays for the stress tensor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gitpeterwind I think it might be a bit more complicated, I need

            nablaPhi[iOrb][0]
            nablaPhi[iOrb][1]
            nablaPhi[iOrb][2]

all on the same rank. How can I make sure that this is the case?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is correct: the last [0],[1],[2] picks out one OrbitalVector, and iOrb only determines the rank. Or did I misunderstood?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, nablaPhi[0][0] contains the x component of the gradient of the zeroth orbital. nablaPhi[0] is an orbital vector that contains the x, y and z component of the gradient of orbital 0.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding the following print to the loop

    for (int i = 0; i < Phi.size(); i++) {
        nablaPhi.push_back(nabla(Phi[i]));
        nablaPhi[i].distribute();
        if (mrcpp::wrk_rank == 0){
            std::cout << "gradient of orbital " << i << " stored on rank: x: " << nablaPhi[i][0].getRank() << " y: " << nablaPhi[i][1].getRank() << " z: " << nablaPhi[i][2].getRank() << std::endl;
        }
    }

results in:

gradient of orbital 0 stored on rank: x: 0 y: 1 z: 2
gradient of orbital 1 stored on rank: x: 0 y: 1 z: 2
gradient of orbital 2 stored on rank: x: 0 y: 1 z: 2
gradient of orbital 3 stored on rank: x: 0 y: 1 z: 2

but I need all components of the gradient on the same rank

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OrbitalVector class expects some properties, like that the size is the "usual" number of orbitals, and that each MPI process only access its own indices. If a vector has only have 3 orbitals, you should not define it as an OrbitalVector (and "distribute them is meaningless). Can you simply define std::vector<std::vector<Orbital>> nablaPhi(3); ? or do you need some OrbitalVector specific properties? Still Phi[i] will only be defined on the MPI process with the right rank.
Anyway, if you use the if (mrcpp::mpi::my_orb(phi_i)) { , do you still get the seg fault?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good idea. I thought that this wouldn't work because nabla(Phi) returns an orbitalvector. I tried your suggestion and it compiled. I will try to solve the problem like that, thanks for your help

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a bug in the master code. A PR has been sent

Comment on lines 286 to 291
std::vector<mrchem::OrbitalVector> nablaPhi;
mrchem::OrbitalVector hessRho = hess(rho);
for (int i = 0; i < Phi.size(); i++) {
nablaPhi.push_back(nabla(Phi[i]));
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I create the vector that contains the gradient of all orbitals

Copy link
Member

@gitpeterwind gitpeterwind Aug 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is necessary (at least it won't harm) to add:

std::vector<mrchem::OrbitalVector> nablaPhi;
    mrchem::OrbitalVector hessRho = hess(rho);
    for (int i = 0; i < Phi.size(); i++) {
        nablaPhi.push_back(nabla(Phi[i]));
    }
    nablaPhi.distribute();

( distribute will tell each element in the vector which rank it has)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, i will add this

@moritzgubler
Copy link
Contributor Author

I can recreate the error with this input:

world_prec = 1.0e-4
world_unit = bohr

WaveFunction {
  method = blyp
  restricted = false
}

Properties {
  geometric_derivative = true
}
Forces {
    method = "surface_integrals"
    #method = 'hellmann_feynman'
    surface_integral_precision = medium
}

Molecule {
$coords
H 0.86387418 -0.02789966 0.13501906
Li -0.44463806 -0.04858572 -0.04959119
$end
}

I start the simulation with:

mrchem --dryrun hli.inp
mpirun -np 4 mrchem.x hli.json

@ilfreddy
Copy link
Contributor

ilfreddy commented Aug 7, 2024

@moritzgubler would you mind adding also some testing to your code? According to the code coverage report, your patch is only marginally covered. Tests are essential to make sure the code does not get broken by others later on.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some class documentation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ilfreddy I added some documentation

Copy link
Contributor

@ilfreddy ilfreddy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a general comment from my side: could you add some extra testing:

  1. There is no unit-testing as far as I can see
  2. There is only one regression test

Some classes could also benefit from some additional documentation.

As for the details of the code, I guess it is best if Stig or Peter comment on those.

@moritzgubler
Copy link
Contributor Author

I added a test, is there a way to look at the codecov report?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants