Skip to content

Commit 179a881

Browse files
committed
BlochWaves structure
1 parent 98964ba commit 179a881

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+262
-229
lines changed

examples/error_estimates_forces.jl

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ tol = 1e-5;
5454
# We compute the reference solution ``P_*`` from which we will compute the
5555
# references forces.
5656
scfres_ref = self_consistent_field(basis_ref; tol, callback=identity)
57-
ψ_ref = DFTK.select_occupied_orbitals(basis_ref, scfres_ref.ψ, scfres_ref.occupation).ψ;
57+
ψ_ref = DFTK.select_occupied_orbitals(scfres_ref.ψ, scfres_ref.occupation).ψ;
5858

5959
# We compute a variational approximation of the reference solution with
6060
# smaller `Ecut`. `ψr`, `ρr` and `Er` are the quantities computed with `Ecut`
@@ -69,16 +69,16 @@ Ecut = 15
6969
basis = PlaneWaveBasis(model; Ecut, kgrid)
7070
scfres = self_consistent_field(basis; tol, callback=identity)
7171
ψr = DFTK.transfer_blochwave(scfres.ψ, basis, basis_ref)
72-
ρr = compute_density(basis_ref, ψr, scfres.occupation)
73-
Er, hamr = energy_hamiltonian(basis_ref, ψr, scfres.occupation; ρ=ρr);
72+
ρr = compute_density(ψr, scfres.occupation)
73+
Er, hamr = energy_hamiltonian(ψr, scfres.occupation; ρ=ρr);
7474

7575
# We then compute several quantities that we need to evaluate the error bounds.
7676

7777
# - Compute the residual ``R(P)``, and remove the virtual orbitals, as required
7878
# in [`src/scf/newton.jl`](https://github.com/JuliaMolSim/DFTK.jl/blob/fedc720dab2d194b30d468501acd0f04bd4dd3d6/src/scf/newton.jl#L121).
79-
res = DFTK.compute_projected_gradient(basis_ref, ψr, scfres.occupation)
80-
res, occ = DFTK.select_occupied_orbitals(basis_ref, res, scfres.occupation)
81-
ψr = DFTK.select_occupied_orbitals(basis_ref, ψr, scfres.occupation).ψ;
79+
res = DFTK.compute_projected_gradient(ψr, scfres.occupation)
80+
res, occ = DFTK.select_occupied_orbitals(BlochWaves(ψr.basis, res), scfres.occupation)
81+
ψr = DFTK.select_occupied_orbitals(ψr, scfres.occupation).ψ;
8282

8383
# - Compute the error ``P-P_*`` on the associated orbitals ``ϕ-ψ`` after aligning
8484
# them: this is done by solving ``\min |ϕ - ψU|`` for ``U`` unitary matrix of
@@ -149,7 +149,7 @@ Mres = apply_metric(ψr.data, P, res, apply_inv_M);
149149

150150
# - Compute the projection of the residual onto the high and low frequencies:
151151
resLF = DFTK.transfer_blochwave(res, basis_ref, basis)
152-
resHF = res - DFTK.transfer_blochwave(resLF, basis, basis_ref);
152+
resHF = denest(res) - denest(DFTK.transfer_blochwave(resLF, basis, basis_ref));
153153

154154
# - Compute ``{\boldsymbol M}^{-1}_{22}R_2(P)``:
155155
e2 = apply_metric(ψr, P, resHF, apply_inv_M);
@@ -163,15 +163,15 @@ e2 = apply_metric(ψr, P, resHF, apply_inv_M);
163163
end
164164
ΩpKe2 = DFTK.apply_Ω(e2, ψr, hamr, Λ) .+ DFTK.apply_K(basis_ref, e2, ψr, ρr, occ)
165165
ΩpKe2 = DFTK.transfer_blochwave(ΩpKe2, basis_ref, basis)
166-
rhs = resLF - ΩpKe2;
166+
rhs = denest(resLF) - denest(ΩpKe2);
167167

168168
# - Solve the Schur system to compute ``R_{\rm Schur}(P)``: this is the most
169169
# costly step, but inverting ``\boldsymbol{Ω} + \boldsymbol{K}`` on the small space has
170170
# the same cost than the full SCF cycle on the small grid.
171-
(; ψ) = DFTK.select_occupied_orbitals(basis, scfres.ψ, scfres.occupation)
172-
e1 = DFTK.solve_ΩplusK(basis, ψ, rhs, occ; tol).δψ
171+
(; ψ) = DFTK.select_occupied_orbitals(scfres.ψ, scfres.occupation)
172+
e1 = DFTK.solve_ΩplusK(ψ, rhs, occ; tol).δψ
173173
e1 = DFTK.transfer_blochwave(e1, basis, basis_ref)
174-
res_schur = e1 + Mres;
174+
res_schur = denest(e1) + Mres;
175175

176176
# ## Error estimates
177177

@@ -197,8 +197,9 @@ relerror["F(P)"] = compute_relerror(f);
197197
# To this end, we use the `ForwardDiff.jl` package to compute ``{\rm d}F(P)``
198198
# using automatic differentiation.
199199
function df(basis, occupation, ψ, δψ, ρ)
200-
δρ = DFTK.compute_δρ(basis, ψ, δψ, occupation)
201-
ForwardDiff.derivative-> compute_forces(basis, ψ.+ε.*δψ, occupation; ρ=ρ+ε.*δρ), 0)
200+
δρ = DFTK.compute_δρ(ψ, δψ, occupation)
201+
ForwardDiff.derivative-> compute_forces(BlochWaves.basis, denest(ψ).+ε.*δψ),
202+
occupation; ρ=ρ+ε.*δρ), 0)
202203
end;
203204

204205
# - Computation of the forces by a linearization argument if we have access to

examples/geometry_optimization.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ function compute_scfres(x)
3838
if isnothing(ρ)
3939
ρ = guess_density(basis)
4040
end
41+
if isnothing(ψ)
42+
ψ = BlochWaves(basis)
43+
else
44+
ψ = BlochWaves(basis, denest(ψ))
45+
end
4146
is_converged = DFTK.ScfConvergenceForce(tol / 10)
4247
scfres = self_consistent_field(basis; ψ, ρ, is_converged, callback=identity)
4348
ψ = scfres.ψ

examples/publications/2022_cazalis.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ using Plots
99
struct Hartree2D end
1010
struct Term2DHartree <: DFTK.TermNonlinear end
1111
(t::Hartree2D)(basis) = Term2DHartree()
12-
function DFTK.ene_ops(term::Term2DHartree, basis::PlaneWaveBasis{T},
13-
ψ, occ; ρ, kwargs...) where {T}
12+
function DFTK.ene_ops(term::Term2DHartree, ψ::BlochWaves{T}, occ; ρ, kwargs...) where {T}
13+
basis = ψ.basis
1414
## 2D Fourier transform of 3D Coulomb interaction 1/|x|
1515
poisson_green_coeffs = 2T(π) ./ [norm(G) for G in G_vectors_cart(basis)]
1616
poisson_green_coeffs[1] = 0 # DC component

src/DFTK.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,10 @@ export compute_fft_size
7272
export G_vectors, G_vectors_cart, r_vectors, r_vectors_cart
7373
export Gplusk_vectors, Gplusk_vectors_cart
7474
export Kpoint
75-
export to_composite_σG
76-
export from_composite_σG
75+
export BlochWaves, view_component, nest, denest
76+
export blochwave_as_matrix
77+
export blochwave_as_tensor
78+
export blochwaves_as_matrices
7779
export ifft
7880
export irfft
7981
export ifft!

src/densities.jl

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
# Densities (and potentials) are represented by arrays
22
# ρ[ix,iy,iz,iσ] in real space, where iσ ∈ [1:n_spin_components]
33

4-
# TODO: We reduce all components for the density. Will need to be though again when we merge
5-
# the components and the spins.
64
"""
7-
compute_density(basis::PlaneWaveBasis, ψ::AbstractVector, occupation::AbstractVector)
5+
compute_density(ψ::BlochWaves, occupation::AbstractVector)
86
9-
Compute the density for a wave function `ψ` discretized on the plane-wave
10-
grid `basis`, where the individual k-points are occupied according to `occupation`.
11-
`ψ` should be one coefficient matrix per ``k``-point.
7+
Compute the density for a wave function `ψ` discretized on the plane-wave grid `ψ.basis`,
8+
where the individual k-points are occupied according to `occupation`.
9+
`ψ` should contain one coefficient matrix per ``k``-point.
1210
It is possible to ask only for occupations higher than a certain level to be computed by
1311
using an optional `occupation_threshold`. By default all occupation numbers are considered.
1412
"""
15-
@views @timing function compute_density(basis::PlaneWaveBasis{T}, ψ, occupation;
16-
occupation_threshold=zero(T)) where {T}
17-
S = promote_type(T, real(eltype(ψ[1])))
13+
# TODO: We reduce all components for the density. Will need to be though again when we merge
14+
# the components and the spins.
15+
@views @timing function compute_density::BlochWaves{T, Tψ}, occupation;
16+
occupation_threshold=zero(T)) where {T, Tψ}
17+
S = promote_type(T, real(Tψ))
1818
# occupation should be on the CPU as we are going to be doing scalar indexing.
1919
occupation = [to_cpu(oc) for oc in occupation]
2020

21+
basis = ψ.basis
2122
mask_occ = [findall(occnk -> abs(occnk) occupation_threshold, occk)
2223
for occk in occupation]
2324
if all(isempty, mask_occ) # No non-zero occupations => return zero density
@@ -66,21 +67,22 @@ using an optional `occupation_threshold`. By default all occupation numbers are
6667
end
6768

6869
# Variation in density corresponding to a variation in the orbitals and occupations.
69-
@views @timing function compute_δρ(basis::PlaneWaveBasis{T}, ψ, δψ,
70-
occupation, δoccupation=zero.(occupation);
70+
@views @timing function compute_δρ(ψ::BlochWaves{T}, δψ, occupation,
71+
δoccupation=zero.(occupation);
7172
occupation_threshold=zero(T)) where {T}
7273
ForwardDiff.derivative(zero(T)) do ε
7374
ψ_ε = [ψk .+ ε .* δψk for (ψk, δψk) in zip(ψ, δψ)]
7475
occ_ε = [occk .+ ε .* δocck for (occk, δocck) in zip(occupation, δoccupation)]
75-
compute_density(basis, ψ_ε, occ_ε; occupation_threshold)
76+
compute_density(BlochWaves.basis, ψ_ε), occ_ε; occupation_threshold)
7677
end
7778
end
7879

79-
@views @timing function compute_kinetic_energy_density(basis::PlaneWaveBasis{TT}, ψ,
80-
occupation) where {TT}
80+
@views @timing function compute_kinetic_energy_density::BlochWaves{T, Tψ},
81+
occupation) where {T, Tψ}
82+
basis = ψ.basis
8183
@assert basis.model.n_components == 1
82-
T = promote_type(TT, real(eltype(ψ[1])))
83-
τ = similar(ψ[1], T, (basis.fft_size..., basis.model.n_spin_components))
84+
TT = promote_type(T, real())
85+
τ = similar(ψ[1], TT, (basis.fft_size..., basis.model.n_spin_components))
8486
τ .= 0
8587
dαψnk_real = zeros(complex(T), basis.fft_size)
8688
for (ik, kpt) in enumerate(basis.kpoints)

src/orbitals.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@ using Random # Used to have a generic API for CPU and GPU computations alike: s
44
# virtual states (or states with small occupation level for metals).
55
# threshold is a parameter to distinguish between states we want to keep and the
66
# others when using temperature. It is set to 0.0 by default, to treat with insulators.
7-
function select_occupied_orbitals(basis, ψ, occupation; threshold=0.0)
7+
function select_occupied_orbitals(ψ, occupation; threshold=0.0)
88
N = [something(findlast(x -> x > threshold, occk), 0) for occk in occupation]
99
selected_ψ = [@view ψk[:, :, 1:N[ik]] for (ik, ψk) in enumerate(ψ)]
1010
selected_occ = [ occk[1:N[ik]] for (ik, occk) in enumerate(occupation)]
1111

12+
ψ = BlochWaves.basis, selected_ψ)
1213
# If we have an insulator, sanity check that the orbitals we kept are the occupied ones.
1314
if iszero(threshold)
14-
model = basis.model
15+
model = ψ.basis.model
1516
n_spin = model.n_spin_components
1617
n_bands = div(model.n_electrons, n_spin * filled_occupation(model), RoundUp)
1718
@assert all([n_bands == size(ψk, 3) for ψk in ψ])
1819
end
19-
(; ψ=selected_ψ, occupation=selected_occ)
20+
(; ψ, occupation=selected_occ)
2021
end
2122

2223
# Packing routines used in direct_minimization and newton algorithms.

src/postprocess/forces.jl

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ lattice vectors. To get cartesian forces use [`compute_forces_cart`](@ref).
55
Returns a list of lists of forces (as SVector{3}) in the same order as the `atoms`
66
and `positions` in the underlying [`Model`](@ref).
77
"""
8-
@timing function compute_forces(basis::PlaneWaveBasis{T}, ψ, occupation; kwargs...) where {T}
8+
@timing function compute_forces::BlochWaves{T}, occupation; kwargs...) where {T}
9+
basis = ψ.basis
910
# no explicit symmetrization is performed here, it is the
1011
# responsability of each term to return symmetric forces
11-
forces_per_term = [compute_forces(term, basis, ψ, occupation; kwargs...)
12+
forces_per_term = [compute_forces(term, ψ, occupation; kwargs...)
1213
for term in basis.terms]
1314
sum(filter(!isnothing, forces_per_term))
1415
end
@@ -19,14 +20,14 @@ Returns a list of lists of forces
1920
`[[force for atom in positions] for (element, positions) in atoms]`
2021
which has the same structure as the `atoms` object passed to the underlying [`Model`](@ref).
2122
"""
22-
function compute_forces_cart(basis::PlaneWaveBasis, ψ, occupation; kwargs...)
23-
forces_reduced = compute_forces(basis, ψ, occupation; kwargs...)
24-
covector_red_to_cart.(basis.model, forces_reduced)
23+
function compute_forces_cart(ψ::BlochWaves, occupation; kwargs...)
24+
forces_reduced = compute_forces(ψ, occupation; kwargs...)
25+
covector_red_to_cart.(ψ.basis.model, forces_reduced)
2526
end
2627

2728
function compute_forces(scfres)
28-
compute_forces(scfres.basis, scfres.ψ, scfres.occupation; scfres.ρ)
29+
compute_forces(scfres.ψ, scfres.occupation; scfres.ρ)
2930
end
3031
function compute_forces_cart(scfres)
31-
compute_forces_cart(scfres.basis, scfres.ψ, scfres.occupation; scfres.ρ)
32+
compute_forces_cart(scfres.ψ, scfres.occupation; scfres.ρ)
3233
end

src/postprocess/stresses.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ Compute the stresses (= 1/Vol dE/d(M*lattice), taken at M=I) of an obtained SCF
1212
basis.kgrid, basis.symmetries_respect_rgrid,
1313
basis.use_symmetries_for_kpoint_reduction,
1414
basis.comm_kpts, basis.architecture)
15-
ρ = compute_density(new_basis, scfres.ψ, scfres.occupation)
16-
energies = energy_hamiltonian(new_basis, scfres.ψ, scfres.occupation;
15+
ψ = BlochWaves(new_basis, denest(scfres.ψ))
16+
ρ = compute_density(ψ, scfres.occupation)
17+
energies = energy_hamiltonian(ψ, scfres.occupation;
1718
ρ, scfres.eigenvalues, scfres.εF).energies
1819
energies.total
1920
end

src/response/hessian.jl

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ end
4040
Compute the application of K defined at ψ to δψ. ρ is the density issued from ψ.
4141
δψ also generates a δρ, computed with `compute_δρ`.
4242
"""
43+
# T@D@ basis redundant; change signature maybe?
4344
@views @timing function apply_K(basis::PlaneWaveBasis, δψ, ψ, ρ, occupation)
4445
δψ = proj_tangent(δψ, ψ)
45-
δρ = compute_δρ(basis, ψ, δψ, occupation)
46+
δρ = compute_δρ(ψ, δψ, occupation)
4647
δV = apply_kernel(basis, δρ; ρ)
4748

4849
Kδψ = map(enumerate(ψ)) do (ik, ψk)
@@ -62,13 +63,14 @@ Compute the application of K defined at ψ to δψ. ρ is the density issued fro
6263
end
6364

6465
"""
65-
solve_ΩplusK(basis::PlaneWaveBasis{T}, ψ, res, occupation;
66+
solve_ΩplusK(ψ::BlochWaves{T}, rhs, occupation;
6667
tol=1e-10, verbose=false) where {T}
6768
6869
Return δψ where (Ω+K) δψ = rhs
6970
"""
70-
@timing function solve_ΩplusK(basis::PlaneWaveBasis{T}, ψ, rhs, occupation;
71-
callback=identity, tol=1e-10) where {T}
71+
@timing function solve_ΩplusK::BlochWaves{T}, rhs, occupation; callback=identity,
72+
tol=1e-10) where {T}
73+
basis = ψ.basis
7274
filled_occ = filled_occupation(basis.model)
7375
# for now, all orbitals have to be fully occupied -> need to strip them beforehand
7476
@assert all(all(occ_k .== filled_occ) for occ_k in occupation)
@@ -79,8 +81,8 @@ Return δψ where (Ω+K) δψ = rhs
7981
@assert mpi_nprocs() == 1 # Distributed implementation not yet available
8082

8183
# compute quantites at the point which define the tangent space
82-
ρ = compute_density(basis, ψ, occupation)
83-
H = energy_hamiltonian(basis, ψ, occupation; ρ).ham
84+
ρ = compute_density(ψ, occupation)
85+
H = energy_hamiltonian(ψ, occupation; ρ).ham
8486

8587
ψ_matrices = blochwaves_as_matrices(ψ)
8688
pack(ψ) = reinterpret_real(pack_ψ(ψ))
@@ -152,11 +154,12 @@ Solve the problem `(Ω+K) δψ = rhs` using a split algorithm, where `rhs` is ty
152154
basis = ham.basis
153155
@assert size(rhs[1]) == size(ψ[1]) # Assume the same number of bands in ψ and rhs
154156

157+
ψ_array = denest(ψ)
155158
# compute δρ0 (ignoring interactions)
156-
δψ0, δoccupation0 = apply_χ0_4P(ham, ψ, occupation, εF, eigenvalues, -rhs;
159+
δψ0, δoccupation0 = apply_χ0_4P(ham, ψ_array, occupation, εF, eigenvalues, -rhs;
157160
tol=tol_sternheimer, occupation_threshold,
158161
kwargs...) # = -χ04P * rhs
159-
δρ0 = compute_δρ(basis, ψ, δψ0, occupation, δoccupation0; occupation_threshold)
162+
δρ0 = compute_δρ(ψ, δψ0, occupation, δoccupation0; occupation_threshold)
160163

161164
# compute total δρ
162165
pack(δρ) = vec(δρ)
@@ -183,13 +186,13 @@ Solve the problem `(Ω+K) δψ = rhs` using a split algorithm, where `rhs` is ty
183186
end
184187

185188
# Compute total change in eigenvalues
186-
δeigenvalues = map(ψ, δHψ) do ψk, δHψk
189+
δeigenvalues = map(ψ_array, δHψ) do ψk, δHψk
187190
map(eachslice(ψk; dims=3), eachslice(δHψk; dims=3)) do ψnk, δHψnk
188191
real(dot(ψnk, δHψnk)) # δε_{nk} = <ψnk | δH | ψnk>
189192
end
190193
end
191194

192-
δψ, δoccupation, δεF = apply_χ0_4P(ham, ψ, occupation, εF, eigenvalues, δHψ;
195+
δψ, δoccupation, δεF = apply_χ0_4P(ham, ψ_array, occupation, εF, eigenvalues, δHψ;
193196
occupation_threshold, tol=tol_sternheimer,
194197
kwargs...)
195198

src/scf/direct_minimization.jl

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,16 @@ Computes the ground state by direct minimization. `kwargs...` are
6363
passed to `Optim.Options()`. Note that the resulting ψ are not
6464
necessarily eigenvectors of the Hamiltonian.
6565
"""
66-
direct_minimization(basis::PlaneWaveBasis; kwargs...) = direct_minimization(basis, nothing; kwargs...)
67-
function direct_minimization(basis::PlaneWaveBasis{T}, ψ0;
68-
prec_type=PreconditionerTPA, maxiter=1_000,
66+
direct_minimization(basis::PlaneWaveBasis; kwargs...) =
67+
direct_minimization(BlochWaves(basis); kwargs...)
68+
69+
function direct_minimization(ψ0::BlochWaves{T}; prec_type=PreconditionerTPA, maxiter=1_000,
6970
optim_solver=Optim.LBFGS, tol=1e-6, kwargs...) where {T}
7071
if mpi_nprocs() > 1
7172
# need synchronization in Optim
7273
error("Direct minimization with MPI is not supported yet")
7374
end
75+
basis = ψ0.basis
7476
model = basis.model
7577
@assert model.n_components == 1
7678
@assert iszero(model.temperature) # temperature is not yet supported
@@ -81,7 +83,7 @@ function direct_minimization(basis::PlaneWaveBasis{T}, ψ0;
8183
Nk = length(basis.kpoints)
8284

8385
if isnothing(ψ0)
84-
ψ0 = [random_orbitals(basis, kpt, n_bands) for kpt in basis.kpoints]
86+
ψ0 = BlochWaves(basis, [random_orbitals(basis, kpt, n_bands) for kpt in basis.kpoints])
8587
end
8688
ψ0_matrices = blochwaves_as_matrices(ψ0)
8789
occupation = [filled_occ * ones(T, n_bands) for _ = 1:Nk]
@@ -100,8 +102,8 @@ function direct_minimization(basis::PlaneWaveBasis{T}, ψ0;
100102
# computes energies and gradients
101103
function fg!(E, G, ψ)
102104
ψ = unpack(ψ)
103-
ρ = compute_density(basis, ψ, occupation)
104-
energies, H = energy_hamiltonian(basis, ψ, occupation; ρ)
105+
ρ = compute_density(BlochWaves(basis, ψ), occupation)
106+
energies, H = energy_hamiltonian(BlochWaves(basis, ψ), occupation; ρ)
105107

106108
# The energy has terms like occ * <ψ|H|ψ>, so the gradient is 2occ Hψ
107109
if G !== nothing
@@ -145,5 +147,6 @@ function direct_minimization(basis::PlaneWaveBasis{T}, ψ0;
145147

146148
# We rely on the fact that the last point where fg! was called is the minimizer to
147149
# avoid recomputing at ψ
148-
(; ham=H, basis, energies, converged=true, ρ, ψ, eigenvalues, occupation, εF, optim_res=res)
150+
(; ham=H, basis, energies, converged=true, ρ, ψ=BlochWaves(basis, ψ), eigenvalues,
151+
occupation, εF, optim_res=res)
149152
end

src/scf/nbands_algorithm.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ function determine_n_bands(bands::AdaptiveBands, occupation::Nothing, eigenvalue
6767
(; n_bands_converge, n_bands_compute)
6868
end
6969
function determine_n_bands(bands::AdaptiveBands, occupation::AbstractVector,
70-
eigenvalues::AbstractVector, ψ::AbstractVector)
70+
eigenvalues::AbstractVector, ψ::BlochWaves)
7171
# TODO Could return different bands per k-Points
7272

7373
# Determine number of bands to be actually converged

0 commit comments

Comments
 (0)