1# coding: utf-8
2# Copyright (c) Pymatgen Development Team.
3# Distributed under the terms of the MIT License.
4
5import warnings
6
7"""
8Created on Mar 19, 2012
9"""
10
11
12__author__ = "Shyue Ping Ong, Stephen Dacek"
13__copyright__ = "Copyright 2012, The Materials Project"
14__version__ = "0.1"
15__maintainer__ = "Shyue Ping Ong"
16__email__ = "shyuep@gmail.com"
17__date__ = "Mar 19, 2012"
18
19import os
20import unittest
21from collections import defaultdict
22from math import sqrt
23from pathlib import Path
24
25import pytest
26from monty.json import MontyDecoder
27
28from pymatgen.core.composition import Composition
29from pymatgen.core.periodic_table import Element
30from pymatgen.core.lattice import Lattice
31from pymatgen.core.structure import Structure
32from pymatgen.entries.compatibility import (
33    MU_H2O,
34    AqueousCorrection,
35    Compatibility,
36    CompatibilityError,
37    MaterialsProject2020Compatibility,
38    MaterialsProjectAqueousCompatibility,
39    MaterialsProjectCompatibility,
40    MITAqueousCompatibility,
41    MITCompatibility,
42)
43from pymatgen.entries.computed_entries import (
44    ComputedEntry,
45    ComputedStructureEntry,
46    ConstantEnergyAdjustment,
47)
48from pymatgen.util.testing import PymatgenTest
49
50
51# abstract Compatibility tests
52class DummyCompatibility(Compatibility):
53    """
54    Dummy class to test abstract Compatibility interface
55    """
56
57    def get_adjustments(self, entry):
58        return [ConstantEnergyAdjustment(-10, name="Dummy adjustment")]
59
60
61def test_process_entries_return_type():
62    """
63    process_entries should accept single entries or a list, and always return a list
64    """
65    entry = ComputedEntry("Fe2O3", -2)
66    compat = DummyCompatibility()
67
68    assert isinstance(compat.process_entries(entry), list)
69    assert isinstance(compat.process_entries([entry]), list)
70
71
72def test_no_duplicate_corrections():
73    """
74    Compatibility should never apply the same correction twice
75    """
76    entry = ComputedEntry("Fe2O3", -2)
77    compat = DummyCompatibility()
78
79    assert entry.correction == 0
80    compat.process_entries(entry)
81    assert entry.correction == -10
82    compat.process_entries(entry)
83    assert entry.correction == -10
84    compat.process_entries(entry, clean=True)
85    assert entry.correction == -10
86
87
88def test_clean_arg():
89    """
90    clean=False should preserve existing corrections, clean=True should delete
91    them before processing
92    """
93    entry = ComputedEntry("Fe2O3", -2, correction=-4)
94    compat = DummyCompatibility()
95
96    assert entry.correction == -4
97    compat.process_entries(entry, clean=False)
98    assert entry.correction == -14
99    compat.process_entries(entry)
100    assert entry.correction == -10
101
102
103def test_energy_adjustment_normalize():
104    """
105    Both manual and automatically generated energy adjustments should be scaled
106    by the normalize method
107    """
108    entry = ComputedEntry("Fe4O6", -2, correction=-4)
109    entry = entry.normalize()
110    for ea in entry.energy_adjustments:
111        if "Manual" in ea.name:
112            assert ea.value == -2
113
114    compat = DummyCompatibility()
115    entry = ComputedEntry("Fe4O6", -2, correction=-4)
116    entry = compat.process_entries(entry)[0]
117    entry = entry.normalize()
118    for ea in entry.energy_adjustments:
119        if "Dummy" in ea.name:
120            assert ea.value == -5
121
122
123def test_overlapping_adjustments():
124    """
125    Compatibility should raise a CompatibilityError if there is already a
126    correction with the same name, but a different value, and process_entries
127    should skip that entry.
128    """
129    ea = ConstantEnergyAdjustment(-5, name="Dummy adjustment")
130    entry = ComputedEntry("Fe2O3", -2, energy_adjustments=[ea])
131    compat = DummyCompatibility()
132
133    assert entry.correction == -5
134
135    # in case of a collision between EnergyAdjustment, check for a UserWarning
136    with pytest.warns(UserWarning, match="already has an energy adjustment called Dummy"):
137        processed = compat.process_entries(entry, clean=False)
138
139    assert len(processed) == 0
140
141
142class MaterialsProjectCompatibilityTest(unittest.TestCase):
143    def setUp(self):
144        warnings.simplefilter("ignore")
145        self.entry1 = ComputedEntry(
146            "Fe2O3",
147            -1,
148            correction=0.0,
149            parameters={
150                "is_hubbard": True,
151                "hubbards": {"Fe": 5.3, "O": 0},
152                "run_type": "GGA+U",
153                "potcar_spec": [
154                    {
155                        "titel": "PAW_PBE Fe_pv 06Sep2000",
156                        "hash": "994537de5c4122b7f1b77fb604476db4",
157                    },
158                    {
159                        "titel": "PAW_PBE O 08Apr2002",
160                        "hash": "7a25bc5b9a5393f46600a4939d357982",
161                    },
162                ],
163            },
164        )
165
166        self.entry_sulfide = ComputedEntry(
167            "FeS",
168            -1,
169            correction=0.0,
170            parameters={
171                "is_hubbard": False,
172                "run_type": "GGA",
173                "potcar_spec": [
174                    {
175                        "titel": "PAW_PBE Fe_pv 06Sep2000",
176                        "hash": "994537de5c4122b7f1b77fb604476db4",
177                    },
178                    {
179                        "titel": "PAW_PBE S 08Apr2002",
180                        "hash": "7a25bc5b9a5393f46600a4939d357982",
181                    },
182                ],
183            },
184        )
185
186        self.entry4 = ComputedEntry(
187            "H8",
188            -27.1,
189            correction=0.0,
190            parameters={
191                "run_type": "LDA",
192                "is_hubbard": False,
193                "pseudo_potential": {
194                    "functional": "PBE",
195                    "labels": ["H"],
196                    "pot_type": "paw",
197                },
198                "hubbards": {},
199                "potcar_symbols": ["PBE H"],
200                "oxide_type": "None",
201            },
202        )
203
204        self.entry2 = ComputedEntry(
205            "Fe3O4",
206            -2,
207            correction=0.0,
208            parameters={
209                "is_hubbard": True,
210                "hubbards": {"Fe": 5.3, "O": 0},
211                "run_type": "GGA+U",
212                "potcar_spec": [
213                    {
214                        "titel": "PAW_PBE Fe_pv 06Sep2000",
215                        "hash": "994537de5c4122b7f1b77fb604476db4",
216                    },
217                    {
218                        "titel": "PAW_PBE O 08Apr2002",
219                        "hash": "7a25bc5b9a5393f46600a4939d357982",
220                    },
221                ],
222            },
223        )
224        self.entry3 = ComputedEntry(
225            "FeO",
226            -2,
227            correction=0.0,
228            parameters={
229                "is_hubbard": True,
230                "hubbards": {"Fe": 4.3, "O": 0},
231                "run_type": "GGA+U",
232                "potcar_spec": [
233                    {
234                        "titel": "PAW_PBE Fe_pv 06Sep2000",
235                        "hash": "994537de5c4122b7f1b77fb604476db4",
236                    },
237                    {
238                        "titel": "PAW_PBE O 08Apr2002",
239                        "hash": "7a25bc5b9a5393f46600a4939d357982",
240                    },
241                ],
242            },
243        )
244
245        self.compat = MaterialsProjectCompatibility(check_potcar_hash=False)
246        self.ggacompat = MaterialsProjectCompatibility("GGA", check_potcar_hash=False)
247
248    def tearDown(self):
249        warnings.simplefilter("default")
250
251    def test_process_entry(self):
252        # Correct parameters
253        self.assertIsNotNone(self.compat.process_entry(self.entry1))
254        self.assertIsNone(self.ggacompat.process_entry(self.entry1))
255
256        # Correct parameters
257        entry = ComputedEntry(
258            "Fe2O3",
259            -1,
260            correction=0.0,
261            parameters={
262                "is_hubbard": False,
263                "hubbards": {},
264                "run_type": "GGA",
265                "potcar_spec": [
266                    {
267                        "titel": "PAW_PBE Fe_pv 06Sep2000",
268                        "hash": "994537de5c4122b7f1b77fb604476db4",
269                    },
270                    {
271                        "titel": "PAW_PBE O 08Apr2002",
272                        "hash": "7a25bc5b9a5393f46600a4939d357982",
273                    },
274                ],
275            },
276        )
277        self.assertIsNone(self.compat.process_entry(entry))
278        self.assertIsNotNone(self.ggacompat.process_entry(entry))
279
280        entry = ComputedEntry(
281            "Fe2O3",
282            -1,
283            correction=0.0,
284            parameters={
285                "is_hubbard": True,
286                "hubbards": {"Fe": 5.3, "O": 0},
287                "run_type": "GGA+U",
288                "potcar_spec": [
289                    {
290                        "titel": "PAW_PBE Fe_pv 06Sep2000",
291                        "hash": "994537de5c4122b7f1b77fb604476db4",
292                    },
293                    {
294                        "titel": "PAW_PBE O 08Apr2002",
295                        "hash": "7a25bc5b9a5393f46600a4939d357982",
296                    },
297                ],
298            },
299        )
300        self.assertIsNotNone(self.compat.process_entry(entry))
301
302    def test_correction_values(self):
303        # test_corrections
304        self.assertAlmostEqual(self.compat.process_entry(self.entry1).correction, -2.733 * 2 - 0.70229 * 3)
305
306        entry = ComputedEntry(
307            "FeF3",
308            -2,
309            correction=0.0,
310            parameters={
311                "is_hubbard": True,
312                "hubbards": {"Fe": 5.3, "F": 0},
313                "run_type": "GGA+U",
314                "potcar_spec": [
315                    {
316                        "titel": "PAW_PBE Fe_pv 06Sep2000",
317                        "hash": "994537de5c4122b7f1b77fb604476db4",
318                    },
319                    {
320                        "titel": "PAW_PBE F 08Apr2002",
321                        "hash": "180141c33d032bfbfff30b3bea9d23dd",
322                    },
323                ],
324            },
325        )
326        self.assertIsNotNone(self.compat.process_entry(entry))
327
328        # Check actual correction
329        self.assertAlmostEqual(self.compat.process_entry(entry).correction, -2.733)
330
331        self.assertAlmostEqual(self.compat.process_entry(self.entry_sulfide).correction, -0.66346)
332
333    def test_U_values(self):
334        # Wrong U value
335        entry = ComputedEntry(
336            "Fe2O3",
337            -1,
338            correction=0.0,
339            parameters={
340                "is_hubbard": True,
341                "hubbards": {"Fe": 5.2, "O": 0},
342                "run_type": "GGA+U",
343                "potcar_spec": [
344                    {
345                        "titel": "PAW_PBE Fe_pv 06Sep2000",
346                        "hash": "994537de5c4122b7f1b77fb604476db4",
347                    },
348                    {
349                        "titel": "PAW_PBE O 08Apr2002",
350                        "hash": "7a25bc5b9a5393f46600a4939d357982",
351                    },
352                ],
353            },
354        )
355        self.assertIsNone(self.compat.process_entry(entry))
356
357        # GGA run of U
358        entry = ComputedEntry(
359            "Fe2O3",
360            -1,
361            correction=0.0,
362            parameters={
363                "is_hubbard": False,
364                "hubbards": None,
365                "run_type": "GGA",
366                "potcar_spec": [
367                    {
368                        "titel": "PAW_PBE Fe_pv 06Sep2000",
369                        "hash": "994537de5c4122b7f1b77fb604476db4",
370                    },
371                    {
372                        "titel": "PAW_PBE O 08Apr2002",
373                        "hash": "7a25bc5b9a5393f46600a4939d357982",
374                    },
375                ],
376            },
377        )
378        self.assertIsNone(self.compat.process_entry(entry))
379
380        # GGA+U run of non-U
381        entry = ComputedEntry(
382            "Al2O3",
383            -1,
384            correction=0.0,
385            parameters={
386                "is_hubbard": True,
387                "hubbards": {"Al": 5.3, "O": 0},
388                "run_type": "GGA+U",
389                "potcar_spec": [
390                    {
391                        "titel": "PAW_PBE Al 06Sep2000",
392                        "hash": "805c888bbd2793e462311f6a20d873d9",
393                    },
394                    {
395                        "titel": "PAW_PBE O 08Apr2002",
396                        "hash": "7a25bc5b9a5393f46600a4939d357982",
397                    },
398                ],
399            },
400        )
401        self.assertIsNone(self.compat.process_entry(entry))
402
403        # Materials project should not have a U for sulfides
404        entry = ComputedEntry(
405            "FeS2",
406            -2,
407            correction=0.0,
408            parameters={
409                "is_hubbard": True,
410                "hubbards": {"Fe": 5.3, "S": 0},
411                "run_type": "GGA+U",
412                "potcar_spec": [
413                    {
414                        "titel": "PAW_PBE Fe_pv 06Sep2000",
415                        "hash": "994537de5c4122b7f1b77fb604476db4",
416                    },
417                    {
418                        "titel": "PAW_PBE S 08Apr2002",
419                        "hash": "f7f8e4a74a6cbb8d63e41f4373b54df2",
420                    },
421                ],
422            },
423        )
424        self.assertIsNone(self.compat.process_entry(entry))
425
426    def test_wrong_psp(self):
427        # Wrong psp
428        entry = ComputedEntry(
429            "Fe2O3",
430            -1,
431            correction=0.0,
432            parameters={
433                "is_hubbard": True,
434                "hubbards": {"Fe": 5.3, "O": 0},
435                "run_type": "GGA+U",
436                "potcar_spec": [
437                    {
438                        "titel": "PAW_PBE Fe 06Sep2000",
439                        "hash": "9530da8244e4dac17580869b4adab115",
440                    },
441                    {
442                        "titel": "PAW_PBE O 08Apr2002",
443                        "hash": "7a25bc5b9a5393f46600a4939d357982",
444                    },
445                ],
446            },
447        )
448        self.assertIsNone(self.compat.process_entry(entry))
449
450    def test_element_processing(self):
451        entry = ComputedEntry(
452            "O",
453            -1,
454            correction=0.0,
455            parameters={
456                "is_hubbard": False,
457                "hubbards": {},
458                "potcar_spec": [
459                    {
460                        "titel": "PAW_PBE O 08Apr2002",
461                        "hash": "7a25bc5b9a5393f46600a4939d357982",
462                    }
463                ],
464                "run_type": "GGA",
465            },
466        )
467        entry = self.compat.process_entry(entry)
468        #        self.assertEqual(entry.entry_id, -8)
469        self.assertAlmostEqual(entry.energy, -1)
470        self.assertAlmostEqual(self.ggacompat.process_entry(entry).energy, -1)
471
472    def test_get_explanation_dict(self):
473        compat = MaterialsProjectCompatibility(check_potcar_hash=False)
474        entry = ComputedEntry(
475            "Fe2O3",
476            -1,
477            correction=0.0,
478            parameters={
479                "is_hubbard": True,
480                "hubbards": {"Fe": 5.3, "O": 0},
481                "run_type": "GGA+U",
482                "potcar_spec": [
483                    {
484                        "titel": "PAW_PBE Fe_pv 06Sep2000",
485                        "hash": "994537de5c4122b7f1b77fb604476db4",
486                    },
487                    {
488                        "titel": "PAW_PBE O 08Apr2002",
489                        "hash": "7a25bc5b9a5393f46600a4939d357982",
490                    },
491                ],
492            },
493        )
494        d = compat.get_explanation_dict(entry)
495        self.assertEqual("MPRelaxSet Potcar Correction", d["corrections"][0]["name"])
496
497    def test_get_corrections_dict(self):
498        compat = MaterialsProjectCompatibility(check_potcar_hash=False)
499        ggacompat = MaterialsProjectCompatibility("GGA", check_potcar_hash=False)
500
501        # Correct parameters
502        entry = ComputedEntry(
503            "Fe2O3",
504            -1,
505            correction=0.0,
506            parameters={
507                "is_hubbard": True,
508                "hubbards": {"Fe": 5.3, "O": 0},
509                "run_type": "GGA+U",
510                "potcar_spec": [
511                    {
512                        "titel": "PAW_PBE Fe_pv 06Sep2000",
513                        "hash": "994537de5c4122b7f1b77fb604476db4",
514                    },
515                    {
516                        "titel": "PAW_PBE O 08Apr2002",
517                        "hash": "7a25bc5b9a5393f46600a4939d357982",
518                    },
519                ],
520            },
521        )
522        c = compat.get_corrections_dict(entry)[0]
523        self.assertAlmostEqual(c["MP Anion Correction"], -2.10687)
524        self.assertAlmostEqual(c["MP Advanced Correction"], -5.466)
525
526        entry.parameters["is_hubbard"] = False
527        del entry.parameters["hubbards"]
528        c = ggacompat.get_corrections_dict(entry)[0]
529        self.assertNotIn("MP Advanced Correction", c)
530
531    def test_process_entries(self):
532        entries = self.compat.process_entries([self.entry1, self.entry2, self.entry3, self.entry4])
533        self.assertEqual(len(entries), 2)
534
535    def test_msonable(self):
536        compat_dict = self.compat.as_dict()
537        decoder = MontyDecoder()
538        temp_compat = decoder.process_decoded(compat_dict)
539        self.assertIsInstance(temp_compat, MaterialsProjectCompatibility)
540
541
542class MaterialsProject2020CompatibilityTest(unittest.TestCase):
543    def setUp(self):
544        warnings.simplefilter("ignore")
545        self.entry1 = ComputedEntry(
546            "Fe2O3",
547            -1,
548            correction=0.0,
549            parameters={
550                "is_hubbard": True,
551                "hubbards": {"Fe": 5.3, "O": 0},
552                "run_type": "GGA+U",
553                "potcar_spec": [
554                    {
555                        "titel": "PAW_PBE Fe_pv 06Sep2000",
556                        "hash": "994537de5c4122b7f1b77fb604476db4",
557                    },
558                    {
559                        "titel": "PAW_PBE O 08Apr2002",
560                        "hash": "7a25bc5b9a5393f46600a4939d357982",
561                    },
562                ],
563            },
564        )
565
566        self.entry_sulfide = ComputedEntry(
567            "FeS",
568            -1,
569            correction=0.0,
570            parameters={
571                "is_hubbard": False,
572                "run_type": "GGA",
573                "potcar_spec": [
574                    {
575                        "titel": "PAW_PBE Fe_pv 06Sep2000",
576                        "hash": "994537de5c4122b7f1b77fb604476db4",
577                    },
578                    {
579                        "titel": "PAW_PBE S 08Apr2002",
580                        "hash": "7a25bc5b9a5393f46600a4939d357982",
581                    },
582                ],
583            },
584        )
585
586        self.entry2 = ComputedEntry(
587            "Fe3O4",
588            -2,
589            correction=0.0,
590            parameters={
591                "is_hubbard": True,
592                "hubbards": {"Fe": 5.3, "O": 0},
593                "run_type": "GGA+U",
594                "potcar_spec": [
595                    {
596                        "titel": "PAW_PBE Fe_pv 06Sep2000",
597                        "hash": "994537de5c4122b7f1b77fb604476db4",
598                    },
599                    {
600                        "titel": "PAW_PBE O 08Apr2002",
601                        "hash": "7a25bc5b9a5393f46600a4939d357982",
602                    },
603                ],
604            },
605        )
606        self.entry3 = ComputedEntry(
607            "FeO",
608            -2,
609            correction=0.0,
610            parameters={
611                "is_hubbard": True,
612                "hubbards": {"Fe": 4.3, "O": 0},
613                "run_type": "GGA+U",
614                "potcar_spec": [
615                    {
616                        "titel": "PAW_PBE Fe_pv 06Sep2000",
617                        "hash": "994537de5c4122b7f1b77fb604476db4",
618                    },
619                    {
620                        "titel": "PAW_PBE O 08Apr2002",
621                        "hash": "7a25bc5b9a5393f46600a4939d357982",
622                    },
623                ],
624            },
625        )
626
627        self.compat = MaterialsProject2020Compatibility(check_potcar_hash=False)
628        self.ggacompat = MaterialsProject2020Compatibility("GGA", check_potcar_hash=False)
629
630    def tearDown(self):
631        warnings.simplefilter("default")
632
633    def test_process_entry(self):
634        # Correct parameters
635        self.assertIsNotNone(self.compat.process_entry(self.entry1))
636        self.assertIsNone(self.ggacompat.process_entry(self.entry1))
637
638        # Correct parameters
639        entry = ComputedEntry(
640            "Fe2O3",
641            -1,
642            correction=0.0,
643            parameters={
644                "is_hubbard": False,
645                "hubbards": {},
646                "run_type": "GGA",
647                "potcar_spec": [
648                    {
649                        "titel": "PAW_PBE Fe_pv 06Sep2000",
650                        "hash": "994537de5c4122b7f1b77fb604476db4",
651                    },
652                    {
653                        "titel": "PAW_PBE O 08Apr2002",
654                        "hash": "7a25bc5b9a5393f46600a4939d357982",
655                    },
656                ],
657            },
658        )
659        self.assertIsNone(self.compat.process_entry(entry))
660        self.assertIsNotNone(self.ggacompat.process_entry(entry))
661
662        entry = ComputedEntry(
663            "Fe2O3",
664            -1,
665            correction=0.0,
666            parameters={
667                "is_hubbard": True,
668                "hubbards": {"Fe": 5.3, "O": 0},
669                "run_type": "GGA+U",
670                "potcar_spec": [
671                    {
672                        "titel": "PAW_PBE Fe_pv 06Sep2000",
673                        "hash": "994537de5c4122b7f1b77fb604476db4",
674                    },
675                    {
676                        "titel": "PAW_PBE O 08Apr2002",
677                        "hash": "7a25bc5b9a5393f46600a4939d357982",
678                    },
679                ],
680            },
681        )
682        self.assertIsNotNone(self.compat.process_entry(entry))
683
684    def test_oxi_state_guess(self):
685        # An entry where Composition.oxi_state_guesses will return an empty list
686        entry_blank = ComputedEntry(
687            "Ga3Te",
688            -12.1900,
689            correction=0.0,
690            parameters={
691                "run_type": "GGA",
692                "is_hubbard": False,
693                "pseudo_potential": {"functional": "PBE", "labels": ["Ga_d", "Te"], "pot_type": "paw"},
694                "hubbards": {},
695                "potcar_symbols": ["PBE Ga_d", "PBE Te"],
696                "oxide_type": "None",
697            },
698        )
699
700        # An entry where one anion will only be corrected if oxidation_states is populated
701        entry_oxi = ComputedEntry(
702            "Mo2Cl8O",
703            -173.0655,
704            correction=0.0,
705            parameters={
706                "run_type": "GGA+U",
707                "is_hubbard": True,
708                "pseudo_potential": {"functional": "PBE", "labels": ["Mo_pv", "Cl", "O"], "pot_type": "paw"},
709                "hubbards": {"Mo": 4.38, "Cl": 0.0, "O": 0.0},
710                "potcar_symbols": ["PBE Mo_pv", "PBE Cl", "PBE O"],
711                "oxide_type": "oxide",
712            },
713        )
714
715        # An entry that should receive multiple anion corrections if oxidation
716        # states are populated
717        entry_multi_anion = ComputedEntry(
718            "C8N4Cl4",
719            -87.69656726,
720            correction=0.0,
721            parameters={
722                "run_type": "GGA",
723                "is_hubbard": False,
724                "pseudo_potential": {"functional": "PBE", "labels": ["C", "N", "Cl"], "pot_type": "paw"},
725                "hubbards": {},
726                "potcar_symbols": ["PBE C", "PBE N", "PBE Cl"],
727                "oxide_type": "None",
728            },
729        )
730
731        with pytest.warns(UserWarning, match="Failed to guess oxidation state"):
732            e1 = self.compat.process_entry(entry_blank)
733            self.assertAlmostEqual(e1.correction, -0.422)
734
735        e2 = self.compat.process_entry(entry_oxi)
736        self.assertAlmostEqual(e2.correction, -0.687 + -3.202 * 2 + -0.614 * 8)
737
738        e3 = self.compat.process_entry(entry_multi_anion)
739        self.assertAlmostEqual(e3.correction, -0.361 * 4 + -0.614 * 4)
740
741    def test_correction_values(self):
742        # test_corrections
743        self.assertAlmostEqual(self.compat.process_entry(self.entry1).correction, -2.256 * 2 - 0.687 * 3)
744
745        entry = ComputedEntry(
746            "FeF3",
747            -2,
748            correction=0.0,
749            parameters={
750                "is_hubbard": True,
751                "hubbards": {"Fe": 5.3, "F": 0},
752                "run_type": "GGA+U",
753                "potcar_spec": [
754                    {
755                        "titel": "PAW_PBE Fe_pv 06Sep2000",
756                        "hash": "994537de5c4122b7f1b77fb604476db4",
757                    },
758                    {
759                        "titel": "PAW_PBE F 08Apr2002",
760                        "hash": "180141c33d032bfbfff30b3bea9d23dd",
761                    },
762                ],
763            },
764        )
765        self.assertIsNotNone(self.compat.process_entry(entry))
766
767        # Check actual correction
768        self.assertAlmostEqual(self.compat.process_entry(entry).correction, -0.462 * 3 + -2.256)
769
770        self.assertAlmostEqual(self.compat.process_entry(self.entry_sulfide).correction, -0.503)
771
772    def test_oxdiation_by_electronegativity(self):
773        # make sure anion corrections are only applied when the element has
774        # a negative oxidation state (e.g., correct CaSi but not SiO2 for Si)
775        # as determined by electronegativity (i.e., the data.oxidation_states key is absent)
776
777        entry1 = ComputedEntry.from_dict(
778            {
779                "@module": "pymatgen.entries.computed_entries",
780                "@class": "ComputedEntry",
781                "energy": -17.01015622,
782                "composition": defaultdict(float, {"Si": 2.0, "Ca": 2.0}),
783                "energy_adjustments": [],
784                "parameters": {
785                    "run_type": "GGA",
786                    "is_hubbard": False,
787                    "pseudo_potential": {
788                        "functional": "PBE",
789                        "labels": ["Ca_sv", "Si"],
790                        "pot_type": "paw",
791                    },
792                    "hubbards": {},
793                    "potcar_symbols": ["PBE Ca_sv", "PBE Si"],
794                    "oxide_type": "None",
795                },
796                "data": {"oxide_type": "None"},
797                "entry_id": "mp-1563",
798                "correction": 0.0,
799            }
800        )
801
802        entry2 = ComputedEntry.from_dict(
803            {
804                "@module": "pymatgen.entries.computed_entries",
805                "@class": "ComputedEntry",
806                "energy": -47.49120119,
807                "composition": defaultdict(float, {"Si": 2.0, "O": 4.0}),
808                "energy_adjustments": [],
809                "parameters": {
810                    "run_type": "GGA",
811                    "is_hubbard": False,
812                    "pseudo_potential": {
813                        "functional": "PBE",
814                        "labels": ["Si", "O"],
815                        "pot_type": "paw",
816                    },
817                    "hubbards": {},
818                    "potcar_symbols": ["PBE Si", "PBE O"],
819                    "oxide_type": "oxide",
820                },
821                "data": {"oxide_type": "oxide"},
822                "entry_id": "mp-546794",
823                "correction": 0.0,
824            }
825        )
826
827        # CaSi; only correction should be Si
828        self.assertAlmostEqual(self.compat.process_entry(entry1).correction, 0.071 * 2)
829
830        # SiO2; only corrections should be oxide
831        self.assertAlmostEqual(self.compat.process_entry(entry2).correction, -0.687 * 4)
832
833    def test_oxdiation(self):
834        # make sure anion corrections are only applied when the element has
835        # a negative oxidation state (e.g., correct CaSi but not SiO2 for Si)
836        # as determined by the data.oxidation_states key
837
838        entry1 = ComputedEntry.from_dict(
839            {
840                "@module": "pymatgen.entries.computed_entries",
841                "@class": "ComputedEntry",
842                "energy": -17.01015622,
843                "composition": defaultdict(float, {"Si": 2.0, "Ca": 2.0}),
844                "energy_adjustments": [],
845                "parameters": {
846                    "run_type": "GGA",
847                    "is_hubbard": False,
848                    "pseudo_potential": {
849                        "functional": "PBE",
850                        "labels": ["Ca_sv", "Si"],
851                        "pot_type": "paw",
852                    },
853                    "hubbards": {},
854                    "potcar_symbols": ["PBE Ca_sv", "PBE Si"],
855                    "oxide_type": "None",
856                },
857                "data": {
858                    "oxide_type": "None",
859                    "oxidation_states": {"Ca": 2.0, "Si": -2.0},
860                },
861                "entry_id": "mp-1563",
862                "correction": 0.0,
863            }
864        )
865
866        entry2 = ComputedEntry.from_dict(
867            {
868                "@module": "pymatgen.entries.computed_entries",
869                "@class": "ComputedEntry",
870                "energy": -47.49120119,
871                "composition": defaultdict(float, {"Si": 2.0, "O": 4.0}),
872                "energy_adjustments": [],
873                "parameters": {
874                    "run_type": "GGA",
875                    "is_hubbard": False,
876                    "pseudo_potential": {
877                        "functional": "PBE",
878                        "labels": ["Si", "O"],
879                        "pot_type": "paw",
880                    },
881                    "hubbards": {},
882                    "potcar_symbols": ["PBE Si", "PBE O"],
883                    "oxide_type": "oxide",
884                },
885                "data": {
886                    "oxide_type": "oxide",
887                    "oxidation_states": {"Si": 4.0, "O": -2.0},
888                },
889                "entry_id": "mp-546794",
890                "correction": 0.0,
891            }
892        )
893
894        # CaSi; only correction should be Si
895        self.assertAlmostEqual(self.compat.process_entry(entry1).correction, 0.071 * 2)
896
897        # SiO2; only corrections should be oxide
898        self.assertAlmostEqual(self.compat.process_entry(entry2).correction, -0.687 * 4)
899
900    def test_U_values(self):
901        # Wrong U value
902        entry = ComputedEntry(
903            "Fe2O3",
904            -1,
905            correction=0.0,
906            parameters={
907                "is_hubbard": True,
908                "hubbards": {"Fe": 5.2, "O": 0},
909                "run_type": "GGA+U",
910                "potcar_spec": [
911                    {
912                        "titel": "PAW_PBE Fe_pv 06Sep2000",
913                        "hash": "994537de5c4122b7f1b77fb604476db4",
914                    },
915                    {
916                        "titel": "PAW_PBE O 08Apr2002",
917                        "hash": "7a25bc5b9a5393f46600a4939d357982",
918                    },
919                ],
920            },
921        )
922        self.assertIsNone(self.compat.process_entry(entry))
923
924        # GGA run of U
925        entry = ComputedEntry(
926            "Fe2O3",
927            -1,
928            correction=0.0,
929            parameters={
930                "is_hubbard": False,
931                "hubbards": None,
932                "run_type": "GGA",
933                "potcar_spec": [
934                    {
935                        "titel": "PAW_PBE Fe_pv 06Sep2000",
936                        "hash": "994537de5c4122b7f1b77fb604476db4",
937                    },
938                    {
939                        "titel": "PAW_PBE O 08Apr2002",
940                        "hash": "7a25bc5b9a5393f46600a4939d357982",
941                    },
942                ],
943            },
944        )
945        self.assertIsNone(self.compat.process_entry(entry))
946
947        # GGA+U run of non-U
948        entry = ComputedEntry(
949            "Al2O3",
950            -1,
951            correction=0.0,
952            parameters={
953                "is_hubbard": True,
954                "hubbards": {"Al": 5.3, "O": 0},
955                "run_type": "GGA+U",
956                "potcar_spec": [
957                    {
958                        "titel": "PAW_PBE Al 06Sep2000",
959                        "hash": "805c888bbd2793e462311f6a20d873d9",
960                    },
961                    {
962                        "titel": "PAW_PBE O 08Apr2002",
963                        "hash": "7a25bc5b9a5393f46600a4939d357982",
964                    },
965                ],
966            },
967        )
968        self.assertIsNone(self.compat.process_entry(entry))
969
970        # Materials project should not have a U for sulfides
971        entry = ComputedEntry(
972            "FeS2",
973            -2,
974            correction=0.0,
975            parameters={
976                "is_hubbard": True,
977                "hubbards": {"Fe": 5.3, "S": 0},
978                "run_type": "GGA+U",
979                "potcar_spec": [
980                    {
981                        "titel": "PAW_PBE Fe_pv 06Sep2000",
982                        "hash": "994537de5c4122b7f1b77fb604476db4",
983                    },
984                    {
985                        "titel": "PAW_PBE S 08Apr2002",
986                        "hash": "f7f8e4a74a6cbb8d63e41f4373b54df2",
987                    },
988                ],
989            },
990        )
991        self.assertIsNone(self.compat.process_entry(entry))
992
993    def test_wrong_psp(self):
994        # Wrong psp
995        entry = ComputedEntry(
996            "Fe2O3",
997            -1,
998            correction=0.0,
999            parameters={
1000                "is_hubbard": True,
1001                "hubbards": {"Fe": 5.3, "O": 0},
1002                "run_type": "GGA+U",
1003                "potcar_spec": [
1004                    {
1005                        "titel": "PAW_PBE Fe 06Sep2000",
1006                        "hash": "9530da8244e4dac17580869b4adab115",
1007                    },
1008                    {
1009                        "titel": "PAW_PBE O 08Apr2002",
1010                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1011                    },
1012                ],
1013            },
1014        )
1015        self.assertIsNone(self.compat.process_entry(entry))
1016
1017    def test_element_processing(self):
1018        entry = ComputedEntry(
1019            "O",
1020            -1,
1021            correction=0.0,
1022            parameters={
1023                "is_hubbard": False,
1024                "hubbards": {},
1025                "potcar_spec": [
1026                    {
1027                        "titel": "PAW_PBE O 08Apr2002",
1028                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1029                    }
1030                ],
1031                "run_type": "GGA",
1032            },
1033        )
1034        entry = self.compat.process_entry(entry)
1035        self.assertAlmostEqual(entry.energy, -1)
1036        self.assertAlmostEqual(self.ggacompat.process_entry(entry).energy, -1)
1037
1038    def test_get_explanation_dict(self):
1039        compat = MaterialsProjectCompatibility(check_potcar_hash=False)
1040        entry = ComputedEntry(
1041            "Fe2O3",
1042            -1,
1043            correction=0.0,
1044            parameters={
1045                "is_hubbard": True,
1046                "hubbards": {"Fe": 5.3, "O": 0},
1047                "run_type": "GGA+U",
1048                "potcar_spec": [
1049                    {
1050                        "titel": "PAW_PBE Fe_pv 06Sep2000",
1051                        "hash": "994537de5c4122b7f1b77fb604476db4",
1052                    },
1053                    {
1054                        "titel": "PAW_PBE O 08Apr2002",
1055                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1056                    },
1057                ],
1058            },
1059        )
1060        d = compat.get_explanation_dict(entry)
1061        self.assertEqual("MPRelaxSet Potcar Correction", d["corrections"][0]["name"])
1062
1063    def test_energy_adjustments(self):
1064        compat = MaterialsProject2020Compatibility(check_potcar_hash=False)
1065        ggacompat = MaterialsProject2020Compatibility("GGA", check_potcar_hash=False)
1066
1067        # Fe 4 Co 2 O 8 (Fe2CoO4)
1068        entry = {
1069            "@module": "pymatgen.entries.computed_entries",
1070            "@class": "ComputedEntry",
1071            "energy": -91.94962744,
1072            "composition": defaultdict(float, {"Fe": 4.0, "Co": 2.0, "O": 8.0}),
1073            "energy_adjustments": [],
1074            "parameters": {
1075                "run_type": "GGA+U",
1076                "is_hubbard": True,
1077                "pseudo_potential": {
1078                    "functional": "PBE",
1079                    "labels": ["Fe_pv", "Co", "O"],
1080                    "pot_type": "paw",
1081                },
1082                "hubbards": {"Fe": 5.3, "Co": 3.32, "O": 0.0},
1083                "potcar_symbols": ["PBE Fe_pv", "PBE Co", "PBE O"],
1084                "oxide_type": "oxide",
1085            },
1086            "data": {"oxide_type": "oxide"},
1087            "entry_id": "mp-753222",
1088            "correction": 0,
1089        }
1090        entry = ComputedEntry.from_dict(entry)
1091
1092        c = compat.process_entry(entry)
1093        assert "MP2020 anion correction (oxide)" in [ea.name for ea in c.energy_adjustments]
1094        assert "MP2020 GGA/GGA+U mixing correction (Fe)" in [ea.name for ea in c.energy_adjustments]
1095        assert "MP2020 GGA/GGA+U mixing correction (Co)" in [ea.name for ea in c.energy_adjustments]
1096
1097        for ea in c.energy_adjustments:
1098            if ea.name == "MP2020 GGA/GGA+U mixing correction (Fe)":
1099                self.assertAlmostEqual(ea.value, -2.256 * 4)
1100                self.assertAlmostEqual(ea.uncertainty, 0.0101 * 4)
1101            elif ea.name == "MP2020 GGA/GGA+U mixing correction (Co)":
1102                self.assertAlmostEqual(ea.value, -1.638 * 2)
1103                self.assertAlmostEqual(ea.uncertainty, 0.006 * 2)
1104            elif ea.name == "MP2020 anion correction (oxide)":
1105                self.assertAlmostEqual(ea.value, -0.687 * 8)
1106                self.assertAlmostEqual(ea.uncertainty, 0.002 * 8)
1107
1108        entry.parameters["is_hubbard"] = False
1109        del entry.parameters["hubbards"]
1110        c = ggacompat.process_entry(entry)
1111        self.assertNotIn(
1112            "MP2020 GGA/GGA+U mixing correction",
1113            [ea.name for ea in c.energy_adjustments],
1114        )
1115
1116    def test_process_entries(self):
1117        entries = self.compat.process_entries([self.entry1, self.entry2, self.entry3])
1118        self.assertEqual(len(entries), 2)
1119
1120    def test_config_file(self):
1121        config_file = Path(PymatgenTest.TEST_FILES_DIR / "MP2020Compatibility_alternate.yaml")
1122        compat = MaterialsProject2020Compatibility(config_file=config_file)
1123        entry = compat.process_entry(self.entry1)
1124        for ea in entry.energy_adjustments:
1125            if ea.name == "MP2020 GGA/GGA+U mixing correction (Fe)":
1126                self.assertAlmostEqual(ea.value, -0.224 * 2)
1127
1128    def test_msonable(self):
1129        compat_dict = self.compat.as_dict()
1130        decoder = MontyDecoder()
1131        temp_compat = decoder.process_decoded(compat_dict)
1132        self.assertIsInstance(temp_compat, MaterialsProject2020Compatibility)
1133
1134
1135class MITCompatibilityTest(unittest.TestCase):
1136    def tearDown(self):
1137        warnings.simplefilter("default")
1138
1139    def setUp(self):
1140        warnings.simplefilter("ignore")
1141        self.compat = MITCompatibility(check_potcar_hash=True)
1142        self.ggacompat = MITCompatibility("GGA", check_potcar_hash=True)
1143        self.entry_O = ComputedEntry(
1144            "Fe2O3",
1145            -1,
1146            correction=0.0,
1147            parameters={
1148                "is_hubbard": True,
1149                "hubbards": {"Fe": 4.0, "O": 0},
1150                "run_type": "GGA+U",
1151                "potcar_spec": [
1152                    {
1153                        "titel": "PAW_PBE Fe 06Sep2000",
1154                        "hash": "9530da8244e4dac17580869b4adab115",
1155                    },
1156                    {
1157                        "titel": "PAW_PBE O 08Apr2002",
1158                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1159                    },
1160                ],
1161            },
1162        )
1163
1164        self.entry_F = ComputedEntry(
1165            "FeF3",
1166            -2,
1167            correction=0.0,
1168            parameters={
1169                "is_hubbard": True,
1170                "hubbards": {"Fe": 4.0, "F": 0},
1171                "run_type": "GGA+U",
1172                "potcar_spec": [
1173                    {
1174                        "titel": "PAW_PBE Fe 06Sep2000",
1175                        "hash": "9530da8244e4dac17580869b4adab115",
1176                    },
1177                    {
1178                        "titel": "PAW_PBE F 08Apr2002",
1179                        "hash": "180141c33d032bfbfff30b3bea9d23dd",
1180                    },
1181                ],
1182            },
1183        )
1184        self.entry_S = ComputedEntry(
1185            "FeS2",
1186            -2,
1187            correction=0.0,
1188            parameters={
1189                "is_hubbard": True,
1190                "hubbards": {"Fe": 1.9, "S": 0},
1191                "run_type": "GGA+U",
1192                "potcar_spec": [
1193                    {
1194                        "titel": "PAW_PBE Fe 06Sep2000",
1195                        "hash": "9530da8244e4dac17580869b4adab115",
1196                    },
1197                    {
1198                        "titel": "PAW_PBE S 08Apr2002",
1199                        "hash": "d368db6899d8839859bbee4811a42a88",
1200                    },
1201                ],
1202            },
1203        )
1204
1205    def test_process_entry(self):
1206        # Correct parameters
1207        self.assertIsNotNone(self.compat.process_entry(self.entry_O))
1208        self.assertIsNotNone(self.compat.process_entry(self.entry_F))
1209
1210    def test_correction_value(self):
1211        # Check actual correction
1212        self.assertAlmostEqual(self.compat.process_entry(self.entry_O).correction, -1.723 * 2 - 0.66975 * 3)
1213        self.assertAlmostEqual(self.compat.process_entry(self.entry_F).correction, -1.723)
1214        self.assertAlmostEqual(self.compat.process_entry(self.entry_S).correction, -1.113)
1215
1216    def test_U_value(self):
1217        # MIT should have a U value for Fe containing sulfides
1218        self.assertIsNotNone(self.compat.process_entry(self.entry_S))
1219
1220        # MIT should not have a U value for Ni containing sulfides
1221        entry = ComputedEntry(
1222            "NiS2",
1223            -2,
1224            correction=0.0,
1225            parameters={
1226                "is_hubbard": True,
1227                "hubbards": {"Ni": 1.9, "S": 0},
1228                "run_type": "GGA+U",
1229                "potcar_spec": [
1230                    {
1231                        "titel": "PAW_PBE Ni 06Sep2000",
1232                        "hash": "653f5772e68b2c7fd87ffd1086c0d710",
1233                    },
1234                    {
1235                        "titel": "PAW_PBE S 08Apr2002",
1236                        "hash": "d368db6899d8839859bbee4811a42a88",
1237                    },
1238                ],
1239            },
1240        )
1241
1242        self.assertIsNone(self.compat.process_entry(entry))
1243
1244        entry = ComputedEntry(
1245            "NiS2",
1246            -2,
1247            correction=0.0,
1248            parameters={
1249                "is_hubbard": True,
1250                "hubbards": None,
1251                "run_type": "GGA",
1252                "potcar_spec": [
1253                    {
1254                        "titel": "PAW_PBE Ni 06Sep2000",
1255                        "hash": "653f5772e68b2c7fd87ffd1086c0d710",
1256                    },
1257                    {
1258                        "titel": "PAW_PBE S 08Apr2002",
1259                        "hash": "d368db6899d8839859bbee4811a42a88",
1260                    },
1261                ],
1262            },
1263        )
1264
1265        self.assertIsNotNone(self.ggacompat.process_entry(entry))
1266
1267    def test_wrong_U_value(self):
1268        # Wrong U value
1269        entry = ComputedEntry(
1270            "Fe2O3",
1271            -1,
1272            correction=0.0,
1273            parameters={
1274                "is_hubbard": True,
1275                "hubbards": {"Fe": 5.2, "O": 0},
1276                "run_type": "GGA+U",
1277                "potcar_spec": [
1278                    {
1279                        "titel": "PAW_PBE Fe 06Sep2000",
1280                        "hash": "9530da8244e4dac17580869b4adab115",
1281                    },
1282                    {
1283                        "titel": "PAW_PBE O 08Apr2002",
1284                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1285                    },
1286                ],
1287            },
1288        )
1289
1290        self.assertIsNone(self.compat.process_entry(entry))
1291
1292        # GGA run
1293        entry = ComputedEntry(
1294            "Fe2O3",
1295            -1,
1296            correction=0.0,
1297            parameters={
1298                "is_hubbard": False,
1299                "hubbards": None,
1300                "run_type": "GGA",
1301                "potcar_spec": [
1302                    {
1303                        "titel": "PAW_PBE Fe 06Sep2000",
1304                        "hash": "9530da8244e4dac17580869b4adab115",
1305                    },
1306                    {
1307                        "titel": "PAW_PBE O 08Apr2002",
1308                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1309                    },
1310                ],
1311            },
1312        )
1313        self.assertIsNone(self.compat.process_entry(entry))
1314        self.assertIsNotNone(self.ggacompat.process_entry(entry))
1315
1316    def test_wrong_psp(self):
1317        # Wrong psp
1318        entry = ComputedEntry(
1319            "Fe2O3",
1320            -1,
1321            correction=0.0,
1322            parameters={
1323                "is_hubbard": True,
1324                "hubbards": {"Fe": 4.0, "O": 0},
1325                "run_type": "GGA+U",
1326                "potcar_spec": [
1327                    {
1328                        "titel": "PAW_PBE Fe_pv 06Sep2000",
1329                        "hash": "994537de5c4122b7f1b77fb604476db4",
1330                    },
1331                    {
1332                        "titel": "PAW_PBE O 08Apr2002",
1333                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1334                    },
1335                ],
1336            },
1337        )
1338        self.assertIsNone(self.compat.process_entry(entry))
1339
1340    def test_element_processing(self):
1341        # Testing processing of elements.
1342        entry = ComputedEntry(
1343            "O",
1344            -1,
1345            correction=0.0,
1346            parameters={
1347                "is_hubbard": False,
1348                "hubbards": {},
1349                "potcar_spec": [
1350                    {
1351                        "titel": "PAW_PBE O 08Apr2002",
1352                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1353                    }
1354                ],
1355                "run_type": "GGA",
1356            },
1357        )
1358        entry = self.compat.process_entry(entry)
1359        self.assertAlmostEqual(entry.energy, -1)
1360
1361    def test_same_potcar_symbol(self):
1362        # Same symbol different hash thus a different potcar
1363        # Correct Hash Correct Symbol
1364        entry = ComputedEntry(
1365            "Fe2O3",
1366            -1,
1367            correction=0.0,
1368            parameters={
1369                "is_hubbard": True,
1370                "hubbards": {"Fe": 4.0, "O": 0},
1371                "run_type": "GGA+U",
1372                "potcar_spec": [
1373                    {
1374                        "titel": "PAW_PBE Fe 06Sep2000",
1375                        "hash": "9530da8244e4dac17580869b4adab115",
1376                    },
1377                    {
1378                        "titel": "PAW_PBE O 08Apr2002",
1379                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1380                    },
1381                ],
1382            },
1383        )
1384        # Incorrect Hash Correct Symbol
1385        entry2 = ComputedEntry(
1386            "Fe2O3",
1387            -1,
1388            correction=0.0,
1389            parameters={
1390                "is_hubbard": True,
1391                "hubbards": {"Fe": 4.0, "O": 0},
1392                "run_type": "GGA+U",
1393                "potcar_spec": [
1394                    {"titel": "PAW_PBE Fe 06Sep2000", "hash": "DifferentHash"},
1395                    {
1396                        "titel": "PAW_PBE O 08Apr2002",
1397                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1398                    },
1399                ],
1400            },
1401        )
1402
1403        compat = MITCompatibility()
1404        self.assertEqual(len(compat.process_entries([entry, entry2])), 2)
1405        self.assertEqual(len(self.compat.process_entries([entry, entry2])), 1)
1406
1407    def test_revert_to_symbols(self):
1408        # Test that you can revert to potcar_symbols if potcar_spec is not present
1409        compat = MITCompatibility()
1410        entry = ComputedEntry(
1411            "Fe2O3",
1412            -1,
1413            correction=0.0,
1414            parameters={
1415                "is_hubbard": True,
1416                "hubbards": {"Fe": 4.0, "O": 0},
1417                "run_type": "GGA+U",
1418                "potcar_symbols": ["PAW_PBE Fe 06Sep2000", "PAW_PBE O 08Apr2002"],
1419            },
1420        )
1421
1422        self.assertIsNotNone(compat.process_entry(entry))
1423        # raise if check_potcar_hash is set
1424        self.assertRaises(ValueError, self.compat.process_entry, entry)
1425
1426    def test_potcar_doenst_match_structure(self):
1427        compat = MITCompatibility()
1428        entry = ComputedEntry(
1429            "Li2O3",
1430            -1,
1431            correction=0.0,
1432            parameters={
1433                "is_hubbard": True,
1434                "hubbards": {"Fe": 4.0, "O": 0},
1435                "run_type": "GGA+U",
1436                "potcar_symbols": ["PAW_PBE Fe_pv 06Sep2000", "PAW_PBE O 08Apr2002"],
1437            },
1438        )
1439
1440        self.assertIsNone(compat.process_entry(entry))
1441
1442    def test_potcar_spec_is_none(self):
1443        compat = MITCompatibility(check_potcar_hash=True)
1444        entry = ComputedEntry(
1445            "Li2O3",
1446            -1,
1447            correction=0.0,
1448            parameters={
1449                "is_hubbard": True,
1450                "hubbards": {"Fe": 4.0, "O": 0},
1451                "run_type": "GGA+U",
1452                "potcar_spec": [None, None],
1453            },
1454        )
1455
1456        self.assertIsNone(compat.process_entry(entry))
1457
1458    def test_get_explanation_dict(self):
1459        compat = MITCompatibility(check_potcar_hash=False)
1460        entry = ComputedEntry(
1461            "Fe2O3",
1462            -1,
1463            correction=0.0,
1464            parameters={
1465                "is_hubbard": True,
1466                "hubbards": {"Fe": 4.0, "O": 0},
1467                "run_type": "GGA+U",
1468                "potcar_spec": [
1469                    {
1470                        "titel": "PAW_PBE Fe 06Sep2000",
1471                        "hash": "994537de5c4122b7f1b77fb604476db4",
1472                    },
1473                    {
1474                        "titel": "PAW_PBE O 08Apr2002",
1475                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1476                    },
1477                ],
1478            },
1479        )
1480        d = compat.get_explanation_dict(entry)
1481        self.assertEqual("MITRelaxSet Potcar Correction", d["corrections"][0]["name"])
1482
1483    def test_msonable(self):
1484        compat_dict = self.compat.as_dict()
1485        decoder = MontyDecoder()
1486        temp_compat = decoder.process_decoded(compat_dict)
1487        self.assertIsInstance(temp_compat, MITCompatibility)
1488
1489
1490class OxideTypeCorrectionTest(unittest.TestCase):
1491    def setUp(self):
1492        self.compat = MITCompatibility(check_potcar_hash=True)
1493
1494    def test_no_struct_compat(self):
1495        lio2_entry_nostruct = ComputedEntry(
1496            Composition("Li2O4"),
1497            -3,
1498            data={"oxide_type": "superoxide"},
1499            parameters={
1500                "is_hubbard": False,
1501                "hubbards": None,
1502                "run_type": "GGA",
1503                "potcar_spec": [
1504                    {
1505                        "titel": "PAW_PBE Li 17Jan2003",
1506                        "hash": "65e83282d1707ec078c1012afbd05be8",
1507                    },
1508                    {
1509                        "titel": "PAW_PBE O 08Apr2002",
1510                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1511                    },
1512                ],
1513            },
1514        )
1515
1516        lio2_entry_corrected = self.compat.process_entry(lio2_entry_nostruct)
1517        self.assertAlmostEqual(lio2_entry_corrected.energy, -3 - 0.13893 * 4, 4)
1518
1519    def test_process_entry_superoxide(self):
1520        el_li = Element("Li")
1521        el_o = Element("O")
1522        latt = Lattice([[3.985034, 0.0, 0.0], [0.0, 4.881506, 0.0], [0.0, 0.0, 2.959824]])
1523        elts = [el_li, el_li, el_o, el_o, el_o, el_o]
1524        coords = []
1525        coords.append([0.500000, 0.500000, 0.500000])
1526        coords.append([0.0, 0.0, 0.0])
1527        coords.append([0.632568, 0.085090, 0.500000])
1528        coords.append([0.367432, 0.914910, 0.500000])
1529        coords.append([0.132568, 0.414910, 0.000000])
1530        coords.append([0.867432, 0.585090, 0.000000])
1531        struct = Structure(latt, elts, coords)
1532        lio2_entry = ComputedStructureEntry(
1533            struct,
1534            -3,
1535            parameters={
1536                "is_hubbard": False,
1537                "hubbards": None,
1538                "run_type": "GGA",
1539                "potcar_spec": [
1540                    {
1541                        "titel": "PAW_PBE Li 17Jan2003",
1542                        "hash": "65e83282d1707ec078c1012afbd05be8",
1543                    },
1544                    {
1545                        "titel": "PAW_PBE O 08Apr2002",
1546                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1547                    },
1548                ],
1549            },
1550        )
1551
1552        lio2_entry_corrected = self.compat.process_entry(lio2_entry)
1553        self.assertAlmostEqual(lio2_entry_corrected.energy, -3 - 0.13893 * 4, 4)
1554
1555    def test_process_entry_peroxide(self):
1556        latt = Lattice.from_parameters(3.159597, 3.159572, 7.685205, 89.999884, 89.999674, 60.000510)
1557        el_li = Element("Li")
1558        el_o = Element("O")
1559        elts = [el_li, el_li, el_li, el_li, el_o, el_o, el_o, el_o]
1560        coords = [
1561            [0.666656, 0.666705, 0.750001],
1562            [0.333342, 0.333378, 0.250001],
1563            [0.000001, 0.000041, 0.500001],
1564            [0.000001, 0.000021, 0.000001],
1565            [0.333347, 0.333332, 0.649191],
1566            [0.333322, 0.333353, 0.850803],
1567            [0.666666, 0.666686, 0.350813],
1568            [0.666665, 0.666684, 0.149189],
1569        ]
1570        struct = Structure(latt, elts, coords)
1571        li2o2_entry = ComputedStructureEntry(
1572            struct,
1573            -3,
1574            parameters={
1575                "is_hubbard": False,
1576                "hubbards": None,
1577                "run_type": "GGA",
1578                "potcar_spec": [
1579                    {
1580                        "titel": "PAW_PBE Li 17Jan2003",
1581                        "hash": "65e83282d1707ec078c1012afbd05be8",
1582                    },
1583                    {
1584                        "titel": "PAW_PBE O 08Apr2002",
1585                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1586                    },
1587                ],
1588            },
1589        )
1590
1591        li2o2_entry_corrected = self.compat.process_entry(li2o2_entry)
1592        self.assertAlmostEqual(li2o2_entry_corrected.energy, -3 - 0.44317 * 4, 4)
1593
1594    def test_process_entry_ozonide(self):
1595        el_li = Element("Li")
1596        el_o = Element("O")
1597        elts = [el_li, el_o, el_o, el_o]
1598        latt = Lattice.from_parameters(3.999911, 3.999911, 3.999911, 133.847504, 102.228244, 95.477342)
1599        coords = [
1600            [0.513004, 0.513004, 1.000000],
1601            [0.017616, 0.017616, 0.000000],
1602            [0.649993, 0.874790, 0.775203],
1603            [0.099587, 0.874790, 0.224797],
1604        ]
1605        struct = Structure(latt, elts, coords)
1606        lio3_entry = ComputedStructureEntry(
1607            struct,
1608            -3,
1609            parameters={
1610                "is_hubbard": False,
1611                "hubbards": None,
1612                "run_type": "GGA",
1613                "potcar_spec": [
1614                    {
1615                        "titel": "PAW_PBE Li 17Jan2003",
1616                        "hash": "65e83282d1707ec078c1012afbd05be8",
1617                    },
1618                    {
1619                        "titel": "PAW_PBE O 08Apr2002",
1620                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1621                    },
1622                ],
1623            },
1624        )
1625
1626        lio3_entry_corrected = self.compat.process_entry(lio3_entry)
1627        self.assertAlmostEqual(lio3_entry_corrected.energy, -3.0)
1628
1629    def test_process_entry_oxide(self):
1630        el_li = Element("Li")
1631        el_o = Element("O")
1632        elts = [el_li, el_li, el_o]
1633        latt = Lattice.from_parameters(3.278, 3.278, 3.278, 60, 60, 60)
1634        coords = [[0.25, 0.25, 0.25], [0.75, 0.75, 0.75], [0.0, 0.0, 0.0]]
1635        struct = Structure(latt, elts, coords)
1636        li2o_entry = ComputedStructureEntry(
1637            struct,
1638            -3,
1639            parameters={
1640                "is_hubbard": False,
1641                "hubbards": None,
1642                "run_type": "GGA",
1643                "potcar_spec": [
1644                    {
1645                        "titel": "PAW_PBE Li 17Jan2003",
1646                        "hash": "65e83282d1707ec078c1012afbd05be8",
1647                    },
1648                    {
1649                        "titel": "PAW_PBE O 08Apr2002",
1650                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1651                    },
1652                ],
1653            },
1654        )
1655
1656        li2o_entry_corrected = self.compat.process_entry(li2o_entry)
1657        self.assertAlmostEqual(li2o_entry_corrected.energy, -3.0 - 0.66975, 4)
1658
1659
1660class SulfideTypeCorrection2020Test(unittest.TestCase):
1661    def setUp(self):
1662        self.compat = MaterialsProject2020Compatibility(check_potcar_hash=False)
1663
1664    def test_struct_no_struct(self):
1665        # Processing an Entry should produce the same correction whether or not
1666        # that entry has a Structure attached to it.
1667
1668        # Na2S2, entry mp-2400, with and without structure
1669        from collections import defaultdict
1670
1671        entry_struct_as_dict = {
1672            "@module": "pymatgen.entries.computed_entries",
1673            "@class": "ComputedStructureEntry",
1674            "energy": -28.42580746,
1675            "composition": defaultdict(float, {"Na": 4.0, "S": 4.0}),
1676            "correction": 0,
1677            "parameters": {
1678                "run_type": "GGA",
1679                "is_hubbard": False,
1680                "pseudo_potential": {
1681                    "functional": "PBE",
1682                    "labels": ["Na_pv", "S"],
1683                    "pot_type": "paw",
1684                },
1685                "hubbards": {},
1686                "potcar_symbols": ["PBE Na_pv", "PBE S"],
1687                "oxide_type": "None",
1688            },
1689            "data": {"oxide_type": "None"},
1690            "entry_id": "mp-2400",
1691            "structure": {
1692                "@module": "pymatgen.core.structure",
1693                "@class": "Structure",
1694                "charge": None,
1695                "lattice": {
1696                    "matrix": [
1697                        [4.5143094, 0.0, 0.0],
1698                        [-2.2571547, 3.90950662, 0.0],
1699                        [0.0, 0.0, 10.28414905],
1700                    ],
1701                    "a": 4.5143094,
1702                    "b": 4.514309399183436,
1703                    "c": 10.28414905,
1704                    "alpha": 90.0,
1705                    "beta": 90.0,
1706                    "gamma": 120.00000000598358,
1707                    "volume": 181.50209256783256,
1708                },
1709                "sites": [
1710                    {
1711                        "species": [{"element": "Na", "occu": 1}],
1712                        "abc": [0.0, 0.0, 0.0],
1713                        "xyz": [0.0, 0.0, 0.0],
1714                        "label": "Na",
1715                        "properties": {"magmom": 0.0},
1716                    },
1717                    {
1718                        "species": [{"element": "Na", "occu": 1}],
1719                        "abc": [0.0, 0.0, 0.5],
1720                        "xyz": [0.0, 0.0, 5.142074525],
1721                        "label": "Na",
1722                        "properties": {"magmom": 0.0},
1723                    },
1724                    {
1725                        "species": [{"element": "Na", "occu": 1}],
1726                        "abc": [0.33333333, 0.66666667, 0.25],
1727                        "xyz": [
1728                            -2.2571547075855847e-08,
1729                            2.6063377596983557,
1730                            2.5710372625,
1731                        ],
1732                        "label": "Na",
1733                        "properties": {"magmom": 0.0},
1734                    },
1735                    {
1736                        "species": [{"element": "Na", "occu": 1}],
1737                        "abc": [0.66666667, 0.33333333, 0.75],
1738                        "xyz": [2.2571547225715474, 1.3031688603016447, 7.7131117875],
1739                        "label": "Na",
1740                        "properties": {"magmom": 0.0},
1741                    },
1742                    {
1743                        "species": [{"element": "S", "occu": 1}],
1744                        "abc": [0.33333333, 0.66666667, 0.644551],
1745                        "xyz": [
1746                            -2.2571547075855847e-08,
1747                            2.6063377596983557,
1748                            6.62865855432655,
1749                        ],
1750                        "label": "S",
1751                        "properties": {"magmom": 0.0},
1752                    },
1753                    {
1754                        "species": [{"element": "S", "occu": 1}],
1755                        "abc": [0.66666667, 0.33333333, 0.144551],
1756                        "xyz": [
1757                            2.2571547225715474,
1758                            1.3031688603016447,
1759                            1.4865840293265502,
1760                        ],
1761                        "label": "S",
1762                        "properties": {"magmom": 0.0},
1763                    },
1764                    {
1765                        "species": [{"element": "S", "occu": 1}],
1766                        "abc": [0.66666667, 0.33333333, 0.355449],
1767                        "xyz": [
1768                            2.2571547225715474,
1769                            1.3031688603016447,
1770                            3.65549049567345,
1771                        ],
1772                        "label": "S",
1773                        "properties": {"magmom": 0.0},
1774                    },
1775                    {
1776                        "species": [{"element": "S", "occu": 1}],
1777                        "abc": [0.33333333, 0.66666667, 0.855449],
1778                        "xyz": [
1779                            -2.2571547075855847e-08,
1780                            2.6063377596983557,
1781                            8.79756502067345,
1782                        ],
1783                        "label": "S",
1784                        "properties": {"magmom": 0.0},
1785                    },
1786                ],
1787            },
1788        }
1789
1790        entry_no_struct_as_dict = {
1791            "@module": "pymatgen.entries.computed_entries",
1792            "@class": "ComputedEntry",
1793            "energy": -28.42580746,
1794            "composition": defaultdict(float, {"Na": 4.0, "S": 4.0}),
1795            "correction": 0,
1796            "parameters": {
1797                "run_type": "GGA",
1798                "is_hubbard": False,
1799                "pseudo_potential": {
1800                    "functional": "PBE",
1801                    "labels": ["Na_pv", "S"],
1802                    "pot_type": "paw",
1803                },
1804                "hubbards": {},
1805                "potcar_symbols": ["PBE Na_pv", "PBE S"],
1806                "oxide_type": "None",
1807            },
1808            "data": {"oxide_type": "None"},
1809            "entry_id": "mp-2400",
1810        }
1811
1812        na2s2_entry_struct = ComputedStructureEntry.from_dict(entry_struct_as_dict)
1813        na2s2_entry_nostruct = ComputedEntry.from_dict(entry_no_struct_as_dict)
1814
1815        struct_corrected = self.compat.process_entry(na2s2_entry_struct)
1816        nostruct_corrected = self.compat.process_entry(na2s2_entry_nostruct)
1817
1818        self.assertAlmostEqual(struct_corrected.correction, nostruct_corrected.correction, 4)
1819
1820
1821class OxideTypeCorrectionNoPeroxideCorrTest(unittest.TestCase):
1822    def setUp(self):
1823        self.compat = MITCompatibility(correct_peroxide=False)
1824
1825    def test_oxide_energy_corr(self):
1826        el_li = Element("Li")
1827        el_o = Element("O")
1828        elts = [el_li, el_li, el_o]
1829        latt = Lattice.from_parameters(3.278, 3.278, 3.278, 60, 60, 60)
1830        coords = [[0.25, 0.25, 0.25], [0.75, 0.75, 0.75], [0.0, 0.0, 0.0]]
1831        struct = Structure(latt, elts, coords)
1832        li2o_entry = ComputedStructureEntry(
1833            struct,
1834            -3,
1835            parameters={
1836                "is_hubbard": False,
1837                "hubbards": None,
1838                "run_type": "GGA",
1839                "potcar_spec": [
1840                    {
1841                        "titel": "PAW_PBE Li 17Jan2003",
1842                        "hash": "65e83282d1707ec078c1012afbd05be8",
1843                    },
1844                    {
1845                        "titel": "PAW_PBE O 08Apr2002",
1846                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1847                    },
1848                ],
1849            },
1850        )
1851
1852        li2o_entry_corrected = self.compat.process_entry(li2o_entry)
1853        self.assertAlmostEqual(li2o_entry_corrected.energy, -3.0 - 0.66975, 4)
1854
1855    def test_peroxide_energy_corr(self):
1856        latt = Lattice.from_parameters(3.159597, 3.159572, 7.685205, 89.999884, 89.999674, 60.000510)
1857        el_li = Element("Li")
1858        el_o = Element("O")
1859        elts = [el_li, el_li, el_li, el_li, el_o, el_o, el_o, el_o]
1860        coords = [
1861            [0.666656, 0.666705, 0.750001],
1862            [0.333342, 0.333378, 0.250001],
1863            [0.000001, 0.000041, 0.500001],
1864            [0.000001, 0.000021, 0.000001],
1865            [0.333347, 0.333332, 0.649191],
1866            [0.333322, 0.333353, 0.850803],
1867            [0.666666, 0.666686, 0.350813],
1868            [0.666665, 0.666684, 0.149189],
1869        ]
1870        struct = Structure(latt, elts, coords)
1871        li2o2_entry = ComputedStructureEntry(
1872            struct,
1873            -3,
1874            parameters={
1875                "is_hubbard": False,
1876                "hubbards": None,
1877                "run_type": "GGA",
1878                "potcar_spec": [
1879                    {
1880                        "titel": "PAW_PBE Li 17Jan2003",
1881                        "hash": "65e83282d1707ec078c1012afbd05be8",
1882                    },
1883                    {
1884                        "titel": "PAW_PBE O 08Apr2002",
1885                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1886                    },
1887                ],
1888            },
1889        )
1890
1891        li2o2_entry_corrected = self.compat.process_entry(li2o2_entry)
1892        self.assertRaises(AssertionError, self.assertAlmostEqual, *(li2o2_entry_corrected.energy, -3 - 0.44317 * 4, 4))
1893        self.assertAlmostEqual(li2o2_entry_corrected.energy, -3 - 0.66975 * 4, 4)
1894
1895    def test_ozonide(self):
1896        el_li = Element("Li")
1897        el_o = Element("O")
1898        elts = [el_li, el_o, el_o, el_o]
1899        latt = Lattice.from_parameters(3.999911, 3.999911, 3.999911, 133.847504, 102.228244, 95.477342)
1900        coords = [
1901            [0.513004, 0.513004, 1.000000],
1902            [0.017616, 0.017616, 0.000000],
1903            [0.649993, 0.874790, 0.775203],
1904            [0.099587, 0.874790, 0.224797],
1905        ]
1906        struct = Structure(latt, elts, coords)
1907        lio3_entry = ComputedStructureEntry(
1908            struct,
1909            -3,
1910            parameters={
1911                "is_hubbard": False,
1912                "hubbards": None,
1913                "run_type": "GGA",
1914                "potcar_spec": [
1915                    {
1916                        "titel": "PAW_PBE Li 17Jan2003",
1917                        "hash": "65e83282d1707ec078c1012afbd05be8",
1918                    },
1919                    {
1920                        "titel": "PAW_PBE O 08Apr2002",
1921                        "hash": "7a25bc5b9a5393f46600a4939d357982",
1922                    },
1923                ],
1924            },
1925        )
1926
1927        lio3_entry_corrected = self.compat.process_entry(lio3_entry)
1928        self.assertAlmostEqual(lio3_entry_corrected.energy, -3.0 - 3 * 0.66975)
1929
1930
1931class TestMaterialsProjectAqueousCompatibility:
1932    """
1933    Test MaterialsProjectAqueousCompatibility
1934
1935    -x- formation energy of H2O should always be -2.458 eV/H2O
1936    -x- H2 energy should always be the same value
1937    -x- H2O energy should always be the same value
1938    -x- Should get warnings if you init without all energy args
1939    -x- Should get CompatibilityError if you get_entry without all energy args
1940    -x- energy args should auto-populate from entries passed to process_entries
1941    -x- check compound entropies appropriately added
1942    -x- check hydrate adjustment appropriately applied
1943
1944    Notes:
1945        Argument values from MaterialsProjectCompatibility as of April 2020:
1946            corrected DFT energy of H2O = -15.5875 eV/H2O (mp-697111) or -5.195 eV/atom
1947            corrected DFT energy of O2 = -4.9276 eV/atom (mp-12957)
1948            total energy corrections applied to H2O (eV/H2O) -0.70229 eV/H2O or -0.234 eV/atom
1949    """
1950
1951    def test_h_h2o_energy_with_args(self):
1952
1953        compat = MaterialsProjectAqueousCompatibility(
1954            o2_energy=-4.9276,
1955            h2o_energy=-5.195,
1956            h2o_adjustments=-0.234,
1957            solid_compat=None,
1958        )
1959
1960        h2o_entry_1 = ComputedEntry(Composition("H2O"), -16)
1961        h2o_entry_2 = ComputedEntry(Composition("H4O2"), -10)
1962        h2_entry_1 = ComputedEntry(Composition("H2"), -16)
1963        h2_entry_2 = ComputedEntry(Composition("H8"), -100)
1964
1965        for entry in [h2o_entry_1, h2o_entry_2, h2_entry_1, h2_entry_2]:
1966            compat.process_entries(entry)
1967
1968        assert h2o_entry_1.energy_per_atom == pytest.approx(h2o_entry_2.energy_per_atom)
1969        assert h2_entry_1.energy_per_atom == pytest.approx(h2_entry_2.energy_per_atom)
1970
1971        o2_entry_1 = ComputedEntry(Composition("O2"), -4.9276 * 2)
1972        o2_entry_1 = compat.process_entries(o2_entry_1)[0]
1973
1974        h2o_form_e = 3 * h2o_entry_2.energy_per_atom - 2 * h2_entry_2.energy_per_atom - o2_entry_1.energy_per_atom
1975        assert h2o_form_e == pytest.approx(MU_H2O)
1976
1977    def test_h_h2o_energy_no_args(self):
1978
1979        with pytest.warns(UserWarning, match="You did not provide the required O2 and H2O energies."):
1980            compat = MaterialsProjectAqueousCompatibility(solid_compat=None)
1981
1982        h2o_entry_1 = ComputedEntry(Composition("H2O"), (-5.195 + 0.234) * 3, correction=-0.234 * 3)
1983        h2o_entry_2 = ComputedEntry(Composition("H4O2"), -10)
1984        h2_entry_1 = ComputedEntry(Composition("H2"), -16)
1985        h2_entry_2 = ComputedEntry(Composition("H8"), -100)
1986        o2_entry_1 = ComputedEntry(Composition("O2"), -4.9276 * 2)
1987
1988        with pytest.raises(CompatibilityError, match="Either specify the energies as arguments to "):
1989            compat.get_adjustments(h2_entry_1)
1990
1991        entries = compat.process_entries([h2o_entry_1, h2o_entry_2, h2_entry_1, h2_entry_2, o2_entry_1])
1992
1993        assert compat.o2_energy == -4.9276
1994        assert compat.h2o_energy == -5.195
1995        assert compat.h2o_adjustments == -0.234
1996
1997        h2o_entries = [e for e in entries if e.composition.reduced_formula == "H2O"]
1998        h2_entries = [e for e in entries if e.composition.reduced_formula == "H2"]
1999
2000        assert h2o_entries[0].energy_per_atom == pytest.approx(h2o_entries[1].energy_per_atom)
2001        assert h2_entries[0].energy_per_atom == pytest.approx(h2_entries[1].energy_per_atom)
2002
2003        h2o_form_e = 3 * h2o_entries[1].energy_per_atom - 2 * h2_entries[0].energy_per_atom - o2_entry_1.energy_per_atom
2004        assert h2o_form_e == pytest.approx(MU_H2O)
2005
2006    def test_compound_entropy(self):
2007        compat = MaterialsProjectAqueousCompatibility(
2008            o2_energy=-10, h2o_energy=-20, h2o_adjustments=-0.5, solid_compat=None
2009        )
2010
2011        o2_entry_1 = ComputedEntry(Composition("O2"), -4.9276 * 2)
2012
2013        initial_energy = o2_entry_1.energy_per_atom
2014        o2_entry_1 = compat.process_entries(o2_entry_1)[0]
2015        processed_energy = o2_entry_1.energy_per_atom
2016
2017        assert initial_energy - processed_energy == pytest.approx(compat.cpd_entropies["O2"])
2018
2019    def test_hydrate_adjustment(self):
2020        compat = MaterialsProjectAqueousCompatibility(
2021            o2_energy=-10, h2o_energy=-20, h2o_adjustments=-0.5, solid_compat=None
2022        )
2023
2024        hydrate_entry = ComputedEntry(Composition("FeH4O2"), -10)
2025
2026        initial_energy = hydrate_entry.energy
2027        hydrate_entry = compat.process_entries(hydrate_entry)[0]
2028        processed_energy = hydrate_entry.energy
2029
2030        assert initial_energy - processed_energy == pytest.approx(2 * (compat.h2o_adjustments * 3 + MU_H2O))
2031
2032
2033class AqueousCorrectionTest(unittest.TestCase):
2034    def setUp(self):
2035        module_dir = os.path.dirname(os.path.abspath(__file__))
2036        fp = os.path.join(module_dir, os.path.pardir, "MITCompatibility.yaml")
2037        self.corr = AqueousCorrection(fp)
2038
2039    def test_compound_energy(self):
2040        O2_entry = self.corr.correct_entry(ComputedEntry(Composition("O2"), -4.9355 * 2))
2041        H2_entry = self.corr.correct_entry(ComputedEntry(Composition("H2"), 3))
2042        H2O_entry = self.corr.correct_entry(ComputedEntry(Composition("H2O"), 3))
2043        H2O_formation_energy = H2O_entry.energy - (H2_entry.energy + O2_entry.energy / 2.0)
2044        self.assertAlmostEqual(H2O_formation_energy, -2.46, 2)
2045
2046        entry = ComputedEntry(Composition("H2O"), -16)
2047        entry = self.corr.correct_entry(entry)
2048        self.assertAlmostEqual(entry.energy, -14.916, 4)
2049
2050        entry = ComputedEntry(Composition("H2O"), -24)
2051        entry = self.corr.correct_entry(entry)
2052        self.assertAlmostEqual(entry.energy, -14.916, 4)
2053
2054        entry = ComputedEntry(Composition("Cl"), -24)
2055        entry = self.corr.correct_entry(entry)
2056        self.assertAlmostEqual(entry.energy, -24.344373, 4)
2057
2058
2059class MITAqueousCompatibilityTest(unittest.TestCase):
2060    def setUp(self):
2061        self.compat = MITCompatibility(check_potcar_hash=True)
2062        self.aqcompat = MITAqueousCompatibility(check_potcar_hash=True)
2063        module_dir = os.path.dirname(os.path.abspath(__file__))
2064        fp = os.path.join(module_dir, os.path.pardir, "MITCompatibility.yaml")
2065        self.aqcorr = AqueousCorrection(fp)
2066
2067    def test_aqueous_compat(self):
2068
2069        el_li = Element("Li")
2070        el_o = Element("O")
2071        el_h = Element("H")
2072        latt = Lattice.from_parameters(3.565276, 3.565276, 4.384277, 90.000000, 90.000000, 90.000000)
2073        elts = [el_h, el_h, el_li, el_li, el_o, el_o]
2074        coords = [
2075            [0.000000, 0.500000, 0.413969],
2076            [0.500000, 0.000000, 0.586031],
2077            [0.000000, 0.000000, 0.000000],
2078            [0.500000, 0.500000, 0.000000],
2079            [0.000000, 0.500000, 0.192672],
2080            [0.500000, 0.000000, 0.807328],
2081        ]
2082        struct = Structure(latt, elts, coords)
2083        lioh_entry = ComputedStructureEntry(
2084            struct,
2085            -3,
2086            parameters={
2087                "is_hubbard": False,
2088                "hubbards": None,
2089                "run_type": "GGA",
2090                "potcar_spec": [
2091                    {
2092                        "titel": "PAW_PBE Li 17Jan2003",
2093                        "hash": "65e83282d1707ec078c1012afbd05be8",
2094                    },
2095                    {
2096                        "titel": "PAW_PBE O 08Apr2002",
2097                        "hash": "7a25bc5b9a5393f46600a4939d357982",
2098                    },
2099                    {
2100                        "titel": "PAW_PBE H 15Jun2001",
2101                        "hash": "bb43c666e3d36577264afe07669e9582",
2102                    },
2103                ],
2104            },
2105        )
2106        lioh_entry_compat = self.compat.process_entry(lioh_entry)
2107        lioh_entry_compat_aqcorr = self.aqcorr.correct_entry(lioh_entry_compat)
2108        lioh_entry_aqcompat = self.aqcompat.process_entry(lioh_entry)
2109        self.assertAlmostEqual(lioh_entry_compat_aqcorr.energy, lioh_entry_aqcompat.energy, 4)
2110
2111    def test_potcar_doenst_match_structure(self):
2112        compat = MITCompatibility()
2113        el_li = Element("Li")
2114        el_o = Element("O")
2115        el_h = Element("H")
2116        latt = Lattice.from_parameters(3.565276, 3.565276, 4.384277, 90.000000, 90.000000, 90.000000)
2117        elts = [el_h, el_h, el_li, el_li, el_o, el_o]
2118        coords = [
2119            [0.000000, 0.500000, 0.413969],
2120            [0.500000, 0.000000, 0.586031],
2121            [0.000000, 0.000000, 0.000000],
2122            [0.500000, 0.500000, 0.000000],
2123            [0.000000, 0.500000, 0.192672],
2124            [0.500000, 0.000000, 0.807328],
2125        ]
2126        struct = Structure(latt, elts, coords)
2127
2128        lioh_entry = ComputedStructureEntry(
2129            struct,
2130            -3,
2131            parameters={
2132                "is_hubbard": False,
2133                "hubbards": None,
2134                "run_type": "GGA",
2135                "potcar_symbols": [
2136                    "PAW_PBE Fe 17Jan2003",
2137                    "PAW_PBE O 08Apr2002",
2138                    "PAW_PBE H 15Jun2001",
2139                ],
2140            },
2141        )
2142
2143        self.assertIsNone(compat.process_entry(lioh_entry))
2144
2145    def test_msonable(self):
2146        compat_dict = self.aqcompat.as_dict()
2147        decoder = MontyDecoder()
2148        temp_compat = decoder.process_decoded(compat_dict)
2149        self.assertIsInstance(temp_compat, MITAqueousCompatibility)
2150
2151    def test_dont_error_on_weird_elements(self):
2152        entry = ComputedEntry(
2153            "AmSi",
2154            -1,
2155            correction=0.0,
2156            parameters={
2157                "potcar_spec": [
2158                    {
2159                        "titel": "PAW_PBE Am 08May2007",
2160                        "hash": "ed5eebd8a143e35a0c19e9f8a2c42a93",
2161                    },
2162                    {
2163                        "titel": "PAW_PBE Si 05Jan2001",
2164                        "hash": "b2b0ea6feb62e7cde209616683b8f7f5",
2165                    },
2166                ]
2167            },
2168        )
2169        self.assertIsNone(self.compat.process_entry(entry))
2170
2171
2172class CorrectionErrors2020CompatibilityTest(unittest.TestCase):
2173    def setUp(self):
2174        warnings.simplefilter("ignore")
2175        self.compat = MaterialsProject2020Compatibility()
2176
2177        self.entry1 = ComputedEntry(
2178            "Fe2O3",
2179            -1,
2180            correction=0.0,
2181            parameters={
2182                "is_hubbard": True,
2183                "hubbards": {"Fe": 5.3, "O": 0},
2184                "run_type": "GGA+U",
2185                "potcar_spec": [
2186                    {
2187                        "titel": "PAW_PBE Fe_pv 06Sep2000",
2188                        "hash": "994537de5c4122b7f1b77fb604476db4",
2189                    },
2190                    {
2191                        "titel": "PAW_PBE O 08Apr2002",
2192                        "hash": "7a25bc5b9a5393f46600a4939d357982",
2193                    },
2194                ],
2195            },
2196        )
2197
2198        self.entry_sulfide = ComputedEntry(
2199            "FeS",
2200            -1,
2201            0.0,
2202            parameters={
2203                "is_hubbard": False,
2204                "run_type": "GGA",
2205                "potcar_spec": [
2206                    {
2207                        "titel": "PAW_PBE Fe_pv 06Sep2000",
2208                        "hash": "994537de5c4122b7f1b77fb604476db4",
2209                    },
2210                    {
2211                        "titel": "PAW_PBE S 08Apr2002",
2212                        "hash": "7a25bc5b9a5393f46600a4939d357982",
2213                    },
2214                ],
2215            },
2216        )
2217
2218        self.entry2 = ComputedEntry(
2219            "Fe3O4",
2220            -2,
2221            correction=0.0,
2222            parameters={
2223                "is_hubbard": True,
2224                "hubbards": {"Fe": 5.3, "O": 0},
2225                "run_type": "GGA+U",
2226                "potcar_spec": [
2227                    {
2228                        "titel": "PAW_PBE Fe_pv 06Sep2000",
2229                        "hash": "994537de5c4122b7f1b77fb604476db4",
2230                    },
2231                    {
2232                        "titel": "PAW_PBE O 08Apr2002",
2233                        "hash": "7a25bc5b9a5393f46600a4939d357982",
2234                    },
2235                ],
2236            },
2237        )
2238
2239        self.entry_fluoride = ComputedEntry(
2240            "FeF3",
2241            -2,
2242            correction=0.0,
2243            parameters={
2244                "is_hubbard": True,
2245                "hubbards": {"Fe": 5.3, "F": 0},
2246                "run_type": "GGA+U",
2247                "potcar_spec": [
2248                    {
2249                        "titel": "PAW_PBE Fe_pv 06Sep2000",
2250                        "hash": "994537de5c4122b7f1b77fb604476db4",
2251                    },
2252                    {
2253                        "titel": "PAW_PBE F 08Apr2002",
2254                        "hash": "180141c33d032bfbfff30b3bea9d23dd",
2255                    },
2256                ],
2257            },
2258        )
2259
2260        self.entry_hydride = ComputedEntry(
2261            "LiH",
2262            -2,
2263            correction=0.0,
2264            parameters={
2265                "is_hubbard": False,
2266                "run_type": "GGA",
2267                "potcar_spec": [
2268                    {
2269                        "titel": "PAW_PBE Li_sv 10Sep2004",
2270                        "hash": "8245d7383d7556214082aa40a887cd96",
2271                    },
2272                    {
2273                        "titel": "PAW_PBE H 15Jun2001",
2274                        "hash": "bb43c666e3d36577264afe07669e9582",
2275                    },
2276                ],
2277            },
2278        )
2279
2280    def tearDown(self):
2281        warnings.simplefilter("default")
2282
2283    def test_errors(self):
2284        entry1_corrected = self.compat.process_entry(self.entry1)
2285        self.assertAlmostEqual(
2286            entry1_corrected.correction_uncertainty,
2287            sqrt((2 * 0.0101) ** 2 + (3 * 0.002) ** 2),
2288        )
2289
2290        entry2_corrected = self.compat.process_entry(self.entry2)
2291        self.assertAlmostEqual(
2292            entry2_corrected.correction_uncertainty,
2293            sqrt((3 * 0.0101) ** 2 + (4 * 0.002) ** 2),
2294        )
2295
2296        entry_sulfide_corrected = self.compat.process_entry(self.entry_sulfide)
2297        self.assertAlmostEqual(entry_sulfide_corrected.correction_uncertainty, 0.0093)
2298
2299        entry_fluoride_corrected = self.compat.process_entry(self.entry_fluoride)
2300        self.assertAlmostEqual(
2301            entry_fluoride_corrected.correction_uncertainty,
2302            sqrt((3 * 0.0026) ** 2 + 0.0101 ** 2),
2303        )
2304
2305        entry_hydride_corrected = self.compat.process_entry(self.entry_hydride)
2306        self.assertAlmostEqual(entry_hydride_corrected.correction_uncertainty, 0.0013)
2307
2308
2309if __name__ == "__main__":
2310    # import sys;sys.argv = ['', 'Test.testName']
2311    unittest.main()
2312