def _sanitize_input_structure(input_structure: Structure) -> Structure: """Sanitize our input structure by removing magnetic information and making primitive. Args: input_structure: Structure Returns: Structure """ input_structure = input_structure.copy() # remove any annotated spin input_structure.remove_spin() # sanitize input structure: first make primitive ... input_structure = input_structure.get_primitive_structure( use_site_props=False) # ... and strip out existing magmoms, which can cause conflicts # with later transformations otherwise since sites would end up # with both magmom site properties and Species spins defined if "magmom" in input_structure.site_properties: input_structure.remove_site_property("magmom") return input_structure
def insert_atoms(structure: Structure, inserted_atoms: List[dict]) -> Tuple[Structure, List[dict]]: """Return the structure with inserted atoms and its inserted atom indices Args: structure (Structure): Input structure. inserted_atoms (list): List of dict with "element" and "coords" keys. Not that "index" is absent here as it is not determined, yet. Return: (inserted Structure, list of dict of inserted atom info) """ inserted_structure = structure.copy() inserted_atoms_with_indices = [] for atom in inserted_atoms: index = first_appearing_index(inserted_structure, atom["element"]) inserted_structure.insert(index, atom["element"], atom["coords"]) # The atom indices locating after k need to be incremented. for i in inserted_atoms_with_indices: if i["index"] >= index: i["index"] += 1 inserted_atoms_with_indices.append({ "element": atom["element"], "index": index, "coords": atom["coords"] }) return inserted_structure, inserted_atoms_with_indices
class SlabTest(unittest.TestCase): def setUp(self): self.cu = Structure(Lattice.cubic(3), ["Cu", "Cu", "Cu", "Cu"], [[0, 0, 0], [0.5, 0.5, 0], [0.5, 0, 0.5], [0, 0.5, 0.5]]) def test_init(self): for hkl in itertools.product(xrange(4), xrange(4), xrange(4)): if any(hkl): ssize = 6 vsize = 10 s = Slab(self.cu, hkl, ssize, vsize) if hkl == [0, 1, 1]: self.assertEqual(len(s), 13) self.assertAlmostEqual(s.surface_area, 12.727922061357855) manual = self.cu.copy() manual.make_supercell(s.scale_factor) self.assertEqual(manual.lattice.lengths_and_angles, s.lattice.lengths_and_angles) # # For visual debugging # from pymatgen import write_structure # write_structure(s.parent, "cu.cif") # write_structure(s, "cu_slab_%s_%.3f_%.3f.cif" % # (str(hkl), ssize, vsize)) def test_adsorb_atom(self): s001 = Slab(self.cu,[0, 0, 1], 5, 5) # print s001 # O adsorb on 4Cu[0.5, 0.5, 0.25], abc = [3, 3, 12] # 1. test site_a = abc input s001_ad1 = Slab.adsorb_atom(structure_a=s001, site_a=[0.5, 0.5, 0.25], atom= ['O'], distance=2) self.assertEqual(len(s001_ad1), 9) for i in xrange(len(s001_ad1)): if str(s001_ad1[i].specie) == 'O': print s001_ad1[i].frac_coords self.assertAlmostEqual(s001_ad1[i].a, 0.5) self.assertAlmostEqual(s001_ad1[i].b, 0.5) self.assertAlmostEqual(s001_ad1[i].c, 0.4166667) self.assertEqual(s001_ad1.lattice.lengths_and_angles, s001.lattice.lengths_and_angles) # 2. test site_a = xyz input s001_ad2 = Slab.adsorb_atom(structure_a=s001, site_a=[1.5, 1.5, 3], atom= ['O'], distance=2, xyz=1) self.assertEqual(len(s001_ad2), 9) for i in xrange(len(s001_ad2)): if str(s001_ad2[i].specie) == 'O': print s001_ad2[i].frac_coords self.assertAlmostEqual(s001_ad2[i].a, 0.5) self.assertAlmostEqual(s001_ad2[i].b, 0.5) self.assertAlmostEqual(s001_ad2[i].c, 0.4166667)
def apply_transformation(self, structure: Structure) -> Structure: """ Apply the transformation. Args: structure: Input Structure Returns: Structure with sites perturbed. """ s = structure.copy() s.perturb(self.distance, min_distance=self.min_distance) return s
def __init__(self, structure: Structure, task: Task, xc: Xc, **input_options): self._input_options = deepcopy(input_options) self._input_options.update( {"initial_structure": structure.copy(), "task": task, "xc": xc}) self._set_kpt_density() self._raise_error_when_unknown_options_exist()
def test_primitive_positions(self): coords = [[0, 0, 0], [0.3, 0.35, 0.45]] s = Structure(Lattice.from_parameters(1,2,3,50,66,88), ["Ag"] * 2, coords) a = [[-1,2,-3], [3,2,-4], [1,0,-1]] b = [[4, 0, 0], [1, 1, 0], [3, 0, 1]] c = [[2, 0, 0], [1, 3, 0], [1, 1, 1]] for sc_matrix in [c]: sc = s.copy() sc.make_supercell(sc_matrix) prim = sc.get_primitive_structure(0.01) self.assertEqual(len(prim), 2) self.assertAlmostEqual(prim.distance_matrix[0,1], 1.0203432356739286)
def test_primitive_positions(self): coords = [[0, 0, 0], [0.3, 0.35, 0.45]] s = Structure(Lattice.from_parameters(1, 2, 3, 50, 66, 88), ["Ag"] * 2, coords) a = [[-1, 2, -3], [3, 2, -4], [1, 0, -1]] b = [[4, 0, 0], [1, 1, 0], [3, 0, 1]] c = [[2, 0, 0], [1, 3, 0], [1, 1, 1]] for sc_matrix in [c]: sc = s.copy() sc.make_supercell(sc_matrix) prim = sc.get_primitive_structure(0.01) self.assertEqual(len(prim), 2) self.assertAlmostEqual(prim.distance_matrix[0, 1], 1.0203432356739286)
def perturb_neighboring_atoms(structure: Structure, center: List[float], cutoff: float, distance: float, inserted_atom_indices: List[int] ) -> Tuple[Structure, list]: """ Return the structure with randomly perturbed atoms near the center Args: structure (Structure): pmg Structure class object center (list): Fractional coordinates of a central position. cutoff (float): Radius of a sphere in which atoms are perturbed. distance (float): Max displacement distance for the perturbation. inserted_atom_indices (list): Inserted atom indices, which will not be perturbed. Return: Tuple of perturbed Structure and list of perturbed atom indices. """ perturbed_structure = structure.copy() cartesian_coords = structure.lattice.get_cartesian_coords(center) # neighbor is (PeriodicSite, distance, index) neighbors = structure.get_sites_in_sphere( pt=cartesian_coords, r=cutoff, include_index=True) if not neighbors: logger.warning(f"No neighbors withing the cutoff {cutoff}.") sites = [] for i in neighbors: # not perturb inserted atoms if i[2] in inserted_atom_indices: continue # Since translate_sites accepts only one vector, iterate this. vector = normalized_random_3d_vector() * distance * np.random.random() site_index = i[2] sites.append(site_index) perturbed_structure.translate_sites( indices=site_index, vector=vector, frac_coords=False) return perturbed_structure, sites
class SlabTest(unittest.TestCase): def setUp(self): self.cu = Structure(Lattice.cubic(3), ["Cu", "Cu", "Cu", "Cu"], [[0, 0, 0], [0.5, 0.5, 0], [0.5, 0, 0.5], [0, 0.5, 0.5]]) def test_init(self): for hkl in itertools.product(xrange(4), xrange(4), xrange(4)): if any(hkl): ssize = 6 vsize = 10 s = Slab(self.cu, hkl, ssize, vsize) if hkl == [0, 1, 1]: self.assertEqual(len(s), 13) self.assertAlmostEqual(s.surface_area, 12.727922061357855) manual = self.cu.copy() manual.make_supercell(s.scale_factor) self.assertEqual(manual.lattice.lengths_and_angles, s.lattice.lengths_and_angles)
def test_copy(self): new_struct = self.propertied_structure.copy(site_properties={'charge': [2, 3]}) self.assertEqual(new_struct[0].magmom, 5) self.assertEqual(new_struct[1].magmom, -5) self.assertEqual(new_struct[0].charge, 2) self.assertEqual(new_struct[1].charge, 3) coords = list() coords.append([0, 0, 0]) coords.append([0., 0, 0.0000001]) structure = Structure(self.lattice, ["O", "Si"], coords, site_properties={'magmom': [5, -5]}) new_struct = structure.copy(site_properties={'charge': [2, 3]}, sanitize=True) self.assertEqual(new_struct[0].magmom, -5) self.assertEqual(new_struct[1].magmom, 5) self.assertEqual(new_struct[0].charge, 3) self.assertEqual(new_struct[1].charge, 2) self.assertAlmostEqual(new_struct.volume, structure.volume)
def num_equivalent_clusters(structure: Structure, inserted_atom_coords: Optional[list], removed_atom_indices: Optional[list], symprec: float = SYMMETRY_TOLERANCE, angle_tolerance: float = ANGLE_TOL ) -> Tuple[int, str]: """Calculate number of equivalent clusters in the structure. Args: structure (Structure): Supercell is assumed to big enough. inserted_atom_coords (list): removed_atom_indices (list): Needs to begin from 0. symprec (float): angle_tolerance (float): Angle tolerance in degree used for identifying the space group. Returns: Tuple of (num_equivalent_clusters (int), point_group (str)) """ inserted_atom_coords = inserted_atom_coords or [] removed_atom_indices = removed_atom_indices or [] sga = SpacegroupAnalyzer(structure, symprec, angle_tolerance) num_symmop = len(sga.get_symmetry_operations()) structure_with_cluster = structure.copy() for i in inserted_atom_coords: structure_with_cluster.append(DummySpecie(), i) structure_with_cluster.remove_sites(removed_atom_indices) sga_with_cluster = \ SpacegroupAnalyzer(structure_with_cluster, symprec, angle_tolerance) sym_dataset = sga_with_cluster.get_symmetry_dataset() point_group = sym_dataset["pointgroup"] return int(num_symmop / num_symmetry_operation(point_group)), point_group
def __init__( self, grid_data: np.ndarray, structure: Structure, normalization: str = "vasp", ): """ Class that contains functions to featurize volumetic data with periodic boundary conditions. Make sure the data being stored is grid-independent Args: grid_data: Volumetric data to read in structure: Atomic structure corresponding to the charge density normalization: the normalization scheme: - 'vasp' sum of the data / number of grid points == number of electrons """ self.structure = structure.copy() self.normalization = normalization if normalization[0].lower() == "n": """ No rescaling """ scaled_data = grid_data elif normalization[0].lower() == "v": """ the standard charge density from VASP is given as (rho*V) such that: sum(rho)/NGRID = NELECT/UC_vol so the real rho is: rho = (rho*UC_vol)*NGRID/UC_vol/UC_vol where the second V account for the different number of electrons in different cells """ scaled_data = grid_data / self.structure.volume else: raise NotImplementedError("Not a valid normalization scheme") super().__init__(grid_data=scaled_data, lattice=None)
class TestMaterials(unittest.TestCase): def setUp(self): coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) lattice = Lattice([[3.8401979337, 0.00, 0.00], [1.9200989668, 3.3257101909, 0.00], [0.00, -2.2171384943, 3.1355090603]]) self.structure = Structure(lattice, ["Si", "Si"], coords) tasks = MongoStore("emmet_test", "tasks") materials = MongoStore("emmet_test", "materials") self.mbuilder = MaterialsBuilder(tasks, materials, mat_prefix="", chunk_size=1) def test_make_mat(self): struc1 = self.structure.copy() struc2 = self.structure.copy() struc2.translate_sites(1, [0.5, 0, 0]) tasks = [{ "task_id": "mp-1", "orig_inputs": { "incar": { "LDAU": True, "ISIF": 3, "IBRION": 1 } }, "output": { "structure": struc1.as_dict(), "bandgap": 1.3 }, "formula_anonymous": "A", "formula_pretty": "Cl", "last_updated": "Never" }, { "task_id": "mp-2", "orig_inputs": { "incar": { "LDAU": True, "ICHARG": 11, "IBRION": 1 } }, "output": { "structure": struc2.as_dict(), "bandgap": 2 }, "formula_anonymous": "A", "formula_pretty": "Cl", "last_updated": "Never" }] mat = self.mbuilder.make_mat(tasks) self.assertEqual(mat["task_ids"], ["mp-1", "mp-2"]) for k in [ "task_ids", "task_id", "origins", "task_types", "formula_anonymous", "bandstructure", "inputs", "formula_pretty", "structure" ]: self.assertIn(k, mat) self.assertIn(self.mbuilder.materials.lu_field, mat) def test_filter_and_group_tasks(self): si = self.structure si2 = si.copy() si2.translate_sites(1, [0.5, 0, 0]) si3 = si.copy() si3.make_supercell(2) si4 = si.copy() si4.make_supercell(2) si4.translate_sites(1, [0.1, 0, 0]) incar = {"incar": {"LDAU": True, "ISIF": 3, "IBRION": 1}} task1 = { "output": { "structure": si.as_dict() }, "task_id": "mp-1", "orig_inputs": incar } task2 = { "output": { "structure": si2.as_dict() }, "task_id": "mp-2", "orig_inputs": incar } task3 = { "output": { "structure": si3.as_dict() }, "task_id": "mp-3", "orig_inputs": incar } task4 = { "output": { "structure": si4.as_dict() }, "task_id": "mp-4", "orig_inputs": incar } grouped_tasks = list( self.mbuilder.filter_and_group_tasks([task1, task2, task3, task4])) self.assertEqual(len(grouped_tasks), 3) task_ids = [[t["task_id"] for t in tasks] for tasks in grouped_tasks] self.assertIn(["mp-2"], task_ids) def test_task_to_prop_list(self): task = { "task_id": "mp-3", "orig_inputs": { "incar": { "LDAU": True, "ISIF": 3, "IBRION": 1 } }, "output": { "bandgap": 1.3, "structure": "What structure" }, "formula_anonymous": "A", "formula_pretty": "Cl", "last_updated": "Never" } prop_list = self.mbuilder.task_to_prop_list(task) for p in prop_list: self.assertIn("value", p) self.assertIn("task_type", p) self.assertIn("quality_score", p) self.assertIn("track", p) self.assertIn("last_updated", p) self.assertIn("task_id", p) self.assertIn("materials_key", p) prop_names = [p["materials_key"] for p in prop_list] props_in = [ 'structure', 'inputs.structure_optimization', 'bandstructure.band_gap' ] props_not_in = [ 'formula_anonymous', 'formula_pretty', 'bandstructure.cbm', 'bandstructure.vbm', 'chemsys', 'analysis.delta_volume', 'thermo.energy' ] for p in props_in: self.assertIn(p, prop_names) for p in props_not_in: self.assertNotIn(p, prop_names)
class StructureTest(PymatgenTest): def setUp(self): coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) lattice = Lattice([[3.8401979337, 0.00, 0.00], [1.9200989668, 3.3257101909, 0.00], [0.00, -2.2171384943, 3.1355090603]]) self.structure = Structure(lattice, ["Si", "Si"], coords) def test_mutable_sequence_methods(self): s = self.structure s[0] = "Fe" self.assertEqual(s.formula, "Fe1 Si1") s[0] = "Fe", [0.5, 0.5, 0.5] self.assertEqual(s.formula, "Fe1 Si1") self.assertArrayAlmostEqual(s[0].frac_coords, [0.5, 0.5, 0.5]) s.reverse() self.assertEqual(s[0].specie, Element("Si")) self.assertArrayAlmostEqual(s[0].frac_coords, [0.75, 0.5, 0.75]) s[0] = {"Mn": 0.5} self.assertEqual(s.formula, "Mn0.5 Fe1") del s[1] self.assertEqual(s.formula, "Mn0.5") s[0] = "Fe", [0.9, 0.9, 0.9], {"magmom": 5} self.assertEqual(s.formula, "Fe1") self.assertEqual(s[0].magmom, 5) def test_non_hash(self): self.assertRaises(TypeError, dict, [(self.structure, 1)]) def test_sort(self): s = self.structure s[0] = "F" s.sort() self.assertEqual(s[0].species_string, "Si") self.assertEqual(s[1].species_string, "F") s.sort(key=lambda site: site.species_string) self.assertEqual(s[0].species_string, "F") self.assertEqual(s[1].species_string, "Si") s.sort(key=lambda site: site.species_string, reverse=True) self.assertEqual(s[0].species_string, "Si") self.assertEqual(s[1].species_string, "F") def test_append_insert_remove_replace(self): s = self.structure s.insert(1, "O", [0.5, 0.5, 0.5]) self.assertEqual(s.formula, "Si2 O1") self.assertTrue(s.ntypesp == 2) self.assertTrue(s.symbol_set == ("Si", "O")) self.assertTrue(s.indices_from_symbol("Si") == (0, 2)) self.assertTrue(s.indices_from_symbol("O") == (1, )) s.remove(2) self.assertEqual(s.formula, "Si1 O1") self.assertTrue(s.indices_from_symbol("Si") == (0, )) self.assertTrue(s.indices_from_symbol("O") == (1, )) s.append("N", [0.25, 0.25, 0.25]) self.assertEqual(s.formula, "Si1 N1 O1") self.assertTrue(s.ntypesp == 3) self.assertTrue(s.symbol_set == ("Si", "O", "N")) self.assertTrue(s.indices_from_symbol("Si") == (0, )) self.assertTrue(s.indices_from_symbol("O") == (1, )) self.assertTrue(s.indices_from_symbol("N") == (2, )) s.replace(0, "Ge") self.assertEqual(s.formula, "Ge1 N1 O1") self.assertTrue(s.symbol_set == ("Ge", "O", "N")) s.replace_species({"Ge": "Si"}) self.assertEqual(s.formula, "Si1 N1 O1") self.assertTrue(s.ntypesp == 3) s.replace_species({"Si": {"Ge": 0.5, "Si": 0.5}}) self.assertEqual(s.formula, "Si0.5 Ge0.5 N1 O1") #this should change the .5Si .5Ge sites to .75Si .25Ge s.replace_species({"Ge": {"Ge": 0.5, "Si": 0.5}}) self.assertEqual(s.formula, "Si0.75 Ge0.25 N1 O1") # In this case, s.ntypesp is ambiguous. # for the time being, we raise AttributeError. with self.assertRaises(AttributeError): s.ntypesp s.remove_species(["Si"]) self.assertEqual(s.formula, "Ge0.25 N1 O1") s.remove_sites([1, 2]) self.assertEqual(s.formula, "Ge0.25") def test_add_site_property(self): s = self.structure s.add_site_property("charge", [4.1, -5]) self.assertEqual(s[0].charge, 4.1) self.assertEqual(s[1].charge, -5) s.add_site_property("magmom", [3, 2]) self.assertEqual(s[0].charge, 4.1) self.assertEqual(s[0].magmom, 3) def test_perturb(self): d = 0.1 pre_perturbation_sites = self.structure.sites[:] self.structure.perturb(distance=d) post_perturbation_sites = self.structure.sites for i, x in enumerate(pre_perturbation_sites): self.assertAlmostEqual(x.distance(post_perturbation_sites[i]), d, 3, "Bad perturbation distance") def test_add_oxidation_states(self): oxidation_states = {"Si": -4} self.structure.add_oxidation_state_by_element(oxidation_states) for site in self.structure: for k in site.species_and_occu.keys(): self.assertEqual(k.oxi_state, oxidation_states[k.symbol], "Wrong oxidation state assigned!") oxidation_states = {"Fe": 2} self.assertRaises(ValueError, self.structure.add_oxidation_state_by_element, oxidation_states) self.structure.add_oxidation_state_by_site([2, -4]) self.assertEqual(self.structure[0].specie.oxi_state, 2) self.assertRaises(ValueError, self.structure.add_oxidation_state_by_site, [1]) def test_remove_oxidation_states(self): co_elem = Element("Co") o_elem = Element("O") co_specie = Specie("Co", 2) o_specie = Specie("O", -2) coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) lattice = Lattice.cubic(10) s_elem = Structure(lattice, [co_elem, o_elem], coords) s_specie = Structure(lattice, [co_specie, o_specie], coords) s_specie.remove_oxidation_states() self.assertEqual(s_elem, s_specie, "Oxidation state remover " "failed") def test_apply_operation(self): op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 90) self.structure.apply_operation(op) self.assertArrayAlmostEqual( self.structure.lattice.matrix, [[0.000000, 3.840198, 0.000000], [-3.325710, 1.920099, 0.000000], [2.217138, -0.000000, 3.135509]], 5) def test_apply_strain(self): s = self.structure initial_coord = s[1].coords s.apply_strain(0.01) self.assertAlmostEqual( s.lattice.abc, (3.8785999130369997, 3.878600984287687, 3.8785999130549516)) self.assertArrayAlmostEqual(s[1].coords, initial_coord * 1.01) a1, b1, c1 = s.lattice.abc s.apply_strain([0.1, 0.2, 0.3]) a2, b2, c2 = s.lattice.abc self.assertAlmostEqual(a2 / a1, 1.1) self.assertAlmostEqual(b2 / b1, 1.2) self.assertAlmostEqual(c2 / c1, 1.3) def test_scale_lattice(self): initial_coord = self.structure[1].coords self.structure.scale_lattice(self.structure.volume * 1.01**3) self.assertArrayAlmostEqual( self.structure.lattice.abc, (3.8785999130369997, 3.878600984287687, 3.8785999130549516)) self.assertArrayAlmostEqual(self.structure[1].coords, initial_coord * 1.01) def test_translate_sites(self): self.structure.translate_sites([0, 1], [0.5, 0.5, 0.5], frac_coords=True) self.assertArrayEqual(self.structure.frac_coords[0], [0.5, 0.5, 0.5]) self.structure.translate_sites([0], [0.5, 0.5, 0.5], frac_coords=False) self.assertArrayAlmostEqual(self.structure.cart_coords[0], [3.38014845, 1.05428585, 2.06775453]) self.structure.translate_sites([0], [0.5, 0.5, 0.5], frac_coords=True, to_unit_cell=False) self.assertArrayAlmostEqual(self.structure.frac_coords[0], [1.00187517, 1.25665291, 1.15946374]) def test_make_supercell(self): self.structure.make_supercell([2, 1, 1]) self.assertEqual(self.structure.formula, "Si4") self.structure.make_supercell([[1, 0, 0], [2, 1, 0], [0, 0, 1]]) self.assertEqual(self.structure.formula, "Si4") self.structure.make_supercell(2) self.assertEqual(self.structure.formula, "Si32") self.assertArrayAlmostEqual(self.structure.lattice.abc, [15.360792, 35.195996, 7.680396], 5) def test_disordered_supercell_primitive_cell(self): l = Lattice.cubic(2) f = [[0.5, 0.5, 0.5]] sp = [{'Si': 0.54738}] s = Structure(l, sp, f) #this supercell often breaks things s.make_supercell([[0, -1, 1], [-1, 1, 0], [1, 1, 1]]) self.assertEqual(len(s.get_primitive_structure()), 1) def test_another_supercell(self): #this is included b/c for some reason the old algo was failing on it s = self.structure.copy() s.make_supercell([[0, 2, 2], [2, 0, 2], [2, 2, 0]]) self.assertEqual(s.formula, "Si32") s = self.structure.copy() s.make_supercell([[0, 2, 0], [1, 0, 0], [0, 0, 1]]) self.assertEqual(s.formula, "Si4") def test_to_from_dict(self): d = self.structure.to_dict s2 = Structure.from_dict(d) self.assertEqual(type(s2), Structure) def test_propertied_structure_mod(self): prop_structure = Structure(self.structure.lattice, ["Si"] * 2, self.structure.frac_coords, site_properties={'magmom': [5, -5]}) prop_structure.append("C", [0.25, 0.25, 0.25]) d = prop_structure.to_dict with warnings.catch_warnings(record=True) as w: # Cause all warnings to always be triggered. warnings.simplefilter("always") s2 = Structure.from_dict(d) self.assertEqual(len(w), 1) self.assertEqual( str(w[0].message), 'Not all sites have property magmom. Missing values are set ' 'to None.')
def apply_transformation(self, structure, return_ranked_list=False): """ Return either a single ordered structure or a sequence of all ordered structures. Args: structure: Structure to order. return_ranked_list (bool): Whether or not multiple structures are returned. If return_ranked_list is a number, that number of structures is returned. Returns: Depending on returned_ranked list, either a transformed structure or a list of dictionaries, where each dictionary is of the form {"structure" = .... , "other_arguments"} The list of ordered structures is ranked by ewald energy / atom, if the input structure is an oxidation state decorated structure. Otherwise, it is ranked by number of sites, with smallest number of sites first. """ try: num_to_return = int(return_ranked_list) except ValueError: num_to_return = 1 if self.occu_tol: species = [dict(d) for d in structure.species_and_occu] # Here, we rescale all occupancies such that they meet the frac # limit. for sp in species: for k, v in sp.items(): sp[k] = float(Fraction(v).limit_denominator(self.occu_tol)) structure = Structure(structure.lattice, species, structure.frac_coords) if self.refine_structure: finder = SpacegroupAnalyzer(structure, self.symm_prec) structure = finder.get_refined_structure() contains_oxidation_state = all( [hasattr(sp, "oxi_state") and sp.oxi_state != 0 for sp in structure.composition.elements] ) if structure.is_ordered: warn("Enumeration skipped for structure with composition {} " "because it is ordered".format(structure.composition)) structures = [structure.copy()] else: adaptor = EnumlibAdaptor( structure, min_cell_size=self.min_cell_size, max_cell_size=self.max_cell_size, symm_prec=self.symm_prec, refine_structure=False, enum_precision_parameter=self.enum_precision_parameter, check_ordered_symmetry=self.check_ordered_symmetry) adaptor.run() structures = adaptor.structures original_latt = structure.lattice inv_latt = np.linalg.inv(original_latt.matrix) ewald_matrices = {} all_structures = [] for s in structures: new_latt = s.lattice transformation = np.dot(new_latt.matrix, inv_latt) transformation = tuple([tuple([int(round(cell)) for cell in row]) for row in transformation]) if contains_oxidation_state: if transformation not in ewald_matrices: s_supercell = structure * transformation ewald = EwaldSummation(s_supercell) ewald_matrices[transformation] = ewald else: ewald = ewald_matrices[transformation] energy = ewald.compute_sub_structure(s) all_structures.append({"num_sites": len(s), "energy": energy, "structure": s}) else: all_structures.append({"num_sites": len(s), "structure": s}) def sort_func(s): return s["energy"] / s["num_sites"] if contains_oxidation_state \ else s["num_sites"] self._all_structures = sorted(all_structures, key=sort_func) if return_ranked_list: return self._all_structures[0:num_to_return] else: return self._all_structures[0]["structure"]
def __init__( self, structure: Structure, overwrite_magmom_mode: Union[OverwriteMagmomMode, str] = "none", round_magmoms: bool = False, detect_valences: bool = False, make_primitive: bool = True, default_magmoms: bool = None, set_net_positive: bool = True, threshold: float = 0.1, ): """ A class which provides a few helpful methods to analyze collinear magnetic structures. If magnetic moments are not defined, moments will be taken either from default_magmoms.yaml (similar to the default magmoms in MPRelaxSet, with a few extra definitions) or from a specie:magmom dict provided by the default_magmoms kwarg. Input magmoms can be replaced using the 'overwrite_magmom_mode' kwarg. This can be: * "none" to do nothing, * "respect_sign" which will overwrite existing magmoms with those from default_magmoms but will keep sites with positive magmoms positive, negative magmoms negative and zero magmoms zero, * "respect_zeros", which will give a ferromagnetic structure (all positive magmoms from default_magmoms) but still keep sites with zero magmoms as zero, * "replace_all" which will try to guess initial magmoms for all sites in the structure irrespective of input structure (this is most suitable for an initial DFT calculation), * "replace_all_if_undefined" is the same as "replace_all" but only if no magmoms are defined in input structure, otherwise it will respect existing magmoms. * "normalize" will normalize magmoms to unity, but will respect sign (used for comparing orderings), magmoms < theshold will be set to zero :param structure: Structure object :param overwrite_magmom_mode (str): default "none" :param round_magmoms (int or bool): will round input magmoms to specified number of decimal places if integer is supplied, if set to a float will try and group magmoms together using a kernel density estimator of provided width, and extracting peaks of the estimator :param detect_valences (bool): if True, will attempt to assign valences to input structure :param make_primitive (bool): if True, will transform to primitive magnetic cell :param default_magmoms (dict): (optional) dict specifying default magmoms :param set_net_positive (bool): if True, will change sign of magnetic moments such that the net magnetization is positive. Argument will be ignored if mode "respect_sign" is used. :param threshold (float): number (in Bohr magnetons) below which magmoms will be rounded to zero, default of 0.1 can probably be increased for many magnetic systems, depending on your application """ if default_magmoms: self.default_magmoms = default_magmoms else: self.default_magmoms = DEFAULT_MAGMOMS structure = structure.copy() # check for disorder if not structure.is_ordered: raise NotImplementedError( "Not implemented for disordered structures, " "make ordered approximation first.") if detect_valences: trans = AutoOxiStateDecorationTransformation() bva = BVAnalyzer() try: structure = trans.apply_transformation(structure) except ValueError: warnings.warn("Could not assign valences " "for {}".format( structure.composition.reduced_formula)) # check to see if structure has magnetic moments # on site properties or species spin properties, # prioritize site properties has_magmoms = bool(structure.site_properties.get("magmom", False)) has_spin = False for comp in structure.species_and_occu: for sp, occu in comp.items(): if getattr(sp, "spin", False): has_spin = True # perform input sanitation ... # rest of class will assume magnetic moments # are stored on site properties: # this is somewhat arbitrary, arguments can # be made for both approaches if has_magmoms and has_spin: raise ValueError("Structure contains magnetic moments on both " "magmom site properties and spin species " "properties. This is ambiguous. Remove one or " "the other.") elif has_magmoms: if None in structure.site_properties["magmom"]: warnings.warn("Be careful with mixing types in your magmom " "site properties. Any 'None' magmoms have been " "replaced with zero.") magmoms = [ m if m else 0 for m in structure.site_properties["magmom"] ] elif has_spin: magmoms = [getattr(sp, "spin", 0) for sp in structure.species] structure.remove_spin() else: # no magmoms present, add zero magmoms for now magmoms = [0] * len(structure) # and overwrite magmoms with default magmoms later unless otherwise stated if overwrite_magmom_mode == "replace_all_if_undefined": overwrite_magmom_mode = "replace_all" # test to see if input structure has collinear magmoms self.is_collinear = Magmom.are_collinear(magmoms) if not self.is_collinear: warnings.warn( "This class is not designed to be used with " "non-collinear structures. If your structure is " "only slightly non-collinear (e.g. canted) may still " "give useful results, but use with caution.") # this is for collinear structures only, make sure magmoms # are all floats magmoms = list(map(float, magmoms)) # set properties that should be done /before/ we process input magmoms self.total_magmoms = sum(magmoms) self.magnetization = sum(magmoms) / structure.volume # round magmoms below threshold to zero magmoms = [m if abs(m) > threshold else 0 for m in magmoms] # overwrite existing magmoms with default_magmoms if overwrite_magmom_mode not in ( "none", "respect_sign", "respect_zeros", "replace_all", "replace_all_if_undefined", "normalize", ): raise ValueError("Unsupported mode.") for idx, site in enumerate(structure): if site.species_string in self.default_magmoms: # look for species first, e.g. Fe2+ default_magmom = self.default_magmoms[site.species_string] elif (isinstance(site.specie, Specie) and str(site.specie.element) in self.default_magmoms): # look for element, e.g. Fe default_magmom = self.default_magmoms[str(site.specie.element)] else: default_magmom = 0 # overwrite_magmom_mode = "respect_sign" will change magnitude of # existing moments only, and keep zero magmoms as # zero: it will keep the magnetic ordering intact if overwrite_magmom_mode == "respect_sign": set_net_positive = False if magmoms[idx] > 0: magmoms[idx] = default_magmom elif magmoms[idx] < 0: magmoms[idx] = -default_magmom # overwrite_magmom_mode = "respect_zeros" will give a ferromagnetic # structure but will keep zero magmoms as zero elif overwrite_magmom_mode == "respect_zeros": if magmoms[idx] != 0: magmoms[idx] = default_magmom # overwrite_magmom_mode = "replace_all" will ignore input magmoms # and give a ferromagnetic structure with magnetic # moments on *all* atoms it thinks could be magnetic elif overwrite_magmom_mode == "replace_all": magmoms[idx] = default_magmom # overwrite_magmom_mode = "normalize" set magmoms magnitude to 1 elif overwrite_magmom_mode == "normalize": if magmoms[idx] != 0: magmoms[idx] = int(magmoms[idx] / abs(magmoms[idx])) # round magmoms, used to smooth out computational data magmoms = (self._round_magmoms(magmoms, round_magmoms) if round_magmoms else magmoms) if set_net_positive: sign = np.sum(magmoms) if sign < 0: magmoms = -np.array(magmoms) structure.add_site_property("magmom", magmoms) if make_primitive: structure = structure.get_primitive_structure(use_site_props=True) self.structure = structure
class StructureTest(unittest.TestCase): def setUp(self): self.si = Element("Si") coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) self.lattice = Lattice([[3.8401979337, 0.00, 0.00], [1.9200989668, 3.3257101909, 0.00], [0.00, -2.2171384943, 3.1355090603]]) self.struct = Structure(self.lattice, [self.si, self.si], coords) self.assertEqual(len(self.struct), 2, "Wrong number of sites in structure!") self.assertTrue(self.struct.is_ordered) coords = list() coords.append([0, 0, 0]) coords.append([0., 0, 0.0000001]) self.assertRaises(StructureError, Structure, self.lattice, [self.si, self.si], coords, True) self.propertied_structure = Structure(self.lattice, [self.si, self.si], coords, site_properties={'magmom': [5, -5]}) def test_volume_and_density(self): self.assertAlmostEqual(self.struct.volume, 40.04, 2, "Volume wrong!") self.assertAlmostEqual(self.struct.density, 2.33, 2, "Incorrect density") def test_specie_init(self): coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) s = Structure(self.lattice, [{Specie('O', -2):1.0}, {Specie('Mg', 2):0.8}], coords) self.assertEqual(str(s.composition), 'Mg2+0.8 O2-1') def test_get_sorted_structure(self): coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) s = Structure(self.lattice, ["O", "Li"], coords, site_properties={'charge': [-2, 1]}) sorted_s = s.get_sorted_structure() self.assertEqual(sorted_s[0].species_and_occu, Composition("Li")) self.assertEqual(sorted_s[1].species_and_occu, Composition("O")) self.assertEqual(sorted_s[0].charge, 1) self.assertEqual(sorted_s[1].charge, -2) def test_fractional_occupations(self): coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) s = Structure(self.lattice, [{Element('O'):1.0}, {Element('Mg'):0.8}], coords) self.assertEqual(str(s.composition), 'Mg0.8 O1') self.assertFalse(s.is_ordered) def test_get_distance(self): self.assertAlmostEqual(self.struct.get_distance(0, 1), 2.35, 2, "Distance calculated wrongly!") pt = [0.9, 0.9, 0.8] self.assertAlmostEqual(self.struct[0].distance_from_point(pt), 1.50332963784, 2, "Distance calculated wrongly!") def test_to_dict(self): si = Specie("Si", 4) mn = Element("Mn") coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) struct = Structure(self.lattice, [{si:0.5, mn:0.5}, {si:0.5}], coords) self.assertIn("lattice", struct.to_dict) self.assertIn("sites", struct.to_dict) d = self.propertied_structure.to_dict self.assertEqual(d['sites'][0]['properties']['magmom'], 5) coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) s = Structure(self.lattice, [{Specie('O', -2, properties={"spin":3}):1.0}, {Specie('Mg', 2, properties={"spin":2}):0.8}], coords, site_properties={'magmom': [5, -5]}) d = s.to_dict self.assertEqual(d['sites'][0]['properties']['magmom'], 5) self.assertEqual(d['sites'][0]['species'][0]['properties']['spin'], 3) def test_from_dict(self): d = self.propertied_structure.to_dict s = Structure.from_dict(d) self.assertEqual(s[0].magmom, 5) d = {'lattice': {'a': 3.8401979337, 'volume': 40.044794644251596, 'c': 3.8401979337177736, 'b': 3.840198994344244, 'matrix': [[3.8401979337, 0.0, 0.0], [1.9200989668, 3.3257101909, 0.0], [0.0, -2.2171384943, 3.1355090603]], 'alpha': 119.9999908639842, 'beta': 90.0, 'gamma': 60.000009137322195}, 'sites': [{'properties': {'magmom': 5}, 'abc': [0.0, 0.0, 0.0], 'occu': 1.0, 'species': [{'occu': 1.0, 'oxidation_state':-2, 'properties': {'spin': 3}, 'element': 'O'}], 'label': 'O2-', 'xyz': [0.0, 0.0, 0.0]}, {'properties': {'magmom':-5}, 'abc': [0.75, 0.5, 0.75], 'occu': 0.8, 'species': [{'occu': 0.8, 'oxidation_state': 2, 'properties': {'spin': 2}, 'element': 'Mg'}], 'label': 'Mg2+:0.800', 'xyz': [3.8401979336749994, 1.2247250003039056e-06, 2.351631795225]}]} s = Structure.from_dict(d) self.assertEqual(s[0].magmom, 5) self.assertEqual(s[0].specie.spin, 3) def test_site_properties(self): site_props = self.propertied_structure.site_properties self.assertEqual(site_props['magmom'], [5, -5]) self.assertEqual(self.propertied_structure[0].magmom, 5) self.assertEqual(self.propertied_structure[1].magmom, -5) def test_copy(self): new_struct = self.propertied_structure.copy(site_properties={'charge': [2, 3]}) self.assertEqual(new_struct[0].magmom, 5) self.assertEqual(new_struct[1].magmom, -5) self.assertEqual(new_struct[0].charge, 2) self.assertEqual(new_struct[1].charge, 3) coords = list() coords.append([0, 0, 0]) coords.append([0., 0, 0.0000001]) structure = Structure(self.lattice, ["O", "Si"], coords, site_properties={'magmom': [5, -5]}) new_struct = structure.copy(site_properties={'charge': [2, 3]}, sanitize=True) self.assertEqual(new_struct[0].magmom, -5) self.assertEqual(new_struct[1].magmom, 5) self.assertEqual(new_struct[0].charge, 3) self.assertEqual(new_struct[1].charge, 2) self.assertAlmostEqual(new_struct.volume, structure.volume) def test_interpolate(self): coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) struct = Structure(self.lattice, [self.si, self.si], coords) coords2 = list() coords2.append([0, 0, 0]) coords2.append([0.5, 0.5, 0.5]) struct2 = Structure(self.struct.lattice, [self.si, self.si], coords2) int_s = struct.interpolate(struct2, 10) for s in int_s: self.assertIsNotNone(s, "Interpolation Failed!") self.assertTrue((int_s[1][1].frac_coords == [0.725, 0.5, 0.725]).all()) badlattice = [[1, 0.00, 0.00], [0, 1, 0.00], [0.00, 0, 1]] struct2 = Structure(badlattice, [self.si, self.si], coords2) self.assertRaises(ValueError, struct.interpolate, struct2) coords2 = list() coords2.append([0, 0, 0]) coords2.append([0.5, 0.5, 0.5]) struct2 = Structure(self.struct.lattice, [self.si, Element("Fe")], coords2) self.assertRaises(ValueError, struct.interpolate, struct2) def test_get_primitive_structure(self): coords = [[0, 0, 0], [0.5, 0.5, 0], [0, 0.5, 0.5], [0.5, 0, 0.5]] fcc_ag = Structure(Lattice.cubic(4.09), ["Ag"] * 4, coords) self.assertEqual(len(fcc_ag.get_primitive_structure()), 1) coords = [[0, 0, 0], [0.5, 0.5, 0.5]] bcc_li = Structure(Lattice.cubic(4.09), ["Li"] * 2, coords) self.assertEqual(len(bcc_li.get_primitive_structure()), 1) def test_primitive_structure_volume_check(self): l = Lattice.tetragonal(10, 30) coords = [[0.5, 0.8, 0], [0.5, 0.2, 0], [0.5, 0.8, 0.333], [0.5, 0.5, 0.333], [0.5, 0.5, 0.666], [0.5, 0.2, 0.666]] s = Structure(l, ["Ag"] * 6, coords) sprim = s.get_primitive_structure(tolerance=0.1) self.assertEqual(len(sprim), 6) def test_get_all_neighbors_and_get_neighbors(self): s = self.struct r = random.uniform(3, 6) all_nn = s.get_all_neighbors(r) for i in range(len(s)): self.assertEqual(len(all_nn[i]), len(s.get_neighbors(s[i], r))) def test_get_dist_matrix(self): ans = [[0., 2.3516318], [2.3516318, 0.]] self.assertTrue(np.allclose(self.struct.distance_matrix, ans))
class StructureTest(PymatgenTest): def setUp(self): coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) lattice = Lattice([[3.8401979337, 0.00, 0.00], [1.9200989668, 3.3257101909, 0.00], [0.00, -2.2171384943, 3.1355090603]]) self.structure = Structure(lattice, ["Si", "Si"], coords) def test_mutable_sequence_methods(self): s = self.structure s[0] = "Fe" self.assertEqual(s.formula, "Fe1 Si1") s[0] = "Fe", [0.5, 0.5, 0.5] self.assertEqual(s.formula, "Fe1 Si1") self.assertArrayAlmostEqual(s[0].frac_coords, [0.5, 0.5, 0.5]) s.reverse() self.assertEqual(s[0].specie, Element("Si")) self.assertArrayAlmostEqual(s[0].frac_coords, [0.75, 0.5, 0.75]) s[0] = {"Mn": 0.5} self.assertEqual(s.formula, "Mn0.5 Fe1") del s[1] self.assertEqual(s.formula, "Mn0.5") s[0] = "Fe", [0.9, 0.9, 0.9], {"magmom": 5} self.assertEqual(s.formula, "Fe1") self.assertEqual(s[0].magmom, 5) # Test atomic replacement. s["Fe"] = "Mn" self.assertEqual(s.formula, "Mn1") # Test slice replacement. s = PymatgenTest.get_structure("Li2O") s[1:3] = "S" self.assertEqual(s.formula, "Li1 S2") def test_non_hash(self): self.assertRaises(TypeError, dict, [(self.structure, 1)]) def test_sort(self): s = self.structure s[0] = "F" s.sort() self.assertEqual(s[0].species_string, "Si") self.assertEqual(s[1].species_string, "F") s.sort(key=lambda site: site.species_string) self.assertEqual(s[0].species_string, "F") self.assertEqual(s[1].species_string, "Si") s.sort(key=lambda site: site.species_string, reverse=True) self.assertEqual(s[0].species_string, "Si") self.assertEqual(s[1].species_string, "F") def test_append_insert_remove_replace(self): s = self.structure s.insert(1, "O", [0.5, 0.5, 0.5]) self.assertEqual(s.formula, "Si2 O1") self.assertTrue(s.ntypesp == 2) self.assertTrue(s.symbol_set == ("Si", "O")) self.assertTrue(s.indices_from_symbol("Si") == (0, 2)) self.assertTrue(s.indices_from_symbol("O") == (1, )) del s[2] self.assertEqual(s.formula, "Si1 O1") self.assertTrue(s.indices_from_symbol("Si") == (0, )) self.assertTrue(s.indices_from_symbol("O") == (1, )) s.append("N", [0.25, 0.25, 0.25]) self.assertEqual(s.formula, "Si1 N1 O1") self.assertTrue(s.ntypesp == 3) self.assertTrue(s.symbol_set == ("Si", "O", "N")) self.assertTrue(s.indices_from_symbol("Si") == (0, )) self.assertTrue(s.indices_from_symbol("O") == (1, )) self.assertTrue(s.indices_from_symbol("N") == (2, )) s[0] = "Ge" self.assertEqual(s.formula, "Ge1 N1 O1") self.assertTrue(s.symbol_set == ("Ge", "O", "N")) s.replace_species({"Ge": "Si"}) self.assertEqual(s.formula, "Si1 N1 O1") self.assertTrue(s.ntypesp == 3) s.replace_species({"Si": {"Ge": 0.5, "Si": 0.5}}) self.assertEqual(s.formula, "Si0.5 Ge0.5 N1 O1") #this should change the .5Si .5Ge sites to .75Si .25Ge s.replace_species({"Ge": {"Ge": 0.5, "Si": 0.5}}) self.assertEqual(s.formula, "Si0.75 Ge0.25 N1 O1") # In this case, s.ntypesp is ambiguous. # for the time being, we raise AttributeError. with self.assertRaises(AttributeError): s.ntypesp s.remove_species(["Si"]) self.assertEqual(s.formula, "Ge0.25 N1 O1") s.remove_sites([1, 2]) self.assertEqual(s.formula, "Ge0.25") def test_add_site_property(self): s = self.structure s.add_site_property("charge", [4.1, -5]) self.assertEqual(s[0].charge, 4.1) self.assertEqual(s[1].charge, -5) s.add_site_property("magmom", [3, 2]) self.assertEqual(s[0].charge, 4.1) self.assertEqual(s[0].magmom, 3) def test_propertied_structure(self): #Make sure that site properties are set to None for missing values. s = self.structure s.add_site_property("charge", [4.1, -5]) s.append("Li", [0.3, 0.3, 0.3]) self.assertEqual(len(s.site_properties["charge"]), 3) def test_perturb(self): d = 0.1 pre_perturbation_sites = self.structure.sites[:] self.structure.perturb(distance=d) post_perturbation_sites = self.structure.sites for i, x in enumerate(pre_perturbation_sites): self.assertAlmostEqual(x.distance(post_perturbation_sites[i]), d, 3, "Bad perturbation distance") def test_add_oxidation_states(self): oxidation_states = {"Si": -4} self.structure.add_oxidation_state_by_element(oxidation_states) for site in self.structure: for k in site.species_and_occu.keys(): self.assertEqual(k.oxi_state, oxidation_states[k.symbol], "Wrong oxidation state assigned!") oxidation_states = {"Fe": 2} self.assertRaises(ValueError, self.structure.add_oxidation_state_by_element, oxidation_states) self.structure.add_oxidation_state_by_site([2, -4]) self.assertEqual(self.structure[0].specie.oxi_state, 2) self.assertRaises(ValueError, self.structure.add_oxidation_state_by_site, [1]) def test_remove_oxidation_states(self): co_elem = Element("Co") o_elem = Element("O") co_specie = Specie("Co", 2) o_specie = Specie("O", -2) coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) lattice = Lattice.cubic(10) s_elem = Structure(lattice, [co_elem, o_elem], coords) s_specie = Structure(lattice, [co_specie, o_specie], coords) s_specie.remove_oxidation_states() self.assertEqual(s_elem, s_specie, "Oxidation state remover " "failed") def test_apply_operation(self): op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 90) s = self.structure.copy() s.apply_operation(op) self.assertArrayAlmostEqual( s.lattice.matrix, [[0.000000, 3.840198, 0.000000], [-3.325710, 1.920099, 0.000000], [2.217138, -0.000000, 3.135509]], 5) op = SymmOp([[1, 1, 0, 0.5], [1, 0, 0, 0.5], [0, 0, 1, 0.5], [0, 0, 0, 1]]) s = self.structure.copy() s.apply_operation(op, fractional=True) self.assertArrayAlmostEqual( s.lattice.matrix, [[5.760297, 3.325710, 0.000000], [3.840198, 0.000000, 0.000000], [0.000000, -2.217138, 3.135509]], 5) def test_apply_strain(self): s = self.structure initial_coord = s[1].coords s.apply_strain(0.01) self.assertAlmostEqual( s.lattice.abc, (3.8785999130369997, 3.878600984287687, 3.8785999130549516)) self.assertArrayAlmostEqual(s[1].coords, initial_coord * 1.01) a1, b1, c1 = s.lattice.abc s.apply_strain([0.1, 0.2, 0.3]) a2, b2, c2 = s.lattice.abc self.assertAlmostEqual(a2 / a1, 1.1) self.assertAlmostEqual(b2 / b1, 1.2) self.assertAlmostEqual(c2 / c1, 1.3) def test_scale_lattice(self): initial_coord = self.structure[1].coords self.structure.scale_lattice(self.structure.volume * 1.01**3) self.assertArrayAlmostEqual( self.structure.lattice.abc, (3.8785999130369997, 3.878600984287687, 3.8785999130549516)) self.assertArrayAlmostEqual(self.structure[1].coords, initial_coord * 1.01) def test_translate_sites(self): self.structure.translate_sites([0, 1], [0.5, 0.5, 0.5], frac_coords=True) self.assertArrayEqual(self.structure.frac_coords[0], [0.5, 0.5, 0.5]) self.structure.translate_sites([0], [0.5, 0.5, 0.5], frac_coords=False) self.assertArrayAlmostEqual(self.structure.cart_coords[0], [3.38014845, 1.05428585, 2.06775453]) self.structure.translate_sites([0], [0.5, 0.5, 0.5], frac_coords=True, to_unit_cell=False) self.assertArrayAlmostEqual(self.structure.frac_coords[0], [1.00187517, 1.25665291, 1.15946374]) def test_mul(self): self.structure *= [2, 1, 1] self.assertEqual(self.structure.formula, "Si4") s = [2, 1, 1] * self.structure self.assertEqual(s.formula, "Si8") self.assertIsInstance(s, Structure) s = self.structure * [[1, 0, 0], [2, 1, 0], [0, 0, 2]] self.assertEqual(s.formula, "Si8") self.assertArrayAlmostEqual(s.lattice.abc, [7.6803959, 17.5979979, 7.6803959]) def test_make_supercell(self): self.structure.make_supercell([2, 1, 1]) self.assertEqual(self.structure.formula, "Si4") self.structure.make_supercell([[1, 0, 0], [2, 1, 0], [0, 0, 1]]) self.assertEqual(self.structure.formula, "Si4") self.structure.make_supercell(2) self.assertEqual(self.structure.formula, "Si32") self.assertArrayAlmostEqual(self.structure.lattice.abc, [15.360792, 35.195996, 7.680396], 5) def test_disordered_supercell_primitive_cell(self): l = Lattice.cubic(2) f = [[0.5, 0.5, 0.5]] sp = [{'Si': 0.54738}] s = Structure(l, sp, f) #this supercell often breaks things s.make_supercell([[0, -1, 1], [-1, 1, 0], [1, 1, 1]]) self.assertEqual(len(s.get_primitive_structure()), 1) def test_another_supercell(self): #this is included b/c for some reason the old algo was failing on it s = self.structure.copy() s.make_supercell([[0, 2, 2], [2, 0, 2], [2, 2, 0]]) self.assertEqual(s.formula, "Si32") s = self.structure.copy() s.make_supercell([[0, 2, 0], [1, 0, 0], [0, 0, 1]]) self.assertEqual(s.formula, "Si4") def test_to_from_dict(self): d = self.structure.as_dict() s2 = Structure.from_dict(d) self.assertEqual(type(s2), Structure) def test_to_from_abivars(self): """Test as_dict, from_dict with fmt == abivars.""" d = self.structure.as_dict(fmt="abivars") s2 = Structure.from_dict(d, fmt="abivars") self.assertEqual(s2, self.structure) self.assertEqual(type(s2), Structure) def test_to_from_file_string(self): for fmt in ["cif", "json", "poscar", "cssr", "yaml", "xsf"]: s = self.structure.to(fmt=fmt) self.assertIsNotNone(s) ss = Structure.from_str(s, fmt=fmt) self.assertArrayAlmostEqual( ss.lattice.lengths_and_angles, self.structure.lattice.lengths_and_angles, decimal=5) self.assertArrayAlmostEqual(ss.frac_coords, self.structure.frac_coords) self.assertIsInstance(ss, Structure) self.structure.to(filename="POSCAR.testing") self.assertTrue(os.path.exists("POSCAR.testing")) os.remove("POSCAR.testing") self.structure.to(filename="structure_testing.json") self.assertTrue(os.path.exists("structure_testing.json")) s = Structure.from_file("structure_testing.json") self.assertEqual(s, self.structure) os.remove("structure_testing.json") def test_from_spacegroup(self): s1 = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3), ["Li", "O"], [[0.25, 0.25, 0.25], [0, 0, 0]]) self.assertEqual(s1.formula, "Li8 O4") s2 = Structure.from_spacegroup(225, Lattice.cubic(3), ["Li", "O"], [[0.25, 0.25, 0.25], [0, 0, 0]]) self.assertEqual(s1, s2) s2 = Structure.from_spacegroup(225, Lattice.cubic(3), ["Li", "O"], [[0.25, 0.25, 0.25], [0, 0, 0]], site_properties={"charge": [1, -2]}) self.assertEqual(sum(s2.site_properties["charge"]), 0) s = Structure.from_spacegroup("Pm-3m", Lattice.cubic(3), ["Cs", "Cl"], [[0, 0, 0], [0.5, 0.5, 0.5]]) self.assertEqual(s.formula, "Cs1 Cl1") self.assertRaises(ValueError, Structure.from_spacegroup, "Pm-3m", Lattice.tetragonal(1, 3), ["Cs", "Cl"], [[0, 0, 0], [0.5, 0.5, 0.5]]) self.assertRaises(ValueError, Structure.from_spacegroup, "Pm-3m", Lattice.cubic(3), ["Cs"], [[0, 0, 0], [0.5, 0.5, 0.5]]) def test_from_magnetic_spacegroup(self): # AFM MnF s1 = Structure.from_magnetic_spacegroup( "P4_2'/mnm'", Lattice.tetragonal(4.87, 3.30), ["Mn", "F"], [[0, 0, 0], [0.30, 0.30, 0.00]], {'magmom': [4, 0]}) self.assertEqual(s1.formula, "Mn2 F4") self.assertEqual(sum(map(float, s1.site_properties['magmom'])), 0) self.assertEqual(max(map(float, s1.site_properties['magmom'])), 4) self.assertEqual(min(map(float, s1.site_properties['magmom'])), -4) # AFM LaMnO3, ordered on (001) planes s2 = Structure.from_magnetic_spacegroup( "Pn'ma'", Lattice.orthorhombic(5.75, 7.66, 5.53), ["La", "Mn", "O", "O"], [[0.05, 0.25, 0.99], [0.00, 0.00, 0.50], [0.48, 0.25, 0.08], [0.31, 0.04, 0.72]], {'magmom': [0, Magmom([4, 0, 0]), 0, 0]}) self.assertEqual(s2.formula, "La4 Mn4 O12") self.assertEqual(sum(map(float, s2.site_properties['magmom'])), 0) self.assertEqual(max(map(float, s2.site_properties['magmom'])), 4) self.assertEqual(min(map(float, s2.site_properties['magmom'])), -4) def test_merge_sites(self): species = [{ 'Ag': 0.5 }, { 'Cl': 0.25 }, { 'Cl': 0.1 }, { 'Ag': 0.5 }, { 'F': 0.15 }, { 'F': 0.1 }] coords = [[0, 0, 0], [0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [0, 0, 0], [0.5, 0.5, 1.501], [0.5, 0.5, 1.501]] s = Structure(Lattice.cubic(1), species, coords) s.merge_sites(mode="s") self.assertEqual(s[0].specie.symbol, 'Ag') self.assertEqual(s[1].species_and_occu, Composition({ 'Cl': 0.35, 'F': 0.25 })) self.assertArrayAlmostEqual(s[1].frac_coords, [.5, .5, .5005]) # Test for TaS2 with spacegroup 166 in 160 setting. l = Lattice.from_lengths_and_angles([3.374351, 3.374351, 20.308941], [90.000000, 90.000000, 120.000000]) species = ["Ta", "S", "S"] coords = [[0.000000, 0.000000, 0.944333], [0.333333, 0.666667, 0.353424], [0.666667, 0.333333, 0.535243]] tas2 = Structure.from_spacegroup(160, l, species, coords) assert len(tas2) == 13 tas2.merge_sites(mode="d") assert len(tas2) == 9 l = Lattice.from_lengths_and_angles([3.587776, 3.587776, 19.622793], [90.000000, 90.000000, 120.000000]) species = ["Na", "V", "S", "S"] coords = [[0.333333, 0.666667, 0.165000], [0.000000, 0.000000, 0.998333], [0.333333, 0.666667, 0.399394], [0.666667, 0.333333, 0.597273]] navs2 = Structure.from_spacegroup(160, l, species, coords) assert len(navs2) == 18 navs2.merge_sites(mode="d") assert len(navs2) == 12 def test_properties(self): self.assertEqual(self.structure.num_sites, len(self.structure)) self.structure.make_supercell(2) self.structure[1] = "C" sites = list(self.structure.group_by_types()) self.assertEqual(sites[-1].specie.symbol, "C") self.structure.add_oxidation_state_by_element({"Si": 4, "C": 2}) self.assertEqual(self.structure.charge, 62) def test_set_item(self): s = self.structure.copy() s[0] = "C" self.assertEqual(s.formula, "Si1 C1") s[(0, 1)] = "Ge" self.assertEqual(s.formula, "Ge2") s[0:2] = "Sn" self.assertEqual(s.formula, "Sn2") s = self.structure.copy() s["Si"] = "C" self.assertEqual(s.formula, "C2") s["C"] = "C0.25Si0.5" self.assertEqual(s.formula, "Si1 C0.5") s["C"] = "C0.25Si0.5" self.assertEqual(s.formula, "Si1.25 C0.125") def test_init_error(self): self.assertRaises(StructureError, Structure, Lattice.cubic(3), ["Si"], [[0, 0, 0], [0.5, 0.5, 0.5]]) def test_from_sites(self): self.structure.add_site_property("hello", [1, 2]) s = Structure.from_sites(self.structure, to_unit_cell=True) self.assertEqual(s.site_properties["hello"][1], 2) def test_magic(self): s = Structure.from_sites(self.structure) self.assertEqual(s, self.structure) self.assertNotEqual(s, None) s.apply_strain(0.5) self.assertNotEqual(s, self.structure) self.assertNotEqual(self.structure * 2, self.structure)
class StructureTest(PymatgenTest): def setUp(self): coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) lattice = Lattice([[3.8401979337, 0.00, 0.00], [1.9200989668, 3.3257101909, 0.00], [0.00, -2.2171384943, 3.1355090603]]) self.structure = Structure(lattice, ["Si", "Si"], coords) def test_mutable_sequence_methods(self): s = self.structure s[0] = "Fe" self.assertEqual(s.formula, "Fe1 Si1") s[0] = "Fe", [0.5, 0.5, 0.5] self.assertEqual(s.formula, "Fe1 Si1") self.assertArrayAlmostEqual(s[0].frac_coords, [0.5, 0.5, 0.5]) s.reverse() self.assertEqual(s[0].specie, Element("Si")) self.assertArrayAlmostEqual(s[0].frac_coords, [0.75, 0.5, 0.75]) s[0] = {"Mn": 0.5} self.assertEqual(s.formula, "Mn0.5 Fe1") del s[1] self.assertEqual(s.formula, "Mn0.5") s[0] = "Fe", [0.9, 0.9, 0.9], {"magmom": 5} self.assertEqual(s.formula, "Fe1") self.assertEqual(s[0].magmom, 5) def test_non_hash(self): self.assertRaises(TypeError, dict, [(self.structure, 1)]) def test_sort(self): s = self.structure s[0] = "F" s.sort() self.assertEqual(s[0].species_string, "Si") self.assertEqual(s[1].species_string, "F") s.sort(key=lambda site: site.species_string) self.assertEqual(s[0].species_string, "F") self.assertEqual(s[1].species_string, "Si") s.sort(key=lambda site: site.species_string, reverse=True) self.assertEqual(s[0].species_string, "Si") self.assertEqual(s[1].species_string, "F") def test_append_insert_remove_replace(self): s = self.structure s.insert(1, "O", [0.5, 0.5, 0.5]) self.assertEqual(s.formula, "Si2 O1") self.assertTrue(s.ntypesp == 2) self.assertTrue(s.symbol_set == ("Si", "O")) self.assertTrue(s.indices_from_symbol("Si") == (0, 2)) self.assertTrue(s.indices_from_symbol("O") == (1, )) del s[2] self.assertEqual(s.formula, "Si1 O1") self.assertTrue(s.indices_from_symbol("Si") == (0, )) self.assertTrue(s.indices_from_symbol("O") == (1, )) s.append("N", [0.25, 0.25, 0.25]) self.assertEqual(s.formula, "Si1 N1 O1") self.assertTrue(s.ntypesp == 3) self.assertTrue(s.symbol_set == ("Si", "O", "N")) self.assertTrue(s.indices_from_symbol("Si") == (0, )) self.assertTrue(s.indices_from_symbol("O") == (1, )) self.assertTrue(s.indices_from_symbol("N") == (2, )) s[0] = "Ge" self.assertEqual(s.formula, "Ge1 N1 O1") self.assertTrue(s.symbol_set == ("Ge", "O", "N")) s.replace_species({"Ge": "Si"}) self.assertEqual(s.formula, "Si1 N1 O1") self.assertTrue(s.ntypesp == 3) s.replace_species({"Si": {"Ge": 0.5, "Si": 0.5}}) self.assertEqual(s.formula, "Si0.5 Ge0.5 N1 O1") #this should change the .5Si .5Ge sites to .75Si .25Ge s.replace_species({"Ge": {"Ge": 0.5, "Si": 0.5}}) self.assertEqual(s.formula, "Si0.75 Ge0.25 N1 O1") # In this case, s.ntypesp is ambiguous. # for the time being, we raise AttributeError. with self.assertRaises(AttributeError): s.ntypesp s.remove_species(["Si"]) self.assertEqual(s.formula, "Ge0.25 N1 O1") s.remove_sites([1, 2]) self.assertEqual(s.formula, "Ge0.25") def test_add_site_property(self): s = self.structure s.add_site_property("charge", [4.1, -5]) self.assertEqual(s[0].charge, 4.1) self.assertEqual(s[1].charge, -5) s.add_site_property("magmom", [3, 2]) self.assertEqual(s[0].charge, 4.1) self.assertEqual(s[0].magmom, 3) def test_propertied_structure(self): #Make sure that site properties are set to None for missing values. s = self.structure s.add_site_property("charge", [4.1, -5]) s.append("Li", [0.3, 0.3, 0.3]) self.assertEqual(len(s.site_properties["charge"]), 3) def test_perturb(self): d = 0.1 pre_perturbation_sites = self.structure.sites[:] self.structure.perturb(distance=d) post_perturbation_sites = self.structure.sites for i, x in enumerate(pre_perturbation_sites): self.assertAlmostEqual(x.distance(post_perturbation_sites[i]), d, 3, "Bad perturbation distance") def test_add_oxidation_states(self): oxidation_states = {"Si": -4} self.structure.add_oxidation_state_by_element(oxidation_states) for site in self.structure: for k in site.species_and_occu.keys(): self.assertEqual(k.oxi_state, oxidation_states[k.symbol], "Wrong oxidation state assigned!") oxidation_states = {"Fe": 2} self.assertRaises(ValueError, self.structure.add_oxidation_state_by_element, oxidation_states) self.structure.add_oxidation_state_by_site([2, -4]) self.assertEqual(self.structure[0].specie.oxi_state, 2) self.assertRaises(ValueError, self.structure.add_oxidation_state_by_site, [1]) def test_remove_oxidation_states(self): co_elem = Element("Co") o_elem = Element("O") co_specie = Specie("Co", 2) o_specie = Specie("O", -2) coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) lattice = Lattice.cubic(10) s_elem = Structure(lattice, [co_elem, o_elem], coords) s_specie = Structure(lattice, [co_specie, o_specie], coords) s_specie.remove_oxidation_states() self.assertEqual(s_elem, s_specie, "Oxidation state remover " "failed") def test_apply_operation(self): op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 90) s = self.structure.copy() s.apply_operation(op) self.assertArrayAlmostEqual( s.lattice.matrix, [[0.000000, 3.840198, 0.000000], [-3.325710, 1.920099, 0.000000], [2.217138, -0.000000, 3.135509]], 5) op = SymmOp([[1, 1, 0, 0.5], [1, 0, 0, 0.5], [0, 0, 1, 0.5], [0, 0, 0, 1]]) s = self.structure.copy() s.apply_operation(op, fractional=True) self.assertArrayAlmostEqual( s.lattice.matrix, [[5.760297, 3.325710, 0.000000], [3.840198, 0.000000, 0.000000], [0.000000, -2.217138, 3.135509]], 5) def test_apply_strain(self): s = self.structure initial_coord = s[1].coords s.apply_strain(0.01) self.assertAlmostEqual( s.lattice.abc, (3.8785999130369997, 3.878600984287687, 3.8785999130549516)) self.assertArrayAlmostEqual(s[1].coords, initial_coord * 1.01) a1, b1, c1 = s.lattice.abc s.apply_strain([0.1, 0.2, 0.3]) a2, b2, c2 = s.lattice.abc self.assertAlmostEqual(a2 / a1, 1.1) self.assertAlmostEqual(b2 / b1, 1.2) self.assertAlmostEqual(c2 / c1, 1.3) def test_scale_lattice(self): initial_coord = self.structure[1].coords self.structure.scale_lattice(self.structure.volume * 1.01**3) self.assertArrayAlmostEqual( self.structure.lattice.abc, (3.8785999130369997, 3.878600984287687, 3.8785999130549516)) self.assertArrayAlmostEqual(self.structure[1].coords, initial_coord * 1.01) def test_translate_sites(self): self.structure.translate_sites([0, 1], [0.5, 0.5, 0.5], frac_coords=True) self.assertArrayEqual(self.structure.frac_coords[0], [0.5, 0.5, 0.5]) self.structure.translate_sites([0], [0.5, 0.5, 0.5], frac_coords=False) self.assertArrayAlmostEqual(self.structure.cart_coords[0], [3.38014845, 1.05428585, 2.06775453]) self.structure.translate_sites([0], [0.5, 0.5, 0.5], frac_coords=True, to_unit_cell=False) self.assertArrayAlmostEqual(self.structure.frac_coords[0], [1.00187517, 1.25665291, 1.15946374]) def test_mul(self): self.structure *= [2, 1, 1] self.assertEqual(self.structure.formula, "Si4") s = [2, 1, 1] * self.structure self.assertEqual(s.formula, "Si8") self.assertIsInstance(s, Structure) s = self.structure * [[1, 0, 0], [2, 1, 0], [0, 0, 2]] self.assertEqual(s.formula, "Si8") self.assertArrayAlmostEqual(s.lattice.abc, [7.6803959, 17.5979979, 7.6803959]) def test_make_supercell(self): self.structure.make_supercell([2, 1, 1]) self.assertEqual(self.structure.formula, "Si4") self.structure.make_supercell([[1, 0, 0], [2, 1, 0], [0, 0, 1]]) self.assertEqual(self.structure.formula, "Si4") self.structure.make_supercell(2) self.assertEqual(self.structure.formula, "Si32") self.assertArrayAlmostEqual(self.structure.lattice.abc, [15.360792, 35.195996, 7.680396], 5) def test_disordered_supercell_primitive_cell(self): l = Lattice.cubic(2) f = [[0.5, 0.5, 0.5]] sp = [{'Si': 0.54738}] s = Structure(l, sp, f) #this supercell often breaks things s.make_supercell([[0, -1, 1], [-1, 1, 0], [1, 1, 1]]) self.assertEqual(len(s.get_primitive_structure()), 1) def test_another_supercell(self): #this is included b/c for some reason the old algo was failing on it s = self.structure.copy() s.make_supercell([[0, 2, 2], [2, 0, 2], [2, 2, 0]]) self.assertEqual(s.formula, "Si32") s = self.structure.copy() s.make_supercell([[0, 2, 0], [1, 0, 0], [0, 0, 1]]) self.assertEqual(s.formula, "Si4") def test_to_from_dict(self): d = self.structure.as_dict() s2 = Structure.from_dict(d) self.assertEqual(type(s2), Structure) def test_to_from_file_string(self): for fmt in ["cif", "json", "poscar", "cssr", "yaml", "xsf"]: s = self.structure.to(fmt=fmt) self.assertIsNotNone(s) ss = Structure.from_str(s, fmt=fmt) self.assertArrayAlmostEqual( ss.lattice.lengths_and_angles, self.structure.lattice.lengths_and_angles, decimal=5) self.assertArrayAlmostEqual(ss.frac_coords, self.structure.frac_coords) self.assertIsInstance(ss, Structure) self.structure.to(filename="POSCAR.testing") self.assertTrue(os.path.exists("POSCAR.testing")) os.remove("POSCAR.testing") self.structure.to(filename="structure_testing.json") self.assertTrue(os.path.exists("structure_testing.json")) s = Structure.from_file("structure_testing.json") self.assertEqual(s, self.structure) os.remove("structure_testing.json") def test_from_spacegroup(self): s1 = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3), ["Li", "O"], [[0.25, 0.25, 0.25], [0, 0, 0]]) self.assertEqual(s1.formula, "Li8 O4") s2 = Structure.from_spacegroup(225, Lattice.cubic(3), ["Li", "O"], [[0.25, 0.25, 0.25], [0, 0, 0]]) self.assertEqual(s1, s2) s2 = Structure.from_spacegroup(225, Lattice.cubic(3), ["Li", "O"], [[0.25, 0.25, 0.25], [0, 0, 0]], site_properties={"charge": [1, -2]}) self.assertEqual(sum(s2.site_properties["charge"]), 0) s = Structure.from_spacegroup("Pm-3m", Lattice.cubic(3), ["Cs", "Cl"], [[0, 0, 0], [0.5, 0.5, 0.5]]) self.assertEqual(s.formula, "Cs1 Cl1") self.assertRaises(ValueError, Structure.from_spacegroup, "Pm-3m", Lattice.tetragonal(1, 3), ["Cs", "Cl"], [[0, 0, 0], [0.5, 0.5, 0.5]]) def test_merge_sites(self): species = [{ 'Ag': 0.5 }, { 'Cl': 0.25 }, { 'Cl': 0.1 }, { 'Ag': 0.5 }, { 'F': 0.15 }, { 'F': 0.1 }] coords = [[0, 0, 0], [0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [0, 0, 0], [0.5, 0.5, 1.501], [0.5, 0.5, 1.501]] s = Structure(Lattice.cubic(1), species, coords) s.merge_sites() self.assertEqual(s[0].specie.symbol, 'Ag') self.assertEqual(s[1].species_and_occu, Composition({ 'Cl': 0.35, 'F': 0.25 })) self.assertArrayAlmostEqual(s[1].frac_coords, [.5, .5, .5005])
gg +=1 if i == len(term_slab) - 1: term_index.append(i) else: if tracker_index[i] != tracker_index[i+1]: term_index.append(i) if gg == len(structure): break slab_list, term_coords = [], [] a, i = 0, 0 new_slab = Structure(term_slab.lattice, el, org_coords) for iii in range(0, len(term_index)): y = [] alt_slab = new_slab.copy() for ii in range(0, len(alt_slab)): index = [] term_scale = 0 index.append(ii) if alt_slab.frac_coords[ii][2] > alt_slab.frac_coords[term_index[iii]][2]: term_scale = min_vacuum_size alt_slab.translate_sites(index, [0, 0, term_scale/(new_slab.lattice.c)]) if standardize: index = [] for f in range(0, len(alt_slab)): index.append(f)
def __init__( self, structure: Structure, overwrite_magmom_mode: Union[OverwriteMagmomMode, str] = "none", round_magmoms: bool = False, detect_valences: bool = False, make_primitive: bool = True, default_magmoms: bool = None, set_net_positive: bool = True, threshold: float = 0.1, ): """ A class which provides a few helpful methods to analyze collinear magnetic structures. If magnetic moments are not defined, moments will be taken either from default_magmoms.yaml (similar to the default magmoms in MPRelaxSet, with a few extra definitions) or from a specie:magmom dict provided by the default_magmoms kwarg. Input magmoms can be replaced using the 'overwrite_magmom_mode' kwarg. This can be: * "none" to do nothing, * "respect_sign" which will overwrite existing magmoms with those from default_magmoms but will keep sites with positive magmoms positive, negative magmoms negative and zero magmoms zero, * "respect_zeros", which will give a ferromagnetic structure (all positive magmoms from default_magmoms) but still keep sites with zero magmoms as zero, * "replace_all" which will try to guess initial magmoms for all sites in the structure irrespective of input structure (this is most suitable for an initial DFT calculation), * "replace_all_if_undefined" is the same as "replace_all" but only if no magmoms are defined in input structure, otherwise it will respect existing magmoms. * "normalize" will normalize magmoms to unity, but will respect sign (used for comparing orderings), magmoms < theshold will be set to zero :param structure: Structure object :param overwrite_magmom_mode (str): default "none" :param round_magmoms (int or bool): will round input magmoms to specified number of decimal places if integer is supplied, if set to a float will try and group magmoms together using a kernel density estimator of provided width, and extracting peaks of the estimator :param detect_valences (bool): if True, will attempt to assign valences to input structure :param make_primitive (bool): if True, will transform to primitive magnetic cell :param default_magmoms (dict): (optional) dict specifying default magmoms :param set_net_positive (bool): if True, will change sign of magnetic moments such that the net magnetization is positive. Argument will be ignored if mode "respect_sign" is used. :param threshold (float): number (in Bohr magnetons) below which magmoms will be rounded to zero, default of 0.1 can probably be increased for many magnetic systems, depending on your application """ if default_magmoms: self.default_magmoms = default_magmoms else: self.default_magmoms = DEFAULT_MAGMOMS structure = structure.copy() # check for disorder if not structure.is_ordered: raise NotImplementedError( "Not implemented for disordered structures, " "make ordered approximation first." ) if detect_valences: trans = AutoOxiStateDecorationTransformation() bva = BVAnalyzer() try: structure = trans.apply_transformation(structure) except ValueError: warnings.warn( "Could not assign valences " "for {}".format(structure.composition.reduced_formula) ) # check to see if structure has magnetic moments # on site properties or species spin properties, # prioritize site properties has_magmoms = bool(structure.site_properties.get("magmom", False)) has_spin = False for comp in structure.species_and_occu: for sp, occu in comp.items(): if getattr(sp, "spin", False): has_spin = True # perform input sanitation ... # rest of class will assume magnetic moments # are stored on site properties: # this is somewhat arbitrary, arguments can # be made for both approaches if has_magmoms and has_spin: raise ValueError( "Structure contains magnetic moments on both " "magmom site properties and spin species " "properties. This is ambiguous. Remove one or " "the other." ) elif has_magmoms: if None in structure.site_properties["magmom"]: warnings.warn( "Be careful with mixing types in your magmom " "site properties. Any 'None' magmoms have been " "replaced with zero." ) magmoms = [m if m else 0 for m in structure.site_properties["magmom"]] elif has_spin: magmoms = [getattr(sp, "spin", 0) for sp in structure.species] structure.remove_spin() else: # no magmoms present, add zero magmoms for now magmoms = [0] * len(structure) # and overwrite magmoms with default magmoms later unless otherwise stated if overwrite_magmom_mode == "replace_all_if_undefined": overwrite_magmom_mode = "replace_all" # test to see if input structure has collinear magmoms self.is_collinear = Magmom.are_collinear(magmoms) if not self.is_collinear: warnings.warn( "This class is not designed to be used with " "non-collinear structures. If your structure is " "only slightly non-collinear (e.g. canted) may still " "give useful results, but use with caution." ) # this is for collinear structures only, make sure magmoms # are all floats magmoms = list(map(float, magmoms)) # set properties that should be done /before/ we process input magmoms self.total_magmoms = sum(magmoms) self.magnetization = sum(magmoms) / structure.volume # round magmoms below threshold to zero magmoms = [m if abs(m) > threshold else 0 for m in magmoms] # overwrite existing magmoms with default_magmoms if overwrite_magmom_mode not in ( "none", "respect_sign", "respect_zeros", "replace_all", "replace_all_if_undefined", "normalize", ): raise ValueError("Unsupported mode.") for idx, site in enumerate(structure): if site.species_string in self.default_magmoms: # look for species first, e.g. Fe2+ default_magmom = self.default_magmoms[site.species_string] elif ( isinstance(site.specie, Specie) and str(site.specie.element) in self.default_magmoms ): # look for element, e.g. Fe default_magmom = self.default_magmoms[str(site.specie.element)] else: default_magmom = 0 # overwrite_magmom_mode = "respect_sign" will change magnitude of # existing moments only, and keep zero magmoms as # zero: it will keep the magnetic ordering intact if overwrite_magmom_mode == "respect_sign": set_net_positive = False if magmoms[idx] > 0: magmoms[idx] = default_magmom elif magmoms[idx] < 0: magmoms[idx] = -default_magmom # overwrite_magmom_mode = "respect_zeros" will give a ferromagnetic # structure but will keep zero magmoms as zero elif overwrite_magmom_mode == "respect_zeros": if magmoms[idx] != 0: magmoms[idx] = default_magmom # overwrite_magmom_mode = "replace_all" will ignore input magmoms # and give a ferromagnetic structure with magnetic # moments on *all* atoms it thinks could be magnetic elif overwrite_magmom_mode == "replace_all": magmoms[idx] = default_magmom # overwrite_magmom_mode = "normalize" set magmoms magnitude to 1 elif overwrite_magmom_mode == "normalize": if magmoms[idx] != 0: magmoms[idx] = int(magmoms[idx] / abs(magmoms[idx])) # round magmoms, used to smooth out computational data magmoms = ( self._round_magmoms(magmoms, round_magmoms) if round_magmoms else magmoms ) if set_net_positive: sign = np.sum(magmoms) if sign < 0: magmoms = -np.array(magmoms) structure.add_site_property("magmom", magmoms) if make_primitive: structure = structure.get_primitive_structure(use_site_props=True) self.structure = structure
class StructureTest(PymatgenTest): def setUp(self): coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) lattice = Lattice([[3.8401979337, 0.00, 0.00], [1.9200989668, 3.3257101909, 0.00], [0.00, -2.2171384943, 3.1355090603]]) self.structure = Structure(lattice, ["Si", "Si"], coords) def test_non_hash(self): self.assertRaises(TypeError, dict, [(self.structure, 1)]) def test_append_insert_remove_replace(self): s = self.structure s.insert(1, "O", [0.5, 0.5, 0.5]) self.assertEqual(s.formula, "Si2 O1") self.assertTrue(s.ntypesp == 2) self.assertTrue(s.symbol_set == ("Si", "O")) self.assertTrue(s.indices_from_symbol("Si") == (0,2)) self.assertTrue(s.indices_from_symbol("O") == (1,)) s.remove(2) self.assertEqual(s.formula, "Si1 O1") self.assertTrue(s.indices_from_symbol("Si") == (0,)) self.assertTrue(s.indices_from_symbol("O") == (1,)) s.append("N", [0.25, 0.25, 0.25]) self.assertEqual(s.formula, "Si1 N1 O1") self.assertTrue(s.ntypesp == 3) self.assertTrue(s.symbol_set == ("Si", "O", "N")) self.assertTrue(s.indices_from_symbol("Si") == (0,)) self.assertTrue(s.indices_from_symbol("O") == (1,)) self.assertTrue(s.indices_from_symbol("N") == (2,)) s.replace(0, "Ge") self.assertEqual(s.formula, "Ge1 N1 O1") self.assertTrue(s.symbol_set == ("Ge", "O", "N")) s.replace_species({"Ge": "Si"}) self.assertEqual(s.formula, "Si1 N1 O1") self.assertTrue(s.ntypesp == 3) s.replace_species({"Si": {"Ge": 0.5, "Si": 0.5}}) self.assertEqual(s.formula, "Si0.5 Ge0.5 N1 O1") #this should change the .5Si .5Ge sites to .75Si .25Ge s.replace_species({"Ge": {"Ge": 0.5, "Si": 0.5}}) self.assertEqual(s.formula, "Si0.75 Ge0.25 N1 O1") # In this case, s.ntypesp is ambiguous. # for the time being, we raise AttributeError. with self.assertRaises(AttributeError): s.ntypesp s.remove_species(["Si"]) self.assertEqual(s.formula, "Ge0.25 N1 O1") s.remove_sites([1, 2]) self.assertEqual(s.formula, "Ge0.25") def test_add_site_property(self): s = self.structure s.add_site_property("charge", [4.1, -5]) self.assertEqual(s[0].charge, 4.1) self.assertEqual(s[1].charge, -5) s.add_site_property("magmom", [3, 2]) self.assertEqual(s[0].charge, 4.1) self.assertEqual(s[0].magmom, 3) def test_perturb(self): d = 0.1 pre_perturbation_sites = self.structure.sites[:] self.structure.perturb(distance=d) post_perturbation_sites = self.structure.sites for i, x in enumerate(pre_perturbation_sites): self.assertAlmostEqual(x.distance(post_perturbation_sites[i]), d, 3, "Bad perturbation distance") def test_add_oxidation_states(self): oxidation_states = {"Si": -4} self.structure.add_oxidation_state_by_element(oxidation_states) for site in self.structure: for k in site.species_and_occu.keys(): self.assertEqual(k.oxi_state, oxidation_states[k.symbol], "Wrong oxidation state assigned!") oxidation_states = {"Fe": 2} self.assertRaises(ValueError, self.structure.add_oxidation_state_by_element, oxidation_states) self.structure.add_oxidation_state_by_site([2, -4]) self.assertEqual(self.structure[0].specie.oxi_state, 2) self.assertRaises(ValueError, self.structure.add_oxidation_state_by_site, [1]) def test_remove_oxidation_states(self): co_elem = Element("Co") o_elem = Element("O") co_specie = Specie("Co", 2) o_specie = Specie("O", -2) coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) lattice = Lattice.cubic(10) s_elem = Structure(lattice, [co_elem, o_elem], coords) s_specie = Structure(lattice, [co_specie, o_specie], coords) s_specie.remove_oxidation_states() self.assertEqual(s_elem, s_specie, "Oxidation state remover " "failed") def test_apply_operation(self): op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 90) self.structure.apply_operation(op) self.assertArrayAlmostEqual( self.structure.lattice.matrix, [[0.000000, 3.840198, 0.000000], [-3.325710, 1.920099, 0.000000], [2.217138, -0.000000, 3.135509]], 5) def test_apply_strain(self): initial_coord = self.structure[1].coords self.structure.apply_strain(0.01) self.assertAlmostEqual( self.structure.lattice.abc, (3.8785999130369997, 3.878600984287687, 3.8785999130549516)) self.assertArrayAlmostEqual(self.structure[1].coords, initial_coord * 1.01) def test_scale_lattice(self): initial_coord = self.structure[1].coords self.structure.scale_lattice(self.structure.volume * 1.01 ** 3) self.assertArrayAlmostEqual( self.structure.lattice.abc, (3.8785999130369997, 3.878600984287687, 3.8785999130549516)) self.assertArrayAlmostEqual(self.structure[1].coords, initial_coord * 1.01) def test_translate_sites(self): self.structure.translate_sites([0, 1], [0.5, 0.5, 0.5], frac_coords=True) self.assertArrayEqual(self.structure.frac_coords[0], [0.5, 0.5, 0.5]) self.structure.translate_sites([0], [0.5, 0.5, 0.5], frac_coords=False) self.assertArrayAlmostEqual(self.structure.cart_coords[0], [3.38014845, 1.05428585, 2.06775453]) self.structure.translate_sites([0], [0.5, 0.5, 0.5], frac_coords=True, to_unit_cell=False) self.assertArrayAlmostEqual(self.structure.frac_coords[0], [1.00187517, 1.25665291, 1.15946374]) def test_make_supercell(self): self.structure.make_supercell([2, 1, 1]) self.assertEqual(self.structure.formula, "Si4") self.structure.make_supercell([[1, 0, 0], [2, 1, 0], [0, 0, 1]]) self.assertEqual(self.structure.formula, "Si4") self.structure.make_supercell(2) self.assertEqual(self.structure.formula, "Si32") self.assertArrayAlmostEqual(self.structure.lattice.abc, [15.360792, 35.195996, 7.680396], 5) def test_disordered_supercell_primitive_cell(self): l = Lattice.cubic(2) f = [[0.5, 0.5, 0.5]] sp = [{'Si': 0.54738}] s = Structure(l, sp, f) #this supercell often breaks things s.make_supercell([[0,-1,1],[-1,1,0],[1,1,1]]) self.assertEqual(len(s.get_primitive_structure()), 1) def test_another_supercell(self): #this is included b/c for some reason the old algo was failing on it s = self.structure.copy() s.make_supercell([[0, 2, 2], [2, 0, 2], [2, 2, 0]]) self.assertEqual(s.formula, "Si32") s = self.structure.copy() s.make_supercell([[0, 2, 0], [1, 0, 0], [0, 0, 1]]) self.assertEqual(s.formula, "Si4") def test_to_from_dict(self): d = self.structure.to_dict s2 = Structure.from_dict(d) self.assertEqual(type(s2), Structure)
def apply_transformation(self, structure, return_ranked_list=False): """ Return either a single ordered structure or a sequence of all ordered structures. Args: structure: Structure to order. return_ranked_list (bool): Whether or not multiple structures are returned. If return_ranked_list is a number, that number of structures is returned. Returns: Depending on returned_ranked list, either a transformed structure or a list of dictionaries, where each dictionary is of the form {"structure" = .... , "other_arguments"} The list of ordered structures is ranked by ewald energy / atom, if the input structure is an oxidation state decorated structure. Otherwise, it is ranked by number of sites, with smallest number of sites first. """ try: num_to_return = int(return_ranked_list) except ValueError: num_to_return = 1 if self.occu_tol: species = [dict(d) for d in structure.species_and_occu] # Here, we rescale all occupancies such that they meet the frac # limit. for sp in species: for k, v in sp.items(): sp[k] = float(Fraction(v).limit_denominator(self.occu_tol)) structure = Structure(structure.lattice, species, structure.frac_coords) if self.refine_structure: finder = SpacegroupAnalyzer(structure, self.symm_prec) structure = finder.get_refined_structure() contains_oxidation_state = all([ hasattr(sp, "oxi_state") and sp.oxi_state != 0 for sp in structure.composition.elements ]) if structure.is_ordered: warn("Enumeration skipped for structure with composition {} " "because it is ordered".format(structure.composition)) structures = [structure.copy()] else: adaptor = EnumlibAdaptor( structure, min_cell_size=self.min_cell_size, max_cell_size=self.max_cell_size, symm_prec=self.symm_prec, refine_structure=False, enum_precision_parameter=self.enum_precision_parameter, check_ordered_symmetry=self.check_ordered_symmetry) adaptor.run() structures = adaptor.structures original_latt = structure.lattice inv_latt = np.linalg.inv(original_latt.matrix) ewald_matrices = {} all_structures = [] for s in structures: new_latt = s.lattice transformation = np.dot(new_latt.matrix, inv_latt) transformation = tuple([ tuple([int(round(cell)) for cell in row]) for row in transformation ]) if contains_oxidation_state: if transformation not in ewald_matrices: s_supercell = structure * transformation ewald = EwaldSummation(s_supercell) ewald_matrices[transformation] = ewald else: ewald = ewald_matrices[transformation] energy = ewald.compute_sub_structure(s) all_structures.append({ "num_sites": len(s), "energy": energy, "structure": s }) else: all_structures.append({"num_sites": len(s), "structure": s}) def sort_func(s): return s["energy"] / s["num_sites"] if contains_oxidation_state \ else s["num_sites"] self._all_structures = sorted(all_structures, key=sort_func) if return_ranked_list: return self._all_structures[0:num_to_return] else: return self._all_structures[0]["structure"]
class StructureTest(PymatgenTest): def setUp(self): coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) lattice = Lattice([[3.8401979337, 0.00, 0.00], [1.9200989668, 3.3257101909, 0.00], [0.00, -2.2171384943, 3.1355090603]]) self.structure = Structure(lattice, ["Si", "Si"], coords) def test_mutable_sequence_methods(self): s = self.structure s[0] = "Fe" self.assertEqual(s.formula, "Fe1 Si1") s[0] = "Fe", [0.5, 0.5, 0.5] self.assertEqual(s.formula, "Fe1 Si1") self.assertArrayAlmostEqual(s[0].frac_coords, [0.5, 0.5, 0.5]) s.reverse() self.assertEqual(s[0].specie, Element("Si")) self.assertArrayAlmostEqual(s[0].frac_coords, [0.75, 0.5, 0.75]) s[0] = {"Mn": 0.5} self.assertEqual(s.formula, "Mn0.5 Fe1") del s[1] self.assertEqual(s.formula, "Mn0.5") s[0] = "Fe", [0.9, 0.9, 0.9], {"magmom": 5} self.assertEqual(s.formula, "Fe1") self.assertEqual(s[0].magmom, 5) def test_non_hash(self): self.assertRaises(TypeError, dict, [(self.structure, 1)]) def test_sort(self): s = self.structure s[0] = "F" s.sort() self.assertEqual(s[0].species_string, "Si") self.assertEqual(s[1].species_string, "F") s.sort(key=lambda site: site.species_string) self.assertEqual(s[0].species_string, "F") self.assertEqual(s[1].species_string, "Si") s.sort(key=lambda site: site.species_string, reverse=True) self.assertEqual(s[0].species_string, "Si") self.assertEqual(s[1].species_string, "F") def test_append_insert_remove_replace(self): s = self.structure s.insert(1, "O", [0.5, 0.5, 0.5]) self.assertEqual(s.formula, "Si2 O1") self.assertTrue(s.ntypesp == 2) self.assertTrue(s.symbol_set == ("Si", "O")) self.assertTrue(s.indices_from_symbol("Si") == (0,2)) self.assertTrue(s.indices_from_symbol("O") == (1,)) del s[2] self.assertEqual(s.formula, "Si1 O1") self.assertTrue(s.indices_from_symbol("Si") == (0,)) self.assertTrue(s.indices_from_symbol("O") == (1,)) s.append("N", [0.25, 0.25, 0.25]) self.assertEqual(s.formula, "Si1 N1 O1") self.assertTrue(s.ntypesp == 3) self.assertTrue(s.symbol_set == ("Si", "O", "N")) self.assertTrue(s.indices_from_symbol("Si") == (0,)) self.assertTrue(s.indices_from_symbol("O") == (1,)) self.assertTrue(s.indices_from_symbol("N") == (2,)) s[0] = "Ge" self.assertEqual(s.formula, "Ge1 N1 O1") self.assertTrue(s.symbol_set == ("Ge", "O", "N")) s.replace_species({"Ge": "Si"}) self.assertEqual(s.formula, "Si1 N1 O1") self.assertTrue(s.ntypesp == 3) s.replace_species({"Si": {"Ge": 0.5, "Si": 0.5}}) self.assertEqual(s.formula, "Si0.5 Ge0.5 N1 O1") #this should change the .5Si .5Ge sites to .75Si .25Ge s.replace_species({"Ge": {"Ge": 0.5, "Si": 0.5}}) self.assertEqual(s.formula, "Si0.75 Ge0.25 N1 O1") # In this case, s.ntypesp is ambiguous. # for the time being, we raise AttributeError. with self.assertRaises(AttributeError): s.ntypesp s.remove_species(["Si"]) self.assertEqual(s.formula, "Ge0.25 N1 O1") s.remove_sites([1, 2]) self.assertEqual(s.formula, "Ge0.25") def test_add_site_property(self): s = self.structure s.add_site_property("charge", [4.1, -5]) self.assertEqual(s[0].charge, 4.1) self.assertEqual(s[1].charge, -5) s.add_site_property("magmom", [3, 2]) self.assertEqual(s[0].charge, 4.1) self.assertEqual(s[0].magmom, 3) def test_propertied_structure(self): #Make sure that site properties are set to None for missing values. s = self.structure s.add_site_property("charge", [4.1, -5]) s.append("Li", [0.3, 0.3 ,0.3]) self.assertEqual(len(s.site_properties["charge"]), 3) def test_perturb(self): d = 0.1 pre_perturbation_sites = self.structure.sites[:] self.structure.perturb(distance=d) post_perturbation_sites = self.structure.sites for i, x in enumerate(pre_perturbation_sites): self.assertAlmostEqual(x.distance(post_perturbation_sites[i]), d, 3, "Bad perturbation distance") def test_add_oxidation_states(self): oxidation_states = {"Si": -4} self.structure.add_oxidation_state_by_element(oxidation_states) for site in self.structure: for k in site.species_and_occu.keys(): self.assertEqual(k.oxi_state, oxidation_states[k.symbol], "Wrong oxidation state assigned!") oxidation_states = {"Fe": 2} self.assertRaises(ValueError, self.structure.add_oxidation_state_by_element, oxidation_states) self.structure.add_oxidation_state_by_site([2, -4]) self.assertEqual(self.structure[0].specie.oxi_state, 2) self.assertRaises(ValueError, self.structure.add_oxidation_state_by_site, [1]) def test_remove_oxidation_states(self): co_elem = Element("Co") o_elem = Element("O") co_specie = Specie("Co", 2) o_specie = Specie("O", -2) coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) lattice = Lattice.cubic(10) s_elem = Structure(lattice, [co_elem, o_elem], coords) s_specie = Structure(lattice, [co_specie, o_specie], coords) s_specie.remove_oxidation_states() self.assertEqual(s_elem, s_specie, "Oxidation state remover " "failed") def test_apply_operation(self): op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 90) s = self.structure.copy() s.apply_operation(op) self.assertArrayAlmostEqual( s.lattice.matrix, [[0.000000, 3.840198, 0.000000], [-3.325710, 1.920099, 0.000000], [2.217138, -0.000000, 3.135509]], 5) op = SymmOp([[1, 1, 0, 0.5], [1, 0, 0, 0.5], [0, 0, 1, 0.5], [0, 0, 0, 1]]) s = self.structure.copy() s.apply_operation(op, fractional=True) self.assertArrayAlmostEqual( s.lattice.matrix, [[5.760297, 3.325710, 0.000000], [3.840198, 0.000000, 0.000000], [0.000000, -2.217138, 3.135509]], 5) def test_apply_strain(self): s = self.structure initial_coord = s[1].coords s.apply_strain(0.01) self.assertAlmostEqual( s.lattice.abc, (3.8785999130369997, 3.878600984287687, 3.8785999130549516)) self.assertArrayAlmostEqual(s[1].coords, initial_coord * 1.01) a1, b1, c1 = s.lattice.abc s.apply_strain([0.1, 0.2, 0.3]) a2, b2, c2 = s.lattice.abc self.assertAlmostEqual(a2 / a1, 1.1) self.assertAlmostEqual(b2 / b1, 1.2) self.assertAlmostEqual(c2 / c1, 1.3) def test_scale_lattice(self): initial_coord = self.structure[1].coords self.structure.scale_lattice(self.structure.volume * 1.01 ** 3) self.assertArrayAlmostEqual( self.structure.lattice.abc, (3.8785999130369997, 3.878600984287687, 3.8785999130549516)) self.assertArrayAlmostEqual(self.structure[1].coords, initial_coord * 1.01) def test_translate_sites(self): self.structure.translate_sites([0, 1], [0.5, 0.5, 0.5], frac_coords=True) self.assertArrayEqual(self.structure.frac_coords[0], [0.5, 0.5, 0.5]) self.structure.translate_sites([0], [0.5, 0.5, 0.5], frac_coords=False) self.assertArrayAlmostEqual(self.structure.cart_coords[0], [3.38014845, 1.05428585, 2.06775453]) self.structure.translate_sites([0], [0.5, 0.5, 0.5], frac_coords=True, to_unit_cell=False) self.assertArrayAlmostEqual(self.structure.frac_coords[0], [1.00187517, 1.25665291, 1.15946374]) def test_mul(self): self.structure *= [2, 1, 1] self.assertEqual(self.structure.formula, "Si4") s = [2, 1, 1] * self.structure self.assertEqual(s.formula, "Si8") self.assertIsInstance(s, Structure) s = self.structure * [[1, 0, 0], [2, 1, 0], [0, 0, 2]] self.assertEqual(s.formula, "Si8") self.assertArrayAlmostEqual(s.lattice.abc, [7.6803959, 17.5979979, 7.6803959]) def test_make_supercell(self): self.structure.make_supercell([2, 1, 1]) self.assertEqual(self.structure.formula, "Si4") self.structure.make_supercell([[1, 0, 0], [2, 1, 0], [0, 0, 1]]) self.assertEqual(self.structure.formula, "Si4") self.structure.make_supercell(2) self.assertEqual(self.structure.formula, "Si32") self.assertArrayAlmostEqual(self.structure.lattice.abc, [15.360792, 35.195996, 7.680396], 5) def test_disordered_supercell_primitive_cell(self): l = Lattice.cubic(2) f = [[0.5, 0.5, 0.5]] sp = [{'Si': 0.54738}] s = Structure(l, sp, f) #this supercell often breaks things s.make_supercell([[0,-1,1],[-1,1,0],[1,1,1]]) self.assertEqual(len(s.get_primitive_structure()), 1) def test_another_supercell(self): #this is included b/c for some reason the old algo was failing on it s = self.structure.copy() s.make_supercell([[0, 2, 2], [2, 0, 2], [2, 2, 0]]) self.assertEqual(s.formula, "Si32") s = self.structure.copy() s.make_supercell([[0, 2, 0], [1, 0, 0], [0, 0, 1]]) self.assertEqual(s.formula, "Si4") def test_to_from_dict(self): d = self.structure.as_dict() s2 = Structure.from_dict(d) self.assertEqual(type(s2), Structure) def test_to_from_file_string(self): for fmt in ["cif", "json", "poscar", "cssr", "yaml", "xsf"]: s = self.structure.to(fmt=fmt) self.assertIsNotNone(s) ss = Structure.from_str(s, fmt=fmt) self.assertArrayAlmostEqual( ss.lattice.lengths_and_angles, self.structure.lattice.lengths_and_angles, decimal=5) self.assertArrayAlmostEqual(ss.frac_coords, self.structure.frac_coords) self.assertIsInstance(ss, Structure) self.structure.to(filename="POSCAR.testing") self.assertTrue(os.path.exists("POSCAR.testing")) os.remove("POSCAR.testing") self.structure.to(filename="structure_testing.json") self.assertTrue(os.path.exists("structure_testing.json")) s = Structure.from_file("structure_testing.json") self.assertEqual(s, self.structure) os.remove("structure_testing.json") def test_from_spacegroup(self): s1 = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3), ["Li", "O"], [[0.25, 0.25, 0.25], [0, 0, 0]]) self.assertEqual(s1.formula, "Li8 O4") s2 = Structure.from_spacegroup(225, Lattice.cubic(3), ["Li", "O"], [[0.25, 0.25, 0.25], [0, 0, 0]]) self.assertEqual(s1, s2) s2 = Structure.from_spacegroup(225, Lattice.cubic(3), ["Li", "O"], [[0.25, 0.25, 0.25], [0, 0, 0]], site_properties={"charge": [1, -2]}) self.assertEqual(sum(s2.site_properties["charge"]), 0) s = Structure.from_spacegroup("Pm-3m", Lattice.cubic(3), ["Cs", "Cl"], [[0, 0, 0], [0.5, 0.5, 0.5]]) self.assertEqual(s.formula, "Cs1 Cl1") self.assertRaises(ValueError, Structure.from_spacegroup, "Pm-3m", Lattice.tetragonal(1, 3), ["Cs", "Cl"], [[0, 0, 0], [0.5, 0.5, 0.5]]) def test_merge_sites(self): species = [{'Ag': 0.5}, {'Cl': 0.25}, {'Cl': 0.1}, {'Ag': 0.5}, {'F': 0.15}, {'F': 0.1}] coords = [[0, 0, 0], [0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [0, 0, 0], [0.5, 0.5, 1.501], [0.5, 0.5, 1.501]] s = Structure(Lattice.cubic(1), species, coords) s.merge_sites() self.assertEqual(s[0].specie.symbol, 'Ag') self.assertEqual(s[1].species_and_occu, Composition({'Cl': 0.35, 'F': 0.25})) self.assertArrayAlmostEqual(s[1].frac_coords, [.5, .5, .5005]) def test_properties(self): self.assertEqual(self.structure.num_sites, len(self.structure)) self.structure.make_supercell(2) self.structure[1] = "C" sites = list(self.structure.group_by_types()) self.assertEqual(sites[-1].specie.symbol, "C") self.structure.add_oxidation_state_by_element({"Si": 4, "C": 2}) self.assertEqual(self.structure.charge, 62) def test_init_error(self): self.assertRaises(StructureError, Structure, Lattice.cubic(3), ["Si"], [[0, 0, 0], [0.5, 0.5, 0.5]]) def test_from_sites(self): self.structure.add_site_property("hello", [1, 2]) s = Structure.from_sites(self.structure, to_unit_cell=True) self.assertEqual(s.site_properties["hello"][1], 2) def test_magic(self): s = Structure.from_sites(self.structure) self.assertEqual(s, self.structure) self.assertNotEqual(s, None) s.apply_strain(0.5) self.assertNotEqual(s, self.structure) self.assertNotEqual(self.structure * 2, self.structure)
def get_slab(self, shift=0, tol=0.1, energy=None): """ This method takes in shift value for the c lattice direction and generates a slab based on the given shift. You should rarely use this method. Instead, it is used by other generation algorithms to obtain all slabs. Arg: shift (float): A shift value in Angstrom that determines how much a slab should be shifted. tol (float): Tolerance to determine primitive cell. energy (float): An energy to assign to the slab. Returns: (Slab) A Slab object with a particular shifted oriented unit cell. """ h = self._proj_height nlayers_slab = int(math.ceil(self.min_slab_size / h)) nlayers_vac = int(math.ceil(self.min_vac_size / h)) nlayers = nlayers_slab + nlayers_vac species = self.oriented_unit_cell.species_and_occu props = self.oriented_unit_cell.site_properties props = {k: v * nlayers_slab for k, v in props.items()} frac_coords = self.oriented_unit_cell.frac_coords frac_coords = np.array(frac_coords) +\ np.array([0, 0, -shift])[None, :] frac_coords = frac_coords - np.floor(frac_coords) a, b, c = self.oriented_unit_cell.lattice.matrix new_lattice = [a, b, nlayers * c] frac_coords[:, 2] = frac_coords[:, 2] / nlayers all_coords = [] for i in range(nlayers_slab): fcoords = frac_coords.copy() fcoords[:, 2] += i / nlayers all_coords.extend(fcoords) slab = Structure(new_lattice, species * nlayers_slab, all_coords, site_properties=props) scale_factor = self.slab_scale_factor # Whether or not to orthogonalize the structure if self.lll_reduce: lll_slab = slab.copy(sanitize=True) mapping = lll_slab.lattice.find_mapping(slab.lattice) scale_factor = np.dot(mapping[2], scale_factor) slab = lll_slab # Whether or not to center the slab layer around the vacuum if self.center_slab: avg_c = np.average([c[2] for c in slab.frac_coords]) slab.translate_sites(list(range(len(slab))), [0, 0, 0.5 - avg_c]) if self.primitive: prim = slab.get_primitive_structure(tolerance=tol) if energy is not None: energy = prim.volume / slab.volume * energy slab = prim return Slab(slab.lattice, slab.species_and_occu, slab.frac_coords, self.miller_index, self.oriented_unit_cell, shift, scale_factor, site_properties=slab.site_properties, energy=energy)
def from_options(cls, task: Task, original_structure: Structure, standardize_structure: bool, sort_structure: bool, is_magnetization: bool, kpt_mode: str, kpt_density: float, kpt_shift: list, only_even: bool, band_ref_dist: float, factor: Optional[int], symprec: float, angle_tolerance: float): """ Construct Structure and Kpoints from task and some options. Note: When task and kpt_mode are not consistent e.g., task=Task.band, kpt_mode="manual", task is prioritized. Args: See ViseInputSet docstrings Return: TaskStructureKpoints class object """ if sort_structure: structure = original_structure.get_sorted_structure() symbol_list = get_symbol_list(structure) orig_symbol_list = get_symbol_list(original_structure) if symbol_list != orig_symbol_list: logger.warning( "CAUTION: The sequence of the species is changed." f"Symbol set in the original structure " f"{symbol_list} " f"Symbol set in the generated structure " f"{orig_symbol_list}") else: structure = original_structure.copy() is_structure_changed = False if task == Task.defect: kpt_mode = "manual_set" elif task == Task.cluster_opt: kpt_mode = "manual_set" kpt_density = 1e-5 only_even = False kpt_shift = [0, 0, 0] elif task == Task.band: kpt_mode = "band" elif task == Task.phonon_force: kpt_mode = "manual_set" if kpt_shift != [0, 0, 0]: logger.warning( "For phonon force calculations, Gamma centering " "is forced for k-point sampling.") kpt_shift = [0, 0, 0] else: primitive_structure, is_structure_changed = \ find_spglib_primitive(structure, symprec, angle_tolerance) if standardize_structure: org = original_structure.lattice.matrix primitive = primitive_structure.lattice.matrix if is_structure_changed: with np.printoptions(precision=3, suppress=True): logger.warning("CAUTION: The structure is changed.\n" f"Original lattice\n {org} \n" f"Generated lattice\n {primitive} \n") structure = primitive_structure else: if is_structure_changed and kpt_mode != "manual_set": logger.warning( "Standardizaion is set to False and the given " "structure is not a primitive cell. Thus, the " "kpoint set is switched to manual_set.") kpt_mode = "manual_set" else: logger.info("The structure is a standardized primitive.") # Gamma-center mesh is a must for GW calculations due to vasp # implementation and tetrahedron method, while is a strong recommend for # dos and dielectric function to sample the band edges. if task in PLOT_TASK and kpt_shift != [0, 0, 0]: logger.warning("Gamma centering is forced for k-point sampling.") kpt_shift = [0, 0, 0] if factor is None: if task == Task.dielectric_function: factor = 3 elif task in (Task.dos, Task.dielectric_dfpt, Task.dielectric_finite_field): factor = 2 else: factor = 1 kpoints = MakeKpoints(mode=kpt_mode, structure=structure, kpt_density=kpt_density, only_even=only_even, ref_distance=band_ref_dist, kpt_shift=kpt_shift, factor=factor, symprec=symprec, angle_tolerance=angle_tolerance, is_magnetization=is_magnetization) kpoints.make_kpoints() return cls(structure=structure, kpoints=kpoints.kpoints, is_structure_changed=kpoints.is_structure_changed, sg=kpoints.sg, num_kpts=kpoints.num_kpts, factor=factor)