class JMolNNTest(PymatgenTest): def setUp(self): self.jmol = JMolNN() self.jmol_update = JMolNN(el_radius_updates={"Li": 1}) def test_get_nn(self): s = self.get_structure('LiFePO4') # Test the default near-neighbor finder. nsites_checked = 0 for site_idx, site in enumerate(s): if site.specie == Element("Li"): self.assertEqual(self.jmol.get_cn(s, site_idx), 0) nsites_checked += 1 elif site.specie == Element("Fe"): self.assertEqual(self.jmol.get_cn(s, site_idx), 6) nsites_checked += 1 elif site.specie == Element("P"): self.assertEqual(self.jmol.get_cn(s, site_idx), 4) nsites_checked += 1 self.assertEqual(nsites_checked, 12) # Test a user override that would cause Li to show up as 6-coordinated self.assertEqual(self.jmol_update.get_cn(s, 0), 6) # Verify get_nn function works self.assertEqual(len(self.jmol_update.get_nn(s, 0)), 6) def tearDown(self): del self.jmol del self.jmol_update
def test_cns(self): cnv = CoordinationNumber.from_preset('VoronoiNN') self.assertEqual(len(cnv.feature_labels()), 1) self.assertEqual(cnv.feature_labels()[0], 'CN_VoronoiNN') self.assertAlmostEqual(cnv.featurize(self.sc, 0)[0], 6) self.assertAlmostEqual(cnv.featurize(self.cscl, 0)[0], 14) self.assertAlmostEqual(cnv.featurize(self.cscl, 1)[0], 14) self.assertEqual(len(cnv.citations()), 2) cnv = CoordinationNumber(VoronoiNN(), use_weights='sum') self.assertEqual(cnv.feature_labels()[0], 'CN_VoronoiNN') self.assertAlmostEqual(cnv.featurize(self.cscl, 0)[0], 9.2584516) self.assertAlmostEqual(cnv.featurize(self.cscl, 1)[0], 9.2584516) self.assertEqual(len(cnv.citations()), 2) cnv = CoordinationNumber(VoronoiNN(), use_weights='effective') self.assertEqual(cnv.feature_labels()[0], 'CN_VoronoiNN') self.assertAlmostEqual(cnv.featurize(self.cscl, 0)[0], 11.648923254) self.assertAlmostEqual(cnv.featurize(self.cscl, 1)[0], 11.648923254) self.assertEqual(len(cnv.citations()), 2) cnj = CoordinationNumber.from_preset('JMolNN') self.assertEqual(cnj.feature_labels()[0], 'CN_JMolNN') self.assertAlmostEqual(cnj.featurize(self.sc, 0)[0], 0) self.assertAlmostEqual(cnj.featurize(self.cscl, 0)[0], 0) self.assertAlmostEqual(cnj.featurize(self.cscl, 1)[0], 0) self.assertEqual(len(cnj.citations()), 1) jmnn = JMolNN(el_radius_updates={"Al": 1.55, "Cl": 1.7, "Cs": 1.7}) cnj = CoordinationNumber(jmnn) self.assertEqual(cnj.feature_labels()[0], 'CN_JMolNN') self.assertAlmostEqual(cnj.featurize(self.sc, 0)[0], 6) self.assertAlmostEqual(cnj.featurize(self.cscl, 0)[0], 8) self.assertAlmostEqual(cnj.featurize(self.cscl, 1)[0], 8) self.assertEqual(len(cnj.citations()), 1) cnmd = CoordinationNumber.from_preset('MinimumDistanceNN') self.assertEqual(cnmd.feature_labels()[0], 'CN_MinimumDistanceNN') self.assertAlmostEqual(cnmd.featurize(self.sc, 0)[0], 6) self.assertAlmostEqual(cnmd.featurize(self.cscl, 0)[0], 8) self.assertAlmostEqual(cnmd.featurize(self.cscl, 1)[0], 8) self.assertEqual(len(cnmd.citations()), 1) cnmok = CoordinationNumber.from_preset('MinimumOKeeffeNN') self.assertEqual(cnmok.feature_labels()[0], 'CN_MinimumOKeeffeNN') self.assertAlmostEqual(cnmok.featurize(self.sc, 0)[0], 6) self.assertAlmostEqual(cnmok.featurize(self.cscl, 0)[0], 8) self.assertAlmostEqual(cnmok.featurize(self.cscl, 1)[0], 6) self.assertEqual(len(cnmok.citations()), 2) cnmvire = CoordinationNumber.from_preset('MinimumVIRENN') self.assertEqual(cnmvire.feature_labels()[0], 'CN_MinimumVIRENN') self.assertAlmostEqual(cnmvire.featurize(self.sc, 0)[0], 6) self.assertAlmostEqual(cnmvire.featurize(self.cscl, 0)[0], 8) self.assertAlmostEqual(cnmvire.featurize(self.cscl, 1)[0], 14) self.assertEqual(len(cnmvire.citations()), 2) self.assertEqual(len(cnmvire.implementors()), 2) self.assertEqual(cnmvire.implementors()[0], 'Nils E. R. Zimmermann')
def get_max_bond_lengths(structure, el_radius_updates=None): """ Provides max bond length estimates for a structure based on the JMol table and algorithms. Args: structure: (structure) el_radius_updates: (dict) symbol->float to update atomic radii Returns: (dict) - (Element1, Element2) -> float. The two elements are ordered by Z. """ #jmc = JMolCoordFinder(el_radius_updates) jmnn = JMolNN(el_radius_updates) bonds_lens = {} els = sorted(structure.composition.elements, key=lambda x: x.Z) for i1 in range(len(els)): for i2 in range(len(els) - i1): bonds_lens[els[i1], els[i1 + i2]] = jmnn.get_max_bond_distance( els[i1].symbol, els[i1 + i2].symbol) return bonds_lens
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 not item in ldict.keys(): species[i] = str(Specie.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 setUp(self): self.jmol = JMolNN() self.jmol_update = JMolNN(el_radius_updates={"Li": 1})
def find_dimension(structure_raw, tolerance=0.45, ldict=JMolNN().el_radius, standardize=True): """ Algorithm for finding the dimensions of connected subunits in a crystal structure. This method finds the dimensionality of the material even when the material is not layered along low-index planes, or does not have flat layers/molecular wires. See details at : Cheon, G.; Duerloo, K.-A. N.; Sendek, A. D.; Porter, C.; Chen, Y.; Reed, E. J. Data Mining for New Two- and One-Dimensional Weakly Bonded Solids and Lattice-Commensurate Heterostructures. Nano Lett. 2017. Args: structure (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: dim: dimension of the largest cluster as a string. If there are ions or molecules it returns 'intercalated ion/molecule' """ if standardize: structure = SpacegroupAnalyzer( structure_raw).get_conventional_standard_structure() structure_save = copy.copy(structure_raw) connected_list1 = find_connected_atoms(structure, tolerance=tolerance, ldict=ldict) max1, min1, clusters1 = find_clusters(structure, connected_list1) structure.make_supercell([[2, 0, 0], [0, 2, 0], [0, 0, 2]]) connected_list2 = find_connected_atoms(structure, tolerance=tolerance, ldict=ldict) max2, min2, clusters2 = find_clusters(structure, connected_list2) if min2 == 1: dim = 'intercalated ion' elif min2 == min1: if max2 == max1: dim = '0D' else: dim = 'intercalated molecule' else: dim = np.log2(float(max2) / max1) if dim == int(dim): dim = str(int(dim)) + 'D' else: structure = copy.copy(structure_save) structure.make_supercell([[3, 0, 0], [0, 3, 0], [0, 0, 3]]) connected_list3 = find_connected_atoms(structure, tolerance=tolerance, ldict=ldict) max3, min3, clusters3 = find_clusters(structure, connected_list3) if min3 == min2: if max3 == max2: dim = '0D' else: dim = 'intercalated molecule' else: dim = np.log2(float(max3) / max1) / np.log2(3) if dim == int(dim): dim = str(int(dim)) + 'D' else: return return dim