def test_to_from_string(self): fe3 = Species("Fe", 3, {"spin": 5}) self.assertEqual(str(fe3), "Fe3+,spin=5") fe = Species.from_string("Fe3+,spin=5") self.assertEqual(fe.spin, 5) mo0 = Species("Mo", 0, {"spin": 5}) self.assertEqual(str(mo0), "Mo0+,spin=5") mo = Species.from_string("Mo0+,spin=4") self.assertEqual(mo.spin, 4)
def test_to_from_string(self): fe3 = Species("Fe", 3, {"spin": 5}) self.assertEqual(str(fe3), "Fe3+,spin=5") fe = Species.from_string("Fe3+,spin=5") self.assertEqual(fe.spin, 5) mo0 = Species("Mo", 0, {"spin": 5}) self.assertEqual(str(mo0), "Mo0+,spin=5") mo = Species.from_string("Mo0+,spin=4") self.assertEqual(mo.spin, 4) fe_no_ox = Species("Fe", oxidation_state=None, properties={"spin": 5}) fe_no_ox_from_str = Species.from_string("Fe,spin=5") self.assertEqual(fe_no_ox, fe_no_ox_from_str)
def __init__(self, lambda_table=None, alpha=-5): """ Args: lambda_table: json table of the weight functions lambda if None, will use the default lambda.json table alpha: weight function for never observed substitutions """ if lambda_table is not None: self._lambda_table = lambda_table else: module_dir = os.path.dirname(__file__) json_file = os.path.join(module_dir, "data", "lambda.json") with open(json_file) as f: self._lambda_table = json.load(f) # build map of specie pairs to lambdas self.alpha = alpha self._l = {} self.species = set() for row in self._lambda_table: if "D1+" not in row: s1 = Species.from_string(row[0]) s2 = Species.from_string(row[1]) self.species.add(s1) self.species.add(s2) self._l[frozenset([s1, s2])] = float(row[2]) # create Z and px self.Z = 0 self._px = defaultdict(float) for s1, s2 in itertools.product(self.species, repeat=2): value = math.exp(self.get_lambda(s1, s2)) self._px[s1] += value / 2 self._px[s2] += value / 2 self.Z += value
def find_connected_atoms(struct, tolerance=0.45, ldict=JmolNN().el_radius): """ Finds bonded atoms and returns a adjacency matrix of bonded atoms. Author: "Gowoon Cheon" Email: "*****@*****.**" Args: struct (Structure): Input structure tolerance: length in angstroms used in finding bonded atoms. Two atoms are considered bonded if (radius of atom 1) + (radius of atom 2) + (tolerance) < (distance between atoms 1 and 2). Default value = 0.45, the value used by JMol and Cheon et al. ldict: dictionary of bond lengths used in finding bonded atoms. Values from JMol are used as default Returns: (np.ndarray): A numpy array of shape (number of atoms, number of atoms); If any image of atom j is bonded to atom i with periodic boundary conditions, the matrix element [atom i, atom j] is 1. """ # pylint: disable=E1136 n_atoms = len(struct.species) fc = np.array(struct.frac_coords) fc_copy = np.repeat(fc[:, :, np.newaxis], 27, axis=2) neighbors = np.array( list(itertools.product([0, 1, -1], [0, 1, -1], [0, 1, -1]))).T neighbors = np.repeat(neighbors[np.newaxis, :, :], 1, axis=0) fc_diff = fc_copy - neighbors species = list(map(str, struct.species)) # in case of charged species for i, item in enumerate(species): if item not in ldict.keys(): species[i] = str(Species.from_string(item).element) latmat = struct.lattice.matrix connected_matrix = np.zeros((n_atoms, n_atoms)) for i in range(n_atoms): for j in range(i + 1, n_atoms): max_bond_length = ldict[species[i]] + ldict[species[j]] + tolerance frac_diff = fc_diff[j] - fc_copy[i] distance_ij = np.dot(latmat.T, frac_diff) # print(np.linalg.norm(distance_ij,axis=0)) if sum(np.linalg.norm(distance_ij, axis=0) < max_bond_length) > 0: connected_matrix[i, j] = 1 connected_matrix[j, i] = 1 return connected_matrix
def find_connected_atoms(struct, tolerance=0.45, ldict=JmolNN().el_radius): """ Finds the list of bonded atoms. Args: struct (Structure): Input structure tolerance: length in angstroms used in finding bonded atoms. Two atoms are considered bonded if (radius of atom 1) + (radius of atom 2) + (tolerance) < (distance between atoms 1 and 2). Default value = 0.45, the value used by JMol and Cheon et al. ldict: dictionary of bond lengths used in finding bonded atoms. Values from JMol are used as default standardize: works with conventional standard structures if True. It is recommended to keep this as True. Returns: connected_list: A numpy array of shape (number of bonded pairs, 2); each row of is of the form [atomi, atomj]. atomi and atomj are the indices of the atoms in the input structure. If any image of atomj is bonded to atomi with periodic boundary conditions, [atomi, atomj] is included in the list. If atomi is bonded to multiple images of atomj, it is only counted once. """ n_atoms = len(struct.species) fc = np.array(struct.frac_coords) species = list(map(str, struct.species)) # in case of charged species for i, item in enumerate(species): if item not in ldict.keys(): species[i] = str(Species.from_string(item).element) latmat = struct.lattice.matrix connected_list = [] for i in range(n_atoms): for j in range(i + 1, n_atoms): max_bond_length = ldict[species[i]] + ldict[species[j]] + tolerance add_ij = False for move_cell in itertools.product([0, 1, -1], [0, 1, -1], [0, 1, -1]): if not add_ij: frac_diff = fc[j] + move_cell - fc[i] distance_ij = np.dot(latmat.T, frac_diff) if np.linalg.norm(distance_ij) < max_bond_length: add_ij = True if add_ij: connected_list.append([i, j]) return np.array(connected_list)
def _get_oxid_state_guesses(self, all_oxi_states, max_sites, oxi_states_override, target_charge): """ Utility operation for guessing oxidation states. See `oxi_state_guesses` for full details. This operation does the calculation of the most likely oxidation states Args: oxi_states_override (dict): dict of str->list to override an element's common oxidation states, e.g. {"V": [2,3,4,5]} target_charge (int): the desired total charge on the structure. Default is 0 signifying charge balance. all_oxi_states (bool): if True, an element defaults to all oxidation states in pymatgen Element.icsd_oxidation_states. Otherwise, default is Element.common_oxidation_states. Note that the full oxidation state list is *very* inclusive and can produce nonsensical results. max_sites (int): if possible, will reduce Compositions to at most this many sites to speed up oxidation state guesses. If the composition cannot be reduced to this many sites a ValueError will be raised. Set to -1 to just reduce fully. If set to a number less than -1, the formula will be fully reduced but a ValueError will be thrown if the number of atoms in the reduced formula is greater than abs(max_sites). Returns: A list of dicts - each dict reports an element symbol and average oxidation state across all sites in that composition. If the composition is not charge balanced, an empty list is returned. A list of dicts - each dict maps the element symbol to a list of oxidation states for each site of that element. For example, Fe3O4 could return a list of [2,2,2,3,3,3] for the oxidation states of If the composition is """ comp = self.copy() # reduce Composition if necessary if max_sites and max_sites < 0: comp = self.reduced_composition if max_sites < -1 and comp.num_atoms > abs(max_sites): raise ValueError(f"Composition {comp} cannot accommodate max_sites setting!") elif max_sites and comp.num_atoms > max_sites: reduced_comp, reduced_factor = self.get_reduced_composition_and_factor() if reduced_factor > 1: reduced_comp *= max(1, int(max_sites / reduced_comp.num_atoms)) comp = reduced_comp # as close to max_sites as possible if comp.num_atoms > max_sites: raise ValueError(f"Composition {comp} cannot accommodate max_sites setting!") # Load prior probabilities of oxidation states, used to rank solutions if not Composition.oxi_prob: module_dir = os.path.join(os.path.dirname(os.path.abspath(__file__))) all_data = loadfn(os.path.join(module_dir, "..", "analysis", "icsd_bv.yaml")) Composition.oxi_prob = {Species.from_string(sp): data for sp, data in all_data["occurrence"].items()} oxi_states_override = oxi_states_override or {} # assert: Composition only has integer amounts if not all(amt == int(amt) for amt in comp.values()): raise ValueError("Charge balance analysis requires integer values in Composition!") # for each element, determine all possible sum of oxidations # (taking into account nsites for that particular element) el_amt = comp.get_el_amt_dict() els = el_amt.keys() el_sums = [] # matrix: dim1= el_idx, dim2=possible sums el_sum_scores = collections.defaultdict(set) # dict of el_idx, sum -> score el_best_oxid_combo = {} # dict of el_idx, sum -> oxid combo with best score for idx, el in enumerate(els): el_sum_scores[idx] = {} el_best_oxid_combo[idx] = {} el_sums.append([]) if oxi_states_override.get(el): oxids = oxi_states_override[el] elif all_oxi_states: oxids = Element(el).oxidation_states else: oxids = Element(el).icsd_oxidation_states or Element(el).oxidation_states # get all possible combinations of oxidation states # and sum each combination for oxid_combo in combinations_with_replacement(oxids, int(el_amt[el])): # List this sum as a possible option oxid_sum = sum(oxid_combo) if oxid_sum not in el_sums[idx]: el_sums[idx].append(oxid_sum) # Determine how probable is this combo? score = sum(Composition.oxi_prob.get(Species(el, o), 0) for o in oxid_combo) # If it is the most probable combo for a certain sum, # store the combination if oxid_sum not in el_sum_scores[idx] or score > el_sum_scores[idx].get(oxid_sum, 0): el_sum_scores[idx][oxid_sum] = score el_best_oxid_combo[idx][oxid_sum] = oxid_combo # Determine which combination of oxidation states for each element # is the most probable all_sols = [] # will contain all solutions all_oxid_combo = [] # will contain the best combination of oxidation states for each site all_scores = [] # will contain a score for each solution for x in product(*el_sums): # each x is a trial of one possible oxidation sum for each element if sum(x) == target_charge: # charge balance condition el_sum_sol = dict(zip(els, x)) # element->oxid_sum # normalize oxid_sum by amount to get avg oxid state sol = {el: v / el_amt[el] for el, v in el_sum_sol.items()} # add the solution to the list of solutions all_sols.append(sol) # determine the score for this solution score = 0 for idx, v in enumerate(x): score += el_sum_scores[idx][v] all_scores.append(score) # collect the combination of oxidation states for each site all_oxid_combo.append({e: el_best_oxid_combo[idx][v] for idx, (e, v) in enumerate(zip(els, x))}) # sort the solutions by highest to lowest score if all_scores: all_sols, all_oxid_combo = zip( *( (y, x) for (z, y, x) in sorted( zip(all_scores, all_sols, all_oxid_combo), key=lambda pair: pair[0], reverse=True, ) ) ) return all_sols, all_oxid_combo
"Br", "I", ] ] module_dir = os.path.dirname(os.path.abspath(__file__)) # Read in BV parameters. BV_PARAMS = {} for k, v in loadfn(os.path.join(module_dir, "bvparam_1991.yaml")).items(): BV_PARAMS[Element(k)] = v # Read in yaml containing data-mined ICSD BV data. all_data = loadfn(os.path.join(module_dir, "icsd_bv.yaml")) ICSD_BV_DATA = { Species.from_string(sp): data for sp, data in all_data["bvsum"].items() } PRIOR_PROB = { Species.from_string(sp): data for sp, data in all_data["occurrence"].items() } def calculate_bv_sum(site, nn_list, scale_factor=1.0): """ Calculates the BV sum of a site. Args: site (PeriodicSite): The central site to calculate the bond valence nn_list ([Neighbor]): A list of namedtuple Neighbors having "distance"
def setUp(self): self.specie1 = Species.from_string("Fe2+") self.specie2 = Species("Fe", 3) self.specie3 = Species("Fe", 2) self.specie4 = Species("Fe", 2, {"spin": 5})
"Cl", "Br", "I", ] ] module_dir = os.path.dirname(os.path.abspath(__file__)) # Read in BV parameters. BV_PARAMS = {} for k, v in loadfn(os.path.join(module_dir, "bvparam_1991.yaml")).items(): BV_PARAMS[Element(k)] = v # Read in yaml containing data-mined ICSD BV data. all_data = loadfn(os.path.join(module_dir, "icsd_bv.yaml")) ICSD_BV_DATA = {Species.from_string(sp): data for sp, data in all_data["bvsum"].items()} PRIOR_PROB = {Species.from_string(sp): data for sp, data in all_data["occurrence"].items()} def calculate_bv_sum(site, nn_list, scale_factor=1.0): """ Calculates the BV sum of a site. Args: site (PeriodicSite): The central site to calculate the bond valence nn_list ([Neighbor]): A list of namedtuple Neighbors having "distance" and "site" attributes scale_factor (float): A scale factor to be applied. This is useful for scaling distance, esp in the case of calculation-relaxed structures which may tend to under (GGA) or over bind (LDA). """