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