def make_defect_cell_vacancy(self, target_site, supercell, move_neighbours=True): """ Create a supercell for a vacancy TODO: This method needs to be changed to something smarter """ structure=self.struct_orig.copy() for i in range(structure.num_sites): s = structure._sites[i] if s.distance_from_point(target_site.coords)<0.001: index_uc=i defect_site=[False]*structure.num_sites defect_site[index_uc]=True structure.add_site_property('defect_site',defect_site) structure.make_supercell(supercell,to_unit_cell=True) for i in range(structure.num_sites): if structure[i].properties['defect_site']: index=i break la,lb,lc=structure.lattice.abc if move_neighbours==True: nearest_neighbours=get_nearest_neighbours(index,structure) smin=0.1;smax=0.3 for i in nearest_neighbours: atom=structure[i] da=random.choice([random.uniform(smin/la, smax/la),random.uniform(-smax/la, -smin/la)]) db=random.choice([random.uniform(smin/lb, smax/lb),random.uniform(-smax/lb, -smin/lb)]) dc=random.choice([random.uniform(smin/lc, smax/lc),random.uniform(-smax/lc, -smin/lc)]) structure[i]=atom.specie,[atom.a+da,atom.b+db,atom.c+dc] structure.replace(index, DummySpecie()) structure.remove_species([DummySpecie()]) return structure
def test_init(self): self.specie1 = DummySpecie("X") self.assertRaises(ValueError, DummySpecie, "Xe") self.assertRaises(ValueError, DummySpecie, "Xec") self.assertRaises(ValueError, DummySpecie, "Vac") self.specie2 = DummySpecie("X", 2, {"spin": 3}) self.assertEqual(self.specie2.spin, 3)
def test_get_composition(self): comp = self.transformed_entry.composition expected_comp = Composition({ DummySpecie('Xa'): 1, DummySpecie('Xb'): 1 }) self.assertEqual(comp, expected_comp, "Wrong composition!")
def setUp(self): comp = Composition("LiFeO2") entry = PDEntry(comp, 53) self.transformed_entry = TransformedPDEntry( { DummySpecie('Xa'): 1, DummySpecie("Xb"): 1 }, entry)
def make_defect_interstitial(self, target_site, supercell): structure=self.struct_orig.copy() structure.append(DummySpecie(), target_site.frac_coords) structure.make_supercell(supercell,to_unit_cell=True) for i in range(structure.num_sites): if structure[i].specie == DummySpecie(): structure.replace(i,target_site.specie) break structure.remove_species([DummySpecie()]) return structure
def test_coloring_with_fixed_species(): lattice = Lattice(3.945 * np.eye(3)) species = ["Sr", "Ti", "O", "O", "O"] frac_coords = np.array([[0, 0, 0], [0.5, 0.5, 0.5], [0.0, 0.5, 0.5], [0.5, 0.0, 0.5], [0.5, 0.5, 0.0]]) aristo = Structure(lattice, species, frac_coords) base_structure = aristo.copy() base_structure.remove_species(["Sr", "Ti"]) additional_species = species[:2] additional_frac_coords = frac_coords[:2] mapping_color_species = [DummySpecie("X"), "O"] num_types = len(mapping_color_species) index = 2 se = StructureEnumerator( base_structure, index, num_types, mapping_color_species=mapping_color_species, color_exchange=False, leave_superperiodic=False, use_all_colors=False, ) list_dstructs = se.generate(additional_species=additional_species, additional_frac_coords=additional_frac_coords) # with base_site_constraints mapping_color_species2 = [DummySpecie("X"), "O", "Sr", "Ti"] num_types2 = len(mapping_color_species2) base_site_constraints = [ [2], # Sr site [3], # Cu site [0, 1], # O or V [0, 1], # O or V [0, 1], # O or V ] se2 = StructureEnumerator( aristo, index, num_types2, mapping_color_species=mapping_color_species2, base_site_constraints=base_site_constraints, color_exchange=False, leave_superperiodic=False, use_all_colors=False, ) list_dstructs2 = se2.generate() # check uniqueness by StructureMatcher stm = StructureMatcher(ltol=1e-4, stol=1e-4) grouped = stm.group_structures(list_dstructs + list_dstructs2) assert len(grouped) == len(list_dstructs) assert all([(len(matched) == 2) for matched in grouped])
def _add_dummy_species(structure, order_parameters): """ :param structure: ordered Structure :param order_parameters: list of MagOrderParameterConstraints :return: A structure decorated with disordered DummySpecies on which to perform the enumeration. Note that the DummySpecies are super-imposed on to the original sites, to make it easier to retrieve the original site after enumeration is performed (this approach is preferred over a simple mapping since multiple species may have the same DummySpecie, depending on the constraints specified). This approach can also preserve site properties even after enumeration. """ dummy_struct = structure.copy() def generate_dummy_specie(): """ Generator which returns DummySpecie symbols Mma, Mmb, etc. """ subscript_length = 1 while True: for subscript in product(ascii_lowercase, repeat=subscript_length): yield "Mm"+"".join(subscript) subscript_length += 1 dummy_species_gen = generate_dummy_specie() # one dummy species for each order parameter constraint dummy_species_symbols = [next(dummy_species_gen) for i in range(len(order_parameters))] dummy_species = [{ DummySpecie(symbol, properties={'spin': Spin.up}): constraint.order_parameter, DummySpecie(symbol, properties={'spin': Spin.down}): 1-constraint.order_parameter } for symbol, constraint in zip(dummy_species_symbols, order_parameters)] sites_to_add = [] for idx, site in enumerate(dummy_struct): satisfies_constraints = [c.satisfies_constraint(site) for c in order_parameters] if satisfies_constraints.count(True) > 1: # site should either not satisfy any constraints, or satisfy # one constraint raise ValueError("Order parameter constraints conflict for site: {}, {}" .format(str(site.specie), site.properties)) elif any(satisfies_constraints): dummy_specie_idx = satisfies_constraints.index(True) dummy_struct.append( dummy_species[dummy_specie_idx], site.coords, site.lattice ) return dummy_struct
def test_coloring_with_fixed_species(sto_perovskite: Structure): base_structure = sto_perovskite.copy() base_structure.remove_species(["Sr", "Ti"]) additional_species = sto_perovskite.species[:2] additional_frac_coords = sto_perovskite.frac_coords[:2] mapping_color_species = [DummySpecie("X"), "O"] num_types = len(mapping_color_species) index = 2 se = StructureEnumerator( base_structure, index, num_types, mapping_color_species=mapping_color_species, color_exchange=False, remove_superperiodic=True, remove_incomplete=False, ) list_dstructs = se.generate(additional_species=additional_species, additional_frac_coords=additional_frac_coords) # with base_site_constraints mapping_color_species2 = [DummySpecie("X"), "O", "Sr", "Ti"] num_types2 = len(mapping_color_species2) base_site_constraints = [ [2], # Sr site [3], # Cu site [0, 1], # O or V [0, 1], # O or V [0, 1], # O or V ] se2 = StructureEnumerator( sto_perovskite, index, num_types2, mapping_color_species=mapping_color_species2, base_site_constraints=base_site_constraints, color_exchange=False, remove_superperiodic=True, remove_incomplete=False, ) list_dstructs2 = se2.generate() # check uniqueness by StructureMatcher stm = StructureMatcher(ltol=1e-4, stol=1e-4) grouped = stm.group_structures(list_dstructs + list_dstructs2) # type: ignore assert len(grouped) == len(list_dstructs) assert all([(len(matched) == 2) for matched in grouped])
def test_get_el_sp(self): self.assertEqual(get_el_sp("Fe2+"), Specie("Fe", 2)) self.assertEqual(get_el_sp("3"), Element("Li")) self.assertEqual(get_el_sp("3.0"), Element("Li")) self.assertEqual(get_el_sp("U"), Element("U")) self.assertEqual(get_el_sp("X2+"), DummySpecie("X", 2)) self.assertEqual(get_el_sp("Mn3+"), Specie("Mn", 3))
def get_nearest_neighbours_from_point(vec_cartesian,struct): site = PeriodicSite(DummySpecie(),vec_cartesian,struct.lattice,coords_are_cartesian=True) dists = [i[1] for i in struct.get_neighbors(site,10,include_index=True) if i[1]>0.1] min_dist = min(dists) nearest_neighbour_infos=struct.get_neighbors(site,min_dist+0.2,include_index=True) nearest_neighbours=[i[2] for i in nearest_neighbour_infos if i[1]>0.1] return nearest_neighbours
def add_dummy_atoms(mol, ring_atom_ids): data = np.array([mol[i].coords for i in ring_atom_ids]) centre = np.average(data, axis=0) # fit the atomic positions to a plane using least squares method, taken # from: https://gist.github.com/amroamroamro/1db8d69b4b65e8bc66a6#file-curve_fitting-py-L27 A = np.c_[data[:, 0], data[:, 1], np.ones(data.shape[0])] C, _, _, _ = scipy.linalg.lstsq(A, data[:, 2]) # calculate the normal unit vector normal = np.array(np.cross([1, 0, C[0]], [0, 1, C[1]])) unit = normal / scipy.linalg.norm(normal) # add the dummy atoms above and below the ring mol.append(DummySpecie('X-Bq'), centre + unit) mol.append(DummySpecie('X-Bq'), centre - unit) return mol
def test_get_el_sp(self): self.assertEqual(get_el_sp("Fe2+"), Specie("Fe", 2)) self.assertEqual(get_el_sp("3"), Element.Li) self.assertEqual(get_el_sp("3.0"), Element.Li) self.assertEqual(get_el_sp("U"), Element.U) self.assertEqual(get_el_sp("X2+"), DummySpecie("X", 2)) self.assertEqual(get_el_sp("Mn3+"), Specie("Mn", 3)) self.assertEqual(get_el_sp(["Li+", "Mn3+"]), [Specie("Li", 1), Specie("Mn", 3)])
def transform_entries(self, entries, terminal_compositions): """ Method to transform all entries to the composition coordinate in the terminal compositions. If the entry does not fall within the space defined by the terminal compositions, they are excluded. For example, Li3PO4 is mapped into a Li2O:1.5, P2O5:0.5 composition. The terminal compositions are represented by DummySpecies. Args: entries: Sequence of all input entries terminal_compositions: Terminal compositions of phase space. Returns: Sequence of TransformedPDEntries falling within the phase space. """ new_entries = [] if self.normalize_terminals: fractional_comp = [ c.get_fractional_composition() for c in terminal_compositions ] else: fractional_comp = terminal_compositions #Map terminal compositions to unique dummy species. sp_mapping = collections.OrderedDict() for i, comp in enumerate(fractional_comp): sp_mapping[comp] = DummySpecie("X" + chr(102 + i)) for entry in entries: try: rxn = Reaction(fractional_comp, [entry.composition]) rxn.normalize_to(entry.composition) #We only allow reactions that have positive amounts of #reactants. if all([ rxn.get_coeff(comp) <= CompoundPhaseDiagram.amount_tol for comp in fractional_comp ]): newcomp = { sp_mapping[comp]: -rxn.get_coeff(comp) for comp in fractional_comp } newcomp = { k: v for k, v in newcomp.items() if v > CompoundPhaseDiagram.amount_tol } transformed_entry = \ TransformedPDEntry(Composition(newcomp), entry) new_entries.append(transformed_entry) except ReactionError: #If the reaction can't be balanced, the entry does not fall #into the phase space. We ignore them. pass return new_entries, sp_mapping
def get_inter_sites_after_symmetry(struct_with_voronoi_vects): spa=SpacegroupAnalyzer(struct_with_voronoi_vects,symprec=0.001, angle_tolerance=1) stru=spa.get_symmetrized_structure() eqs=stru.equivalent_sites re_inters=[] for i in eqs: if i[0].specie==DummySpecie(): re_inters.append(list(i[0].frac_coords)) re_inters.sort() return re_inters
def __init__( self, base_structure: Structure, index: int, num_types: int, mapping_color_species: list[SpeciesLike] = None, composition_constraints=None, base_site_constraints=None, color_exchange=True, remove_superperiodic=True, remove_incomplete=True, verbose=True, ): self.base_structure = base_structure self.index = index self.num_types = num_types self.composition_constraints = composition_constraints # settings self.color_exchange = color_exchange self.remove_superperiodic = remove_superperiodic self.remove_incomplete = remove_incomplete list_reduced_HNF, rotations, translations = generate_symmetry_distinct_superlattices( index, base_structure, return_symops=True ) self.list_reduced_HNF = list_reduced_HNF self.rotations = rotations self.translations = translations # self.site_constraints[i] is a list of allowed species at the i-th site in supercell self.site_constraints = None if base_site_constraints: assert len(base_site_constraints) == self.num_sites_base self.site_constraints = convert_site_constraints(base_site_constraints, self.index) if mapping_color_species and len(mapping_color_species) != self.num_types: raise ValueError("mapping_color_species must have num_type species.") if mapping_color_species is None: mapping_color_species = [DummySpecie(str(i)) for i in range(1, self.num_types + 1)] self.mapping_color_species = mapping_color_species self.verbose = verbose
def get_lattice(kind): if kind == "hcp": latt = Lattice( np.array([[1, 0, 0], [0.5, np.sqrt(3) / 2, 0], [0, 0, 2 * np.sqrt(6) / 3]])) coords = [[0, 0, 0], [1 / 3, 1 / 3, 0.5]] else: coords = [[0, 0, 0]] if kind == "sc": latt = Lattice(np.eye(3)) elif kind == "fcc": latt = Lattice(np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]])) elif kind == "bcc": latt = Lattice(np.array([[-1, 1, 1], [1, -1, 1], [1, 1, -1]])) elif kind == "hex": latt = Lattice.hexagonal(1, 2 * np.sqrt(6) / 3) elif kind == "tet": latt = Lattice(np.diag([1, 1, 1.2])) struct = Structure(latt, [DummySpecie("X")] * len(coords), coords) return struct
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 struct_with_voronoi_verts(structure): struct = structure.copy() sites_cart=struct.cart_coords s=[] for x in sites_cart: for i in range(-2,4): for j in range(-2,4): for k in range(-2,4): s.append(x+i*lat[0]+j*lat[1]+k*lat[2]) vor=Voronoi(s) verts=vor.vertices inters=[] for i in verts: fracs= struct.lattice.get_fractional_coords(i) inbox=True for j in fracs: if j<0 or j>=1.0: inbox = False break if inbox: inters.append(list(fracs)) struct.append(DummySpecie(),fracs) return struct, inters
def test_smart_element_or_specie(self): self.assertEqual(smart_element_or_specie("Fe2+"), Specie("Fe", 2)) self.assertEqual(smart_element_or_specie("3"), Element("Li")) self.assertEqual(smart_element_or_specie("U"), Element("U")) self.assertEqual(smart_element_or_specie("X2+"), DummySpecie("X", 2)) self.assertEqual(smart_element_or_specie("Mn3+"), Specie("Mn", 3))
def test_eq(self): self.assertFalse(DummySpecie("Xg") == DummySpecie("Xh")) self.assertFalse(DummySpecie("Xg") == DummySpecie("Xg", 3)) self.assertTrue(DummySpecie("Xg", 3) == DummySpecie("Xg", 3))
def _gen_input_file(self): """ Generate the necessary struct_enum.in file for enumlib. See enumlib documentation for details. """ coord_format = "{:.6f} {:.6f} {:.6f}" # Using symmetry finder, get the symmetrically distinct sites. fitter = SpacegroupAnalyzer(self.structure, self.symm_prec) symmetrized_structure = fitter.get_symmetrized_structure() logger.debug("Spacegroup {} ({}) with {} distinct sites".format( fitter.get_space_group_symbol(), fitter.get_space_group_number(), len(symmetrized_structure.equivalent_sites))) """ Enumlib doesn"t work when the number of species get too large. To simplify matters, we generate the input file only with disordered sites and exclude the ordered sites from the enumeration. The fact that different disordered sites with the exact same species may belong to different equivalent sites is dealt with by having determined the spacegroup earlier and labelling the species differently. """ # index_species and index_amounts store mappings between the indices # used in the enum input file, and the actual species and amounts. index_species = [] index_amounts = [] # Stores the ordered sites, which are not enumerated. ordered_sites = [] disordered_sites = [] coord_str = [] for sites in symmetrized_structure.equivalent_sites: if sites[0].is_ordered: ordered_sites.append(sites) else: sp_label = [] species = {k: v for k, v in sites[0].species.items()} if sum(species.values()) < 1 - EnumlibAdaptor.amount_tol: # Let us first make add a dummy element for every single # site whose total occupancies don't sum to 1. species[DummySpecie("X")] = 1 - sum(species.values()) for sp in species.keys(): if sp not in index_species: index_species.append(sp) sp_label.append(len(index_species) - 1) index_amounts.append(species[sp] * len(sites)) else: ind = index_species.index(sp) sp_label.append(ind) index_amounts[ind] += species[sp] * len(sites) sp_label = "/".join(["{}".format(i) for i in sorted(sp_label)]) for site in sites: coord_str.append("{} {}".format( coord_format.format(*site.coords), sp_label)) disordered_sites.append(sites) def get_sg_info(ss): finder = SpacegroupAnalyzer(Structure.from_sites(ss), self.symm_prec) return finder.get_space_group_number() target_sgnum = get_sg_info(symmetrized_structure.sites) curr_sites = list(itertools.chain.from_iterable(disordered_sites)) sgnum = get_sg_info(curr_sites) ordered_sites = sorted(ordered_sites, key=lambda sites: len(sites)) logger.debug("Disordered sites has sg # %d" % (sgnum)) self.ordered_sites = [] # progressively add ordered sites to our disordered sites # until we match the symmetry of our input structure if self.check_ordered_symmetry: while sgnum != target_sgnum and len(ordered_sites) > 0: sites = ordered_sites.pop(0) temp_sites = list(curr_sites) + sites new_sgnum = get_sg_info(temp_sites) if sgnum != new_sgnum: logger.debug("Adding %s in enum. New sg # %d" % (sites[0].specie, new_sgnum)) index_species.append(sites[0].specie) index_amounts.append(len(sites)) sp_label = len(index_species) - 1 for site in sites: coord_str.append("{} {}".format( coord_format.format(*site.coords), sp_label)) disordered_sites.append(sites) curr_sites = temp_sites sgnum = new_sgnum else: self.ordered_sites.extend(sites) for sites in ordered_sites: self.ordered_sites.extend(sites) self.index_species = index_species lattice = self.structure.lattice output = [self.structure.formula, "bulk"] for vec in lattice.matrix: output.append(coord_format.format(*vec)) output.append("%d" % len(index_species)) output.append("%d" % len(coord_str)) output.extend(coord_str) output.append("{} {}".format(self.min_cell_size, self.max_cell_size)) output.append(str(self.enum_precision_parameter)) output.append("full") ndisordered = sum([len(s) for s in disordered_sites]) base = int(ndisordered * lcm(*[ f.limit_denominator(ndisordered * self.max_cell_size).denominator for f in map(fractions.Fraction, index_amounts) ])) # This multiplicative factor of 10 is to prevent having too small bases # which can lead to rounding issues in the next step. # An old bug was that a base was set to 8, with a conc of 0.4:0.6. That # resulted in a range that overlaps and a conc of 0.5 satisfying this # enumeration. See Cu7Te5.cif test file. base *= 10 # base = ndisordered #10 ** int(math.ceil(math.log10(ndisordered))) # To get a reasonable number of structures, we fix concentrations to the # range expected in the original structure. total_amounts = sum(index_amounts) for amt in index_amounts: conc = amt / total_amounts if abs(conc * base - round(conc * base)) < 1e-5: output.append("{} {} {}".format(int(round(conc * base)), int(round(conc * base)), base)) else: min_conc = int(math.floor(conc * base)) output.append("{} {} {}".format(min_conc - 1, min_conc + 1, base)) output.append("") logger.debug("Generated input file:\n{}".format("\n".join(output))) with open("struct_enum.in", "w") as f: f.write("\n".join(output))
def _get_structure(self, data, primitive): """ Generate structure from part of the cif. """ def parse_symbol(sym): # Common representations for elements/water in cif files # TODO: fix inconsistent handling of water special = { "D": "D", "Hw": "H", "Ow": "O", "Wat": "O", "wat": "O", "OH": "", "OH2": "" } m = re.findall(r"w?[A-Z][a-z]*", sym) if m and m != "?": if sym in special: v = special[sym] else: v = special.get(m[0], m[0]) if len(m) > 1 or (m[0] in special): warnings.warn("{} parsed as {}".format(sym, v)) return v lattice = self.get_lattice(data) # if magCIF, get magnetic symmetry moments and magmoms # else standard CIF, and use empty magmom dict if self.feature_flags["magcif_incommensurate"]: raise NotImplementedError( "Incommensurate structures not currently supported.") elif self.feature_flags["magcif"]: self.symmetry_operations = self.get_magsymops(data) magmoms = self.parse_magmoms(data, lattice=lattice) else: self.symmetry_operations = self.get_symops(data) magmoms = {} oxi_states = self.parse_oxi_states(data) coord_to_species = OrderedDict() coord_to_magmoms = OrderedDict() def get_matching_coord(coord): keys = list(coord_to_species.keys()) coords = np.array(keys) for op in self.symmetry_operations: c = op.operate(coord) inds = find_in_coord_list_pbc(coords, c, atol=self._site_tolerance) # cant use if inds, because python is dumb and np.array([0]) evaluates # to False if len(inds): return keys[inds[0]] return False for i in range(len(data["_atom_site_label"])): try: # If site type symbol exists, use it. Otherwise, we use the # label. symbol = parse_symbol(data["_atom_site_type_symbol"][i]) except KeyError: symbol = parse_symbol(data["_atom_site_label"][i]) if not symbol: continue if oxi_states is not None: o_s = oxi_states.get(symbol, 0) # use _atom_site_type_symbol if possible for oxidation state if "_atom_site_type_symbol" in data.data.keys(): oxi_symbol = data["_atom_site_type_symbol"][i] o_s = oxi_states.get(oxi_symbol, o_s) try: el = Specie(symbol, o_s) except: el = DummySpecie(symbol, o_s) else: el = get_el_sp(symbol) x = str2float(data["_atom_site_fract_x"][i]) y = str2float(data["_atom_site_fract_y"][i]) z = str2float(data["_atom_site_fract_z"][i]) magmom = magmoms.get(data["_atom_site_label"][i], Magmom(0)) try: occu = str2float(data["_atom_site_occupancy"][i]) except (KeyError, ValueError): occu = 1 if occu > 0: coord = (x, y, z) match = get_matching_coord(coord) if not match: coord_to_species[coord] = Composition({el: occu}) coord_to_magmoms[coord] = magmom else: coord_to_species[match] += {el: occu} coord_to_magmoms[ match] = None # disordered magnetic not currently supported sum_occu = [sum(c.values()) for c in coord_to_species.values()] if any([o > 1 for o in sum_occu]): warnings.warn( "Some occupancies (%s) sum to > 1! If they are within " "the tolerance, they will be rescaled." % str(sum_occu)) allspecies = [] allcoords = [] allmagmoms = [] # check to see if magCIF file is disordered if self.feature_flags["magcif"]: for k, v in coord_to_magmoms.items(): if v is None: # Proposed solution to this is to instead store magnetic moments # as Specie 'spin' property, instead of site property, but this # introduces ambiguities for end user (such as unintended use of # `spin` and Specie will have fictious oxidation state). raise NotImplementedError( 'Disordered magnetic structures not currently supported.' ) if coord_to_species.items(): for species, group in groupby(sorted(list( coord_to_species.items()), key=lambda x: x[1]), key=lambda x: x[1]): tmp_coords = [site[0] for site in group] tmp_magmom = [ coord_to_magmoms[tmp_coord] for tmp_coord in tmp_coords ] if self.feature_flags["magcif"]: coords, magmoms = self._unique_coords( tmp_coords, tmp_magmom) else: coords, magmoms = self._unique_coords(tmp_coords) allcoords.extend(coords) allspecies.extend(len(coords) * [species]) allmagmoms.extend(magmoms) # rescale occupancies if necessary for i, species in enumerate(allspecies): totaloccu = sum(species.values()) if 1 < totaloccu <= self._occupancy_tolerance: allspecies[i] = species / totaloccu if allspecies and len(allspecies) == len(allcoords) and len( allspecies) == len(allmagmoms): if self.feature_flags["magcif"]: struct = Structure(lattice, allspecies, allcoords, site_properties={"magmom": allmagmoms}) else: struct = Structure(lattice, allspecies, allcoords) struct = struct.get_sorted_structure() if primitive: struct = struct.get_primitive_structure() struct = struct.get_reduced_structure() return struct
def compute_composition_vector(self, composition_space): """ Returns the composition vector of the organism, as a numpy array. Args: composition_space: the CompositionSpace of the search. """ if composition_space.objective_function == 'epa': return None elif composition_space.objective_function == 'pd': # make CompoundPhaseDiagram and PDAnalyzer objects pdentries = [] for endpoint in composition_space.endpoints: pdentries.append(PDEntry(endpoint, -10)) compound_pd = CompoundPhaseDiagram(pdentries, composition_space.endpoints) # transform the organism's composition transformed_entry = compound_pd.transform_entries( [PDEntry(self.composition, 10)], composition_space.endpoints) # get the transformed species and amounts if len(transformed_entry[0]) == 0: return None transformed_list = str(transformed_entry[0][0]).split() del transformed_list[0] popped = '' while popped != 'with': popped = transformed_list.pop() # separate the dummy species symbols from the amounts symbols = [] amounts = [] for entry in transformed_list: split_entry = entry.split('0+') symbols.append(split_entry[0]) amounts.append(float(split_entry[1])) # make a dictionary mapping dummy species to amounts dummy_species_amounts = {} for i in range(len(symbols)): dummy_species_amounts[DummySpecie(symbol=symbols[i])] = \ amounts[i] # make Composition object with dummy species, get decomposition dummy_comp = Composition(dummy_species_amounts) decomp = compound_pd.get_decomposition(dummy_comp) # get amounts of the decomposition in terms of the (untransformed) # composition space endpoints formatted_decomp = {} for key in decomp: key_dict = key.as_dict() comp = Composition(key_dict['entry']['composition']) formatted_decomp[comp] = decomp[key] # make the composition vector composition_vector = [] # because the random organism creator shuffles the endpoints composition_space.endpoints.sort() for endpoint in composition_space.endpoints: if endpoint in formatted_decomp: composition_vector.append(formatted_decomp[endpoint]) else: composition_vector.append(0.0) return np.array(composition_vector)
def test_cached(self): sp1 = DummySpecie("X", 2) sp2 = DummySpecie("X", 2)
def _gen_input_file(self): """ Generate the necessary struct_enum.in file for enumlib. See enumlib documentation for details. """ coord_format = "{:.6f} {:.6f} {:.6f}" # Using symmetry finder, get the symmetrically distinct sites. fitter = SpacegroupAnalyzer(self.structure, self.symm_prec) symmetrized_structure = fitter.get_symmetrized_structure() logger.debug("Spacegroup {} ({}) with {} distinct sites".format( fitter.get_space_group_symbol(), fitter.get_space_group_number(), len(symmetrized_structure.equivalent_sites))) """ Enumlib doesn"t work when the number of species get too large. To simplify matters, we generate the input file only with disordered sites and exclude the ordered sites from the enumeration. The fact that different disordered sites with the exact same species may belong to different equivalent sites is dealt with by having determined the spacegroup earlier and labelling the species differently. """ # index_species and index_amounts store mappings between the indices # used in the enum input file, and the actual species and amounts. index_species = [] index_amounts = [] # Stores the ordered sites, which are not enumerated. ordered_sites = [] disordered_sites = [] coord_str = [] for sites in symmetrized_structure.equivalent_sites: if sites[0].is_ordered: ordered_sites.append(sites) else: sp_label = [] species = {k: v for k, v in sites[0].species_and_occu.items()} if sum(species.values()) < 1 - EnumlibAdaptor.amount_tol: # Let us first make add a dummy element for every single # site whose total occupancies don't sum to 1. species[DummySpecie("X")] = 1 - sum(species.values()) for sp in species.keys(): if sp not in index_species: index_species.append(sp) sp_label.append(len(index_species) - 1) index_amounts.append(species[sp] * len(sites)) else: ind = index_species.index(sp) sp_label.append(ind) index_amounts[ind] += species[sp] * len(sites) sp_label = "/".join(["{}".format(i) for i in sorted(sp_label)]) for site in sites: coord_str.append("{} {}".format( coord_format.format(*site.coords), sp_label)) disordered_sites.append(sites) def get_sg_info(ss): finder = SpacegroupAnalyzer(Structure.from_sites(ss), self.symm_prec) return finder.get_space_group_number() curr_sites = list(itertools.chain.from_iterable(disordered_sites)) min_sgnum = get_sg_info(curr_sites) logger.debug("Disorderd sites has sgnum %d" % (min_sgnum)) # It could be that some of the ordered sites has a lower symmetry than # the disordered sites. So we consider the lowest symmetry sites as # disordered in our enumeration. self.ordered_sites = [] to_add = [] if self.check_ordered_symmetry: for sites in ordered_sites: temp_sites = list(curr_sites) + sites sgnum = get_sg_info(temp_sites) if sgnum < min_sgnum: logger.debug("Adding {} to sites to be ordered. " "New sgnum {}".format(sites, sgnum)) to_add = sites min_sgnum = sgnum for sites in ordered_sites: if sites == to_add: index_species.append(sites[0].specie) index_amounts.append(len(sites)) sp_label = len(index_species) - 1 logger.debug( "Lowest symmetry {} sites are included in enum.".format( sites[0].specie)) for site in sites: coord_str.append("{} {}".format( coord_format.format(*site.coords), sp_label)) disordered_sites.append(sites) else: self.ordered_sites.extend(sites) self.index_species = index_species lattice = self.structure.lattice output = [self.structure.formula, "bulk"] for vec in lattice.matrix: output.append(coord_format.format(*vec)) output.append("{}".format(len(index_species))) output.append("{}".format(len(coord_str))) output.extend(coord_str) output.append("{} {}".format(self.min_cell_size, self.max_cell_size)) output.append(str(self.enum_precision_parameter)) output.append("partial") ndisordered = sum([len(s) for s in disordered_sites]) base = int(ndisordered * reduce(lcm, [ f.limit_denominator(ndisordered * self.max_cell_size).denominator for f in map(fractions.Fraction, index_amounts) ])) # base = ndisordered #10 ** int(math.ceil(math.log10(ndisordered))) # To get a reasonable number of structures, we fix concentrations to the # range expected in the original structure. total_amounts = sum(index_amounts) for amt in index_amounts: conc = amt / total_amounts if abs(conc * base - round(conc * base)) < 1e-5: output.append("{} {} {}".format(int(round(conc * base)), int(round(conc * base)), base)) else: min_conc = int(math.floor(conc * base)) output.append("{} {} {}".format(min_conc - 1, min_conc + 1, base)) output.append("") logger.debug("Generated input file:\n{}".format("\n".join(output))) with open("struct_enum.in", "w") as f: f.write("\n".join(output))
def test_sort(self): r = sorted([Element.Fe, DummySpecie("X")]) self.assertEqual(r, [DummySpecie("X"), Element.Fe]) self.assertTrue(DummySpecie("X", 3) < DummySpecie("X", 4))
def test_cached(self): sp1 = DummySpecie("X", 2) sp2 = DummySpecie("X", 2) self.assertEqual(id(sp1), id(sp2))
def test_pickle(self): el1 = DummySpecie("X", 3) o = pickle.dumps(el1) self.assertEqual(el1, pickle.loads(o))
def test_sort(self): r = sorted([Element('Fe'), DummySpecie("X")]) self.assertEqual(r, [DummySpecie("X"), Element('Fe')])
def get_interstitial_sites(structure, dis_bar=2.0, num=2): """ get interstitial positions(IntrPoints) for a specific structure. method: 1st, collect all atoms in a 3x3x3 supercell. 2nd, get all Voronoi cell cornors by Voronoi analysis 3rd, take all the atoms and Voronoi cell cornors (DummySpecie atoms) in the centeral unit cell as a new unit cell to make a crystal. 4th, keep nonequivalent DummySpecie atoms by space group symmetry analysis. 5th, if the distances among some DummySpecie atoms are less than 'criter', just leave one of them. 6th, screen the final DummySpecie atom to make each one have different neighbours. Args: struct: the structure under consideration for obtaining the interstitial positions. standadized: True or False. must be the same as the standidized parameter in DefectsMaker function. save_inters: True or False. If True, the unitcell structure with all interstitial positions occupied by 'Lr' or 'No' atoms are output with cif format. """ criter=2.0 struct = structure.copy() struct_orig = structure.copy() lat=struct.lattice.matrix def struct_with_voronoi_verts(structure): struct = structure.copy() sites_cart=struct.cart_coords s=[] for x in sites_cart: for i in range(-2,4): for j in range(-2,4): for k in range(-2,4): s.append(x+i*lat[0]+j*lat[1]+k*lat[2]) vor=Voronoi(s) verts=vor.vertices inters=[] for i in verts: fracs= struct.lattice.get_fractional_coords(i) inbox=True for j in fracs: if j<0 or j>=1.0: inbox = False break if inbox: inters.append(list(fracs)) struct.append(DummySpecie(),fracs) return struct, inters def get_inter_sites_after_symmetry(struct_with_voronoi_vects): spa=SpacegroupAnalyzer(struct_with_voronoi_vects,symprec=0.001, angle_tolerance=1) stru=spa.get_symmetrized_structure() eqs=stru.equivalent_sites re_inters=[] for i in eqs: if i[0].specie==DummySpecie(): re_inters.append(list(i[0].frac_coords)) re_inters.sort() return re_inters struct_withvv, inters = struct_with_voronoi_verts(struct) try: inters_sym = get_inter_sites_after_symmetry(struct_withvv,) except: inters_sym = inters polys_all = [polyhedral_largest(i,struct_orig) for i in inters_sym] polys_cut = [polyhedral_cut(i) for i in polys_all if i] polys = [i for i in polys_cut if i] OK = False while not OK: polys_dis = screen_polys_dist(polys,struct_orig,dis_bar) if polys == polys_dis: OK = True else: polys = polys_dis inter_sites = [i['frac_coord'] for i in screen_polys(polys_dis,num)] for i in inter_sites: struct_orig.append(DummySpecie(),i) struct_orig.to('poscar','POSCAR') return inter_sites