Skip to content

Commit 96bf0ea

Browse files
committed
Implement core projection
1 parent cfd76c1 commit 96bf0ea

File tree

33 files changed

+977
-146
lines changed

33 files changed

+977
-146
lines changed

app/quoll.jl

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ global_logger(
4343

4444
@info "Parsing QUOLL input file"
4545

46-
# input_filepath = "/home/chem/phrpwt/Jobs/Data_Pipeline/Quoll_Workspace/Quoll/examples/SiC/input_file.toml"
4746
input_filepath = abspath(ARGS[1])
4847
params, input_dirs, output_dirs = Quoll.Parser.parse_inputfile(input_filepath)
4948

@@ -52,6 +51,11 @@ params, input_dirs, output_dirs = Quoll.Parser.parse_inputfile(input_filepath)
5251
ndirs = length(input_dirs)
5352
my_idirs = Quoll.split_work(ndirs, global_comm, Quoll.DefaultSplit())
5453

54+
# Construct an MPI sub-communicator for MPI tasks working on the same atomic configuration
55+
global_color = first(my_idirs)
56+
config_comm = MPI.Comm_split(global_comm, global_color, global_rank)
57+
config_rank = MPI.Comm_rank(config_comm)
58+
5559
for idir in my_idirs
5660
input_dir = input_dirs[idir]
5761

@@ -61,7 +65,6 @@ for idir in my_idirs
6165
operatorkinds = find_operatorkinds(input_dir, params)
6266
operators = load_operators(input_dir, operatorkinds, params.input.format)
6367

64-
# TODO: in the future could allow for <other canonical formats/shortcut conversions>
6568
@info "Converting operators into BSparseOperator format"
6669

6770
operators = convert_operators(operators, BSparseOperator)
@@ -71,7 +74,18 @@ for idir in my_idirs
7174

7275
# Assuming all operators in the directory are related to the same atoms
7376
atoms = Quoll.get_atoms(first(operators))
74-
kgrid = get_kgrid(atoms, params)
77+
kgrid = get_kgrid(atoms, operatorkinds, params)
78+
79+
@info "Splitting k-points across MPI tasks"
80+
# TODO: add an option for different splits in the input file
81+
nkpoints = Quoll.get_nkpoints(kgrid)
82+
my_ikpoints = Quoll.split_work(nkpoints, config_comm, Quoll.FHIaimsLAPACKSplit())
83+
end
84+
85+
if !isnothing(params.basis_projection)
86+
@info "Projecting the core states"
87+
88+
operators = perform_core_projection(operators, kgrid, my_ikpoints, config_comm, params)
7589
end
7690

7791
if !isnothing(params.output)

examples/SiC/input_file.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ directory = "SiC_DeepH_converted"
1111
# Should also allow different types,
1212
# i.e. Monkhorst-Pack and even possibly different shifts
1313
[kpoint_grid]
14-
mesh = [10, 10, 10]
14+
mesh = [10, 20, 20]
15+
# mesh = [1, 1, 3]
1516
density = 8.0
1617

1718
[basis_projection]
@@ -21,7 +22,7 @@ projected_basis = [
2122
"Si (2, 0, 0) type = atomic",
2223
"Si (2, 1, *) type = atomic",
2324
]
24-
method = "FC99V"
25+
method = "LaikovCore"
2526

2627
[postprocessing]
2728
fermi_level = true

src/Parser.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,24 @@ using Unitful
77
using UnitfulAtomic
88
using AtomsBase
99
using ArgCheck
10+
using MPI
1011

1112
using ..Quoll
1213
using ..Quoll:
1314
AbstractOperator,
1415
OperatorKind,
16+
KGrid,
1517
allows_symmetry,
1618
get_avail_operatorkinds,
1719
get_operatorkinds,
1820
get_readformat,
1921
get_writeformat
2022
import ..Quoll: find_operatorkinds, get_kgrid
23+
2124
using ..Projections
2225
using ..Projections: AbstractBasisProjection, get_basis_projection
26+
import ..Projections: perform_core_projection
27+
2328
using ..Postprocessing
2429
using ..Postprocessing: SmearingFunction, get_smearing
2530

src/Projections.jl

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,42 @@
11
module Projections
22

3+
using LinearAlgebra
4+
using StaticArrays
5+
using ArgCheck
6+
using MPI
7+
8+
using ..Quoll
9+
using ..Quoll:
10+
AbstractOperator,
11+
KGrid,
12+
precompute_phases,
13+
get_float,
14+
get_kind,
15+
get_data,
16+
get_metadata,
17+
get_sparsity,
18+
get_basisset,
19+
get_spins,
20+
get_dense_subbasis_mask,
21+
construct_subbasis_metadata,
22+
build_operator,
23+
fourier_transform,
24+
inv_fourier_transform_data!,
25+
synchronise_data!
26+
327
abstract type AbstractBasisProjection end
428

5-
function basis_projection end
29+
function get_basis_projection end
30+
31+
function perform_core_projection end
32+
33+
export perform_core_projection
34+
include("projections/common.jl")
635

736
export FC99V
837
include("projections/fc99v.jl")
938

39+
export LaikovCore
40+
include("projections/laikov.jl")
41+
1042
end

src/Quoll.jl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
module Quoll
22

3+
using Base
34
using Reexport
45
using ArgCheck
56
using LinearAlgebra
67
using StaticArrays
8+
using OffsetArrays
79
using Dictionaries
810
using AutoHashEquals
911
using AxisKeys
@@ -43,7 +45,7 @@ include("sparsity.jl")
4345
include("shconversion.jl")
4446

4547
export get_kgrid
46-
include("kgrid.jl")
48+
include("kpoints.jl")
4749

4850
# Operator formats and their IO routines
4951
# (Assuming no dependencies between different format types in each file)
@@ -54,9 +56,10 @@ export write_operator, write_operators
5456
export find_operatorkinds
5557
include("operators/interface.jl")
5658

57-
export BSparseOperator
59+
export BSparseOperator, DenseOperator
5860
include("operators/canonical/abstract.jl")
5961
include("operators/canonical/bsparse.jl")
62+
include("operators/canonical/dense.jl")
6063

6164
export DeepHOperator
6265
include("operators/deeph/deeph.jl")
@@ -68,8 +71,10 @@ include("operators/fhiaims/fhiaims_csc.jl")
6871
# Operator format conversions
6972

7073
export convert_operator, convert_operators
74+
export fourier_transform
7175
include("conversions/interface.jl")
7276

77+
include("conversions/canonical/bsparse.jl")
7378
include("conversions/fhiaims/fhiaims_csc.jl")
7479
include("conversions/deeph/deeph.jl")
7580

src/basis.jl

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,47 @@ basis_species(basisset::BasisSetMetadata, species::ChemicalSpecies) = basisset.b
3838
get_unique_species(basisset::BasisSetMetadata) = unique(basisset.atom2species)
3939

4040
# Assuming ms that belong to a given l are next to each other
41-
function get_angular_momenta(basis::Vector{T}) where T<:BasisMetadata
41+
function get_angular_momenta(basis::AbstractVector{T}) where T<:BasisMetadata
4242
return getfield.(filter(orbital -> orbital.l == orbital.m, basis), :l)
4343
end
4444

45+
function BasisSetMetadata(basisset::BasisSetMetadata, masks::SpeciesDictionary)
46+
unique_species_keys = Indices(keys(basisset.basis))
47+
subbasis_dict = map(unique_species_keys) do z
48+
basis_z = basis_species(basisset, z)
49+
basis_z[masks[z]]
50+
end
51+
return BasisSetMetadata(Base.ImmutableDict(pairs(subbasis_dict)...), basisset.atom2species)
52+
end
53+
54+
# Returns Dictionary{ChemicalSpecies, BitVector}
55+
function get_subbasis_masks(basisset::BasisSetMetadata, subbasis::Vector{T};
56+
inverted = false) where T<:BasisMetadata
57+
58+
unique_species_keys = Indices(keys(basisset.basis))
59+
60+
return map(unique_species_keys) do z
61+
basis_z = basis_species(basisset, z)
62+
subbasis_z = filter(orbital -> isequal(orbital.z, z), subbasis)
63+
64+
# Throw a warning if some orbitals in subbasis are not present in basisset
65+
# (which could happen if the orbitals were supplied incorrectly in the input file)
66+
if !all(subbasis_z .∈ Ref(basis_z))
67+
@warn "Some orbitals in the subbasis are not present in the full basis"
68+
end
69+
70+
inverted ? basis_z .∉ Ref(subbasis_z) : basis_z .∈ Ref(subbasis_z)
71+
end
72+
end
73+
74+
# Returns BitVector to be used with dense operators
75+
function get_dense_subbasis_mask(basisset::BasisSetMetadata, subbasis::Vector{T};
76+
inverted = false) where T<:BasisMetadata
77+
78+
subbasis_masks = get_subbasis_masks(basisset, subbasis, inverted = inverted)
79+
return reduce(vcat, (subbasis_masks[z] for z in basisset.atom2species))
80+
end
81+
4582
get_atom2nbasis(basisset::BasisSetMetadata) = get_atom2nbasis(basisset.basis, basisset.atom2species)
4683
get_atom2nbasis(basis, atom2species) = [length(basis[z]) for z in atom2species]
4784

@@ -85,4 +122,4 @@ function get_atom2offset(basisset::BasisSetMetadata)
85122
return get_atom2offset(atom2basis)
86123
end
87124

88-
get_atom2offset(atom2basis) = [first(interval) - 1 for interval in atom2basis]
125+
get_atom2offset(atom2basis) = [first(interval) - 1 for interval in atom2basis]

src/constants.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
### TYPE ALIASES ###
22

33
const AbstractAnyDict{K, V} = Union{<:AbstractDict{K, V}, <:AbstractDictionary{K, V}}
4-
54
const SpeciesPairAnyDict{V} = AbstractAnyDict{NTuple{2, ChemicalSpecies}, V}
65
const SpeciesAnyDict{V} = AbstractAnyDict{ChemicalSpecies, V}
7-
86
const AtomPairAnyDict{V} = AbstractAnyDict{NTuple{2, Int}, V}
97

8+
const SpeciesDictionary{V} = AbstractDictionary{ChemicalSpecies, V}
9+
const SpeciesDict{V} = AbstractDict{ChemicalSpecies, V}
10+
1011
const ThreeDimSliceView{T} = SubArray{
1112
T, 3, Array{T, 3},
1213
Tuple{
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
### FOURIER TRANSFORMS ###
2+
3+
# TODO: the same function could be used for both DenseOperator and FHIaimsDenseOperator
4+
# (except for the data I would need to use SH conversion)
5+
# TODO: modify to enable float conversions
6+
function fourier_transform(in_operator::BSparseOperator, kpoints, phases, ::Type{DenseOperator}; float = Float64)
7+
in_metadata = get_metadata(in_operator)
8+
in_sparsity = get_sparsity(in_operator)
9+
in_atoms = get_atoms(in_operator)
10+
in_basisset = get_basisset(in_operator)
11+
in_spins = get_spins(in_operator)
12+
in_kind = get_kind(in_operator)
13+
14+
# Convert metadata
15+
out_sparsity = convert_sparsity(in_metadata, RecipDenseSparsity, hermitian = in_sparsity.hermitian)
16+
out_metadata_list = [
17+
RecipDenseMetadata(in_atoms, out_sparsity, in_basisset, in_spins, SVector{3}(kpoint))
18+
for kpoint in kpoints
19+
]
20+
21+
# Initialize out_operator with zeros
22+
out_operator_list = [
23+
build_operator(DenseOperator, in_kind, out_metadata; uninit = false, value = zero(Complex{float}))
24+
for out_metadata in out_metadata_list
25+
]
26+
27+
# Perform Fourier transform
28+
for (out_operator, phases_k) in zip(out_operator_list, phases)
29+
fourier_transform_data!(out_operator, in_operator, phases_k)
30+
end
31+
32+
return length(out_operator_list) == 1 ? first(out_operator_list) : out_operator_list
33+
end
34+
35+
# TODO: write another method but with shifts (or alternatively write the method with shifts
36+
# but force the compiler to remove shifts for matching SH convention)
37+
function fourier_transform_data!(out_operator::DenseOperator, in_operator::BSparseOperator, phases_k)
38+
in_keydata = get_keydata(in_operator)
39+
in_sparsity = get_sparsity(in_operator)
40+
out_data = get_data(out_operator)
41+
out_basisset = get_basisset(out_operator)
42+
out_float = get_float(out_operator)
43+
44+
ilocal2iglobal = get_ilocal2iglobal(in_sparsity)
45+
atom2offset = get_atom2offset(out_basisset)
46+
47+
for ((iat, jat), in_block) in pairs(in_keydata)
48+
49+
phases_kij = phases_k[ilocal2iglobal[(iat, jat)]]
50+
atom2offset_i = atom2offset[iat]
51+
atom2offset_j = atom2offset[jat]
52+
53+
for jb in axes(in_block, 2)
54+
jb_dense = jb + atom2offset_j
55+
for ib in axes(in_block, 1)
56+
ib_dense = ib + atom2offset_i
57+
58+
tmp = zero(out_float)
59+
@inbounds for iR in axes(in_block, 3)
60+
tmp += in_block[ib, jb, iR] * phases_kij[iR]
61+
end
62+
63+
out_data[ib_dense, jb_dense] = tmp
64+
end
65+
end
66+
67+
# @tullio tmp[ib,jb] := in_block[ib,jb,R] * phases_kij[R]
68+
# out_data[atom2basis[iat], atom2basis[jat]] = tmp
69+
end
70+
71+
# TODO: I could use Hermitian array instead, probably directly in build stage
72+
if in_sparsity.hermitian
73+
for jb_dense in 1:size(out_data, 1)
74+
for ib_dense in 1:jb_dense
75+
out_data[jb_dense, ib_dense] = conj(out_data[ib_dense, jb_dense])
76+
end
77+
end
78+
end
79+
end
80+
81+
# Only a contribution to out_operator because in_operator contains only a single k-point
82+
function inv_fourier_transform_data!(out_operator::BSparseOperator, in_operator::DenseOperator, phases_k, weight)
83+
out_basisset = get_basisset(out_operator)
84+
out_keydata = get_keydata(out_operator)
85+
out_sparsity = get_sparsity(out_operator)
86+
in_data = get_data(in_operator)
87+
88+
ilocal2iglobal = get_ilocal2iglobal(out_sparsity)
89+
atom2offset = get_atom2offset(out_basisset)
90+
91+
for ((iat, jat), out_block) in pairs(out_keydata)
92+
93+
# Assuming phases is a vector here (i.e. for a single k-point)
94+
inv_phases_kij = conj(phases_k[ilocal2iglobal[(iat, jat)]])
95+
atom2offset_i = atom2offset[iat]
96+
atom2offset_j = atom2offset[jat]
97+
98+
for iR in axes(out_block, 3)
99+
for jb in axes(out_block, 2)
100+
jb_dense = jb + atom2offset_j
101+
for ib in axes(out_block, 1)
102+
ib_dense = ib + atom2offset_i
103+
out_block[ib, jb, iR] += weight * real(in_data[ib_dense, jb_dense] * inv_phases_kij[iR])
104+
end
105+
end
106+
end
107+
end
108+
109+
end

0 commit comments

Comments
 (0)