Skip to content

[WIP] Additional exx kernels: Voxel averaged and Wigner-Seitz truncation#1282

Open
toschaefer wants to merge 11 commits into
JuliaMolSim:masterfrom
toschaefer:exx_kernels
Open

[WIP] Additional exx kernels: Voxel averaged and Wigner-Seitz truncation#1282
toschaefer wants to merge 11 commits into
JuliaMolSim:masterfrom
toschaefer:exx_kernels

Conversation

@toschaefer

@toschaefer toschaefer commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Two additional useful strategies for the Coulomb kernel for electron repulsion integrals (e.g. for exx).

Wigner-Seitz truncation
R. Sundararaman, T. A. Arias. Phys. Rev. B 87, 165122 (2013)

Voxel averaged (mean potential)
T. Schaefer et al, J. Chem. Phys. 160, 051101 (2024)
This requires Gauss-Legendre quadrature points, hence I added the package FastGaussQuadrature to the dependencies.

@antoine-levitt

Copy link
Copy Markdown
Member

Nice! Particularly interested in the Wigner-Seitz truncation - is there any reason to do anything else? It seems to me this is the superior approach? Do we really want to support the voxel averaged? Also maybe some opportunities to share code with ewald.jl, but I haven't looked too closely.

It'd be nice to have a generalized API for all these functions that return centered functions (we have a couple of those: densities guesses, local pseudos, projectors, coulomb kernels, etc.). I think long-term we want the ability to get their real-space and partial Fourier transforms (wrt 1 or 2 dimensions), so we can do eg 2D materials. But maybe it's not worth unifying since they have quite different requirements (numerical psp vs analytic but singular coulomb really are quite different computationally)

CC @denizunel

@mfherbst

Copy link
Copy Markdown
Member

Also maybe some opportunities to share code with ewald.jl,

I think so, but I'd keep that for later, same as the generalized API. I'm currently looking at this on the side to streamline the code a bit, will hopefully push a review in the next week.

@toschaefer

Copy link
Copy Markdown
Contributor Author

Nice! Particularly interested in the Wigner-Seitz truncation - is there any reason to do anything else? It seems to me this is the superior approach?

For EXX of gapped systems Wigner-Seitz truncation is probably the best approach to reach the thermodynamic llimit.
But for beyond-EXX stuff like many-electron correlation methods (MP2, CC, ...) it may not be quite as superior. In particular when we consider relatively small cells where a truncation is simply too rough (no matter if spehrically or Wigner-Seitz).

Do we really want to support the voxel averaged?

We need it for surface science (strongly anisotropic cells) with coupled cluster theory. Since our plan is to make DFTK a standard workflow, voxel averaged would be essential.
It would also be nice to have it for tests with the new (planned) mixers for SCF calculations with EXX. In particular for metals, voxel averaged could be advantageous.

@toschaefer

Copy link
Copy Markdown
Contributor Author

rebased to make merge easier

@antoine-levitt

Copy link
Copy Markdown
Member

Is this still WIP or ready for review?

@toschaefer

Copy link
Copy Markdown
Contributor Author

from my end, ready for review

@antoine-levitt antoine-levitt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Very nice!

Comment thread src/coulomb.jl Outdated
- [`SphericallyTruncatedCoulomb`](@ref): θ(R-r)/r
- [`WignerSeitzTruncatedCoulomb`](@ref): χ(r)/r where χ(r)=1 inside Wigner-Seitz cell, otherwise 0.

If an interaction model features a singularity, that requires some special treatment,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

no comma after singularity (germanism)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

features a singularity -> is long-range

Comment thread src/coulomb.jl Outdated
# where Γ is BZ volume) is used.
# (where Γ is BZ volume) is used.
# _compute_kernel_fourier(::InteractionKernel, basis, qpt, q)
# The single q-point version of compute_kernel_fourier

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

there's compute_ and eval_, should all these be merged?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(we really need to have a unified API for localized functions that can be evaluated in real or reciprocal space)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

also should we have a eval_kernel_real? This is often useful for debug purposes (can be left as TODO if you don't feel like doing it)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

compute_ sets up and returns the full vector with all the complexity of how the sampling points (including the singularity) are defined for each G+q while eval_ is more the mathematical evaluation of the kernel at a given point G+q.

I added a TODO for eval_kernel_real.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't get this. Both compute the same thing and should be consistent. If the point is that _compute_kernel_fourier(::InteractionKernel, basis, qpt, q) takes multiple kpoints then that should be a separate method of the same function. To be clear, I'd suggest

# computes khat(p). Not guaranteed to be fast
eval_kernel_fourier(::Coulomb, p) = 1/p^2

# eval_kernel_fourier computes khat(G+pshift) for all G in basis using a possibly faster algorithm than just looping over all G+pshift
# default implementation
function eval_kernel_fourier(k::InteractionKernel, basis, pshift)
    map(G_vectors(basis)) do G
        eval_kernel_fourier(G+pshift)
    end
end

function eval_kernel_fourier(k::WignerSeitz, p)
    r = eval_kernel_fourier(ShortRange(k.ω))
    # add to r the long-range by "slow Fourier transform", just looping over all r_vectors
end
function eval_kernel_fourier(k::WignerSeitz, pshift)
    # FFT based algorithm
end
# add a test that this overload is correct: for all kernels, the result eval_kernel_fourier(k,basis,pshift)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I see. I think there was a misleading comment. I updated the doc string for InteractionKernel to explain why we have eval_kernel_fourier and compute_kernel_fourier for the splitting: (i) formula and (ii) discretization/singularity handling.

@doc raw"""                                                                                                        
Abstract type for different interaction models.                                                                    
                                                                                                                   
### Architecture                                                                                                   
                                                                                                                   
Computing interaction kernels is split into two parts: the mathematical formula (e.g. 4\pi/G^2) and the grid discretization. This split is primarily driven by the need to handle singularities in long-range kernels.                
                                                                                                                   
1. **InteractionKernel:** Defines the pure mathematical formula (via `eval_kernel_fourier`).                       
2. **regularization:** Necessary for long-range kernels (like `Coulomb` and `LongRangeCoulomb`) diverge as ``G+q \to 0``. Evaluating them on a periodic grid requires a specific strategy to handle this divergence.                  
                                                                                                                   
Because of this divergence, long-range `InteractionKernel`s contain a `regularization` field to dictate how the ``G
+q=0`` component is built via `_compute_kernel_fourier`. Short-range kernels have a finite limit at `G+q \to 0``` a
nd don't need a regularizatin.                                                                                     
                                                                                                                   
### Available models:                                                                                              
- [`Coulomb`](@ref): 1/r                                                                                           
- [`ShortRangeCoulomb`](@ref): erfc(μr)/r                                                                          
- [`LongRangeCoulomb`](@ref): erf(μr)/r                                                                            
- [`SphericallyTruncatedCoulomb`](@ref): θ(R-r)/r                                                                  
- [`WignerSeitzTruncatedCoulomb`](@ref): χ(r)/r (1 inside Wigner-Seitz cell, 0 otherwise)                          
                                                                                                                   
### Available singularity corrections (regularizations):                                                           
- [`ProbeCharge`](@ref): Gygi-Baldereschi probe charge method                                                      
- [`ReplaceSingularity`](@ref): Set the G+q=0 component to a specific value                                        
- [`VoxelAveraged`](@ref): Average the continuous kernel over the Brillouin zone voxel                             
                                                                                                                   
See also: [`compute_kernel_fourier`](@ref)                                                                         
"""                                                                                                                
abstract type InteractionKernel end                                                                                
Base.Broadcast.broadcastable(k::InteractionKernel) = Ref(k)                                                        
                                                                                                                   
# TODO: should we have a eval_kernel_real?                                                                         
# TODO: rename "k" in _compute_kernel_fourier(k...                                                                 
# TODO: change notation: p instead of G, G+q, ...                                                                  

Comment thread src/coulomb.jl

See also: [`compute_kernel_fourier`](@ref)
"""
abstract type InteractionKernel end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AbstractCoulombKernel? Interaction seems a bit generic (these are all Coulomb-like)

@toschaefer toschaefer May 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

After some back-and-forth we called it InteractionKernel in #1223 (comment)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hm OK, fine.

Comment thread src/coulomb.jl Outdated
@@ -18,4 +21,4 @@

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we use relatively consistently (or at least we wanted to) p for the Fourier-space variable in \R^3 (so usually things like p=G+k, and q=k-k'). Why mention G at all in this file? Can't you just use p?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Principally, I am happy to follow your stylistic preferences. I don't think we've mentioned this yet during the EXX development, and if I shall change it, I would prefer to postpone it for now, since I'm already working on the next PR (which also includes some changes in src/coulomb.jl).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

OK, maybe leave a TODO? I think generally this file should be about just providing interaction(p), regardless of where p is coming from (G+q, G...)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

Comment thread src/coulomb.jl Outdated
@@ -26,7 +29,7 @@

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

give the equation defining this. Gsq -> p? Or ps?)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Gsq refers to G squared so psq? But I am not sure if I understand correctly...

Comment thread src/coulomb.jl
d_min = min(d_min, d)
end
if d_min > sqrt(eps(T))
V_lr_real[idx] = erf(ω * d_min) / d_min

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

as above, define a erf(x)/x function?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hm I think we should have a general way of doing f(x)/x stably and AD-friendly, this is coming up pretty frequently (also with @Technici4n in the spherical harmonics stuff)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah but of course it's my old friend the divided difference (high order in the case of f(x)/x^l). We have a general solution in https://github.com/xuequan818/MatrixFuns.jl but it might be a bit overkill...?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(the reason I want to get this right is otherwise it will come up to bite us in the ass with AD)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I want to do things like

function expm1_over_x(x)
    if abs(x) < 1000floatmin(Float64)
        x = 1000floatmin(Float64)
    end
    expm1(x) / x
end

but of course this screws up AD at 0. We can have a cutoff point and switch to a finite order taylor expansion. This is ugly and getting good precision of higher derivatives is annoying, but I'm not sure what else to do.

Copy link
Copy Markdown
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 extend the TODO comments by a general statement, that we need a clever and AD-friendly solution for a possible f(x)/x machinery.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Has this been already done ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes, below abstract type InteractionKernel end.

Comment thread src/coulomb.jl Outdated
V_lr_real = zeros(Complex{T}, basis.fft_size...)
for idx in CartesianIndices(V_lr_real)
r_frac = r_vectors[idx]
r_centered = r_frac .- round.(r_frac) # MIC

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

write it out explicitly (I had to think to get what it was referring to)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

Comment thread src/coulomb.jl Outdated
r_centered = r_frac .- round.(r_frac) # MIC
r_cart = model.lattice * r_centered
d_min = norm(r_cart)
for dx in -nx:nx, dy in -ny:ny, dz in -nz:nz # Check neighbors for non-orthorhombic cells

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have to say I don't understand what's going on here, why isn't this just erf(r)/r for r in r_vectors?, maybe factor out the geometry stuff to another function?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Took me a while to understand what I did here. Added better comments to make it understandable.

Comment thread src/coulomb.jl Outdated
Gnorm2 = sum(abs2, G_cart)
found_singularity = (iG==1 && iszero(q))
Rcut = cbrt(basis.model.unit_cell_volume*3/4/π)
if !found_singularity

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why don't you just write the condition here? Also why not iszero(G_cart)?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(also same comment as above, hopefully we can avoid this special casing)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok

Comment thread src/coulomb.jl
end
kernel_fourier
end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Running out of time today, note to self to resume the review from here.

@toschaefer

toschaefer commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review!
I have another branch for the EXX k-points feature waiting on this one, so I would love to get this merged soon to keep the rebase simple. Does everything look ready to go on your end?

@antoine-levitt

Copy link
Copy Markdown
Member

I'll try to get to it soon but it's pretty hectic at the moment, sorry... Since you're very active on this I don't want to be a blocker, but at the same time I think one of the key differentiator of DFTK is that we have relatively clean code (not really in the sense that we follow best practices, but rather in the sense that the code is relatively minimal and understandable), and code review really helps with that I find (in the sense that the code we let in has been understood at least once by at least two people).

I think the best way to do that is that you fork DFTK and make your changes there. Then, when we merge a PR on the main DFTK repo (or even along the way when you make commits to the PR), you merge it on to your fork. Since the git history is preserved I think the whole process should be relatively painless (in the sense that you shouldn't have to deal with much conflicts) and you don't have to wait on us to merge PRs to progress. (also AI made everything git much smoother, I find) In exchange I promise I'll get to it eventually in a reasonable timeframe. @mfherbst might have a better idea?

@toschaefer

Copy link
Copy Markdown
Contributor Author

I totally agree with all of that. That’s why DFTK is my choice ;)

Regarding the fork strategy, normally I do it exactly like that. In this case, I shot myself in the foot because I refactored the coulomb.jl files in both branches in a somewhat non-orthogonal way. This is why I put the k-points branch on hold until this one merged.

That’s the only reason I brought it up, but please don't stress. I appreciate you taking the time to review it properly when you find time.

@antoine-levitt antoine-levitt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

OK, I'm fine with merging the current code with lots of todo, to be adressed (by you, me or Claude :-p) at some point in the future. To me the API is the most pressing concern - it'd be nice to merge eval_kernel_fourier and _compute_kernel_fourier and clarify the relationship between the two.

Comment thread src/coulomb.jl

See also: [`compute_kernel_fourier`](@ref)
"""
abstract type InteractionKernel end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hm OK, fine.

Comment thread src/coulomb.jl Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

OK, maybe leave a TODO? I think generally this file should be about just providing interaction(p), regardless of where p is coming from (G+q, G...)

Comment thread src/coulomb.jl Outdated
# where Γ is BZ volume) is used.
# (where Γ is BZ volume) is used.
# _compute_kernel_fourier(::InteractionKernel, basis, qpt, q)
# The single q-point version of compute_kernel_fourier

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't get this. Both compute the same thing and should be consistent. If the point is that _compute_kernel_fourier(::InteractionKernel, basis, qpt, q) takes multiple kpoints then that should be a separate method of the same function. To be clear, I'd suggest

# computes khat(p). Not guaranteed to be fast
eval_kernel_fourier(::Coulomb, p) = 1/p^2

# eval_kernel_fourier computes khat(G+pshift) for all G in basis using a possibly faster algorithm than just looping over all G+pshift
# default implementation
function eval_kernel_fourier(k::InteractionKernel, basis, pshift)
    map(G_vectors(basis)) do G
        eval_kernel_fourier(G+pshift)
    end
end

function eval_kernel_fourier(k::WignerSeitz, p)
    r = eval_kernel_fourier(ShortRange(k.ω))
    # add to r the long-range by "slow Fourier transform", just looping over all r_vectors
end
function eval_kernel_fourier(k::WignerSeitz, pshift)
    # FFT based algorithm
end
# add a test that this overload is correct: for all kernels, the result eval_kernel_fourier(k,basis,pshift)

Comment thread src/coulomb.jl
end
ShortRangeCoulomb(; μ=0.2/u"Å") = ShortRangeCoulomb(austrip(μ))
ShortRangeCoulomb(μ::Quantity) = ShortRangeCoulomb(austrip(μ))
function eval_kernel_fourier(k::ShortRangeCoulomb, Gsq::T) where {T}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

sorry I really should have reviewed the previous PR...

Comment thread src/coulomb.jl
end
function LongRangeCoulomb(; μ=0.2/u"Å", regularization=ProbeCharge())
LongRangeCoulomb(austrip(μ), regularization)
end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@mfherbst I think we should keep the unit stuff away from the code as much as possible

Comment thread src/coulomb.jl
Comment thread src/coulomb.jl

@mfherbst mfherbst left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Some minor comments. Main point is still the missing test. I've added some ideas what one could test. Feel free to object and defer if you don't have the time now, but I think it will help in the long run to have such consistency tests.

Comment thread Project.toml Outdated
Comment thread src/coulomb.jl

### Architecture

Computing interaction kernels is split into two parts: the mathematical formula (e.g. 4\pi/G^2) and the grid discretization. This split is primarily driven by the need to handle singularities in long-range kernels.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Refer to the new issue here to make it clear this is not yet finalised.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

Comment thread src/coulomb.jl
Comment on lines -24 to -31
# Each InteractionKernel should support the following functions:
# eval_kernel_fourier(::InteractionKernel, Gsq)
# eval_probe_charge_integral(::InteractionKernel, α)
# Should return ∫_{BZ} kernel(q) * e^(-α * q^2) dq
# This is needed for the ProbeCharge regularisation. Note, that no factor 1/Γ
# where Γ is BZ volume) is used.
# _compute_kernel_fourier(::InteractionKernel, basis, qpt, q)
# The single q-point version of compute_kernel_fourier

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would not remove this, it's a pretty concise and compact overview of what needs to be done.

Comment thread src/coulomb.jl Outdated
Comment on lines +65 to +66
# TODO: introduce a clever and AD-friendly way to deal with f(x)/x for x->0. E.g. intoduce phi(x) = iszero(x) ? one(x) : expm1(x) / x
# Also very useful for other InteractionKernels.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This feels a bit out of place here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

moved it to the other TODOs below InteractionKernel

Comment thread src/coulomb.jl Outdated
# TODO: This is a bit hackish as the parameter needs to be re-computed every kernel
# evaluation. Cleaner would be to move this further up in the call hierarchy,
# such that compute_kernel_fourier is never called without Rcut being set to
# not nothing

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree with the first sentence, but I don't agree with the second (in the sense that this would be the solution. Maybe just have the first sentence and let us consider how to solve it when it actually becomes an issue. It does not cost that much compute after all ...

Comment thread src/coulomb.jl
d_min = min(d_min, d)
end
if d_min > sqrt(eps(T))
V_lr_real[idx] = erf(ω * d_min) / d_min

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Has this been already done ?

Comment thread src/coulomb.jl

# Use ReplaceSingularity regularisation to explicitly set as the G==0
# component the exact limit of the kernel for G->0
_compute_kernel_fourier(kRcut, ReplaceSingularity(2π*Rcut^2), basis, qpt, q)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Has this been already done ?

Comment thread test/coulomb.jl
k_wstrunc = compute_kernel_fourier(WignerSeitzTruncatedCoulomb(), basis)
E_wstrunc = exx_energy_only(basis, kpt, k_wstrunc, ψk_real, occk)
E_ref = -2.3456813523805415
@test abs(E_ref - E_wstrunc) < 1e-6

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it possible to add a test that this is somewhat related to SphericallyTruncatedCoulomb, e.g. test that we get similar entries in k_wstrunc versus k_strunc or that some behaviour between the two agrees ? One may need to set Rcut for the SphericallyTruncatedCoulomb to a special value for this (e.g. the actual size of the cell etc.), but that sounds fair to me.

A good way to test this could be to consider evaluating the kernel on cells of the form a * Diagonal([1, 1, 1]) with larger and larger values of a, I would expect the two implementations to agree more and more as we make a larger.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'll also add a few tests on non-cubic cells, just that we have something where Wigner-Seitz should make a real difference.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the non-cubic test.

I am not sure if the sampling points of the kernel of SphericallyTruncatedCoulomb and WignerSeitzTruncatedCoulomb will get more and more similar when you increase the volume of a cubic cell. When I look at the spherically truncated case, the cos function will oscillate wildly with increasing R: v(G) = 4π/G² * (1 - cos(GR)).
Wigner-Seitz will also oscillate like crazy but probably not in the same way.

In the limit, the physics will be the same, but the individual sampling points probably not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In the limit, the physics will be the same, but the individual sampling points probably not. But does this not imply that at least the energy should get similar ?

Well if it does not really work, than let's not get too hung up about it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, the energy should get similar, but in the limit this should hold for all kernels. Of course, converging at different rates...
So what you mean is to define a large a and then calculate exx_energy_only. The energies will agree to some extend and we simply define a test like @test E_wstrunc ≈ E_strunc atol=1e-3 with some apropriate atol?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, essentially this captures we get the right limit and that things converge at the right order (if we take two different as ... at least in theory

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.

3 participants