-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathJDFTxCalcBase.pyx
402 lines (330 loc) · 13.9 KB
/
JDFTxCalcBase.pyx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# This file is modified into JDFTxCalcCPU.pyx and JDFTxCalcGPU.pyx
# by setup.py. `{TARGET}` is replaced by `CPU` and `GPU` for these files.
# The following line left blank intentionally
# Author: Yalcin Ozhabes
# email: [email protected]
DEF TARGET = '{TARGET}'
include "JDFTxCalcBase.pxi"
cdef class JDFTxCalc{TARGET}:
""" JDFTxCalc base class.
Defines the interface between Python and C++ objects. None of the inheriting
classes should worry about the unit conversion (ASE uses Angstroms and eV
whereas JDFTx uses Bohrs and Hartrees) or indexing order differences between
ASE objects and JDFTx objects (for example ASE specifies the lattice vectors
as row vectors, in JDFTx they are column vectors).
"""
cdef Everything e "e"
cdef MPIUtil* _mpiUtil "_mpiUtil"
cdef pyComm comm "comm"
cdef cmpi.MPI_Comm _comm "_comm"
cdef FILE* _globalLog "_globalLog"
cdef FILE* _nullLog "_nullLog"
cdef int _nThreads "_nThreads"
cdef IonicMinimizer* imin "imin"
cdef bool didSetupRun "didSetupRun"
#c level functions
def __cinit__(self, *args,**kwargs):
self.didSetupRun = False
IF TARGET == 'GPU':
gpuInit(stdout)
self.e.cntrl.basisKdep = BasisKdep_BasisKpointDep
#coords-type Cartesian
self.e.iInfo.coordsType = CoordsType_CoordsCartesian
#core-overlap-check vector
self.e.iInfo.coreOverlapCondition = Condition_vector
#coulomb-interaction Periodic
self.e.coulombParams.geometry = Geometry_Periodic
#dump End None
self.e.dump.insert(<const pair[DumpFrequency,DumpVariable]>
pair[DumpFrequency,DumpVariable](DumpFreq_End, DumpNone))
#elec-cutoff 20
self.e.cntrl.Ecut = 20.0
self.e.cntrl.EcutRho = 0.0
#elec-eigen-algo Davidson
self.e.cntrl.elecEigenAlgo = ElecEigenDavidson
#elec-ex-corr gga-PBE
#This command does nothing by default, constructor takes care of initialization
#electronic-minimize energyDiffThreshold 1e-08
self.e.elecMinParams.energyDiffThreshold = 1e-8
self.e.elecMinParams.nIterations = 400
#exchange-regularization WignerSeitzTruncated
self.e.coulombParams.exchangeRegularization = XReg_WignerSeitzTruncated
#fluid None
self.e.eVars.fluidParams.fluidType = FluidType_FluidNone
#fluid-ex-corr lda-TF gga-PBE
self.e.eVars.fluidParams.exCorr.kineticType = ExCorr_KineticTF
self.e.eVars.fluidParams.exCorr.exCorrType = ExCorr_ExCorrLDA_PZ
#fluid-gummel-loop 10 1.000000e-05
self.e.cntrl.fluidGummel_nIterations = 10
self.e.cntrl.fluidGummel_Atol = 1e-5
#forces-output-coords Positions
self.e.iInfo.forcesOutputCoords = ForcesOutput_Cartesian
#ion-width 0
self.e.iInfo.ionWidthMethod = IonWidthManual
#ionic-minimize nIterations 0
self.e.ionicMinParams.nIterations = 0
#kpoint 0.000000000000 0.000000000000 0.000000000000 1.00000000000000
# self.kpts = 'Gamma'
# kpoint-folding 1 1 1
for i in range(3):
self.e.eInfo.kfold[i] = 1
# latt-move-scale 1 1 1
for i in range(3):
self.e.cntrl.lattMoveScale[i] = 1.0
# latt-scale 1 1 1
for i in range(3):
self.e.gInfo.lattScale[i] = 1.0
# lcao-params -1 1e-06 0.001
# nIteration is set to 3 by the constructor of ElecVars::LCAO()
self.e.eVars.lcaoIter = 3
self.e.eVars.lcaoTol = 1e-06
self.e.eInfo.kT = 1e-3
# pcm-variant GLSSA13
#reorthogonalize-orbitals 20 1.5
self.e.cntrl.overlapCheckInterval = 20
self.e.cntrl.overlapConditionThreshold = 1.5
# spintype no-spin --> initialized in __init__()
# subspace-rotation-factor 30
self.e.eVars.subspaceRotationFactor = 30.0
# symmetries automatic no
self.e.symm.mode = SymmetryMode_None
self.e.symm.shouldMoveAtoms = False
# self.imin = new IonicMinimizer(self.e)
def __dealloc__(self):
"""finalizeSystem()"""
del self._mpiUtil
fclose(self._nullLog)
if not (self.imin is NULL):
del self.imin
cdef inline void setGlobalsInline(self):
"""
Set the global namespace for the current calculator.
JDFTx has variables in global namespace which we can't replicate for
each calculator. The only way to go is to call a function that modifies
the global namespace everytime the scheduler decides to make progress
with a calculator.
"""
global mpiUtil, globalLog, nullLog, nProcsAvailable
mpiUtil = self._mpiUtil
nProcsAvailable = self._nThreads
if mpiUtil.isHead():
globalLog = self._globalLog
else:
globalLog = self._nullLog
nullLog = self._nullLog
#some getters and setters for easy access from python side
property cell:
"""Lattice vectors in Angstrom. Handles the conversion internally.
Handles both the unit conversion and row major to column major
conversion. It is a 3x3 matrix with row vectors being lattice vectors."""
def __get__(self):
out = np.zeros((3,3), dtype=np.double)
for i in range(3):
for j in range(3):
out[i,j] = <double>self.e.gInfo.R(j,i) * cBohr
return out
def __set__(self, double[:, :] value):
for i in range(3):
for j in range(3):
(&self.e.gInfo.R(j,i))[0] = <double>value[i,j] / cBohr
property spin:
""" In order to match the convention of ASE, skip 0 when counting
1: Unpolarized calculation
2: Spin-polarized calculation
3: Non-collinear magnetism (supports spin-orbit coupling)
4: Non-collinear without magnetization, to allow for spin-orbit"""
def __get__(self):
return self.e.eInfo.spinType + 1
def __set__(self, value):
self.e.eInfo.spinType = value - 1
property dragWavefunctions:
"""Drag wavefunctions when ions are moved using
atomic orbital projections (yes by default)."""
def __get__(self):
return self.e.cntrl.dragWavefunctions
def __set__(self, value):
self.e.cntrl.dragWavefunctions = <bool>value
property kpts:
"""k-points interface with JDFTx
JDFTx takes k-points by explicit coordinates with weights and a grid to
fold the k-points over. ASE has a dft.kpoints module where various
Brillouin-zone sampling schemes are implemented.
kpts: list of tuples of kpoints and weights, all in cell coordinates.
kpts = [(np.asarray(k), w) for k, w in zip(kpoints, weights)]
"""
def __get__(self):
kpts = []
for qnum in self.e.eInfo.qnums:
# kpt = np.ndarray(qnum.k[0], qnum.k[1], qnum.k[2]])
kpt = np.asarray([ qnum.k[0], qnum.k[1], qnum.k[2] ])
weight = qnum.weight
kpts.append((kpt, weight))
return kpts
def __set__(self, kpts):
clearQnums(self.e)
cdef QuantumNumber qnum
if kpts=='Gamma':
addQnum(self.e, np.zeros(3), 1.0)
return
# else:
cdef double[:] kpt
cdef double weight
for kpt, weight in kpts:
addQnum(self.e, kpt, weight)
property settings:
"""Dictionary that holds the initial settings.
The settings are fixed after the first run of setup()."""
def __set__(self, settings):
if self.didSetupRun:
raise AttributeError("Can't change settings after setup() runs.")
if 'Ecut' in settings:
self.e.cntrl.Ecut = <double>settings['Ecut']
if 'kT' in settings:
self.e.eInfo.kT = <double>settings['kT']
self.e.eInfo.mixInterval = 0
if settings['kT'] > 0:
self.e.eInfo.fillingsUpdate = FermiFillingsAux
else:
self.e.eInfo.fillingsUpdate = ConstantFillings
if 'nBands' in settings:
self.e.eInfo.nBands = <int>settings['nBands']
def __get__(self):
settings = dict()
settings['Ecut'] = self.e.cntrl.Ecut
settings['kT'] = self.e.eInfo.kT
settings['nBands'] = self.e.eInfo.nBands
return settings
#python level functions
def __init__(self, comm = None, nThreads = None, **kwargs):
""""""
# set up the parallelization and global values
if comm is None:
self.comm = mpi.COMM_WORLD
elif isinstance(comm, pyComm):
self.comm = comm
else:
raise TypeError("comm has to be of type mpi4py.MPI.Comm")
self._comm = self.comm.ob_mpi
self._mpiUtil = new MPIUtil(self._comm)
if nThreads is None:
self._nThreads = utils.nThreads(self.comm)
elif isinstance(nThreads, int):
self._nThreads = nThreads
else:
raise TypeError("nThreads has to be of type int")
# global variables (global in JDFTx not in pythonJDFTx)
self._nullLog = fopen("/dev/null", "w")
if 'log' in kwargs:
log = kwargs['log']
if log is True:
self._globalLog = stdout
elif isinstance(log, str):
self._globalLog = fopen(pyStrToCharStar(log), "w")
elif (log is False) or (log is None):
self._globalLog = self._nullLog
else:
raise TypeError("log must be one of True, False, None or a string")
self.spin = 1
def setGlobals(self):
self.setGlobalsInline()
def add_ion(self, atom):
"""
"""
cdef char* symbol = pyStrToCharStar(atom.symbol)
cdef string id
cdef vector3[double] pos
id.assign(symbol)
cdef shared_ptr[SpeciesInfo] sp
sp = findSpecies(id, self.e)
invCell = np.linalg.inv(self.cell)
positionInLatticeCoordinates = atom.position.dot(invCell)
for i in range(3):
pos[i] = <double>positionInLatticeCoordinates[i]
if sp != 0:
pass
elif 'pseudopotential' in atom.data:
if atom.data['pseudopotential'].lower() in ['uspp', 'fhi', 'upf']:
pspFile = _makePspPath(atom.symbol, atom.data['pseudopotential'].lower())
elif os.path.exists(atom.data['pseudopotential']):
pspFile = atom.data['pseudopotential']
else:
raise ValueError("Can't find file " + atom.data['pseudopotential'] +
" or unknown format.")
if pspFile.lower().endswith("uspp"):
sp = newSpecies(id, pyStrToCharStar(pspFile), PspFormat_Uspp)
elif pspFile.lower().endswith("fhi"):
sp = newSpecies(id, pyStrToCharStar(pspFile), PspFormat_Fhi)
elif pspFile.lower().endswith("upf"):
sp = newSpecies(id, pyStrToCharStar(pspFile), PspFormat_UPF)
else:
raise ValueError("pseudopotential format is not supported\n" +
pspFile)
self.e.iInfo.species.push_back(sp)
else:
pspFile = _makePspPath(atom.symbol)
sp = newSpecies(id, pyStrToCharStar(pspFile), PspFormat_Uspp)
self.e.iInfo.species.push_back(sp)
deref(sp).atpos.push_back(pos)
cdef Species_Constraint constraint
constraint.moveScale = 0.0
constraint.type = Species_Constraint_None
deref(sp).constraints.push_back(constraint)
def setup(self):
"""
Runs Everything.setup()
"""
if self.didSetupRun:
raise RuntimeError("setup() has already run once")
self.setGlobalsInline()
self.e.setup()
self.didSetupRun = True
def getIonicPositions(self):
"""
Getter for ionic positions.
Returns np.ndarray of ionic positions in cartesian coordinates,
in JDFTx order.
"""
cdef extern vector3[double] operator*(matrix3[double], vector3[double]&)
cdef shared_ptr[SpeciesInfo] sp
atpos = []
for i in range(self.e.iInfo.species.size()):
sp = self.e.iInfo.species[i]
for j in range(deref(sp).atpos.size()):
for k in range(3):
atpos.append((self.e.gInfo.R * deref(sp).atpos[j])[k])
atpos = np.asarray(atpos, dtype = np.double)
atpos.resize((len(atpos)/3,3))
return atpos
def updateIonicPositions(self, double[:,:] dpos):
self.setGlobalsInline()
if self.imin==NULL:
self.imin = new IonicMinimizer(self.e)
cdef IonicGradient d
cdef int row = 0
d.init(self.e.iInfo)
for i in range(d.size()):
for j in range(d[i].size()):
for k in range(3):
d[i][j][k] = dpos[row][k]
row += 1
self.imin.step(d, 1.0)
def runElecMin(self):
self.setGlobalsInline()
if self.imin is NULL:
self.imin = new IonicMinimizer(self.e)
# with nogil:
resumeOperatorThreading()
self.e.iInfo.printPositions(self._globalLog)
self.imin.minimize(self.e.ionicMinParams)
def readTotalEnergy(self):
cdef extern double double(EnergyComponents)
return double(self.e.ener.E)
def readForces(self):
cdef extern vector3[double] operator*(matrix3[double], vector3[double]&)
forces = []
# cdef size_t i, j, k
for i in range(int(self.e.iInfo.forces.size())):
for j in range(int(self.e.iInfo.forces[i].size())):
for k in range(3):
forces.append((self.e.gInfo.invRT * self.e.iInfo.forces[i][j])[k])
return forces