Octopus
magnetic_constrain.F90
Go to the documentation of this file.
1!! Copyright (C) 2021 N. Tancogne-Dejean
2!!
3!! This program is free software; you can redistribute it and/or modify
4!! it under the terms of the GNU General Public License as published by
5!! the Free Software Foundation; either version 2, or (at your option)
6!! any later version.
7!!
8!! This program is distributed in the hope that it will be useful,
9!! but WITHOUT ANY WARRANTY; without even the implied warranty of
10!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11!! GNU General Public License for more details.
12!!
13!! You should have received a copy of the GNU General Public License
14!! along with this program; if not, write to the Free Software
15!! Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16!! 02110-1301, USA.
17!!
18
19#include "global.h"
20
25 use debug_oct_m
27 use global_oct_m
28 use, intrinsic :: iso_fortran_env
32 use mesh_oct_m
34 use parser_oct_m
36 use space_oct_m
39
40 implicit none
41
42 private
43 public :: &
48
49 integer, public, parameter :: &
50 CONSTRAIN_NONE = 0, &
51 constrain_dir = 1, &
53
54
57 private
58 integer, public :: level = constrain_none
59 real(real64) :: lambda = m_zero
60 real(real64), allocatable :: constrain(:,:)
61 real(real64), allocatable, public :: pot(:,:)
62 real(real64), public :: energy
63
64 real(real64) :: lmm_r
66
67 ! Threshold for considering zero magnetic moments
68 real(real64), parameter :: tol_mag_norm = 1.0e-6_real64
69
70contains
71
72 ! ---------------------------------------------------------
74 subroutine magnetic_constrain_init(this, namespace, mesh, std, natoms, min_dist)
75 type(magnetic_constrain_t), intent(inout) :: this
76 type(namespace_t), intent(in) :: namespace
77 class(mesh_t), intent(in) :: mesh
78 type(states_elec_dim_t), intent(in) :: std
79 integer, intent(in) :: natoms
80 real(real64), intent(in) :: min_dist
81
82 integer :: ia, idir
83 real(real64) :: rmin, norm
84 type(block_t) :: blk
85
87
88 !%Variable MagneticConstrain
89 !%Type integer
90 !%Default no
91 !%Section Hamiltonian
92 !%Description
93 !% This variable selects which magnetic constrain expression is added to the Hamiltonian.
94 !% The <tt>AtomsMagnetDirection</tt> block is used for determining the constrained direction.
95 !%
96 !% This is ignored for independent particles, as in this case, there is nothing that
97 !% determines the magnetic direction.
98 !%
99 !%Option constrain_none 0
100 !% No constrain is added to the Hamiltonian.
101 !%Option constrain_dir 1
102 !% We are adding a constrain for the direction and sign of the magnetic moments only,
103 !% see Ma and Dudarev, PRB 91, 054420 (2015).
104 !%Option constrain_full 2
105 !% We are adding a constrain for the direction and norm of the magnetic moments only,
106 !% see Ma and Dudarev, PRB 91, 054420 (2015).
107 !%End
108 call parse_variable(namespace, 'MagneticConstrain', constrain_none, this%level)
109 call messages_print_var_value('MagneticConstrain', this%level, namespace=namespace)
110 if (this%level == constrain_none) then
112 return
113 end if
114
115 call messages_experimental('MagneticConstrain', namespace=namespace)
116
117 !%Variable MagneticConstrainStrength
118 !%Type float
119 !%Default 0.01
120 !%Section Hamiltonian
121 !%Description
122 !% This variable determines the value of the Lagrange multiplier used for the constrain term.
123 !%End
124 call parse_variable(namespace, 'MagneticConstrainStrength', 0.01_real64, this%lambda)
125 call messages_print_var_value('MagneticConstrainStrength', this%lambda, namespace=namespace)
126
127
128 !We are using the AtomsMagnetDirection block for the contrain
129 if (parse_block(namespace, 'AtomsMagnetDirection', blk) < 0) then
130 message(1) = "AtomsMagnetDirection block is not defined."
131 message(2) = "Magnetic constrained DFT cannot be used without it."
132 call messages_fatal(2, namespace=namespace)
133 end if
134
135 if (parse_block_n(blk) /= natoms) then
136 message(1) = "AtomsMagnetDirection block has the wrong number of rows."
137 call messages_fatal(1, namespace=namespace)
138 end if
139
140 if(std%ispin == unpolarized) then
141 message(1) = "Magnetic constrains can only be used for spin-polized and spinor calculations."
142 call messages_fatal(1, namespace=namespace)
143 end if
144
145 safe_allocate(this%constrain(1:3, natoms))
146 do ia = 1, natoms
147 !Read from AtomsMagnetDirection block
148 if (std%ispin == spin_polarized) then
149 call parse_block_float(blk, ia-1, 0, this%constrain(3, ia))
150 this%constrain(1:2, ia) = m_zero
151 elseif (std%ispin == spinors) then
152 do idir = 1, 3
153 call parse_block_float(blk, ia-1, idir-1, this%constrain(idir, ia))
154 if (abs(this%constrain(idir, ia)) < tol_mag_norm) this%constrain(idir, ia) = m_zero
155 end do
156 end if
158 if(this%level == constrain_dir) then
159 !Renormalization for constrained directions
160 norm = norm2(this%constrain(1:3, ia))
161 this%constrain(1:3, ia) = this%constrain(1:3, ia) / norm
162 end if
163 end do
164 call parse_block_end(blk)
165
166
167 safe_allocate(this%pot(1:mesh%np, 1:std%nspin))
168 this%pot = m_zero
169
170 rmin = min_dist
171 call parse_variable(namespace, 'LocalMagneticMomentsSphereRadius', min(m_half*rmin, 100.0_real64), &
172 this%lmm_r)
173
175 end subroutine magnetic_constrain_init
176
177 ! ---------------------------------------------------------
179 subroutine magnetic_constrain_end(this)
180 type(magnetic_constrain_t), intent(inout) :: this
181
182 push_sub(magnetic_constrain_end)
183
184 safe_deallocate_a(this%constrain)
185 safe_deallocate_a(this%pot)
186
188 end subroutine magnetic_constrain_end
189
190 ! ---------------------------------------------------------
192 subroutine magnetic_constrain_update(this, mesh, std, space, latt, pos, rho)
193 type(magnetic_constrain_t), intent(inout) :: this
194 class(mesh_t), intent(in) :: mesh
195 type(states_elec_dim_t), intent(in) :: std
196 class(space_t), intent(in) :: space
197 type(lattice_vectors_t), intent(in) :: latt
198 real(real64), intent(in) :: pos(:,:)
199 real(real64), intent(in) :: rho(:,:)
200
201 integer :: ia, idir, ip
202 real(real64) :: bb(3), b2, lmm(3), dotp, norm, xx, max_r
203 real(real64), allocatable :: md(:,:), mdf(:), mask(:)
204 type(submesh_t) :: sphere
205 integer :: natoms
206 ! Threshold for computes sinc(x) with a Taylor expansion
207 ! The error in the Taylor expansion is below 8e-11 for this value
208 real(real64), parameter :: tol_sinc = 0.01_real64
209
210 if (this%level == constrain_none) return
211
213
214 call profiling_in(tostring(magnetic_constrain))
215
216 this%pot = m_zero
217 this%energy = m_zero
218
219 safe_allocate(md(1:mesh%np, 1:max(space%dim, 3)))
220 call magnetic_density(mesh, std, rho, md)
221
222 natoms = size(pos, dim=2)
223
224 do ia = 1, natoms
225 call submesh_init(sphere, space, mesh, latt, pos(:, ia), this%lmm_r)
226
227 ! Create a mask to be applied to the magnetic moment, see
228 ! Ma and Dudarev, PRB 91, 054420 (2015)
229 ! This helps the convergence in avoiding jitter at the edge of the sphere,
230 ! and produces a continuous potential
231 max_r = maxval(sphere%r)
232 safe_allocate(mask(1:sphere%np))
233 mask = m_zero
234 !$omp parallel do private(xx)
235 do ip = 1, sphere%np
236 xx = sphere%r(ip) * m_pi / max_r
237 if(xx < tol_sinc) then
238 mask(ip) = m_one-xx*xx/6.0_real64
239 else
240 mask(ip) = sin(xx)/xx
241 end if
242 end do
243
244 ! We compute the local moment here
245 ! We multiply here by a function that decreases monotonically to zero
246 ! at the boundary of the atomic sphere.
247 lmm = m_zero
248 safe_allocate(mdf(1:sphere%np))
249 do idir = 1, max(space%dim, 3)
250 !$omp parallel do
251 do ip = 1, sphere%np
252 mdf(ip) = md(sphere%map(ip), idir) * mask(ip)
253 end do
254 lmm(idir) = dsm_integrate(mesh, sphere, mdf)
255 end do
256 safe_deallocate_a(mdf)
257
258 ! See Ma and Dudarev, PRB 91, 054420 (2015)
259 ! See for instance https://www.vasp.at/wiki/index.php/I_CONSTRAINED_M
260 select case(this%level)
261 case(constrain_dir)
262 norm = norm2(lmm)
263 if (norm > tol_mag_norm) then
264 dotp = dot_product(lmm, this%constrain(1:3, ia))
265 this%energy = this%energy + this%lambda * (norm - dotp)
266 bb = lmm/norm - this%constrain(:,ia)
267 else
268 cycle
269 end if
270 case(constrain_full)
271 this%energy = this%energy + this%lambda * sum((lmm - this%constrain(:,ia))**2)
272 bb = m_two * (lmm - this%constrain(:,ia))
273 end select
274 bb = bb * this%lambda
275
276 ! We are adding an effective Zeeman term within the atomic sphere
277 select case(std%ispin)
278 case (spin_polarized)
279 b2 = norm2(bb(1:max(space%dim, 3)))
280 do ip = 1, sphere%np
281 this%pot(sphere%map(ip), 1) = this%pot(sphere%map(ip), 1) + b2 * mask(ip)
282 this%pot(sphere%map(ip), 2) = this%pot(sphere%map(ip), 2) - b2 * mask(ip)
283 end do
284
285 case (spinors)
286 do ip = 1, sphere%np
287 this%pot(sphere%map(ip), 1) = this%pot(sphere%map(ip), 1) + bb(3) * mask(ip)
288 this%pot(sphere%map(ip), 2) = this%pot(sphere%map(ip), 2) - bb(3) * mask(ip)
289 this%pot(sphere%map(ip), 3) = this%pot(sphere%map(ip), 3) + bb(1) * mask(ip)
290 this%pot(sphere%map(ip), 4) = this%pot(sphere%map(ip), 4) - bb(2) * mask(ip)
291 end do
292
293 end select
294
295 safe_deallocate_a(mask)
296 call submesh_end(sphere)
297 end do
298
299 safe_deallocate_a(md)
300 safe_deallocate_a(mdf)
301
302 call profiling_out(tostring(magnetic_constrain))
303
305 end subroutine magnetic_constrain_update
306
308
309!! Local Variables:
310!! mode: f90
311!! coding: utf-8
312!! End:
Prints out to iunit a message in the form: ["InputVariable" = value] where "InputVariable" is given b...
Definition: messages.F90:180
double sin(double __x) __attribute__((__nothrow__
integer, parameter, public unpolarized
Parameters...
integer, parameter, public spinors
integer, parameter, public spin_polarized
real(real64), parameter, public m_two
Definition: global.F90:190
real(real64), parameter, public m_zero
Definition: global.F90:188
real(real64), parameter, public m_pi
some mathematical constants
Definition: global.F90:186
real(real64), parameter, public m_half
Definition: global.F90:194
real(real64), parameter, public m_one
Definition: global.F90:189
This modules implements the routines for doing constrain DFT for noncollinear magnetism.
subroutine, public magnetic_constrain_end(this)
Releases memory of the magnetic constrain.
subroutine, public magnetic_constrain_update(this, mesh, std, space, latt, pos, rho)
Recomputes the magnetic contraining potential.
integer, parameter, public constrain_full
integer, parameter, public constrain_dir
subroutine, public magnetic_constrain_init(this, namespace, mesh, std, natoms, min_dist)
Initilializes the magnetic_constrain_t object.
subroutine, public magnetic_density(mesh, std, rho, md)
Definition: magnetic.F90:156
This module defines the meshes, which are used in Octopus.
Definition: mesh.F90:118
character(len=256), dimension(max_lines), public message
to be output by fatal, warning
Definition: messages.F90:160
subroutine, public messages_fatal(no_lines, only_root_writes, namespace)
Definition: messages.F90:414
subroutine, public messages_experimental(name, namespace)
Definition: messages.F90:1085
integer function, public parse_block(namespace, name, blk, check_varinfo_)
Definition: parser.F90:618
subroutine, public profiling_out(label)
Increment out counter and sum up difference between entry and exit time.
Definition: profiling.F90:623
subroutine, public profiling_in(label, exclude)
Increment in counter and save entry time.
Definition: profiling.F90:552
This module handles spin dimensions of the states and the k-point distribution.
real(real64) function, public dsm_integrate(mesh, sm, ff, reduce)
Definition: submesh.F90:1165
subroutine, public submesh_end(this)
Definition: submesh.F90:735
subroutine, public submesh_init(this, space, mesh, latt, center, rc)
Definition: submesh.F90:280
Datatype containing the magnetic constrain information.
Describes mesh distribution to nodes.
Definition: mesh.F90:186
class for organizing spins and k-points
A submesh is a type of mesh, used for the projectors in the pseudopotentials It contains points on a ...
Definition: submesh.F90:175