• 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

85.1
/pymatgen/electronic_structure/cohp.py
1
# Copyright (c) Pymatgen Development Team.
2
# Distributed under the terms of the MIT License.
3

4
"""
1✔
5
This module defines classes to represent crystal orbital Hamilton
6
populations (COHP) and integrated COHP (ICOHP), but can also be used
7
for crystal orbital overlap populations (COOP).
8
"""
9

10
from __future__ import annotations
1✔
11

12
import re
1✔
13
import sys
1✔
14
import warnings
1✔
15

16
import numpy as np
1✔
17
from monty.json import MSONable
1✔
18
from scipy.interpolate import InterpolatedUnivariateSpline
1✔
19

20
from pymatgen.core.sites import PeriodicSite
1✔
21
from pymatgen.core.structure import Structure
1✔
22
from pymatgen.electronic_structure.core import Orbital, Spin
1✔
23
from pymatgen.io.lmto import LMTOCopl
1✔
24
from pymatgen.io.lobster import Cohpcar
1✔
25
from pymatgen.util.coord import get_linear_interpolated_value
1✔
26
from pymatgen.util.num import round_to_sigfigs
1✔
27

28
__author__ = "Marco Esters, Janine George"
1✔
29
__copyright__ = "Copyright 2017, The Materials Project"
1✔
30
__version__ = "0.2"
1✔
31
__maintainer__ = "Marco Esters, Janine George"
1✔
32
__email__ = "esters@uoregon.edu, janine.george@uclouvain.be"
1✔
33
__date__ = "Dec 13, 2017"
1✔
34

35

36
class Cohp(MSONable):
1✔
37
    """
38
    Basic COHP object.
39
    """
40

41
    def __init__(self, efermi, energies, cohp, are_coops=False, are_cobis=False, icohp=None):
1✔
42
        """
43
        Args:
44
            are_coops: Indicates whether this object describes COOPs.
45
            are_cobis: Indicates whether this object describes COBIs.
46
            efermi: Fermi energy.
47
            energies: A sequence of energies.
48
            cohp ({Spin: np.array}): representing the COHP for each spin.
49
            icohp ({Spin: np.array}): representing the ICOHP for each spin.
50
        """
51
        self.are_coops = are_coops
1✔
52
        self.are_cobis = are_cobis
1✔
53
        self.efermi = efermi
1✔
54
        self.energies = np.array(energies)
1✔
55
        self.cohp = cohp
1✔
56
        self.icohp = icohp
1✔
57

58
    def __repr__(self):
1✔
59
        return str(self)
×
60

61
    def __str__(self):
1✔
62
        """
63
        Returns a string that can be easily plotted (e.g. using gnuplot).
64
        """
65
        if self.are_coops:
1✔
66
            cohpstring = "COOP"
1✔
67
        elif self.are_cobis:
1✔
68
            cohpstring = "COBI"
×
69
        else:
70
            cohpstring = "COHP"
1✔
71
        header = ["Energy", cohpstring + "Up"]
1✔
72
        data = [self.energies, self.cohp[Spin.up]]
1✔
73
        if Spin.down in self.cohp:
1✔
74
            header.append(cohpstring + "Down")
1✔
75
            data.append(self.cohp[Spin.down])
1✔
76
        if self.icohp:
1✔
77
            header.append("I" + cohpstring + "Up")
1✔
78
            data.append(self.icohp[Spin.up])
1✔
79
            if Spin.down in self.cohp:
1✔
80
                header.append("I" + cohpstring + "Down")
1✔
81
                data.append(self.icohp[Spin.down])
1✔
82
        formatheader = "#" + " ".join("{:15s}" for __ in header)
1✔
83
        formatdata = " ".join("{:.5f}" for __ in header)
1✔
84
        stringarray = [formatheader.format(*header)]
1✔
85
        for i, __ in enumerate(self.energies):
1✔
86
            stringarray.append(formatdata.format(*(d[i] for d in data)))
1✔
87
        return "\n".join(stringarray)
1✔
88

89
    def as_dict(self):
1✔
90
        """
91
        JSON-serializable dict representation of COHP.
92
        """
93
        d = {
1✔
94
            "@module": type(self).__module__,
95
            "@class": type(self).__name__,
96
            "are_coops": self.are_coops,
97
            "are_cobis": self.are_cobis,
98
            "efermi": self.efermi,
99
            "energies": self.energies.tolist(),
100
            "COHP": {str(spin): pops.tolist() for spin, pops in self.cohp.items()},
101
        }
102
        if self.icohp:
1✔
103
            d["ICOHP"] = {str(spin): pops.tolist() for spin, pops in self.icohp.items()}
1✔
104
        return d
1✔
105

106
    def get_cohp(self, spin=None, integrated=False):
1✔
107
        """
108
        Returns the COHP or ICOHP for a particular spin.
109

110
        Args:
111
            spin: Spin. Can be parsed as spin object, integer (-1/1)
112
                or str ("up"/"down")
113
            integrated: Return COHP (False) or ICOHP (True)
114

115
        Returns:
116
            Returns the CHOP or ICOHP for the input spin. If Spin is
117
            None and both spins are present, both spins will be returned
118
            as a dictionary.
119
        """
120
        if not integrated:
1✔
121
            populations = self.cohp
1✔
122
        else:
123
            populations = self.icohp
1✔
124

125
        if populations is None:
1✔
126
            return None
1✔
127
        if spin is None:
1✔
128
            return populations
1✔
129
        if isinstance(spin, int):
×
130
            spin = Spin(spin)
×
131
        elif isinstance(spin, str):
×
132
            s = {"up": 1, "down": -1}[spin.lower()]
×
133
            spin = Spin(s)
×
134
        return {spin: populations[spin]}
×
135

136
    def get_icohp(self, spin=None):
1✔
137
        """
138
        Convenient alternative to get the ICOHP for a particular spin.
139
        """
140
        return self.get_cohp(spin=spin, integrated=True)
1✔
141

142
    def get_interpolated_value(self, energy, integrated=False):
1✔
143
        """
144
        Returns the COHP for a particular energy.
145

146
        Args:
147
            energy: Energy to return the COHP value for.
148
        """
149
        inter = {}
1✔
150
        for spin in self.cohp:
1✔
151
            if not integrated:
1✔
152
                inter[spin] = get_linear_interpolated_value(self.energies, self.cohp[spin], energy)
×
153
            elif self.icohp is not None:
1✔
154
                inter[spin] = get_linear_interpolated_value(self.energies, self.icohp[spin], energy)
1✔
155
            else:
156
                raise ValueError("ICOHP is empty.")
1✔
157
        return inter
1✔
158

159
    def has_antibnd_states_below_efermi(self, spin=None, limit=0.01):
1✔
160
        """
161
        Returns dict indicating if there are antibonding states below the Fermi level depending on the spin
162
            spin: Spin
163
            limit: -COHP smaller -limit will be considered.
164
        """
165
        warnings.warn("This method has not been tested on many examples. Check the parameter limit, pls!")
1✔
166

167
        populations = self.cohp
1✔
168
        number_energies_below_efermi = len([x for x in self.energies if x <= self.efermi])
1✔
169

170
        if populations is None:
1✔
171
            return None
×
172
        if spin is None:
1✔
173
            dict_to_return = {}
1✔
174
            for sp, cohpvalues in populations.items():
1✔
175
                if (max(cohpvalues[0:number_energies_below_efermi])) > limit:
1✔
176
                    dict_to_return[sp] = True
1✔
177
                else:
178
                    dict_to_return[sp] = False
1✔
179
        else:
180
            dict_to_return = {}
1✔
181
            if isinstance(spin, int):
1✔
182
                spin = Spin(spin)
×
183
            elif isinstance(spin, str):
1✔
184
                s = {"up": 1, "down": -1}[spin.lower()]
×
185
                spin = Spin(s)
×
186
            if (max(populations[spin][0:number_energies_below_efermi])) > limit:
1✔
187
                dict_to_return[spin] = True
×
188
            else:
189
                dict_to_return[spin] = False
1✔
190

191
        return dict_to_return
1✔
192

193
    @classmethod
1✔
194
    def from_dict(cls, d):
1✔
195
        """
196
        Returns a COHP object from a dict representation of the COHP.
197
        """
198
        if "ICOHP" in d:
1✔
199
            icohp = {Spin(int(key)): np.array(val) for key, val in d["ICOHP"].items()}
1✔
200
        else:
201
            icohp = None
×
202
        if "are_cobis" not in d:
1✔
203
            are_cobis = False
1✔
204
        else:
205
            are_cobis = d["are_cobis"]
1✔
206
        return Cohp(
1✔
207
            d["efermi"],
208
            d["energies"],
209
            {Spin(int(key)): np.array(val) for key, val in d["COHP"].items()},
210
            icohp=icohp,
211
            are_coops=d["are_coops"],
212
            are_cobis=are_cobis,
213
        )
214

215

216
class CompleteCohp(Cohp):
1✔
217
    """
218
    A wrapper class that defines an average COHP, and individual COHPs.
219

220
    .. attribute: are_coops
221

222
         Indicates whether the object is consisting of COOPs.
223

224

225
    .. attribute: are_cobis
226

227
         Indicates whether the object is consisting of COBIs.
228

229

230
    .. attribute: efermi
231

232
         Fermi energy
233

234
    .. attribute: energies
235

236
         Sequence of energies
237

238
    .. attribute: structure
239

240
         Structure associated with the COHPs.
241

242
    .. attribute: cohp, icohp
243

244
         The average COHP/ICOHP.
245

246
    .. attribute: all_cohps
247

248
         A dict of COHPs for individual bonds of the form {label: COHP}
249

250
    .. attribute: orb_res_cohp
251

252
        Orbital-resolved COHPs.
253
    """
254

255
    def __init__(
1✔
256
        self,
257
        structure,
258
        avg_cohp,
259
        cohp_dict,
260
        bonds=None,
261
        are_coops=False,
262
        are_cobis=False,
263
        orb_res_cohp=None,
264
    ):
265
        """
266
        Args:
267
            structure: Structure associated with this COHP.
268
            avg_cohp: The average cohp as a COHP object.
269
            cohps: A dict of COHP objects for individual bonds of the form
270
                {label: COHP}
271
            bonds: A dict containing information on the bonds of the form
272
                {label: {key: val}}. The key-val pair can be any information
273
                the user wants to put in, but typically contains the sites,
274
                the bond length, and the number of bonds. If nothing is
275
                supplied, it will default to an empty dict.
276
            are_coops: indicates whether the Cohp objects are COOPs.
277
                Defaults to False for COHPs.
278
            are_cobis: indicates whether the Cohp objects are COBIs.
279
                Defaults to False for COHPs.
280
            orb_res_cohp: Orbital-resolved COHPs.
281
        """
282
        if are_coops and are_cobis:
1✔
283
            raise ValueError("You cannot have info about COOPs and COBIs in the same file.")
×
284
        super().__init__(
1✔
285
            avg_cohp.efermi,
286
            avg_cohp.energies,
287
            avg_cohp.cohp,
288
            are_coops=are_coops,
289
            are_cobis=are_cobis,
290
            icohp=avg_cohp.icohp,
291
        )
292
        self.structure = structure
1✔
293
        self.are_coops = are_coops
1✔
294
        self.are_cobis = are_cobis
1✔
295
        self.all_cohps = cohp_dict
1✔
296
        self.orb_res_cohp = orb_res_cohp
1✔
297
        if bonds is None:
1✔
298
            self.bonds = {label: {} for label in self.all_cohps}
×
299
        else:
300
            self.bonds = bonds
1✔
301

302
    def __str__(self):
1✔
303
        if self.are_coops:
×
304
            return "Complete COOPs for " + str(self.structure)
×
305
        if self.are_cobis:
×
306
            return "Complete COBIs for " + str(self.structure)
×
307
        return "Complete COHPs for " + str(self.structure)
×
308

309
    def as_dict(self):
1✔
310
        """
311
        JSON-serializable dict representation of CompleteCohp.
312
        """
313
        d = {
1✔
314
            "@module": type(self).__module__,
315
            "@class": type(self).__name__,
316
            "are_coops": self.are_coops,
317
            "are_cobis": self.are_cobis,
318
            "efermi": self.efermi,
319
            "structure": self.structure.as_dict(),
320
            "energies": self.energies.tolist(),
321
            "COHP": {"average": {str(spin): pops.tolist() for spin, pops in self.cohp.items()}},
322
        }
323

324
        if self.icohp is not None:
1✔
325
            d["ICOHP"] = {"average": {str(spin): pops.tolist() for spin, pops in self.icohp.items()}}
1✔
326

327
        for label in self.all_cohps:
1✔
328
            d["COHP"].update({label: {str(spin): pops.tolist() for spin, pops in self.all_cohps[label].cohp.items()}})
1✔
329
            if self.all_cohps[label].icohp is not None:
1✔
330
                if "ICOHP" not in d:
1✔
331
                    d["ICOHP"] = {
×
332
                        label: {str(spin): pops.tolist() for spin, pops in self.all_cohps[label].icohp.items()}
333
                    }
334
                else:
335
                    d["ICOHP"].update(
1✔
336
                        {label: {str(spin): pops.tolist() for spin, pops in self.all_cohps[label].icohp.items()}}
337
                    )
338
        if False in [bond_dict == {} for bond_dict in self.bonds.values()]:
1✔
339
            d["bonds"] = {
1✔
340
                bond: {
341
                    "length": self.bonds[bond]["length"],
342
                    "sites": [site.as_dict() for site in self.bonds[bond]["sites"]],
343
                }
344
                for bond in self.bonds
345
            }
346
        if self.orb_res_cohp:
1✔
347
            orb_dict = {}
1✔
348
            for label in self.orb_res_cohp:
1✔
349
                orb_dict[label] = {}
1✔
350
                for orbs in self.orb_res_cohp[label]:
1✔
351
                    cohp = {str(spin): pops.tolist() for spin, pops in self.orb_res_cohp[label][orbs]["COHP"].items()}
1✔
352
                    orb_dict[label][orbs] = {"COHP": cohp}
1✔
353
                    icohp = {str(spin): pops.tolist() for spin, pops in self.orb_res_cohp[label][orbs]["ICOHP"].items()}
1✔
354
                    orb_dict[label][orbs]["ICOHP"] = icohp
1✔
355
                    orbitals = [[orb[0], orb[1].name] for orb in self.orb_res_cohp[label][orbs]["orbitals"]]
1✔
356
                    orb_dict[label][orbs]["orbitals"] = orbitals
1✔
357
            d["orb_res_cohp"] = orb_dict
1✔
358

359
        return d
1✔
360

361
    def get_cohp_by_label(self, label, summed_spin_channels=False):
1✔
362
        """
363
        Get specific COHP object.
364

365
        Args:
366
            label: string (for newer Lobster versions: a number)
367
            summed_spin_channels: bool, will sum the spin channels and return the sum in Spin.up if true
368

369
        Returns:
370
            Returns the COHP object to simplify plotting
371
        """
372
        # TODO: possibly add option to only get one spin channel as Spin.down in the future!
373
        if label.lower() == "average":
1✔
374
            divided_cohp = self.cohp
1✔
375
            divided_icohp = self.icohp
1✔
376

377
        else:
378
            divided_cohp = self.all_cohps[label].get_cohp(spin=None, integrated=False)
1✔
379
            divided_icohp = self.all_cohps[label].get_icohp(spin=None)
1✔
380

381
        if summed_spin_channels and Spin.down in self.cohp:
1✔
382
            final_cohp = {}
1✔
383
            final_icohp = {}
1✔
384
            final_cohp[Spin.up] = np.sum([divided_cohp[Spin.up], divided_cohp[Spin.down]], axis=0)
1✔
385
            final_icohp[Spin.up] = np.sum([divided_icohp[Spin.up], divided_icohp[Spin.down]], axis=0)
1✔
386
        else:
387
            final_cohp = divided_cohp
1✔
388
            final_icohp = divided_icohp
1✔
389

390
        return Cohp(
1✔
391
            efermi=self.efermi,
392
            energies=self.energies,
393
            cohp=final_cohp,
394
            are_coops=self.are_coops,
395
            are_cobis=self.are_cobis,
396
            icohp=final_icohp,
397
        )
398

399
    def get_summed_cohp_by_label_list(self, label_list, divisor=1, summed_spin_channels=False):
1✔
400
        """
401
        Returns a COHP object that includes a summed COHP divided by divisor
402

403
        Args:
404
            label_list: list of labels for the COHP that should be included in the summed cohp
405
            divisor: float/int, the summed cohp will be divided by this divisor
406
            summed_spin_channels: bool, will sum the spin channels and return the sum in Spin.up if true
407
        Returns:
408
            Returns a COHP object including a summed COHP
409
        """
410
        # check if cohps are spinpolarized or not
411
        first_cohpobject = self.get_cohp_by_label(label_list[0])
1✔
412
        summed_cohp = first_cohpobject.cohp.copy()
1✔
413
        summed_icohp = first_cohpobject.icohp.copy()
1✔
414
        for label in label_list[1:]:
1✔
415
            cohp_here = self.get_cohp_by_label(label)
1✔
416
            summed_cohp[Spin.up] = np.sum([summed_cohp[Spin.up], cohp_here.cohp[Spin.up]], axis=0)
1✔
417

418
            if Spin.down in summed_cohp:
1✔
419
                summed_cohp[Spin.down] = np.sum([summed_cohp[Spin.down], cohp_here.cohp[Spin.down]], axis=0)
1✔
420

421
            summed_icohp[Spin.up] = np.sum([summed_icohp[Spin.up], cohp_here.icohp[Spin.up]], axis=0)
1✔
422

423
            if Spin.down in summed_icohp:
1✔
424
                summed_icohp[Spin.down] = np.sum([summed_icohp[Spin.down], cohp_here.icohp[Spin.down]], axis=0)
1✔
425

426
        divided_cohp = {}
1✔
427
        divided_icohp = {}
1✔
428
        divided_cohp[Spin.up] = np.divide(summed_cohp[Spin.up], divisor)
1✔
429
        divided_icohp[Spin.up] = np.divide(summed_icohp[Spin.up], divisor)
1✔
430
        if Spin.down in summed_cohp:
1✔
431
            divided_cohp[Spin.down] = np.divide(summed_cohp[Spin.down], divisor)
1✔
432
            divided_icohp[Spin.down] = np.divide(summed_icohp[Spin.down], divisor)
1✔
433

434
        if summed_spin_channels and Spin.down in summed_cohp:
1✔
435
            final_cohp = {}
1✔
436
            final_icohp = {}
1✔
437
            final_cohp[Spin.up] = np.sum([divided_cohp[Spin.up], divided_cohp[Spin.down]], axis=0)
1✔
438
            final_icohp[Spin.up] = np.sum([divided_icohp[Spin.up], divided_icohp[Spin.down]], axis=0)
1✔
439
        else:
440
            final_cohp = divided_cohp
1✔
441
            final_icohp = divided_icohp
1✔
442

443
        return Cohp(
1✔
444
            efermi=first_cohpobject.efermi,
445
            energies=first_cohpobject.energies,
446
            cohp=final_cohp,
447
            are_coops=first_cohpobject.are_coops,
448
            are_cobis=first_cohpobject.are_coops,
449
            icohp=final_icohp,
450
        )
451

452
    def get_summed_cohp_by_label_and_orbital_list(
1✔
453
        self, label_list, orbital_list, divisor=1, summed_spin_channels=False
454
    ):
455
        """
456
        Returns a COHP object that includes a summed COHP divided by divisor
457

458
        Args:
459
            label_list: list of labels for the COHP that should be included in the summed cohp
460
            orbital_list: list of orbitals for the COHPs that should be included in the summed cohp (same order as
461
                label_list)
462
            divisor: float/int, the summed cohp will be divided by this divisor
463
            summed_spin_channels: bool, will sum the spin channels and return the sum in Spin.up if true
464

465
        Returns:
466
            Returns a COHP object including a summed COHP
467
        """
468
        # check length of label_list and orbital_list:
469
        if not len(label_list) == len(orbital_list):
1✔
470
            raise ValueError("label_list and orbital_list don't have the same length!")
1✔
471
        # check if cohps are spinpolarized or not
472
        first_cohpobject = self.get_orbital_resolved_cohp(label_list[0], orbital_list[0])
1✔
473
        summed_cohp = first_cohpobject.cohp.copy()
1✔
474
        summed_icohp = first_cohpobject.icohp.copy()
1✔
475
        for ilabel, label in enumerate(label_list[1:], 1):
1✔
476
            cohp_here = self.get_orbital_resolved_cohp(label, orbital_list[ilabel])
1✔
477
            summed_cohp[Spin.up] = np.sum([summed_cohp[Spin.up], cohp_here.cohp.copy()[Spin.up]], axis=0)
1✔
478
            if Spin.down in summed_cohp:
1✔
479
                summed_cohp[Spin.down] = np.sum([summed_cohp[Spin.down], cohp_here.cohp.copy()[Spin.down]], axis=0)
×
480
            summed_icohp[Spin.up] = np.sum([summed_icohp[Spin.up], cohp_here.icohp.copy()[Spin.up]], axis=0)
1✔
481
            if Spin.down in summed_icohp:
1✔
482
                summed_icohp[Spin.down] = np.sum([summed_icohp[Spin.down], cohp_here.icohp.copy()[Spin.down]], axis=0)
×
483

484
        divided_cohp = {}
1✔
485
        divided_icohp = {}
1✔
486
        divided_cohp[Spin.up] = np.divide(summed_cohp[Spin.up], divisor)
1✔
487
        divided_icohp[Spin.up] = np.divide(summed_icohp[Spin.up], divisor)
1✔
488
        if Spin.down in summed_cohp:
1✔
489
            divided_cohp[Spin.down] = np.divide(summed_cohp[Spin.down], divisor)
1✔
490
            divided_icohp[Spin.down] = np.divide(summed_icohp[Spin.down], divisor)
1✔
491

492
        if summed_spin_channels and Spin.down in divided_cohp:
1✔
493
            final_cohp = {}
1✔
494
            final_icohp = {}
1✔
495

496
            final_cohp[Spin.up] = np.sum([divided_cohp[Spin.up], divided_cohp[Spin.down]], axis=0)
1✔
497
            final_icohp[Spin.up] = np.sum([divided_icohp[Spin.up], divided_icohp[Spin.down]], axis=0)
1✔
498
        else:
499
            final_cohp = divided_cohp
1✔
500
            final_icohp = divided_icohp
1✔
501

502
        return Cohp(
1✔
503
            efermi=first_cohpobject.efermi,
504
            energies=first_cohpobject.energies,
505
            cohp=final_cohp,
506
            are_coops=first_cohpobject.are_coops,
507
            are_cobis=first_cohpobject.are_cobis,
508
            icohp=final_icohp,
509
        )
510

511
    def get_orbital_resolved_cohp(self, label, orbitals, summed_spin_channels=False):
1✔
512
        """
513
        Get orbital-resolved COHP.
514

515
        Args:
516
            label: bond label (Lobster: labels as in ICOHPLIST/ICOOPLIST.lobster).
517

518
            orbitals: The orbitals as a label, or list or tuple of the form
519
                [(n1, orbital1), (n2, orbital2)]. Orbitals can either be str,
520
                int, or Orbital.
521

522
            summed_spin_channels: bool, will sum the spin channels and return the sum in Spin.up if true
523

524
        Returns:
525
            A Cohp object if CompleteCohp contains orbital-resolved cohp,
526
            or None if it doesn't.
527

528
        Note: It currently assumes that orbitals are str if they aren't the
529
            other valid types. This is not ideal, but the easiest way to
530
            avoid unicode issues between python 2 and python 3.
531
        """
532
        if self.orb_res_cohp is None:
1✔
533
            return None
×
534
        if isinstance(orbitals, (list, tuple)):
1✔
535
            cohp_orbs = [d["orbitals"] for d in self.orb_res_cohp[label].values()]
1✔
536
            orbs = []
1✔
537
            for orbital in orbitals:
1✔
538
                if isinstance(orbital[1], int):
1✔
539
                    orbs.append(tuple((orbital[0], Orbital(orbital[1]))))
1✔
540
                elif isinstance(orbital[1], Orbital):
1✔
541
                    orbs.append(tuple((orbital[0], orbital[1])))
1✔
542
                elif isinstance(orbital[1], str):
1✔
543
                    orbs.append(tuple((orbital[0], Orbital[orbital[1]])))
1✔
544
                else:
545
                    raise TypeError("Orbital must be str, int, or Orbital.")
×
546
            orb_index = cohp_orbs.index(orbs)
1✔
547
            orb_label = list(self.orb_res_cohp[label])[orb_index]
1✔
548
        elif isinstance(orbitals, str):
1✔
549
            orb_label = orbitals
1✔
550
        else:
551
            raise TypeError("Orbitals must be str, list, or tuple.")
×
552
        try:
1✔
553
            icohp = self.orb_res_cohp[label][orb_label]["ICOHP"]
1✔
554
        except KeyError:
×
555
            icohp = None
×
556

557
        start_cohp = self.orb_res_cohp[label][orb_label]["COHP"]
1✔
558
        start_icohp = icohp
1✔
559

560
        if summed_spin_channels and Spin.down in start_cohp:
1✔
561
            final_cohp = {}
1✔
562
            final_icohp = {}
1✔
563
            final_cohp[Spin.up] = np.sum([start_cohp[Spin.up], start_cohp[Spin.down]], axis=0)
1✔
564
            if start_icohp is not None:
1✔
565
                final_icohp[Spin.up] = np.sum([start_icohp[Spin.up], start_icohp[Spin.down]], axis=0)
1✔
566
        else:
567
            final_cohp = start_cohp
1✔
568
            final_icohp = start_icohp
1✔
569

570
        return Cohp(
1✔
571
            self.efermi,
572
            self.energies,
573
            final_cohp,
574
            icohp=final_icohp,
575
            are_coops=self.are_coops,
576
            are_cobis=self.are_cobis,
577
        )
578

579
    @classmethod
1✔
580
    def from_dict(cls, d):
1✔
581
        """
582
        Returns CompleteCohp object from dict representation.
583
        """
584
        cohp_dict = {}
1✔
585
        efermi = d["efermi"]
1✔
586
        energies = d["energies"]
1✔
587
        structure = Structure.from_dict(d["structure"])
1✔
588
        if "bonds" in d:
1✔
589
            bonds = {
1✔
590
                bond: {
591
                    "length": d["bonds"][bond]["length"],
592
                    "sites": tuple(PeriodicSite.from_dict(site) for site in d["bonds"][bond]["sites"]),
593
                }
594
                for bond in d["bonds"]
595
            }
596
        else:
597
            bonds = None
×
598
        for label in d["COHP"]:
1✔
599
            cohp = {Spin(int(spin)): np.array(d["COHP"][label][spin]) for spin in d["COHP"][label]}
1✔
600
            try:
1✔
601
                icohp = {Spin(int(spin)): np.array(d["ICOHP"][label][spin]) for spin in d["ICOHP"][label]}
1✔
602
            except KeyError:
×
603
                icohp = None
×
604
            if label == "average":
1✔
605
                avg_cohp = Cohp(efermi, energies, cohp, icohp=icohp)
1✔
606
            else:
607
                cohp_dict[label] = Cohp(efermi, energies, cohp, icohp=icohp)
1✔
608

609
        if "orb_res_cohp" in d:
1✔
610
            orb_cohp = {}
1✔
611
            for label in d["orb_res_cohp"]:
1✔
612
                orb_cohp[label] = {}
1✔
613
                for orb in d["orb_res_cohp"][label]:
1✔
614
                    cohp = {
1✔
615
                        Spin(int(s)): np.array(d["orb_res_cohp"][label][orb]["COHP"][s], dtype=float)
616
                        for s in d["orb_res_cohp"][label][orb]["COHP"]
617
                    }
618
                    try:
1✔
619
                        icohp = {
1✔
620
                            Spin(int(s)): np.array(d["orb_res_cohp"][label][orb]["ICOHP"][s], dtype=float)
621
                            for s in d["orb_res_cohp"][label][orb]["ICOHP"]
622
                        }
623
                    except KeyError:
×
624
                        icohp = None
×
625
                    orbitals = [tuple((int(o[0]), Orbital[o[1]])) for o in d["orb_res_cohp"][label][orb]["orbitals"]]
1✔
626
                    orb_cohp[label][orb] = {
1✔
627
                        "COHP": cohp,
628
                        "ICOHP": icohp,
629
                        "orbitals": orbitals,
630
                    }
631
                # If no total COHPs are present, calculate the total
632
                # COHPs from the single-orbital populations. Total COHPs
633
                # may not be present when the cohpgenerator keyword is used
634
                # in LOBSTER versions 2.2.0 and earlier.
635
                if label not in d["COHP"] or d["COHP"][label] is None:
1✔
636
                    cohp = {
×
637
                        Spin.up: np.sum(
638
                            np.array([orb_cohp[label][orb]["COHP"][Spin.up] for orb in orb_cohp[label]]),
639
                            axis=0,
640
                        )
641
                    }
642
                    try:
×
643
                        cohp[Spin.down] = np.sum(
×
644
                            np.array([orb_cohp[label][orb]["COHP"][Spin.down] for orb in orb_cohp[label]]),
645
                            axis=0,
646
                        )
647
                    except KeyError:
×
648
                        pass
×
649

650
                orb_res_icohp = None in [orb_cohp[label][orb]["ICOHP"] for orb in orb_cohp[label]]
1✔
651
                if (label not in d["ICOHP"] or d["ICOHP"][label] is None) and orb_res_icohp:
1✔
652
                    icohp = {
×
653
                        Spin.up: np.sum(
654
                            np.array([orb_cohp[label][orb]["ICOHP"][Spin.up] for orb in orb_cohp[label]]),
655
                            axis=0,
656
                        )
657
                    }
658
                    try:
×
659
                        icohp[Spin.down] = np.sum(
×
660
                            np.array([orb_cohp[label][orb]["ICOHP"][Spin.down] for orb in orb_cohp[label]]),
661
                            axis=0,
662
                        )
663
                    except KeyError:
×
664
                        pass
×
665
        else:
666
            orb_cohp = None
1✔
667

668
        if "average" not in d["COHP"]:
1✔
669
            # calculate average
670
            cohp = np.array([np.array(c) for c in d["COHP"].values()]).mean(axis=0)
×
671
            try:
×
672
                icohp = np.array([np.array(c) for c in d["ICOHP"].values()]).mean(axis=0)
×
673
            except KeyError:
×
674
                icohp = None
×
675
            avg_cohp = Cohp(efermi, energies, cohp, icohp=icohp)
×
676

677
        if "are_cobis" not in d:
1✔
678
            are_cobis = False
1✔
679
        else:
680
            are_cobis = d["are_cobis"]
×
681

682
        return CompleteCohp(
1✔
683
            structure,
684
            avg_cohp,
685
            cohp_dict,
686
            bonds=bonds,
687
            are_coops=d["are_coops"],
688
            are_cobis=are_cobis,
689
            orb_res_cohp=orb_cohp,
690
        )
691

692
    @classmethod
1✔
693
    def from_file(cls, fmt, filename=None, structure_file=None, are_coops=False, are_cobis=False):
1✔
694
        """
695
        Creates a CompleteCohp object from an output file of a COHP
696
        calculation. Valid formats are either LMTO (for the Stuttgart
697
        LMTO-ASA code) or LOBSTER (for the LOBSTER code).
698

699
        Args:
700
            cohp_file: Name of the COHP output file. Defaults to COPL
701
                for LMTO and COHPCAR.lobster/COOPCAR.lobster for LOBSTER.
702

703
            are_coops: Indicates whether the populations are COOPs or
704
                COHPs. Defaults to False for COHPs.
705

706
            are_cobis: Indicates whether the populations are COBIs or
707
                COHPs. Defaults to False for COHPs.
708

709
            fmt: A string for the code that was used to calculate
710
                the COHPs so that the output file can be handled
711
                correctly. Can take the values "LMTO" or "LOBSTER".
712

713
            structure_file: Name of the file containing the structure.
714
                If no file name is given, use CTRL for LMTO and POSCAR
715
                for LOBSTER.
716

717
        Returns:
718
            A CompleteCohp object.
719
        """
720
        if are_coops and are_cobis:
1✔
721
            raise ValueError("You cannot have info about COOPs and COBIs in the same file.")
×
722
        fmt = fmt.upper()
1✔
723
        if fmt == "LMTO":
1✔
724
            # LMTO COOPs and orbital-resolved COHP cannot be handled yet.
725
            are_coops = False
1✔
726
            are_cobis = False
1✔
727
            orb_res_cohp = None
1✔
728
            if structure_file is None:
1✔
729
                structure_file = "CTRL"
×
730
            if filename is None:
1✔
731
                filename = "COPL"
×
732
            cohp_file = LMTOCopl(filename=filename, to_eV=True)
1✔
733
        elif fmt == "LOBSTER":
1✔
734
            if are_coops and are_cobis:
1✔
735
                raise ValueError("You cannot have info about COOPs and COBIs in the same file.")
×
736
            if structure_file is None:
1✔
737
                structure_file = "POSCAR"
×
738
            if filename is None:
1✔
739
                if filename is None:
×
740
                    if are_coops:
×
741
                        filename = "COOPCAR.lobster"
×
742
                    elif are_cobis:
×
743
                        filename = "COBICAR.lobster"
×
744
                    else:
745
                        filename = "COHPCAR.lobster"
×
746
            cohp_file = Cohpcar(filename=filename, are_coops=are_coops, are_cobis=are_cobis)
1✔
747
            orb_res_cohp = cohp_file.orb_res_cohp
1✔
748
        else:
749
            raise ValueError(f"Unknown format {fmt}. Valid formats are LMTO and LOBSTER.")
×
750

751
        structure = Structure.from_file(structure_file)
1✔
752
        efermi = cohp_file.efermi
1✔
753
        cohp_data = cohp_file.cohp_data
1✔
754
        energies = cohp_file.energies
1✔
755

756
        # Lobster shifts the energies so that the Fermi energy is at zero.
757
        # Shifting should be done by the plotter object though.
758

759
        spins = [Spin.up, Spin.down] if cohp_file.is_spin_polarized else [Spin.up]
1✔
760
        if fmt == "LOBSTER":
1✔
761
            energies += efermi
1✔
762

763
        if orb_res_cohp is not None:
1✔
764
            # If no total COHPs are present, calculate the total
765
            # COHPs from the single-orbital populations. Total COHPs
766
            # may not be present when the cohpgenerator keyword is used
767
            # in LOBSTER versions 2.2.0 and earlier.
768
            # TODO: Test this more extensively
769
            # pylint: disable=E1133,E1136
770
            for label in orb_res_cohp:
1✔
771
                if cohp_file.cohp_data[label]["COHP"] is None:
1✔
772
                    # print(label)
773
                    cohp_data[label]["COHP"] = {
1✔
774
                        sp: np.sum(
775
                            [orb_res_cohp[label][orbs]["COHP"][sp] for orbs in orb_res_cohp[label]],
776
                            axis=0,
777
                        )
778
                        for sp in spins
779
                    }
780
                if cohp_file.cohp_data[label]["ICOHP"] is None:
1✔
781
                    cohp_data[label]["ICOHP"] = {
1✔
782
                        sp: np.sum(
783
                            [orb_res_cohp[label][orbs]["ICOHP"][sp] for orbs in orb_res_cohp[label]],
784
                            axis=0,
785
                        )
786
                        for sp in spins
787
                    }
788

789
        if fmt == "LMTO":
1✔
790
            # Calculate the average COHP for the LMTO file to be
791
            # consistent with LOBSTER output.
792
            avg_data = {"COHP": {}, "ICOHP": {}}
1✔
793
            for i in avg_data:  # pylint: disable=C0206
1✔
794
                for spin in spins:
1✔
795
                    rows = np.array([v[i][spin] for v in cohp_data.values()])
1✔
796
                    avg = np.average(rows, axis=0)
1✔
797
                    # LMTO COHPs have 5 significant figures
798
                    avg_data[i].update({spin: np.array([round_to_sigfigs(a, 5) for a in avg], dtype=float)})
1✔
799
            avg_cohp = Cohp(efermi, energies, avg_data["COHP"], icohp=avg_data["ICOHP"])
1✔
800
        else:
801
            avg_cohp = Cohp(
1✔
802
                efermi,
803
                energies,
804
                cohp_data["average"]["COHP"],
805
                icohp=cohp_data["average"]["COHP"],
806
                are_coops=are_coops,
807
                are_cobis=are_cobis,
808
            )
809
            del cohp_data["average"]
1✔
810

811
        cohp_dict = {
1✔
812
            label: Cohp(efermi, energies, v["COHP"], icohp=v["ICOHP"], are_coops=are_coops, are_cobis=are_cobis)
813
            for label, v in cohp_data.items()
814
        }
815

816
        bond_dict = {
1✔
817
            label: {
818
                "length": v["length"],
819
                "sites": [structure.sites[site] for site in v["sites"]],
820
            }
821
            for label, v in cohp_data.items()
822
        }
823

824
        return CompleteCohp(
1✔
825
            structure,
826
            avg_cohp,
827
            cohp_dict,
828
            bonds=bond_dict,
829
            are_coops=are_coops,
830
            are_cobis=are_cobis,
831
            orb_res_cohp=orb_res_cohp,
832
        )
833

834

835
class IcohpValue(MSONable):
1✔
836
    """
837
    Class to store information on an ICOHP or ICOOP value
838

839
    .. attribute:: num_bonds
840
            number of bonds used for the average cohp (relevant for Lobster versions <3.0) (int)
841

842
    .. attribute:: are_coops
843
            Boolean to indicates ICOOPs
844

845
    .. attribute:: are_cobis
846
            Boolean to indicates ICOBIs
847

848
    .. attribute:: icohp
849
            dict={Spin.up: icohpvalue for spin.up, Spin.down: icohpvalue for spin.down}
850

851
    .. attribute:: summed_icohp:
852
            sum of icohp/icoop of both spin channels
853

854
    """
855

856
    def __init__(self, label, atom1, atom2, length, translation, num, icohp, are_coops=False, are_cobis=False):
1✔
857
        """
858
        Args:
859
            label: label for the icohp
860
            atom1: str of atom that is contributing to the bond
861
            atom2: str of second atom that is contributing to the bond
862
            length: float of bond lengths
863
            translation: translation list, e.g. [0,0,0]
864
            num: integer describing how often the bond exists
865
            icohp: dict={Spin.up: icohpvalue for spin.up, Spin.down: icohpvalue for spin.down}
866
            are_coops: if True, this are COOPs
867
            are_cobis: if True, this are COBIs
868
        """
869
        if are_coops and are_cobis:
1✔
870
            raise ValueError("You cannot have info about COOPs and COBIs in the same file.")
×
871
        self._are_coops = are_coops
1✔
872
        self._are_cobis = are_cobis
1✔
873
        self._label = label
1✔
874
        self._atom1 = atom1
1✔
875
        self._atom2 = atom2
1✔
876
        self._length = length
1✔
877
        self._translation = translation
1✔
878
        self._num = num
1✔
879
        self._icohp = icohp
1✔
880
        if Spin.down in self._icohp:
1✔
881
            self._is_spin_polarized = True
1✔
882
        else:
883
            self._is_spin_polarized = False
1✔
884

885
    def __str__(self):
1✔
886
        if not self._are_coops and not self._are_cobis:
×
887
            if self._is_spin_polarized:
×
888
                return (
×
889
                    "ICOHP "
890
                    + str(self._label)
891
                    + " between "
892
                    + str(self._atom1)
893
                    + " and "
894
                    + str(self._atom2)
895
                    + " ("
896
                    + str(self._translation)
897
                    + "): "
898
                    + str(self._icohp[Spin.up])
899
                    + " eV (Spin up) and "
900
                    + str(self._icohp[Spin.down])
901
                    + " eV (Spin down)"
902
                )
903
            return (
×
904
                "ICOHP "
905
                + str(self._label)
906
                + " between "
907
                + str(self._atom1)
908
                + " and "
909
                + str(self._atom2)
910
                + " ("
911
                + str(self._translation)
912
                + "): "
913
                + str(self._icohp[Spin.up])
914
                + " eV (Spin up)"
915
            )
916
        if self._are_coops and not self._are_cobis:
×
917
            if self._is_spin_polarized:
×
918
                return (
×
919
                    "ICOOP "
920
                    + str(self._label)
921
                    + " between "
922
                    + str(self._atom1)
923
                    + " and "
924
                    + str(self._atom2)
925
                    + " ("
926
                    + str(self._translation)
927
                    + "): "
928
                    + str(self._icohp[Spin.up])
929
                    + " (Spin up) and "
930
                    + str(self._icohp[Spin.down])
931
                    + " (Spin down)"
932
                )
933
            return (
×
934
                "ICOOP "
935
                + str(self._label)
936
                + " between "
937
                + str(self._atom1)
938
                + " and "
939
                + str(self._atom2)
940
                + " ("
941
                + str(self._translation)
942
                + "): "
943
                + str(self._icohp[Spin.up])
944
                + " (Spin up)"
945
            )
946
        if self._are_cobis and not self._are_coops:
×
947
            if self._is_spin_polarized:
×
948
                return (
×
949
                    "ICOBI "
950
                    + str(self._label)
951
                    + " between "
952
                    + str(self._atom1)
953
                    + " and "
954
                    + str(self._atom2)
955
                    + " ("
956
                    + str(self._translation)
957
                    + "): "
958
                    + str(self._icohp[Spin.up])
959
                    + " (Spin up) and "
960
                    + str(self._icohp[Spin.down])
961
                    + " (Spin down)"
962
                )
963
            return (
×
964
                "ICOBI "
965
                + str(self._label)
966
                + " between "
967
                + str(self._atom1)
968
                + " and "
969
                + str(self._atom2)
970
                + " ("
971
                + str(self._translation)
972
                + "): "
973
                + str(self._icohp[Spin.up])
974
                + " (Spin up)"
975
            )
976

977
    @property
1✔
978
    def num_bonds(self):
1✔
979
        """
980
        tells the number of bonds for which the ICOHP value is an average
981
        Returns:
982
            Int
983
        """
984
        return self._num
1✔
985

986
    @property
1✔
987
    def are_coops(self) -> bool:
1✔
988
        """
989
        tells if ICOOPs or not
990
        Returns:
991
            Boolean
992
        """
993
        return self._are_coops
1✔
994

995
    @property
1✔
996
    def are_cobis(self) -> bool:
1✔
997
        """
998
        tells if ICOBIs or not
999
        Returns:
1000
            Boolean
1001
        """
1002
        return self._are_cobis
1✔
1003

1004
    @property
1✔
1005
    def is_spin_polarized(self) -> bool:
1✔
1006
        """
1007
        tells if spin polarized calculation or not
1008
        Returns:
1009
            Boolean
1010
        """
1011
        return self._is_spin_polarized
1✔
1012

1013
    def icohpvalue(self, spin=Spin.up):
1✔
1014
        """
1015
        Args:
1016
            spin: Spin.up or Spin.down
1017
        Returns:
1018
            icohpvalue (float) corresponding to chosen spin
1019
        """
1020
        if not self.is_spin_polarized and spin == Spin.down:
1✔
1021
            raise ValueError("The calculation was not performed with spin polarization")
×
1022

1023
        return self._icohp[spin]
1✔
1024

1025
    @property
1✔
1026
    def icohp(self):
1✔
1027
        """
1028
        dict with icohps for spinup and spindown
1029
        Return:
1030
            dict={Spin.up: icohpvalue for spin.up, Spin.down: icohpvalue for spin.down}
1031
        """
1032
        return self._icohp
1✔
1033

1034
    @property
1✔
1035
    def summed_icohp(self):
1✔
1036
        """
1037
        Adds ICOHPs of both spin channels for spin polarized compounds
1038
        Returns:
1039
             icohp value in eV
1040
        """
1041
        if self._is_spin_polarized:
1✔
1042
            sum_icohp = self._icohp[Spin.down] + self._icohp[Spin.up]
1✔
1043
        else:
1044
            sum_icohp = self._icohp[Spin.up]
1✔
1045
        return sum_icohp
1✔
1046

1047

1048
class IcohpCollection(MSONable):
1✔
1049
    """
1050
    Class to store IcohpValues
1051

1052
    .. attribute:: are_coops
1053
        Boolean to indicate if this are ICOOPs
1054

1055
    .. attribute:: are_cobis
1056
        Boolean to indicate if this are ICOOPs
1057

1058
    .. attribute:: is_spin_polarized
1059
        Boolean to indicate if the Lobster calculation was done spin polarized or not
1060

1061
    """
1062

1063
    def __init__(
1✔
1064
        self,
1065
        list_labels,
1066
        list_atom1,
1067
        list_atom2,
1068
        list_length,
1069
        list_translation,
1070
        list_num,
1071
        list_icohp,
1072
        is_spin_polarized,
1073
        are_coops=False,
1074
        are_cobis=False,
1075
    ):
1076
        """
1077
        Args:
1078
            is_spin_polarized: Boolean to indicate if the Lobster calculation was done spin polarized or not Boolean to
1079
                indicate if the Lobster calculation was done spin polarized or not
1080
            are_coops: Boolean to indicate whether ICOOPs are stored
1081
            are_cobis: Boolean to indicate whether ICOBIs are stored
1082
            list_labels: list of labels for ICOHP/ICOOP values
1083
            list_atom1: list of str of atomnames e.g. "O1"
1084
            list_atom2: list of str of atomnames e.g. "O1"
1085
            list_length: list of lengths of corresponding bonds in Angstrom
1086
            list_translation: list of translation list, e.g. [0,0,0]
1087
            list_num: list of equivalent bonds, usually 1 starting from Lobster 3.0.0
1088
            list_icohp: list of dict={Spin.up: icohpvalue for spin.up, Spin.down: icohpvalue for spin.down}
1089
        """
1090
        if are_coops and are_cobis:
1✔
1091
            raise ValueError("You cannot have info about COOPs and COBIs in the same file.")
×
1092
        self._are_coops = are_coops
1✔
1093
        self._are_cobis = are_cobis
1✔
1094
        self._icohplist = {}
1✔
1095
        self._is_spin_polarized = is_spin_polarized
1✔
1096
        self._list_labels = list_labels
1✔
1097
        self._list_atom1 = list_atom1
1✔
1098
        self._list_atom2 = list_atom2
1✔
1099
        self._list_length = list_length
1✔
1100
        self._list_translation = list_translation
1✔
1101
        self._list_num = list_num
1✔
1102
        self._list_icohp = list_icohp
1✔
1103

1104
        for ilist, listel in enumerate(list_labels):
1✔
1105
            self._icohplist[listel] = IcohpValue(
1✔
1106
                label=listel,
1107
                atom1=list_atom1[ilist],
1108
                atom2=list_atom2[ilist],
1109
                length=list_length[ilist],
1110
                translation=list_translation[ilist],
1111
                num=list_num[ilist],
1112
                icohp=list_icohp[ilist],
1113
                are_coops=are_coops,
1114
                are_cobis=are_cobis,
1115
            )
1116

1117
    def __str__(self):
1✔
1118
        joinstr = []
×
1119
        for value in self._icohplist.values():
×
1120
            joinstr.append(str(value))
×
1121
        return "\n".join(joinstr)
×
1122

1123
    def get_icohp_by_label(self, label, summed_spin_channels=True, spin=Spin.up):
1✔
1124
        """
1125
        get an icohp value for a certain bond as indicated by the label (bond labels starting by "1" as in
1126
        ICOHPLIST/ICOOPLIST)
1127

1128
        Args:
1129
            label: label in str format (usually the bond number in Icohplist.lobster/Icooplist.lobster
1130
            summed_spin_channels: Boolean to indicate whether the ICOHPs/ICOOPs of both spin channels should be summed
1131
            spin: if summed_spin_channels is equal to False, this spin indicates which spin channel should be returned
1132

1133
        Returns:
1134
            float describing ICOHP/ICOOP value
1135
        """
1136
        icohp_here = self._icohplist[label]
1✔
1137
        if icohp_here._is_spin_polarized:
1✔
1138
            if summed_spin_channels:
1✔
1139
                return icohp_here.summed_icohp
1✔
1140
            return icohp_here.icohpvalue(spin)
1✔
1141
        return icohp_here.icohpvalue(spin)
1✔
1142

1143
    def get_summed_icohp_by_label_list(self, label_list, divisor=1.0, summed_spin_channels=True, spin=Spin.up):
1✔
1144
        """
1145
        get the sum of several ICOHP values that are indicated by a list of labels (labels of the bonds are the same as
1146
        in ICOHPLIST/ICOOPLIST)
1147

1148
        Args:
1149
            label_list: list of labels of the ICOHPs/ICOOPs that should be summed
1150
            divisor: is used to divide the sum
1151
            summed_spin_channels: Boolean to indicate whether the ICOHPs/ICOOPs of both spin channels should be summed
1152
            spin: if summed_spin_channels is equal to False, this spin indicates which spin channel should be returned
1153

1154
        Returns:
1155
             float that is a sum of all ICOHPs/ICOOPs as indicated with label_list
1156
        """
1157
        sum_icohp = 0
1✔
1158
        for label in label_list:
1✔
1159
            icohp_here = self._icohplist[label]
1✔
1160
            if icohp_here.num_bonds != 1:
1✔
1161
                warnings.warn("One of the ICOHP values is an average over bonds. This is currently not considered.")
1✔
1162
            # prints warning if num_bonds is not equal to 1
1163
            if icohp_here._is_spin_polarized:
1✔
1164
                if summed_spin_channels:
1✔
1165
                    sum_icohp = sum_icohp + icohp_here.summed_icohp
1✔
1166
                else:
1167
                    sum_icohp = sum_icohp + icohp_here.icohpvalue(spin)
1✔
1168
            else:
1169
                sum_icohp = sum_icohp + icohp_here.icohpvalue(spin)
1✔
1170
        return sum_icohp / divisor
1✔
1171

1172
    def get_icohp_dict_by_bondlengths(self, minbondlength=0.0, maxbondlength=8.0):
1✔
1173
        """
1174
        get a dict of IcohpValues corresponding to certain bond lengths
1175
        Args:
1176
            minbondlength: defines the minimum of the bond lengths of the bonds
1177
            maxbondlength: defines the maximum of the bond lengths of the bonds
1178
        Returns:
1179
             dict of IcohpValues, the keys correspond to the values from the initial list_labels
1180
        """
1181
        newicohp_dict = {}
1✔
1182
        for value in self._icohplist.values():
1✔
1183
            if value._length >= minbondlength and value._length <= maxbondlength:
1✔
1184
                newicohp_dict[value._label] = value
1✔
1185
        return newicohp_dict
1✔
1186

1187
    def get_icohp_dict_of_site(
1✔
1188
        self,
1189
        site,
1190
        minsummedicohp=None,
1191
        maxsummedicohp=None,
1192
        minbondlength=0.0,
1193
        maxbondlength=8.0,
1194
        only_bonds_to=None,
1195
    ):
1196
        """
1197
        get a dict of IcohpValue for a certain site (indicated by integer)
1198
        Args:
1199
            site: integer describing the site of interest, order as in Icohplist.lobster/Icooplist.lobster, starts at 0
1200
            minsummedicohp: float, minimal icohp/icoop of the bonds that are considered. It is the summed ICOHP value
1201
                from both spin channels for spin polarized cases
1202
            maxsummedicohp: float, maximal icohp/icoop of the bonds that are considered. It is the summed ICOHP value
1203
                from both spin channels for spin polarized cases
1204
            minbondlength: float, defines the minimum of the bond lengths of the bonds
1205
            maxbondlength: float, defines the maximum of the bond lengths of the bonds
1206
            only_bonds_to: list of strings describing the bonding partners that are allowed, e.g. ['O']
1207
        Returns:
1208
             dict of IcohpValues, the keys correspond to the values from the initial list_labels
1209
        """
1210
        newicohp_dict = {}
1✔
1211
        for key, value in self._icohplist.items():
1✔
1212
            atomnumber1 = int(re.split(r"(\d+)", value._atom1)[1]) - 1
1✔
1213
            atomnumber2 = int(re.split(r"(\d+)", value._atom2)[1]) - 1
1✔
1214
            if site in (atomnumber1, atomnumber2):
1✔
1215
                # manipulate order of atoms so that searched one is always atom1
1216
                if site == atomnumber2:
1✔
1217
                    save = value._atom1
1✔
1218
                    value._atom1 = value._atom2
1✔
1219
                    value._atom2 = save
1✔
1220

1221
                if only_bonds_to is None:
1✔
1222
                    second_test = True
1✔
1223
                else:
1224
                    second_test = re.split(r"(\d+)", value._atom2)[0] in only_bonds_to
1✔
1225
                if value._length >= minbondlength and value._length <= maxbondlength and second_test:
1✔
1226
                    if minsummedicohp is not None:
1✔
1227
                        if value.summed_icohp >= minsummedicohp:
1✔
1228
                            if maxsummedicohp is not None:
1✔
1229
                                if value.summed_icohp <= maxsummedicohp:
1✔
1230
                                    newicohp_dict[key] = value
1✔
1231
                            else:
1232
                                newicohp_dict[key] = value
1✔
1233
                    else:
1234
                        if maxsummedicohp is not None:
1✔
1235
                            if value.summed_icohp <= maxsummedicohp:
1✔
1236
                                newicohp_dict[key] = value
1✔
1237
                        else:
1238
                            newicohp_dict[key] = value
1✔
1239

1240
        return newicohp_dict
1✔
1241

1242
    def extremum_icohpvalue(self, summed_spin_channels=True, spin=Spin.up):
1✔
1243
        """
1244
        get ICOHP/ICOOP of strongest bond
1245
        Args:
1246
            summed_spin_channels: Boolean to indicate whether the ICOHPs/ICOOPs of both spin channels should be summed
1247

1248
            spin: if summed_spin_channels is equal to False, this spin indicates which spin channel should be returned
1249
        Returns:
1250
            lowest ICOHP/largest ICOOP value (i.e. ICOHP/ICOOP value of strongest bond)
1251
        """
1252
        if self._are_coops or self._are_cobis:
1✔
1253
            extremum = -sys.float_info.max
1✔
1254

1255
        else:
1256
            extremum = sys.float_info.max
1✔
1257

1258
        if not self._is_spin_polarized:
1✔
1259
            if spin == Spin.down:
1✔
1260
                warnings.warn("This spin channel does not exist. I am switching to Spin.up")
×
1261
            spin = Spin.up
1✔
1262

1263
        for value in self._icohplist.values():
1✔
1264
            if not value.is_spin_polarized or not summed_spin_channels:
1✔
1265
                if not self._are_coops and not self._are_cobis:
1✔
1266
                    if value.icohpvalue(spin) < extremum:
1✔
1267
                        extremum = value.icohpvalue(spin)
1✔
1268
                        # print(extremum)
1269
                else:
1270
                    if value.icohpvalue(spin) > extremum:
1✔
1271
                        extremum = value.icohpvalue(spin)
1✔
1272
                        # print(extremum)
1273
            else:
1274
                if not self._are_coops and not self._are_cobis:
1✔
1275
                    if value.summed_icohp < extremum:
1✔
1276
                        extremum = value.summed_icohp
1✔
1277
                        # print(extremum)
1278
                else:
1279
                    if value.summed_icohp > extremum:
1✔
1280
                        extremum = value.summed_icohp
1✔
1281
                        # print(extremum)
1282
        return extremum
1✔
1283

1284
    @property
1✔
1285
    def is_spin_polarized(self) -> bool:
1✔
1286
        """
1287
        :return: Whether it is spin polarized.
1288
        """
1289
        return self._is_spin_polarized
1✔
1290

1291
    @property
1✔
1292
    def are_coops(self) -> bool:
1✔
1293
        """
1294
        :return: Whether this is a coop.
1295
        """
1296
        return self._are_coops
×
1297

1298
    @property
1✔
1299
    def are_cobis(self) -> bool:
1✔
1300
        """
1301
        :return: Whether this a cobi.
1302
        """
1303
        return self._are_cobis
×
1304

1305

1306
def get_integrated_cohp_in_energy_range(
1✔
1307
    cohp, label, orbital=None, energy_range=None, relative_E_Fermi=True, summed_spin_channels=True
1308
):
1309
    """
1310
    Method that can integrate completecohp objects which include data on integrated COHPs
1311
    Args:
1312
        cohp: CompleteCOHP object
1313
        label: label of the COHP data
1314
        orbital: If not None, a orbital resolved integrated COHP will be returned
1315
        energy_range:   if None, returns icohp value at Fermi level;
1316
                        if float, integrates from this float up to the Fermi level;
1317
                        if [float,float], will integrate in between
1318
        relative_E_Fermi: if True, energy scale with E_Fermi at 0 eV is chosen
1319
        summed_spin_channels: if True, Spin channels will be summed
1320

1321
    Returns:
1322
        float indicating the integrated COHP if summed_spin_channels==True, otherwise dict of the following form {
1323
        Spin.up:float, Spin.down:float}
1324
    """
1325
    summedicohp = {}
1✔
1326
    if orbital is None:
1✔
1327
        icohps = cohp.all_cohps[label].get_icohp(spin=None)
1✔
1328
        if summed_spin_channels and Spin.down in icohps:
1✔
1329
            summedicohp[Spin.up] = icohps[Spin.up] + icohps[Spin.down]
1✔
1330
        else:
1331
            summedicohp = icohps
1✔
1332
    else:
1333
        icohps = cohp.get_orbital_resolved_cohp(label=label, orbitals=orbital).icohp
1✔
1334
        if summed_spin_channels and Spin.down in icohps:
1✔
1335
            summedicohp[Spin.up] = icohps[Spin.up] + icohps[Spin.down]
1✔
1336
        else:
1337
            summedicohp = icohps
1✔
1338

1339
    if energy_range is None:
1✔
1340
        energies_corrected = cohp.energies - cohp.efermi
1✔
1341
        spl_spinup = InterpolatedUnivariateSpline(energies_corrected, summedicohp[Spin.up], ext=0)
1✔
1342

1343
        if not summed_spin_channels and Spin.down in icohps:
1✔
1344
            spl_spindown = InterpolatedUnivariateSpline(energies_corrected, summedicohp[Spin.down], ext=0)
1✔
1345
            return {Spin.up: spl_spinup(0.0), Spin.down: spl_spindown(0.0)}
1✔
1346
        if summed_spin_channels:
1✔
1347
            return spl_spinup(0.0)
1✔
1348

1349
        return {Spin.up: spl_spinup(0.0)}
1✔
1350

1351
    # returns icohp value at the Fermi level!
1352
    if isinstance(energy_range, float):
1✔
1353
        if relative_E_Fermi:
1✔
1354
            energies_corrected = cohp.energies - cohp.efermi
1✔
1355
            spl_spinup = InterpolatedUnivariateSpline(energies_corrected, summedicohp[Spin.up], ext=0)
1✔
1356

1357
            if not summed_spin_channels and Spin.down in icohps:
1✔
1358
                spl_spindown = InterpolatedUnivariateSpline(energies_corrected, summedicohp[Spin.down], ext=0)
1✔
1359
                return {
1✔
1360
                    Spin.up: spl_spinup(0) - spl_spinup(energy_range),
1361
                    Spin.down: spl_spindown(0) - spl_spindown(energy_range),
1362
                }
1363
            if summed_spin_channels:
1✔
1364
                return spl_spinup(0) - spl_spinup(energy_range)
1✔
1365
            return {Spin.up: spl_spinup(0) - spl_spinup(energy_range)}
1✔
1366

1367
        energies_corrected = cohp.energies
1✔
1368
        spl_spinup = InterpolatedUnivariateSpline(energies_corrected, summedicohp[Spin.up], ext=0)
1✔
1369

1370
        if not summed_spin_channels and Spin.down in icohps:
1✔
1371
            spl_spindown = InterpolatedUnivariateSpline(energies_corrected, summedicohp[Spin.down], ext=0)
1✔
1372
            return {
1✔
1373
                Spin.up: spl_spinup(cohp.efermi) - spl_spinup(energy_range),
1374
                Spin.down: spl_spindown(cohp.efermi) - spl_spindown(energy_range),
1375
            }
1376
        if summed_spin_channels:
1✔
1377
            return spl_spinup(cohp.efermi) - spl_spinup(energy_range)
1✔
1378
        return {Spin.up: spl_spinup(cohp.efermi) - spl_spinup(energy_range)}
1✔
1379

1380
    if relative_E_Fermi:
1✔
1381
        energies_corrected = cohp.energies - cohp.efermi
1✔
1382
    else:
1383
        energies_corrected = cohp.energies
1✔
1384

1385
    spl_spinup = InterpolatedUnivariateSpline(energies_corrected, summedicohp[Spin.up], ext=0)
1✔
1386

1387
    if not summed_spin_channels and Spin.down in icohps:
1✔
1388
        spl_spindown = InterpolatedUnivariateSpline(energies_corrected, summedicohp[Spin.down], ext=0)
1✔
1389
        return {
1✔
1390
            Spin.up: spl_spinup(energy_range[1]) - spl_spinup(energy_range[0]),
1391
            Spin.down: spl_spindown(energy_range[1]) - spl_spindown(energy_range[0]),
1392
        }
1393
    if summed_spin_channels:
1✔
1394
        return spl_spinup(energy_range[1]) - spl_spinup(energy_range[0])
1✔
1395

1396
    return {Spin.up: spl_spinup(energy_range[1]) - spl_spinup(energy_range[0])}
1✔
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