(git:e7e05ae)
qs_mo_occupation.F
Go to the documentation of this file.
1 !--------------------------------------------------------------------------------------------------!
2 ! CP2K: A general program to perform molecular dynamics simulations !
3 ! Copyright 2000-2024 CP2K developers group <https://cp2k.org> !
4 ! !
5 ! SPDX-License-Identifier: GPL-2.0-or-later !
6 !--------------------------------------------------------------------------------------------------!
7 
8 ! **************************************************************************************************
9 !> \brief Set occupation of molecular orbitals
10 !> \par History
11 !> - set_mo_occupation subroutines moved from qs_mo_types (11.12.2014 MI)
12 !> \author MI
13 ! **************************************************************************************************
14 
16 
17  USE cp_log_handling, ONLY: cp_to_string
18  USE fermi_utils, ONLY: fermifixed,&
23  USE kahan_sum, ONLY: accurate_sum
24  USE kinds, ONLY: dp
25  USE qs_mo_types, ONLY: get_mo_set,&
27  mo_set_type,&
29  USE scf_control_types, ONLY: smear_type
30  USE util, ONLY: sort
31  USE xas_env_types, ONLY: get_xas_env,&
32  xas_environment_type
33 #include "./base/base_uses.f90"
34 
35  IMPLICIT NONE
36 
37  PRIVATE
38 
39  CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'qs_mo_occupation'
40 
41  PUBLIC :: set_mo_occupation
42 
43  INTERFACE set_mo_occupation
44  MODULE PROCEDURE set_mo_occupation_1, set_mo_occupation_2
45  END INTERFACE
46 
47 CONTAINS
48 
49 ! **************************************************************************************************
50 !> \brief Occupation for smeared spin polarized electronic structures
51 !> with relaxed multiplicity
52 !>
53 !> \param mo_array ...
54 !> \param smear ...
55 !> \date 10.03.2011 (MI)
56 !> \author MI
57 !> \version 1.0
58 ! **************************************************************************************************
59  SUBROUTINE set_mo_occupation_3(mo_array, smear)
60 
61  TYPE(mo_set_type), DIMENSION(2), INTENT(INOUT) :: mo_array
62  TYPE(smear_type), POINTER :: smear
63 
64  CHARACTER(LEN=*), PARAMETER :: routineN = 'set_mo_occupation_3'
65 
66  INTEGER :: all_nmo, handle, homo_a, homo_b, i, &
67  lfomo_a, lfomo_b, nmo_a, nmo_b, &
68  xas_estate
69  INTEGER, ALLOCATABLE, DIMENSION(:) :: all_index
70  LOGICAL :: is_large
71  REAL(KIND=dp) :: all_nelec, kts, mu, nelec_a, nelec_b, &
72  occ_estate
73  REAL(KIND=dp), ALLOCATABLE, DIMENSION(:) :: all_eigval, all_occ
74  REAL(KIND=dp), DIMENSION(:), POINTER :: eigval_a, eigval_b, occ_a, occ_b
75 
76  CALL timeset(routinen, handle)
77 
78  NULLIFY (eigval_a, eigval_b, occ_a, occ_b)
79  CALL get_mo_set(mo_set=mo_array(1), nmo=nmo_a, eigenvalues=eigval_a, &
80  occupation_numbers=occ_a)
81  CALL get_mo_set(mo_set=mo_array(2), nmo=nmo_b, eigenvalues=eigval_b, &
82  occupation_numbers=occ_b)
83  all_nmo = nmo_a + nmo_b
84  ALLOCATE (all_eigval(all_nmo))
85  ALLOCATE (all_occ(all_nmo))
86  ALLOCATE (all_index(all_nmo))
87 
88  all_eigval(1:nmo_a) = eigval_a(1:nmo_a)
89  all_eigval(nmo_a + 1:all_nmo) = eigval_b(1:nmo_b)
90 
91  CALL sort(all_eigval, all_nmo, all_index)
92 
93  xas_estate = -1
94  occ_estate = 0.0_dp
95 
96  nelec_a = 0.0_dp
97  nelec_b = 0.0_dp
98  all_nelec = 0.0_dp
99  nelec_a = accurate_sum(occ_a(:))
100  nelec_b = accurate_sum(occ_b(:))
101  all_nelec = nelec_a + nelec_b
102 
103  DO i = 1, all_nmo
104  IF (all_index(i) <= nmo_a) THEN
105  all_occ(i) = occ_a(all_index(i))
106  ELSE
107  all_occ(i) = occ_b(all_index(i) - nmo_a)
108  END IF
109  END DO
110 
111  CALL fermifixed(all_occ, mu, kts, all_eigval, all_nelec, &
112  smear%electronic_temperature, 1._dp, xas_estate, occ_estate)
113 
114  is_large = abs(maxval(all_occ) - 1.0_dp) > smear%eps_fermi_dirac
115  ! this is not a real problem, but the temperature might be a bit large
116  IF (is_large) &
117  cpwarn("Fermi-Dirac smearing includes the first MO")
118 
119  is_large = abs(minval(all_occ)) > smear%eps_fermi_dirac
120  IF (is_large) &
121  CALL cp_warn(__location__, &
122  "Fermi-Dirac smearing includes the last MO => "// &
123  "Add more MOs for proper smearing.")
124 
125  ! check that the total electron count is accurate
126  is_large = (abs(all_nelec - accurate_sum(all_occ(:))) > smear%eps_fermi_dirac*all_nelec)
127  IF (is_large) &
128  cpwarn("Total number of electrons is not accurate")
129 
130  DO i = 1, all_nmo
131  IF (all_index(i) <= nmo_a) THEN
132  occ_a(all_index(i)) = all_occ(i)
133  eigval_a(all_index(i)) = all_eigval(i)
134  ELSE
135  occ_b(all_index(i) - nmo_a) = all_occ(i)
136  eigval_b(all_index(i) - nmo_a) = all_eigval(i)
137  END IF
138  END DO
139 
140  nelec_a = accurate_sum(occ_a(:))
141  nelec_b = accurate_sum(occ_b(:))
142 
143  DO i = 1, nmo_a
144  IF (occ_a(i) < 1.0_dp) THEN
145  lfomo_a = i
146  EXIT
147  END IF
148  END DO
149  DO i = 1, nmo_b
150  IF (occ_b(i) < 1.0_dp) THEN
151  lfomo_b = i
152  EXIT
153  END IF
154  END DO
155  homo_a = lfomo_a - 1
156  DO i = nmo_a, lfomo_a, -1
157  IF (occ_a(i) > smear%eps_fermi_dirac) THEN
158  homo_a = i
159  EXIT
160  END IF
161  END DO
162  homo_b = lfomo_b - 1
163  DO i = nmo_b, lfomo_b, -1
164  IF (occ_b(i) > smear%eps_fermi_dirac) THEN
165  homo_b = i
166  EXIT
167  END IF
168  END DO
169 
170  CALL set_mo_set(mo_set=mo_array(1), kts=kts/2.0_dp, mu=mu, n_el_f=nelec_a, &
171  lfomo=lfomo_a, homo=homo_a, uniform_occupation=.false.)
172  CALL set_mo_set(mo_set=mo_array(2), kts=kts/2.0_dp, mu=mu, n_el_f=nelec_b, &
173  lfomo=lfomo_b, homo=homo_b, uniform_occupation=.false.)
174 
175  CALL timestop(handle)
176 
177  END SUBROUTINE set_mo_occupation_3
178 
179 ! **************************************************************************************************
180 !> \brief Prepare an occupation of alpha and beta MOs following an Aufbau
181 !> principle, i.e. allowing a change in multiplicity.
182 !> \param mo_array ...
183 !> \param smear ...
184 !> \param eval_deriv ...
185 !> \param tot_zeff_corr ...
186 !> \date 25.01.2010 (MK)
187 !> \par History
188 !> 10.2019 Added functionality to adjust mo occupation if the core
189 !> charges are changed via CORE_CORRECTION during surface dipole
190 !> calculation. Total number of electrons matches the total core
191 !> charges if tot_zeff_corr is non-zero. Not yet implemented for
192 !> OT type method. [Soumya Ghosh]
193 !> \author Matthias Krack (MK)
194 !> \version 1.0
195 ! **************************************************************************************************
196  SUBROUTINE set_mo_occupation_2(mo_array, smear, eval_deriv, tot_zeff_corr)
197 
198  TYPE(mo_set_type), DIMENSION(:), INTENT(INOUT) :: mo_array
199  TYPE(smear_type), POINTER :: smear
200  REAL(KIND=dp), DIMENSION(:), OPTIONAL, POINTER :: eval_deriv
201  REAL(KIND=dp), OPTIONAL :: tot_zeff_corr
202 
203  CHARACTER(LEN=*), PARAMETER :: routineN = 'set_mo_occupation_2'
204 
205  INTEGER :: handle, i, lumo_a, lumo_b, &
206  multiplicity_new, multiplicity_old, &
207  nelec
208  REAL(KIND=dp) :: nelec_f, threshold
209  REAL(KIND=dp), DIMENSION(:), POINTER :: eigval_a, eigval_b
210 
211  CALL timeset(routinen, handle)
212 
213  ! Fall back for the case that we have only one MO set
214  IF (SIZE(mo_array) == 1) THEN
215  IF (PRESENT(eval_deriv)) THEN
216 ! Change of MO occupancy to account for CORE_CORRECTION is not yet implemented
217  CALL set_mo_occupation_1(mo_array(1), smear=smear, eval_deriv=eval_deriv)
218  ELSE
219  IF (PRESENT(tot_zeff_corr)) THEN
220  CALL set_mo_occupation_1(mo_array(1), smear=smear, tot_zeff_corr=tot_zeff_corr)
221  ELSE
222  CALL set_mo_occupation_1(mo_array(1), smear=smear)
223  END IF
224  END IF
225  CALL timestop(handle)
226  RETURN
227  END IF
228 
229  IF (smear%do_smear) THEN
230  IF (smear%fixed_mag_mom < 0.0_dp) THEN
231  IF (PRESENT(tot_zeff_corr)) THEN
232  CALL cp_warn(__location__, &
233  "CORE_CORRECTION /= 0.0 might cause the cell to charge up "// &
234  "that will lead to application of different background "// &
235  "correction compared to the reference system. "// &
236  "Use FIXED_MAGNETIC_MOMENT >= 0.0 if using SMEAR keyword "// &
237  "to correct the electron density")
238  END IF
239  IF (smear%fixed_mag_mom /= -1.0_dp) THEN
240  cpassert(.NOT. (PRESENT(eval_deriv)))
241  CALL set_mo_occupation_3(mo_array, smear=smear)
242  CALL timestop(handle)
243  RETURN
244  END IF
245  ELSE
246  nelec_f = mo_array(1)%n_el_f + mo_array(2)%n_el_f
247  IF (abs((mo_array(1)%n_el_f - mo_array(2)%n_el_f) - smear%fixed_mag_mom) > smear%eps_fermi_dirac*nelec_f) THEN
248  mo_array(1)%n_el_f = nelec_f/2.0_dp + smear%fixed_mag_mom/2.0_dp
249  mo_array(2)%n_el_f = nelec_f/2.0_dp - smear%fixed_mag_mom/2.0_dp
250  END IF
251  cpassert(.NOT. (PRESENT(eval_deriv)))
252  IF (PRESENT(tot_zeff_corr)) THEN
253  CALL set_mo_occupation_1(mo_array(1), smear=smear, tot_zeff_corr=tot_zeff_corr)
254  CALL set_mo_occupation_1(mo_array(2), smear=smear, tot_zeff_corr=tot_zeff_corr)
255  ELSE
256  CALL set_mo_occupation_1(mo_array(1), smear=smear)
257  CALL set_mo_occupation_1(mo_array(2), smear=smear)
258  END IF
259  END IF
260  END IF
261 
262  IF (.NOT. ((mo_array(1)%flexible_electron_count > 0.0_dp) .AND. &
263  (mo_array(2)%flexible_electron_count > 0.0_dp))) THEN
264  IF (PRESENT(eval_deriv)) THEN
265  CALL set_mo_occupation_1(mo_array(1), smear=smear, eval_deriv=eval_deriv)
266  CALL set_mo_occupation_1(mo_array(2), smear=smear, eval_deriv=eval_deriv)
267  ELSE
268  IF (PRESENT(tot_zeff_corr)) THEN
269  CALL set_mo_occupation_1(mo_array(1), smear=smear, tot_zeff_corr=tot_zeff_corr)
270  CALL set_mo_occupation_1(mo_array(2), smear=smear, tot_zeff_corr=tot_zeff_corr)
271  ELSE
272  CALL set_mo_occupation_1(mo_array(1), smear=smear)
273  CALL set_mo_occupation_1(mo_array(2), smear=smear)
274  END IF
275  END IF
276  CALL timestop(handle)
277  RETURN
278  END IF
279 
280  nelec = mo_array(1)%nelectron + mo_array(2)%nelectron
281 
282  multiplicity_old = mo_array(1)%nelectron - mo_array(2)%nelectron + 1
283 
284  IF (mo_array(1)%nelectron >= mo_array(1)%nmo) &
285  CALL cp_warn(__location__, &
286  "All alpha MOs are occupied. Add more alpha MOs to "// &
287  "allow for a higher multiplicity")
288  IF ((mo_array(2)%nelectron >= mo_array(2)%nmo) .AND. (mo_array(2)%nelectron /= mo_array(1)%nelectron)) &
289  CALL cp_warn(__location__, "All beta MOs are occupied. Add more beta MOs to "// &
290  "allow for a lower multiplicity")
291 
292  eigval_a => mo_array(1)%eigenvalues
293  eigval_b => mo_array(2)%eigenvalues
294 
295  lumo_a = 1
296  lumo_b = 1
297 
298  ! Apply Aufbau principle
299  DO i = 1, nelec
300  ! Threshold is needed to ensure a preference for alpha occupation in the case
301  ! of degeneracy
302  threshold = max(mo_array(1)%flexible_electron_count, mo_array(2)%flexible_electron_count)
303  IF ((eigval_a(lumo_a) - threshold) < eigval_b(lumo_b)) THEN
304  lumo_a = lumo_a + 1
305  ELSE
306  lumo_b = lumo_b + 1
307  END IF
308  IF (lumo_a > mo_array(1)%nmo) THEN
309  IF (i /= nelec) &
310  CALL cp_warn(__location__, &
311  "All alpha MOs are occupied. Add more alpha MOs to "// &
312  "allow for a higher multiplicity")
313  IF (i < nelec) THEN
314  lumo_a = lumo_a - 1
315  lumo_b = lumo_b + 1
316  END IF
317  END IF
318  IF (lumo_b > mo_array(2)%nmo) THEN
319  IF (lumo_b < lumo_a) &
320  CALL cp_warn(__location__, &
321  "All beta MOs are occupied. Add more beta MOs to "// &
322  "allow for a lower multiplicity")
323  IF (i < nelec) THEN
324  lumo_a = lumo_a + 1
325  lumo_b = lumo_b - 1
326  END IF
327  END IF
328  END DO
329 
330  mo_array(1)%homo = lumo_a - 1
331  mo_array(2)%homo = lumo_b - 1
332 
333  IF (mo_array(2)%homo > mo_array(1)%homo) THEN
334  CALL cp_warn(__location__, &
335  "More beta ("// &
336  trim(adjustl(cp_to_string(mo_array(2)%homo)))// &
337  ") than alpha ("// &
338  trim(adjustl(cp_to_string(mo_array(1)%homo)))// &
339  ") MOs are occupied. Resorting to low spin state")
340  mo_array(1)%homo = nelec/2 + modulo(nelec, 2)
341  mo_array(2)%homo = nelec/2
342  END IF
343 
344  mo_array(1)%nelectron = mo_array(1)%homo
345  mo_array(2)%nelectron = mo_array(2)%homo
346  multiplicity_new = mo_array(1)%nelectron - mo_array(2)%nelectron + 1
347 
348  IF (multiplicity_new /= multiplicity_old) &
349  CALL cp_warn(__location__, &
350  "Multiplicity changed from "// &
351  trim(adjustl(cp_to_string(multiplicity_old)))//" to "// &
352  trim(adjustl(cp_to_string(multiplicity_new))))
353 
354  IF (PRESENT(eval_deriv)) THEN
355  CALL set_mo_occupation_1(mo_array(1), smear=smear, eval_deriv=eval_deriv)
356  CALL set_mo_occupation_1(mo_array(2), smear=smear, eval_deriv=eval_deriv)
357  ELSE
358  IF (PRESENT(tot_zeff_corr)) THEN
359  CALL set_mo_occupation_1(mo_array(1), smear=smear, tot_zeff_corr=tot_zeff_corr)
360  CALL set_mo_occupation_1(mo_array(2), smear=smear, tot_zeff_corr=tot_zeff_corr)
361  ELSE
362  CALL set_mo_occupation_1(mo_array(1), smear=smear)
363  CALL set_mo_occupation_1(mo_array(2), smear=smear)
364  END IF
365  END IF
366 
367  CALL timestop(handle)
368 
369  END SUBROUTINE set_mo_occupation_2
370 
371 ! **************************************************************************************************
372 !> \brief Smearing of the MO occupation with all kind of occupation numbers
373 !> \param mo_set MO dataset structure
374 !> \param smear optional smearing information
375 !> \param eval_deriv on entry the derivative of the KS energy wrt to the occupation number
376 !> on exit the derivative of the full free energy (i.e. KS and entropy) wrt to the eigenvalue
377 !> \param xas_env ...
378 !> \param tot_zeff_corr ...
379 !> \date 17.04.2002 (v1.0), 26.08.2008 (v1.1)
380 !> \par History
381 !> 10.2019 Added functionality to adjust mo occupation if the core
382 !> charges are changed via CORE_CORRECTION during surface dipole
383 !> calculation. Total number of electrons matches the total core
384 !> charges if tot_zeff_corr is non-zero. Not yet implemented for
385 !> OT type method. [Soumya Ghosh]
386 !> \author Matthias Krack
387 !> \version 1.1
388 ! **************************************************************************************************
389  SUBROUTINE set_mo_occupation_1(mo_set, smear, eval_deriv, xas_env, tot_zeff_corr)
390 
391  TYPE(mo_set_type), INTENT(INOUT) :: mo_set
392  TYPE(smear_type), OPTIONAL, POINTER :: smear
393  REAL(KIND=dp), DIMENSION(:), OPTIONAL, POINTER :: eval_deriv
394  TYPE(xas_environment_type), OPTIONAL, POINTER :: xas_env
395  REAL(KIND=dp), OPTIONAL :: tot_zeff_corr
396 
397  CHARACTER(LEN=*), PARAMETER :: routineN = 'set_mo_occupation_1'
398 
399  INTEGER :: handle, i_first, imo, ir, irmo, nmo, &
400  nomo, xas_estate
401  LOGICAL :: equal_size, is_large
402  REAL(KIND=dp) :: delectron, e1, e2, edelta, edist, &
403  el_count, lengthscale, nelec, &
404  occ_estate, total_zeff_corr, &
405  xas_nelectron
406  REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :) :: dfde
407 
408  CALL timeset(routinen, handle)
409 
410  cpassert(ASSOCIATED(mo_set%eigenvalues))
411  cpassert(ASSOCIATED(mo_set%occupation_numbers))
412  mo_set%occupation_numbers(:) = 0.0_dp
413 
414  ! Quick return, if no electrons are available
415  IF (mo_set%nelectron == 0) THEN
416  CALL timestop(handle)
417  RETURN
418  END IF
419 
420  xas_estate = -1
421  occ_estate = 0.0_dp
422  IF (PRESENT(xas_env)) THEN
423  CALL get_xas_env(xas_env=xas_env, xas_nelectron=xas_nelectron, occ_estate=occ_estate, xas_estate=xas_estate)
424  nomo = ceiling(xas_nelectron + 1.0 - occ_estate - epsilon(0.0_dp))
425 
426  mo_set%occupation_numbers(1:nomo) = mo_set%maxocc
427  IF (xas_estate > 0) mo_set%occupation_numbers(xas_estate) = occ_estate
428  el_count = sum(mo_set%occupation_numbers(1:nomo))
429  IF (el_count > xas_nelectron) &
430  mo_set%occupation_numbers(nomo) = mo_set%occupation_numbers(nomo) - (el_count - xas_nelectron)
431  el_count = sum(mo_set%occupation_numbers(1:nomo))
432  is_large = abs(el_count - xas_nelectron) > xas_nelectron*epsilon(el_count)
433  cpassert(.NOT. is_large)
434  ELSE
435  IF (modulo(mo_set%nelectron, int(mo_set%maxocc)) == 0) THEN
436  nomo = nint(mo_set%nelectron/mo_set%maxocc)
437  ! Initialize MO occupations
438  mo_set%occupation_numbers(1:nomo) = mo_set%maxocc
439  ELSE
440  nomo = int(mo_set%nelectron/mo_set%maxocc) + 1
441  ! Initialize MO occupations
442  mo_set%occupation_numbers(1:nomo - 1) = mo_set%maxocc
443  mo_set%occupation_numbers(nomo) = mo_set%nelectron - (nomo - 1)*mo_set%maxocc
444  END IF
445 ! introduce applied potential correction here
446 ! electron density is adjusted according to applied core correction
447 ! ref: SS, MT, MWF, JN PRL, 2018, 120, 246801
448 ! see whether both surface dipole correction and core correction is present in
449 ! the inputfile
450  IF (PRESENT(tot_zeff_corr)) THEN
451 ! find the additional core charges
452  total_zeff_corr = tot_zeff_corr
453  IF (int(mo_set%maxocc) == 1) total_zeff_corr = total_zeff_corr/2.0_dp
454  delectron = 0.0_dp
455  IF (total_zeff_corr < 0.0_dp) THEN
456 ! remove electron density from the mos
457  delectron = abs(total_zeff_corr) - real(mo_set%maxocc, kind=dp)
458  IF (delectron > 0.0_dp) THEN
459  mo_set%occupation_numbers(nomo) = 0.0_dp
460  irmo = ceiling(delectron/real(mo_set%maxocc, kind=dp))
461  DO ir = 1, irmo
462  delectron = delectron - real(mo_set%maxocc, kind=dp)
463  IF (delectron < 0.0_dp) THEN
464  mo_set%occupation_numbers(nomo - ir) = -delectron
465  ELSE
466  mo_set%occupation_numbers(nomo - ir) = 0.0_dp
467  END IF
468  END DO
469  nomo = nomo - irmo
470  IF (mo_set%occupation_numbers(nomo) == 0.0_dp) nomo = nomo - 1
471  ELSEIF (delectron < 0.0_dp) THEN
472  mo_set%occupation_numbers(nomo) = -delectron
473  ELSE
474  mo_set%occupation_numbers(nomo) = 0.0_dp
475  nomo = nomo - 1
476  END IF
477  ELSEIF (total_zeff_corr > 0.0_dp) THEN
478 ! add electron density to the mos
479  delectron = total_zeff_corr - real(mo_set%maxocc, kind=dp)
480  IF (delectron > 0.0_dp) THEN
481  mo_set%occupation_numbers(nomo + 1) = real(mo_set%maxocc, kind=dp)
482  nomo = nomo + 1
483  irmo = ceiling(delectron/real(mo_set%maxocc, kind=dp))
484  DO ir = 1, irmo
485  delectron = delectron - real(mo_set%maxocc, kind=dp)
486  IF (delectron < 0.0_dp) THEN
487  mo_set%occupation_numbers(nomo + ir) = delectron + real(mo_set%maxocc, kind=dp)
488  ELSE
489  mo_set%occupation_numbers(nomo + ir) = real(mo_set%maxocc, kind=dp)
490  END IF
491  END DO
492  nomo = nomo + irmo
493  ELSE
494  mo_set%occupation_numbers(nomo + 1) = total_zeff_corr
495  nomo = nomo + 1
496  END IF
497  END IF
498  END IF
499  END IF
500  nmo = SIZE(mo_set%eigenvalues)
501 
502  cpassert(nmo >= nomo)
503  cpassert((SIZE(mo_set%occupation_numbers) == nmo))
504 
505  mo_set%homo = nomo
506  mo_set%lfomo = nomo + 1
507  mo_set%mu = mo_set%eigenvalues(nomo)
508 
509  ! Check consistency of the array lengths
510  IF (PRESENT(eval_deriv)) THEN
511  equal_size = (SIZE(mo_set%occupation_numbers, 1) == SIZE(eval_deriv, 1))
512  cpassert(equal_size)
513  END IF
514 
515  ! Quick return, if no smearing information is supplied (TO BE FIXED, smear should become non-optional...)
516  IF (.NOT. PRESENT(smear)) THEN
517  ! there is no dependence of the energy on the eigenvalues
518  mo_set%uniform_occupation = .true.
519  IF (PRESENT(eval_deriv)) THEN
520  eval_deriv = 0.0_dp
521  END IF
522  CALL timestop(handle)
523  RETURN
524  END IF
525 
526  ! Check if proper eigenvalues are already available
527  IF (smear%method /= smear_list) THEN
528  IF ((abs(mo_set%eigenvalues(1)) < 1.0e-12_dp) .AND. &
529  (abs(mo_set%eigenvalues(nmo)) < 1.0e-12_dp)) THEN
530  CALL timestop(handle)
531  RETURN
532  END IF
533  END IF
534 
535  ! Perform smearing
536  IF (smear%do_smear) THEN
537  IF (PRESENT(xas_env)) THEN
538  i_first = xas_estate + 1
539  nelec = xas_nelectron
540  ELSE
541  i_first = 1
542  IF (smear%fixed_mag_mom == -1.0_dp) THEN
543  nelec = real(mo_set%nelectron, dp)
544  ELSE
545  nelec = mo_set%n_el_f
546  END IF
547  END IF
548  SELECT CASE (smear%method)
549  CASE (smear_fermi_dirac)
550  IF (.NOT. PRESENT(eval_deriv)) THEN
551  CALL fermifixed(mo_set%occupation_numbers, mo_set%mu, mo_set%kTS, mo_set%eigenvalues, nelec, &
552  smear%electronic_temperature, mo_set%maxocc, xas_estate, occ_estate)
553  ELSE
554  ! could be a relatively large matrix, but one could get rid of it by never storing it
555  ! we only need dE/df * df/de, one could equally parallelize over entries, this could become expensive
556  ALLOCATE (dfde(nmo, nmo))
557  ! lengthscale could become a parameter, but this is pretty good
558  lengthscale = 10*smear%electronic_temperature
559 
560  CALL fermifixedderiv(dfde, mo_set%occupation_numbers, mo_set%mu, mo_set%kTS, mo_set%eigenvalues, nelec, &
561  smear%electronic_temperature, mo_set%maxocc, lengthscale, xas_estate, occ_estate)
562 
563  ! deriv of E_{KS}-kT*S wrt to f_i
564  eval_deriv = eval_deriv - mo_set%eigenvalues + mo_set%mu
565  ! correspondingly the deriv of E_{KS}-kT*S wrt to e_i
566  eval_deriv = matmul(transpose(dfde), eval_deriv)
567 
568  DEALLOCATE (dfde)
569  END IF
570 
571  ! Find the lowest fractional occupied MO (LFOMO)
572  DO imo = i_first, nmo
573  IF (mo_set%occupation_numbers(imo) < mo_set%maxocc) THEN
574  mo_set%lfomo = imo
575  EXIT
576  END IF
577  END DO
578  is_large = abs(maxval(mo_set%occupation_numbers) - mo_set%maxocc) > smear%eps_fermi_dirac
579  ! this is not a real problem, but the temperature might be a bit large
580  IF (is_large) &
581  cpwarn("Fermi-Dirac smearing includes the first MO")
582 
583  ! Find the highest (fractional) occupied MO which will be now the HOMO
584  DO imo = nmo, mo_set%lfomo, -1
585  IF (mo_set%occupation_numbers(imo) > smear%eps_fermi_dirac) THEN
586  mo_set%homo = imo
587  EXIT
588  END IF
589  END DO
590  is_large = abs(minval(mo_set%occupation_numbers)) > smear%eps_fermi_dirac
591  IF (is_large) &
592  CALL cp_warn(__location__, &
593  "Fermi-Dirac smearing includes the last MO => "// &
594  "Add more MOs for proper smearing.")
595 
596  ! check that the total electron count is accurate
597  is_large = (abs(nelec - accurate_sum(mo_set%occupation_numbers(:))) > smear%eps_fermi_dirac*nelec)
598  IF (is_large) &
599  cpwarn("Total number of electrons is not accurate")
600 
601  CASE (smear_energy_window)
602  ! not implemented
603  cpassert(.NOT. PRESENT(eval_deriv))
604 
605  ! Define the energy window for the eigenvalues
606  e1 = mo_set%eigenvalues(mo_set%homo) - 0.5_dp*smear%window_size
607  IF (e1 <= mo_set%eigenvalues(1)) &
608  cpwarn("Energy window for smearing includes the first MO")
609 
610  e2 = mo_set%eigenvalues(mo_set%homo) + 0.5_dp*smear%window_size
611  IF (e2 >= mo_set%eigenvalues(nmo)) &
612  CALL cp_warn(__location__, &
613  "Energy window for smearing includes the last MO => "// &
614  "Add more MOs for proper smearing.")
615 
616  ! Find the lowest fractional occupied MO (LFOMO)
617  DO imo = i_first, nomo
618  IF (mo_set%eigenvalues(imo) > e1) THEN
619  mo_set%lfomo = imo
620  EXIT
621  END IF
622  END DO
623 
624  ! Find the highest fractional occupied (non-zero) MO which will be the HOMO
625  DO imo = nmo, nomo, -1
626  IF (mo_set%eigenvalues(imo) < e2) THEN
627  mo_set%homo = imo
628  EXIT
629  END IF
630  END DO
631 
632  ! Get the number of electrons to be smeared
633  edist = 0.0_dp
634  nelec = 0.0_dp
635 
636  DO imo = mo_set%lfomo, mo_set%homo
637  nelec = nelec + mo_set%occupation_numbers(imo)
638  edist = edist + abs(e2 - mo_set%eigenvalues(imo))
639  END DO
640 
641  ! Smear electrons inside the energy window
642  DO imo = mo_set%lfomo, mo_set%homo
643  edelta = abs(e2 - mo_set%eigenvalues(imo))
644  mo_set%occupation_numbers(imo) = min(mo_set%maxocc, nelec*edelta/edist)
645  nelec = nelec - mo_set%occupation_numbers(imo)
646  edist = edist - edelta
647  END DO
648 
649  CASE (smear_list)
650  equal_size = SIZE(mo_set%occupation_numbers, 1) == SIZE(smear%list, 1)
651  cpassert(equal_size)
652  mo_set%occupation_numbers = smear%list
653  ! there is no dependence of the energy on the eigenvalues
654  IF (PRESENT(eval_deriv)) THEN
655  eval_deriv = 0.0_dp
656  END IF
657  ! most general case
658  mo_set%lfomo = 1
659  mo_set%homo = nmo
660  END SELECT
661 
662  ! Check, if the smearing involves more than one MO
663  IF (mo_set%lfomo == mo_set%homo) THEN
664  mo_set%homo = nomo
665  mo_set%lfomo = nomo + 1
666  ELSE
667  mo_set%uniform_occupation = .false.
668  END IF
669 
670  END IF ! do smear
671 
672  ! zeros don't count as uniform
673  mo_set%uniform_occupation = has_uniform_occupation(mo_set=mo_set)
674 
675  CALL timestop(handle)
676 
677  END SUBROUTINE set_mo_occupation_1
678 
679 END MODULE qs_mo_occupation
static GRID_HOST_DEVICE int modulo(int a, int m)
Equivalent of Fortran's MODULO, which always return a positive number. https://gcc....
Definition: grid_common.h:117
various routines to log and control the output. The idea is that decisions about where to log should ...
deal with the Fermi distribution, compute it, fix mu, get derivs
Definition: fermi_utils.F:13
subroutine, public fermifixedderiv(dfde, f, mu, kTS, e, N, T, maxocc, l, estate, festate)
returns f and dfde for a given set of energies and number of electrons it is a numerical derivative,...
Definition: fermi_utils.F:413
subroutine, public fermifixed(f, mu, kTS, e, N, T, maxocc, estate, festate)
returns occupations according to Fermi-Dirac statistics for a given set of energies and number of ele...
Definition: fermi_utils.F:202
collects all constants needed in input so that they can be used without circular dependencies
integer, parameter, public smear_fermi_dirac
integer, parameter, public smear_energy_window
integer, parameter, public smear_list
sums arrays of real/complex numbers with much reduced round-off as compared to a naive implementation...
Definition: kahan_sum.F:29
Defines the basic variable types.
Definition: kinds.F:23
integer, parameter, public dp
Definition: kinds.F:34
Set occupation of molecular orbitals.
Definition and initialisation of the mo data type.
Definition: qs_mo_types.F:22
subroutine, public set_mo_set(mo_set, maxocc, homo, lfomo, nao, nelectron, n_el_f, nmo, eigenvalues, occupation_numbers, uniform_occupation, kTS, mu, flexible_electron_count)
Set the components of a MO set data structure.
Definition: qs_mo_types.F:452
subroutine, public get_mo_set(mo_set, maxocc, homo, lfomo, nao, nelectron, n_el_f, nmo, eigenvalues, occupation_numbers, mo_coeff, mo_coeff_b, uniform_occupation, kTS, mu, flexible_electron_count)
Get the components of a MO set data structure.
Definition: qs_mo_types.F:397
logical function, public has_uniform_occupation(mo_set, first_mo, last_mo, occupation, tolerance)
Check if the set of MOs in mo_set specifed by the MO index range [first_mo,last_mo] an integer occupa...
Definition: qs_mo_types.F:503
parameters that control an scf iteration
All kind of helpful little routines.
Definition: util.F:14
define create destroy get and put information in xas_env to calculate the x-ray absorption spectra
Definition: xas_env_types.F:15
subroutine, public get_xas_env(xas_env, exc_state, nao, nvirtual, nvirtual2, centers_wfn, atom_of_state, exc_atoms, nexc_states, type_of_state, mykind_of_atom, mykind_of_kind, state_of_atom, spectrum, groundstate_coeff, ostrength_sm, dip_fm_set, excvec_coeff, excvec_overlap, unoccupied_orbs, unoccupied_evals, unoccupied_max_iter, unoccupied_eps, all_vectors, all_evals, my_gto_basis, qs_loc_env, stogto_overlap, occ_estate, xas_nelectron, xas_estate, nexc_atoms, nexc_search, spin_channel, scf_env, scf_control)
...