1# coding: utf-8 2# Copyright (c) Pymatgen Development Team. 3# Distributed under the terms of the MIT License. 4 5 6import unittest 7import warnings 8 9import numpy as np 10 11from pymatgen.core.composition import Composition 12from pymatgen.core.periodic_table import DummySpecies, Element, Species 13from pymatgen.core.lattice import Lattice 14from pymatgen.core.structure import Structure 15from pymatgen.analysis.structure_matcher import StructureMatcher 16from pymatgen.electronic_structure.core import Magmom 17from pymatgen.symmetry.structure import SymmetrizedStructure 18from pymatgen.io.cif import CifBlock, CifParser, CifWriter 19from pymatgen.io.vasp.inputs import Poscar 20from pymatgen.util.testing import PymatgenTest 21 22try: 23 import pybtex 24except ImportError: 25 pybtex = None 26 27 28class CifBlockTest(PymatgenTest): 29 def test_to_string(self): 30 with open(self.TEST_FILES_DIR / "Graphite.cif") as f: 31 s = f.read() 32 c = CifBlock.from_string(s) 33 cif_str_2 = str(CifBlock.from_string(str(c))) 34 cif_str = """data_53781-ICSD 35_database_code_ICSD 53781 36_audit_creation_date 2003-04-01 37_audit_update_record 2013-02-01 38_chemical_name_systematic Carbon 39_chemical_formula_structural C 40_chemical_formula_sum C1 41_chemical_name_structure_type Graphite(2H) 42_chemical_name_mineral 'Graphite 2H' 43_exptl_crystal_density_diffrn 2.22 44_publ_section_title 'Structure of graphite' 45loop_ 46 _citation_id 47 _citation_journal_full 48 _citation_year 49 _citation_journal_volume 50 _citation_page_first 51 _citation_page_last 52 _citation_journal_id_ASTM 53 primary 'Physical Review (1,1893-132,1963/141,1966-188,1969)' 54 1917 10 661 696 PHRVAO 55loop_ 56 _publ_author_name 57 'Hull, A.W.' 58_cell_length_a 2.47 59_cell_length_b 2.47 60_cell_length_c 6.8 61_cell_angle_alpha 90. 62_cell_angle_beta 90. 63_cell_angle_gamma 120. 64_cell_volume 35.93 65_cell_formula_units_Z 4 66_symmetry_space_group_name_H-M 'P 63/m m c' 67_symmetry_Int_Tables_number 194 68loop_ 69 _symmetry_equiv_pos_site_id 70 _symmetry_equiv_pos_as_xyz 71 1 'x, x-y, -z+1/2' 72 2 '-x+y, y, -z+1/2' 73 3 '-y, -x, -z+1/2' 74 4 '-x+y, -x, -z+1/2' 75 5 '-y, x-y, -z+1/2' 76 6 'x, y, -z+1/2' 77 7 '-x, -x+y, z+1/2' 78 8 'x-y, -y, z+1/2' 79 9 'y, x, z+1/2' 80 10 'x-y, x, z+1/2' 81 11 'y, -x+y, z+1/2' 82 12 '-x, -y, z+1/2' 83 13 '-x, -x+y, -z' 84 14 'x-y, -y, -z' 85 15 'y, x, -z' 86 16 'x-y, x, -z' 87 17 'y, -x+y, -z' 88 18 '-x, -y, -z' 89 19 'x, x-y, z' 90 20 '-x+y, y, z' 91 21 '-y, -x, z' 92 22 '-x+y, -x, z' 93 23 '-y, x-y, z' 94 24 'x, y, z' 95loop_ 96 _atom_type_symbol 97 _atom_type_oxidation_number 98 C0+ 0 99loop_ 100 _atom_site_label 101 _atom_site_type_symbol 102 _atom_site_symmetry_multiplicity 103 _atom_site_Wyckoff_symbol 104 _atom_site_fract_x 105 _atom_site_fract_y 106 _atom_site_fract_z 107 _atom_site_B_iso_or_equiv 108 _atom_site_occupancy 109 _atom_site_attached_hydrogens 110 C1 C0+ 2 b 0 0 0.25 . 1. 0 111 C2 C0+ 2 c 0.3333 0.6667 0.25 . 1. 0""" 112 for l1, l2, l3 in zip(str(c).split("\n"), cif_str.split("\n"), cif_str_2.split("\n")): 113 self.assertEqual(l1.strip(), l2.strip()) 114 self.assertEqual(l2.strip(), l3.strip()) 115 116 def test_double_quotes_and_underscore_data(self): 117 cif_str = """data_test 118_symmetry_space_group_name_H-M "P -3 m 1" 119_thing '_annoying_data'""" 120 cb = CifBlock.from_string(cif_str) 121 self.assertEqual(cb["_symmetry_space_group_name_H-M"], "P -3 m 1") 122 self.assertEqual(cb["_thing"], "_annoying_data") 123 self.assertEqual(str(cb), cif_str.replace('"', "'")) 124 125 def test_double_quoted_data(self): 126 cif_str = """data_test 127_thing ' '_annoying_data'' 128_other " "_more_annoying_data"" 129_more ' "even more" ' """ 130 cb = CifBlock.from_string(cif_str) 131 self.assertEqual(cb["_thing"], " '_annoying_data'") 132 self.assertEqual(cb["_other"], ' "_more_annoying_data"') 133 self.assertEqual(cb["_more"], ' "even more" ') 134 135 def test_nested_fake_multiline_quotes(self): 136 cif_str = """data_test 137_thing 138; 139long quotes 140 ; 141 still in the quote 142 ; 143actually going to end now 144;""" 145 cb = CifBlock.from_string(cif_str) 146 self.assertEqual( 147 cb["_thing"], 148 " long quotes ; still in the quote" " ; actually going to end now", 149 ) 150 151 def test_long_loop(self): 152 data = { 153 "_stuff1": ["A" * 30] * 2, 154 "_stuff2": ["B" * 30] * 2, 155 "_stuff3": ["C" * 30] * 2, 156 } 157 loops = [["_stuff1", "_stuff2", "_stuff3"]] 158 cif_str = """data_test 159loop_ 160 _stuff1 161 _stuff2 162 _stuff3 163 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 164 CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC 165 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB 166 CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC""" 167 self.assertEqual(str(CifBlock(data, loops, "test")), cif_str) 168 169 170class CifIOTest(PymatgenTest): 171 def test_CifParser(self): 172 parser = CifParser(self.TEST_FILES_DIR / "LiFePO4.cif") 173 for s in parser.get_structures(True): 174 self.assertEqual(s.formula, "Li4 Fe4 P4 O16", "Incorrectly parsed cif.") 175 176 parser = CifParser(self.TEST_FILES_DIR / "V2O3.cif") 177 for s in parser.get_structures(True): 178 self.assertEqual(s.formula, "V4 O6") 179 180 bibtex_str = """ 181@article{cifref0, 182 author = "Andersson, G.", 183 title = "Studies on vanadium oxides. I. Phase analysis", 184 journal = "Acta Chemica Scandinavica (1-27,1973-42,1988)", 185 volume = "8", 186 year = "1954", 187 pages = "1599--1606" 188} 189 """ 190 self.assertEqual(parser.get_bibtex_string().strip(), bibtex_str.strip()) 191 192 parser = CifParser(self.TEST_FILES_DIR / "Li2O.cif") 193 prim = parser.get_structures(True)[0] 194 self.assertEqual(prim.formula, "Li2 O1") 195 conv = parser.get_structures(False)[0] 196 self.assertEqual(conv.formula, "Li8 O4") 197 198 # test for disordered structures 199 parser = CifParser(self.TEST_FILES_DIR / "Li10GeP2S12.cif") 200 for s in parser.get_structures(True): 201 self.assertEqual(s.formula, "Li20.2 Ge2.06 P3.94 S24", "Incorrectly parsed cif.") 202 cif_str = r"""#\#CIF1.1 203########################################################################## 204# Crystallographic Information Format file 205# Produced by PyCifRW module 206# 207# This is a CIF file. CIF has been adopted by the International 208# Union of Crystallography as the standard for data archiving and 209# transmission. 210# 211# For information on this file format, follow the CIF links at 212# http://www.iucr.org 213########################################################################## 214 215data_FePO4 216_symmetry_space_group_name_H-M 'P 1' 217_cell_length_a 10.4117668699 218_cell_length_b 6.06717187997 219_cell_length_c 4.75948953998 220loop_ # sometimes this is in a loop (incorrectly) 221_cell_angle_alpha 22291.0 223_cell_angle_beta 92.0 224_cell_angle_gamma 93.0 225_chemical_name_systematic 'Generated by pymatgen' 226_symmetry_Int_Tables_number 1 227_chemical_formula_structural FePO4 228_chemical_formula_sum 'Fe4 P4 O16' 229_cell_volume 300.65685512 230_cell_formula_units_Z 4 231loop_ 232 _symmetry_equiv_pos_site_id 233 _symmetry_equiv_pos_as_xyz 234 1 'x, y, z' 235 236loop_ 237 _atom_site_type_symbol 238 _atom_site_label 239 _atom_site_symmetry_multiplicity 240 _atom_site_fract_x 241 _atom_site_fract_y 242 _atom_site_fract_z 243 _atom_site_attached_hydrogens 244 _atom_site_B_iso_or_equiv 245 _atom_site_occupancy 246 Fe Fe1 1 0.218728 0.750000 0.474867 0 . 1 247 Fe JJ2 1 0.281272 0.250000 0.974867 0 . 1 248 # there's a typo here, parser should read the symbol from the 249 # _atom_site_type_symbol 250 Fe Fe3 1 0.718728 0.750000 0.025133 0 . 1 251 Fe Fe4 1 0.781272 0.250000 0.525133 0 . 1 252 P P5 1 0.094613 0.250000 0.418243 0 . 1 253 P P6 1 0.405387 0.750000 0.918243 0 . 1 254 P P7 1 0.594613 0.250000 0.081757 0 . 1 255 P P8 1 0.905387 0.750000 0.581757 0 . 1 256 O O9 1 0.043372 0.750000 0.707138 0 . 1 257 O O10 1 0.096642 0.250000 0.741320 0 . 1 258 O O11 1 0.165710 0.046072 0.285384 0 . 1 259 O O12 1 0.165710 0.453928 0.285384 0 . 1 260 O O13 1 0.334290 0.546072 0.785384 0 . 1 261 O O14 1 0.334290 0.953928 0.785384 0 . 1 262 O O15 1 0.403358 0.750000 0.241320 0 . 1 263 O O16 1 0.456628 0.250000 0.207138 0 . 1 264 O O17 1 0.543372 0.750000 0.792862 0 . 1 265 O O18 1 0.596642 0.250000 0.758680 0 . 1 266 O O19 1 0.665710 0.046072 0.214616 0 . 1 267 O O20 1 0.665710 0.453928 0.214616 0 . 1 268 O O21 1 0.834290 0.546072 0.714616 0 . 1 269 O O22 1 0.834290 0.953928 0.714616 0 . 1 270 O O23 1 0.903358 0.750000 0.258680 0 . 1 271 O O24 1 0.956628 0.250000 0.292862 0 . 1 272 273""" 274 parser = CifParser.from_string(cif_str) 275 struct = parser.get_structures(primitive=False)[0] 276 self.assertEqual(struct.formula, "Fe4 P4 O16") 277 self.assertAlmostEqual(struct.lattice.a, 10.4117668699) 278 self.assertAlmostEqual(struct.lattice.b, 6.06717187997) 279 self.assertAlmostEqual(struct.lattice.c, 4.75948953998) 280 self.assertAlmostEqual(struct.lattice.alpha, 91) 281 self.assertAlmostEqual(struct.lattice.beta, 92) 282 self.assertAlmostEqual(struct.lattice.gamma, 93) 283 284 with warnings.catch_warnings(): 285 warnings.simplefilter("ignore") 286 parser = CifParser(self.TEST_FILES_DIR / "srycoo.cif") 287 self.assertEqual(parser.get_structures()[0].formula, "Sr5.6 Y2.4 Co8 O21") 288 289 # Test with a decimal Xyz. This should parse as two atoms in 290 # conventional cell if it is correct, one if not. 291 parser = CifParser(self.TEST_FILES_DIR / "Fe.cif") 292 self.assertEqual(len(parser.get_structures(primitive=False)[0]), 2) 293 self.assertFalse(parser.has_errors) 294 295 def test_get_symmetrized_structure(self): 296 parser = CifParser(self.TEST_FILES_DIR / "Li2O.cif") 297 sym_structure = parser.get_structures(primitive=False, symmetrized=True)[0] 298 structure = parser.get_structures(primitive=False, symmetrized=False)[0] 299 self.assertIsInstance(sym_structure, SymmetrizedStructure) 300 self.assertEqual(structure, sym_structure) 301 self.assertEqual(sym_structure.equivalent_indices, [[0, 1, 2, 3], [4, 5, 6, 7, 8, 9, 10, 11]]) 302 303 def test_site_symbol_preference(self): 304 parser = CifParser(self.TEST_FILES_DIR / "site_type_symbol_test.cif") 305 self.assertEqual(parser.get_structures()[0].formula, "Ge0.4 Sb0.4 Te1") 306 307 def test_implicit_hydrogen(self): 308 with warnings.catch_warnings(): 309 warnings.simplefilter("ignore") 310 parser = CifParser(self.TEST_FILES_DIR / "Senegalite_implicit_hydrogen.cif") 311 for s in parser.get_structures(): 312 self.assertEqual(s.formula, "Al8 P4 O32") 313 self.assertEqual(sum(s.site_properties["implicit_hydrogens"]), 20) 314 self.assertIn( 315 "Structure has implicit hydrogens defined, " 316 "parsed structure unlikely to be suitable for use " 317 "in calculations unless hydrogens added.", 318 parser.warnings, 319 ) 320 parser = CifParser(self.TEST_FILES_DIR / "cif_implicit_hydrogens_cod_1011130.cif") 321 s = parser.get_structures()[0] 322 self.assertIn( 323 "Structure has implicit hydrogens defined, " 324 "parsed structure unlikely to be suitable for use " 325 "in calculations unless hydrogens added.", 326 parser.warnings, 327 ) 328 329 def test_CifParserSpringerPauling(self): 330 with warnings.catch_warnings(): 331 warnings.simplefilter("ignore") 332 # Below are 10 tests for CIFs from the Springer Materials/Pauling file DBs. 333 334 # Partial occupancy on sites, incorrect label, previously unparsable 335 parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1928405.cif") 336 for s in parser.get_structures(True): 337 self.assertEqual(s.formula, "Er1 Mn3.888 Fe2.112 Sn6") 338 self.assertTrue(parser.has_errors) 339 340 # Partial occupancy on sites, previously parsed as an ordered structure 341 parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1011081.cif") 342 for s in parser.get_structures(True): 343 self.assertEqual(s.formula, "Zr0.2 Nb0.8") 344 self.assertTrue(parser.has_errors) 345 346 # Partial occupancy on sites, incorrect label, previously unparsable 347 parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1615854.cif") 348 for s in parser.get_structures(True): 349 self.assertEqual(s.formula, "Na2 Al2 Si6 O16") 350 self.assertTrue(parser.has_errors) 351 352 # Partial occupancy on sites, incorrect label, previously unparsable 353 parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1622133.cif") 354 for s in parser.get_structures(True): 355 self.assertEqual(s.formula, "Ca0.184 Mg13.016 Fe2.8 Si16 O48") 356 self.assertTrue(parser.has_errors) 357 358 # Partial occupancy on sites, previously parsed as an ordered structure 359 parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1908491.cif") 360 for s in parser.get_structures(True): 361 self.assertEqual(s.formula, "Mn0.48 Zn0.52 Ga2 Se4") 362 self.assertTrue(parser.has_errors) 363 364 # Partial occupancy on sites, incorrect label, previously unparsable 365 parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1811457.cif") 366 for s in parser.get_structures(True): 367 self.assertEqual(s.formula, "Ba2 Mg0.6 Zr0.2 Ta1.2 O6") 368 self.assertTrue(parser.has_errors) 369 370 # Incomplete powder diffraction data, previously unparsable 371 # This CIF file contains the molecular species "NH3" which is 372 # parsed as "N" because the label is "N{x}" (x = 1,2,..) and the 373 # corresponding symbol is "NH3". Since, the label and symbol are switched 374 # in CIFs from Springer Materials/Pauling file DBs, CifParser parses the 375 # element as "Nh" (Nihonium). 376 parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1002871.cif") 377 self.assertEqual(parser.get_structures(True)[0].formula, "Cu1 Br2 Nh6") 378 self.assertEqual(parser.get_structures(True)[1].formula, "Cu1 Br4 Nh6") 379 self.assertTrue(parser.has_errors) 380 381 # Incomplete powder diffraction data, previously unparsable 382 parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1704003.cif") 383 for s in parser.get_structures(): 384 self.assertEqual(s.formula, "Rb4 Mn2 F12") 385 self.assertTrue(parser.has_errors) 386 387 # Unparsable species 'OH/OH2', previously parsed as "O" 388 parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1500382.cif") 389 for s in parser.get_structures(): 390 self.assertEqual(s.formula, "Mg6 B2 O6 F1.764") 391 self.assertTrue(parser.has_errors) 392 393 # Unparsable species 'OH/OH2', previously parsed as "O" 394 parser = CifParser(self.TEST_FILES_DIR / "PF_sd_1601634.cif") 395 for s in parser.get_structures(): 396 self.assertEqual(s.formula, "Zn1.29 Fe0.69 As2 Pb1.02 O8") 397 398 def test_CifParserCod(self): 399 """ 400 Parsing problematic cif files from the COD database 401 """ 402 with warnings.catch_warnings(): 403 warnings.simplefilter("ignore") 404 405 # Symbol in capital letters 406 parser = CifParser(self.TEST_FILES_DIR / "Cod_2100513.cif") 407 for s in parser.get_structures(True): 408 self.assertEqual(s.formula, "Ca4 Nb2.0 Al2 O12") 409 410 # Label in capital letters 411 parser = CifParser(self.TEST_FILES_DIR / "Cod_4115344.cif") 412 for s in parser.get_structures(True): 413 self.assertEqual(s.formula, "Mo4 P2 H60 C60 I4 O4") 414 415 def test_parse_symbol(self): 416 """ 417 Test the _parse_symbol function with several potentially 418 problematic examples of symbols and labels. 419 """ 420 421 test_cases = { 422 "MgT": "Mg", 423 "MgT1": "Mg", 424 "H(46A)": "H", 425 "O(M)": "O", 426 "N(Am)": "N", 427 "H1N2a": "H", 428 "CO(1)": "Co", 429 "Wat1": "O", 430 "MgM2A": "Mg", 431 "CaX": "Ca", 432 "X1": "X", 433 "X": "X", 434 "OA1": "O", 435 "NaA2": "Na", 436 "O-H2": "O", 437 "OD2": "O", 438 "OW": "O", 439 "SiT": "Si", 440 "SiTet": "Si", 441 "Na-Int": "Na", 442 "CaD1": "Ca", 443 "KAm": "K", 444 "D+1": "D", 445 "D": "D", 446 "D1-": "D", 447 "D4": "D", 448 "D0": "D", 449 "NH": "Nh", 450 "NH2": "Nh", 451 "NH3": "Nh", 452 "SH": "S", 453 } 454 455 for e in Element: 456 name = e.name 457 test_cases[name] = name 458 if len(name) == 2: 459 test_cases[name.upper()] = name 460 test_cases[name.upper() + str(1)] = name 461 test_cases[name.upper() + "A"] = name 462 test_cases[name + str(1)] = name 463 test_cases[name + str(2)] = name 464 test_cases[name + str(3)] = name 465 test_cases[name + str(1) + "A"] = name 466 467 special = {"Hw": "H", "Ow": "O", "Wat": "O", "wat": "O", "OH": "", "OH2": ""} 468 test_cases.update(special) 469 470 with warnings.catch_warnings(): 471 warnings.simplefilter("ignore") 472 parser = CifParser(self.TEST_FILES_DIR / "LiFePO4.cif") 473 for sym, expected_symbol in test_cases.items(): 474 self.assertEqual(parser._parse_symbol(sym), expected_symbol) 475 476 def test_CifWriter(self): 477 filepath = self.TEST_FILES_DIR / "POSCAR" 478 poscar = Poscar.from_file(filepath) 479 writer = CifWriter(poscar.structure, symprec=0.01) 480 ans = """# generated using pymatgen 481data_FePO4 482_symmetry_space_group_name_H-M Pnma 483_cell_length_a 10.41176687 484_cell_length_b 6.06717188 485_cell_length_c 4.75948954 486_cell_angle_alpha 90.00000000 487_cell_angle_beta 90.00000000 488_cell_angle_gamma 90.00000000 489_symmetry_Int_Tables_number 62 490_chemical_formula_structural FePO4 491_chemical_formula_sum 'Fe4 P4 O16' 492_cell_volume 300.65685512 493_cell_formula_units_Z 4 494loop_ 495 _symmetry_equiv_pos_site_id 496 _symmetry_equiv_pos_as_xyz 497 1 'x, y, z' 498 2 '-x, -y, -z' 499 3 '-x+1/2, -y, z+1/2' 500 4 'x+1/2, y, -z+1/2' 501 5 'x+1/2, -y+1/2, -z+1/2' 502 6 '-x+1/2, y+1/2, z+1/2' 503 7 '-x, y+1/2, -z' 504 8 'x, -y+1/2, z' 505loop_ 506 _atom_site_type_symbol 507 _atom_site_label 508 _atom_site_symmetry_multiplicity 509 _atom_site_fract_x 510 _atom_site_fract_y 511 _atom_site_fract_z 512 _atom_site_occupancy 513 Fe Fe0 4 0.21872822 0.75000000 0.47486711 1 514 P P1 4 0.09461309 0.25000000 0.41824327 1 515 O O2 8 0.16570974 0.04607233 0.28538394 1 516 O O3 4 0.04337231 0.75000000 0.70713767 1 517 O O4 4 0.09664244 0.25000000 0.74132035 1""" 518 for l1, l2 in zip(str(writer).split("\n"), ans.split("\n")): 519 self.assertEqual(l1.strip(), l2.strip()) 520 521 def test_symmetrized(self): 522 filepath = self.TEST_FILES_DIR / "POSCAR" 523 poscar = Poscar.from_file(filepath, check_for_POTCAR=False) 524 writer = CifWriter(poscar.structure, symprec=0.1) 525 ans = """# generated using pymatgen 526data_FePO4 527_symmetry_space_group_name_H-M Pnma 528_cell_length_a 10.41176687 529_cell_length_b 6.06717188 530_cell_length_c 4.75948954 531_cell_angle_alpha 90.00000000 532_cell_angle_beta 90.00000000 533_cell_angle_gamma 90.00000000 534_symmetry_Int_Tables_number 62 535_chemical_formula_structural FePO4 536_chemical_formula_sum 'Fe4 P4 O16' 537_cell_volume 300.65685512 538_cell_formula_units_Z 4 539loop_ 540 _symmetry_equiv_pos_site_id 541 _symmetry_equiv_pos_as_xyz 542 1 'x, y, z' 543 2 '-x, -y, -z' 544 3 '-x+1/2, -y, z+1/2' 545 4 'x+1/2, y, -z+1/2' 546 5 'x+1/2, -y+1/2, -z+1/2' 547 6 '-x+1/2, y+1/2, z+1/2' 548 7 '-x, y+1/2, -z' 549 8 'x, -y+1/2, z' 550loop_ 551 _atom_site_type_symbol 552 _atom_site_label 553 _atom_site_symmetry_multiplicity 554 _atom_site_fract_x 555 _atom_site_fract_y 556 _atom_site_fract_z 557 _atom_site_occupancy 558 Fe Fe1 4 0.218728 0.250000 0.525133 1 559 P P2 4 0.094613 0.750000 0.581757 1 560 O O3 8 0.165710 0.546072 0.714616 1 561 O O4 4 0.043372 0.250000 0.292862 1 562 O O5 4 0.096642 0.750000 0.258680 1""" 563 564 cif = CifParser.from_string(str(writer)) 565 m = StructureMatcher() 566 567 self.assertTrue(m.fit(cif.get_structures()[0], poscar.structure)) 568 569 # for l1, l2 in zip(str(writer).split("\n"), ans.split("\n")): 570 # self.assertEqual(l1.strip(), l2.strip()) 571 572 ans = """# generated using pymatgen 573data_LiFePO4 574_symmetry_space_group_name_H-M Pnma 575_cell_length_a 10.41037000 576_cell_length_b 6.06577000 577_cell_length_c 4.74480000 578_cell_angle_alpha 90.00000000 579_cell_angle_beta 90.00000000 580_cell_angle_gamma 90.00000000 581_symmetry_Int_Tables_number 62 582_chemical_formula_structural LiFePO4 583_chemical_formula_sum 'Li4 Fe4 P4 O16' 584_cell_volume 299.619458734 585_cell_formula_units_Z 4 586loop_ 587 _symmetry_equiv_pos_site_id 588 _symmetry_equiv_pos_as_xyz 589 1 'x, y, z' 590 2 '-x, -y, -z' 591 3 '-x+1/2, -y, z+1/2' 592 4 'x+1/2, y, -z+1/2' 593 5 'x+1/2, -y+1/2, -z+1/2' 594 6 '-x+1/2, y+1/2, z+1/2' 595 7 '-x, y+1/2, -z' 596 8 'x, -y+1/2, z' 597loop_ 598 _atom_site_type_symbol 599 _atom_site_label 600 _atom_site_symmetry_multiplicity 601 _atom_site_fract_x 602 _atom_site_fract_y 603 _atom_site_fract_z 604 _atom_site_occupancy 605 Li Li1 4 0.000000 0.000000 0.000000 1.0 606 Fe Fe2 4 0.218845 0.750000 0.474910 1.0 607 P P3 4 0.094445 0.250000 0.417920 1.0 608 O O4 8 0.165815 0.044060 0.286540 1.0 609 O O5 4 0.043155 0.750000 0.708460 1.0 610 O O6 4 0.096215 0.250000 0.741480 1.0 611""" 612 s = Structure.from_file(self.TEST_FILES_DIR / "LiFePO4.cif") 613 writer = CifWriter(s, symprec=0.1) 614 s2 = CifParser.from_string(str(writer)).get_structures()[0] 615 616 self.assertTrue(m.fit(s, s2)) 617 618 s = self.get_structure("Li2O") 619 writer = CifWriter(s, symprec=0.1) 620 s2 = CifParser.from_string(str(writer)).get_structures()[0] 621 self.assertTrue(m.fit(s, s2)) 622 623 # test angle tolerance. 624 s = Structure.from_file(self.TEST_FILES_DIR / "LiFePO4.cif") 625 writer = CifWriter(s, symprec=0.1, angle_tolerance=0) 626 d = list(writer.ciffile.data.values())[0] 627 self.assertEqual(d["_symmetry_Int_Tables_number"], 14) 628 s = Structure.from_file(self.TEST_FILES_DIR / "LiFePO4.cif") 629 writer = CifWriter(s, symprec=0.1, angle_tolerance=2) 630 d = list(writer.ciffile.data.values())[0] 631 self.assertEqual(d["_symmetry_Int_Tables_number"], 62) 632 633 def test_disordered(self): 634 si = Element("Si") 635 n = Element("N") 636 coords = [] 637 coords.append(np.array([0, 0, 0])) 638 coords.append(np.array([0.75, 0.5, 0.75])) 639 lattice = Lattice( 640 np.array( 641 [ 642 [3.8401979337, 0.00, 0.00], 643 [1.9200989668, 3.3257101909, 0.00], 644 [0.00, -2.2171384943, 3.1355090603], 645 ] 646 ) 647 ) 648 struct = Structure(lattice, [si, {si: 0.5, n: 0.5}], coords) 649 writer = CifWriter(struct) 650 ans = """# generated using pymatgen 651data_Si1.5N0.5 652_symmetry_space_group_name_H-M 'P 1' 653_cell_length_a 3.84019793 654_cell_length_b 3.84019899 655_cell_length_c 3.84019793 656_cell_angle_alpha 119.99999086 657_cell_angle_beta 90.00000000 658_cell_angle_gamma 60.00000914 659_symmetry_Int_Tables_number 1 660_chemical_formula_structural Si1.5N0.5 661_chemical_formula_sum 'Si1.5 N0.5' 662_cell_volume 40.04479464 663_cell_formula_units_Z 1 664loop_ 665 _symmetry_equiv_pos_site_id 666 _symmetry_equiv_pos_as_xyz 667 1 'x, y, z' 668loop_ 669 _atom_site_type_symbol 670 _atom_site_label 671 _atom_site_symmetry_multiplicity 672 _atom_site_fract_x 673 _atom_site_fract_y 674 _atom_site_fract_z 675 _atom_site_occupancy 676 Si Si0 1 0.00000000 0.00000000 0.00000000 1 677 Si Si1 1 0.75000000 0.50000000 0.75000000 0.5 678 N N2 1 0.75000000 0.50000000 0.75000000 0.5""" 679 680 for l1, l2 in zip(str(writer).split("\n"), ans.split("\n")): 681 self.assertEqual(l1.strip(), l2.strip()) 682 683 def test_cifwrite_without_refinement(self): 684 si2 = Structure.from_file(self.TEST_FILES_DIR / "abinit" / "si.cif") 685 writer = CifWriter(si2, symprec=1e-3, significant_figures=10, refine_struct=False) 686 s = str(writer) 687 assert "Fd-3m" in s 688 same_si2 = CifParser.from_string(s).get_structures()[0] 689 assert len(si2) == len(same_si2) 690 691 def test_specie_cifwriter(self): 692 si4 = Species("Si", 4) 693 si3 = Species("Si", 3) 694 n = DummySpecies("X", -3) 695 coords = [] 696 coords.append(np.array([0.5, 0.5, 0.5])) 697 coords.append(np.array([0.75, 0.5, 0.75])) 698 coords.append(np.array([0, 0, 0])) 699 lattice = Lattice( 700 np.array( 701 [ 702 [3.8401979337, 0.00, 0.00], 703 [1.9200989668, 3.3257101909, 0.00], 704 [0.00, -2.2171384943, 3.1355090603], 705 ] 706 ) 707 ) 708 struct = Structure(lattice, [n, {si3: 0.5, n: 0.5}, si4], coords) 709 writer = CifWriter(struct) 710 ans = """# generated using pymatgen 711data_X1.5Si1.5 712_symmetry_space_group_name_H-M 'P 1' 713_cell_length_a 3.84019793 714_cell_length_b 3.84019899 715_cell_length_c 3.84019793 716_cell_angle_alpha 119.99999086 717_cell_angle_beta 90.00000000 718_cell_angle_gamma 60.00000914 719_symmetry_Int_Tables_number 1 720_chemical_formula_structural X1.5Si1.5 721_chemical_formula_sum 'X1.5 Si1.5' 722_cell_volume 40.04479464 723_cell_formula_units_Z 1 724loop_ 725 _symmetry_equiv_pos_site_id 726 _symmetry_equiv_pos_as_xyz 727 1 'x, y, z' 728loop_ 729 _atom_type_symbol 730 _atom_type_oxidation_number 731 X3- -3.0 732 Si3+ 3.0 733 Si4+ 4.0 734loop_ 735 _atom_site_type_symbol 736 _atom_site_label 737 _atom_site_symmetry_multiplicity 738 _atom_site_fract_x 739 _atom_site_fract_y 740 _atom_site_fract_z 741 _atom_site_occupancy 742 X3- X0 1 0.50000000 0.50000000 0.50000000 1 743 X3- X1 1 0.75000000 0.50000000 0.75000000 0.5 744 Si3+ Si2 1 0.75000000 0.50000000 0.75000000 0.5 745 Si4+ Si3 1 0.00000000 0.00000000 0.00000000 1 746""" 747 for l1, l2 in zip(str(writer).split("\n"), ans.split("\n")): 748 self.assertEqual(l1.strip(), l2.strip()) 749 750 # test that mixed valence works properly 751 s2 = Structure.from_str(ans, "cif") 752 self.assertEqual(struct.composition, s2.composition) 753 754 def test_primes(self): 755 with warnings.catch_warnings(): 756 warnings.simplefilter("ignore") 757 parser = CifParser(self.TEST_FILES_DIR / "C26H16BeN2O2S2.cif") 758 for s in parser.get_structures(False): 759 self.assertEqual(s.composition, 8 * Composition("C26H16BeN2O2S2")) 760 761 def test_missing_atom_site_type_with_oxistates(self): 762 with warnings.catch_warnings(): 763 warnings.simplefilter("ignore") 764 parser = CifParser(self.TEST_FILES_DIR / "P24Ru4H252C296S24N16.cif") 765 c = Composition({"S0+": 24, "Ru0+": 4, "H0+": 252, "C0+": 296, "N0+": 16, "P0+": 24}) 766 for s in parser.get_structures(False): 767 self.assertEqual(s.composition, c) 768 769 def test_no_coords_or_species(self): 770 with warnings.catch_warnings(): 771 warnings.simplefilter("ignore") 772 string = """#generated using pymatgen 773 data_Si1.5N1.5 774 _symmetry_space_group_name_H-M 'P 1' 775 _cell_length_a 3.84019793 776 _cell_length_b 3.84019899 777 _cell_length_c 3.84019793 778 _cell_angle_alpha 119.99999086 779 _cell_angle_beta 90.00000000 780 _cell_angle_gamma 60.00000914 781 _symmetry_Int_Tables_number 1 782 _chemical_formula_structural Si1.5N1.5 783 _chemical_formula_sum 'Si1.5 N1.5' 784 _cell_volume 40.0447946443 785 _cell_formula_units_Z 0 786 loop_ 787 _symmetry_equiv_pos_site_id 788 _symmetry_equiv_pos_as_xyz 789 1 'x, y, z' 790 loop_ 791 _atom_type_symbol 792 _atom_type_oxidation_number 793 Si3+ 3.0 794 Si4+ 4.0 795 N3- -3.0 796 loop_ 797 _atom_site_type_symbol 798 _atom_site_label 799 _atom_site_symmetry_multiplicity 800 _atom_site_fract_x 801 _atom_site_fract_y 802 _atom_site_fract_z 803 _atom_site_occupancy 804 ? ? ? ? ? ? ? 805 """ 806 parser = CifParser.from_string(string) 807 self.assertRaises(ValueError, parser.get_structures) 808 809 def test_get_lattice_from_lattice_type(self): 810 cif_structure = """#generated using pymatgen 811data_FePO4 812_symmetry_space_group_name_H-M Pnma 813_cell_length_a 10.41176687 814_cell_length_b 6.06717188 815_cell_length_c 4.75948954 816_chemical_formula_structural FePO4 817_chemical_formula_sum 'Fe4 P4 O16' 818_cell_volume 300.65685512 819_cell_formula_units_Z 4 820_symmetry_cell_setting Orthorhombic 821loop_ 822 _symmetry_equiv_pos_site_id 823 _symmetry_equiv_pos_as_xyz 824 1 'x, y, z' 825loop_ 826 _atom_site_type_symbol 827 _atom_site_label 828 _atom_site_symmetry_multiplicity 829 _atom_site_fract_x 830 _atom_site_fract_y 831 _atom_site_fract_z 832 _atom_site_occupancy 833 Fe Fe1 1 0.218728 0.750000 0.474867 1 834 Fe Fe2 1 0.281272 0.250000 0.974867 1 835 Fe Fe3 1 0.718728 0.750000 0.025133 1 836 Fe Fe4 1 0.781272 0.250000 0.525133 1 837 P P5 1 0.094613 0.250000 0.418243 1 838 P P6 1 0.405387 0.750000 0.918243 1 839 P P7 1 0.594613 0.250000 0.081757 1 840 P P8 1 0.905387 0.750000 0.581757 1 841 O O9 1 0.043372 0.750000 0.707138 1 842 O O10 1 0.096642 0.250000 0.741320 1 843 O O11 1 0.165710 0.046072 0.285384 1 844 O O12 1 0.165710 0.453928 0.285384 1 845 O O13 1 0.334290 0.546072 0.785384 1 846 O O14 1 0.334290 0.953928 0.785384 1 847 O O15 1 0.403358 0.750000 0.241320 1 848 O O16 1 0.456628 0.250000 0.207138 1 849 O O17 1 0.543372 0.750000 0.792862 1 850 O O18 1 0.596642 0.250000 0.758680 1 851 O O19 1 0.665710 0.046072 0.214616 1 852 O O20 1 0.665710 0.453928 0.214616 1 853 O O21 1 0.834290 0.546072 0.714616 1 854 O O22 1 0.834290 0.953928 0.714616 1 855 O O23 1 0.903358 0.750000 0.258680 1 856 O O24 1 0.956628 0.250000 0.292862 1 857 858""" 859 cp = CifParser.from_string(cif_structure) 860 s_test = cp.get_structures(False)[0] 861 filepath = self.TEST_FILES_DIR / "POSCAR" 862 poscar = Poscar.from_file(filepath) 863 s_ref = poscar.structure 864 865 sm = StructureMatcher(stol=0.05, ltol=0.01, angle_tol=0.1) 866 self.assertTrue(sm.fit(s_ref, s_test)) 867 868 def test_empty(self): 869 # single line 870 cb = CifBlock.from_string("data_mwe\nloop_\n_tag\n ''") 871 self.assertEqual(cb.data["_tag"][0], "") 872 873 # multi line 874 cb = CifBlock.from_string("data_mwe\nloop_\n_tag\n;\n;") 875 self.assertEqual(cb.data["_tag"][0], "") 876 877 cb2 = CifBlock.from_string(str(cb)) 878 self.assertEqual(cb, cb2) 879 880 def test_bad_cif(self): 881 with warnings.catch_warnings(): 882 warnings.simplefilter("ignore") 883 f = self.TEST_FILES_DIR / "bad_occu.cif" 884 p = CifParser(f) 885 self.assertRaises(ValueError, p.get_structures) 886 p = CifParser(f, occupancy_tolerance=2) 887 s = p.get_structures()[0] 888 self.assertAlmostEqual(s[0].species["Al3+"], 0.5) 889 890 def test_one_line_symm(self): 891 with warnings.catch_warnings(): 892 warnings.simplefilter("ignore") 893 f = self.TEST_FILES_DIR / "OneLineSymmP1.cif" 894 p = CifParser(f) 895 s = p.get_structures()[0] 896 self.assertEqual(s.formula, "Ga4 Pb2 O8") 897 898 def test_no_symmops(self): 899 with warnings.catch_warnings(): 900 warnings.simplefilter("ignore") 901 f = self.TEST_FILES_DIR / "nosymm.cif" 902 p = CifParser(f) 903 s = p.get_structures()[0] 904 self.assertEqual(s.formula, "H96 C60 O8") 905 906 def test_dot_positions(self): 907 f = self.TEST_FILES_DIR / "ICSD59959.cif" 908 p = CifParser(f) 909 s = p.get_structures()[0] 910 self.assertEqual(s.formula, "K1 Mn1 F3") 911 912 def test_replacing_finite_precision_frac_coords(self): 913 f = self.TEST_FILES_DIR / "cif_finite_precision_frac_coord_error.cif" 914 with warnings.catch_warnings(): 915 p = CifParser(f) 916 s = p.get_structures()[0] 917 self.assertEqual(str(s.composition), "N5+24") 918 self.assertIn( 919 "Some fractional co-ordinates rounded to ideal " "values to avoid issues with finite precision.", 920 p.warnings, 921 ) 922 923 def test_empty_deque(self): 924 s = """data_1526655 925_journal_name_full 926_space_group_IT_number 227 927_symmetry_space_group_name_Hall 'F 4d 2 3 -1d' 928_symmetry_space_group_name_H-M 'F d -3 m :1' 929_cell_angle_alpha 90 930_cell_angle_beta 90 931_cell_angle_gamma 90 932_cell_formula_units_Z 8 933_cell_length_a 5.381 934_cell_length_b 5.381 935_cell_length_c 5.381 936_cell_volume 155.808 937loop_ 938_atom_site_label 939_atom_site_type_symbol 940_atom_site_fract_x 941_atom_site_fract_y 942_atom_site_fract_z 943_atom_site_occupancy 944_atom_site_U_iso_or_equiv 945Si1 Si 0 0 0 1 0.0 946_iucr_refine_fcf_details 947; 948data_symmetries 949loop_ 950 _space_group_symop_id 951 _space_group_symop_operation_xyz 952 1 x,y,z 953 2 -x+1/2,y+1/2,-z+1/2 954 3 -x,-y,-z 955 4 x-1/2,-y-1/2,z-1/2 956;""" 957 p = CifParser.from_string(s) 958 self.assertEqual(p.get_structures()[0].formula, "Si1") 959 cif = """ 960data_1526655 961_journal_name_full 962_space_group_IT_number 227 963_symmetry_space_group_name_Hall 'F 4d 2 3 -1d' 964_symmetry_space_group_name_H-M 'F d -3 m :1' 965_cell_angle_alpha 90 966_cell_angle_beta 90 967_cell_angle_gamma 90 968_cell_formula_units_Z 8 969_cell_length_a 5.381 970_cell_length_b 5.381 971_cell_length_c 5.381 972_cell_volume 155.808 973_iucr_refine_fcf_details 974; 975data_symmetries 976Some arbitrary multiline string 977; 978loop_ 979_atom_site_label 980_atom_site_type_symbol 981_atom_site_fract_x 982_atom_site_fract_y 983_atom_site_fract_z 984_atom_site_occupancy 985_atom_site_U_iso_or_equiv 986Si1 Si 0 0 0 1 0.0 987""" 988 p = CifParser.from_string(cif) 989 self.assertRaises(ValueError, p.get_structures) 990 991 992class MagCifTest(PymatgenTest): 993 def setUp(self): 994 warnings.filterwarnings("ignore") 995 self.mcif = CifParser(self.TEST_FILES_DIR / "magnetic.example.NiO.mcif") 996 self.mcif_ncl = CifParser(self.TEST_FILES_DIR / "magnetic.ncl.example.GdB4.mcif") 997 self.mcif_incom = CifParser(self.TEST_FILES_DIR / "magnetic.incommensurate.example.Cr.mcif") 998 self.mcif_disord = CifParser(self.TEST_FILES_DIR / "magnetic.disordered.example.CuMnO2.mcif") 999 self.mcif_ncl2 = CifParser(self.TEST_FILES_DIR / "Mn3Ge_IR2.mcif") 1000 1001 def tearDown(self): 1002 warnings.simplefilter("default") 1003 1004 def test_mcif_detection(self): 1005 self.assertTrue(self.mcif.feature_flags["magcif"]) 1006 self.assertTrue(self.mcif_ncl.feature_flags["magcif"]) 1007 self.assertTrue(self.mcif_incom.feature_flags["magcif"]) 1008 self.assertTrue(self.mcif_disord.feature_flags["magcif"]) 1009 self.assertFalse(self.mcif.feature_flags["magcif_incommensurate"]) 1010 self.assertFalse(self.mcif_ncl.feature_flags["magcif_incommensurate"]) 1011 self.assertTrue(self.mcif_incom.feature_flags["magcif_incommensurate"]) 1012 self.assertFalse(self.mcif_disord.feature_flags["magcif_incommensurate"]) 1013 1014 def test_get_structures(self): 1015 # incommensurate structures not currently supported 1016 self.assertRaises(NotImplementedError, self.mcif_incom.get_structures) 1017 1018 # disordered magnetic structures not currently supported 1019 self.assertRaises(NotImplementedError, self.mcif_disord.get_structures) 1020 1021 # taken from self.mcif_ncl, removing explicit magnetic symmops 1022 # so that MagneticSymmetryGroup() has to be invoked 1023 magcifstr = """ 1024data_5yOhtAoR 1025 1026_space_group.magn_name_BNS "P 4/m' b' m' " 1027_cell_length_a 7.1316 1028_cell_length_b 7.1316 1029_cell_length_c 4.0505 1030_cell_angle_alpha 90.00 1031_cell_angle_beta 90.00 1032_cell_angle_gamma 90.00 1033 1034loop_ 1035_atom_site_label 1036_atom_site_type_symbol 1037_atom_site_fract_x 1038_atom_site_fract_y 1039_atom_site_fract_z 1040_atom_site_occupancy 1041Gd1 Gd 0.31746 0.81746 0.00000 1 1042B1 B 0.00000 0.00000 0.20290 1 1043B2 B 0.17590 0.03800 0.50000 1 1044B3 B 0.08670 0.58670 0.50000 1 1045 1046loop_ 1047_atom_site_moment_label 1048_atom_site_moment_crystalaxis_x 1049_atom_site_moment_crystalaxis_y 1050_atom_site_moment_crystalaxis_z 1051Gd1 5.05 5.05 0.0""" 1052 1053 s = self.mcif.get_structures(primitive=False)[0] 1054 self.assertEqual(s.formula, "Ni32 O32") 1055 self.assertTrue(Magmom.are_collinear(s.site_properties["magmom"])) 1056 1057 # example with non-collinear spin 1058 s_ncl = self.mcif_ncl.get_structures(primitive=False)[0] 1059 s_ncl_from_msg = CifParser.from_string(magcifstr).get_structures(primitive=False)[0] 1060 self.assertEqual(s_ncl.formula, "Gd4 B16") 1061 self.assertFalse(Magmom.are_collinear(s_ncl.site_properties["magmom"])) 1062 1063 self.assertTrue(s_ncl.matches(s_ncl_from_msg)) 1064 1065 def test_write(self): 1066 cw_ref_string = """# generated using pymatgen 1067data_GdB4 1068_symmetry_space_group_name_H-M 'P 1' 1069_cell_length_a 7.13160000 1070_cell_length_b 7.13160000 1071_cell_length_c 4.05050000 1072_cell_angle_alpha 90.00000000 1073_cell_angle_beta 90.00000000 1074_cell_angle_gamma 90.00000000 1075_symmetry_Int_Tables_number 1 1076_chemical_formula_structural GdB4 1077_chemical_formula_sum 'Gd4 B16' 1078_cell_volume 206.00729003 1079_cell_formula_units_Z 4 1080loop_ 1081 _symmetry_equiv_pos_site_id 1082 _symmetry_equiv_pos_as_xyz 1083 1 'x, y, z' 1084loop_ 1085 _atom_site_type_symbol 1086 _atom_site_label 1087 _atom_site_symmetry_multiplicity 1088 _atom_site_fract_x 1089 _atom_site_fract_y 1090 _atom_site_fract_z 1091 _atom_site_occupancy 1092 Gd Gd0 1 0.31746000 0.81746000 0.00000000 1.0 1093 Gd Gd1 1 0.18254000 0.31746000 0.00000000 1.0 1094 Gd Gd2 1 0.81746000 0.68254000 0.00000000 1.0 1095 Gd Gd3 1 0.68254000 0.18254000 0.00000000 1.0 1096 B B4 1 0.00000000 0.00000000 0.20290000 1.0 1097 B B5 1 0.50000000 0.50000000 0.79710000 1.0 1098 B B6 1 0.00000000 0.00000000 0.79710000 1.0 1099 B B7 1 0.50000000 0.50000000 0.20290000 1.0 1100 B B8 1 0.17590000 0.03800000 0.50000000 1.0 1101 B B9 1 0.96200000 0.17590000 0.50000000 1.0 1102 B B10 1 0.03800000 0.82410000 0.50000000 1.0 1103 B B11 1 0.67590000 0.46200000 0.50000000 1.0 1104 B B12 1 0.32410000 0.53800000 0.50000000 1.0 1105 B B13 1 0.82410000 0.96200000 0.50000000 1.0 1106 B B14 1 0.53800000 0.67590000 0.50000000 1.0 1107 B B15 1 0.46200000 0.32410000 0.50000000 1.0 1108 B B16 1 0.08670000 0.58670000 0.50000000 1.0 1109 B B17 1 0.41330000 0.08670000 0.50000000 1.0 1110 B B18 1 0.58670000 0.91330000 0.50000000 1.0 1111 B B19 1 0.91330000 0.41330000 0.50000000 1.0 1112loop_ 1113 _atom_site_moment_label 1114 _atom_site_moment_crystalaxis_x 1115 _atom_site_moment_crystalaxis_y 1116 _atom_site_moment_crystalaxis_z 1117 Gd0 5.05000000 5.05000000 0.00000000 1118 Gd1 -5.05000000 5.05000000 0.00000000 1119 Gd2 5.05000000 -5.05000000 0.00000000 1120 Gd3 -5.05000000 -5.05000000 0.00000000 1121""" 1122 s_ncl = self.mcif_ncl.get_structures(primitive=False)[0] 1123 1124 cw = CifWriter(s_ncl, write_magmoms=True) 1125 self.assertEqual(cw.__str__(), cw_ref_string) 1126 1127 # from list-type magmoms 1128 list_magmoms = [list(m) for m in s_ncl.site_properties["magmom"]] 1129 1130 # float magmoms (magnitude only) 1131 float_magmoms = [float(m) for m in s_ncl.site_properties["magmom"]] 1132 1133 s_ncl.add_site_property("magmom", list_magmoms) 1134 cw = CifWriter(s_ncl, write_magmoms=True) 1135 self.assertEqual(cw.__str__(), cw_ref_string) 1136 1137 s_ncl.add_site_property("magmom", float_magmoms) 1138 cw = CifWriter(s_ncl, write_magmoms=True) 1139 1140 cw_ref_string_magnitudes = """# generated using pymatgen 1141data_GdB4 1142_symmetry_space_group_name_H-M 'P 1' 1143_cell_length_a 7.13160000 1144_cell_length_b 7.13160000 1145_cell_length_c 4.05050000 1146_cell_angle_alpha 90.00000000 1147_cell_angle_beta 90.00000000 1148_cell_angle_gamma 90.00000000 1149_symmetry_Int_Tables_number 1 1150_chemical_formula_structural GdB4 1151_chemical_formula_sum 'Gd4 B16' 1152_cell_volume 206.00729003 1153_cell_formula_units_Z 4 1154loop_ 1155 _symmetry_equiv_pos_site_id 1156 _symmetry_equiv_pos_as_xyz 1157 1 'x, y, z' 1158loop_ 1159 _atom_site_type_symbol 1160 _atom_site_label 1161 _atom_site_symmetry_multiplicity 1162 _atom_site_fract_x 1163 _atom_site_fract_y 1164 _atom_site_fract_z 1165 _atom_site_occupancy 1166 Gd Gd0 1 0.31746000 0.81746000 0.00000000 1.0 1167 Gd Gd1 1 0.18254000 0.31746000 0.00000000 1.0 1168 Gd Gd2 1 0.81746000 0.68254000 0.00000000 1.0 1169 Gd Gd3 1 0.68254000 0.18254000 0.00000000 1.0 1170 B B4 1 0.00000000 0.00000000 0.20290000 1.0 1171 B B5 1 0.50000000 0.50000000 0.79710000 1.0 1172 B B6 1 0.00000000 0.00000000 0.79710000 1.0 1173 B B7 1 0.50000000 0.50000000 0.20290000 1.0 1174 B B8 1 0.17590000 0.03800000 0.50000000 1.0 1175 B B9 1 0.96200000 0.17590000 0.50000000 1.0 1176 B B10 1 0.03800000 0.82410000 0.50000000 1.0 1177 B B11 1 0.67590000 0.46200000 0.50000000 1.0 1178 B B12 1 0.32410000 0.53800000 0.50000000 1.0 1179 B B13 1 0.82410000 0.96200000 0.50000000 1.0 1180 B B14 1 0.53800000 0.67590000 0.50000000 1.0 1181 B B15 1 0.46200000 0.32410000 0.50000000 1.0 1182 B B16 1 0.08670000 0.58670000 0.50000000 1.0 1183 B B17 1 0.41330000 0.08670000 0.50000000 1.0 1184 B B18 1 0.58670000 0.91330000 0.50000000 1.0 1185 B B19 1 0.91330000 0.41330000 0.50000000 1.0 1186loop_ 1187 _atom_site_moment_label 1188 _atom_site_moment_crystalaxis_x 1189 _atom_site_moment_crystalaxis_y 1190 _atom_site_moment_crystalaxis_z 1191 Gd0 0.00000000 0.00000000 7.14177849 1192 Gd1 0.00000000 0.00000000 7.14177849 1193 Gd2 0.00000000 0.00000000 -7.14177849 1194 Gd3 0.00000000 0.00000000 -7.14177849 1195""" 1196 self.assertEqual(cw.__str__().strip(), cw_ref_string_magnitudes.strip()) 1197 # test we're getting correct magmoms in ncl case 1198 s_ncl2 = self.mcif_ncl2.get_structures()[0] 1199 list_magmoms = [list(m) for m in s_ncl2.site_properties["magmom"]] 1200 self.assertEqual(list_magmoms[0][0], 0.0) 1201 self.assertAlmostEqual(list_magmoms[0][1], 5.9160793408726366) 1202 self.assertAlmostEqual(list_magmoms[1][0], -5.1234749999999991) 1203 self.assertAlmostEqual(list_magmoms[1][1], 2.9580396704363183) 1204 1205 # test creating an structure without oxidation state doesn't raise errors 1206 s_manual = Structure(Lattice.cubic(4.2), ["Cs", "Cl"], [[0, 0, 0], [0.5, 0.5, 0.5]]) 1207 s_manual.add_spin_by_site([1, -1]) 1208 cw = CifWriter(s_manual, write_magmoms=True) 1209 1210 # check oxidation state 1211 cw_manual_oxi_string = """# generated using pymatgen 1212data_CsCl 1213_symmetry_space_group_name_H-M 'P 1' 1214_cell_length_a 4.20000000 1215_cell_length_b 4.20000000 1216_cell_length_c 4.20000000 1217_cell_angle_alpha 90.00000000 1218_cell_angle_beta 90.00000000 1219_cell_angle_gamma 90.00000000 1220_symmetry_Int_Tables_number 1 1221_chemical_formula_structural CsCl 1222_chemical_formula_sum 'Cs1 Cl1' 1223_cell_volume 74.08800000 1224_cell_formula_units_Z 1 1225loop_ 1226 _symmetry_equiv_pos_site_id 1227 _symmetry_equiv_pos_as_xyz 1228 1 'x, y, z' 1229loop_ 1230 _atom_type_symbol 1231 _atom_type_oxidation_number 1232 Cs+ 1.0 1233 Cl+ 1.0 1234loop_ 1235 _atom_site_type_symbol 1236 _atom_site_label 1237 _atom_site_symmetry_multiplicity 1238 _atom_site_fract_x 1239 _atom_site_fract_y 1240 _atom_site_fract_z 1241 _atom_site_occupancy 1242 Cs+ Cs0 1 0.00000000 0.00000000 0.00000000 1 1243 Cl+ Cl1 1 0.50000000 0.50000000 0.50000000 1 1244loop_ 1245 _atom_site_moment_label 1246 _atom_site_moment_crystalaxis_x 1247 _atom_site_moment_crystalaxis_y 1248 _atom_site_moment_crystalaxis_z 1249""" 1250 s_manual.add_oxidation_state_by_site([1, 1]) 1251 cw = CifWriter(s_manual, write_magmoms=True) 1252 self.assertEqual(cw.__str__(), cw_manual_oxi_string) 1253 1254 @unittest.skipIf(pybtex is None, "pybtex not present") 1255 def test_bibtex(self): 1256 ref_bibtex_string = """@article{cifref0, 1257 author = "Blanco, J.A.", 1258 journal = "PHYSICAL REVIEW B", 1259 volume = "73", 1260 year = "2006", 1261 pages = "?--?" 1262} 1263""" 1264 self.assertEqual(self.mcif_ncl.get_bibtex_string(), ref_bibtex_string) 1265 1266 1267if __name__ == "__main__": 1268 unittest.main() 1269