def test_no_oxidation_state(self): mo0 = Species("Mo", None, {"spin": 5}) self.assertEqual(str(mo0), "Mo,spin=5")
def test_stringify(self): self.assertEqual(self.specie2.to_latex_string(), "Fe$^{3+}$") self.assertEqual(self.specie2.to_unicode_string(), "Fe³⁺") self.assertEqual(Species("S", -2).to_latex_string(), "S$^{2-}$") self.assertEqual(Species("S", -2).to_unicode_string(), "S²⁻")
def test_deepcopy(self): el1 = Species("Fe", 4) el2 = Species("Na", 1) ellist = [el1, el2] self.assertEqual(ellist, deepcopy(ellist), "Deepcopy operation doesn't produce exact copy.")
def test_get_crystal_field_spin(self): self.assertEqual(Species("Fe", 2).get_crystal_field_spin(), 4) self.assertEqual(Species("Fe", 3).get_crystal_field_spin(), 5) self.assertEqual(Species("Fe", 4).get_crystal_field_spin(), 4) self.assertEqual( Species("Co", 3).get_crystal_field_spin(spin_config="low"), 0) self.assertEqual( Species("Co", 4).get_crystal_field_spin(spin_config="low"), 1) self.assertEqual( Species("Ni", 3).get_crystal_field_spin(spin_config="low"), 1) self.assertEqual( Species("Ni", 4).get_crystal_field_spin(spin_config="low"), 0) self.assertRaises(AttributeError, Species("Li", 1).get_crystal_field_spin) self.assertRaises(AttributeError, Species("Ge", 4).get_crystal_field_spin) self.assertRaises(AttributeError, Species("H", 1).get_crystal_field_spin) self.assertRaises(AttributeError, Species("Fe", 10).get_crystal_field_spin) self.assertRaises(ValueError, Species("Fe", 2).get_crystal_field_spin, "hex") s = Species("Co", 3).get_crystal_field_spin("tet", spin_config="low") self.assertEqual(s, 2)
def test_ionic_radius(self): self.assertEqual(self.specie2.ionic_radius, 78.5 / 100) self.assertEqual(self.specie3.ionic_radius, 92 / 100) self.assertAlmostEqual(Species("Mn", 4).ionic_radius, 0.67)
def test_cmp(self): self.assertLess(self.specie1, self.specie2, "Fe2+ should be < Fe3+") self.assertLess(Species("C", 1), Species("Se", 1))
class SpecieTestCase(PymatgenTest): 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}) def test_init(self): self.assertRaises(ValueError, Species, "Fe", 2, {"magmom": 5}) def test_ionic_radius(self): self.assertEqual(self.specie2.ionic_radius, 78.5 / 100) self.assertEqual(self.specie3.ionic_radius, 92 / 100) self.assertAlmostEqual(Species("Mn", 4).ionic_radius, 0.67) def test_eq(self): self.assertEqual( self.specie1, self.specie3, "Static and actual constructor gives unequal result!", ) self.assertNotEqual(self.specie1, self.specie2, "Fe2+ should not be equal to Fe3+") self.assertNotEqual(self.specie4, self.specie3) self.assertFalse(self.specie1 == Element("Fe")) self.assertFalse(Element("Fe") == self.specie1) def test_cmp(self): self.assertLess(self.specie1, self.specie2, "Fe2+ should be < Fe3+") self.assertLess(Species("C", 1), Species("Se", 1)) def test_attr(self): self.assertEqual(self.specie1.Z, 26, "Z attribute for Fe2+ should be = Element Fe.") self.assertEqual(self.specie4.spin, 5) def test_deepcopy(self): el1 = Species("Fe", 4) el2 = Species("Na", 1) ellist = [el1, el2] self.assertEqual(ellist, deepcopy(ellist), "Deepcopy operation doesn't produce exact copy.") def test_pickle(self): self.assertEqual(self.specie1, pickle.loads(pickle.dumps(self.specie1))) for i in range(1, 5): self.serialize_with_pickle(getattr(self, "specie%d" % i), test_eq=True) cs = Species("Cs", 1) cl = Species("Cl", 1) with open("cscl.pickle", "wb") as f: pickle.dump((cs, cl), f) with open("cscl.pickle", "rb") as f: d = pickle.load(f) self.assertEqual(d, (cs, cl)) os.remove("cscl.pickle") def test_get_crystal_field_spin(self): self.assertEqual(Species("Fe", 2).get_crystal_field_spin(), 4) self.assertEqual(Species("Fe", 3).get_crystal_field_spin(), 5) self.assertEqual(Species("Fe", 4).get_crystal_field_spin(), 4) self.assertEqual( Species("Co", 3).get_crystal_field_spin(spin_config="low"), 0) self.assertEqual( Species("Co", 4).get_crystal_field_spin(spin_config="low"), 1) self.assertEqual( Species("Ni", 3).get_crystal_field_spin(spin_config="low"), 1) self.assertEqual( Species("Ni", 4).get_crystal_field_spin(spin_config="low"), 0) self.assertRaises(AttributeError, Species("Li", 1).get_crystal_field_spin) self.assertRaises(AttributeError, Species("Ge", 4).get_crystal_field_spin) self.assertRaises(AttributeError, Species("H", 1).get_crystal_field_spin) self.assertRaises(AttributeError, Species("Fe", 10).get_crystal_field_spin) self.assertRaises(ValueError, Species("Fe", 2).get_crystal_field_spin, "hex") s = Species("Co", 3).get_crystal_field_spin("tet", spin_config="low") self.assertEqual(s, 2) def test_get_nmr_mom(self): self.assertEqual(Species("H").get_nmr_quadrupole_moment(), 2.860) self.assertEqual(Species("Li").get_nmr_quadrupole_moment(), -0.808) self.assertEqual( Species("Li").get_nmr_quadrupole_moment("Li-7"), -40.1) self.assertEqual(Species("Si").get_nmr_quadrupole_moment(), 0.0) self.assertRaises(ValueError, Species("Li").get_nmr_quadrupole_moment, "Li-109") def test_get_shannon_radius(self): self.assertEqual(Species("Li", 1).get_shannon_radius("IV"), 0.59) mn2 = Species("Mn", 2) self.assertEqual(mn2.get_shannon_radius("IV", "High Spin"), 0.66) self.assertEqual(mn2.get_shannon_radius("V", "High Spin"), 0.75) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") # Trigger a warning. r = mn2.get_shannon_radius("V") # Verify some things self.assertEqual(len(w), 1) self.assertIs(w[-1].category, UserWarning) self.assertEqual(r, 0.75) self.assertEqual(mn2.get_shannon_radius("VI", "Low Spin"), 0.67) self.assertEqual(mn2.get_shannon_radius("VI", "High Spin"), 0.83) self.assertEqual(mn2.get_shannon_radius("VII", "High Spin"), 0.9) self.assertEqual(mn2.get_shannon_radius("VIII"), 0.96) def test_sort(self): els = map(get_el_sp, ["N3-", "Si4+", "Si3+"]) self.assertEqual( sorted(els), [Species("Si", 3), Species("Si", 4), Species("N", -3)]) 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) # Shyue Ping: I don't understand the need for a None for oxidation state. That to me is basically an element. # Why make the thing so complicated for a use case that I have never seen??? # 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 test_no_oxidation_state(self): mo0 = Species("Mo", None, {"spin": 5}) self.assertEqual(str(mo0), "Mo,spin=5") def test_stringify(self): self.assertEqual(self.specie2.to_latex_string(), "Fe$^{3+}$") self.assertEqual(self.specie2.to_unicode_string(), "Fe³⁺") self.assertEqual(Species("S", -2).to_latex_string(), "S$^{2-}$") self.assertEqual(Species("S", -2).to_unicode_string(), "S²⁻")
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})
def test_get_nmr_mom(self): self.assertEqual(Species("H").get_nmr_quadrupole_moment(), 2.860) self.assertEqual(Species("Li").get_nmr_quadrupole_moment(), -0.808) self.assertEqual(Species("Li").get_nmr_quadrupole_moment("Li-7"), -40.1) self.assertEqual(Species("Si").get_nmr_quadrupole_moment(), 0.0) self.assertRaises(ValueError, Species("Li").get_nmr_quadrupole_moment, "Li-109")
def test_sort(self): els = map(get_el_sp, ["N3-", "Si4+", "Si3+"]) self.assertEqual(sorted(els), [Species("Si", 3), Species("Si", 4), Species("N", -3)])
def test_light_structure_environments(self): with ScratchDir("."): f = open(f"{se_files_dir}/se_mp-7000.json") dd = json.load(f) f.close() se = StructureEnvironments.from_dict(dd) strategy = SimplestChemenvStrategy() lse = LightStructureEnvironments.from_structure_environments( structure_environments=se, strategy=strategy, valences="undefined") isite = 6 nb_set = lse.neighbors_sets[isite][0] neighb_coords = [ np.array([0.2443798, 1.80409653, -1.13218359]), np.array([1.44020353, 1.11368738, 1.13218359]), np.array([2.75513098, 2.54465207, -0.70467298]), np.array([0.82616785, 3.65833945, 0.70467298]), ] neighb_indices = [0, 3, 5, 1] neighb_images = [[0, 0, -1], [0, 0, 0], [0, 0, -1], [0, 0, 0]] np.testing.assert_array_almost_equal(neighb_coords, nb_set.neighb_coords) np.testing.assert_array_almost_equal( neighb_coords, [s.coords for s in nb_set.neighb_sites]) nb_sai = nb_set.neighb_sites_and_indices np.testing.assert_array_almost_equal( neighb_coords, [sai["site"].coords for sai in nb_sai]) np.testing.assert_array_almost_equal( neighb_indices, [sai["index"] for sai in nb_sai]) nb_iai = nb_set.neighb_indices_and_images np.testing.assert_array_almost_equal( neighb_indices, [iai["index"] for iai in nb_iai]) np.testing.assert_array_equal( neighb_images, [iai["image_cell"] for iai in nb_iai]) self.assertEqual(nb_set.__len__(), 4) self.assertEqual(nb_set.__hash__(), 4) self.assertFalse(nb_set.__ne__(nb_set)) self.assertEqual( nb_set.__str__(), "Neighbors Set for site #6 :\n" " - Coordination number : 4\n" " - Neighbors sites indices : 0, 1, 2, 3\n", ) stats = lse.get_statistics() neighbors = lse.strategy.get_site_neighbors( site=lse.structure[isite]) self.assertArrayAlmostEqual( neighbors[0].coords, np.array([0.2443798, 1.80409653, -1.13218359])) self.assertArrayAlmostEqual( neighbors[1].coords, np.array([1.44020353, 1.11368738, 1.13218359])) self.assertArrayAlmostEqual( neighbors[2].coords, np.array([2.75513098, 2.54465207, -0.70467298])) self.assertArrayAlmostEqual( neighbors[3].coords, np.array([0.82616785, 3.65833945, 0.70467298])) equiv_site_index_and_transform = lse.strategy.equivalent_site_index_and_transform( neighbors[0]) self.assertEqual(equiv_site_index_and_transform[0], 0) self.assertArrayAlmostEqual(equiv_site_index_and_transform[1], [0.0, 0.0, 0.0]) self.assertArrayAlmostEqual(equiv_site_index_and_transform[2], [0.0, 0.0, -1.0]) equiv_site_index_and_transform = lse.strategy.equivalent_site_index_and_transform( neighbors[1]) self.assertEqual(equiv_site_index_and_transform[0], 3) self.assertArrayAlmostEqual(equiv_site_index_and_transform[1], [0.0, 0.0, 0.0]) self.assertArrayAlmostEqual(equiv_site_index_and_transform[2], [0.0, 0.0, 0.0]) self.assertEqual(stats["atom_coordination_environments_present"], {"Si": { "T:4": 3.0 }}) self.assertEqual(stats["coordination_environments_atom_present"], {"T:4": { "Si": 3.0 }}) self.assertEqual( stats["fraction_atom_coordination_environments_present"], {"Si": { "T:4": 1.0 }}, ) site_info_ce = lse.get_site_info_for_specie_ce(specie=Species( "Si", 4), ce_symbol="T:4") np.testing.assert_array_almost_equal(site_info_ce["fractions"], [1.0, 1.0, 1.0]) np.testing.assert_array_almost_equal( site_info_ce["csms"], [ 0.009887784240541068, 0.009887786546730826, 0.009887787384385317 ], ) self.assertEqual(site_info_ce["isites"], [6, 7, 8]) site_info_allces = lse.get_site_info_for_specie_allces( specie=Species("Si", 4)) self.assertEqual(site_info_allces["T:4"], site_info_ce) self.assertFalse(lse.contains_only_one_anion("I-")) self.assertFalse(lse.contains_only_one_anion_atom("I")) self.assertTrue( lse.site_contains_environment(isite=isite, ce_symbol="T:4")) self.assertFalse( lse.site_contains_environment(isite=isite, ce_symbol="S:4")) self.assertFalse( lse.structure_contains_atom_environment(atom_symbol="Si", ce_symbol="S:4")) self.assertTrue( lse.structure_contains_atom_environment(atom_symbol="Si", ce_symbol="T:4")) self.assertFalse( lse.structure_contains_atom_environment(atom_symbol="O", ce_symbol="T:4")) self.assertTrue(lse.uniquely_determines_coordination_environments) self.assertFalse(lse.__ne__(lse)) envs = lse.strategy.get_site_coordination_environments( lse.structure[6]) self.assertEqual(len(envs), 1) self.assertEqual(envs[0][0], "T:4") multi_strategy = MultiWeightsChemenvStrategy.stats_article_weights_parameters( ) lse_multi = LightStructureEnvironments.from_structure_environments( strategy=multi_strategy, structure_environments=se, valences="undefined") self.assertAlmostEqual( lse_multi.coordination_environments[isite][0]["csm"], 0.009887784240541068, ) self.assertAlmostEqual( lse_multi.coordination_environments[isite][0]["ce_fraction"], 1.0) self.assertEqual( lse_multi.coordination_environments[isite][0]["ce_symbol"], "T:4")
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("Composition {} cannot accommodate max_sites " "setting!".format(comp)) 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("Composition {} cannot accommodate max_sites " "setting!".format(comp)) # 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( dict((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
"H", "B", "C", "Si", "N", "P", "As", "Sb", "O", "S", "Se", "Te", "F", "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"
"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). """
def test_find_codopant(self): self.assertEqual(_find_codopant(Species("Fe", 2), 1), Species("Cu", 1)) self.assertEqual(_find_codopant(Species("Fe", 2), 3), Species("In", 3))