-
Notifications
You must be signed in to change notification settings - Fork 6
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
Redesign methods and structures for multi-element and IAP combinations #14
Comments
I don't fully understand the struct HybridPotential <: ArbitraryPotential
interactions::Dict{Set{Symbol}, EmpericalPotential}
rcutoff::AbstractFloat
end
get_potential(p::HybridPotential, A::AbstractSystem, i::Integer, j::Integer)
p.interactions[Set([atomic_species(A, ii), atomic_species(A, jj)])]
end
function force(A::AbstractSystem, p::HybridPotential)
f = fill(zeros(3), length(A))
nnlist = neighborlist(A, p.rcutoff)
for ii in 1:length(A)
j = nnlist.j[ii]
r = nnlist.r[ii]
R = nnlist.R[ii]
for (jj, rj, Rj) in zip(j, r, R)
fo = force(Rj, rj, get_potential(p, A, ii, jj))
f[ii] += fo
f[jj] -= fo
end
end
SVector{3}.(f)
end
function potential_energy(A::AbstractSystem, p::HybridPotential)
e = 0.0
nnlist = neighborlist(A, p.rcutoff)
for ii in 1:length(A)
j = nnlist.j[ii]
R = nnlist.R[ii]
for (jj, Rj) in zip(j, R)
e += potential_energy(Rj, get_potential(p, A, ii, jj))
end
end
e
end
function virial(A::AbstractSystem, p::HybridPotential)
v = virial_stress(A, p)
v[1] + v[2] + v[3]
end
function virial_stress(A::AbstractSystem, p::HybridPotential)
v = zeros(6)
nnlist = neighborlist(A, p.rcutoff)
for ii in 1:length(A)
r = nnlist.r[ii]
R = nnlist.R[ii]
for (rj, Rj) in zip(r, R)
fo = force(Rj, rj, get_potential(p, A, ii, jj))
vi = rj * fo'
v += [vi[1, 1], vi[2, 2], vi[3, 3], vi[3, 2], vi[3, 1], vi[2, 1]]
end
end
SVector{6}(v)
end This is just off the top of my head, so the names could probably use some work. I could also come up with a way to dispatch things to avoid having the duplicated code (basically something along the lines of make this the default implementation for all So then the usage would be something like this: GaN = HybridPotential(
Dict(
Set([:Ga, :Ga]) => _____,
Set([:N, :N]) => _____,
Set([:Ga, :N]) => _____
),
rcut
) If you don't want all the initialization syntax, we could probably add some sort of macro, though I'll admit that isn't something I have experience with. Does this design accomplish the main things you are looking for? The one thing I can see it doesn't do is handle different rcut for each potential. If that is important we would have to change the order of steps (i.e. have separate ball trees for each pair of atom types (3 in the case of this example) -- this feels doable but would certainly add some more overhead). You can also pretty easily add a method of struct CombinedPotential{A<:ArbitraryPotential, B<:ArbitraryPotential} <: ArbitraryPotential
p1:A
p2:B
end
Base.:+(p1::A, p2::B) where {A <: ArbitraryPotential, B <: ArbitraryPotential} = CombinedPotential{A, B}(p1, p2)
force(R::AbstractFloat, r::SVector{3,<:AbstractFloat}, p::CombinedPotential) = force(R, r, p.p1) + force(R, r, p.p2)
potential_energy(R::AbstractFloat, p::CombinedPotential) = potential_energy(R, p.p1) + potential_energy(R, p.p2) That's the gist of it anyway. I'd have to look a bit more how you are doing things for types other than the If you want me to implement any of this stuff, just let me know and I'll gladly code it up in a PR. Reading your summary of the SNAP side of things, I have some thoughts about how you could go about it, but there is enough of a gap in my understanding that I'll hold off for now. If you want my (half-formed) thoughts on that, it will be easier to convey live when I can get some real time answers to fill in those gaps. |
Regarding the modeling of problems such as GaN, an alternative based on multiple dispatch could be the following: lj_Ga_Ga = LennardJones(ε_Ga, σ_Ga); lj_N_N = LennardJones(ε_N, σ_N)
c_Ga_Ga = Coulomb(q_Ga, q_Ga, e0); c_N_N = Coulomb(q_Ga, q_N, e0);
c_Ga_N = Coulomb(q_Ga, q_N, e0); bm_Ga_N = BornMayer(a, p)
function potential_energy(particle1::Ga, particle2::Ga)
r = position(particle1) - position(particle2)
return potential_energy(r,c_Ga_Ga) + potential_energy(r,lj_Ga)
end
function potential_energy(particle1::N, particle2::N)
r = position(particle1) - position(particle2)
return potential_energy(r,c_N_N) + potential_energy(r,lj_N)
end
function potential_energy(particle1::Ga, particle2::N)
r = position(particle1) - position(particle2)
return potential_energy(r,c_Ga_N) + potential_energy(r,bm_Ga_N)
end
function potential_energy(particle1::N, particle2::Ga)
return potential_energy(particle2, particle1)
end This would avoid directly modeling the GaN potential. |
If we need more flexibility, we can add one more parameter, something like this: struct GaN <: InteratomicPotential end
function potential_energy(particle1::Ga, particle2::Ga, p::GaN)
r = position(particle1) - position(particle2)
return potential_energy(r,c_Ga_Ga) + potential_energy(r,lj_Ga)
end
function potential_energy(particle1::Ga, particle2::Ga, p)
r = position(particle1) - position(particle2)
return potential_energy(r, p)
end If we need to compute SNAP plus ZBL: s = SNAP(...); z = ZBL(...)
p = potential_energy(particle1, particle2, s) + potential_energy(particle1, particle2, z) or p = sum( potential_energy.(particle1, particle2, [s, z]) ) That's what comes to my mind at the moment :) |
From having read this, I have to say I like @emmanuellujan's approach best. But I think before designing the interface we should think about a few crazy things one would want to be able to do and try to ensure the interface is able to do it, e.g.
Being able to have full flexibility in defining potentials is quite important in my opinion for our toolkit and as these examples illustrate combinatorial complexity quickly explodes, so I would not start writing such high-level macros or convenience functions until setting up the problem becomes a pain. Undoing these abstractions later is much harder than adding them. I often found (similar to what @emmanuellujan suggested) plain functions to be a good starting point. Why not just provide a good set of functions that are easy-to-use building blocks and leave the composition completely to the user (and ask him to define his types for that purpose)? The part I don't like about @emmanuellujan's approach that I am not so sure about is to use types to encode atoms. For me this is just "data" and not so much worth a type. Also I guess that leads to potentially a lot a lot of types (think about 4-body interactions for example). So perhaps just provide a Functor for each kind of interaction as basic building blocks (holding the state of the parameters) and then leave the combination of it all completely to the user. Then for standard tasks (i.e. one type of potential for each atom type and interaction) provide a small set of convenience |
Some of the more realistic use cases in the CESMIX universe are systems with multiple atom types (i.e., HfO2, HfB, Hydrogen/Palladium cluster). In such systems there are often 2 modeling choices that occur: (1) the forces between atoms is modeled as a combination of potentials (i.e., an empirical potential like ZBL and a basis potential like SNAP) and (2) the function that is used to calculate the force between two atoms (atom_i, atom_j) might depend on the elements of atom_i and atom_j (i.e., in the GaN example Ga-Ga, Ga-N, and N-N are all modeled differently).
In the hope of providing flexibility and ease of use for users of InteratomicPotentials.jl, I think there should be some mechanism available to easily describe the modeling choices of the system. An example could be something like what is implemented in Turing.jl:
With such a model, you could compute the energy and force like so:
There are some challenges with this approach. First, I have no idea how to implement such a thing in Julia (help?). Second, if a CompiledModel contained a BasisPotential that needed to be fitted (i.e., SNAP). It would be helpful to extract the descriptors associated with that potential. Normally you would invoke the code
But this could become more complicated take the following example:
This model only needs the single SNAP potential (not three separate SNAP potentials) and single Bessel potential. If I call the descriptors for this model, I want to make sure that they are separated into the SNAP descriptors and Bessel descriptors. Furthermore, for solving linear systems (those generated by SNAP, ACE, etc.), it is important to separate out the so-called 'reference' energies and forces provided by the empirical potentials from the contributions of the BasisPotentials.
The text was updated successfully, but these errors were encountered: