TB2Jflows 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- TB2Jflows/__init__.py +2 -0
- TB2Jflows/ase_siesta.py +425 -0
- TB2Jflows/auto_siesta_TB2J.py +130 -0
- TB2Jflows/run_abacus.py +120 -0
- tb2jflows-0.0.1.dist-info/LICENSE +25 -0
- tb2jflows-0.0.1.dist-info/METADATA +30 -0
- tb2jflows-0.0.1.dist-info/RECORD +9 -0
- tb2jflows-0.0.1.dist-info/WHEEL +5 -0
- tb2jflows-0.0.1.dist-info/top_level.txt +1 -0
TB2Jflows/__init__.py
ADDED
TB2Jflows/ase_siesta.py
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import ase
|
|
7
|
+
from ase.io.jsonio import decode, encode
|
|
8
|
+
from pyDFTutils.siesta import MySiesta
|
|
9
|
+
from TB2J.interfaces import gen_exchange_siesta
|
|
10
|
+
from TB2J.io_merge import merge
|
|
11
|
+
from TB2J.rotate_atoms import rotate_atom_spin, rotate_atom_xyz
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SiestaFlow:
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
atoms,
|
|
18
|
+
basis_set: str = "DZP",
|
|
19
|
+
xc: str = "PBEsol",
|
|
20
|
+
spin: str = "collinear",
|
|
21
|
+
kpts=[6, 6, 6],
|
|
22
|
+
Udict: dict = {},
|
|
23
|
+
root_path="./",
|
|
24
|
+
restart=True,
|
|
25
|
+
metadata={},
|
|
26
|
+
fdf_arguments={},
|
|
27
|
+
split_soc=False,
|
|
28
|
+
**kwargs,
|
|
29
|
+
):
|
|
30
|
+
self.atoms = atoms
|
|
31
|
+
self.basis_set = basis_set
|
|
32
|
+
self.xc = xc
|
|
33
|
+
self.kpts = kpts
|
|
34
|
+
self.Udict = Udict
|
|
35
|
+
# default fdf arguments
|
|
36
|
+
self.fdf_arguments = {
|
|
37
|
+
"MaxSCFIterations": 350,
|
|
38
|
+
"SCF.Mixer.Method": "Pulay",
|
|
39
|
+
"SCF.Mixer.History": 16,
|
|
40
|
+
"SCF.Mixer.Weight": 0.4,
|
|
41
|
+
"SCF.Mix.Spin": "sum",
|
|
42
|
+
"SCF.DM.Tolerance": 1e-4,
|
|
43
|
+
"SCF.EDM.Tolerance": "1e-2 eV",
|
|
44
|
+
"SCF.H.Tolerance": "1e-3 eV",
|
|
45
|
+
"Diag.ParallelOverK": "True",
|
|
46
|
+
}
|
|
47
|
+
if fdf_arguments:
|
|
48
|
+
self.fdf_arguments.update(fdf_arguments)
|
|
49
|
+
self.spin = spin
|
|
50
|
+
self.root_path = root_path
|
|
51
|
+
self.restart = restart
|
|
52
|
+
self.kwargs = kwargs
|
|
53
|
+
|
|
54
|
+
# paths
|
|
55
|
+
self.metadata_path = os.path.join(self.root_path, "metadata.json")
|
|
56
|
+
if not os.path.exists(self.root_path):
|
|
57
|
+
os.makedirs(self.root_path)
|
|
58
|
+
self.relax_path = os.path.join(self.root_path, "relax")
|
|
59
|
+
self.scf_path = os.path.join(self.root_path, "scf")
|
|
60
|
+
|
|
61
|
+
self.relaxed_atoms = None
|
|
62
|
+
self.split_soc = split_soc
|
|
63
|
+
self.initialize_metadata(metadata)
|
|
64
|
+
|
|
65
|
+
def initialize_metadata(self, metadata):
|
|
66
|
+
"""Initialize the metadata.
|
|
67
|
+
If already exist, read from it.
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
if (
|
|
71
|
+
self.restart
|
|
72
|
+
and os.path.exists(self.metadata_path)
|
|
73
|
+
and os.path.isfile(self.metadata_path)
|
|
74
|
+
):
|
|
75
|
+
self.load_metadata()
|
|
76
|
+
else:
|
|
77
|
+
self.metadata = {
|
|
78
|
+
"root_path": self.root_path,
|
|
79
|
+
"calculator": "siesta",
|
|
80
|
+
"initial_atoms": encode(self.atoms),
|
|
81
|
+
"spin": self.spin,
|
|
82
|
+
"xc": self.xc,
|
|
83
|
+
"already_relaxed": False,
|
|
84
|
+
}
|
|
85
|
+
self.metadata.update(metadata)
|
|
86
|
+
|
|
87
|
+
def load_metadata(self):
|
|
88
|
+
with open(self.metadata_path, "r") as myfile:
|
|
89
|
+
self.metadata = json.load(myfile)
|
|
90
|
+
self.initial_atoms = decode(self.metadata["initial_atoms"])
|
|
91
|
+
if ("relaxed_atoms" in self.metadata) and (
|
|
92
|
+
self.metadata["relaxed_atoms"] is not None
|
|
93
|
+
):
|
|
94
|
+
self.relaxed_atoms = decode(self.metadata["relaxed_atoms"])
|
|
95
|
+
|
|
96
|
+
def write_metadata(self):
|
|
97
|
+
with open(self.metadata_path, "w") as myfile:
|
|
98
|
+
json.dump(self.metadata, myfile)
|
|
99
|
+
|
|
100
|
+
def update_metadata(self, d):
|
|
101
|
+
self.initialize_metadata(d)
|
|
102
|
+
self.write_metadata()
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def spin(self):
|
|
106
|
+
return self._spin
|
|
107
|
+
|
|
108
|
+
@spin.setter
|
|
109
|
+
def spin(self, spin):
|
|
110
|
+
self._spin = spin
|
|
111
|
+
if spin == "spin-orbit":
|
|
112
|
+
self.fdf_arguments.update({"SCF.Mix": "hamiltonian", "WriteOrbMom": True})
|
|
113
|
+
else:
|
|
114
|
+
if "writeOrbMom" in self.fdf_arguments:
|
|
115
|
+
self.fdf_arguments.pop("WriteOrbMom")
|
|
116
|
+
if spin == "spin-orbit":
|
|
117
|
+
self.rel = "fr"
|
|
118
|
+
else:
|
|
119
|
+
self.rel = "sr"
|
|
120
|
+
|
|
121
|
+
def get_calculator(
|
|
122
|
+
self,
|
|
123
|
+
atoms: ase.Atoms,
|
|
124
|
+
path="./",
|
|
125
|
+
label="siesta",
|
|
126
|
+
fdf_arguments={},
|
|
127
|
+
):
|
|
128
|
+
fdf_args = copy.deepcopy(self.fdf_arguments)
|
|
129
|
+
fdf_args.update(fdf_arguments)
|
|
130
|
+
calc = MySiesta(
|
|
131
|
+
atoms=atoms,
|
|
132
|
+
label=label,
|
|
133
|
+
xc=self.xc,
|
|
134
|
+
basis_set=self.basis_set,
|
|
135
|
+
kpts=self.kpts,
|
|
136
|
+
spin=self.spin,
|
|
137
|
+
fdf_arguments=fdf_args,
|
|
138
|
+
**self.kwargs,
|
|
139
|
+
)
|
|
140
|
+
if self.Udict:
|
|
141
|
+
# calc.set_Hubbard_U(self.Udict)
|
|
142
|
+
calc.set_Udict(self.Udict)
|
|
143
|
+
calc.directory = path
|
|
144
|
+
calc.atoms = atoms
|
|
145
|
+
return copy.deepcopy(calc)
|
|
146
|
+
|
|
147
|
+
def relax(
|
|
148
|
+
self,
|
|
149
|
+
atoms,
|
|
150
|
+
use_collinear=True,
|
|
151
|
+
path="./relax",
|
|
152
|
+
label="siesta",
|
|
153
|
+
TypeOfRun="Broyden",
|
|
154
|
+
VariableCell=True,
|
|
155
|
+
ConstantVolume=False,
|
|
156
|
+
RelaxCellOnly=False,
|
|
157
|
+
MaxForceTol=0.001,
|
|
158
|
+
MaxStressTol=0.1,
|
|
159
|
+
NumCGSteps=200,
|
|
160
|
+
):
|
|
161
|
+
if (
|
|
162
|
+
self.restart
|
|
163
|
+
and self.metadata["already_relaxed"]
|
|
164
|
+
and self.relaxed_atoms is not None
|
|
165
|
+
):
|
|
166
|
+
self.load_metadata()
|
|
167
|
+
atoms = self.relaxed_atoms
|
|
168
|
+
else:
|
|
169
|
+
old_spin = self.spin
|
|
170
|
+
if use_collinear:
|
|
171
|
+
self.spin = "collinear"
|
|
172
|
+
calc = self.get_calculator(atoms, path=self.relax_path, label=label)
|
|
173
|
+
calc.atoms = atoms
|
|
174
|
+
atoms = calc.relax(
|
|
175
|
+
atoms,
|
|
176
|
+
TypeOfRun=TypeOfRun,
|
|
177
|
+
VariableCell=VariableCell,
|
|
178
|
+
ConstantVolume=ConstantVolume,
|
|
179
|
+
RelaxCellOnly=RelaxCellOnly,
|
|
180
|
+
MaxForceTol=MaxForceTol,
|
|
181
|
+
MaxStressTol=MaxStressTol,
|
|
182
|
+
NumCGSteps=NumCGSteps,
|
|
183
|
+
)
|
|
184
|
+
self.spin = old_spin
|
|
185
|
+
self.relaxed_atoms = atoms
|
|
186
|
+
self.update_metadata(
|
|
187
|
+
{
|
|
188
|
+
"already_relaxed": True,
|
|
189
|
+
"relax_path": self.relax_path,
|
|
190
|
+
"relaxed_atoms": encode(self.relaxed_atoms),
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
return atoms
|
|
194
|
+
|
|
195
|
+
def scf_calculation(self, atoms, path="./scf", label="siesta"):
|
|
196
|
+
print(f"SCF calculation in {path}")
|
|
197
|
+
HS_args = {
|
|
198
|
+
"SaveHS": True,
|
|
199
|
+
#'CDF.Save': True,
|
|
200
|
+
#'CDF.Compress': 9,
|
|
201
|
+
}
|
|
202
|
+
calc = self.get_calculator(atoms, path=path, label=label, fdf_arguments=HS_args)
|
|
203
|
+
calc.get_potential_energy()
|
|
204
|
+
|
|
205
|
+
def scf_calculation_with_rotations(
|
|
206
|
+
self, atoms, label="siesta", rotate_type="structure"
|
|
207
|
+
):
|
|
208
|
+
self.fdf_arguments["Spin.Fix"] = False
|
|
209
|
+
self.spin = "spin-orbit"
|
|
210
|
+
if rotate_type == "structure":
|
|
211
|
+
atoms_xyz = rotate_atom_xyz(atoms)
|
|
212
|
+
elif rotate_type == "spin":
|
|
213
|
+
atoms_xyz = rotate_atom_spin(atoms)
|
|
214
|
+
else:
|
|
215
|
+
raise NotImplementedError(f"rotate_type={rotate_type} is not implemented")
|
|
216
|
+
for ratoms, rot in zip(atoms_xyz, ("x", "y", "z")):
|
|
217
|
+
self.scf_calculation(
|
|
218
|
+
ratoms, path=os.path.join(self.scf_path, rot), label=label
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def scf_calculation_single_noncollinear(self, atoms, label="siesta"):
|
|
222
|
+
self.fdf_arguments["Spin.Fix"] = False
|
|
223
|
+
self.scf_calculation(
|
|
224
|
+
atoms, path=os.path.join(self.scf_path, "single_noncollinear"), label=label
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def scf_calculatoin_split_soc(self, atoms, label="siesta", nscf=False):
|
|
228
|
+
fdf_args = {
|
|
229
|
+
"SOC_split_SR_SO": True,
|
|
230
|
+
#"Spin.OrbitStrength": 3,
|
|
231
|
+
"SaveHS.so": True,
|
|
232
|
+
"SaveHS": True,
|
|
233
|
+
"Spin.Fix": False,
|
|
234
|
+
}
|
|
235
|
+
nscf_args={"SCF.DM.Converge": True,
|
|
236
|
+
"SCF.DM.Tolerance": 1e4,
|
|
237
|
+
"SCF.H.Converge": False,
|
|
238
|
+
"SCF.EDM.Converge": False,
|
|
239
|
+
"SCF.Mix.First": False,
|
|
240
|
+
"SCF.Mix": "density",
|
|
241
|
+
"MaxSCFIterations": 1,
|
|
242
|
+
}
|
|
243
|
+
if nscf:
|
|
244
|
+
fdf_args.update(nscf_args)
|
|
245
|
+
|
|
246
|
+
self.spin = "spin-orbit"
|
|
247
|
+
path = Path(self.scf_path) / "split_soc"
|
|
248
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
249
|
+
collinear_path = Path(self.scf_path) / "collinear"
|
|
250
|
+
# density matrix from collinear calculation
|
|
251
|
+
# copy density matrix from collinear calculation
|
|
252
|
+
dmfile = collinear_path / f"{label}.DM"
|
|
253
|
+
if dmfile.exists():
|
|
254
|
+
dmfile_new = path / f"{label}.DM"
|
|
255
|
+
print(f"Copying {dmfile} to {dmfile_new}")
|
|
256
|
+
os.system(f"cp {dmfile} {dmfile_new}")
|
|
257
|
+
calc = self.get_calculator(
|
|
258
|
+
atoms, path=path, label=label, fdf_arguments=fdf_args
|
|
259
|
+
)
|
|
260
|
+
calc.get_potential_energy()
|
|
261
|
+
|
|
262
|
+
def scf_calculation_collinear(self, atoms, label="siesta"):
|
|
263
|
+
old_spin = self.spin
|
|
264
|
+
self.spin = "collinear"
|
|
265
|
+
self.scf_calculation(
|
|
266
|
+
atoms, path=os.path.join(self.scf_path, "collinear"), label=label
|
|
267
|
+
)
|
|
268
|
+
self.spin = old_spin
|
|
269
|
+
|
|
270
|
+
def run_TB2J_collinear(self, **kwargs):
|
|
271
|
+
path = os.path.join(self.scf_path, "collinear")
|
|
272
|
+
fdf_fname = os.path.join(path, "siesta.fdf")
|
|
273
|
+
gen_exchange_siesta(
|
|
274
|
+
fdf_fname=fdf_fname,
|
|
275
|
+
**kwargs,
|
|
276
|
+
output_path=os.path.join(self.root_path, "TB2J_results_collinear"),
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
def run_TB2J_single_noncollinear(self, **kwargs):
|
|
280
|
+
path = os.path.join(self.scf_path, "single_noncollinear")
|
|
281
|
+
fdf_fname = os.path.join(path, "siesta.fdf")
|
|
282
|
+
gen_exchange_siesta(
|
|
283
|
+
fdf_fname=fdf_fname,
|
|
284
|
+
**kwargs,
|
|
285
|
+
output_path=os.path.join(
|
|
286
|
+
self.root_path, "TB2J_results_single_noncollinear"
|
|
287
|
+
),
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
def set_nonscf_params(self):
|
|
291
|
+
nscf_params={"SCF.DM.Converge": False,
|
|
292
|
+
"SCF.DM.Tolerance": 1e4,
|
|
293
|
+
"SCF.H.Converge": False,
|
|
294
|
+
"SCF.EDM.Converge": False,
|
|
295
|
+
"SCF.Mix.First": False,
|
|
296
|
+
"SCF.Mix": "density",
|
|
297
|
+
"MaxSCFIterations": 1,
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
def run_TB2J_split_soc(self, **kwargs):
|
|
301
|
+
path = os.path.join(self.scf_path, "split_soc")
|
|
302
|
+
fdf_fname = os.path.join(path, "siesta.fdf")
|
|
303
|
+
gen_exchange_siesta(
|
|
304
|
+
fdf_fname=fdf_fname,
|
|
305
|
+
read_H_soc=True,
|
|
306
|
+
**kwargs,
|
|
307
|
+
output_path=os.path.join(self.root_path, "TB2J_results_split_soc"),
|
|
308
|
+
)
|
|
309
|
+
# merge results
|
|
310
|
+
paths = [
|
|
311
|
+
os.path.join(self.root_path, f"TB2J_results_split_soc_{rot}")
|
|
312
|
+
for rot in ("x", "y", "z")
|
|
313
|
+
]
|
|
314
|
+
merge(
|
|
315
|
+
*paths,
|
|
316
|
+
# method=rotate_type,
|
|
317
|
+
write_path=os.path.join(self.root_path, "TB2J_results_merged"),
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
def run_TB2J(self, skip=False, **kwargs):
|
|
321
|
+
paths = []
|
|
322
|
+
for rot in ("x", "y", "z"):
|
|
323
|
+
path = os.path.join(self.scf_path, rot)
|
|
324
|
+
paths.append(path)
|
|
325
|
+
TB2J_path = os.path.join(self.root_path, f"TB2J_results_{rot}")
|
|
326
|
+
if not (skip and os.path.exists(os.path.join(TB2J_path, "exchange.txt"))):
|
|
327
|
+
fdf_fname = os.path.join(path, "siesta.fdf")
|
|
328
|
+
gen_exchange_siesta(
|
|
329
|
+
fdf_fname=fdf_fname, **kwargs, output_path=TB2J_path
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
def run_TB2J_rotate_structure(self, skip=False, **kwargs):
|
|
333
|
+
paths = []
|
|
334
|
+
for rot in ("x", "y", "z"):
|
|
335
|
+
path = os.path.join(self.scf_path, rot)
|
|
336
|
+
paths.append(path)
|
|
337
|
+
TB2J_path = os.path.join(self.root_path, f"TB2J_results_{rot}")
|
|
338
|
+
if not (skip and os.path.exists(os.path.join(TB2J_path, "exchange.txt"))):
|
|
339
|
+
fdf_fname = os.path.join(path, "siesta.fdf")
|
|
340
|
+
gen_exchange_siesta(
|
|
341
|
+
fdf_fname=fdf_fname, **kwargs, output_path=TB2J_path
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
def run_TB2J_merge(self, rotate_type="structure"):
|
|
345
|
+
paths = [
|
|
346
|
+
os.path.join(self.root_path, f"TB2J_results_{rot}")
|
|
347
|
+
for rot in ("x", "y", "z")
|
|
348
|
+
]
|
|
349
|
+
merge(
|
|
350
|
+
*paths,
|
|
351
|
+
# method=rotate_type,
|
|
352
|
+
write_path=os.path.join(self.root_path, "TB2J_results_merged"),
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def runall_collinear(self, atoms, relax=True, scf=True, TB2J=True, **kwargs):
|
|
356
|
+
if relax:
|
|
357
|
+
atoms = self.relax(atoms)
|
|
358
|
+
if scf:
|
|
359
|
+
self.scf_calculation_collinear(atoms, label="siesta")
|
|
360
|
+
if TB2J:
|
|
361
|
+
self.run_TB2J_collinear(**kwargs)
|
|
362
|
+
|
|
363
|
+
def runall_nc(
|
|
364
|
+
self, atoms, relax=True, scf=True, TB2J=True, rotate_type="structure", **kwargs
|
|
365
|
+
):
|
|
366
|
+
if relax:
|
|
367
|
+
atoms = self.relax(atoms)
|
|
368
|
+
if scf:
|
|
369
|
+
self.scf_calculation_with_rotations(
|
|
370
|
+
atoms, rotate_type=rotate_type, label="siesta"
|
|
371
|
+
)
|
|
372
|
+
if TB2J:
|
|
373
|
+
self.run_TB2J(**kwargs)
|
|
374
|
+
self.run_TB2J_merge(rotate_type=rotate_type)
|
|
375
|
+
|
|
376
|
+
def runall_split_soc(self, atoms, relax=True, scf=True, TB2J=True, **kwargs):
|
|
377
|
+
if relax:
|
|
378
|
+
atoms = self.relax(atoms)
|
|
379
|
+
if scf:
|
|
380
|
+
self.scf_calculatoin_split_soc(atoms, label="siesta")
|
|
381
|
+
if TB2J:
|
|
382
|
+
self.run_TB2J_split_soc(**kwargs)
|
|
383
|
+
|
|
384
|
+
def runall(
|
|
385
|
+
self, atoms, relax=True, scf=True, TB2J=True, rotate_type="structure", **kwargs
|
|
386
|
+
):
|
|
387
|
+
if self.spin == "collinear":
|
|
388
|
+
self.runall_collinear(atoms, relax=relax, scf=scf, TB2J=TB2J, **kwargs)
|
|
389
|
+
elif self.spin == "spin-orbit+onsite":
|
|
390
|
+
self.runall_nc(
|
|
391
|
+
atoms,
|
|
392
|
+
relax=relax,
|
|
393
|
+
scf=scf,
|
|
394
|
+
TB2J=TB2J,
|
|
395
|
+
rotate_type=rotate_type,
|
|
396
|
+
**kwargs,
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
elif self.spin == "spin-orbit":
|
|
400
|
+
if self.split_soc:
|
|
401
|
+
self.runall_split_soc(atoms, relax=relax, scf=scf, TB2J=TB2J, **kwargs)
|
|
402
|
+
else:
|
|
403
|
+
self.runall_nc(
|
|
404
|
+
atoms,
|
|
405
|
+
relax=relax,
|
|
406
|
+
scf=scf,
|
|
407
|
+
TB2J=TB2J,
|
|
408
|
+
rotate_type=rotate_type,
|
|
409
|
+
**kwargs,
|
|
410
|
+
)
|
|
411
|
+
elif self.spin == "collinear+spin-orbit":
|
|
412
|
+
self.runall_collinear(atoms, relax=relax, scf=scf, TB2J=TB2J, **kwargs)
|
|
413
|
+
if self.split_soc:
|
|
414
|
+
self.runall_split_soc(atoms, relax=relax, scf=scf, TB2J=TB2J, **kwargs)
|
|
415
|
+
else:
|
|
416
|
+
self.runall_nc(
|
|
417
|
+
atoms,
|
|
418
|
+
relax=relax,
|
|
419
|
+
scf=scf,
|
|
420
|
+
TB2J=TB2J,
|
|
421
|
+
rotate_type=rotate_type,
|
|
422
|
+
**kwargs,
|
|
423
|
+
)
|
|
424
|
+
else:
|
|
425
|
+
raise NotImplementedError(f"spin={self.spin} is not implemented in runall")
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import numpy as np
|
|
3
|
+
from ase.io import read
|
|
4
|
+
from ase.units import Ry
|
|
5
|
+
|
|
6
|
+
from TB2Jflows import SiestaFlow
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def atoms_mag_along_z(atoms, mag, toz=True):
|
|
10
|
+
symbols = atoms.get_chemical_symbols()
|
|
11
|
+
symbols = np.array(symbols)
|
|
12
|
+
|
|
13
|
+
norm = np.linalg.norm(mag, axis=1)
|
|
14
|
+
imaxmag = np.argmax(norm)
|
|
15
|
+
ipolarized = np.where(norm > 0.1)
|
|
16
|
+
|
|
17
|
+
elems = set(symbols[ipolarized])
|
|
18
|
+
|
|
19
|
+
# rotate to collinear
|
|
20
|
+
if toz:
|
|
21
|
+
m_col = np.zeros(mag.shape[0], dtype=float)
|
|
22
|
+
m_ref = mag[imaxmag]
|
|
23
|
+
for i, m in enumerate(mag):
|
|
24
|
+
mrot = m @ m_ref
|
|
25
|
+
n = np.linalg.norm(m)
|
|
26
|
+
m_col[i] = n if mrot > 0 else -n
|
|
27
|
+
atoms.set_initial_magnetic_moments(None)
|
|
28
|
+
else:
|
|
29
|
+
atoms.set_initial_magnetic_moments(mag)
|
|
30
|
+
return atoms, elems
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def read_to_collinear_mag_atoms(name):
|
|
34
|
+
path = f"../structures/{name}"
|
|
35
|
+
mag = np.load(f"{path}/mag.npy")
|
|
36
|
+
atoms = read(f"{path}/POSCAR.vasp")
|
|
37
|
+
atoms, elems = atoms_mag_along_z(atoms, mag)
|
|
38
|
+
return atoms, elems
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def auto_siesta_TB2J(
|
|
42
|
+
path,
|
|
43
|
+
atoms,
|
|
44
|
+
spin,
|
|
45
|
+
elems,
|
|
46
|
+
Udict={},
|
|
47
|
+
xc="PBE",
|
|
48
|
+
kmesh=None,
|
|
49
|
+
split_soc=False,
|
|
50
|
+
relax=False,
|
|
51
|
+
scf=True,
|
|
52
|
+
TB2J=True,
|
|
53
|
+
rotate_type="structure",
|
|
54
|
+
fincore=True,
|
|
55
|
+
siesta_kwargs={},
|
|
56
|
+
TB2J_kwargs={},
|
|
57
|
+
fdf_kwargs={},
|
|
58
|
+
):
|
|
59
|
+
# mag_elems = list(elems)
|
|
60
|
+
symbols = atoms.get_chemical_symbols()
|
|
61
|
+
cell = atoms.get_cell_lengths_and_angles()
|
|
62
|
+
Uname = {}
|
|
63
|
+
sset = set()
|
|
64
|
+
for s in symbols:
|
|
65
|
+
if s not in sset:
|
|
66
|
+
sset.add(s)
|
|
67
|
+
Uname[s] = s + f".{len(sset)}"
|
|
68
|
+
if kmesh is None:
|
|
69
|
+
kmesh = [int(40 // cell[0] + 1), int(40 // cell[1] + 1), int(40 // cell[2] + 1)]
|
|
70
|
+
|
|
71
|
+
fdf_arguments = {
|
|
72
|
+
"SCF.DM.Tolerance": "0.0001",
|
|
73
|
+
"ElectronicTemperature": "100 K",
|
|
74
|
+
"SCF.Mixer.Weight": "0.1",
|
|
75
|
+
"SCF.Mixer.History": "16",
|
|
76
|
+
"SCF.Mix.Spin": "sum",
|
|
77
|
+
"DM.NumberPulay": "6",
|
|
78
|
+
"SCF.Mixer.Method": "Pulay",
|
|
79
|
+
"MaxSCFIterations": 500,
|
|
80
|
+
"SCF.MustConverge": "False",
|
|
81
|
+
"SCFMustConverge": "False",
|
|
82
|
+
"WriteMullikenPop": "1",
|
|
83
|
+
"MullikenInSCF": "True",
|
|
84
|
+
"WriteHirshfeldPop": "True",
|
|
85
|
+
"WriteVoronoiPop": "True",
|
|
86
|
+
"CDF.save": "True",
|
|
87
|
+
}
|
|
88
|
+
fdf_arguments.update(fdf_kwargs)
|
|
89
|
+
flow = SiestaFlow(
|
|
90
|
+
atoms=atoms,
|
|
91
|
+
xc=xc,
|
|
92
|
+
spin=spin,
|
|
93
|
+
restart=True,
|
|
94
|
+
root_path=path,
|
|
95
|
+
kpts=kmesh,
|
|
96
|
+
mesh_cutoff=600 * Ry,
|
|
97
|
+
energy_shift=0.1,
|
|
98
|
+
fdf_arguments=fdf_arguments,
|
|
99
|
+
fincore=fincore,
|
|
100
|
+
Udict=Udict,
|
|
101
|
+
split_soc=split_soc,
|
|
102
|
+
**siesta_kwargs,
|
|
103
|
+
)
|
|
104
|
+
flow.write_metadata()
|
|
105
|
+
flow.runall(
|
|
106
|
+
atoms,
|
|
107
|
+
relax=relax,
|
|
108
|
+
scf=scf,
|
|
109
|
+
TB2J=TB2J,
|
|
110
|
+
rotate_type=rotate_type,
|
|
111
|
+
magnetic_elements=elems,
|
|
112
|
+
**TB2J_kwargs,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
if __name__ == "__main__":
|
|
117
|
+
atoms = None
|
|
118
|
+
elems = None
|
|
119
|
+
path = "./"
|
|
120
|
+
auto_siesta_TB2J(
|
|
121
|
+
path,
|
|
122
|
+
atoms,
|
|
123
|
+
spin="collinear",
|
|
124
|
+
elems=elems,
|
|
125
|
+
Udict={},
|
|
126
|
+
kmesh=None,
|
|
127
|
+
relax=False,
|
|
128
|
+
scf=True,
|
|
129
|
+
rotate_type="structure",
|
|
130
|
+
)
|
TB2Jflows/run_abacus.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from ase import Atoms
|
|
2
|
+
from ase.calculators.abacus import Abacus, AbacusProfile
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import shutil
|
|
5
|
+
|
|
6
|
+
abacus = "/home/hexu/.local/bin/abacus"
|
|
7
|
+
profile = AbacusProfile(argv=["mpirun", "-n", "8", abacus])
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def gen_atoms():
|
|
11
|
+
"""
|
|
12
|
+
2D Fe slab
|
|
13
|
+
"""
|
|
14
|
+
atoms = Atoms(
|
|
15
|
+
"Fe", scaled_positions=[(0, 0, 0)], cell=(2.315, 2.315, 15), pbc=(1, 1, 1)
|
|
16
|
+
)
|
|
17
|
+
return atoms
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def gen_atoms_x():
|
|
21
|
+
"""
|
|
22
|
+
2D Fe slab
|
|
23
|
+
"""
|
|
24
|
+
atoms = Atoms(
|
|
25
|
+
"Fe", scaled_positions=[(0, 0, 0)], cell=(15, 2.315, 2.315), pbc=(1, 1, 1)
|
|
26
|
+
)
|
|
27
|
+
return atoms
|
|
28
|
+
|
|
29
|
+
pdir = "/home/hexu/.local/pp/abacus/ABACUS-orbitals-main/Dojo-NC-FR"
|
|
30
|
+
|
|
31
|
+
default_params = dict(
|
|
32
|
+
profile=profile,
|
|
33
|
+
directory="Fe_soc0_x",
|
|
34
|
+
pseudo_dir=f"{pdir}/Pseudopotential",
|
|
35
|
+
orbital_dir=f"{pdir}/Orbitals/Fe/Orbital_Fe_DZP",
|
|
36
|
+
pp={"Fe": "Fe.upf"},
|
|
37
|
+
basis={"Fe": "Fe_gga_10au_100Ry_4s2p2d1f.orb"},
|
|
38
|
+
calculation="scf",
|
|
39
|
+
xc="PBE",
|
|
40
|
+
kpts=(9, 9, 1),
|
|
41
|
+
nspin=4,
|
|
42
|
+
symmetry=0,
|
|
43
|
+
noncolin=1,
|
|
44
|
+
lspinorb=1,
|
|
45
|
+
ecutwfc=100,
|
|
46
|
+
scf_thr=1.0e-6,
|
|
47
|
+
init_chg="atomic",
|
|
48
|
+
out_mul=1,
|
|
49
|
+
out_chg=1,
|
|
50
|
+
out_dos=0,
|
|
51
|
+
out_band=0,
|
|
52
|
+
out_wfc_lcao=1,
|
|
53
|
+
out_mat_hs2=1, # output H(R) and S(R) matrix
|
|
54
|
+
ks_solver="scalapack_gvx",
|
|
55
|
+
scf_nmax=500,
|
|
56
|
+
out_bandgap=0,
|
|
57
|
+
basis_type="lcao",
|
|
58
|
+
gamma_only=0,
|
|
59
|
+
smearing_method="gaussian",
|
|
60
|
+
smearing_sigma=0.01,
|
|
61
|
+
mixing_type="broyden",
|
|
62
|
+
mixing_beta=0.5,
|
|
63
|
+
soc_lambda=1.0,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
params_nosoc = default_params.copy()
|
|
67
|
+
params_nosoc.update(dict(
|
|
68
|
+
soc_lambda=0.0,
|
|
69
|
+
noncolin=1,
|
|
70
|
+
))
|
|
71
|
+
|
|
72
|
+
params_soc_nscf = default_params.copy()
|
|
73
|
+
params_soc_nscf.update(dict(
|
|
74
|
+
calculation="scf",
|
|
75
|
+
soc_lambda=1.0,
|
|
76
|
+
init_chg="file",
|
|
77
|
+
init_wfc="file",
|
|
78
|
+
out_chg=0,
|
|
79
|
+
out_wfc_lcao=0,
|
|
80
|
+
scf_nmax=1,
|
|
81
|
+
mixing_beta=1e-6,
|
|
82
|
+
scf_thr=1e6,
|
|
83
|
+
))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def run_abacus(root="Fe_x", M=[[3, 0, 0]]):
|
|
87
|
+
atoms = gen_atoms()
|
|
88
|
+
atoms.set_initial_magnetic_moments(M)
|
|
89
|
+
|
|
90
|
+
path0=Path(root)/"soc0"
|
|
91
|
+
path1=Path(root)/"soc1"
|
|
92
|
+
|
|
93
|
+
params = params_nosoc.copy()
|
|
94
|
+
params.update(directory=path0)
|
|
95
|
+
|
|
96
|
+
calc = Abacus(**params)
|
|
97
|
+
atoms.set_calculator(calc)
|
|
98
|
+
atoms.get_potential_energy()
|
|
99
|
+
|
|
100
|
+
path1.mkdir(parents=True, exist_ok=True)
|
|
101
|
+
if (path1/"OUT.ABACUS").exists():
|
|
102
|
+
shutil.rmtree(str(path1/"OUT.ABACUS"))
|
|
103
|
+
shutil.copytree(str(path0/"OUT.ABACUS"), str(path1/"OUT.ABACUS"))
|
|
104
|
+
|
|
105
|
+
params = params_soc_nscf.copy()
|
|
106
|
+
params.update(directory=path1)
|
|
107
|
+
calc = Abacus(**params)
|
|
108
|
+
atoms.set_calculator(calc)
|
|
109
|
+
atoms.get_potential_energy()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def main():
|
|
113
|
+
run_abacus("Fe_x", [[3, 0, 0]])
|
|
114
|
+
run_abacus("Fe_z", [[0, 0, 3]])
|
|
115
|
+
run_abacus("Fe_-z", [[0, 0, -3]])
|
|
116
|
+
run_abacus("Fe_-x", [[-3, 0, 0]])
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
main()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020, Xu He
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
20
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
21
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
22
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
23
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
24
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
25
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: TB2Jflows
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: TB2Jflows: Workflows for automatically calculation of exchange parameters using TB2J
|
|
5
|
+
Author: Xu He
|
|
6
|
+
Author-email: mailhexu@gmail.com
|
|
7
|
+
License: BSD-2-clause
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
14
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
15
|
+
Requires-Python: >=3.6
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: TB2J
|
|
18
|
+
Requires-Dist: ase
|
|
19
|
+
Requires-Dist: sisl
|
|
20
|
+
Requires-Dist: pyDFTutils
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: author-email
|
|
23
|
+
Dynamic: classifier
|
|
24
|
+
Dynamic: description
|
|
25
|
+
Dynamic: license
|
|
26
|
+
Dynamic: requires-dist
|
|
27
|
+
Dynamic: requires-python
|
|
28
|
+
Dynamic: summary
|
|
29
|
+
|
|
30
|
+
TB2Jflows: Workflows for automatically calculation of exchange parameters using TB2J
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
TB2Jflows/__init__.py,sha256=0aRVj9v-u64LxJl7Cyxcr4YUjpeMkBGPuv-nJbxbHOg,100
|
|
2
|
+
TB2Jflows/ase_siesta.py,sha256=BagiujWCwUZ6Aj03oRFdAL1DzJ4pw1_upW4G5d9tMXw,14363
|
|
3
|
+
TB2Jflows/auto_siesta_TB2J.py,sha256=c5dviznEbXyO3BJbh-LNBlC9efT7YbdC7ydWxyCJZ5E,3113
|
|
4
|
+
TB2Jflows/run_abacus.py,sha256=96tZ2n02FFLl8IZf4W_4TvkCcsd6oS1FVEIqhZpY76o,2692
|
|
5
|
+
tb2jflows-0.0.1.dist-info/LICENSE,sha256=KNu68sa-XR_2jZJKhDcSnxoNve8jtHgkw_w9PjP1YOk,1315
|
|
6
|
+
tb2jflows-0.0.1.dist-info/METADATA,sha256=avwrwge2xfv6PgTUy92JCpBdqUFrJNbiNlQpurzX7Ss,947
|
|
7
|
+
tb2jflows-0.0.1.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
|
|
8
|
+
tb2jflows-0.0.1.dist-info/top_level.txt,sha256=iYRLHB7ZeHb59fEZLnbqJDymBKWPqfVgmvqd9S51Txw,10
|
|
9
|
+
tb2jflows-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
TB2Jflows
|