• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

materialsproject / pymatgen / 4075885785

pending completion
4075885785

push

github

Shyue Ping Ong
Merge branch 'master' of github.com:materialsproject/pymatgen

96 of 96 new or added lines in 27 files covered. (100.0%)

81013 of 102710 relevant lines covered (78.88%)

0.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

51.28
/pymatgen/io/fiesta.py
1
"""
2
This module implements input and output for Fiesta (http://perso.neel.cnrs.fr/xavier.blase/fiesta/index.html).
3

4
and
5

6
-Nwchem2Fiesta class: to create the input files needed for a Fiesta run
7
-Fiesta_run: run gw_fiesta and bse_fiesta
8
-Localised Basis set reader
9
"""
10

11
from __future__ import annotations
1✔
12

13
import os
1✔
14
import re
1✔
15
import shutil
1✔
16
import subprocess
1✔
17
from string import Template
1✔
18

19
from monty.io import zopen
1✔
20
from monty.json import MSONable
1✔
21

22
from pymatgen.core.structure import Molecule
1✔
23

24
__author__ = "ndardenne"
1✔
25
__copyright__ = "Copyright 2012, The Materials Project"
1✔
26
__version__ = "0.1"
1✔
27
__email__ = "n.dardenne@uclouvain.be"
1✔
28
__date__ = "24/5/15"
1✔
29

30

31
class Nwchem2Fiesta(MSONable):
1✔
32
    """
33
    To run NWCHEM2FIESTA inside python:
34

35
    If nwchem.nw is the input, nwchem.out the output, and structure.movecs the
36
    "movecs" file, the syntax to run NWCHEM2FIESTA is: NWCHEM2FIESTA
37
    nwchem.nw  nwchem.nwout  structure.movecs > log_n2f
38
    """
39

40
    def __init__(self, folder, filename="nwchem", log_file="log_n2f"):
1✔
41
        """
42
        folder: where are stored the nwchem
43
        filename: name of nwchem files read by NWCHEM2FIESTA (filename.nw, filename.nwout and filename.movecs)
44
        logfile: logfile of NWCHEM2FIESTA
45

46
        the run method launches NWCHEM2FIESTA
47
        """
48
        self.folder = folder
×
49
        self.filename = filename
×
50
        self.log_file = log_file
×
51

52
        self._NWCHEM2FIESTA_cmd = "NWCHEM2FIESTA"
×
53
        self._nwcheminput_fn = filename + ".nw"
×
54
        self._nwchemoutput_fn = filename + ".nwout"
×
55
        self._nwchemmovecs_fn = filename + ".movecs"
×
56

57
    def run(self):
1✔
58
        """
59
        Performs actual NWCHEM2FIESTA run
60
        """
61
        init_folder = os.getcwd()
×
62
        os.chdir(self.folder)
×
63

64
        with zopen(self.log_file, "w") as fout:
×
65
            subprocess.call(
×
66
                [
67
                    self._NWCHEM2FIESTA_cmd,
68
                    self._nwcheminput_fn,
69
                    self._nwchemoutput_fn,
70
                    self._nwchemmovecs_fn,
71
                ],
72
                stdout=fout,
73
            )
74

75
        os.chdir(init_folder)
×
76

77
    def as_dict(self):
1✔
78
        """
79
        :return: MSONable dict
80
        """
81
        return {
×
82
            "@module": type(self).__module__,
83
            "@class": type(self).__name__,
84
            "filename": self.filename,
85
            "folder": self.folder,
86
        }
87

88
    @classmethod
1✔
89
    def from_dict(cls, d):
1✔
90
        """
91
        :param d: Dict representation.
92
        :return: Nwchem2Fiesta
93
        """
94
        return cls(folder=d["folder"], filename=d["filename"])
×
95

96

97
class FiestaRun(MSONable):
1✔
98
    """
99
    To run FIESTA inside python:
100
        if grid is [x,x] then bse runs
101
        if grid is [x,x,y] the fiesta(gw) runs
102
        otherwise it breaks
103
    """
104

105
    def __init__(
1✔
106
        self, folder: str | None = None, grid: tuple[int, int, int] = (2, 2, 2), log_file: str = "log"
107
    ) -> None:
108
        """
109
        Args:
110
            folder: Folder to look for runs.
111
            grid:
112
            log_file: logfile of Fiesta
113
        """
114
        self.folder = folder or os.getcwd()
×
115
        self.log_file = log_file
×
116
        self.grid = grid
×
117

118
    def run(self):
1✔
119
        """
120
        Performs FIESTA (gw) run.
121
        """
122
        if len(self.grid) == 3:
×
123
            self.mpi_procs = self.grid[0] * self.grid[1] * self.grid[2]
×
124
            self._gw_run()
×
125
        elif len(self.grid) == 2:
×
126
            self.mpi_procs = self.grid[0] * self.grid[1]
×
127
            self.bse_run()
×
128
        else:
129
            raise ValueError("Wrong grid size: must be [nrow, ncolumn, nslice] for gw of [nrow, nslice] for bse")
×
130

131
    def _gw_run(self):
1✔
132
        """
133
        Performs FIESTA (gw) run
134
        """
135
        if self.folder != os.getcwd():
×
136
            init_folder = os.getcwd()
×
137
            os.chdir(self.folder)
×
138

139
        with zopen(self.log_file, "w") as fout:
×
140
            subprocess.call(
×
141
                [
142
                    "mpirun",
143
                    "-n",
144
                    str(self.mpi_procs),
145
                    "fiesta",
146
                    str(self.grid[0]),
147
                    str(self.grid[1]),
148
                    str(self.grid[2]),
149
                ],
150
                stdout=fout,
151
            )
152

153
        if self.folder != os.getcwd():
×
154
            os.chdir(init_folder)
×
155

156
    def bse_run(self):
1✔
157
        """
158
        Performs BSE run
159
        """
160
        if self.folder != os.getcwd():
×
161
            init_folder = os.getcwd()
×
162
            os.chdir(self.folder)
×
163

164
        with zopen(self.log_file, "w") as fout:
×
165
            subprocess.call(
×
166
                [
167
                    "mpirun",
168
                    "-n",
169
                    str(self.mpi_procs),
170
                    "bse",
171
                    str(self.grid[0]),
172
                    str(self.grid[1]),
173
                ],
174
                stdout=fout,
175
            )
176

177
        if self.folder != os.getcwd():
×
178
            os.chdir(init_folder)
×
179

180
    def as_dict(self):
1✔
181
        """
182
        :return: MSONable dict
183
        """
184
        return {
×
185
            "@module": type(self).__module__,
186
            "@class": type(self).__name__,
187
            "log_file": self.log_file,
188
            "grid": self.grid,
189
            "folder": self.folder,
190
        }
191

192
    @classmethod
1✔
193
    def from_dict(cls, d):
1✔
194
        """
195
        :param d: Dict representation
196
        :return: FiestaRun
197
        """
198
        return cls(folder=d["folder"], grid=d["grid"], log_file=d["log_file"])
×
199

200

201
class BasisSetReader:
1✔
202
    """
203
    A basis set reader.
204
    Basis set are stored in data as a dict:
205
    :key l_zeta_ng for each nl orbitals which contain list of tuple (alpha, coef) for each of the ng gaussians
206
    in l_zeta orbital
207
    """
208

209
    def __init__(self, filename):
1✔
210
        """
211
        Args:
212
            filename: Filename to read.
213
        """
214
        self.filename = filename
×
215

216
        with zopen(filename) as f:
×
217
            basis_set = f.read()
×
218

219
        self.data = self._parse_file(basis_set)
×
220
        # compute the number of nlm orbitals per atom
221
        self.data.update(n_nlmo=self.set_n_nlmo())
×
222

223
    @staticmethod
1✔
224
    def _parse_file(input):
1✔
225
        lmax_nnlo_patt = re.compile(r"\s* (\d+) \s+ (\d+) \s+ \# .* ", re.VERBOSE)
×
226

227
        nl_orbital_patt = re.compile(r"\s* (\d+) \s+ (\d+) \s+ (\d+) \s+ \# .* ", re.VERBOSE)
×
228

229
        coef_alpha_patt = re.compile(r"\s* ([-\d.\D]+) \s+ ([-\d.\D]+) \s* ", re.VERBOSE)
×
230

231
        preamble = []
×
232
        basis_set = {}
×
233
        parse_preamble = False
×
234
        parse_lmax_nnlo = False
×
235
        parse_nl_orbital = False
×
236
        nnlo = None  # fix pylint E0601: Using variable 'nnlo' before assignment
×
237
        lmax = None
×
238

239
        for line in input.split("\n"):
×
240
            if parse_nl_orbital:
×
241
                m = nl_orbital_patt.search(line)
×
242
                n = coef_alpha_patt.search(line)
×
243
                if m:
×
244
                    l = m.group(1)
×
245
                    zeta = m.group(2)
×
246
                    ng = m.group(3)
×
247
                    basis_set[l + "_" + zeta + "_" + ng] = []
×
248
                elif n:
×
249
                    alpha = n.group(1)
×
250
                    coef = n.group(2)
×
251
                    basis_set[l + "_" + zeta + "_" + ng].append((alpha, coef))
×
252
            elif parse_lmax_nnlo:
×
253
                m = lmax_nnlo_patt.search(line)
×
254
                if m:
×
255
                    lmax = m.group(1)
×
256
                    nnlo = m.group(2)
×
257
                    parse_lmax_nnlo = False
×
258
                    parse_nl_orbital = True
×
259
            elif parse_preamble:
×
260
                preamble.append(line.strip())
×
261

262
            if line.find("</preamble>") != -1:
×
263
                parse_preamble = False
×
264
                parse_lmax_nnlo = True
×
265
            elif line.find("<preamble>") != -1:
×
266
                parse_preamble = True
×
267

268
        basis_set.update(lmax=lmax, n_nlo=nnlo, preamble=preamble)
×
269
        return basis_set
×
270

271
    def set_n_nlmo(self):
1✔
272
        """
273
        :return: the number of nlm orbitals for the basis set
274
        """
275
        nnlmo = 0
×
276

277
        data_tmp = self.data
×
278
        data_tmp.pop("lmax")
×
279
        data_tmp.pop("n_nlo")
×
280
        data_tmp.pop("preamble")
×
281

282
        for l_zeta_ng in data_tmp:
×
283
            l = l_zeta_ng.split("_")[0]
×
284
            nnlmo = nnlmo + (2 * int(l) + 1)
×
285

286
        return str(nnlmo)
×
287

288
    def infos_on_basis_set(self):
1✔
289
        """
290
        infos on the basis set as in Fiesta log
291
        """
292
        o = []
×
293
        o.append("=========================================")
×
294
        o.append("Reading basis set:")
×
295
        o.append("")
×
296
        o.append(f" Basis set for {self.filename} atom ")
×
297
        o.append(f" Maximum angular momentum = {self.data['lmax']}")
×
298
        o.append(f" Number of atomics orbitals = {self.data['n_nlo']}")
×
299
        o.append(f" Number of nlm orbitals = {self.data['n_nlmo']}")
×
300
        o.append("=========================================")
×
301

302
        return str(0)
×
303

304

305
class FiestaInput(MSONable):
1✔
306
    """
307
    Input File for Fiesta called "cell.in" by default (mandatory in Fiesta for now)
308
    """
309

310
    def __init__(
1✔
311
        self,
312
        mol,
313
        correlation_grid: dict[str, str] | None = None,
314
        Exc_DFT_option: dict[str, str] | None = None,
315
        COHSEX_options: dict[str, str] | None = None,
316
        GW_options: dict[str, str] | None = None,
317
        BSE_TDDFT_options: dict[str, str] | None = None,
318
    ):
319
        """
320
        :param mol: pymatgen mol
321
        :param correlation_grid: dict
322
        :param Exc_DFT_option: dict
323
        :param COHSEX_options: dict
324
        :param GW_options: dict
325
        :param BSE_TDDFT_options: dict
326
        """
327
        self._mol = mol
1✔
328
        self.correlation_grid = correlation_grid or {"dE_grid": "0.500", "n_grid": "14"}
1✔
329
        self.Exc_DFT_option = Exc_DFT_option or {"rdVxcpsi": "1"}
1✔
330
        self.COHSEX_options = COHSEX_options or {
1✔
331
            "eigMethod": "C",
332
            "mix_cohsex": "0.500",
333
            "nc_cohsex": "0",
334
            "nit_cohsex": "0",
335
            "nv_cohsex": "0",
336
            "resMethod": "V",
337
            "scf_cohsex_wf": "0",
338
        }
339
        self.GW_options = GW_options or {"nc_corr": "10", "nit_gw": "3", "nv_corr": "10"}
1✔
340
        self.BSE_TDDFT_options = BSE_TDDFT_options or {
1✔
341
            "do_bse": "1",
342
            "do_tddft": "0",
343
            "nc_bse": "382",
344
            "nit_bse": "50",
345
            "npsi_bse": "1",
346
            "nv_bse": "21",
347
        }
348

349
    def set_auxiliary_basis_set(self, folder, auxiliary_folder, auxiliary_basis_set_type="aug_cc_pvtz"):
1✔
350
        """
351
        copy in the desired folder the needed auxiliary basis set "X2.ion" where X is a specie.
352
        :param auxiliary_folder: folder where the auxiliary basis sets are stored
353
        :param auxiliary_basis_set_type: type of basis set (string to be found in the extension of the file name; must
354
            be in lower case). ex: C2.ion_aug_cc_pvtz_RI_Weigend find "aug_cc_pvtz"
355
        """
356
        list_files = os.listdir(auxiliary_folder)
×
357

358
        for specie in self._mol.symbol_set:
×
359
            for file in list_files:
×
360
                if file.upper().find(specie.upper() + "2") != -1 and file.lower().find(auxiliary_basis_set_type) != -1:
×
361
                    shutil.copyfile(auxiliary_folder + "/" + file, folder + "/" + specie + "2.ion")
×
362

363
    def set_GW_options(self, nv_band=10, nc_band=10, n_iteration=5, n_grid=6, dE_grid=0.5):
1✔
364
        """
365
        Set parameters in cell.in for a GW computation
366
        :param nv__band: number of valence bands to correct with GW
367
        :param nc_band: number of conduction bands to correct with GW
368
        :param n_iteration: number of iteration
369
        :param n_grid and dE_grid:: number of points and spacing in eV for correlation grid
370
        """
371
        self.GW_options.update(nv_corr=nv_band, nc_corr=nc_band, nit_gw=n_iteration)
×
372
        self.correlation_grid.update(dE_grid=dE_grid, n_grid=n_grid)
×
373

374
    @staticmethod
1✔
375
    def make_FULL_BSE_Densities_folder(folder):
1✔
376
        """
377
        mkdir "FULL_BSE_Densities" folder (needed for bse run) in the desired folder
378
        """
379
        if os.path.exists(folder + "/FULL_BSE_Densities"):
×
380
            return "FULL_BSE_Densities folder already exists"
×
381

382
        os.makedirs(folder + "/FULL_BSE_Densities")
×
383
        return "makedirs FULL_BSE_Densities folder"
×
384

385
    def set_BSE_options(self, n_excitations=10, nit_bse=200):
1✔
386
        """
387
        Set parameters in cell.in for a BSE computation
388
        :param nv_bse: number of valence bands
389
        :param nc_bse: number of conduction bands
390
        :param n_excitations: number of excitations
391
        :param nit_bse: number of iterations
392
        """
393
        self.BSE_TDDFT_options.update(npsi_bse=n_excitations, nit_bse=nit_bse)
×
394

395
    def dump_BSE_data_in_GW_run(self, BSE_dump=True):
1✔
396
        """
397
        :param BSE_dump: boolean
398
        :return: set the "do_bse" variable to one in cell.in
399
        """
400
        if BSE_dump:
×
401
            self.BSE_TDDFT_options.update(do_bse=1, do_tddft=0)
×
402
        else:
403
            self.BSE_TDDFT_options.update(do_bse=0, do_tddft=0)
×
404

405
    def dump_TDDFT_data_in_GW_run(self, TDDFT_dump=True):
1✔
406
        """
407
        :param TDDFT_dump: boolean
408
        :return: set the do_tddft variable to one in cell.in
409
        """
410
        if TDDFT_dump:
×
411
            self.BSE_TDDFT_options.update(do_bse=0, do_tddft=1)
×
412
        else:
413
            self.BSE_TDDFT_options.update(do_bse=0, do_tddft=0)
×
414

415
    @property
1✔
416
    def infos_on_system(self):
1✔
417
        """
418
        Returns infos on initial parameters as in the log file of Fiesta
419
        """
420
        o = []
×
421
        o.append("=========================================")
×
422
        o.append("Reading infos on system:")
×
423
        o.append("")
×
424
        o.append(
×
425
            f" Number of atoms = {self._mol.composition.num_atoms} ; number of species = {len(self._mol.symbol_set)}"
426
        )
427
        o.append(f" Number of valence bands = {int(self._mol.nelectrons / 2)}")
×
428
        o.append(
×
429
            f" Sigma grid specs: n_grid = {self.correlation_grid['n_grid']} ;  "
430
            f"dE_grid = {self.correlation_grid['dE_grid']} (eV)"
431
        )
432
        if int(self.Exc_DFT_option["rdVxcpsi"]) == 1:
×
433
            o.append(" Exchange and correlation energy read from Vxcpsi.mat")
×
434
        elif int(self.Exc_DFT_option["rdVxcpsi"]) == 0:
×
435
            o.append(" Exchange and correlation energy re-computed")
×
436

437
        if self.COHSEX_options["eigMethod"] == "C":
×
438
            o.append(
×
439
                f" Correcting  {self.COHSEX_options['nv_cohsex']} valence bands and  "
440
                f"{self.COHSEX_options['nc_cohsex']} conduction bands at COHSEX level"
441
            )
442
            o.append(f" Performing   {self.COHSEX_options['nit_cohsex']} diagonal COHSEX iterations")
×
443
        elif self.COHSEX_options["eigMethod"] == "HF":
×
444
            o.append(
×
445
                f" Correcting  {self.COHSEX_options['nv_cohsex']} valence bands and  "
446
                f"{self.COHSEX_options['nc_cohsex']} conduction bands at HF level"
447
            )
448
            o.append(f" Performing   {self.COHSEX_options['nit_cohsex']} diagonal HF iterations")
×
449

450
        o.append(f" Using resolution of identity : {self.COHSEX_options['resMethod']}")
×
451
        o.append(
×
452
            f" Correcting  {self.GW_options['nv_corr']} valence bands and "
453
            f"{self.GW_options['nc_corr']} conduction bands at GW level"
454
        )
455
        o.append(f" Performing   {self.GW_options['nit_gw']} GW iterations")
×
456

457
        if int(self.BSE_TDDFT_options["do_bse"]) == 1:
×
458
            o.append(" Dumping data for BSE treatment")
×
459

460
        if int(self.BSE_TDDFT_options["do_tddft"]) == 1:
×
461
            o.append(" Dumping data for TD-DFT treatment")
×
462
        o.append("")
×
463
        o.append(" Atoms in cell cartesian A:")
×
464
        symbols = []
×
465
        for syb in self._mol.symbol_set:
×
466
            symbols.append(syb)
×
467

468
        for site in self._mol:
×
469
            o.append(f" {site.x} {site.y} {site.z} {int(symbols.index(site.specie.symbol)) + 1}")
×
470

471
        o.append("=========================================")
×
472

473
        return str(o)
×
474

475
    @property
1✔
476
    def molecule(self):
1✔
477
        """
478
        Returns molecule associated with this FiestaInput.
479
        """
480
        return self._mol
1✔
481

482
    def __str__(self):
1✔
483
        symbols = []
1✔
484
        for syb in self._mol.symbol_set:
1✔
485
            symbols.append(syb)
1✔
486

487
        geometry = []
1✔
488
        for site in self._mol:
1✔
489
            geometry.append(f" {site.x} {site.y} {site.z} {int(symbols.index(site.specie.symbol)) + 1}")
1✔
490

491
        t = Template(
1✔
492
            """# number of atoms and species
493
   $nat    $nsp
494
# number of valence bands
495
    $nvbands
496
# number of points and spacing in eV for correlation grid
497
    $n_grid    $dE_grid
498
# relire=1 ou recalculer=0 Exc DFT
499
    $rdVxcpsi
500
# number of COHSEX corrected occp and unoccp bands: C=COHSEX  H=HF
501
    $nv_cohsex    $nc_cohsex   $eigMethod
502
# number of COHSEX iter, scf on wfns, mixing coeff; V=RI-V  I=RI-D
503
    $nit_cohsex   $resMethod       $scf_cohsex_wf       $mix_cohsex
504
# number of GW corrected occp and unoccp bands
505
   $nv_corr   $nc_corr
506
# number of GW iterations
507
    $nit_gw
508
# dumping for BSE and TDDFT
509
    $do_bse    $do_tddft
510
# number of occp. and virtual bands of BSE: nocore and up to 40 eVs
511
    $nv_bse   $nc_bse
512
# number of excitations needed and number of iterations
513
    $npsi_bse   $nit_bse
514
# list of symbols in order
515
$symbols
516
# scaling factor
517
    1.000
518
# atoms x,y,z cartesian .. will be multiplied by scale
519
$geometry
520
            """
521
        )
522

523
        return t.substitute(
1✔
524
            nat=int(self._mol.composition.num_atoms),
525
            nsp=len(self._mol.symbol_set),
526
            nvbands=int(self._mol.nelectrons / 2),
527
            n_grid=self.correlation_grid["n_grid"],
528
            dE_grid=self.correlation_grid["dE_grid"],
529
            rdVxcpsi=self.Exc_DFT_option["rdVxcpsi"],
530
            nv_cohsex=self.COHSEX_options["nv_cohsex"],
531
            nc_cohsex=self.COHSEX_options["nc_cohsex"],
532
            eigMethod=self.COHSEX_options["eigMethod"],
533
            nit_cohsex=self.COHSEX_options["nit_cohsex"],
534
            resMethod=self.COHSEX_options["resMethod"],
535
            scf_cohsex_wf=self.COHSEX_options["scf_cohsex_wf"],
536
            mix_cohsex=self.COHSEX_options["mix_cohsex"],
537
            nv_corr=self.GW_options["nv_corr"],
538
            nc_corr=self.GW_options["nc_corr"],
539
            nit_gw=self.GW_options["nit_gw"],
540
            do_bse=self.BSE_TDDFT_options["do_bse"],
541
            do_tddft=self.BSE_TDDFT_options["do_tddft"],
542
            nv_bse=self.BSE_TDDFT_options["nv_bse"],
543
            nc_bse=self.BSE_TDDFT_options["nc_bse"],
544
            npsi_bse=self.BSE_TDDFT_options["npsi_bse"],
545
            nit_bse=self.BSE_TDDFT_options["nit_bse"],
546
            symbols="\n".join(symbols),
547
            geometry="\n".join(geometry),
548
        )
549

550
    def write_file(self, filename):
1✔
551
        """
552
        Write FiestaInput to a file
553
        :param filename: Filename
554
        """
555
        with zopen(filename, "w") as f:
×
556
            f.write(str(self))
×
557

558
    def as_dict(self):
1✔
559
        """
560
        :return: MSONable dict
561
        """
562
        return {
×
563
            "mol": self._mol.as_dict(),
564
            "correlation_grid": self.correlation_grid,
565
            "Exc_DFT_option": self.Exc_DFT_option,
566
            "COHSEX_options": self.COHSEX_options,
567
            "GW_options": self.GW_options,
568
            "BSE_TDDFT_options": self.BSE_TDDFT_options,
569
        }
570

571
    @classmethod
1✔
572
    def from_dict(cls, d):
1✔
573
        """
574
        :param d: Dict representation
575
        :return: FiestaInput
576
        """
577
        return cls(
×
578
            Molecule.from_dict(d["mol"]),
579
            correlation_grid=d["correlation_grid"],
580
            Exc_DFT_option=d["Exc_DFT_option"],
581
            COHSEX_options=d["geometry_options"],
582
            GW_options=d["symmetry_options"],
583
            BSE_TDDFT_options=d["memory_options"],
584
        )
585

586
    @classmethod
1✔
587
    def from_string(cls, string_input):
1✔
588
        """
589
        Read an FiestaInput from a string. Currently tested to work with
590
        files generated from this class itself.
591

592
        Args:
593
            string_input: string_input to parse.
594
        Returns:
595
            FiestaInput object
596
        """
597
        correlation_grid = {}
1✔
598
        Exc_DFT_option = {}
1✔
599
        COHSEX_options = {}
1✔
600
        GW_options = {}
1✔
601
        BSE_TDDFT_options = {}
1✔
602

603
        lines = string_input.strip().split("\n")
1✔
604

605
        # number of atoms and species
606
        lines.pop(0)
1✔
607
        l = lines.pop(0).strip()
1✔
608
        toks = l.split()
1✔
609
        nat = toks[0]
1✔
610
        nsp = toks[1]
1✔
611
        # number of valence bands
612
        lines.pop(0)
1✔
613
        l = lines.pop(0).strip()
1✔
614
        toks = l.split()
1✔
615

616
        # correlation_grid
617
        # number of points and spacing in eV for correlation grid
618
        lines.pop(0)
1✔
619
        l = lines.pop(0).strip()
1✔
620
        toks = l.split()
1✔
621
        correlation_grid["n_grid"] = toks[0]
1✔
622
        correlation_grid["dE_grid"] = toks[1]
1✔
623

624
        # Exc DFT
625
        # relire=1 ou recalculer=0 Exc DFT
626
        lines.pop(0)
1✔
627
        l = lines.pop(0).strip()
1✔
628
        toks = l.split()
1✔
629
        Exc_DFT_option["rdVxcpsi"] = toks[0]
1✔
630

631
        # COHSEX
632
        # number of COHSEX corrected occp and unoccp bands: C=COHSEX  H=HF
633
        lines.pop(0)
1✔
634
        l = lines.pop(0).strip()
1✔
635
        toks = l.split()
1✔
636
        COHSEX_options["nv_cohsex"] = toks[0]
1✔
637
        COHSEX_options["nc_cohsex"] = toks[1]
1✔
638
        COHSEX_options["eigMethod"] = toks[2]
1✔
639
        # number of COHSEX iter, scf on wfns, mixing coeff; V=RI-V  I=RI-D
640
        lines.pop(0)
1✔
641
        l = lines.pop(0).strip()
1✔
642
        toks = l.split()
1✔
643
        COHSEX_options["nit_cohsex"] = toks[0]
1✔
644
        COHSEX_options["resMethod"] = toks[1]
1✔
645
        COHSEX_options["scf_cohsex_wf"] = toks[2]
1✔
646
        COHSEX_options["mix_cohsex"] = toks[3]
1✔
647

648
        # GW
649
        # number of GW corrected occp and unoccp bands
650
        lines.pop(0)
1✔
651
        l = lines.pop(0).strip()
1✔
652
        toks = l.split()
1✔
653
        GW_options["nv_corr"] = toks[0]
1✔
654
        GW_options["nc_corr"] = toks[1]
1✔
655
        # number of GW iterations
656
        lines.pop(0)
1✔
657
        l = lines.pop(0).strip()
1✔
658
        toks = l.split()
1✔
659
        GW_options["nit_gw"] = toks[0]
1✔
660

661
        # BSE
662
        # dumping for BSE and TDDFT
663
        lines.pop(0)
1✔
664
        l = lines.pop(0).strip()
1✔
665
        toks = l.split()
1✔
666
        BSE_TDDFT_options["do_bse"] = toks[0]
1✔
667
        BSE_TDDFT_options["do_tddft"] = toks[1]
1✔
668
        # number of occp. and virtual bands of BSE: nocore and up to 40 eVs
669
        lines.pop(0)
1✔
670
        l = lines.pop(0).strip()
1✔
671
        toks = l.split()
1✔
672
        BSE_TDDFT_options["nv_bse"] = toks[0]
1✔
673
        BSE_TDDFT_options["nc_bse"] = toks[1]
1✔
674
        # number of excitations needed and number of iterations
675
        lines.pop(0)
1✔
676
        l = lines.pop(0).strip()
1✔
677
        toks = l.split()
1✔
678
        BSE_TDDFT_options["npsi_bse"] = toks[0]
1✔
679
        BSE_TDDFT_options["nit_bse"] = toks[1]
1✔
680

681
        # Molecule
682
        # list of symbols in order
683
        lines.pop(0)
1✔
684
        atname = []
1✔
685
        i = int(nsp)
1✔
686
        while i != 0:
1✔
687
            l = lines.pop(0).strip()
1✔
688
            toks = l.split()
1✔
689
            atname.append(toks[0])
1✔
690
            i -= 1
1✔
691

692
        # scaling factor
693
        lines.pop(0)
1✔
694
        l = lines.pop(0).strip()
1✔
695
        toks = l.split()
1✔
696
        # atoms x,y,z cartesian .. will be multiplied by scale
697
        lines.pop(0)
1✔
698
        # Parse geometry
699
        species = []
1✔
700
        coords = []
1✔
701
        i = int(nat)
1✔
702
        while i != 0:
1✔
703
            l = lines.pop(0).strip()
1✔
704
            toks = l.split()
1✔
705
            coords.append([float(j) for j in toks[0:3]])
1✔
706
            species.append(atname[int(toks[3]) - 1])
1✔
707
            i -= 1
1✔
708

709
        mol = Molecule(species, coords)
1✔
710

711
        return FiestaInput(
1✔
712
            mol=mol,
713
            correlation_grid=correlation_grid,
714
            Exc_DFT_option=Exc_DFT_option,
715
            COHSEX_options=COHSEX_options,
716
            GW_options=GW_options,
717
            BSE_TDDFT_options=BSE_TDDFT_options,
718
        )
719

720
    @classmethod
1✔
721
    def from_file(cls, filename):
1✔
722
        """
723
        Read an Fiesta input from a file. Currently tested to work with
724
        files generated from this class itself.
725

726
        Args:
727
            filename: Filename to parse.
728

729
        Returns:
730
            FiestaInput object
731
        """
732
        with zopen(filename) as f:
×
733
            return cls.from_string(f.read())
×
734

735

736
class FiestaOutput:
1✔
737
    """
738
    A Fiesta output file parser.
739

740
    All energies are in eV.
741
    """
742

743
    def __init__(self, filename):
1✔
744
        """
745
        Args:
746
            filename: Filename to read.
747
        """
748
        self.filename = filename
1✔
749

750
        with zopen(filename) as f:
1✔
751
            data = f.read()
1✔
752

753
        chunks = re.split(r"GW Driver iteration", data)
1✔
754

755
        # preamble: everything before the first GW Driver iteration
756
        chunks.pop(0)
1✔
757

758
        # self.job_info = self._parse_preamble(preamble)
759
        self.data = [self._parse_job(c) for c in chunks]
1✔
760

761
    @staticmethod
1✔
762
    def _parse_job(output):
1✔
763
        GW_BANDS_results_patt = re.compile(
1✔
764
            r"^<it.*  \| \s+ (\D+\d*) \s+ \| \s+ ([-\d.]+) \s+ ([-\d.]+) \s+ ([-\d.]+) \s+ \| "
765
            r" \s+ ([-\d.]+) \s+ ([-\d.]+) \s+ ([-\d.]+) \s+ \|"
766
            r" \s+ ([-\d.]+) \s+ ([-\d.]+) \s+ ",
767
            re.VERBOSE,
768
        )
769

770
        GW_GAPS_results_patt = re.compile(
1✔
771
            r"^<it.*  \| \s+ Egap_KS \s+ = \s+ ([-\d.]+) \s+ \| \s+ Egap_QP \s+ = \s+ ([-\d.]+) \s+ \| "
772
            r" \s+ Egap_QP \s+ = \s+ ([-\d.]+) \s+ \|",
773
            re.VERBOSE,
774
        )
775

776
        end_patt = re.compile(r"\s*program returned normally\s*")
1✔
777

778
        total_time_patt = re.compile(r"\s*total \s+ time: \s+  ([\d.]+) .*", re.VERBOSE)
1✔
779

780
        GW_results = {}
1✔
781
        parse_gw_results = False
1✔
782
        parse_total_time = False
1✔
783

784
        for l in output.split("\n"):
1✔
785
            if parse_total_time:
1✔
786
                m = end_patt.search(l)
1✔
787
                if m:
1✔
788
                    GW_results.update(end_normally=True)
1✔
789

790
                m = total_time_patt.search(l)
1✔
791
                if m:
1✔
792
                    GW_results.update(total_time=m.group(1))
1✔
793

794
            if parse_gw_results:
1✔
795
                if l.find("Dumping eigen energies") != -1:
1✔
796
                    parse_total_time = True
1✔
797
                    parse_gw_results = False
1✔
798
                    continue
1✔
799

800
                m = GW_BANDS_results_patt.search(l)
1✔
801
                if m:
1✔
802
                    d = {}
1✔
803
                    d.update(
1✔
804
                        band=m.group(1).strip(),
805
                        eKS=m.group(2),
806
                        eXX=m.group(3),
807
                        eQP_old=m.group(4),
808
                        z=m.group(5),
809
                        sigma_c_Linear=m.group(6),
810
                        eQP_Linear=m.group(7),
811
                        sigma_c_SCF=m.group(8),
812
                        eQP_SCF=m.group(9),
813
                    )
814
                    GW_results[m.group(1).strip()] = d
1✔
815

816
                n = GW_GAPS_results_patt.search(l)
1✔
817
                if n:
1✔
818
                    d = {}
1✔
819
                    d.update(
1✔
820
                        Egap_KS=n.group(1),
821
                        Egap_QP_Linear=n.group(2),
822
                        Egap_QP_SCF=n.group(3),
823
                    )
824
                    GW_results["Gaps"] = d
1✔
825

826
            if l.find("GW Results") != -1:
1✔
827
                parse_gw_results = True
1✔
828

829
        return GW_results
1✔
830

831

832
class BSEOutput:
1✔
833
    """
834
    A bse output file parser. The start...
835

836
    All energies are in eV.
837
    """
838

839
    def __init__(self, filename):
1✔
840
        """
841
        Args:
842
            filename: Filename to read.
843
        """
844
        self.filename = filename
×
845

846
        with zopen(filename) as f:
×
847
            log_bse = f.read()
×
848

849
        # self.job_info = self._parse_preamble(preamble)
850
        self.exiton = self._parse_job(log_bse)
×
851

852
    @staticmethod
1✔
853
    def _parse_job(output):
1✔
854
        BSE_exitons_patt = re.compile(
×
855
            r"^exiton \s+ (\d+)  : \s+  ([\d.]+) \( \s+ ([-\d.]+) \) \s+ \| .*  ",
856
            re.VERBOSE,
857
        )
858

859
        end_patt = re.compile(r"\s*program returned normally\s*")
×
860

861
        total_time_patt = re.compile(r"\s*total \s+ time: \s+  ([\d.]+) .*", re.VERBOSE)
×
862

863
        BSE_results = {}
×
864
        parse_BSE_results = False
×
865
        parse_total_time = False
×
866

867
        for l in output.split("\n"):
×
868
            if parse_total_time:
×
869
                m = end_patt.search(l)
×
870
                if m:
×
871
                    BSE_results.update(end_normally=True)
×
872

873
                m = total_time_patt.search(l)
×
874
                if m:
×
875
                    BSE_results.update(total_time=m.group(1))
×
876

877
            if parse_BSE_results:
×
878
                if l.find("FULL BSE main valence -> conduction transitions weight:") != -1:
×
879
                    parse_total_time = True
×
880
                    parse_BSE_results = False
×
881
                    continue
×
882

883
                m = BSE_exitons_patt.search(l)
×
884
                if m:
×
885
                    d = {}
×
886
                    d.update(bse_eig=m.group(2), osc_strength=m.group(3))
×
887
                    BSE_results[str(m.group(1).strip())] = d
×
888

889
            if l.find("FULL BSE eig.(eV), osc. strength and dipoles:") != -1:
×
890
                parse_BSE_results = True
×
891

892
        return BSE_results
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc