SIRIUS 7.5.0
Electronic structure library and applications
adaptor.cpp
Go to the documentation of this file.
1// Copyright (c) 2023 Simon Pintarelli, Anton Kozhevnikov, Thomas Schulthess
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without modification, are permitted provided that
5// the following conditions are met:
6//
7// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
8// following disclaimer.
9// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
10// and the following disclaimer in the documentation and/or other materials provided with the distribution.
11//
12// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
13// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
14// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
15// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
16// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
17// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
18// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19
20/** \file adaptor.cpp
21 *
22 * \brief Contains implementation of the interface to nlcglib.
23 */
24
25#include "SDDK/memory.hpp"
26#include "core/rte/rte.hpp"
28#ifdef SIRIUS_NLCGLIB
29#include <stdexcept>
30
31#include "adaptor.hpp"
32#include "apply_hamiltonian.hpp"
35#include "dft/energy.hpp"
36
37using namespace nlcglib;
38
39using prec_t = double;
40
41namespace sirius {
42
43template <class wfc_ptr_t>
44std::shared_ptr<Matrix>
45make_vector(std::vector<wfc_ptr_t> const& wfct, Simulation_context const& ctx, K_point_set const& kset,
46 nlcglib::memory_type memory = nlcglib::memory_type::none)
47{
48 std::map<sddk::memory_t, nlcglib::memory_type> memtype = {
49 {sddk::memory_t::device, nlcglib::memory_type::device},
50 {sddk::memory_t::host, nlcglib::memory_type::host},
51 {sddk::memory_t::host_pinned, nlcglib::memory_type::host}};
52 std::map<nlcglib::memory_type, sddk::memory_t> memtype_lookup = {
53 {nlcglib::memory_type::none, sddk::memory_t::none},
54 {nlcglib::memory_type::device, sddk::memory_t::device},
55 {nlcglib::memory_type::host, sddk::memory_t::host},
56 {nlcglib::memory_type::host, sddk::memory_t::host_pinned}};
57
58 sddk::memory_t target_memory = memtype_lookup.at(memory);
59 if (target_memory == sddk::memory_t::none) {
60 target_memory = ctx.processing_unit_memory_t();
61 }
62
63 std::vector<Matrix::buffer_t> data;
64 std::vector<std::pair<int, int>> kpoint_indices;
65 // sddk::memory_t preferred_memory = ctx.preferred_memory_t();
66 int num_spins = ctx.num_spins();
67 int nb = ctx.num_bands();
68 for (auto i = 0u; i < wfct.size(); ++i) {
69 auto gidk = kset.spl_num_kpoints().global_index(typename kp_index_t::local(i)); //(i); // global k-point index
70 for (int ispn = 0; ispn < num_spins; ++ispn) {
71 auto& array = wfct[i]->pw_coeffs(wf::spin_index(ispn));
72 int lda = array.size(0);
73 MPI_Comm comm = wfct[i]->comm().native();
74 // check that wfct has been allocated
75 if (is_device_memory(target_memory)) {
76 // make sure that array is on device
77 if (!array.on_device()) {
78 RTE_THROW("Error: expected device storage, but got nullptr");
79 }
80 }
81 kpoint_indices.emplace_back(std::make_pair(gidk, ispn));
82 data.emplace_back(std::array<int, 2>{1, lda}, /* stride */
83 std::array<int, 2>{lda, nb}, /* size */
84 array.at(target_memory), /* pointer */
85 memtype.at(target_memory), comm /* mpi communicator */);
86 }
87 }
88 return std::make_shared<Matrix>(std::move(data), std::move(kpoint_indices), kset.comm().native());
89}
90
91Matrix::buffer_t
92Matrix::get(int i)
93{
94 return data[i];
95}
96
97const Matrix::buffer_t
98Matrix::get(int i) const
99{
100 return data[i];
101}
102
103Energy::Energy(K_point_set& kset, Density& density, Potential& potential)
104 : kset_(kset)
105 , density_(density)
106 , potential_(potential)
107{
108 int nk = kset.spl_num_kpoints().local_size();
109 auto& ctx = kset.ctx();
110 hphis_.resize(nk);
111 cphis_.resize(nk);
112 for (auto it : kset.spl_num_kpoints()) {
113 auto& kp = *kset.get<double>(it.i);
114 sddk::memory_t preferred_memory_t = ctx.processing_unit_memory_t();
115 auto num_mag_dims = wf::num_mag_dims(ctx.num_mag_dims());
116 auto num_bands = wf::num_bands(ctx.num_bands());
117 // make a new wf for Hamiltonian apply...
118 hphis_[it.li] =
119 std::make_shared<wf::Wave_functions<prec_t>>(kp.gkvec_sptr(), num_mag_dims, num_bands, preferred_memory_t);
120 cphis_[it.li] = &kp.spinor_wave_functions();
121 hphis_[it.li]->allocate(sddk::memory_t::host);
122 }
123 // need to allocate wavefunctions on GPU
124}
125
126void
127Energy::compute()
128{
129 auto& ctx = kset_.ctx();
130 int num_spins = ctx.num_spins();
131 int num_bands = ctx.num_bands();
132
133 density_.generate<prec_t>(kset_, ctx.use_symmetry(), true /* add core */, true /* transform to rg */);
134
135 potential_.generate(density_, ctx.use_symmetry(), true);
136
137 /* compute H@X and new band energies */
138 auto H0 = Hamiltonian0<double>(potential_, true);
139
140 auto proc_mem_t = ctx.processing_unit_memory_t();
141
142 // apply Hamiltonian
143 for (auto it : kset_.spl_num_kpoints()) {
144 auto& kp = *kset_.get<prec_t>(it.i);
145 std::vector<double> band_energies(num_bands);
146
147 auto mem_guard = cphis_[it.li]->memory_guard(proc_mem_t, wf::copy_to::device);
148 auto mem_guard_h = hphis_[it.li]->memory_guard(proc_mem_t, wf::copy_to::host);
149
150 auto null_ptr_wfc = std::shared_ptr<wf::Wave_functions<prec_t>>();
151 apply_hamiltonian(H0, kp, *hphis_[it.li], kp.spinor_wave_functions(), null_ptr_wfc);
152 // compute band energies = diag(<psi|H|psi>)
153 for (int ispn = 0; ispn < num_spins; ++ispn) {
154 for (int jj = 0; jj < num_bands; ++jj) {
155 la::dmatrix<std::complex<double>> dmat(1, 1, sddk::memory_t::host);
156 dmat.allocate(sddk::memory_t::device);
157 wf::band_range bandr{jj, jj + 1};
158 wf::inner(ctx.spla_context(), proc_mem_t, wf::spin_range(ispn),
159 /* bra */ kp.spinor_wave_functions(), bandr,
160 /* ket */ *hphis_[it.li], bandr,
161 /*result*/ dmat, 0, 0);
162 kp.band_energy(jj, ispn, dmat(0, 0).real());
163 }
164 }
165 }
166
167 kset_.sync_band<double, sync_band_t::energy>();
168
169 // evaluate total energy
170 double eewald = ewald_energy(ctx, ctx.gvec(), ctx.unit_cell());
171 energy_components_ = total_energy_components(ctx, kset_, density_, potential_, eewald);
172 etot_ = ks_energy(ctx, this->energy_components_);
173}
174
175int
176Energy::occupancy()
177{
178 return kset_.ctx().max_occupancy();
179}
180
181int
182Energy::nelectrons()
183{
184 return kset_.unit_cell().num_electrons();
185}
186
187std::shared_ptr<nlcglib::MatrixBaseZ>
188Energy::get_hphi(nlcglib::memory_type memory = nlcglib::memory_type::none)
189{
190 return make_vector(this->hphis_, this->kset_.ctx(), this->kset_, memory);
191}
192
193std::shared_ptr<nlcglib::MatrixBaseZ>
194Energy::get_sphi(nlcglib::memory_type memory = nlcglib::memory_type::none)
195{
196 return make_vector(this->sphis_, this->kset_.ctx(), this->kset_, memory);
197}
198
199std::shared_ptr<nlcglib::MatrixBaseZ>
200Energy::get_C(nlcglib::memory_type memory = nlcglib::memory_type::none)
201{
202 return make_vector(this->cphis_, this->kset_.ctx(), this->kset_, memory);
203}
204
205std::shared_ptr<nlcglib::VectorBaseZ>
206Energy::get_fn()
207{
208 const int ns = kset_.ctx().num_spins();
209 int nbands = kset_.ctx().num_bands();
210 std::vector<std::vector<double>> fn;
211 std::vector<std::pair<int, int>> kindices;
212 for (auto it : kset_.spl_num_kpoints()) {
213 auto& kp = *kset_.get<prec_t>(it.i);
214 for (int ispn = 0; ispn < ns; ++ispn) {
215 std::vector<double> fn_local(nbands);
216 for (int i = 0; i < nbands; ++i) {
217 fn_local[i] = kp.band_occupancy(i, ispn);
218 }
219 fn.push_back(std::move(fn_local));
220 kindices.emplace_back(it.i, ispn);
221 }
222 }
223 return std::make_shared<Array1d>(fn, kindices, kset_.comm().native());
224}
225
226void
227Energy::set_fn(const std::vector<std::pair<int, int>>& keys, const std::vector<std::vector<double>>& fn)
228{
229 const int nbands = kset_.ctx().num_bands();
230#ifndef NDEBUG
231 const int ns = kset_.ctx().num_spins();
232 auto nk = kset_.spl_num_kpoints().local_size();
233 const double max_occ = ns == 1 ? 2.0 : 1.0;
234#endif
235
236 assert(static_cast<int>(fn.size()) == nk * ns);
237 for (auto iloc = 0u; iloc < fn.size(); ++iloc) {
238 // global k-point index
239 int gidk = keys[iloc].first;
240 int ispn = keys[iloc].second;
241 auto& kp = *kset_.get<prec_t>(gidk);
242 const auto& fn_loc = fn[iloc];
243 assert(static_cast<int>(fn_loc.size()) == nbands);
244 for (int i = 0; i < nbands; ++i) {
245 assert(fn_loc[i] >= 0);
246 kp.band_occupancy(i, ispn, fn_loc[i]);
247 }
248 }
249 kset_.sync_band<double, sync_band_t::occupancy>();
250}
251
252std::shared_ptr<nlcglib::VectorBaseZ>
253Energy::get_ek()
254{
255 const int ns = kset_.ctx().num_spins();
256 int nbands = kset_.ctx().num_bands();
257 std::vector<std::vector<double>> ek;
258 std::vector<std::pair<int, int>> kindices;
259 for (auto it : kset_.spl_num_kpoints()) {
260 auto& kp = *kset_.get<prec_t>(it.i);
261 for (int ispn = 0; ispn < ns; ++ispn) {
262 std::vector<double> ek_local(nbands);
263 for (int i = 0; i < nbands; ++i) {
264 ek_local[i] = kp.band_energy(i, ispn);
265 }
266 ek.push_back(std::move(ek_local));
267 kindices.emplace_back(it.i.get(), ispn);
268 }
269 }
270 return std::make_shared<Array1d>(ek, kindices, kset_.comm().native());
271}
272
273std::shared_ptr<nlcglib::VectorBaseZ>
274Energy::get_gkvec_ekin()
275{
276 const int ns = kset_.ctx().num_spins();
277 std::vector<std::vector<double>> gkvec_cart;
278 std::vector<std::pair<int, int>> kindices;
279 for (auto it : kset_.spl_num_kpoints()) {
280 auto& kp = *kset_.get<prec_t>(it.i);
281 for (int ispn = 0; ispn < ns; ++ispn) {
282 int gkvec_count = kp.gkvec().count();
283 auto& gkvec = kp.gkvec();
284 std::vector<double> gkvec_local(gkvec_count);
285 for (int i = 0; i < gkvec_count; ++i) {
286 gkvec_local[i] = gkvec.gkvec_cart<index_domain_t::global>(i).length();
287 }
288 gkvec_cart.push_back(std::move(gkvec_local));
289 kindices.emplace_back(it.i.get(), ispn);
290 }
291 }
292 return std::make_shared<Array1d>(gkvec_cart, kindices, kset_.comm().native());
293}
294
295std::shared_ptr<nlcglib::ScalarBaseZ>
296Energy::get_kpoint_weights()
297{
298 const int ns = kset_.ctx().num_spins();
299 std::vector<double> weights;
300 std::vector<std::pair<int, int>> kindices;
301 for (auto it : kset_.spl_num_kpoints()) {
302 auto& kp = *kset_.get<double>(it.i);
303
304 // also return weights for every spin index
305 for (int ispn = 0; ispn < ns; ++ispn) {
306 weights.push_back(kp.weight());
307 kindices.emplace_back(it.i.get(), ispn);
308 }
309 }
310 return std::make_shared<Scalar>(weights, kindices, kset_.comm().native());
311}
312
313double
314Energy::get_total_energy()
315{
316 return etot_;
317}
318
319std::map<std::string, double>
320Energy::get_energy_components()
321{
322 return energy_components_;
323}
324
325void
326Energy::print_info() const
327{
328 auto& ctx = kset_.ctx();
329 auto& unit_cell = kset_.unit_cell();
330
331 auto result_mag = density_.get_magnetisation();
332 auto mt_mag = std::get<2>(result_mag);
333
334 if (ctx.num_mag_dims() && ctx.comm().rank() == 0) {
335 std::printf("atom moment |moment|");
336 std::printf("\n");
337 for (int i = 0; i < 80; i++) {
338 std::printf("-");
339 }
340 std::printf("\n");
341
342 for (int ia = 0; ia < unit_cell.num_atoms(); ia++) {
343 r3::vector<double> v(mt_mag[ia]);
344 std::printf("%4i [%8.4f, %8.4f, %8.4f] %10.6f", ia, v[0], v[1], v[2], v.length());
345 std::printf("\n");
346 }
347
348 std::printf("\n");
349 }
350}
351
352void
353Energy::set_chemical_potential(double mu)
354{
355 // set Fermi energy.
356 kset_.set_energy_fermi(mu);
357}
358
359double
360Energy::get_chemical_potential()
361{
362 return kset_.energy_fermi();
363}
364
365Array1d::buffer_t
366Array1d::get(int i)
367{
368 // call 1d constructor
369 return buffer_t(data[i].size(), data[i].data(), nlcglib::memory_type::host);
370}
371
372const Array1d::buffer_t
373Array1d::get(int i) const
374{
375 // call 1d constructor
376 return buffer_t(data[i].size(), const_cast<double*>(data[i].data()), nlcglib::memory_type::host);
377}
378
379} // namespace sirius
380#endif
Contains defintion of nlcglib interface.
Helper function for nlcglib.
Total energy terms.
Contains declaration and definition of sirius::Hamiltonian class.
Declaration of sirius::Local_operator class.
Memory management functions and classes.
bool is_device_memory(memory_t mem__)
Check if this is a valid device memory (memory, accessible by the device).
Definition: memory.hpp:93
@ array
array (ordered collection of values)
std::enable_if_t< std::is_same< T, real_type< F > >::value, void > inner(::spla::Context &spla_ctx__, sddk::memory_t mem__, spin_range spins__, W const &wf_i__, band_range br_i__, Wave_functions< T > const &wf_j__, band_range br_j__, la::dmatrix< F > &result__, int irow0__, int jcol0__)
Compute inner product between the two sets of wave-functions.
Namespace of the SIRIUS library.
Definition: sirius.f90:5
double ewald_energy(const Simulation_context &ctx, const fft::Gvec &gvec, const Unit_cell &unit_cell)
Compute the ion-ion electrostatic energy using Ewald method.
Definition: energy.cpp:30
Eror and warning handling during run-time execution.
Contains declaration and implementation of Wave_functions class.