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

Adding a Glue Type / N-Body Empirical Potential - the Finnis-Sinclair 1984 potential for single element systems #32

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

Conversation

eschmidt42
Copy link

Glue: Finnis-Sinclair Potential

Hi, I'm new to Julia and was very happy to find that this Molly.jl repo exists. Playing with it (great documentation btw! 🙂) and wanting to simulate metals, I noticed potentials for metals seemed to be missing. So, I've written a small piece of code implementing one of the standard empirical potential types for metal, the Finnis-Sinclair empirical potential, for myself. btw thank you for documenting, how to best extend Molly.jl, so even newbies in Julia like myself can get started making custom changes 🙂. Metal potentials are not on your list of features you want to add as far as I can see, but I thought this still might be interesting for some users, hence this pull request.

Main Change

Note: The "glue" (kind of like local electron density, but not really) is a scalar pre-computed for each atom and is a required quantity to compute energies and forces. So simply iterating over atom pairs to compute energy and force contributions is insufficient, without having pre-computed/updaed the glue values. Hence, in a few places, the MD code needed to be adjusted to allow for this extra step.

Minor Changes

  • added a notebook fs.ipynb to document the usage of the added interaction type
  • modified accelerations in src/forces.jl: to enable glue calculation before pair contributions to the forces
  • modified potential_energy in src/analysis.jl to enable the use of a potential_energy function for the glue interaction type
  • modified src/loggers.jl by adding new loggers which proved useful for debugging: GlueDensityLogger, VelocityLogger, ForceLogger
  • modified Simulation in src/types.jl by adding glue_densities as an array where per-atom glue values are stored for the glue potential
  • added test/glue.jl as a new test to run to verify the correctness of the predicitons of potential energies, forces using reference values from the Finnis and Sinclair paper linked above as well as testing the behavior of the glue scalar values.

Note: A peculiarity of the Finnis-Sinclair potential is that the potential energy of an atom depends on the scalar glue value of that atom by taking the square root. Hence, cases in which the glue value, for whatever reason, becomes negative, will crash a simulation, since it is not defined in the model how to handle complex potential energy values. But negative glue values should really never occur, and if they do they indicate that something else is wrong with the simulation (crystal configured incorrectly or so). A test is present in test/glue.jl to verify that this doesn't happen due to algebraic sign errors or so in the implementation of the potential itself for a correctly configured simulation.

Next steps

Since this is a minimal first implementation of one empirical potential type for metals, which is only parameterized for single element systems (Fe, Nb, ...) there are a couple more things that could be done. The more relevant next steps could be to:

  • add shifted force / potential version of the glue potential
  • add parallelism for the glue computation
  • test CUDA compatibility
  • add other types of glue potentials, like the Embedded Atom method and
  • verify the correctness of the implementation for two, three and more element systems (the implemented Finnis-Sinclair paper only deals with single element systems).

…on of glue forces using the general_inters loop
…ivates with FordwardDiff.derivate to re-use the non-derivative forms also used to successfully test ground state energies
Merged small changes removing commented out lines
Copy link
Member

@SebastianM-C SebastianM-C left a comment

Choose a reason for hiding this comment

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

Hello! Thank you for contributing.
I personally was not aware of this kind of potentials, but it seems like an interesting thing to add.

I worked last summer (during Julia Summer of Code) on optimizing Molly for performance and I have a few suggestions that could help your implementation to be faster.
Regarding the documentation, I would suggest to add it to the docs folder in the format of Documenter pages since that way it would be more integrated with the rest of the package documentation.

Project.toml Outdated
KernelDensity = "5ab0869b-81aa-558d-bb23-cbf5423bbe9b"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
NearestNeighbors = "b8a86587-4115-5ab1-83bc-aa920d37bbce"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Copy link
Member

Choose a reason for hiding this comment

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

Plots should not be added as a direct dependency. Instead you can add it in the docs/Project.toml if you need to use the package for documentation purposes. In that case, you should also have the documentation prepared for Documenter.jl accordingly.

end


kb = 8.617333262145e-5 # eV/K
Copy link
Member

Choose a reason for hiding this comment

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

For performance reasons you should avoid global variables. You could make kb a parameter in the interaction as is done in the case of electrostatic interaction for example. See https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-global-variables

Project.toml Outdated
@@ -8,10 +8,14 @@ BioStructures = "de9282ab-8554-53be-b2d6-f6c222edabfc"
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
Distances = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
Crystal = "3c6eccdf-2a89-4c24-a1d4-ff210daa8476"
Copy link
Member

Choose a reason for hiding this comment

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

If you need some packages for tests, you should add them as test dependencies in the [extras] section, not as direct package dependencies.

element_pairings = [string(el, el) for el in elements]
element_pair_map = Dict(pair => i for (i,pair) in enumerate(element_pairings))

df = DataFrame(
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if adding the DataFrames.jl dependency if worth it just for this convenience functionality.


Derivative of the glue density function.
"""
∂glue_∂r(r, β, d) = ForwardDiff.derivative(r -> glue(r,β,d), r)
Copy link
Member

Choose a reason for hiding this comment

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

In this case I think that the derivative could be done analytically since the formula is not very complicated.

Project.toml Outdated
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
Copy link
Member

Choose a reason for hiding this comment

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

I don't think there is a need to use ForwardDiff for the derivatives introduced by this change. The derivatives expressions could be derived analytically and used directly. This might also be slightly more performant than doing them numerically.

src/loggers.jl Outdated

function ForceLogger(T, n_steps::Integer; dims::Integer=3)
return ForceLogger(n_steps,
Array{SArray{Tuple{dims}, T, 1, dims}, 1}[])
Copy link
Member

Choose a reason for hiding this comment

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

You could just use Vector{SVector{dims}} here for readability.

Suggested change
Array{SArray{Tuple{dims}, T, 1, dims}, 1}[])
Vector{SVector{dims}}[])

Comment on lines 19 to 20
element_pair_map::Dict
params::DataFrame
Copy link
Member

Choose a reason for hiding this comment

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

These are not a concrete types and will cause performance problems. I would recommend using a (parametrized) NamedTuple or another (concretely typed) struct instead. See https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-fields-with-abstract-type


Energy derivative given glue density.
"""
∂Uglue_∂ρ(ρ,A) = ForwardDiff.derivative(ρ -> Uglue(ρ,A), ρ)
Copy link
Member

Choose a reason for hiding this comment

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

This could be simply derived analytically

Suggested change
∂Uglue_∂ρ(ρ,A) = ForwardDiff.derivative-> Uglue(ρ,A), ρ)
∂Uglue_∂ρ(ρ,A) = -A /(2 * ρ)

src/forces.jl Outdated
end

Copy link
Member

Choose a reason for hiding this comment

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

Remove whitespace

Suggested change

@eschmidt42
Copy link
Author

Glad to hear this could be a relevant contribution and thank you for your suggestions 😃. I'll incorporate them them over the next couple of days.

@jgreener64
Copy link
Collaborator

Thanks for making this PR, and I'm glad you liked the package and docs. This potential would certainly be welcome in some form. As a first pass, addressing @SebastianM-C's useful comments would be good. In particular we should avoid adding any more dependencies unless they are very light or add something crucial.

More broadly though, I've been thinking about this PR over the last few days and how it backs up something we have discussed previously: Molly needs a "free-form" interaction type to go with SpecificInteraction and GeneralInteraction (which is really a pairwise interaction), where the new type has all atoms available to it. Then you could do the glue calculation with this interaction type and update some array in the interaction struct, or even update the properties of a custom atom type.

That way, you wouldn't need to change the force/energy calculation functions or modify the Simulation type, both of which I am uneasy with. In particular, code like if typeof(inter) <: GlueInteraction would be dealt with via dispatch rather than branching.

One of the principles of the package is that you should be able to implement custom potentials solely by adding your own interaction types. I'd rather add a new class of interactions which has all atoms available to facilitate that, rather than special casing the summation functions.

I might have a chance to implement such an interaction type over Easter, and if so will look to make it work with this example, at which point this PR could be reworked to fit on top of that. All ideas welcome of course.

test/runtests.jl Outdated
@@ -22,6 +22,8 @@ end

CUDA.allowscalar(false) # Check that we never do scalar indexing on the GPU

include("glue.jl")
Copy link
Member

Choose a reason for hiding this comment

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

You could also wrap the glue potential tests in a testset, so that it is more clear to identify them.

@jgreener64 I think it would also be a good idea to separate more of the tests in separate files and maybe use SafeTestsets.jl to ensure that we don't have variable definitions leaking from one test to another.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes probably a good idea.

return Upair(r, pij.c, pij.c₀, pij.c₁, pij.c₂)
end
end
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
end
end

Project.toml Outdated
ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
SingleCrystal = "3c6eccdf-2a89-4c24-a1d4-ff210daa8476"
Copy link
Member

Choose a reason for hiding this comment

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

Why is SingleCrystal added as a dependency of the package? Should it not be just a test dependency?

Copy link
Author

Choose a reason for hiding this comment

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

Yep, thanks, should be fixed, as soon as I push my changes.

params::DataFrame
pairs::Dict{String,FinnisSinclairPair}
singles::Dict{String,FinnisSinclairSingle}
kb::Real
Copy link
Member

Choose a reason for hiding this comment

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

You should either specify the type or parametrize it (which would be more general). Using abstract types in the fields of a struct can lead to performance problems. See https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-fields-with-abstract-containers

Copy link
Author

Choose a reason for hiding this comment

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

Thank you for the link :). Have read it but not sure my changes are correct. Current version better?^^

Comment on lines 21 to 23
A::Real
β::Real
d::Real
Copy link
Member

Choose a reason for hiding this comment

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

Using abstract types in the fields of a struct can lead to performance problems. You could parametrize the struct to solve this.

Comment on lines 13 to 16
c::Real
c₀::Real
c₁::Real
c₂::Real
Copy link
Member

Choose a reason for hiding this comment

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

Real is an abstract type and thus your structs will not be concrete and that will lead to performance problems.

Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Molly = "aa0f7f06-fcc0-5ec4-a7f3-a573f33f9c4c"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Copy link
Member

Choose a reason for hiding this comment

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

The plots in the documentation could be generated during the docs build, but it would be ideal to use the same package for all the plots (preferably one of the Makie backends). @jgreener64 One idea would be to use CairoMakie, but it might also be possible to use WGLMakie (it would be a bit more complex, but we get interactive plots). For this to work, the visualize function should be available with AbstractPlotting. A more elegant solution would be to use recipes, but AbstractPlotting is not a light dependency right now.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It would indeed be better to use the same plotting package throughout, though at the minute the code in the docs is not run during the docs build and Makie plots are pasted as images into the doc files.

It would be good if the docs notebook appeared in the docs - perhaps it could go in the examples section as markdown. Then these docs dependencies can be removed until we deal with doctests later, as the examples can assume that users have the package installed.

Copy link
Author

Choose a reason for hiding this comment

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

Tried using Makie before but had some problem I can't remember. Plots is mostly required for the docs/fs.ipynb notebook. Can try to replace Plots there though if required.

@@ -1,6 +1,10 @@
[deps]
Crystal = "3c6eccdf-2a89-4c24-a1d4-ff210daa8476"
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be SingleCrystal?

Suggested change
Crystal = "3c6eccdf-2a89-4c24-a1d4-ff210daa8476"
SingleCrystal = "3c6eccdf-2a89-4c24-a1d4-ff210daa8476"

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.

None yet

3 participants