def parse(self, options, *args, **kwargs): structure = StructureFileArgument(options)() species = list(set([element.symbol for element in structure.species])) sublattice_composition = {} sublattice_mapping = self.raw_value for mapping in sublattice_mapping: try: sublattice, composition = mapping.split('=') except: self.write_message( 'Could not parse sublattice definition "{0}"'.format( mapping)) raise InvalidOption sublattice_species = [] dummy_z = 1 for sublattice_composition_string in parse_separated_string( composition): try: _species, mole_fraction = sublattice_composition_string.split( ':') except ValueError: if Element.is_valid_symbol( sublattice_composition_string ) or sublattice_composition_string.startswith('0'): _species = sublattice_composition_string else: _species = Element.from_Z(dummy_z).symbol dummy_z += 1 sublattice_species.append(_species) for _species in sublattice_species: if not Element.is_valid_symbol( _species) and not _species == '0': self.write_message( 'Element "{0}" is not a valid symbol in the periodic table. To get a list of all symbols you may have a look at pymatgen.core.periodic_table' .format(_species)) raise InvalidOption if sublattice == '0': self.write_message( 'Vacancies cannot be specified as sublattice'.format( sublattice)) raise InvalidOption elif sublattice not in species: self.write_message( '"{0}" is not a specified species'.format(sublattice)) raise InvalidOption if len(sublattice_species) == 0: self.write_message( 'You must specify which species should be placed on the {0} sublattice' .format(sublattice)) raise InvalidOption sublattice_composition[sublattice] = \ self.parse_composition(composition, sublattice_species, atoms=self.get_atom_number_on_sublattice(sublattice, structure)) return sublattice_composition
def from_dict(cls, d, lattice=None): """ Create PeriodicSite from dict representation. Args: d (dict): dict representation of PeriodicSite lattice: Optional lattice to override lattice specified in d. Useful for ensuring all sites in a structure share the same lattice. Returns: PeriodicSite """ atoms_n_occu = {} for sp_occu in d["species"]: if "oxidation_state" in sp_occu and Element.is_valid_symbol( sp_occu["element"]): sp = Specie.from_dict(sp_occu) elif "oxidation_state" in sp_occu: sp = DummySpecie.from_dict(sp_occu) else: sp = Element(sp_occu["element"]) atoms_n_occu[sp] = sp_occu["occu"] props = d.get("properties", None) lattice = lattice if lattice else Lattice.from_dict(d["lattice"]) return cls(atoms_n_occu, d["abc"], lattice, properties=props)
def from_dict(cls, d, lattice=None): """ Create PeriodicSite from dict representation. Args: d (dict): dict representation of PeriodicSite lattice: Optional lattice to override lattice specified in d. Useful for ensuring all sites in a structure share the same lattice. Returns: PeriodicSite """ species = {} for sp_occu in d["species"]: if "oxidation_state" in sp_occu and Element.is_valid_symbol( sp_occu["element"]): sp = Species.from_dict(sp_occu) elif "oxidation_state" in sp_occu: sp = DummySpecies.from_dict(sp_occu) else: sp = Element(sp_occu["element"]) species[sp] = sp_occu["occu"] props = d.get("properties", None) if props is not None: for key in props.keys(): props[key] = json.loads(json.dumps(props[key], cls=MontyEncoder), cls=MontyDecoder) lattice = lattice if lattice else Lattice.from_dict(d["lattice"]) return cls(species, d["abc"], lattice, properties=props)
def _parse_chomp_and_rank(m, f, m_dict, m_points): """ A helper method for formula parsing that helps in interpreting and ranking indeterminate formulas Author: Anubhav Jain Args: m: A regex match, with the first group being the element and the second group being the amount f: The formula part containing the match m_dict: A symbol:amt dictionary from the previously parsed formula m_points: Number of points gained from the previously parsed formula Returns: A tuple of (f, m_dict, points) where m_dict now contains data from the match and the match has been removed (chomped) from the formula f. The "goodness" of the match determines the number of points returned for chomping. Returns (None, None, None) if no element could be found... """ points = 0 # Points awarded if the first element of the element is correctly # specified as a capital points_first_capital = 100 # Points awarded if the second letter of the element is correctly # specified as lowercase points_second_lowercase = 100 # get element and amount from regex match el = m.group(1) if len(el) > 2 or len(el) < 1: raise ValueError("Invalid element symbol entered!") amt = float(m.group(2)) if m.group(2).strip() != "" else 1 # convert the element string to proper [uppercase,lowercase] format # and award points if it is already in that format char1 = el[0] char2 = el[1] if len(el) > 1 else "" if char1 == char1.upper(): points += points_first_capital if char2 and char2 == char2.lower(): points += points_second_lowercase el = char1.upper() + char2.lower() # if it's a valid element, chomp and add to the points if Element.is_valid_symbol(el): if el in m_dict: m_dict[el] += amt * factor else: m_dict[el] = amt * factor return f.replace(m.group(), "", 1), m_dict, m_points + points # else return None return None, None, None
def _parse_chomp_and_rank(m, f, m_dict, m_points): """ A helper method for formula parsing that helps in interpreting and ranking indeterminate formulas Author: Anubhav Jain Args: m: A regex match, with the first group being the element and the second group being the amount f: The formula part containing the match m_dict: A symbol:amt dictionary from the previously parsed formula m_points: Number of points gained from the previously parsed formula Returns: A tuple of (f, m_dict, points) where m_dict now contains data from the match and the match has been removed (chomped) from the formula f. The "goodness" of the match determines the number of points returned for chomping. Returns (None, None, None) if no element could be found... """ points = 0 # Points awarded if the first element of the element is correctly # specified as a capital points_first_capital = 100 # Points awarded if the second letter of the element is correctly # specified as lowercase points_second_lowercase = 100 #get element and amount from regex match el = m.group(1) if len(el) > 2 or len(el) < 1: raise CompositionError("Invalid element symbol entered!") amt = float(m.group(2)) if m.group(2).strip() != "" else 1 #convert the element string to proper [uppercase,lowercase] format #and award points if it is already in that format char1 = el[0] char2 = el[1] if len(el) > 1 else "" if char1 == char1.upper(): points += points_first_capital if char2 and char2 == char2.lower(): points += points_second_lowercase el = char1.upper() + char2.lower() #if it's a valid element, chomp and add to the points if Element.is_valid_symbol(el): if el in m_dict: m_dict[el] += amt * factor else: m_dict[el] = amt * factor return f.replace(m.group(), "", 1), m_dict, m_points + points #else return None return None, None, None
def from_dict(cls, d): """ Create Site from dict representation """ atoms_n_occu = {} for sp_occu in d["species"]: if "oxidation_state" in sp_occu and Element.is_valid_symbol( sp_occu["element"]): sp = Specie.from_dict(sp_occu) elif "oxidation_state" in sp_occu: sp = DummySpecie.from_dict(sp_occu) else: sp = Element(sp_occu["element"]) atoms_n_occu[sp] = sp_occu["occu"] props = d.get("properties", None) return cls(atoms_n_occu, d["xyz"], properties=props)
def from_dict(cls, d: dict) -> "Site": """ Create Site from dict representation """ atoms_n_occu = {} for sp_occu in d["species"]: if "oxidation_state" in sp_occu and Element.is_valid_symbol(sp_occu["element"]): sp = Species.from_dict(sp_occu) elif "oxidation_state" in sp_occu: sp = DummySpecies.from_dict(sp_occu) else: sp = Element(sp_occu["element"]) # type: ignore atoms_n_occu[sp] = sp_occu["occu"] props = d.get("properties", None) if props is not None: for key in props.keys(): props[key] = json.loads(json.dumps(props[key], cls=MontyEncoder), cls=MontyDecoder) return cls(atoms_n_occu, d["xyz"], properties=props)
def dopant_info(dopant: Union[str, Element]) -> Union[str, None]: """Print dopant info. This method is used to add dopant info a posteriori. Args: dopant (str): Dopant element name e.g., Mg Returns: String of dopant information. """ dopant = str(dopant) if Element.is_valid_symbol(dopant): out = [ f" Dopant element: {dopant}", f"Electronegativity: {get_electronegativity(dopant)}", f" Oxidation state: {get_oxidation_state(dopant)}" ] return "\n".join(out) else: logger.warning(f"{dopant} is not a proper element name.") return
def from_string(data, default_names=None): """ Reads a Poscar from a string. The code will try its best to determine the elements in the POSCAR in the following order: 1. If default_names are supplied and valid, it will use those. Usually, default names comes from an external source, such as a POTCAR in the same directory. 2. If there are no valid default names but the input file is Vasp5-like and contains element symbols in the 6th line, the code will use that. 3. Failing (2), the code will check if a symbol is provided at the end of each coordinate. If all else fails, the code will just assign the first n elements in increasing atomic number, where n is the number of species, to the Poscar. For example, H, He, Li, .... This will ensure at least a unique element is assigned to each site and any analysis that does not require specific elemental properties should work fine. Args: data: string containing Poscar data. default_names: default symbols for the POSCAR file, usually coming from a POTCAR in the same directory. Returns: Poscar object. """ chunks = re.split("^\s*$", data.strip(), flags=re.MULTILINE) #Parse positions lines = tuple(clean_lines(chunks[0].split("\n"), False)) comment = lines[0] scale = float(lines[1]) lattice = np.array([map(float, line.split()) for line in lines[2:5]]) if scale < 0: # In vasp, a negative scale factor is treated as a volume. We need # to translate this to a proper lattice vector scaling. vol = abs(det(lattice)) lattice *= (-scale / vol) ** (1 / 3) else: lattice *= scale vasp5_symbols = False try: natoms = map(int, lines[5].split()) ipos = 6 except ValueError: vasp5_symbols = True symbols = lines[5].split() natoms = map(int, lines[6].split()) atomic_symbols = list() for i in xrange(len(natoms)): atomic_symbols.extend([symbols[i]] * natoms[i]) ipos = 7 postype = lines[ipos].split()[0] sdynamics = False # Selective dynamics if postype[0] in "sS": sdynamics = True ipos += 1 postype = lines[ipos].split()[0] cart = postype[0] in "cCkK" nsites = sum(natoms) # If default_names is specified (usually coming from a POTCAR), use # them. This is in line with Vasp"s parsing order that the POTCAR # specified is the default used. if default_names: try: atomic_symbols = [] for i in xrange(len(natoms)): atomic_symbols.extend([default_names[i]] * natoms[i]) vasp5_symbols = True except IndexError: pass if not vasp5_symbols: ind = 3 if not sdynamics else 6 try: #check if names are appended at the end of the coordinates. atomic_symbols = [l.split()[ind] for l in lines[ipos + 1:ipos + 1 + nsites]] #Ensure symbols are valid elements if not all([Element.is_valid_symbol(sym) for sym in atomic_symbols]): raise ValueError("Non-valid symbols detected.") vasp5_symbols = True except (ValueError, IndexError): #Defaulting to false names. atomic_symbols = [] for i in xrange(len(natoms)): sym = Element.from_Z(i + 1).symbol atomic_symbols.extend([sym] * natoms[i]) warnings.warn("Elements in POSCAR cannot be determined. " "Defaulting to false names {}." .format(" ".join(atomic_symbols))) # read the atomic coordinates coords = [] selective_dynamics = list() if sdynamics else None for i in xrange(nsites): toks = lines[ipos + 1 + i].split() coords.append(map(float, toks[:3])) if sdynamics: selective_dynamics.append([tok.upper()[0] == "T" for tok in toks[3:6]]) struct = Structure(lattice, atomic_symbols, coords, False, False, cart) #parse velocities if any velocities = [] if len(chunks) > 1: for line in chunks[1].strip().split("\n"): velocities.append([float(tok) for tok in line.split()]) predictor_corrector = [] if len(chunks) > 2: lines = chunks[2].strip().split("\n") predictor_corrector.append([int(lines[0])]) for line in lines[1:]: predictor_corrector.append([float(tok) for tok in line.split()]) return Poscar(struct, comment, selective_dynamics, vasp5_symbols, velocities=velocities, predictor_corrector=predictor_corrector)
def from_string(data): """ Reads the exciting input from a string """ root = ET.fromstring(data) speciesnode = root.find('structure').iter('species') elements = [] positions = [] vectors = [] lockxyz = [] # get title title_in = str(root.find('title').text) # Read elements and coordinates for nodes in speciesnode: symbol = nodes.get('speciesfile').split('.')[0] if len(symbol.split('_')) == 2: symbol = symbol.split('_')[0] if Element.is_valid_symbol(symbol): # Try to recognize the element symbol element = symbol else: raise ValueError("Unknown element!") for atom in nodes.iter('atom'): x, y, z = atom.get('coord').split() positions.append([float(x), float(y), float(z)]) elements.append(element) # Obtain lockxyz for each atom if atom.get('lockxyz') is not None: lxyz = [] for l in atom.get('lockxyz').split(): if l == 'True' or l == 'true': lxyz.append(True) else: lxyz.append(False) lockxyz.append(lxyz) else: lockxyz.append([False, False, False]) # check the atomic positions type if 'cartesian' in root.find('structure').attrib.keys(): if root.find('structure').attrib['cartesian']: cartesian = True for i in range(len(positions)): for j in range(3): positions[i][ j] = positions[i][j] * ExcitingInput.bohr2ang print(positions) else: cartesian = False # get the scale attribute scale_in = root.find('structure').find('crystal').get('scale') if scale_in: scale = float(scale_in) * ExcitingInput.bohr2ang else: scale = ExcitingInput.bohr2ang # get the stretch attribute stretch_in = root.find('structure').find('crystal').get('stretch') if stretch_in: stretch = np.array([float(a) for a in stretch_in]) else: stretch = np.array([1.0, 1.0, 1.0]) # get basis vectors and scale them accordingly basisnode = root.find('structure').find('crystal').iter('basevect') for vect in basisnode: x, y, z = vect.text.split() vectors.append([ float(x) * stretch[0] * scale, float(y) * stretch[1] * scale, float(z) * stretch[2] * scale ]) # create lattice and structure object lattice_in = Lattice(vectors) structure_in = Structure(lattice_in, elements, positions, coords_are_cartesian=cartesian) return ExcitingInput(structure_in, title_in, lockxyz)
def is_valid_species(s: str): return Element.is_valid_symbol(s) or s == "0"
def validate(self, value): if not all(Element.is_valid_symbol(k) for k in value.keys()): self.error('Keys should be element symbols') if not all(isinstance(v, (float, int)) for v in value.values()): self.error('Values should be numbers') super(DictField, self).validate(value)
def __init__(self, structure, max_min_oxi=None, substitutions=None, oxi_states=None, cellmax=128, antisites_flag=True, include_interstitials=False, interstitial_elements=None, intersites=None, standardized=False, struct_type='semiconductor'): """ Args: structure (Structure): the bulk structure. max_min_oxi (dict): The minimal and maximum oxidation state of each element as a dict. For instance {"O":(-2,0)}. If not given, the oxi-states of pymatgen are considered. substitutions (dict): The allowed substitutions of elements as a dict. If not given, intrinsic defects are computed. If given, intrinsic (e.g., anti-sites) and extrinsic are considered explicitly specified. Example: {"Co":["Zn","Mn"]} means Co sites can be substituted by Mn or Zn. oxi_states (dict): The oxidation state of the elements in the compound e.g. {"Fe":2,"O":-2}. If not given, the oxidation state of each site is computed with bond valence sum. WARNING: Bond-valence method can fail for mixed-valence compounds. cellmax (int): Maximum number of atoms allowed in the supercell. antisites_flag (bool): If False, don't generate antisites. include_interstitials (bool): If true, do generate interstitial defect configurations (default: False). interstitial_elements ([str]): List of strings containing symbols of the elements that are to be considered for interstitial sites. The default (None) triggers self-interstitial generation, given that include_interstitials is True. intersites ([PeriodicSite]): A list of PeriodicSites in the bulk structure on which we put interstitials. Note that you still have to set flag include_interstitials to True in order to make use of this manual way of providing interstitial sites. If this is used, then no additional interstitials are generated beyond the list that is provided in intersites. standardized (bool): If True, use the primitive standard structure as unit cell for generating the defect configurations (default is False). The primitive standard structure is obtained from the SpacegroupAnalyzer class with a symprec of 0.01. struct_type (string): Options are 'semiconductor' and 'insulator'. If semiconductor is selected, charge states based on database of semiconductors is used to assign defect charges. For insulators, defect charges are conservatively assigned. """ max_min_oxi = max_min_oxi if max_min_oxi is not None else {} substitutions = substitutions if substitutions is not None else {} oxi_states = oxi_states if oxi_states is not None else {} interstitial_elements = interstitial_elements if interstitial_elements is not None else [] intersites = intersites if intersites is not None else [] self.defects = [] self.cellmax = cellmax self.substitutions = {} self.struct_type = struct_type for key, val in substitutions.items(): self.substitutions[key] = val spa = SpacegroupAnalyzer(structure, symprec=1e-2) prim_struct = spa.get_primitive_standard_structure() if standardized: self.struct = prim_struct else: self.struct = structure struct_species = self.struct.types_of_specie if self.struct_type == 'semiconductor': self.defect_charger = DefectChargerSemiconductor( self.struct, min_max_oxi=max_min_oxi) elif self.struct_type == 'insulator': self.defect_charger = DefectChargerInsulator(self.struct) elif self.struct_type == 'manual': self.defect_charger = DefectChargerUserCustom( self.struct, oxi_states=oxi_states) elif self.struct_type == 'ionic': self.defect_charger = DefectChargerIonic(self.struct) else: raise NotImplementedError if include_interstitials and interstitial_elements: for elem_str in interstitial_elements: if not Element.is_valid_symbol(elem_str): raise ValueError("invalid interstitial element" " \"{}\"".format(elem_str)) sc_scale = get_optimized_sc_scale(self.struct, cellmax) self.defects = {} sc = self.struct.copy() sc.make_supercell(sc_scale) self.defects['bulk'] = { 'name': 'bulk', 'supercell': { 'size': sc_scale, 'structure': sc } } # If interstitials are provided as a list of PeriodicSites, # make sure that the lattice has not changed. if include_interstitials and intersites: for intersite in intersites: #list of PeriodicSite objects if intersite.lattice != self.struct.lattice: raise RuntimeError( "Discrepancy between lattices" " underlying the input interstitials and" " the bulk structure; possibly because of" " standardizing the input structure.") vacancies = [] as_defs = [] sub_defs = [] VG = VacancyGenerator(self.struct) print("Setting up defects...") for i, vac in enumerate(VG): vac_site = vac.site vac_symbol = vac.site.specie.symbol vac_sc = vac.generate_defect_structure(sc_scale) #create a trivial defect structure to find where supercell transformation moves the lattice struct_for_defect_site = Structure( vac.bulk_structure.copy().lattice, [vac.site.specie], [vac.site.frac_coords], to_unit_cell=True, coords_are_cartesian=False) struct_for_defect_site.make_supercell(sc_scale) vac_sc_site = struct_for_defect_site[0] charges_vac = self.defect_charger.get_charges( 'vacancy', vac_symbol) vacancies.append({ 'name': "vac_{}_{}".format(i + 1, vac_symbol), 'unique_site': vac_site, 'bulk_supercell_site': vac_sc_site, 'defect_type': 'vacancy', 'site_specie': vac_symbol, 'site_multiplicity': vac.multiplicity, 'supercell': { 'size': sc_scale, 'structure': vac_sc }, 'charges': charges_vac }) if antisites_flag: for as_specie in set(struct_species): SG = SubstitutionGenerator(self.struct, as_specie) for i, sub in enumerate(SG): as_symbol = as_specie.symbol as_sc = sub.generate_defect_structure(sc_scale) # create a trivial defect structure to find where supercell transformation moves the defect struct_for_defect_site = Structure( sub.bulk_structure.copy().lattice, [sub.site.specie], [sub.site.frac_coords], to_unit_cell=True, coords_are_cartesian=False) struct_for_defect_site.make_supercell(sc_scale) as_sc_site = struct_for_defect_site[0] #get bulk_site (non sc) poss_deflist = sorted( sub.bulk_structure.get_sites_in_sphere( sub.site.coords, 0.01, include_index=True), key=lambda x: x[1]) if not len(poss_deflist): raise ValueError( "Could not find substitution site inside bulk structure for {}?" .format(sub.name)) defindex = poss_deflist[0][2] as_site = sub.bulk_structure[defindex] vac_symbol = as_site.specie charges_as = self.defect_charger.get_charges( 'antisite', vac_symbol, as_symbol) as_defs.append({ 'name': "as_{}_{}_on_{}".format(i + 1, as_symbol, vac_symbol), 'unique_site': as_site, 'bulk_supercell_site': as_sc_site, 'defect_type': 'antisite', 'site_specie': vac_symbol, 'substitution_specie': as_symbol, 'site_multiplicity': sub.multiplicity, 'supercell': { 'size': sc_scale, 'structure': as_sc }, 'charges': charges_as }) for vac_symbol, subspecie_list in self.substitutions.items(): for subspecie_symbol in subspecie_list: SG = SubstitutionGenerator(self.struct, subspecie_symbol) for i, sub in enumerate(SG): sub_symbol = sub.site.specie.symbol #get bulk_site (non sc) poss_deflist = sorted( sub.bulk_structure.get_sites_in_sphere( sub.site.coords, 0.1, include_index=True), key=lambda x: x[1]) if not len(poss_deflist): raise ValueError( "Could not find substitution site inside bulk structure for {}?" .format(sub.name)) defindex = poss_deflist[0][2] sub_site = self.struct[defindex] this_vac_symbol = sub_site.specie.symbol if (sub_symbol != subspecie_symbol) or (this_vac_symbol != vac_symbol): continue else: sub_sc = sub.generate_defect_structure(sc_scale) # create a trivial defect structure to find where supercell transformation moves the defect struct_for_defect_site = Structure( sub.bulk_structure.copy().lattice, [sub.site.specie], [sub.site.frac_coords], to_unit_cell=True, coords_are_cartesian=False) struct_for_defect_site.make_supercell(sc_scale) sub_sc_site = struct_for_defect_site[0] charges_sub = self.defect_charger.get_charges( 'substitution', vac_symbol, subspecie_symbol) sub_defs.append({ 'name': "sub_{}_{}_on_{}".format(i + 1, subspecie_symbol, vac_symbol), 'unique_site': sub_site, 'bulk_supercell_site': sub_sc_site, 'defect_type': 'substitution', 'site_specie': vac_symbol, 'substitution_specie': subspecie_symbol, 'site_multiplicity': sub.multiplicity, 'supercell': { 'size': sc_scale, 'structure': sub_sc }, 'charges': charges_sub }) self.defects['vacancies'] = vacancies self.defects['substitutions'] = sub_defs self.defects['substitutions'] += as_defs if include_interstitials: interstitials = [] if interstitial_elements: inter_elems = interstitial_elements else: inter_elems = [elem.symbol for elem in \ self.struct.composition.elements] if len(inter_elems) == 0: raise RuntimeError("empty element list for interstitials") if intersites: #manual specification of interstitials for i, intersite in enumerate(intersites): for elt in inter_elems: name = "inter_{}_{}".format(i + 1, elt) if intersite.lattice != self.struct.lattice: err_msg = "Lattice matching error occurs between provided interstitial and the bulk structure." if standardized: err_msg += "\nLikely because the standardized flag was used. Turn this flag off or reset " \ "your interstitial PeriodicSite to match the standardized form of the bulk structure." raise ValueError(err_msg) else: intersite_object = Interstitial( self.struct, intersite) # create a trivial defect structure to find where supercell transformation moves the defect site struct_for_defect_site = Structure( intersite_object.bulk_structure.copy().lattice, [intersite_object.site.specie], [intersite_object.site.frac_coords], to_unit_cell=True, coords_are_cartesian=False) struct_for_defect_site.make_supercell(sc_scale) site_sc = struct_for_defect_site[0] sc_with_inter = intersite_object.generate_defect_structure( sc_scale) charges_inter = self.defect_charger.get_charges( 'interstitial', elt) interstitials.append({ 'name': name, 'unique_site': intersite_object.site, 'bulk_supercell_site': site_sc, 'defect_type': 'interstitial', 'site_specie': intersite_object.site.specie.symbol, 'site_multiplicity': intersite_object.multiplicity, 'supercell': { 'size': sc_scale, 'structure': sc_with_inter }, 'charges': charges_inter }) else: print( "Searching for interstitial sites (this can take awhile)..." ) for elt in inter_elems: #TODO: Add ability to use other interstitial finding methods in pymatgen IG = InterstitialGenerator(self.struct, elt) for i, intersite_object in enumerate(IG): name = intersite_object.name # create a trivial defect structure to find where supercell transformation moves the defect site struct_for_defect_site = Structure( intersite_object.bulk_structure.copy().lattice, [intersite_object.site.specie], [intersite_object.site.frac_coords], to_unit_cell=True, coords_are_cartesian=False) struct_for_defect_site.make_supercell(sc_scale) site_sc = struct_for_defect_site[0] sc_with_inter = intersite_object.generate_defect_structure( sc_scale) charges_inter = self.defect_charger.get_charges( 'interstitial', elt) interstitials.append({ 'name': "inter_{}_{}".format( i + 1, elt), #TODO fix naming convention 'unique_site': intersite_object.site, 'bulk_supercell_site': site_sc, 'defect_type': 'interstitial', 'site_specie': intersite_object.site.specie.symbol, 'site_multiplicity': intersite_object.multiplicity, 'supercell': { 'size': sc_scale, 'structure': sc_with_inter }, 'charges': charges_inter }) self.defects['interstitials'] = interstitials print("\nNumber of jobs created:") tottmp = 0 for j in self.defects.keys(): if j == 'bulk': print(" bulk = 1") tottmp += 1 else: print(" {}:".format(j)) for lis in self.defects[j]: print(" {} = {}".format(lis['name'], len(lis['charges']))) tottmp += len(lis['charges']) print("Total (non dielectric) jobs created = {}\n".format(tottmp))
def from_string(data): """ Reads the exciting input from a string """ root=ET.fromstring(data) speciesnode=root.find('structure').iter('species') elements = [] positions = [] vectors=[] lockxyz=[] # get title title_in=str(root.find('title').text) # Read elements and coordinates for nodes in speciesnode: symbol = nodes.get('speciesfile').split('.')[0] if len(symbol.split('_'))==2: symbol=symbol.split('_')[0] if Element.is_valid_symbol(symbol): # Try to recognize the element symbol element = symbol else: raise NLValueError("Unknown element!") natoms = nodes.getiterator('atom') for atom in natoms: x, y, z = atom.get('coord').split() positions.append([float(x), float(y), float(z)]) elements.append(element) # Obtain lockxyz for each atom if atom.get('lockxyz') is not None: lxy=[] for l in atom.get('lockxyz').split(): if l=='True' or l=='true': lxyz.append(True) else: lxyz.append(False) lockxyz.append(lxyz) else: lockxyz.append([False, False, False]) #check the atomic positions type if 'cartesian' in root.find('structure').attrib.keys(): if root.find('structure').attrib['cartesian']: cartesian=True for i in range(len(positions)): for j in range(3): positions[i][j]=positions[i][j]*ExcitingInput.bohr2ang print(positions) else: cartesian=False # get the scale attribute scale_in=root.find('structure').find('crystal').get('scale') if scale_in: scale=float(scale_in)*ExcitingInput.bohr2ang else: scale=ExcitingInput.bohr2ang # get the stretch attribute stretch_in=root.find('structure').find('crystal').get('stretch') if stretch_in: stretch=np.array([float(a) for a in stretch_in]) else: stretch=np.array([1.0,1.0,1.0]) # get basis vectors and scale them accordingly basisnode=root.find('structure').find('crystal').iter('basevect') for vect in basisnode: x, y, z=vect.text.split() vectors.append([float(x)*stretch[0]*scale, float(y)*stretch[1]*scale, float(z)*stretch[2]*scale]) # create lattice and structure object lattice_in=Lattice(vectors) structure_in=Structure(lattice_in,elements,positions,coords_are_cartesian=cartesian) return ExcitingInput(structure_in, title_in, lockxyz)
def parse_composition(self, composition, species, atoms): try: composition = parse_separated_string(composition) mole_fractions = {} dummy_z = 1 dummy_species = [] atoms_in_supercell = atoms * SupercellXArgument( self._options)() * SupercellYArgument( self._options)() * SupercellZArgument(self._options)() atoms_mode = False for species_comp in composition: try: _species, mole_fraction = species_comp.split(':') except ValueError: if ':' not in species_comp: if species_comp == '0': _species = species_comp elif not Element.is_valid_symbol(species_comp): _species = Element.from_Z(dummy_z).symbol dummy_species.append(_species) dummy_z += 1 mole_fraction = species_comp else: continue try: mole_fraction = abs(float(mole_fraction)) if mole_fraction.is_integer() and mole_fraction >= 1: atoms_mode = True mole_fraction = mole_fraction / atoms_in_supercell else: mole_fraction = float(mole_fraction) except ValueError: try: mole_fraction = float(mole_fraction) except ValueError: self.write_message( 'Could not parse {0} in composition string. Resuming with 0.0' .format(mole_fraction)) raise InvalidOption if _species == '0' and '0' not in species: species.append('0') # if _species not in species and _species != '0': # write_message('Species "{0}" is not defined in POSCAR file'.format(_species)) if mole_fraction: mole_fractions[_species] = mole_fraction missing_species = [ specie for specie in species if specie not in mole_fractions.keys() ] if sum(mole_fractions.values()) > 1.0: print(mole_fractions) self.write_message( 'The mole fractions specified exceed 1. {0}'.format( mole_fractions)) raise InvalidOption if len(missing_species) > 1: self.write_message( 'You did not specify enough species. I\'m missing {0}!'. format(missing_species)) raise InvalidOption elif len(missing_species) == 1: missing_species = missing_species[0] mole_fractions[missing_species] = ( 1.0 - sum(mole_fractions.values())) elif len(missing_species) == 0: if not isclose(sum(mole_fractions.values()), 1.0): if '0' not in mole_fractions: self.write_message( "The mole fractions you specified do not. Resuming by filling the missing percentage with vacancies in composition definition {0}" .format(composition), level=WARNING) mole_fractions['0'] = (1 - sum(mole_fractions.values())) else: self.write_message( 'Your composition does not sum up to 1') raise InvalidOption else: self.write_message( 'The amount of specified mole fractions does not match the species specified. Expected list of length {0} or {1}' .format(len(species), len(species) - 1)) raise InvalidOption if len(dummy_species) > 0: self.write_message( 'Missing elements resuming with {0} for undefined elements' .format(dummy_species), level=WARNING) return mole_fractions except: write_message('Could not parse composition')