-
Notifications
You must be signed in to change notification settings - Fork 97
Ultrasoft pseudopotentials #1125
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,5 +45,57 @@ function ylm_real(l::Integer, m::Integer, rvec::AbstractVector{T}) where {T} | |
| (m == 3) && return sqrt( 35 / 32T(π)) * (x^2 - 3y^2) * x / r^3 | ||
| end | ||
|
|
||
| if l == 4 | ||
| (m == -4) && return sqrt( 315 / 16T(π)) * x * y * (x^2 - y^2) / r^4 | ||
| (m == -3) && return sqrt( 315 / 32T(π)) * y * (3x^2 - y^2) * z / r^4 | ||
| (m == -2) && return sqrt( 45 / 16T(π)) * x * y * (7z^2 - r^2) / r^4 | ||
| (m == -1) && return sqrt( 45 / 32T(π)) * y * (7z^3 - 3*z*r^2) / r^4 | ||
| (m == 0) && return sqrt( 9 /256T(π)) * (35z^4 - 30*z^2*r^2 + 3*r^4) / r^4 | ||
| (m == 1) && return sqrt( 45 / 32T(π)) * x * (7z^3 - 3*z*r^2) / r^4 | ||
| (m == 2) && return sqrt( 45 / 64T(π)) * (x^2 - y^2) * (7z^2 - r^2) / r^4 | ||
| (m == 3) && return sqrt( 315 / 32T(π)) * x * (x^2 - 3y^2) * z / r^4 | ||
| (m == 4) && return sqrt( 315 /256T(π)) * (x^2 * (x^2 - 3y^2) - y^2 * (3x^2 - y^2)) / r^4 | ||
| end | ||
|
|
||
| error("The case l = $l and m = $m is not implemented") | ||
| end | ||
|
|
||
| function pack_lm(l::Integer, m::Integer) | ||
| @assert 0 ≤ l | ||
| @assert -l ≤ m ≤ l | ||
| return l^2 + l + m + 1 | ||
| end | ||
|
|
||
| # TODO: check this | ||
| function unpack_lm(lm::Integer) | ||
| @assert lm ≥ 1 | ||
| l = floor(Int, sqrt(lm - 1)) | ||
| m = lm - (l^2 + l + 1) | ||
| return l, m | ||
| end | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. define in the same vein a nharm(l) function or something like that (to have at all the same place the details of the isomorphism from (l,m) to lm) |
||
|
|
||
| function gaunt_coefficients(T::Type, l::Integer) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This algorithm is weird (but I know nothing about spherical harmonics), is there no deterministic way to get those? Add a ref at least.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is inspired by what QE does (it's probably the exact same thing?). The idea is that since spherical harmonics are a basis (of functions), randomly evaluating them should also give a basis (of finite dimensional vectors). But with finite dimensional vectors it's just a simple matrix inversion to recover the basis coefficients. We could also compute the coefficients directly but it's more technical (two Clebsch-Gordan coefficients - can be computed with WignerSymbols.jl but still annoying :/), especially with real spherical harmonics for which the formulas are apparently slightly different. For sure this needs a bit of thought wrt. condition numbers, maybe adding a few extra points, etc... And certainly worth some unit tests as well. |
||
| nharm = (2l+1)^2 | ||
| # Sample points randomly on a sphere | ||
| rng = Xoshiro(42) | ||
| points = rand(rng, T, 3, nharm) | ||
| points ./= sqrt.(sum(points.^2, dims=1)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we have a columnwise_norm function now I think |
||
| # Evaluate spherical harmonics at these points | ||
| Y = zeros(T, nharm, nharm) | ||
| for j in 1:nharm | ||
| for i in 1:nharm | ||
| Y[i, j] = ylm_real(unpack_lm(j)..., points[:, i]) | ||
| end | ||
| end | ||
| fact = factorize(Y) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd possibly be concerned about the possibility of degeneracy here (ie the potentially bad conditioning), but maybe it's very rare. Can you make sure the rng you use is not subject to changes in future versions of julia? Then you can just run this function with various seeds for various l, and pick a seed such that the condition number is mild. (Otherwise you can maybe get something more robust by doing a rectangular method (ie fitting the gaunt coefficients least squares, with M observations for N unknowns, M >= N)) |
||
|
|
||
| coefs = zeros(T, nharm, (l+1)^2, (l+1)^2) | ||
|
|
||
| for j in 1:(l+1)^2 | ||
| for i in 1:(l+1)^2 | ||
| coefs[:, i, j] = fact \ (Y[:, i] .* Y[:, j]) | ||
| end | ||
| end | ||
|
|
||
| coefs | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,7 +42,51 @@ using an optional `occupation_threshold`. By default all occupation numbers are | |
| end | ||
| ρ = sum(getfield.(storages, :ρ)) | ||
|
|
||
| # Hacky augmentation charges | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need some kind of extra_density mechanism to handle core correction and ultrasoft in the same framework
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (which could be part of the terms interface, so that densities.jl calls out to all the terms to ask them for their contribution to the density)
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-linear core correction (NLCC) is exclusive to the exchange-correlation term, so it's currently implemented in the Xc term only (and was already done like this before this PR). (In some papers you really see it as a separate density that is added only in the XC functional like You can really see US as separating local "hard" contributions to the density; and "soft" contributions from the orbitals. So the real physical charge density (as used for hartree, local potentials, xc) needs both the soft contribution from the orbitals and the hard contribution from the augmentation charges. But we might want the two parts of the density somewhat separate in case it makes sense for preconditioning or other cases. |
||
| ρ_augmentation = zero(ρ) | ||
| nonloc_ops = AtomicNonlocal()(basis).ops | ||
| aug_coeffs = zeros(Complex{T}, 36, 36) | ||
| for (ik, n) in range | ||
| P = nonloc_ops[ik].P | ||
|
|
||
| projections = P' * ψ[ik][:, n] | ||
| for iatom in 1:length(basis.model.positions) | ||
| # TODO: only loop over iproj:18? | ||
| for iproj=1:18, jproj=1:18 | ||
| coeff = projections[(iatom-1) * 18 + iproj]' * | ||
| projections[(iatom-1) * 18 + jproj] | ||
| aug_coeffs[(iatom-1) * 18 + iproj, (iatom-1) * 18 + jproj] += | ||
| (real(coeff) * occupation[ik][n] # TODO: is the real call correct? | ||
| * basis.kweights[ik]) | ||
| end | ||
| end | ||
| end | ||
|
|
||
| # TODO: hardcoded for the Si psp I have | ||
| psp = basis.model.atoms[1].psp | ||
| # proj_indices = [1, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6] | ||
| # proj_to_m = [0, 0, -1, 0, 1, -1, 0, 1, -2, -1, 0, 1, 2, -2, -1, 0, 1, 2] | ||
| proj_indices = [1, 2, 3, 4, 3, 4, 3, 4, 5, 6, 5, 6, 5, 6, 5, 6, 5, 6] | ||
| proj_to_m = [0, 0, -1, -1, 0, 0, 1, 1, -2, -2, -1, -1, 0, 0, 1, 1, 2, 2] | ||
|
|
||
| for iatom in 1:length(basis.model.positions) | ||
| box = basis.augmentation_regions[iatom] | ||
| @info "Looping over grid for atom $iatom: box size $(length(box.grid_indices))" | ||
| for (xyz, Q) in zip(box.grid_indices, eachslice(box.augmentations; dims=1)) | ||
| tot = zero(T) | ||
| # TODO: only loop over iproj:18? | ||
| for iproj=1:18, jproj=1:18 | ||
| tot += aug_coeffs[(iatom-1) * 18 + iproj, (iatom-1) * 18 + jproj] * Q[iproj, jproj] | ||
| end | ||
| # TODO: assumes 1 spin channel | ||
| ρ_augmentation[xyz..., 1] += tot | ||
| end | ||
| end | ||
|
|
||
| ρ += ρ_augmentation | ||
|
|
||
| mpi_sum!(ρ, basis.comm_kpts) | ||
| # TODO: is this fine to do after the charge augmentation? | ||
| ρ = symmetrize_ρ(basis, ρ; do_lowpass=false) | ||
|
|
||
| # There can always be small negative densities, e.g. due to numerical fluctuations | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,28 @@ function diagonalize_all_kblocks(eigensolver, ham::Hamiltonian, nev_per_kpoint:: | |
| kpoints = ham.basis.kpoints | ||
| results = Vector{Any}(undef, length(kpoints)) | ||
|
|
||
| # TODO: temporary plumbing :P | ||
| B = [I for _ in kpoints] | ||
| if any(ham.basis.model.atoms) do el el isa ElementPsp && el.psp isa PspUpf && el.psp.type == "US" end | ||
| model = ham.basis.model | ||
|
|
||
| # keep only pseudopotential atoms and positions | ||
| psp_groups = [group for group in model.atom_groups | ||
| if model.atoms[first(group)] isa ElementPsp] | ||
| psps = [model.atoms[first(group)].psp for group in psp_groups] | ||
| psp_positions = [model.positions[group] for group in psp_groups] | ||
|
|
||
| # We have an overlap term | ||
| B = map(ham.blocks) do block | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @antoine-levitt this is my hacky implementation of the overlap term. It has the same structure as the nonlocal term (nonlocal is PDP'; overlap is I + PQP'), just with a different matrix in the center: replace D by Q. (Right now this Q matrix comes straight from the PP_Q in the pseudopotential file, but we will likely want to integrate the discretized augmentation charges to ensure that we get the right number of electrons in the integrated density.) Btw it's nice that LOBPCG "just works" with the overlap B. :)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, somehow I missed that. |
||
| op = block.nonlocal_op | ||
| Q = build_projection_coefficients(eltype(ham.basis), psps, psp_positions, "Q") | ||
| LinearMap(size(op.P, 1)) do Sψ, ψ | ||
| mul!(Sψ, op.P, (Q * (op.P' * ψ)), 1, 0) | ||
| Sψ .+= ψ # + identity | ||
| end | ||
| end | ||
| end | ||
|
|
||
| for (ik, kpt) in enumerate(kpoints) | ||
| n_Gk = length(G_vectors(ham.basis, kpt)) | ||
| if n_Gk < nev_per_kpoint | ||
|
|
@@ -48,7 +70,7 @@ function diagonalize_all_kblocks(eigensolver, ham::Hamiltonian, nev_per_kpoint:: | |
| prec = nothing | ||
| !isnothing(prec_type) && (prec = prec_type(ham[ik])) | ||
| results[ik] = eigensolver(ham[ik], ψguessk; | ||
| prec, tol, miniter, maxiter, n_conv_check) | ||
| B=B[ik], prec, tol, miniter, maxiter, n_conv_check) | ||
| end | ||
|
|
||
| # Transform results into a nicer datastructure | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isqrt?