def perturb_structure( structure: Structure, center: List[float], cutoff: float) -> Tuple[Structure, Tuple[PerturbedSite, ...]]: """ structure perturbation Args: structure: pmg Structure class object center: Fractional coordinates of a central position. cutoff: Radius of a sphere in which atoms are perturbed. """ p_structure = structure.copy() perturbed_sites = [] cartesian_coords = structure.lattice.get_cartesian_coords(center) neighboring_atoms: List[PeriodicNeighbor] = structure.get_sites_in_sphere( pt=cartesian_coords, r=cutoff, include_index=True) for p_neighbor in neighboring_atoms: vector, distance = random_3d_vector(defaults.displace_distance) p_structure.translate_sites(p_neighbor.index, vector, frac_coords=False) s = PerturbedSite(element=str(p_neighbor.specie), distance=p_neighbor.nn_distance, initial_coords=tuple(p_neighbor.frac_coords), perturbed_coords=tuple( p_structure[p_neighbor.index].frac_coords), displacement=distance) perturbed_sites.append(s) return p_structure, tuple(perturbed_sites)
def __init__( self, initial_structure: Structure, task: Task, kpt_mode: Union[KpointsMode, str, None] = None, # None for default kpt_density: Optional[float] = None, # in Å gamma_centered: Optional[bool] = None, # Vasp definition only_even_num_kpts: bool = False, # If ceil kpt numbers to be even. num_kpt_factor: Optional[int] = None, # Set NKRED to this as well. band_ref_dist: float = defaults.band_mesh_distance, symprec: float = defaults.symmetry_length_tolerance, angle_tolerance: float = defaults.symmetry_angle_tolerance, is_magnetization: bool = False): # Whether the system is magnetic. if kpt_mode: kpt_mode = KpointsMode.from_string(str(kpt_mode)) self._initial_structure = initial_structure.copy() self._task = task self._kpt_density = kpt_density or defaults.kpoint_density logger.info(f"kpoint density is set to {self._kpt_density}") self._is_magnetization = is_magnetization self._num_kpt_factor = num_kpt_factor or self._task.default_kpt_factor if self._num_kpt_factor != 1: logger.info(f"kpoint factor is set to {self._num_kpt_factor}") self._symmetrizer = \ StructureSymmetrizer(structure=self._initial_structure, symprec=symprec, angle_tolerance=angle_tolerance, band_mesh_distance=band_ref_dist, time_reversal=(not self._is_magnetization)) # overwrite options fixed by other options self._gamma_centered = task.requisite_gamma_centered or gamma_centered self._adjust_only_even_num_kpts(only_even_num_kpts) self._adjust_kpt_mode(kpt_mode)
def refine_defect_structure(structure: Structure, anchor_atom_index: int = None, anchor_atom_coords: np.ndarray = None): symmetrizer = StructureSymmetrizer(structure, defaults.symmetry_length_tolerance, defaults.symmetry_angle_tolerance) result = structure.copy() spglib_data = symmetrizer.spglib_sym_data origin_shift = spglib_data["origin_shift"] inv_trans_mat = inv(spglib_data["transformation_matrix"]) coords = spglib_data["std_positions"] # This transformation is key for refinement. new_coords = np.array([ np.dot(inv_trans_mat, (coords[i] - origin_shift)) for i in range(len(result)) ]) if anchor_atom_index: offset = new_coords[anchor_atom_index] - anchor_atom_coords new_coords = new_coords - offset for i, coords in zip(result, new_coords): i.frac_coords = coords % 1 if result != structure: logger.info(f"Structure is refined to the one with point group " f"{symmetrizer.point_group}.") return result else: logger.info(f"Refined structure is same as before, so do nothing.")
def get_111_struct(self): lattice = Lattice( self.lattconst * np.identity(3)) species = [self.eltA, self.eltB, self.eltC, self.eltC, self.eltC] coords = [[0., 0., 0.], [0.5, 0.5, 0.5], [0.5, 0.5, 0.], [0.5, 0., 0.5], [0., 0.5, 0.5]] struct = Structure(lattice, species, coords, coords_are_cartesian=False) return struct.copy()
def test_coloring_with_fixed_species(): lattice = Lattice(3.945 * np.eye(3)) species = ["Sr", "Ti", "O", "O", "O"] frac_coords = np.array([[0, 0, 0], [0.5, 0.5, 0.5], [0.0, 0.5, 0.5], [0.5, 0.0, 0.5], [0.5, 0.5, 0.0]]) aristo = Structure(lattice, species, frac_coords) base_structure = aristo.copy() base_structure.remove_species(["Sr", "Ti"]) additional_species = species[:2] additional_frac_coords = frac_coords[:2] mapping_color_species = [DummySpecie("X"), "O"] num_types = len(mapping_color_species) index = 2 se = StructureEnumerator( base_structure, index, num_types, mapping_color_species=mapping_color_species, color_exchange=False, leave_superperiodic=False, use_all_colors=False, ) list_dstructs = se.generate(additional_species=additional_species, additional_frac_coords=additional_frac_coords) # with base_site_constraints mapping_color_species2 = [DummySpecie("X"), "O", "Sr", "Ti"] num_types2 = len(mapping_color_species2) base_site_constraints = [ [2], # Sr site [3], # Cu site [0, 1], # O or V [0, 1], # O or V [0, 1], # O or V ] se2 = StructureEnumerator( aristo, index, num_types2, mapping_color_species=mapping_color_species2, base_site_constraints=base_site_constraints, color_exchange=False, leave_superperiodic=False, use_all_colors=False, ) list_dstructs2 = se2.generate() # check uniqueness by StructureMatcher stm = StructureMatcher(ltol=1e-4, stol=1e-4) grouped = stm.group_structures(list_dstructs + list_dstructs2) assert len(grouped) == len(list_dstructs) assert all([(len(matched) == 2) for matched in grouped])
def structure(self): strained_lattice = perform_strain( self.base.lattice.matrix, self.strain_tensor) species, coords = [], [] for ind, perturb in enumerate(self.atomic_perturbations): species.append( self.base.species[ind]) coords.append( self.base.cart_coords[ind] + np.array(perturb) ) struct = Structure(strained_lattice, species, coords, coords_are_cartesian=True) return struct.copy()
def test_coloring_with_fixed_species(sto_perovskite: Structure): base_structure = sto_perovskite.copy() base_structure.remove_species(["Sr", "Ti"]) additional_species = sto_perovskite.species[:2] additional_frac_coords = sto_perovskite.frac_coords[:2] mapping_color_species = [DummySpecie("X"), "O"] num_types = len(mapping_color_species) index = 2 se = StructureEnumerator( base_structure, index, num_types, mapping_color_species=mapping_color_species, color_exchange=False, remove_superperiodic=True, remove_incomplete=False, ) list_dstructs = se.generate(additional_species=additional_species, additional_frac_coords=additional_frac_coords) # with base_site_constraints mapping_color_species2 = [DummySpecie("X"), "O", "Sr", "Ti"] num_types2 = len(mapping_color_species2) base_site_constraints = [ [2], # Sr site [3], # Cu site [0, 1], # O or V [0, 1], # O or V [0, 1], # O or V ] se2 = StructureEnumerator( sto_perovskite, index, num_types2, mapping_color_species=mapping_color_species2, base_site_constraints=base_site_constraints, color_exchange=False, remove_superperiodic=True, remove_incomplete=False, ) list_dstructs2 = se2.generate() # check uniqueness by StructureMatcher stm = StructureMatcher(ltol=1e-4, stol=1e-4) grouped = stm.group_structures(list_dstructs + list_dstructs2) # type: ignore assert len(grouped) == len(list_dstructs) assert all([(len(matched) == 2) for matched in grouped])
def __init__(self, perfect: Structure, initial: Structure, final: Structure, symprec: float, dist_tol: float, neighbor_cutoff_factor: float = None): self.cutoff = neighbor_cutoff_factor or defaults.cutoff_distance_factor self.symprec = symprec self.perfect, self.initial, self.final = perfect, initial, final self.dist_tol = dist_tol assert perfect.lattice == initial.lattice == final.lattice self.lattice = perfect.lattice self._orig_comp = DefectStructureComparator(final, perfect, dist_tol) self._orig_center = self._orig_comp.defect_center_coord self._calc_drift() self.shifted_final = final.copy() self.center = tuple(self._orig_center - self._drift_vector) for site in self.shifted_final: site.frac_coords -= np.array(self._drift_vector) self.comp_w_perf = DefectStructureComparator(self.shifted_final, perfect, dist_tol) self.comp_w_init = DefectStructureComparator(self.shifted_final, initial, dist_tol) self.defect_structure_info = DefectStructureInfo( shifted_final_structure=self.shifted_final, initial_site_sym=self.initial_site_sym, final_site_sym=self.final_site_sym, site_diff=self.comp_w_perf.make_site_diff(), site_diff_from_initial=self.comp_w_init.make_site_diff(), symprec=symprec, dist_tol=dist_tol, anchor_atom_idx=self._anchor_atom_idx, neighbor_atom_indices=self._neighbor_atom_indices, neighbor_cutoff_factor=self.cutoff, drift_vector=self._drift_vector, drift_dist=self._drift_distance, center=self.center, displacements=self.calc_displacements())
def __init__(self, structure: Structure, symprec: float = defaults.symmetry_length_tolerance, angle_tolerance: float = defaults.symmetry_angle_tolerance, time_reversal: bool = True, band_mesh_distance: float = defaults.band_mesh_distance): """Get full information of seekpath band path. Note: site properties such as magmom are removed. The structures of aP (SG:1, 2), mC (5, 8, 9, 12, 15) and oA (38, 39, 40, 41) can be different between spglib and seekpath. see Y. Hinuma et al. Comput. Mater. Sci. 128 (2017) 140–184 -- spglib mC 6.048759 -3.479491 0.000000 6.048759 3.479491 0.000000 -4.030758 0.000000 6.044512 -- seekpath mC 6.048759 3.479491 0.000000 -6.048759 3.479491 0.000000 -4.030758 0.000000 6.044512 """ self.structure = structure.copy() if structure.site_properties: logger.warning( f"Site property {structure.site_properties.keys()} " f"removed in primitive and conventional structures.") self.symprec = symprec self.angle_tolerance = angle_tolerance self.time_reversal = time_reversal self.ref_distance = band_mesh_distance lattice_matrix = structure.lattice.matrix positions = structure.frac_coords.tolist() atomic_numbers = [i.specie.number for i in structure.sites] self.cell = (lattice_matrix, positions, atomic_numbers) # evaluated lazily self._spglib_sym_data = None self._conventional = None self._primitive = None self._second_primitive = None self._seekpath_data = None self._band_primitive = None self._irreducible_kpoints = None
def quick_view( structure: Structure, *args, conventional: bool = False, supercell: list = [1, 1, 1], symprec: float = 0.01, angle_tolerance: float = 5.0 ) -> JsmolView: """A function to visualize pymatgen Structure objects in jupyter notebook using jupyter_jsmol package. Args: structure: pymatgen Structure object. *args: Extra arguments for JSmol's load command. Eg. "{2 2 2}", "packed" conventional: use conventional cell. Defaults to False. supercell: can be used to make supercells with pymatgen.Structure.make_supercell method. symprec: If not none, finds the symmetry of the structure and writes the cif with symmetry information. Passes symprec to the SpacegroupAnalyzer. angle_tolerance: Angle tolerance for symmetry finding. Passes angle_tolerance to the SpacegroupAnalyzer. Used only if symprec is not None. Returns: A jupyter widget object. """ s = structure.copy() if conventional: spga = SpacegroupAnalyzer(s, symprec=symprec, angle_tolerance=angle_tolerance) s = spga.get_conventional_standard_structure() cif = CifWriter( s, symprec=symprec, angle_tolerance=angle_tolerance, refine_struct=False ) supercell_str = "{" + " ".join(map(str, supercell)) + "}" return JsmolView.from_str(str(cif), supercell_str, *args)
def get_sqrt2_1_struct(self): """Setup a sqrt 2 x sqrt 2 x 1 structure""" orig_scaled_mat = np.array([[self.lattconst * np.sqrt(2), 0., 0.], [0., self.lattconst * np.sqrt(2), 0.], [0., 0., self.lattconst]]) rotation = np.array([[np.cos(45. * np.pi / 180.), -np.sin(45. * np.pi / 180.), 0], [np.sin(45. * np.pi / 180.), np.cos(45. * np.pi / 180.), 0], [0., 0., 1.]]) rotated_mat = np.dot(orig_scaled_mat, rotation) lattice = Lattice(rotated_mat) species = [self.eltA, self.eltA, self.eltB, self.eltB, self.eltC, self.eltC, self.eltC, self.eltC, self.eltC, self.eltC] coords = [[0.5, 0., 0.5], [0., 0.5, 0.5], [0., 0., 0.], [0.5, 0.5, 0.], [0.25, 0.25, 0.], [0.75, 0.75, 0.], [0.25, 0.75, 0.], [0.75, 0.25, 0.], [0., 0., 0.5], [0.5, 0.5, 0.5]] struct = Structure(lattice, species, coords, coords_are_cartesian=False) return struct.copy()
class CollinearMagneticStructureAnalyzerTest(unittest.TestCase): def setUp(self): parser = CifParser(os.path.join(PymatgenTest.TEST_FILES_DIR, "Fe.cif")) self.Fe = parser.get_structures()[0] parser = CifParser( os.path.join(PymatgenTest.TEST_FILES_DIR, "LiFePO4.cif")) self.LiFePO4 = parser.get_structures()[0] parser = CifParser( os.path.join(PymatgenTest.TEST_FILES_DIR, "Fe3O4.cif")) self.Fe3O4 = parser.get_structures()[0] parser = CifParser( os.path.join(PymatgenTest.TEST_FILES_DIR, "magnetic.ncl.example.GdB4.mcif")) self.GdB4 = parser.get_structures()[0] parser = CifParser( os.path.join(PymatgenTest.TEST_FILES_DIR, "magnetic.example.NiO.mcif")) self.NiO_expt = parser.get_structures()[0] latt = Lattice.cubic(4.17) species = ["Ni", "O"] coords = [[0, 0, 0], [0.5, 0.5, 0.5]] self.NiO = Structure.from_spacegroup(225, latt, species, coords) latt = Lattice([[2.085, 2.085, 0.0], [0.0, -2.085, -2.085], [-2.085, 2.085, -4.17]]) species = ["Ni", "Ni", "O", "O"] coords = [[0.5, 0, 0.5], [0, 0, 0], [0.25, 0.5, 0.25], [0.75, 0.5, 0.75]] self.NiO_AFM_111 = Structure(latt, species, coords, site_properties={"magmom": [-5, 5, 0, 0]}) latt = Lattice([[2.085, 2.085, 0], [0, 0, -4.17], [-2.085, 2.085, 0]]) species = ["Ni", "Ni", "O", "O"] coords = [[0.5, 0.5, 0.5], [0, 0, 0], [0, 0.5, 0], [0.5, 0, 0.5]] self.NiO_AFM_001 = Structure(latt, species, coords, site_properties={"magmom": [-5, 5, 0, 0]}) latt = Lattice([[2.085, 2.085, 0], [0, 0, -4.17], [-2.085, 2.085, 0]]) species = ["Ni", "Ni", "O", "O"] coords = [[0.5, 0.5, 0.5], [0, 0, 0], [0, 0.5, 0], [0.5, 0, 0.5]] self.NiO_AFM_001_opposite = Structure( latt, species, coords, site_properties={"magmom": [5, -5, 0, 0]}) latt = Lattice([[2.085, 2.085, 0], [0, 0, -4.17], [-2.085, 2.085, 0]]) species = ["Ni", "Ni", "O", "O"] coords = [[0.5, 0.5, 0.5], [0, 0, 0], [0, 0.5, 0], [0.5, 0, 0.5]] self.NiO_unphysical = Structure( latt, species, coords, site_properties={"magmom": [-3, 0, 0, 0]}) warnings.simplefilter("ignore") def tearDown(self): warnings.simplefilter("default") def test_get_representations(self): # tests to convert between storing magnetic moment information # on site_properties or on Species 'spin' property # test we store magnetic moments on site properties self.Fe.add_site_property("magmom", [5]) msa = CollinearMagneticStructureAnalyzer(self.Fe) self.assertEqual(msa.structure.site_properties["magmom"][0], 5) # and that we can retrieve a spin representation Fe_spin = msa.get_structure_with_spin() self.assertFalse("magmom" in Fe_spin.site_properties) self.assertEqual(Fe_spin[0].specie.spin, 5) # test we can remove magnetic moment information msa.get_nonmagnetic_structure() self.assertFalse("magmom" in Fe_spin.site_properties) # test with disorder on magnetic site self.Fe[0] = { Species("Fe", oxidation_state=0, properties={"spin": 5}): 0.5, "Ni": 0.5, } self.assertRaises(NotImplementedError, CollinearMagneticStructureAnalyzer, self.Fe) def test_matches(self): self.assertTrue(self.NiO.matches(self.NiO_AFM_111)) self.assertTrue(self.NiO.matches(self.NiO_AFM_001)) # MSA adds magmoms to Structure, so not equal msa = CollinearMagneticStructureAnalyzer( self.NiO, overwrite_magmom_mode="replace_all") self.assertFalse(msa.matches_ordering(self.NiO)) self.assertFalse(msa.matches_ordering(self.NiO_AFM_111)) self.assertFalse(msa.matches_ordering(self.NiO_AFM_001)) msa = CollinearMagneticStructureAnalyzer( self.NiO_AFM_001, overwrite_magmom_mode="respect_sign") self.assertFalse(msa.matches_ordering(self.NiO)) self.assertFalse(msa.matches_ordering(self.NiO_AFM_111)) self.assertTrue(msa.matches_ordering(self.NiO_AFM_001)) self.assertTrue(msa.matches_ordering(self.NiO_AFM_001_opposite)) msa = CollinearMagneticStructureAnalyzer( self.NiO_AFM_111, overwrite_magmom_mode="respect_sign") self.assertFalse(msa.matches_ordering(self.NiO)) self.assertTrue(msa.matches_ordering(self.NiO_AFM_111)) self.assertFalse(msa.matches_ordering(self.NiO_AFM_001)) self.assertFalse(msa.matches_ordering(self.NiO_AFM_001_opposite)) def test_modes(self): mode = "none" msa = CollinearMagneticStructureAnalyzer(self.NiO, overwrite_magmom_mode=mode) magmoms = msa.structure.site_properties["magmom"] self.assertEqual(magmoms, [0, 0]) mode = "respect_sign" msa = CollinearMagneticStructureAnalyzer(self.NiO_unphysical, overwrite_magmom_mode=mode) magmoms = msa.structure.site_properties["magmom"] self.assertEqual(magmoms, [-5, 0, 0, 0]) mode = "respect_zeros" msa = CollinearMagneticStructureAnalyzer(self.NiO_unphysical, overwrite_magmom_mode=mode) magmoms = msa.structure.site_properties["magmom"] self.assertEqual(magmoms, [5, 0, 0, 0]) mode = "replace_all" msa = CollinearMagneticStructureAnalyzer(self.NiO_unphysical, overwrite_magmom_mode=mode, make_primitive=False) magmoms = msa.structure.site_properties["magmom"] self.assertEqual(magmoms, [5, 5, 0, 0]) mode = "replace_all_if_undefined" msa = CollinearMagneticStructureAnalyzer(self.NiO, overwrite_magmom_mode=mode) magmoms = msa.structure.site_properties["magmom"] self.assertEqual(magmoms, [5, 0]) mode = "normalize" msa = CollinearMagneticStructureAnalyzer( msa.structure, overwrite_magmom_mode="normalize") magmoms = msa.structure.site_properties["magmom"] self.assertEqual(magmoms, [1, 0]) def test_net_positive(self): msa = CollinearMagneticStructureAnalyzer(self.NiO_unphysical) magmoms = msa.structure.site_properties["magmom"] self.assertEqual(magmoms, [3, 0, 0, 0]) def test_get_ferromagnetic_structure(self): msa = CollinearMagneticStructureAnalyzer( self.NiO, overwrite_magmom_mode="replace_all_if_undefined") s1 = msa.get_ferromagnetic_structure() s1_magmoms = [float(m) for m in s1.site_properties["magmom"]] s1_magmoms_ref = [5.0, 0.0] self.assertListEqual(s1_magmoms, s1_magmoms_ref) _ = CollinearMagneticStructureAnalyzer( self.NiO_AFM_111, overwrite_magmom_mode="replace_all_if_undefined") s2 = msa.get_ferromagnetic_structure(make_primitive=False) s2_magmoms = [float(m) for m in s2.site_properties["magmom"]] s2_magmoms_ref = [5.0, 0.0] self.assertListEqual(s2_magmoms, s2_magmoms_ref) s2_prim = msa.get_ferromagnetic_structure(make_primitive=True) self.assertTrue( CollinearMagneticStructureAnalyzer(s1).matches_ordering(s2_prim)) def test_magnetic_properties(self): msa = CollinearMagneticStructureAnalyzer(self.GdB4) self.assertFalse(msa.is_collinear) msa = CollinearMagneticStructureAnalyzer(self.Fe) self.assertFalse(msa.is_magnetic) self.Fe.add_site_property("magmom", [5]) msa = CollinearMagneticStructureAnalyzer(self.Fe) self.assertTrue(msa.is_magnetic) self.assertTrue(msa.is_collinear) self.assertEqual(msa.ordering, Ordering.FM) msa = CollinearMagneticStructureAnalyzer( self.NiO, make_primitive=False, overwrite_magmom_mode="replace_all_if_undefined", ) self.assertEqual(msa.number_of_magnetic_sites, 4) self.assertEqual(msa.number_of_unique_magnetic_sites(), 1) self.assertEqual(msa.types_of_magnetic_species, (Element.Ni, )) self.assertEqual(msa.get_exchange_group_info(), ("Fm-3m", 225)) def test_str(self): msa = CollinearMagneticStructureAnalyzer(self.NiO_AFM_001) ref_msa_str = """Structure Summary Lattice abc : 2.948635277547903 4.17 2.948635277547903 angles : 90.0 90.0 90.0 volume : 36.2558565 A : 2.085 2.085 0.0 B : 0.0 0.0 -4.17 C : -2.085 2.085 0.0 Magmoms Sites +5.00 PeriodicSite: Ni (0.0000, 0.0000, 0.0000) [0.0000, 0.0000, 0.0000] PeriodicSite: O (0.0000, 0.0000, -2.0850) [0.0000, 0.5000, 0.0000] PeriodicSite: O (0.0000, 2.0850, 0.0000) [0.5000, 0.0000, 0.5000] -5.00 PeriodicSite: Ni (0.0000, 2.0850, -2.0850) [0.5000, 0.5000, 0.5000]""" # just compare lines form 'Magmoms Sites', # since lattice param string can vary based on machine precision self.assertEqual( "\n".join(str(msa).split("\n")[-5:-1]), "\n".join(ref_msa_str.split("\n")[-5:-1]), ) def test_round_magmoms(self): struct = self.NiO_AFM_001.copy() struct.add_site_property("magmom", [-5.0143, -5.02, 0.147, 0.146]) msa = CollinearMagneticStructureAnalyzer(struct, round_magmoms=0.001, make_primitive=False) self.assertTrue( np.allclose(msa.magmoms, [5.0171, 5.0171, -0.1465, -0.1465])) self.assertAlmostEqual(msa.magnetic_species_and_magmoms["Ni"], 5.0171) self.assertAlmostEqual(msa.magnetic_species_and_magmoms["O"], 0.1465) struct.add_site_property("magmom", [-5.0143, 4.5, 0.147, 0.146]) msa = CollinearMagneticStructureAnalyzer(struct, round_magmoms=0.001, make_primitive=False) self.assertTrue( np.allclose(msa.magmoms, [5.0143, -4.5, -0.1465, -0.1465])) self.assertAlmostEqual(msa.magnetic_species_and_magmoms["Ni"][0], 4.5) self.assertAlmostEqual(msa.magnetic_species_and_magmoms["Ni"][1], 5.0143) self.assertAlmostEqual(msa.magnetic_species_and_magmoms["O"], 0.1465)
def get_start_end_structures( isite: PeriodicSite, esite: PeriodicSite, base_struct: Structure, sc_mat: List[List[Union[int, float]]], vac_mode: bool, debug: bool = False, ) -> Tuple[Structure, Structure, Structure]: """ Obtain the starting and terminating structures in a supercell for NEB calculations. Args: hop: object presenting the migration event base_struct: unit cell representation of the structure sc_mat: supercell transformation to create the simulation cell for the NEB calc Returns: initial structure, final structure, empty structure all in the supercell """ def remove_site_at_pos(structure: Structure, site: PeriodicSite): new_struct_sites = [] for isite in structure: if not vac_mode or (isite.distance(site) <= 1e-8): continue new_struct_sites.append(isite) return Structure.from_sites(new_struct_sites) base_sc = base_struct.copy() * sc_mat start_struct = base_struct.copy() * sc_mat end_struct = base_struct.copy() * sc_mat sc_mat_inv = np.linalg.inv(sc_mat) if not vac_mode: # insertion the endpoints start_struct.insert( 0, esite.species_string, np.dot(isite.frac_coords, sc_mat_inv), properties={"magmom": 0}, ) end_struct.insert( 0, esite.species_string, np.dot(esite.frac_coords, sc_mat_inv), properties={"magmom": 0}, ) else: # remove the other endpoint ipos_sc = np.dot(isite.frac_coords, sc_mat_inv) epos_sc = np.dot(esite.frac_coords, sc_mat_inv) if debug: icart = base_sc.lattice.get_cartesian_coords(ipos_sc) ecart = base_sc.lattice.get_cartesian_coords(epos_sc) assert abs( np.linalg.norm(icart - ecart) - np.linalg.norm(isite.coords - esite.coords)) < 1e-5 i_ref_ = PeriodicSite(species=esite.species_string, coords=ipos_sc, lattice=base_sc.lattice) e_ref_ = PeriodicSite(species=esite.species_string, coords=epos_sc, lattice=base_sc.lattice) start_struct = remove_site_at_pos(start_struct, e_ref_) end_struct = remove_site_at_pos(end_struct, i_ref_) return start_struct, end_struct, base_sc
class CollinearMagneticStructureAnalyzerTest(unittest.TestCase): def setUp(self): parser = CifParser(os.path.join(test_dir, 'Fe.cif')) self.Fe = parser.get_structures()[0] parser = CifParser(os.path.join(test_dir, 'LiFePO4.cif')) self.LiFePO4 = parser.get_structures()[0] parser = CifParser(os.path.join(test_dir, 'Fe3O4.cif')) self.Fe3O4 = parser.get_structures()[0] parser = CifParser(os.path.join(test_dir, 'magnetic.ncl.example.GdB4.mcif')) self.GdB4 = parser.get_structures()[0] parser = CifParser(os.path.join(test_dir, 'magnetic.example.NiO.mcif')) self.NiO_expt = parser.get_structures()[0] latt = Lattice.cubic(4.17) species = ["Ni", "O"] coords = [[0, 0, 0], [0.5, 0.5, 0.5]] self.NiO = Structure.from_spacegroup(225, latt, species, coords) latt = Lattice([[2.085, 2.085, 0.0], [0.0, -2.085, -2.085], [-2.085, 2.085, -4.17]]) species = ["Ni", "Ni", "O", "O"] coords = [[0.5, 0, 0.5], [0, 0, 0], [0.25, 0.5, 0.25], [0.75, 0.5, 0.75]] self.NiO_AFM_111 = Structure(latt, species, coords, site_properties={'magmom': [-5, 5, 0, 0]}) latt = Lattice([[2.085, 2.085, 0], [0, 0, -4.17], [-2.085, 2.085, 0]]) species = ["Ni", "Ni", "O", "O"] coords = [[0.5, 0.5, 0.5], [0, 0, 0], [0, 0.5, 0], [0.5, 0, 0.5]] self.NiO_AFM_001 = Structure(latt, species, coords, site_properties={'magmom': [-5, 5, 0, 0]}) latt = Lattice([[2.085, 2.085, 0], [0, 0, -4.17], [-2.085, 2.085, 0]]) species = ["Ni", "Ni", "O", "O"] coords = [[0.5, 0.5, 0.5], [0, 0, 0], [0, 0.5, 0], [0.5, 0, 0.5]] self.NiO_AFM_001_opposite = Structure(latt, species, coords, site_properties={'magmom': [5, -5, 0, 0]}) latt = Lattice([[2.085, 2.085, 0], [0, 0, -4.17], [-2.085, 2.085, 0]]) species = ["Ni", "Ni", "O", "O"] coords = [[0.5, 0.5, 0.5], [0, 0, 0], [0, 0.5, 0], [0.5, 0, 0.5]] self.NiO_unphysical = Structure(latt, species, coords, site_properties={'magmom': [-3, 0, 0, 0]}) warnings.simplefilter("ignore") def tearDown(self): warnings.simplefilter("default") def test_get_representations(self): # tests to convert between storing magnetic moment information # on site_properties or on Specie 'spin' property # test we store magnetic moments on site properties self.Fe.add_site_property('magmom', [5]) msa = CollinearMagneticStructureAnalyzer(self.Fe) self.assertEqual(msa.structure.site_properties['magmom'][0], 5) # and that we can retrieve a spin representaiton Fe_spin = msa.get_structure_with_spin() self.assertFalse('magmom' in Fe_spin.site_properties) self.assertEqual(Fe_spin[0].specie.spin, 5) # test we can remove magnetic moment information Fe_none = msa.get_nonmagnetic_structure() self.assertFalse('magmom' in Fe_spin.site_properties) # test with disorder on magnetic site self.Fe[0] = {Specie('Fe', oxidation_state=0, properties={'spin': 5}): 0.5, 'Ni': 0.5} self.assertRaises(NotImplementedError, CollinearMagneticStructureAnalyzer, self.Fe) def test_matches(self): self.assertTrue(self.NiO.matches(self.NiO_AFM_111)) self.assertTrue(self.NiO.matches(self.NiO_AFM_001)) # MSA adds magmoms to Structure, so not equal msa = CollinearMagneticStructureAnalyzer(self.NiO, overwrite_magmom_mode="replace_all") self.assertFalse(msa.matches_ordering(self.NiO)) self.assertFalse(msa.matches_ordering(self.NiO_AFM_111)) self.assertFalse(msa.matches_ordering(self.NiO_AFM_001)) msa = CollinearMagneticStructureAnalyzer(self.NiO_AFM_001, overwrite_magmom_mode="respect_sign") self.assertFalse(msa.matches_ordering(self.NiO)) self.assertFalse(msa.matches_ordering(self.NiO_AFM_111)) self.assertTrue(msa.matches_ordering(self.NiO_AFM_001)) self.assertTrue(msa.matches_ordering(self.NiO_AFM_001_opposite)) msa = CollinearMagneticStructureAnalyzer(self.NiO_AFM_111, overwrite_magmom_mode="respect_sign") self.assertFalse(msa.matches_ordering(self.NiO)) self.assertTrue(msa.matches_ordering(self.NiO_AFM_111)) self.assertFalse(msa.matches_ordering(self.NiO_AFM_001)) self.assertFalse(msa.matches_ordering(self.NiO_AFM_001_opposite)) def test_modes(self): mode = "none" msa = CollinearMagneticStructureAnalyzer(self.NiO, overwrite_magmom_mode=mode) magmoms = msa.structure.site_properties['magmom'] self.assertEqual(magmoms, [0, 0]) mode = "respect_sign" msa = CollinearMagneticStructureAnalyzer(self.NiO_unphysical, overwrite_magmom_mode=mode) magmoms = msa.structure.site_properties['magmom'] self.assertEqual(magmoms, [-5, 0, 0, 0]) mode = "respect_zeros" msa = CollinearMagneticStructureAnalyzer(self.NiO_unphysical, overwrite_magmom_mode=mode) magmoms = msa.structure.site_properties['magmom'] self.assertEqual(magmoms, [5, 0, 0, 0]) mode = "replace_all" msa = CollinearMagneticStructureAnalyzer(self.NiO_unphysical, overwrite_magmom_mode=mode, make_primitive=False) magmoms = msa.structure.site_properties['magmom'] self.assertEqual(magmoms, [5, 5, 0, 0]) mode = "replace_all_if_undefined" msa = CollinearMagneticStructureAnalyzer(self.NiO, overwrite_magmom_mode=mode) magmoms = msa.structure.site_properties['magmom'] self.assertEqual(magmoms, [5, 0]) mode = "normalize" msa = CollinearMagneticStructureAnalyzer(msa.structure, overwrite_magmom_mode='normalize') magmoms = msa.structure.site_properties['magmom'] self.assertEqual(magmoms, [1, 0]) def test_get_ferromagnetic_structure(self): msa = CollinearMagneticStructureAnalyzer(self.NiO, overwrite_magmom_mode="replace_all_if_undefined") s1 = msa.get_ferromagnetic_structure() s1_magmoms = [float(m) for m in s1.site_properties['magmom']] s1_magmoms_ref = [5.0, 0.0] self.assertListEqual(s1_magmoms, s1_magmoms_ref) msa2 = CollinearMagneticStructureAnalyzer(self.NiO_AFM_111, overwrite_magmom_mode="replace_all_if_undefined") s2 = msa.get_ferromagnetic_structure(make_primitive=False) s2_magmoms = [float(m) for m in s2.site_properties['magmom']] s2_magmoms_ref = [5.0, 0.0] self.assertListEqual(s2_magmoms, s2_magmoms_ref) s2_prim = msa.get_ferromagnetic_structure(make_primitive=True) self.assertTrue(CollinearMagneticStructureAnalyzer(s1).matches_ordering(s2_prim)) def test_magnetic_properties(self): msa = CollinearMagneticStructureAnalyzer(self.GdB4) self.assertFalse(msa.is_collinear) msa = CollinearMagneticStructureAnalyzer(self.Fe) self.assertFalse(msa.is_magnetic) self.Fe.add_site_property('magmom', [5]) msa = CollinearMagneticStructureAnalyzer(self.Fe) self.assertTrue(msa.is_magnetic) self.assertTrue(msa.is_collinear) self.assertEqual(msa.ordering, Ordering.FM) msa = CollinearMagneticStructureAnalyzer(self.NiO, make_primitive=False, overwrite_magmom_mode="replace_all_if_undefined") self.assertEqual(msa.number_of_magnetic_sites, 4) self.assertEqual(msa.number_of_unique_magnetic_sites(), 1) self.assertEqual(msa.types_of_magnetic_specie, [Element('Ni')]) self.assertEqual(msa.get_exchange_group_info(), ('Fm-3m', 225)) def test_str(self): msa = CollinearMagneticStructureAnalyzer(self.NiO_AFM_001) ref_msa_str = """Structure Summary Lattice abc : 2.948635277547903 4.17 2.948635277547903 angles : 90.0 90.0 90.0 volume : 36.2558565 A : 2.085 2.085 0.0 B : 0.0 0.0 -4.17 C : -2.085 2.085 0.0 Magmoms Sites +5.00 PeriodicSite: Ni (0.0000, 0.0000, 0.0000) [0.0000, 0.0000, 0.0000] PeriodicSite: O (0.0000, 0.0000, -2.0850) [0.0000, 0.5000, 0.0000] PeriodicSite: O (0.0000, 2.0850, 0.0000) [0.5000, 0.0000, 0.5000] -5.00 PeriodicSite: Ni (0.0000, 2.0850, -2.0850) [0.5000, 0.5000, 0.5000]""" # just compare lines form 'Magmoms Sites', # since lattice param string can vary based on machine precision self.assertEqual("\n".join(str(msa).split("\n")[-5:-1]), "\n".join(ref_msa_str.split("\n")[-5:-1])) def test_round_magmoms(self): struct = self.NiO_AFM_001.copy() struct.add_site_property('magmom', [-5.0143, -5.02, 0.147, 0.146]) msa = CollinearMagneticStructureAnalyzer(struct, round_magmoms=0.001, make_primitive=False) self.assertTrue(np.allclose(msa.magmoms, [-5.0171, -5.0171, 0.1465, 0.1465])) self.assertAlmostEqual(msa.magnetic_species_and_magmoms['Ni'], 5.0171) self.assertAlmostEqual(msa.magnetic_species_and_magmoms['O'], 0.1465) struct.add_site_property('magmom', [-5.0143, 4.5, 0.147, 0.146]) msa = CollinearMagneticStructureAnalyzer(struct, round_magmoms=0.001, make_primitive=False) self.assertTrue(np.allclose(msa.magmoms, [-5.0143, 4.5, 0.1465, 0.1465])) self.assertAlmostEqual(msa.magnetic_species_and_magmoms['Ni'][0], 4.5) self.assertAlmostEqual(msa.magnetic_species_and_magmoms['Ni'][1], 5.0143) self.assertAlmostEqual(msa.magnetic_species_and_magmoms['O'], 0.1465)
def test_supercell_subsets(self): sm = StructureMatcher(ltol=0.2, stol=0.3, angle_tol=5, primitive_cell=False, scale=True, attempt_supercell=True, allow_subset=True, supercell_size='volume') sm_no_s = StructureMatcher(ltol=0.2, stol=0.3, angle_tol=5, primitive_cell=False, scale=True, attempt_supercell=True, allow_subset=False, supercell_size='volume') l = Lattice.orthorhombic(1, 2, 3) s1 = Structure(l, ['Ag', 'Si', 'Si'], [[.7, .4, .5], [0, 0, 0.1], [0, 0, 0.2]]) s1.make_supercell([2, 1, 1]) s2 = Structure(l, ['Si', 'Si', 'Ag'], [[0, 0.1, -0.95], [0, 0.1, 0], [-.7, .5, .375]]) shuffle = [0, 2, 1, 3, 4, 5] s1 = Structure.from_sites([s1[i] for i in shuffle]) #test when s1 is exact supercell of s2 result = sm.get_s2_like_s1(s1, s2) for a, b in zip(s1, result): self.assertTrue(a.distance(b) < 0.08) self.assertEqual(a.species_and_occu, b.species_and_occu) self.assertTrue(sm.fit(s1, s2)) self.assertTrue(sm.fit(s2, s1)) self.assertTrue(sm_no_s.fit(s1, s2)) self.assertTrue(sm_no_s.fit(s2, s1)) rms = (0.048604032430991401, 0.059527539448807391) self.assertTrue(np.allclose(sm.get_rms_dist(s1, s2), rms)) self.assertTrue(np.allclose(sm.get_rms_dist(s2, s1), rms)) #test when the supercell is a subset of s2 subset_supercell = s1.copy() del subset_supercell[0] result = sm.get_s2_like_s1(subset_supercell, s2) self.assertEqual(len(result), 6) for a, b in zip(subset_supercell, result): self.assertTrue(a.distance(b) < 0.08) self.assertEqual(a.species_and_occu, b.species_and_occu) self.assertTrue(sm.fit(subset_supercell, s2)) self.assertTrue(sm.fit(s2, subset_supercell)) self.assertFalse(sm_no_s.fit(subset_supercell, s2)) self.assertFalse(sm_no_s.fit(s2, subset_supercell)) rms = (0.053243049896333279, 0.059527539448807336) self.assertTrue(np.allclose(sm.get_rms_dist(subset_supercell, s2), rms)) self.assertTrue(np.allclose(sm.get_rms_dist(s2, subset_supercell), rms)) #test when s2 (once made a supercell) is a subset of s1 s2_missing_site = s2.copy() del s2_missing_site[1] result = sm.get_s2_like_s1(s1, s2_missing_site) for a, b in zip((s1[i] for i in (0, 2, 4, 5)), result): self.assertTrue(a.distance(b) < 0.08) self.assertEqual(a.species_and_occu, b.species_and_occu) self.assertTrue(sm.fit(s1, s2_missing_site)) self.assertTrue(sm.fit(s2_missing_site, s1)) self.assertFalse(sm_no_s.fit(s1, s2_missing_site)) self.assertFalse(sm_no_s.fit(s2_missing_site, s1)) rms = (0.029763769724403633, 0.029763769724403987) self.assertTrue(np.allclose(sm.get_rms_dist(s1, s2_missing_site), rms)) self.assertTrue(np.allclose(sm.get_rms_dist(s2_missing_site, s1), rms))
def test_supercell_subsets(self): sm = StructureMatcher(ltol=0.2, stol=0.3, angle_tol=5, primitive_cell=False, scale=True, attempt_supercell=True, allow_subset=True, supercell_size='volume') sm_no_s = StructureMatcher(ltol=0.2, stol=0.3, angle_tol=5, primitive_cell=False, scale=True, attempt_supercell=True, allow_subset=False, supercell_size='volume') l = Lattice.orthorhombic(1, 2, 3) s1 = Structure(l, ['Ag', 'Si', 'Si'], [[.7,.4,.5],[0,0,0.1],[0,0,0.2]]) s1.make_supercell([2,1,1]) s2 = Structure(l, ['Si', 'Si', 'Ag'], [[0,0.1,-0.95],[0,0.1,0],[-.7,.5,.375]]) shuffle = [0,2,1,3,4,5] s1 = Structure.from_sites([s1[i] for i in shuffle]) #test when s1 is exact supercell of s2 result = sm.get_s2_like_s1(s1, s2) for a, b in zip(s1, result): self.assertTrue(a.distance(b) < 0.08) self.assertEqual(a.species_and_occu, b.species_and_occu) self.assertTrue(sm.fit(s1, s2)) self.assertTrue(sm.fit(s2, s1)) self.assertTrue(sm_no_s.fit(s1, s2)) self.assertTrue(sm_no_s.fit(s2, s1)) rms = (0.048604032430991401, 0.059527539448807391) self.assertTrue(np.allclose(sm.get_rms_dist(s1, s2), rms)) self.assertTrue(np.allclose(sm.get_rms_dist(s2, s1), rms)) #test when the supercell is a subset of s2 subset_supercell = s1.copy() del subset_supercell[0] result = sm.get_s2_like_s1(subset_supercell, s2) self.assertEqual(len(result), 6) for a, b in zip(subset_supercell, result): self.assertTrue(a.distance(b) < 0.08) self.assertEqual(a.species_and_occu, b.species_and_occu) self.assertTrue(sm.fit(subset_supercell, s2)) self.assertTrue(sm.fit(s2, subset_supercell)) self.assertFalse(sm_no_s.fit(subset_supercell, s2)) self.assertFalse(sm_no_s.fit(s2, subset_supercell)) rms = (0.053243049896333279, 0.059527539448807336) self.assertTrue(np.allclose(sm.get_rms_dist(subset_supercell, s2), rms)) self.assertTrue(np.allclose(sm.get_rms_dist(s2, subset_supercell), rms)) #test when s2 (once made a supercell) is a subset of s1 s2_missing_site = s2.copy() del s2_missing_site[1] result = sm.get_s2_like_s1(s1, s2_missing_site) for a, b in zip((s1[i] for i in (0, 2, 4, 5)), result): self.assertTrue(a.distance(b) < 0.08) self.assertEqual(a.species_and_occu, b.species_and_occu) self.assertTrue(sm.fit(s1, s2_missing_site)) self.assertTrue(sm.fit(s2_missing_site, s1)) self.assertFalse(sm_no_s.fit(s1, s2_missing_site)) self.assertFalse(sm_no_s.fit(s2_missing_site, s1)) rms = (0.029763769724403633, 0.029763769724403987) self.assertTrue(np.allclose(sm.get_rms_dist(s1, s2_missing_site), rms)) self.assertTrue(np.allclose(sm.get_rms_dist(s2_missing_site, s1), rms))
import numpy as np from pymatgen.core import Lattice, Structure from pymatgen.core.periodic_table import Specie, DummySpecie from pymatgen.analysis.structure_matcher import StructureMatcher from dsenum import StructureEnumerator from dsenum.utils import write_cif, refine_and_resize_structure if __name__ == "__main__": lattice = Lattice(3.945 * np.eye(3)) species = ["Sr", "Ti", "O", "O", "O"] frac_coords = np.array([[0, 0, 0], [0.5, 0.5, 0.5], [0.0, 0.5, 0.5], [0.5, 0.0, 0.5], [0.5, 0.5, 0.0]]) aristo = Structure(lattice, species, frac_coords) base_structure = aristo.copy() base_structure.remove_species(["Sr", "Ti"]) additional_species = species[:2] additional_frac_coords = frac_coords[:2] mapping_color_species = [DummySpecie("X"), "O"] num_types = len(mapping_color_species) index = 2 se = StructureEnumerator( base_structure, index, num_types, mapping_color_species=mapping_color_species, color_exchange=False, leave_superperiodic=False,