def test_from_magnetic_spacegroup(self): # AFM MnF s1 = Structure.from_magnetic_spacegroup("P4_2'/mnm'", Lattice.tetragonal(4.87, 3.30), ["Mn", "F"], [[0, 0, 0], [0.30, 0.30, 0.00]], {'magmom': [4, 0]}) self.assertEqual(s1.formula, "Mn2 F4") self.assertEqual(sum(map(float, s1.site_properties['magmom'])), 0) self.assertEqual(max(map(float, s1.site_properties['magmom'])), 4) self.assertEqual(min(map(float, s1.site_properties['magmom'])), -4) # AFM LaMnO3, ordered on (001) planes s2 = Structure.from_magnetic_spacegroup("Pn'ma'", Lattice.orthorhombic(5.75, 7.66, 5.53), ["La", "Mn", "O", "O"], [[0.05, 0.25, 0.99], [0.00, 0.00, 0.50], [0.48, 0.25, 0.08], [0.31, 0.04, 0.72]], {'magmom': [0, Magmom([4, 0, 0]), 0, 0]}) self.assertEqual(s2.formula, "La4 Mn4 O12") self.assertEqual(sum(map(float, s2.site_properties['magmom'])), 0) self.assertEqual(max(map(float, s2.site_properties['magmom'])), 4) self.assertEqual(min(map(float, s2.site_properties['magmom'])), -4)
def test_get_slabs(self): gen = SlabGenerator(self.get_structure("CsCl"), [0, 0, 1], 10, 10) #Test orthogonality of some internal variables. a, b, c = gen.oriented_unit_cell.lattice.matrix self.assertAlmostEqual(np.dot(a, gen._normal), 0) self.assertAlmostEqual(np.dot(b, gen._normal), 0) self.assertEqual(len(gen.get_slabs()), 1) s = self.get_structure("LiFePO4") gen = SlabGenerator(s, [0, 0, 1], 10, 10) self.assertEqual(len(gen.get_slabs()), 5) self.assertEqual(len(gen.get_slabs(bonds={("P", "O"): 3})), 2) # There are no slabs in LFP that does not break either P-O or Fe-O # bonds for a miller index of [0, 0, 1]. self.assertEqual(len(gen.get_slabs( bonds={("P", "O"): 3, ("Fe", "O"): 3})), 0) #If we allow some broken bonds, there are a few slabs. self.assertEqual(len(gen.get_slabs( bonds={("P", "O"): 3, ("Fe", "O"): 3}, max_broken_bonds=2)), 2) # At this threshold, only the origin and center Li results in # clustering. All other sites are non-clustered. So the of # slabs is of sites in LiFePO4 unit cell - 2 + 1. self.assertEqual(len(gen.get_slabs(tol=1e-4)), 15) LiCoO2=Structure.from_file(get_path("icsd_LiCoO2.cif"), primitive=False) gen = SlabGenerator(LiCoO2, [0, 0, 1], 10, 10) lco = gen.get_slabs(bonds={("Co", "O"): 3}) self.assertEqual(len(lco), 1) a, b, c = gen.oriented_unit_cell.lattice.matrix self.assertAlmostEqual(np.dot(a, gen._normal), 0) self.assertAlmostEqual(np.dot(b, gen._normal), 0) scc = Structure.from_spacegroup("Pm-3m", Lattice.cubic(3), ["Fe"], [[0, 0, 0]]) gen = SlabGenerator(scc, [0, 0, 1], 10, 10) slabs = gen.get_slabs() self.assertEqual(len(slabs), 1) gen = SlabGenerator(scc, [1, 1, 1], 10, 10, max_normal_search=1) slabs = gen.get_slabs() self.assertEqual(len(slabs), 1) # Test whether using units of hkl planes instead of Angstroms for # min_slab_size and min_vac_size will give us the same number of atoms natoms = [] for a in [1, 1.4, 2.5, 3.6]: s = Structure.from_spacegroup("Im-3m", Lattice.cubic(a), ["Fe"], [[0,0,0]]) slabgen = SlabGenerator(s, (1,1,1), 10, 10, in_unit_planes=True, max_normal_search=2) natoms.append(len(slabgen.get_slab())) n = natoms[0] for i in natoms: self.assertEqual(n, i)
def test_get_pattern(self): s = self.get_structure("CsCl") c = XRDCalculator() xrd = c.get_pattern(s, two_theta_range=(0, 90)) self.assertTrue(xrd.to_json()) # Test MSONAble property # Check the first two peaks self.assertAlmostEqual(xrd.x[0], 21.107738329639844) self.assertAlmostEqual(xrd.y[0], 36.483184003748946) self.assertEqual(xrd.hkls[0], [{'hkl': (1, 0, 0), 'multiplicity': 6}]) self.assertAlmostEqual(xrd.d_hkls[0], 4.2089999999999996) self.assertAlmostEqual(xrd.x[1], 30.024695921112777) self.assertAlmostEqual(xrd.y[1], 100) self.assertEqual(xrd.hkls[1], [{"hkl": (1, 1, 0), "multiplicity": 12}]) self.assertAlmostEqual(xrd.d_hkls[1], 2.976212442014178) s = self.get_structure("LiFePO4") xrd = c.get_pattern(s, two_theta_range=(0, 90)) self.assertAlmostEqual(xrd.x[1], 17.03504233621785) self.assertAlmostEqual(xrd.y[1], 50.400928948337075) s = self.get_structure("Li10GeP2S12") xrd = c.get_pattern(s, two_theta_range=(0, 90)) self.assertAlmostEqual(xrd.x[1], 14.058274883353876) self.assertAlmostEqual(xrd.y[1], 4.4111123641667671) # Test a hexagonal structure. s = self.get_structure("Graphite") xrd = c.get_pattern(s, two_theta_range=(0, 90)) self.assertAlmostEqual(xrd.x[0], 26.21057350859598) self.assertAlmostEqual(xrd.y[0], 100) self.assertAlmostEqual(len(xrd.hkls[0][0]["hkl"]), 4) # Add test case with different lengths of coefficients. # Also test d_hkl. coords = [[0.25, 0.25, 0.173], [0.75, 0.75, 0.827], [0.75, 0.25, 0], [0.25, 0.75, 0], [0.25, 0.25, 0.676], [0.75, 0.75, 0.324]] sp = ["Si", "Si", "Ru", "Ru", "Pr", "Pr"] s = Structure(Lattice.tetragonal(4.192, 6.88), sp, coords) xrd = c.get_pattern(s) self.assertAlmostEqual(xrd.x[0], 12.86727341476735) self.assertAlmostEqual(xrd.y[0], 31.448239816769796) self.assertAlmostEqual(xrd.d_hkls[0], 6.88) self.assertEqual(len(xrd), 42) xrd = c.get_pattern(s, two_theta_range=[0, 60]) self.assertEqual(len(xrd), 18) # Test with and without Debye-Waller factor tungsten = Structure(Lattice.cubic(3.1653), ["W"] * 2, [[0, 0, 0], [0.5, 0.5, 0.5]]) xrd = c.get_pattern(tungsten, scaled=False) self.assertAlmostEqual(xrd.x[0], 40.294828554672264) self.assertAlmostEqual(xrd.y[0], 2414237.5633093244) self.assertAlmostEqual(xrd.d_hkls[0], 2.2382050944897789) c = XRDCalculator(debye_waller_factors={"W": 0.1526}) xrd = c.get_pattern(tungsten, scaled=False) self.assertAlmostEqual(xrd.x[0], 40.294828554672264) self.assertAlmostEqual(xrd.y[0], 2377745.2296686019) self.assertAlmostEqual(xrd.d_hkls[0], 2.2382050944897789) c.get_plot(tungsten).show()
def test_normal_search(self): fcc = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3), ["Fe"], [[0, 0, 0]]) for miller in [(1, 0, 0), (1, 1, 0), (1, 1, 1), (2, 1, 1)]: gen = SlabGenerator(fcc, miller, 10, 10) gen_normal = SlabGenerator(fcc, miller, 10, 10, max_normal_search=max(miller)) slab = gen_normal.get_slab() self.assertAlmostEqual(slab.lattice.alpha, 90) self.assertAlmostEqual(slab.lattice.beta, 90) self.assertGreaterEqual(len(gen_normal.oriented_unit_cell), len(gen.oriented_unit_cell)) graphite = self.get_structure("Graphite") for miller in [(1, 0, 0), (1, 1, 0), (0, 0, 1), (2, 1, 1)]: gen = SlabGenerator(graphite, miller, 10, 10) gen_normal = SlabGenerator(graphite, miller, 10, 10, max_normal_search=max(miller)) self.assertGreaterEqual(len(gen_normal.oriented_unit_cell), len(gen.oriented_unit_cell)) sc = Structure(Lattice.hexagonal(3.32, 5.15), ["Sc", "Sc"], [[1/3, 2/3, 0.25], [2/3, 1/3, 0.75]]) gen = SlabGenerator(sc, (1, 1, 1), 10, 10, max_normal_search=1) self.assertAlmostEqual(gen.oriented_unit_cell.lattice.angles[1], 90)
def test_get_slab(self): s = self.get_structure("LiFePO4") gen = SlabGenerator(s, [0, 0, 1], 10, 10) s = gen.get_slab(0.25) self.assertAlmostEqual(s.lattice.abc[2], 20.820740000000001) fcc = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3), ["Fe"], [[0, 0, 0]]) gen = SlabGenerator(fcc, [1, 1, 1], 10, 10) slab = gen.get_slab() gen = SlabGenerator(fcc, [1, 1, 1], 10, 10, primitive=False) slab_non_prim = gen.get_slab() self.assertEqual(len(slab), 6) self.assertEqual(len(slab_non_prim), len(slab) * 4) #Some randomized testing of cell vectors for i in range(1, 231): i = random.randint(1, 230) sg = SpaceGroup.from_int_number(i) if sg.crystal_system == "hexagonal" or (sg.crystal_system == \ "trigonal" and sg.symbol.endswith("H")): latt = Lattice.hexagonal(5, 10) else: #Cubic lattice is compatible with all other space groups. latt = Lattice.cubic(5) s = Structure.from_spacegroup(i, latt, ["H"], [[0, 0, 0]]) miller = (0, 0, 0) while miller == (0, 0, 0): miller = (random.randint(0, 6), random.randint(0, 6), random.randint(0, 6)) gen = SlabGenerator(s, miller, 10, 10) a, b, c = gen.oriented_unit_cell.lattice.matrix self.assertAlmostEqual(np.dot(a, gen._normal), 0) self.assertAlmostEqual(np.dot(b, gen._normal), 0)
def setUp(self): zno1 = Structure.from_file(get_path("ZnO-wz.cif"), primitive=False) zno55 = SlabGenerator(zno1, [1, 0, 0], 5, 5, lll_reduce=False, center_slab=False).get_slab() Ti = Structure(Lattice.hexagonal(4.6, 2.82), ["Ti", "Ti", "Ti"], [[0.000000, 0.000000, 0.000000], [0.333333, 0.666667, 0.500000], [0.666667, 0.333333, 0.500000]]) Ag_fcc = Structure(Lattice.cubic(4.06), ["Ag", "Ag", "Ag", "Ag"], [[0.000000, 0.000000, 0.000000], [0.000000, 0.500000, 0.500000], [0.500000, 0.000000, 0.500000], [0.500000, 0.500000, 0.000000]]) laue_groups = ["-1", "2/m", "mmm", "4/m", "4/mmm", "-3", "-3m", "6/m", "6/mmm", "m-3", "m-3m"] self.ti = Ti self.agfcc = Ag_fcc self.zno1 = zno1 self.zno55 = zno55 self.h = Structure(Lattice.cubic(3), ["H"], [[0, 0, 0]]) self.libcc = Structure(Lattice.cubic(3.51004), ["Li", "Li"], [[0, 0, 0], [0.5, 0.5, 0.5]]) self.laue_groups = laue_groups
def test_is_compatible(self): cubic = Lattice.cubic(1) hexagonal = Lattice.hexagonal(1, 2) rhom = Lattice.rhombohedral(3, 80) tet = Lattice.tetragonal(1, 2) ortho = Lattice.orthorhombic(1, 2, 3) sg = SpaceGroup("Fm-3m") self.assertTrue(sg.is_compatible(cubic)) self.assertFalse(sg.is_compatible(hexagonal)) sg = SpaceGroup("R-3mH") self.assertFalse(sg.is_compatible(cubic)) self.assertTrue(sg.is_compatible(hexagonal)) sg = SpaceGroup("R-3m") self.assertTrue(sg.is_compatible(cubic)) self.assertTrue(sg.is_compatible(rhom)) self.assertFalse(sg.is_compatible(hexagonal)) sg = SpaceGroup("Pnma") self.assertTrue(sg.is_compatible(cubic)) self.assertTrue(sg.is_compatible(tet)) self.assertTrue(sg.is_compatible(ortho)) self.assertFalse(sg.is_compatible(rhom)) self.assertFalse(sg.is_compatible(hexagonal)) sg = SpaceGroup("P12/c1") self.assertTrue(sg.is_compatible(cubic)) self.assertTrue(sg.is_compatible(tet)) self.assertTrue(sg.is_compatible(ortho)) self.assertFalse(sg.is_compatible(rhom)) self.assertFalse(sg.is_compatible(hexagonal)) sg = SpaceGroup("P-1") self.assertTrue(sg.is_compatible(cubic)) self.assertTrue(sg.is_compatible(tet)) self.assertTrue(sg.is_compatible(ortho)) self.assertTrue(sg.is_compatible(rhom)) self.assertTrue(sg.is_compatible(hexagonal))
def get_lattice_quanta(self, convert_to_muC_per_cm2=True, all_in_polar=True): """ Returns the dipole / polarization quanta along a, b, and c for all structures. """ lattices = [s.lattice for s in self.structures] volumes = np.array([s.lattice.volume for s in self.structures]) L = len(self.structures) e_to_muC = -1.6021766e-13 cm2_to_A2 = 1e16 units = 1.0 / np.array(volumes) units *= e_to_muC * cm2_to_A2 # convert polarizations and lattice lengths prior to adjustment if convert_to_muC_per_cm2 and not all_in_polar: # adjust lattices for i in range(L): lattice = lattices[i] l, a = lattice.lengths_and_angles lattices[i] = Lattice.from_lengths_and_angles( np.array(l) * units.ravel()[i], a) elif convert_to_muC_per_cm2 and all_in_polar: for i in range(L): lattice = lattices[-1] l, a = lattice.lengths_and_angles lattices[i] = Lattice.from_lengths_and_angles( np.array(l) * units.ravel()[-1], a) quanta = np.array( [np.array(l.lengths_and_angles[0]) for l in lattices]) return quanta
class TestLattice(unittest.TestCase): def setUp(self): # fixture matrix = [ 100,0,0,0,100,0,0,0,100 ] self.lat = Lattice(matrix) self.assertEqual(self.lat._lengths[0],100.0) self.assertEqual(self.lat._lengths[1],100.0) self.assertEqual(self.lat._lengths[2],100.0) self.assertEqual(self.lat._angles[0],90.0) self.assertEqual(self.lat._angles[1],90.0) self.assertEqual(self.lat._angles[2],90.0) def test_changematrix(self): matrix = [ 132,0,0,0,127,0,0,0,150 ] self.lat.set_matrix(matrix) self.assertEqual(self.lat._lengths[0],132.0) self.assertEqual(self.lat._lengths[1],127.0) self.assertEqual(self.lat._lengths[2],150.0) self.assertEqual(self.lat._angles[0],90.0) self.assertEqual(self.lat._angles[1],90.0) self.assertEqual(self.lat._angles[2],90.0) def tearDown(self): del self.lat self.lat = None
def test_merge_sites(self): species = [{'Ag': 0.5}, {'Cl': 0.25}, {'Cl': 0.1}, {'Ag': 0.5}, {'F': 0.15}, {'F': 0.1}] coords = [[0, 0, 0], [0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [0, 0, 0], [0.5, 0.5, 1.501], [0.5, 0.5, 1.501]] s = Structure(Lattice.cubic(1), species, coords) s.merge_sites(mode="s") self.assertEqual(s[0].specie.symbol, 'Ag') self.assertEqual(s[1].species_and_occu, Composition({'Cl': 0.35, 'F': 0.25})) self.assertArrayAlmostEqual(s[1].frac_coords, [.5, .5, .5005]) # Test for TaS2 with spacegroup 166 in 160 setting. l = Lattice.from_lengths_and_angles([3.374351, 3.374351, 20.308941], [90.000000, 90.000000, 120.000000]) species = ["Ta", "S", "S"] coords = [[0.000000, 0.000000, 0.944333], [0.333333, 0.666667, 0.353424], [0.666667, 0.333333, 0.535243]] tas2 = Structure.from_spacegroup(160, l, species, coords) assert len(tas2) == 13 tas2.merge_sites(mode="d") assert len(tas2) == 9 l = Lattice.from_lengths_and_angles([3.587776, 3.587776, 19.622793], [90.000000, 90.000000, 120.000000]) species = ["Na", "V", "S", "S"] coords = [[0.333333, 0.666667, 0.165000], [0.000000, 0.000000, 0.998333], [0.333333, 0.666667, 0.399394], [0.666667, 0.333333, 0.597273]] navs2 = Structure.from_spacegroup(160, l, species, coords) assert len(navs2) == 18 navs2.merge_sites(mode="d") assert len(navs2) == 12
def setUp(self): lattice = Lattice.cubic(3.010) frac_coords = [[0.00000, 0.00000, 0.00000], [0.00000, 0.50000, 0.50000], [0.50000, 0.00000, 0.50000], [0.50000, 0.50000, 0.00000], [0.50000, 0.00000, 0.00000], [0.50000, 0.50000, 0.50000], [0.00000, 0.00000, 0.50000], [0.00000, 0.50000, 0.00000]] species = ['Mg', 'Mg', 'Mg', 'Mg', 'O', 'O', 'O', 'O'] self.MgO = Structure(lattice, species, frac_coords) self.MgO.add_oxidation_state_by_element({"Mg": 2, "O": -6}) lattice_Dy = Lattice.hexagonal(3.58, 25.61) frac_coords_Dy = [[0.00000, 0.00000, 0.00000], [0.66667, 0.33333, 0.11133], [0.00000, 0.00000, 0.222], [0.66667, 0.33333, 0.33333], [0.33333, 0.66666, 0.44467], [0.66667, 0.33333, 0.55533], [0.33333, 0.66667, 0.66667], [0.00000, 0.00000, 0.778], [0.33333, 0.66667, 0.88867]] species_Dy = ['Dy', 'Dy', 'Dy', 'Dy', 'Dy', 'Dy', 'Dy', 'Dy', 'Dy'] self.Dy = Structure(lattice_Dy, species_Dy, frac_coords_Dy)
def test_get_primitive_structure(self): coords = [[0, 0, 0], [0.5, 0.5, 0], [0, 0.5, 0.5], [0.5, 0, 0.5]] fcc_ag = Structure(Lattice.cubic(4.09), ["Ag"] * 4, coords) self.assertEqual(len(fcc_ag.get_primitive_structure()), 1) coords = [[0, 0, 0], [0.5, 0.5, 0.5]] bcc_li = Structure(Lattice.cubic(4.09), ["Li"] * 2, coords) self.assertEqual(len(bcc_li.get_primitive_structure()), 1)
def test_get_xrd_data(self): a = 4.209 latt = Lattice.cubic(a) structure = Structure(latt, ["Cs", "Cl"], [[0, 0, 0], [0.5, 0.5, 0.5]]) c = XRDCalculator() data = c.get_xrd_data(structure, two_theta_range=(0, 90)) #Check the first two peaks self.assertAlmostEqual(data[0][0], 21.107738329639844) self.assertAlmostEqual(data[0][1], 36.483184003748946) self.assertEqual(data[0][2], {(1, 0, 0): 6}) self.assertAlmostEqual(data[0][3], 4.2089999999999996) self.assertAlmostEqual(data[1][0], 30.024695921112777) self.assertAlmostEqual(data[1][1], 100) self.assertEqual(data[1][2], {(1, 1, 0): 12}) self.assertAlmostEqual(data[1][3], 2.976212442014178) s = read_structure(os.path.join(test_dir, "LiFePO4.cif")) data = c.get_xrd_data(s, two_theta_range=(0, 90)) self.assertAlmostEqual(data[1][0], 17.03504233621785) self.assertAlmostEqual(data[1][1], 50.400928948337075) s = read_structure(os.path.join(test_dir, "Li10GeP2S12.cif")) data = c.get_xrd_data(s, two_theta_range=(0, 90)) self.assertAlmostEqual(data[1][0], 14.058274883353876) self.assertAlmostEqual(data[1][1], 4.4111123641667671) # Test a hexagonal structure. s = read_structure(os.path.join(test_dir, "Graphite.cif"), primitive=False) data = c.get_xrd_data(s, two_theta_range=(0, 90)) self.assertAlmostEqual(data[0][0], 7.929279053132362) self.assertAlmostEqual(data[0][1], 100) self.assertAlmostEqual(len(list(data[0][2].keys())[0]), 4) #Add test case with different lengths of coefficients. #Also test d_hkl. coords = [[0.25, 0.25, 0.173], [0.75, 0.75, 0.827], [0.75, 0.25, 0], [0.25, 0.75, 0], [0.25, 0.25, 0.676], [0.75, 0.75, 0.324]] sp = ["Si", "Si", "Ru", "Ru", "Pr", "Pr"] s = Structure(Lattice.tetragonal(4.192, 6.88), sp, coords) data = c.get_xrd_data(s) self.assertAlmostEqual(data[0][0], 12.86727341476735) self.assertAlmostEqual(data[0][1], 31.448239816769796) self.assertAlmostEqual(data[0][3], 6.88) self.assertEqual(len(data), 42) data = c.get_xrd_data(s, two_theta_range=[0, 60]) self.assertEqual(len(data), 18) #Test with and without Debye-Waller factor tungsten = Structure(Lattice.cubic(3.1653), ["W"] * 2, [[0, 0, 0], [0.5, 0.5, 0.5]]) data = c.get_xrd_data(tungsten, scaled=False) self.assertAlmostEqual(data[0][0], 40.294828554672264) self.assertAlmostEqual(data[0][1], 2414237.5633093244) self.assertAlmostEqual(data[0][3], 2.2382050944897789) c = XRDCalculator(debye_waller_factors={"W": 0.1526}) data = c.get_xrd_data(tungsten, scaled=False) self.assertAlmostEqual(data[0][0], 40.294828554672264) self.assertAlmostEqual(data[0][1], 2377745.2296686019) self.assertAlmostEqual(data[0][3], 2.2382050944897789)
def test_distance_and_image(self): other_site = PeriodicSite("Fe", np.array([1, 1, 1]), self.lattice) (distance, image) = self.site.distance_and_image(other_site) self.assertAlmostEqual(distance, 6.22494979899, 5) self.assertTrue(([-1, -1, -1] == image).all()) (distance, image) = self.site.distance_and_image(other_site, [1, 0, 0]) self.assertAlmostEqual(distance, 19.461500456028563, 5) # Test that old and new distance algo give the same ans for # "standard lattices" lattice = Lattice(np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])) site1 = PeriodicSite("Fe", np.array([0.01, 0.02, 0.03]), lattice) site2 = PeriodicSite("Fe", np.array([0.99, 0.98, 0.97]), lattice) self.assertAlmostEqual(get_distance_and_image_old(site1, site2)[0], site1.distance_and_image(site2)[0]) lattice = Lattice.from_parameters(1, 0.01, 1, 10, 10, 10) site1 = PeriodicSite("Fe", np.array([0.01, 0.02, 0.03]), lattice) site2 = PeriodicSite("Fe", np.array([0.99, 0.98, 0.97]), lattice) self.assertTrue(get_distance_and_image_old(site1, site2)[0] > site1.distance_and_image(site2)[0]) site2 = PeriodicSite("Fe", np.random.rand(3), lattice) (dist_old, jimage_old) = get_distance_and_image_old(site1, site2) (dist_new, jimage_new) = site1.distance_and_image(site2) self.assertTrue(dist_old - dist_new > -1e-8, "New distance algo should give smaller answers!") self.assertFalse((abs(dist_old - dist_new) < 1e-8) ^ (jimage_old == jimage_new).all(), "If old dist == new dist, images must be the same!") latt = Lattice.from_parameters(3.0, 3.1, 10.0, 2.96, 2.0, 1.0) site = PeriodicSite("Fe", [0.1, 0.1, 0.1], latt) site2 = PeriodicSite("Fe", [0.99, 0.99, 0.99], latt) (dist, img) = site.distance_and_image(site2) self.assertAlmostEqual(dist, 0.15495358379511573) self.assertEqual(list(img), [-11, 6, 0])
def __init__(self, structure, scaling_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1))): """ Create a supercell. Arguments: structure: pymatgen.core.structure Structure object. scaling_matrix: a matrix of transforming the lattice vectors. Defaults to the identity matrix. Has to be all integers. e.g., [[2,1,0],[0,3,0],[0,0,1]] generates a new structure with lattice vectors a' = 2a + b, b' = 3b, c' = c where a, b, and c are the lattice vectors of the original structure. """ self._original_structure = structure old_lattice = structure.lattice scale_matrix = np.array(scaling_matrix) new_lattice = Lattice(np.dot(scale_matrix, old_lattice.matrix)) new_species = [] new_fcoords = [] def range_vec(i): return range(max(scale_matrix[:][:, i]) - min(scale_matrix[:][:, i])) for site in structure.sites: for (i, j, k) in itertools.product(range_vec(0), range_vec(1), range_vec(2)): new_species.append(site.species_and_occu) fcoords = site.frac_coords coords = old_lattice.get_cartesian_coords(fcoords + np.array([i, j, k])) new_fcoords.append(new_lattice.get_fractional_coords(coords)) self._modified_structure = Structure(new_lattice, new_species, new_fcoords, False)
def get_aligned_lattices(slab_sub, slab_2d, max_area=200, max_mismatch=0.05, max_angle_diff=1, r1r2_tol=0.2): """ given the 2 slab structures and the alignment paramters, return slab structures with lattices that are aligned with respect to each other """ # get the matching substrate and 2D material lattices uv_substrate, uv_mat2d = get_matching_lattices(slab_sub, slab_2d, max_area=max_area, max_mismatch=max_mismatch, max_angle_diff=max_angle_diff, r1r2_tol=r1r2_tol) if not uv_substrate and not uv_mat2d: print("no matching u and v, trying adjusting the parameters") sys.exit() substrate = Structure.from_sites(slab_sub) mat2d = Structure.from_sites(slab_2d) # map the intial slabs to the newly found matching lattices substrate_latt = Lattice(np.array( [ uv_substrate[0][:], uv_substrate[1][:], substrate.lattice.matrix[2, :] ])) # to avoid numerical issues with find_mapping mat2d_fake_c = mat2d.lattice.matrix[2, :] / np.linalg.norm(mat2d.lattice.matrix[2, :]) * 5.0 mat2d_latt = Lattice(np.array( [ uv_mat2d[0][:], uv_mat2d[1][:], mat2d_fake_c ])) mat2d_latt_fake = Lattice(np.array( [ mat2d.lattice.matrix[0, :], mat2d.lattice.matrix[1, :], mat2d_fake_c ])) _, __, scell = substrate.lattice.find_mapping(substrate_latt, ltol=0.05, atol=1) scell[2] = np.array([0, 0, 1]) substrate.make_supercell(scell) _, __, scell = mat2d_latt_fake.find_mapping(mat2d_latt, ltol=0.05, atol=1) scell[2] = np.array([0, 0, 1]) mat2d.make_supercell(scell) # modify the substrate lattice so that the 2d material can be # grafted on top of it lmap = Lattice(np.array( [ substrate.lattice.matrix[0, :], substrate.lattice.matrix[1, :], mat2d.lattice.matrix[2, :] ])) mat2d.modify_lattice(lmap) return substrate, mat2d
def setUp(self): l = Lattice.cubic(3.51) species = ["Ni"] coords = [[0,0,0]] self.Ni = Structure.from_spacegroup("Fm-3m", l, species, coords) self.Si = Structure.from_spacegroup("Fd-3m", Lattice.cubic(5.430500), ["Si"], [(0, 0, 0.5)])
def test_mapping_symmetry(self): l = Lattice.cubic(1) l2 = Lattice.orthorhombic(1.1001, 1, 1) self.assertEqual(l.find_mapping(l2, ltol=0.1), None) self.assertEqual(l2.find_mapping(l, ltol=0.1), None) l2 = Lattice.orthorhombic(1.0999, 1, 1) self.assertNotEqual(l2.find_mapping(l, ltol=0.1), None) self.assertNotEqual(l.find_mapping(l2, ltol=0.1), None)
def setUp(self): zno1 = Structure.from_file(get_path("ZnO-wz.cif"), primitive=False) zno55 = SlabGenerator(zno1, [1, 0, 0], 5, 5, lll_reduce=False, center_slab=False).get_slab() self.zno1 = zno1 self.zno55 = zno55 self.h = Structure(Lattice.cubic(3), ["H"], [[0, 0, 0]]) self.libcc = Structure(Lattice.cubic(3.51004), ["Li", "Li"], [[0, 0, 0], [0.5, 0.5, 0.5]])
def test_get_points_in_sphere(self): # This is a non-niggli representation of a cubic lattice latt = Lattice([[1,5,0],[0,1,0],[5,0,1]]) # evenly spaced points array between 0 and 1 pts = np.array(list(itertools.product(range(5), repeat=3))) / 5 pts = latt.get_fractional_coords(pts) self.assertEqual(len(latt.get_points_in_sphere( pts, [0, 0, 0], 0.20001)), 7) self.assertEqual(len(latt.get_points_in_sphere( pts, [0.5, 0.5, 0.5], 1.0001)), 552)
def test_to_from_dict(self): d = self.tetragonal.to_dict t = Lattice.from_dict(d) for i in range(3): self.assertEqual(t.abc[i], self.tetragonal.abc[i]) self.assertEqual(t.angles[i], self.tetragonal.angles[i]) #Make sure old style dicts work. del d["matrix"] t = Lattice.from_dict(d) for i in range(3): self.assertEqual(t.abc[i], self.tetragonal.abc[i]) self.assertEqual(t.angles[i], self.tetragonal.angles[i])
def test_get_vector_along_lattice_directions(self): lattice_mat = np.array([[0.5, 0., 0.], [0.5, np.sqrt(3) / 2., 0.], [0., 0., 1.0]]) lattice = Lattice(lattice_mat) cart_coord = np.array([0.5, np.sqrt(3)/4., 0.5]) latt_coord = np.array([0.25, 0.5, 0.5]) from_direct = lattice.get_fractional_coords(cart_coord) * lattice.lengths_and_angles[0] self.assertArrayAlmostEqual(lattice.get_vector_along_lattice_directions(cart_coord), from_direct) self.assertArrayAlmostEqual(lattice.get_vector_along_lattice_directions(cart_coord), latt_coord) self.assertArrayEqual(lattice.get_vector_along_lattice_directions(cart_coord).shape, [3,]) self.assertArrayEqual(lattice.get_vector_along_lattice_directions(cart_coord.reshape([1,3])).shape, [1,3])
def test_find_mapping(self): m = np.array([[0.1, 0.2, 0.3], [-0.1, 0.2, 0.7], [0.6, 0.9, 0.2]]) latt = Lattice(m) op = SymmOp.from_origin_axis_angle([0, 0, 0], [2, 3, 3], 35) rot = op.rotation_matrix scale = np.array([[1, 1, 0], [0, 1, 0], [0, 0, 1]]) latt2 = Lattice(np.dot(rot, np.dot(scale, m).T).T) (latt, rot, scale2) = latt2.find_mapping(latt) self.assertAlmostEqual(abs(np.linalg.det(rot)), 1) self.assertTrue(np.allclose(scale2, scale) or np.allclose(scale2, -scale))
def setUp(self): self.cscl = Structure.from_spacegroup( "Pm-3m", Lattice.cubic(4.2), ["Cs", "Cl"], [[0, 0, 0], [0.5, 0.5, 0.5]]) self.lifepo4 = self.get_structure("LiFePO4") self.tei = Structure.from_file(get_path("icsd_TeI.cif"), primitive=False) self.LiCoO2 = Structure.from_file(get_path("icsd_LiCoO2.cif"), primitive=False) self.p1 = Structure(Lattice.from_parameters(3, 4, 5, 31, 43, 50), ["H", "He"], [[0, 0, 0], [0.1, 0.2, 0.3]]) self.graphite = self.get_structure("Graphite")
def test_get_primitive_structure(self): coords = [[0, 0, 0], [0.5, 0.5, 0], [0, 0.5, 0.5], [0.5, 0, 0.5]] fcc_ag = IStructure(Lattice.cubic(4.09), ["Ag"] * 4, coords) self.assertEqual(len(fcc_ag.get_primitive_structure()), 1) coords = [[0, 0, 0], [0.5, 0.5, 0.5]] bcc_li = IStructure(Lattice.cubic(4.09), ["Li"] * 2, coords) bcc_prim = bcc_li.get_primitive_structure() self.assertEqual(len(bcc_prim), 1) self.assertAlmostEqual(bcc_prim.lattice.alpha, 109.47122, 3) coords = [[0] * 3, [0.5] * 3, [0.25] * 3, [0.26] * 3] s = IStructure(Lattice.cubic(4.09), ["Ag"] * 4, coords) self.assertEqual(len(s.get_primitive_structure()), 4)
def test_interpolate(self): coords = list() coords.append([0, 0, 0]) coords.append([0.75, 0.5, 0.75]) struct = IStructure(self.lattice, ["Si"] * 2, coords) coords2 = list() coords2.append([0, 0, 0]) coords2.append([0.5, 0.5, 0.5]) struct2 = IStructure(self.struct.lattice, ["Si"] * 2, coords2) int_s = struct.interpolate(struct2, 10) for s in int_s: self.assertIsNotNone(s, "Interpolation Failed!") self.assertEqual(int_s[0].lattice, s.lattice) self.assertArrayEqual(int_s[1][1].frac_coords, [0.725, 0.5, 0.725]) badlattice = [[1, 0.00, 0.00], [0, 1, 0.00], [0.00, 0, 1]] struct2 = IStructure(badlattice, ["Si"] * 2, coords2) self.assertRaises(ValueError, struct.interpolate, struct2) coords2 = list() coords2.append([0, 0, 0]) coords2.append([0.5, 0.5, 0.5]) struct2 = IStructure(self.struct.lattice, ["Si", "Fe"], coords2) self.assertRaises(ValueError, struct.interpolate, struct2) # Test autosort feature. s1 = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3), ["Fe"], [[0, 0, 0]]) s1.pop(0) s2 = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3), ["Fe"], [[0, 0, 0]]) s2.pop(2) random.shuffle(s2) for s in s1.interpolate(s2, autosort_tol=0.5): self.assertArrayAlmostEqual(s1[0].frac_coords, s[0].frac_coords) self.assertArrayAlmostEqual(s1[2].frac_coords, s[2].frac_coords) # Make sure autosort has no effect on simpler interpolations, # and with shuffled sites. s1 = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3), ["Fe"], [[0, 0, 0]]) s2 = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3), ["Fe"], [[0, 0, 0]]) s2[0] = "Fe", [0.01, 0.01, 0.01] random.shuffle(s2) for s in s1.interpolate(s2, autosort_tol=0.5): self.assertArrayAlmostEqual(s1[1].frac_coords, s[1].frac_coords) self.assertArrayAlmostEqual(s1[2].frac_coords, s[2].frac_coords) self.assertArrayAlmostEqual(s1[3].frac_coords, s[3].frac_coords)
def write_vib_file(self, xyz_file, qpoint, displ, do_real=True, frac_coords=True, scale_matrix=None, max_supercell=None): """ write into the file descriptor xyz_file the positions and displacements of the atoms Args: xyz_file: file_descriptor qpoint: qpoint to be analyzed displ: eigendisplacements to be analyzed do_real: True if you want to get only real part, False means imaginary part frac_coords: True if the eigendisplacements are given in fractional coordinates scale_matrix: Scale matrix for supercell max_supercell: Maximum size of supercell vectors with respect to primitive cell """ if scale_matrix is None: if max_supercell is None: raise ValueError("If scale_matrix is not provided, please provide max_supercell !") scale_matrix = self.get_smallest_supercell(qpoint, max_supercell=max_supercell) old_lattice = self._lattice new_lattice = Lattice(np.dot(scale_matrix, old_lattice.matrix)) tvects = self.get_trans_vect(scale_matrix) new_displ = np.zeros(3, dtype=np.float) fmtstr = "{{}} {{:.{0}f}} {{:.{0}f}} {{:.{0}f}} {{:.{0}f}} {{:.{0}f}} {{:.{0}f}}\n".format(6) for at, site in enumerate(self): for t in tvects: if do_real: new_displ[:] = np.real(np.exp(2*1j*np.pi*(np.dot(qpoint,t)))*displ[at,:]) else: new_displ[:] = np.imag(np.exp(2*1j*np.pi*(np.dot(qpoint,t)))*displ[at,:]) if frac_coords: # Convert to fractional coordinates. new_displ = self.lattice.get_cartesian_coords(new_displ) # We don't normalize here !!! fcoords = site.frac_coords + t coords = old_lattice.get_cartesian_coords(fcoords) new_fcoords = new_lattice.get_fractional_coords(coords) # New_fcoords -> map into 0 - 1 new_fcoords = np.mod(new_fcoords, 1) coords = new_lattice.get_cartesian_coords(new_fcoords) xyz_file.write(fmtstr.format(site.specie, coords[0], coords[1], coords[2], new_displ[0], new_displ[1], new_displ[2]))
def compile_crystal(datarow, flavor='pmg'): """ Helper method for representing the MPDS crystal structures in two flavors: either as a Pymatgen Structure object, or as an ASE Atoms object. Attention! These two flavors are not compatible, e.g. primitive vs. crystallographic cell is defaulted, atoms wrapped or non-wrapped into the unit cell etc. Note, that the crystal structures are not retrieved by default, so one needs to specify the fields while retrieval: - cell_abc - sg_n - setting - basis_noneq - els_noneq e.g. like this: {'S':['cell_abc', 'sg_n', 'setting', 'basis_noneq', 'els_noneq']} NB. occupancies are not considered. Args: datarow: (list) Required data to construct crystal structure: [cell_abc, sg_n, setting, basis_noneq, els_noneq] flavor: (str) Either "pmg", or "ase" Returns: - if flavor is pmg, returns Pymatgen Structure object - if flavor is ase, returns ASE Atoms object """ if not datarow or not datarow[-1]: return None cell_abc, sg_n, setting, basis_noneq, els_noneq = \ datarow[-5], int(datarow[-4]), datarow[-3], datarow[-2], datarow[-1] if flavor == 'pmg': return Structure.from_spacegroup( sg_n, Lattice.from_parameters(*cell_abc), els_noneq, basis_noneq ) elif flavor == 'ase' and use_ase: atom_data = [] setting = 2 if setting == '2' else 1 for num, i in enumerate(basis_noneq): atom_data.append(Atom(els_noneq[num], tuple(i))) return crystal( atom_data, spacegroup=sg_n, cellpar=cell_abc, primitive_cell=True, setting=setting, onduplicates='replace' ) else: raise APIError("Crystal structure treatment unavailable")
def test_consistency(self): """ when only lengths and angles are given for constructors, the internal matrix representation is ambiguous since the lattice rotation is not specified. This test makes sure that a consistent definition is specified for the lattice rotation when using different constructors from lengths angles """ l = [3.840198, 3.84019885, 3.8401976] a = [119.99998575, 90, 60.00000728] mat1 = Lattice.from_lengths_and_angles(l, a).matrix mat2 = Lattice.from_parameters(l[0], l[1], l[2], a[0], a[1], a[2]).matrix for i in range(0, 3): for j in range(0, 3): self.assertAlmostEqual(mat1[i][j], mat2[i][j], 5)
def test_pbc_shortest_vectors(self): fcoords = np.array([[0.3, 0.3, 0.5], [0.1, 0.1, 0.3], [0.9, 0.9, 0.8], [0.1, 0.0, 0.5], [0.9, 0.7, 0.0]]) lattice = Lattice.from_lengths_and_angles([8, 8, 4], [90, 76, 58]) expected = np.array([[0.000, 3.015, 4.072, 3.519, 3.245], [3.015, 0.000, 3.207, 1.131, 4.453], [4.072, 3.207, 0.000, 2.251, 1.788], [3.519, 1.131, 2.251, 0.000, 3.852]]) vectors = pbc_shortest_vectors(lattice, fcoords[:-1], fcoords) dists = np.sum(vectors**2, axis = -1)**0.5 self.assertArrayAlmostEqual(dists, expected, 3) #now try with small loop threshold from pymatgen.util import coord_utils prev_threshold = coord_utils.LOOP_THRESHOLD coord_utils.LOOP_THRESHOLD = 0 vectors = pbc_shortest_vectors(lattice, fcoords[:-1], fcoords) dists = np.sum(vectors**2, axis = -1)**0.5 self.assertArrayAlmostEqual(dists, expected, 3) coord_utils.LOOP_THRESHOLD = prev_threshold
def get_primitive_standard_structure(self, international_monoclinic=True): """ Gives a structure with a primitive cell according to certain standards the standards are defined in Setyawan, W., & Curtarolo, S. (2010). High-throughput electronic band structure calculations: Challenges and tools. Computational Materials Science, 49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010 Returns: The structure in a primitive standardized cell """ conv = self.get_conventional_standard_structure( international_monoclinic=international_monoclinic) lattice = self.get_lattice_type() if "P" in self.get_spacegroup_symbol() or lattice == "hexagonal": return conv if lattice == "rhombohedral": # check if the conventional representation is hexagonal or # rhombohedral lengths, angles = conv.lattice.lengths_and_angles if abs(lengths[0] - lengths[2]) < 0.0001: transf = np.eye else: transf = np.array([[-1, 1, 1], [2, 1, 1], [-1, -2, 1]], dtype=np.float) / 3 elif "I" in self.get_spacegroup_symbol(): transf = np.array([[-1, 1, 1], [1, -1, 1], [1, 1, -1]], dtype=np.float) / 2 elif "F" in self.get_spacegroup_symbol(): transf = np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]], dtype=np.float) / 2 elif "C" in self.get_spacegroup_symbol(): if self.get_crystal_system() == "monoclinic": transf = np.array([[1, 1, 0], [-1, 1, 0], [0, 0, 2]], dtype=np.float) / 2 else: transf = np.array([[1, -1, 0], [1, 1, 0], [0, 0, 2]], dtype=np.float) / 2 else: transf = np.eye(3) new_sites = [] latt = Lattice(np.dot(transf, conv.lattice.matrix)) for s in conv: new_s = PeriodicSite(s.specie, s.coords, latt, to_unit_cell=True, coords_are_cartesian=True, properties=s.properties) if not any(map(new_s.is_periodic_image, new_sites)): new_sites.append(new_s) if lattice == "rhombohedral": prim = Structure.from_sites(new_sites) lengths, angles = prim.lattice.lengths_and_angles a = lengths[0] alpha = math.pi * angles[0] / 180 new_matrix = [[a * cos(alpha / 2), -a * sin(alpha / 2), 0], [a * cos(alpha / 2), a * sin(alpha / 2), 0], [ a * cos(alpha) / cos(alpha / 2), 0, a * math.sqrt(1 - (cos(alpha)**2 / (cos(alpha / 2)**2))) ]] new_sites = [] latt = Lattice(new_matrix) for s in prim: new_s = PeriodicSite(s.specie, s.frac_coords, latt, to_unit_cell=True, properties=s.properties) if not any(map(new_s.is_periodic_image, new_sites)): new_sites.append(new_s) return Structure.from_sites(new_sites) return Structure.from_sites(new_sites)
def is_all_acute_or_obtuse(m): recp_angles = np.array(Lattice(m).reciprocal_lattice.angles) return np.all(recp_angles <= 90) or np.all(recp_angles > 90)
parser = argparse.ArgumentParser(description='Generate INCAR') parser.add_argument('eps', type=float, help='averaged dielectric constant') parser.add_argument('slab_d', type=float, help='corresponding slab thickness (Angstroms)') ## read in the above arguments from command line args = parser.parse_args() ## the bash script already put us in the appropriate subdirectory dir_sub = os.getcwd() # lattice = Poscar.from_file(folder1+"POSCAR").structure.lattice with open(os.path.join(dir_sub, "defectproperty.json"), 'r') as file: defprop = json.loads(file.read()) lattice = Lattice.from_dict(defprop["lattice"]) ## STRUCTURE GROUP s = structure_grp(lattice) ## SLAB GROUP ## assume slab is centered in the cell vertically slabmin = (lattice.c - args.slab_d) / 2 slabmax = (lattice.c + args.slab_d) / 2 s += slab_grp(slabmin, slabmax, eps=args.eps * np.eye(3)) ## CHARGE GROUP posZ = np.mean([def_site[2] for def_site in defprop["defect_site"]]) s += charge_grp(posZ * lattice.c, defprop["charge"]) ## ISOLATED GROUP
def from_string(header_str): """ Reads Header string and returns Header object if header was generated by pymatgen. Args: header_str: pymatgen generated feff.inp header Returns: structure object. """ # Checks to see if generated by pymatgen, if not it is impossible to # generate structure object so it is not possible to generate header # object and routine ends lines = tuple(clean_lines(header_str.split("\n"), False)) comment1 = lines[0] feffpmg = comment1.find("pymatgen") if feffpmg > 0: comment2 = ' '.join(lines[1].split()[2:]) #This sec section gets information to create structure object source = ' '.join(lines[2].split()[2:]) natoms = int(lines[8].split()[2]) basis_vec = lines[6].split() a = float(basis_vec[2]) b = float(basis_vec[3]) c = float(basis_vec[4]) lengths = [a, b, c] basis_ang = lines[7].split() alpha = float(basis_ang[2]) beta = float(basis_ang[3]) gamma = float(basis_ang[4]) angles = [alpha, beta, gamma] lattice = Lattice.from_lengths_and_angles(lengths, angles) atomic_symbols = [] for i in xrange(9, 9 + natoms): atomic_symbols.append(lines[i].split()[2]) # read the atomic coordinates coords = [] for i in xrange(natoms): toks = lines[i + 9].split() coords.append([float(s) for s in toks[3:]]) #Structure object is now generated and Header object returned struct_fromfile = Structure(lattice, atomic_symbols, coords, False, False, False) h = Header(struct_fromfile, source, comment2) return h else: return "Header not generated by pymatgen, " \ "cannot return header object"
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)
class StructureEditor(StructureModifier): """ Editor for adding, removing and changing sites from a structure """ DISTANCE_TOLERANCE = 0.01 def __init__(self, structure): """ Args: structure: pymatgen.core.structure Structure object. """ self._original_structure = structure self._lattice = structure.lattice self._sites = list(structure.sites) def add_site_property(self, property_name, values): """ Adds a property to a site. Args: property_name: The name of the property to add. values: A sequence of values. Must be same length as number of sites. """ if len(values) != len(self._sites): raise ValueError("Values must be same length as sites.") for i in xrange(len(self._sites)): site = self._sites[i] props = site.properties if not props: props = {} props[property_name] = values[i] self._sites[i] = PeriodicSite(site.species_and_occu, site.frac_coords, self._lattice, properties=props) def replace_species(self, species_mapping): """ Swap species in a structure. Args: species_mapping: dict of species to swap. Species can be elements too. e.g., {Element("Li"): Element("Na")} performs a Li for Na substitution. The second species can be a sp_and_occu dict. For example, a site with 0.5 Si that is passed the mapping {Element('Si): {Element('Ge'):0.75, Element('C'):0.25} } will have .375 Ge and .125 C. """ def mod_site(site): new_atom_occu = collections.defaultdict(int) for sp, amt in site.species_and_occu.items(): if sp in species_mapping: if isinstance(species_mapping[sp], (Element, Specie)): new_atom_occu[species_mapping[sp]] += amt elif isinstance(species_mapping[sp], dict): for new_sp, new_amt in species_mapping[sp].items(): new_atom_occu[new_sp] += amt * new_amt else: new_atom_occu[sp] += amt return PeriodicSite(new_atom_occu, site.frac_coords, self._lattice, properties=site.properties) self._sites = map(mod_site, self._sites) def replace_site(self, index, species_n_occu): """ Replace a single site. Takes either a species or a dict of species and occupations. Args: index: The index of the site in the _sites list. species: A species object. """ self._sites[index] = PeriodicSite( species_n_occu, self._sites[index].frac_coords, self._lattice, properties=self._sites[index].properties) def remove_species(self, species): """ Remove all occurrences of a species from a structure. Args: species: species to remove. """ new_sites = [] for site in self._sites: new_sp_occu = { sp: amt for sp, amt in site.species_and_occu.items() if sp not in species } if len(new_sp_occu) > 0: new_sites.append( PeriodicSite(new_sp_occu, site.frac_coords, self._lattice, properties=site.properties)) self._sites = new_sites def append_site(self, species, coords, coords_are_cartesian=False, validate_proximity=True): """ Append a site to the structure at the end. Args: species: species of inserted site coords: coordinates of inserted site fractional_coord: Whether coordinates are cartesian. Defaults to False. validate_proximity: Whether to check if inserted site is too close to an existing site. Defaults to True. """ self.insert_site(len(self._sites), species, coords, coords_are_cartesian, validate_proximity) def insert_site(self, i, species, coords, coords_are_cartesian=False, validate_proximity=True, properties=None): """ Insert a site to the structure. Args: i: index to insert site species: species of inserted site coords: coordinates of inserted site coords_are_cartesian: Whether coordinates are cartesian. Defaults to False. validate_proximity: Whether to check if inserted site is too close to an existing site. Defaults to True. """ if not coords_are_cartesian: new_site = PeriodicSite(species, coords, self._lattice, properties=properties) else: frac_coords = self._lattice.get_fractional_coords(coords) new_site = PeriodicSite(species, frac_coords, self._lattice, properties=properties) if validate_proximity: for site in self._sites: if site.distance(new_site) < self.DISTANCE_TOLERANCE: raise ValueError("New site is too close to an existing " "site!") self._sites.insert(i, new_site) def delete_site(self, i): """ Delete site at index i. Args: i: index of site to delete. """ del (self._sites[i]) def delete_sites(self, indices): """ Delete sites with at indices. Args: indices: sequence of indices of sites to delete. """ self._sites = [ self._sites[i] for i in range(len(self._sites)) if i not in indices ] def apply_operation(self, symmop): """ Apply a symmetry operation to the structure and return the new structure. The lattice is operated by the rotation matrix only. Coords are operated in full and then transformed to the new lattice. Args: symmop: Symmetry operation to apply. """ self._lattice = Lattice( [symmop.apply_rotation_only(row) for row in self._lattice.matrix]) def operate_site(site): new_cart = symmop.operate(site.coords) new_frac = self._lattice.get_fractional_coords(new_cart) return PeriodicSite(site.species_and_occu, new_frac, self._lattice, properties=site.properties) self._sites = map(operate_site, self._sites) def modify_lattice(self, new_lattice): """ Modify the lattice of the structure. Mainly used for changing the basis. Args: new_lattice: New lattice """ self._lattice = new_lattice new_sites = [] for site in self._sites: new_sites.append( PeriodicSite(site.species_and_occu, site.frac_coords, self._lattice, properties=site.properties)) self._sites = new_sites def apply_strain(self, strain): """ Apply an isotropic strain to the lattice. Args: strain: Amount of strain to apply. E.g., 0.01 means all lattice vectors are increased by 1%. This is equivalent to calling modify_lattice with a lattice with lattice parameters that are 1% larger. """ self.modify_lattice(Lattice(self._lattice.matrix * (1 + strain))) def translate_sites(self, indices, vector, frac_coords=True): """ Translate specific sites by some vector, keeping the sites within the unit cell. Args: sites: List of site indices on which to perform the translation. vector: Translation vector for sites. frac_coords: Boolean stating whether the vector corresponds to fractional or cartesian coordinates. """ for i in indices: site = self._sites[i] if frac_coords: fcoords = site.frac_coords + vector else: fcoords = self._lattice.get_fractional_coords(site.coords + vector) new_site = PeriodicSite(site.species_and_occu, fcoords, self._lattice, to_unit_cell=True, coords_are_cartesian=False, properties=site.properties) self._sites[i] = new_site def perturb_structure(self, distance=0.1): """ Performs a random perturbation of the sites in a structure to break symmetries. Args: distance: distance in angstroms by which to perturb each site. """ def get_rand_vec(): #deals with zero vectors. vector = np.random.randn(3) vnorm = np.linalg.norm(vector) return vector / vnorm * distance if vnorm != 0 else get_rand_vec() for i in range(len(self._sites)): self.translate_sites([i], get_rand_vec(), frac_coords=False) def add_oxidation_state_by_element(self, oxidation_states): """ Add oxidation states to a structure. Args: structure: pymatgen.core.structure Structure object. oxidation_states: dict of oxidation states. E.g., {"Li":1, "Fe":2, "P":5, "O":-2} """ try: for i, site in enumerate(self._sites): new_sp = {} for el, occu in site.species_and_occu.items(): sym = el.symbol new_sp[Specie(sym, oxidation_states[sym])] = occu new_site = PeriodicSite(new_sp, site.frac_coords, self._lattice, coords_are_cartesian=False, properties=site.properties) self._sites[i] = new_site except KeyError: raise ValueError("Oxidation state of all elements must be " "specified in the dictionary.") def add_oxidation_state_by_site(self, oxidation_states): """ Add oxidation states to a structure by site. Args: oxidation_states: List of oxidation states. E.g., [1, 1, 1, 1, 2, 2, 2, 2, 5, 5, 5, 5, -2, -2, -2, -2] """ try: for i, site in enumerate(self._sites): new_sp = {} for el, occu in site.species_and_occu.items(): sym = el.symbol new_sp[Specie(sym, oxidation_states[i])] = occu new_site = PeriodicSite(new_sp, site.frac_coords, self._lattice, coords_are_cartesian=False, properties=site.properties) self._sites[i] = new_site except IndexError: raise ValueError("Oxidation state of all sites must be " "specified in the dictionary.") def remove_oxidation_states(self): """ Removes oxidation states from a structure. """ for i, site in enumerate(self._sites): new_sp = collections.defaultdict(float) for el, occu in site.species_and_occu.items(): sym = el.symbol new_sp[Element(sym)] += occu new_site = PeriodicSite(new_sp, site.frac_coords, self._lattice, coords_are_cartesian=False, properties=site.properties) self._sites[i] = new_site def to_unit_cell(self, tolerance=0.1): """ Returns all the sites to their position inside the unit cell. If there is a site within the tolerance already there, the site is deleted instead of moved. """ new_sites = [] for site in self._sites: if not new_sites: new_sites.append(site) frac_coords = np.array([site.frac_coords]) continue if len( get_points_in_sphere_pbc(self._lattice, frac_coords, site.coords, tolerance)): continue frac_coords = np.append(frac_coords, [site.frac_coords % 1], axis=0) new_sites.append(site.to_unit_cell) self._sites = new_sites @property def original_structure(self): """ The original structure. """ return self._original_structure @property def modified_structure(self): coords = [site.frac_coords for site in self._sites] species = [site.species_and_occu for site in self._sites] props = {} if self._sites[0].properties: for k in self._sites[0].properties.keys(): props[k] = [site.properties[k] for site in self._sites] return Structure(self._lattice, species, coords, False, site_properties=props)
def get_high_accuracy_voronoi_nodes(structure, rad_dict, probe_rad=0.1): """ Analyze the void space in the input structure using high accuracy voronoi decomposition. Calls Zeo++ for Voronoi decomposition. Args: structure: pymatgen.core.structure.Structure rad_dict (optional): Dictionary of radii of elements in structure. If not given, Zeo++ default values are used. Note: Zeo++ uses atomic radii of elements. For ionic structures, pass rad_dict with ionic radii probe_rad (optional): Sampling probe radius in Angstroms. Default is 0.1 A Returns: voronoi nodes as pymatgen.core.structure.Strucutre within the unit cell defined by the lattice of input structure voronoi face centers as pymatgen.core.structure.Strucutre within the unit cell defined by the lattice of input structure """ with ScratchDir('.'): name = "temp_zeo1" zeo_inp_filename = name + ".cssr" ZeoCssr(structure).write_file(zeo_inp_filename) rad_flag = True rad_file = name + ".rad" with open(rad_file, 'w+') as fp: for el in rad_dict.keys(): print("{} {}".format(el, rad_dict[el].real), file=fp) atmnet = AtomNetwork.read_from_CSSR(zeo_inp_filename, rad_flag=rad_flag, rad_file=rad_file) # vornet, vor_edge_centers, vor_face_centers = \ # atmnet.perform_voronoi_decomposition() red_ha_vornet = \ prune_voronoi_network_close_node(atmnet) # generate_simplified_highaccuracy_voronoi_network(atmnet) # get_nearest_largest_diameter_highaccuracy_vornode(atmnet) red_ha_vornet.analyze_writeto_XYZ(name, probe_rad, atmnet) voro_out_filename = name + '_voro.xyz' voro_node_mol = ZeoVoronoiXYZ.from_file(voro_out_filename).molecule species = ["X"] * len(voro_node_mol.sites) coords = [] prop = [] for site in voro_node_mol.sites: coords.append(list(site.coords)) prop.append(site.properties['voronoi_radius']) lattice = Lattice.from_lengths_and_angles(structure.lattice.abc, structure.lattice.angles) vor_node_struct = Structure(lattice, species, coords, coords_are_cartesian=True, to_unit_cell=True, site_properties={"voronoi_radius": prop}) return vor_node_struct
def _get_structure(self, data, primitive): """ Generate structure from part of the cif. """ lengths = [ str2float(data["_cell_length_" + i]) for i in ["a", "b", "c"] ] angles = [ str2float(data["_cell_angle_" + i]) for i in ["alpha", "beta", "gamma"] ] lattice = Lattice.from_lengths_and_angles(lengths, angles) try: sympos = data["_symmetry_equiv_pos_as_xyz"] except KeyError: try: sympos = data["_symmetry_equiv_pos_as_xyz_"] except KeyError: warnings.warn("No _symmetry_equiv_pos_as_xyz type key found. " "Defaulting to P1.") sympos = ['x, y, z'] self.symmetry_operations = [SymmOp.from_xyz_string(s) for s in sympos] def parse_symbol(sym): # capitalization conventions are not strictly followed, eg Cu will be CU m = re.search("([A-Za-z]*)", sym) if m: return m.group(1)[:2].capitalize() return "" try: oxi_states = { data["_atom_type_symbol"][i]: str2float(data["_atom_type_oxidation_number"][i]) for i in range(len(data["_atom_type_symbol"])) } except (ValueError, KeyError): oxi_states = None coord_to_species = OrderedDict() for i in range(len(data["_atom_site_label"])): symbol = parse_symbol(data["_atom_site_label"][i]) # make sure symbol was properly parsed from _atom_site_label # otherwise get it from _atom_site_type_symbol try: Element(symbol) except KeyError: symbol = parse_symbol(data["_atom_site_type_symbol"][i]) if oxi_states is not None: # sometimes the site doesn't have the type_symbol. # we then hope the type_symbol can be parsed from the label if "_atom_site_type_symbol" in data.data.keys(): k = data["_atom_site_type_symbol"][i] else: k = symbol el = Specie(symbol, oxi_states[k]) else: el = Element(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]) try: occu = str2float(data["_atom_site_occupancy"][i]) except (KeyError, ValueError): occu = 1 if occu > 0: coord = (x, y, z) if coord not in coord_to_species: coord_to_species[coord] = {el: occu} else: coord_to_species[coord][el] = occu allspecies = [] allcoords = [] for coord, species in coord_to_species.items(): coords = self._unique_coords(coord) allcoords.extend(coords) allspecies.extend(len(coords) * [species]) #rescale occupancies if necessary for species in allspecies: totaloccu = sum(species.values()) if 1 < totaloccu <= self._occupancy_tolerance: for key, value in six.iteritems(species): species[key] = value / totaloccu struct = Structure(lattice, allspecies, allcoords) if primitive: struct = struct.get_primitive_structure().get_reduced_structure() return struct.get_sorted_structure()
def test_get_wigner_seitz_cell(self): ws_cell = Lattice([[10, 0, 0], [0, 5, 0], [0, 0, 1]]) \ .get_wigner_seitz_cell() self.assertEqual(6, len(ws_cell)) for l in ws_cell[3]: self.assertEqual([abs(i) for i in l], [5.0, 2.5, 0.5])
def test_get_lll_reduced_lattice(self): lattice = Lattice([1.0, 1, 1, -1.0, 0, 2, 3.0, 5, 6]) reduced_latt = lattice.get_lll_reduced_lattice() expected_ans = Lattice( np.array([0.0, 1.0, 0.0, 1.0, 0.0, 1.0, -2.0, 0.0, 1.0]).reshape( (3, 3))) self.assertAlmostEqual( np.linalg.det( np.linalg.solve(expected_ans.matrix, reduced_latt.matrix)), 1) self.assertArrayAlmostEqual(sorted(reduced_latt.abc), sorted(expected_ans.abc)) self.assertAlmostEqual(reduced_latt.volume, lattice.volume) latt = [ 7.164750, 2.481942, 0.000000, -4.298850, 2.481942, 0.000000, 0.000000, 0.000000, 14.253000 ] expected_ans = Lattice( np.array([ -4.298850, 2.481942, 0.000000, 2.865900, 4.963884, 0.000000, 0.000000, 0.000000, 14.253000 ])) reduced_latt = Lattice(latt).get_lll_reduced_lattice() self.assertAlmostEqual( np.linalg.det( np.linalg.solve(expected_ans.matrix, reduced_latt.matrix)), 1) self.assertArrayAlmostEqual(sorted(reduced_latt.abc), sorted(expected_ans.abc)) expected_ans = Lattice( [0.0, 10.0, 10.0, 10.0, 10.0, 0.0, 30.0, -30.0, 40.0]) lattice = np.array([100., 0., 10., 10., 10., 20., 10., 10., 10.]) lattice = lattice.reshape(3, 3) lattice = Lattice(lattice.T) reduced_latt = lattice.get_lll_reduced_lattice() self.assertAlmostEqual( np.linalg.det( np.linalg.solve(expected_ans.matrix, reduced_latt.matrix)), 1) self.assertArrayAlmostEqual(sorted(reduced_latt.abc), sorted(expected_ans.abc)) random_latt = Lattice(np.random.random((3, 3))) if np.linalg.det(random_latt.matrix) > 1e-8: reduced_random_latt = random_latt.get_lll_reduced_lattice() self.assertAlmostEqual(reduced_random_latt.volume, random_latt.volume)
def from_files(path_dir): """ get a BoltztrapAnalyzer object from a set of files Args: path_dir: directory where the boltztrap files are Returns: a BoltztrapAnalyzer object """ t_steps = set() m_steps = set() gap = None doping = [] data_doping_full = [] data_doping_hall = [] with open(os.path.join(path_dir, "boltztrap.condtens"), 'r') as f: data_full = [] for line in f: if not line.startswith("#"): t_steps.add(int(float(line.split()[1]))) m_steps.add(float(line.split()[0])) data_full.append([float(c) for c in line.split()]) with open(os.path.join(path_dir, "boltztrap.halltens"), 'r') as f: data_hall = [] for line in f: if not line.startswith("#"): data_hall.append([float(c) for c in line.split()]) data_dos = [] with open(os.path.join(path_dir, "boltztrap.transdos"), 'r') as f: for line in f: if not line.startswith(" #"): data_dos.append([ Energy(line.split()[0], "Ry").to("eV"), 2 * Energy(line.split()[1], "eV").to("Ry") ]) with open(os.path.join(path_dir, "boltztrap.outputtrans"), 'r') as f: warning = False step = 0 for line in f: if "WARNING" in line: warning = True if line.startswith("VBM"): efermi = Energy(line.split()[1], "Ry").to("eV") if step == 2: l_tmp = line.split("-")[1:] doping.extend([-float(d) for d in l_tmp]) step = 0 if step == 1: doping.extend([float(d) for d in line.split()]) step = 2 if line.startswith("Doping levels to be output for") or \ line.startswith(" Doping levels to be output for"): step = 1 if line.startswith("Egap:"): gap = float(line.split()[1]) if len(doping) != 0: with open(os.path.join(path_dir, "fort.26"), 'r') as f: for line in f: if not line.startswith("#") and len(line) > 2: data_doping_full.append( [float(c) for c in line.split()]) with open(os.path.join(path_dir, "fort.27"), 'r') as f: for line in f: if not line.startswith("#") and len(line) > 2: data_doping_hall.append( [float(c) for c in line.split()]) with open(os.path.join(path_dir, "boltztrap.struct"), 'r') as f: tokens = f.readlines() vol = Lattice([[ Length(float(tokens[i].split()[j]), "bohr").to("ang") for j in range(3) ] for i in range(1, 4)]).volume return BoltztrapAnalyzer._make_boltztrap_analyzer_from_data( data_full, data_hall, data_dos, sorted([t for t in t_steps]), sorted([Energy(m, "Ry").to("eV") for m in m_steps]), efermi, Energy(gap, "Ry").to("eV"), doping, data_doping_full, data_doping_hall, vol, warning)
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 in ("True", "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, p in enumerate(positions): for j in range(3): p[j] = p[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 get_relaxed_cell(self, atom_dump_path, data_in_path, element_symbols): """ Parses the relaxed cell from the dump.atom file. Returns the relaxed cell as a Cell object. Args: atom_dump_path: the path (as a string) to the dump.atom file in_data_path: the path (as a string) to the in.data file element_symbols: a tuple containing the set of chemical symbols of all the elements in the compositions space """ # read the dump.atom file as a list of strings with open(atom_dump_path, 'r') as atom_dump: lines = atom_dump.readlines() # get the lattice vectors a_data = lines[5].split() b_data = lines[6].split() c_data = lines[7].split() # parse the tilt factors xy = float(a_data[2]) xz = float(b_data[2]) yz = float(c_data[2]) # parse the bounds xlo_bound = float(a_data[0]) xhi_bound = float(a_data[1]) ylo_bound = float(b_data[0]) yhi_bound = float(b_data[1]) zlo_bound = float(c_data[0]) zhi_bound = float(c_data[1]) # compute xlo, xhi, ylo, yhi, zlo and zhi according to the conversion # given by LAMMPS # http://lammps.sandia.gov/doc/Section_howto.html#howto-12 xlo = xlo_bound - min([0.0, xy, xz, xy + xz]) xhi = xhi_bound - max([0.0, xy, xz, xy + xz]) ylo = ylo_bound - min(0.0, yz) yhi = yhi_bound - max([0.0, yz]) zlo = zlo_bound zhi = zhi_bound # construct a Lattice object from the lo's and hi's and tilts a = [xhi - xlo, 0.0, 0.0] b = [xy, yhi - ylo, 0.0] c = [xz, yz, zhi - zlo] relaxed_lattice = Lattice([a, b, c]) # get the number of atoms num_atoms = int(lines[3]) # get the atom types and their Cartesian coordinates types = [] relaxed_cart_coords = [] for i in range(num_atoms): atom_info = lines[9 + i].split() types.append(int(atom_info[1])) relaxed_cart_coords.append([ float(atom_info[2]) - xlo, float(atom_info[3]) - ylo, float(atom_info[4]) - zlo ]) # read the atom types and corresponding atomic masses from in.data with open(data_in_path, 'r') as data_in: lines = data_in.readlines() types_masses = {} for i in range(len(lines)): if 'Masses' in lines[i]: for j in range(len(element_symbols)): types_masses[int(lines[i + j + 2].split()[0])] = float( lines[i + j + 2].split()[1]) # map the atom types to chemical symbols types_symbols = {} for symbol in element_symbols: for atom_type in types_masses: # round the atomic masses to one decimal point for comparison if format(float(Element(symbol).atomic_mass), '.1f') == format(types_masses[atom_type], '.1f'): types_symbols[atom_type] = symbol # make a list of chemical symbols (one for each site) relaxed_symbols = [] for atom_type in types: relaxed_symbols.append(types_symbols[atom_type]) return Cell(relaxed_lattice, relaxed_symbols, relaxed_cart_coords, coords_are_cartesian=True)
def get_relaxed_structure(self, gout): #Find the structure lines structure_lines = [] cell_param_lines = [] #print gout output_lines = gout.split("\n") no_lines = len(output_lines) i = 0 # Compute the input lattice parameters while i < no_lines: line = output_lines[i] if "Full cell parameters" in line: i += 2 line = output_lines[i] a = float(line.split()[8]) alpha = float(line.split()[11]) line = output_lines[i + 1] b = float(line.split()[8]) beta = float(line.split()[11]) line = output_lines[i + 2] c = float(line.split()[8]) gamma = float(line.split()[11]) i += 3 break elif "Cell parameters" in line: i += 2 line = output_lines[i] a = float(line.split()[2]) alpha = float(line.split()[5]) line = output_lines[i + 1] b = float(line.split()[2]) beta = float(line.split()[5]) line = output_lines[i + 2] c = float(line.split()[2]) gamma = float(line.split()[5]) i += 3 break else: i += 1 while i < no_lines: line = output_lines[i] if "Final fractional coordinates of atoms" in line: # read the site coordinates in the following lines i += 6 line = output_lines[i] while line[0:2] != '--': structure_lines.append(line) i += 1 line = output_lines[i] # read the cell parameters i += 9 line = output_lines[i] if "Final cell parameters" in line: i += 3 for del_i in range(6): line = output_lines[i + del_i] cell_param_lines.append(line) break else: i += 1 #Process the structure lines if structure_lines: sp = [] coords = [] for line in structure_lines: fields = line.split() if fields[2] == 'c': sp.append(fields[1]) coords.append(list(float(x) for x in fields[3:6])) else: raise IOError("No structure found") if cell_param_lines: a = float(cell_param_lines[0].split()[1]) b = float(cell_param_lines[1].split()[1]) c = float(cell_param_lines[2].split()[1]) alpha = float(cell_param_lines[3].split()[1]) beta = float(cell_param_lines[4].split()[1]) gamma = float(cell_param_lines[5].split()[1]) latt = Lattice.from_parameters(a, b, c, alpha, beta, gamma) return Structure(latt, sp, coords)
def get_same_branch_polarization_data(self, convert_to_muC_per_cm2=False): """ Get same branch dipole moment (convert_to_muC_per_cm2=False) or polarization for given polarization data (convert_to_muC_per_cm2=True). Polarization is a lattice vector, meaning it is only defined modulo the quantum of polarization: P = P_0 + \\sum_i \\frac{n_i e R_i}{\\Omega} where n_i is an integer, e is the charge of the electron in microCoulombs, R_i is a lattice vector, and \\Omega is the unit cell volume in cm**3 (giving polarization units of microCoulomb per centimeter**2). The quantum of the dipole moment in electron Angstroms (as given by VASP) is: \\sum_i n_i e R_i where e, the electron charge, is 1 and R_i is a lattice vector, and n_i is an integer. Given N polarization calculations in order from nonpolar to polar, this algorithm minimizes the distance between adjacent polarization images. To do this, it constructs a polarization lattice for each polarization calculation using the pymatgen.core.structure class and calls the get_nearest_site method to find the image of a given polarization lattice vector that is closest to the previous polarization lattice vector image. convert_to_muC_per_cm2: convert polarization from electron * Angstroms to microCoulomb per centimeter**2 """ p_elec, p_ion = self.get_pelecs_and_pions() p_tot = p_elec + p_ion p_tot = np.array(p_tot) lattices = [s.lattice for s in self.structures] volumes = np.array([s.lattice.volume for s in self.structures]) L = len(p_elec) # convert polarizations and lattice lengths prior to adjustment if convert_to_muC_per_cm2: e_to_muC = -1.6021766e-13 cm2_to_A2 = 1e16 units = 1.0 / np.array(volumes) units *= e_to_muC * cm2_to_A2 # Convert the total polarization p_tot = np.multiply(units.T[:, np.newaxis], p_tot) # adjust lattices for i in range(L): lattice = lattices[i] l, a = lattice.lengths_and_angles lattices[i] = Lattice.from_lengths_and_angles( np.array(l) * units.ravel()[i], a) d_structs = [] sites = [] for i in range(L): l = lattices[i] frac_coord = np.divide(np.array([p_tot[i]]), np.array([l.a, l.b, l.c])) d = PolarizationLattice(l, ["C"], [np.array(frac_coord).ravel()]) d_structs.append(d) site = d[0] if i == 0: # Adjust nonpolar polarization to be closest to zero. # This is compatible with both a polarization of zero or a half quantum. prev_site = [0, 0, 0] else: prev_site = sites[-1].coords new_site = d.get_nearest_site(prev_site, site) sites.append(new_site[0]) adjust_pol = [] for s, d in zip(sites, d_structs): l = d.lattice adjust_pol.append( np.multiply(s.frac_coords, np.array([l.a, l.b, l.c])).ravel()) adjust_pol = np.array(adjust_pol) return adjust_pol
def test_get_slabs(self): gen = SlabGenerator(self.get_structure("CsCl"), [0, 0, 1], 10, 10) #Test orthogonality of some internal variables. a, b, c = gen.oriented_unit_cell.lattice.matrix self.assertAlmostEqual(np.dot(a, gen._normal), 0) self.assertAlmostEqual(np.dot(b, gen._normal), 0) self.assertEqual(len(gen.get_slabs()), 1) s = self.get_structure("LiFePO4") gen = SlabGenerator(s, [0, 0, 1], 10, 10) self.assertEqual(len(gen.get_slabs()), 5) self.assertEqual(len(gen.get_slabs(bonds={("P", "O"): 3})), 2) # There are no slabs in LFP that does not break either P-O or Fe-O # bonds for a miller index of [0, 0, 1]. self.assertEqual( len(gen.get_slabs(bonds={ ("P", "O"): 3, ("Fe", "O"): 3 })), 0) #If we allow some broken bonds, there are a few slabs. self.assertEqual( len( gen.get_slabs(bonds={ ("P", "O"): 3, ("Fe", "O"): 3 }, max_broken_bonds=2)), 2) # At this threshold, only the origin and center Li results in # clustering. All other sites are non-clustered. So the of # slabs is of sites in LiFePO4 unit cell - 2 + 1. self.assertEqual(len(gen.get_slabs(tol=1e-4)), 15) LiCoO2 = Structure.from_file(get_path("icsd_LiCoO2.cif"), primitive=False) gen = SlabGenerator(LiCoO2, [0, 0, 1], 10, 10) lco = gen.get_slabs(bonds={("Co", "O"): 3}) self.assertEqual(len(lco), 1) a, b, c = gen.oriented_unit_cell.lattice.matrix self.assertAlmostEqual(np.dot(a, gen._normal), 0) self.assertAlmostEqual(np.dot(b, gen._normal), 0) scc = Structure.from_spacegroup("Pm-3m", Lattice.cubic(3), ["Fe"], [[0, 0, 0]]) gen = SlabGenerator(scc, [0, 0, 1], 10, 10) slabs = gen.get_slabs() self.assertEqual(len(slabs), 1) gen = SlabGenerator(scc, [1, 1, 1], 10, 10, max_normal_search=1) slabs = gen.get_slabs() self.assertEqual(len(slabs), 1) # Test whether using units of hkl planes instead of Angstroms for # min_slab_size and min_vac_size will give us the same number of atoms natoms = [] for a in [1, 1.4, 2.5, 3.6]: s = Structure.from_spacegroup("Im-3m", Lattice.cubic(a), ["Fe"], [[0, 0, 0]]) slabgen = SlabGenerator(s, (1, 1, 1), 10, 10, in_unit_planes=True, max_normal_search=2) natoms.append(len(slabgen.get_slab())) n = natoms[0] for i in natoms: self.assertEqual(n, i)
from_ase=True) substrate.to(fmt='poscar', filename='POSCAR_substrate_initial.vasp') mat2d.to(fmt='poscar', filename='POSCAR_mat2d_initial.vasp') # get the matching substrate and 2D material lattices uv_substrate, uv_mat2d = get_matching_lattices(substrate, mat2d, max_area=200, max_mismatch=0.01, max_angle_diff=1, r1r2_tol=0.02) # map the intial slabs to the newly found matching lattices substrate_latt = Lattice( np.array([ uv_substrate[0][:], uv_substrate[1][:], substrate.lattice.matrix[2, :] ])) mat2d_latt = Lattice( np.array([uv_mat2d[0][:], uv_mat2d[1][:], mat2d.lattice.matrix[2, :]])) _, __, scell = substrate.lattice.find_mapping(substrate_latt, ltol=0.01, atol=1) substrate.make_supercell(scell) _, __, scell = mat2d.lattice.find_mapping(mat2d_latt, ltol=0.01, atol=1) mat2d.make_supercell(scell) substrate.to(fmt='poscar', filename='POSCAR_substrate_matching.vasp') mat2d.to(fmt='poscar', filename='POSCAR_mat2d_matching.vasp') # modify the substrate lattice so that the 2d material can be # grafted on top of it
def _get_structure_from_spcgrp(self): if "A" not in self.lattice: raise AssertionError() if "B" not in self.lattice: logging.info("Lattice vector B not given, assume equal to A") self.lattice["B"] = self.lattice["A"] if "C" not in self.lattice: logging.info("Lattice vector C not given, assume equal to A") self.lattice["C"] = self.lattice["C"] if "ALPHA" not in self.lattice: logging.info("Lattice angle ALPHA not given, assume right-angle") self.lattice["ALPHA"] = 90 if "BETA" not in self.lattice: logging.info("Lattice angle BETA not given, assume right-angle") self.lattice["BETA"] = 90 if "GAMMA" not in self.lattice: try: spcgrp_number = int(self.lattice["SPCGRP"]) except ValueError: spcgrp_number = 0 if 167 < spcgrp_number < 195: logging.info("Lattice angle GAMMA not given, " "hexagonal space group, assume 120") self.lattice["GAMMA"] = 120 else: logging.info( "Lattice angle GAMMA not given, assume right-angle") self.lattice["GAMMA"] = 90 if self.cartesian: logging.info( "Warning: Cartesian positions used without explicit lattice vectors" ) if "UNITS" not in self.lattice or self.lattice["UNITS"] is None: if self.ignore_units: pass else: for length in ("A", "B", "C"): self.lattice[length] *= _bohr_to_angstrom if "ALAT" not in self.lattice: raise AssertionError() for length in ("A", "B", "C"): self.lattice[length] *= self.lattice["ALAT"] lattice = Lattice.from_parameters( self.lattice["A"], self.lattice["B"], self.lattice["C"], self.lattice["ALPHA"], self.lattice["BETA"], self.lattice["GAMMA"], ) species, coords = self._get_species_coords() return Structure.from_spacegroup( self.lattice["SPCGRP"], lattice, species, coords, coords_are_cartesian=self.cartesian, tol=self.tol, )
def __init__( self, reciprocal_lattice: Lattice, original_points: np.ndarray, original_dim: np.ndarray, extra_points: np.ndarray, ir_to_full_idx: Optional[np.ndarray] = None, extra_ir_points_idx: Optional[np.ndarray] = None, nworkers: int = pdefaults["nworkers"], ): """ Add a warning about only using the symmetry options if you are sure your extra k-points have been symmetrized Args: original_points: nworkers: """ self._nworkers = nworkers if nworkers != -1 else cpu_count() self._final_points = np.concatenate([original_points, extra_points]) self._reciprocal_lattice = reciprocal_lattice if ir_to_full_idx is None: ir_to_full_idx = np.arange( len(original_points) + len(extra_points)) if extra_ir_points_idx is None: extra_ir_points_idx = np.arange(len(extra_points)) logger.debug("Initializing periodic Voronoi calculator") all_points = np.concatenate((original_points, extra_points)) logger.debug(" ├── getting supercell k-points") supercell_points = get_supercell_points(all_points) supercell_idxs = np.arange(supercell_points.shape[0]) # filter points far from the zone boundary, this will lead to errors for # very small meshes < 5x5x5 but we are not interested in those mask = ((supercell_points > -0.75) & (supercell_points < 0.75)).all(axis=1) supercell_points = supercell_points[mask] supercell_idxs = supercell_idxs[mask] # want points in cartesian space so we can define a regular spherical # cutoff even if reciprocal lattice is not cubic. If we used a # fractional cutoff, the cutoff regions would not be spherical logger.debug(" ├── getting cartesian points") cart_points = reciprocal_lattice.get_cartesian_coords(supercell_points) cart_extra_points = reciprocal_lattice.get_cartesian_coords( extra_points[extra_ir_points_idx]) # small cutoff is slightly larger than the max regular grid spacing # means at least 1 neighbour point will always be included in each # direction, need to find cartesian length which covers the longest direction # of the mesh spacing = 1 / original_dim body_diagonal = reciprocal_lattice.get_cartesian_coords(spacing) xy = reciprocal_lattice.get_cartesian_coords( [spacing[0], spacing[1], 0]) xz = reciprocal_lattice.get_cartesian_coords( [spacing[0], 0, spacing[2]]) yz = reciprocal_lattice.get_cartesian_coords( [0, spacing[1], spacing[2]]) len_diagonal = np.linalg.norm(body_diagonal) len_xy = np.linalg.norm(xy) len_xz = np.linalg.norm(xz) len_yz = np.linalg.norm(yz) small_cutoff = (np.max([len_diagonal, len_xy, len_xz, len_yz]) * 1.6) big_cutoff = (small_cutoff * 1.77) logger.debug(" ├── initializing ball tree") # use BallTree for quickly evaluating which points are within cutoffs tree = BallTree(cart_points) n_supercell_points = len(supercell_points) # big points are those which surround the extra points within the big cutoff # (including the extra points themselves) logger.debug(" ├── calculating points in big radius") big_points_idx = _query_radius_iteratively(tree, n_supercell_points, cart_extra_points, big_cutoff) # Voronoi points are those we actually include in the Voronoi diagram self._voronoi_points = cart_points[big_points_idx] # small points are the points in all_points (i.e., original + extra points) for # which we want to calculate the Voronoi volumes. Outside the small cutoff, the # weights will just be the regular grid weight. logger.debug(" └── calculating points in small radius") small_points_idx = _query_radius_iteratively(tree, n_supercell_points, cart_extra_points, small_cutoff) # get the irreducible small points small_points_in_all_points = supercell_idxs[small_points_idx] % len( all_points) mapping = ir_to_full_idx[small_points_in_all_points] unique_mappings, ir_idx = np.unique(mapping, return_index=True) small_points_idx = small_points_idx[ir_idx] # get a mapping to go from the ir small points to the full BZ. groups = groupby(np.arange(len(all_points)), ir_to_full_idx) grouped_ir = groups[unique_mappings] counts = [len(g) for g in grouped_ir] self._expand_ir = np.repeat(np.arange(len(ir_idx)), counts) # get the indices of the expanded ir_small_points in all_points self._volume_in_final_idx = np.concatenate(grouped_ir) # get the indices of ir_small_points_idx (i.e., the points for which we will # calculate the volume) in voronoi_points self._volume_points_idx = _get_loc(big_points_idx, small_points_idx) # Prepopulate the final volumes array. By default, each point has the # volume of the original mesh. Note: at this point, the extra points # will have zero volume. This will array will be updated by # compute_volumes self._volume = reciprocal_lattice.volume self._final_volumes = np.full(len(all_points), 1 / len(original_points)) self._final_volumes[len(original_points):] = 0 self._final_volumes[self._volume_in_final_idx] = 0
def av_lat(l1, l2): params = (np.array(l1.lengths_and_angles) + np.array(l2.lengths_and_angles)) / 2 return Lattice.from_lengths_and_angles(*params)
def from_string(string): """ Reads an PWInput object from a string. Args: string (str): PWInput string Returns: PWInput object """ lines = list(clean_lines(string.splitlines())) def input_mode(line): if line[0] == "&": return ("sections", line[1:].lower()) if "ATOMIC_SPECIES" in line: return ("pseudo",) if "K_POINTS" in line: return "kpoints", line.split("{")[1][:-1] if "CELL_PARAMETERS" in line or "ATOMIC_POSITIONS" in line: return "structure", line.split("{")[1][:-1] if line == "/": return None return mode sections = { "control": {}, "system": {}, "electrons": {}, "ions": {}, "cell": {}, } pseudo = {} pseudo_index = 0 lattice = [] species = [] coords = [] structure = None site_properties = {"pseudo": []} mode = None for line in lines: mode = input_mode(line) if mode is None: pass elif mode[0] == "sections": section = mode[1] m = re.match(r"(\w+)\(?(\d*?)\)?\s*=\s*(.*)", line) if m: key = m.group(1).strip() key_ = m.group(2).strip() val = m.group(3).strip() if key_ != "": if sections[section].get(key, None) is None: val_ = [0.0] * 20 # MAX NTYP DEFINITION val_[int(key_) - 1] = PWInput.proc_val(key, val) sections[section][key] = val_ site_properties[key] = [] else: sections[section][key][int(key_) - 1] = PWInput.proc_val(key, val) else: sections[section][key] = PWInput.proc_val(key, val) elif mode[0] == "pseudo": m = re.match(r"(\w+)\s+(\d*.\d*)\s+(.*)", line) if m: pseudo[m.group(1).strip()] = {} pseudo[m.group(1).strip()]["index"] = pseudo_index pseudo[m.group(1).strip()]["pseudopot"] = m.group(3).strip() pseudo_index += 1 elif mode[0] == "kpoints": m = re.match(r"(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)", line) if m: kpoints_grid = (int(m.group(1)), int(m.group(2)), int(m.group(3))) kpoints_shift = (int(m.group(4)), int(m.group(5)), int(m.group(6))) else: kpoints_mode = mode[1] elif mode[0] == "structure": m_l = re.match(r"(-?\d+\.?\d*)\s+(-?\d+\.?\d*)\s+(-?\d+\.?\d*)", line) m_p = re.match(r"(\w+)\s+(-?\d+\.\d*)\s+(-?\d+\.?\d*)\s+(-?\d+\.?\d*)", line) if m_l: lattice += [ float(m_l.group(1)), float(m_l.group(2)), float(m_l.group(3)), ] elif m_p: site_properties["pseudo"].append(pseudo[m_p.group(1)]["pseudopot"]) species += [pseudo[m_p.group(1)]["pseudopot"].split(".")[0]] coords += [[float(m_p.group(2)), float(m_p.group(3)), float(m_p.group(4))]] for k, v in site_properties.items(): if k != "pseudo": site_properties[k].append(sections["system"][k][pseudo[m_p.group(1)]["index"]]) if mode[1] == "angstrom": coords_are_cartesian = True elif mode[1] == "crystal": coords_are_cartesian = False structure = Structure( Lattice(lattice), species, coords, coords_are_cartesian=coords_are_cartesian, site_properties=site_properties, ) return PWInput( structure=structure, control=sections["control"], system=sections["system"], electrons=sections["electrons"], ions=sections["ions"], cell=sections["cell"], kpoints_mode=kpoints_mode, kpoints_grid=kpoints_grid, kpoints_shift=kpoints_shift, )
def convert_to_ieee(self, structure): """ Given a structure associated with a tensor, attempts a calculation of the tensor in IEEE format according to the 1987 IEEE standards. Args: structure (Structure): a structure associated with the tensor to be converted to the IEEE standard """ def get_uvec(vec): """ Gets a unit vector parallel to input vector""" return vec / np.linalg.norm(vec) # Check conventional setting: sga = SpacegroupAnalyzer(structure) dataset = sga.get_symmetry_dataset() trans_mat = dataset['transformation_matrix'] conv_latt = Lattice( np.transpose( np.dot(np.transpose(structure.lattice.matrix), np.linalg.inv(trans_mat)))) xtal_sys = sga.get_crystal_system() vecs = conv_latt.matrix lengths = np.array(conv_latt.abc) angles = np.array(conv_latt.angles) a = b = c = None rotation = np.zeros((3, 3)) # IEEE rules: a,b,c || x1,x2,x3 if xtal_sys == "cubic": rotation = [vecs[i] / lengths[i] for i in range(3)] # IEEE rules: a=b in length; c,a || x3, x1 elif xtal_sys == "tetragonal": rotation = np.array([ vec / mag for (mag, vec) in sorted(zip(lengths, vecs), key=lambda x: x[0]) ]) if abs(lengths[2] - lengths[1]) < abs(lengths[1] - lengths[0]): rotation[0], rotation[2] = rotation[2], rotation[0].copy() rotation[1] = get_uvec(np.cross(rotation[2], rotation[0])) # IEEE rules: c<a<b; c,a || x3,x1 elif xtal_sys == "orthorhombic": rotation = [vec / mag for (mag, vec) in sorted(zip(lengths, vecs))] rotation = np.roll(rotation, 2, axis=0) # IEEE rules: c,a || x3,x1, c is threefold axis # Note this also includes rhombohedral crystal systems elif xtal_sys in ("trigonal", "hexagonal"): # find threefold axis: tf_index = np.argmin(abs(angles - 120.)) non_tf_mask = np.logical_not(angles == angles[tf_index]) rotation[2] = get_uvec(vecs[tf_index]) rotation[0] = get_uvec(vecs[non_tf_mask][0]) rotation[1] = get_uvec(np.cross(rotation[2], rotation[0])) # IEEE rules: b,c || x2,x3; alpha=beta=90, c<a elif xtal_sys == "monoclinic": # Find unique axis u_index = np.argmax(abs(angles - 90.)) n_umask = np.logical_not(angles == angles[u_index]) rotation[1] = get_uvec(vecs[u_index]) # Shorter of remaining lattice vectors for c axis c = [ vec / mag for (mag, vec) in sorted(zip(lengths[n_umask], vecs[n_umask])) ][0] rotation[2] = np.array(c) rotation[0] = np.cross(rotation[1], rotation[2]) # IEEE rules: c || x3 elif xtal_sys == "triclinic": rotation = [vec / mag for (mag, vec) in sorted(zip(lengths, vecs))] rotation = np.roll(rotation, 2, axis=0) rotation[1] = get_uvec(np.cross(rotation[2], rotation[1])) rotation[0] = np.cross(rotation[1], rotation[2]) return self.rotate(rotation)
from crystal_toolkit.settings import SETTINGS from crystal_toolkit.helpers.layouts import H1, H3, Container # import for this example from pymatgen.ext.matproj import MPRester from pymatgen.analysis.phase_diagram import PhaseDiagram from pymatgen.analysis.diffraction.xrd import XRDCalculator # create Dash app as normal app = dash.Dash(assets_folder=SETTINGS.ASSETS_PATH) # create our crystal structure using pymatgen from pymatgen.core.structure import Structure from pymatgen.core.lattice import Lattice structure = Structure(Lattice.cubic(4.2), ["Na", "K"], [[0, 0, 0], [0.5, 0.5, 0.5]]) xrd_component = ctc.XRayDiffractionComponent(initial_structure=structure) # example layout to demonstrate capabilities of component my_layout = Container([ H1("XRDComponent Example"), H3("Generated from Structure object"), xrd_component.layout(), ]) # as explained in "preamble" section in documentation ctc.register_crystal_toolkit(app=app, layout=my_layout) # allow app to be run using "python structure.py"
def setUp(self): self.silicon = Structure( Lattice.from_lengths_and_angles([5.47, 5.47, 5.47], [90.0, 90.0, 90.0]), ["Si", "Si", "Si", "Si", "Si", "Si", "Si", "Si"], [[0.000000, 0.000000, 0.500000], [0.750000, 0.750000, 0.750000], [0.000000, 0.500000, 1.000000], [0.750000, 0.250000, 0.250000], [0.500000, 0.000000, 1.000000], [0.250000, 0.750000, 0.250000], [0.500000, 0.500000, 0.500000], [0.250000, 0.250000, 0.750000]], validate_proximity=False, to_unit_cell=False, coords_are_cartesian=False, site_properties=None) self.smi = StructureMotifInterstitial( self.silicon, "Si", motif_types=["tetrahedral", "octahedral"], op_threshs=[0.3, 0.5], dl=0.4, doverlap=1.0, facmaxdl=1.51) self.diamond = Structure( Lattice([[2.189, 0, 1.264], [0.73, 2.064, 1.264], [0, 0, 2.528]]), ["C0+", "C0+"], [[2.554, 1.806, 4.423], [0.365, 0.258, 0.632]], validate_proximity=False, to_unit_cell=False, coords_are_cartesian=True, site_properties=None) self.nacl = Structure(Lattice([[3.485, 0, 2.012], [1.162, 3.286, 2.012], [0, 0, 4.025]]), ["Na1+", "Cl1-"], [[0, 0, 0], [2.324, 1.643, 4.025]], validate_proximity=False, to_unit_cell=False, coords_are_cartesian=True, site_properties=None) self.cscl = Structure(Lattice([[4.209, 0, 0], [0, 4.209, 0], [0, 0, 4.209]]), ["Cl1-", "Cs1+"], [[2.105, 2.105, 2.105], [0, 0, 0]], validate_proximity=False, to_unit_cell=False, coords_are_cartesian=True, site_properties=None) self.square_pyramid = Structure(Lattice([[100, 0, 0], [0, 100, 0], [0, 0, 100]]), ["C", "C", "C", "C", "C", "C"], [[0, 0, 0], [1, 0, 0], [-1, 0, 0], [0, 1, 0], [0, -1, 0], [0, 0, 1]], validate_proximity=False, to_unit_cell=False, coords_are_cartesian=True, site_properties=None) self.trigonal_bipyramid = Structure( Lattice([[100, 0, 0], [0, 100, 0], [0, 0, 100]]), ["P", "Cl", "Cl", "Cl", "Cl", "Cl"], [[0, 0, 0], [0, 0, 2.14], [0, 2.02, 0], [1.74937, -1.01, 0], [-1.74937, -1.01, 0], [0, 0, -2.14]], validate_proximity=False, to_unit_cell=False, coords_are_cartesian=True, site_properties=None)
if not os.path.exists(file_dir): os.makedirs(file_dir) else: print(f'{file_dir} already exists') # INCAR # creating Incar object incar = Incar(incar_dict) # writing INCAR in designated path incar.write_file(file_dir + 'INCAR') # POSCAR a = a_min + interval * i new_lattice = Lattice([[a, 0, 0], [0, a, 0], [0, 0, a]]) # creating new structure struct.lattice = new_lattice # new_struct_dict = struct.as_dict() # print(new_struct_dict['lattice']['a']) poscar = Poscar(struct) poscar.write_file(file_dir + 'POSCAR') # KPOINTS k = 6 kpoints = Kpoints.gamma_automatic(kpts=(k, k, k), shift=(0.0, 0.0, 0.0)) kpoints.write_file(file_dir + 'KPOINTS') # POTCAR
def read_data(data=None,ff=None): pot_file=open(ff,"r") lines = pot_file.read().splitlines() symb=[] count=0 from pymatgen.core.periodic_table import Element for i, line in enumerate(lines): if "pair_coeff" in line.split(): sp=line.split() #print "spsplit",sp,os.getcwd() for el in sp: try: if Element(el): #if el=='M': # el='Mo' #count=count+1 #if count >4: symb.append(Element(el)) except: pass f=open(data,"r") lines = f.read().splitlines() for i, line in enumerate(lines): if "atoms" in line.split(): natoms=int(line.split()[0]) if "types" in line.split(): #print line ntypes=int(line.split()[0]) if "xlo" in line.split(): xlo=float(line.split()[0]) xhi=float(line.split()[1]) if "ylo" in line.split(): ylo=float(line.split()[0]) yhi=float(line.split()[1]) if "zlo" in line.split(): zlo=float(line.split()[0]) zhi=float(line.split()[1]) if "xy" in line.split(): xy=float(line.split()[0]) xz=float(line.split()[1]) yz=float(line.split()[2]) if len(symb) != ntypes: print ("Something wrong in atom type assignment",len(symb),ntypes) sys.exit() lat=Lattice([[ xhi-xlo,0.0,0.0],[xy,yhi-ylo,0.0],[xz,yz,zhi-zlo]]) typ= [] #np.empty((natoms),dtype="S20") x=np.zeros((natoms)) y=np.zeros((natoms)) z=np.zeros((natoms)) q=np.zeros((natoms)) coords = list() for i, line in enumerate(lines): if "Atoms" in line.split(): for j in range(0,natoms): typ.append(symb[int(((lines[i+j+2]).split()[1]))-1]) q[j]=(lines[i+j+2]).split()[2] x[j]=(lines[i+j+2]).split()[3] y[j]=(lines[i+j+2]).split()[4] z[j]=(lines[i+j+2]).split()[5] coords.append([x[j],y[j],z[j]]) f.close() #print "info",len(typ),len(coords) pot_file.close() struct=Structure(lat,typ,coords,coords_are_cartesian=True) finder = SpacegroupAnalyzer(struct) num=finder.get_space_group_symbol() return struct
def get_conventional_standard_structure(self, international_monoclinic=True): """ Gives a structure with a conventional cell according to certain standards. The standards are defined in Setyawan, W., & Curtarolo, S. (2010). High-throughput electronic band structure calculations: Challenges and tools. Computational Materials Science, 49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010 They basically enforce as much as possible norm(a1)<norm(a2)<norm(a3) Returns: The structure in a conventional standardized cell """ tol = 1e-5 struct = self.get_refined_structure() latt = struct.lattice latt_type = self.get_lattice_type() sorted_lengths = sorted(latt.abc) sorted_dic = sorted([{ 'vec': latt.matrix[i], 'length': latt.abc[i], 'orig_index': i } for i in [0, 1, 2]], key=lambda k: k['length']) if latt_type in ("orthorhombic", "cubic"): #you want to keep the c axis where it is #to keep the C- settings transf = np.zeros(shape=(3, 3)) if self.get_spacegroup_symbol().startswith("C"): transf[2] = [0, 0, 1] a, b = sorted(latt.abc[:2]) sorted_dic = sorted([{ 'vec': latt.matrix[i], 'length': latt.abc[i], 'orig_index': i } for i in [0, 1]], key=lambda k: k['length']) for i in range(2): transf[i][sorted_dic[i]['orig_index']] = 1 c = latt.abc[2] else: for i in range(len(sorted_dic)): transf[i][sorted_dic[i]['orig_index']] = 1 a, b, c = sorted_lengths latt = Lattice.orthorhombic(a, b, c) elif latt_type == "tetragonal": #find the "a" vectors #it is basically the vector repeated two times transf = np.zeros(shape=(3, 3)) a, b, c = sorted_lengths for d in range(len(sorted_dic)): transf[d][sorted_dic[d]['orig_index']] = 1 if abs(b - c) < tol: a, c = c, a transf = np.dot([[0, 0, 1], [0, 1, 0], [1, 0, 0]], transf) latt = Lattice.tetragonal(a, c) elif latt_type in ("hexagonal", "rhombohedral"): #for the conventional cell representation, #we allways show the rhombohedral lattices as hexagonal #check first if we have the refined structure shows a rhombohedral #cell #if so, make a supercell a, b, c = latt.abc if np.all(np.abs([a - b, c - b, a - c]) < 0.001): struct.make_supercell(((1, -1, 0), (0, 1, -1), (1, 1, 1))) a, b, c = sorted(struct.lattice.abc) if abs(b - c) < 0.001: a, c = c, a new_matrix = [[a / 2, -a * math.sqrt(3) / 2, 0], [a / 2, a * math.sqrt(3) / 2, 0], [0, 0, c]] latt = Lattice(new_matrix) transf = np.eye(3, 3) elif latt_type == "monoclinic": #you want to keep the c axis where it is #to keep the C- settings if self.get_spacegroup().int_symbol.startswith("C"): transf = np.zeros(shape=(3, 3)) transf[2] = [0, 0, 1] sorted_dic = sorted([{ 'vec': latt.matrix[i], 'length': latt.abc[i], 'orig_index': i } for i in [0, 1]], key=lambda k: k['length']) a = sorted_dic[0]['length'] b = sorted_dic[1]['length'] c = latt.abc[2] new_matrix = None for t in itertools.permutations(list(range(2)), 2): m = latt.matrix landang = Lattice([m[t[0]], m[t[1]], m[2]]).lengths_and_angles if landang[1][0] > 90: #if the angle is > 90 we invert a and b to get #an angle < 90 landang = Lattice([-m[t[0]], -m[t[1]], m[2]]).lengths_and_angles transf = np.zeros(shape=(3, 3)) transf[0][t[0]] = -1 transf[1][t[1]] = -1 transf[2][2] = 1 a, b, c = landang[0] alpha = math.pi * landang[1][0] / 180 new_matrix = [[a, 0, 0], [0, b, 0], [0, c * cos(alpha), c * sin(alpha)]] continue elif landang[1][0] < 90: transf = np.zeros(shape=(3, 3)) transf[0][t[0]] = 1 transf[1][t[1]] = 1 transf[2][2] = 1 a, b, c = landang[0] alpha = math.pi * landang[1][0] / 180 new_matrix = [[a, 0, 0], [0, b, 0], [0, c * cos(alpha), c * sin(alpha)]] if new_matrix is None: #this if is to treat the case #where alpha==90 (but we still have a monoclinic sg new_matrix = [[a, 0, 0], [0, b, 0], [0, 0, c]] transf = np.zeros(shape=(3, 3)) for c in range(len(sorted_dic)): transf[c][sorted_dic[c]['orig_index']] = 1 #if not C-setting else: #try all permutations of the axis #keep the ones with the non-90 angle=alpha #and b<c new_matrix = None for t in itertools.permutations(list(range(3)), 3): m = latt.matrix landang = Lattice([m[t[0]], m[t[1]], m[t[2]]]).lengths_and_angles if landang[1][0] > 90 and landang[0][1] < landang[0][2]: landang = Lattice([-m[t[0]], -m[t[1]], m[t[2]]]).lengths_and_angles transf = np.zeros(shape=(3, 3)) transf[0][t[0]] = -1 transf[1][t[1]] = -1 transf[2][t[2]] = 1 a, b, c = landang[0] alpha = math.pi * landang[1][0] / 180 new_matrix = [[a, 0, 0], [0, b, 0], [0, c * cos(alpha), c * sin(alpha)]] continue elif landang[1][0] < 90 and landang[0][1] < landang[0][2]: transf = np.zeros(shape=(3, 3)) transf[0][t[0]] = 1 transf[1][t[1]] = 1 transf[2][t[2]] = 1 a, b, c = landang[0] alpha = math.pi * landang[1][0] / 180 new_matrix = [[a, 0, 0], [0, b, 0], [0, c * cos(alpha), c * sin(alpha)]] if new_matrix is None: #this if is to treat the case #where alpha==90 (but we still have a monoclinic sg new_matrix = [[sorted_lengths[0], 0, 0], [0, sorted_lengths[1], 0], [0, 0, sorted_lengths[2]]] transf = np.zeros(shape=(3, 3)) for c in range(len(sorted_dic)): transf[c][sorted_dic[c]['orig_index']] = 1 if international_monoclinic: # The above code makes alpha the non-right angle. # The following will convert to proper international convention # that beta is the non-right angle. op = [[0, 1, 0], [1, 0, 0], [0, 0, -1]] transf = np.dot(op, transf) new_matrix = np.dot(op, new_matrix) beta = Lattice(new_matrix).beta if beta < 90: op = [[-1, 0, 0], [0, -1, 0], [0, 0, 1]] transf = np.dot(op, transf) new_matrix = np.dot(op, new_matrix) latt = Lattice(new_matrix) elif latt_type == "triclinic": #we use a LLL Minkowski-like reduction for the triclinic cells struct = struct.get_reduced_structure("LLL") a, b, c = latt.lengths_and_angles[0] alpha, beta, gamma = [ math.pi * i / 180 for i in latt.lengths_and_angles[1] ] new_matrix = None test_matrix = [ [a, 0, 0], [b * cos(gamma), b * sin(gamma), 0.0], [ c * cos(beta), c * (cos(alpha) - cos(beta) * cos(gamma)) / sin(gamma), c * math.sqrt( sin(gamma)**2 - cos(alpha)**2 - cos(beta)**2 + 2 * cos(alpha) * cos(beta) * cos(gamma)) / sin(gamma) ] ] def is_all_acute_or_obtuse(m): recp_angles = np.array(Lattice(m).reciprocal_lattice.angles) return np.all(recp_angles <= 90) or np.all(recp_angles > 90) if is_all_acute_or_obtuse(test_matrix): transf = np.eye(3) new_matrix = test_matrix test_matrix = [ [-a, 0, 0], [b * cos(gamma), b * sin(gamma), 0.0], [ -c * cos(beta), -c * (cos(alpha) - cos(beta) * cos(gamma)) / sin(gamma), -c * math.sqrt( sin(gamma)**2 - cos(alpha)**2 - cos(beta)**2 + 2 * cos(alpha) * cos(beta) * cos(gamma)) / sin(gamma) ] ] if is_all_acute_or_obtuse(test_matrix): transf = [[-1, 0, 0], [0, 1, 0], [0, 0, -1]] new_matrix = test_matrix test_matrix = [ [-a, 0, 0], [-b * cos(gamma), -b * sin(gamma), 0.0], [ c * cos(beta), c * (cos(alpha) - cos(beta) * cos(gamma)) / sin(gamma), c * math.sqrt( sin(gamma)**2 - cos(alpha)**2 - cos(beta)**2 + 2 * cos(alpha) * cos(beta) * cos(gamma)) / sin(gamma) ] ] if is_all_acute_or_obtuse(test_matrix): transf = [[-1, 0, 0], [0, -1, 0], [0, 0, 1]] new_matrix = test_matrix test_matrix = [ [a, 0, 0], [-b * cos(gamma), -b * sin(gamma), 0.0], [ -c * cos(beta), -c * (cos(alpha) - cos(beta) * cos(gamma)) / sin(gamma), -c * math.sqrt( sin(gamma)**2 - cos(alpha)**2 - cos(beta)**2 + 2 * cos(alpha) * cos(beta) * cos(gamma)) / sin(gamma) ] ] if is_all_acute_or_obtuse(test_matrix): transf = [[1, 0, 0], [0, -1, 0], [0, 0, -1]] new_matrix = test_matrix latt = Lattice(new_matrix) new_coords = np.dot(transf, np.transpose(struct.frac_coords)).T new_struct = Structure(latt, struct.species_and_occu, new_coords, site_properties=struct.site_properties, to_unit_cell=True) return new_struct.get_sorted_structure()
def test_tune_for_gamma(self): lattice = Lattice([[4.692882, -8.12831, 0.0], [4.692882, 8.12831, 0.0], [0.0, 0.0, 10.03391]]) epsilon = 10.0 * np.identity(3) gamma = tune_for_gamma(lattice, epsilon) self.assertAlmostEqual(gamma, 0.19357221)
def get_ieee_rotation(structure, refine_rotation=True): """ Given a structure associated with a tensor, determines the rotation matrix for IEEE conversion according to the 1987 IEEE standards. Args: structure (Structure): a structure associated with the tensor to be converted to the IEEE standard refine_rotation (bool): whether to refine the rotation using SquareTensor.refine_rotation """ # Check conventional setting: sga = SpacegroupAnalyzer(structure) dataset = sga.get_symmetry_dataset() trans_mat = dataset["transformation_matrix"] conv_latt = Lattice( np.transpose( np.dot(np.transpose(structure.lattice.matrix), np.linalg.inv(trans_mat)))) xtal_sys = sga.get_crystal_system() vecs = conv_latt.matrix lengths = np.array(conv_latt.abc) angles = np.array(conv_latt.angles) rotation = np.zeros((3, 3)) # IEEE rules: a,b,c || x1,x2,x3 if xtal_sys == "cubic": rotation = [vecs[i] / lengths[i] for i in range(3)] # IEEE rules: a=b in length; c,a || x3, x1 elif xtal_sys == "tetragonal": rotation = np.array([ vec / mag for (mag, vec) in sorted(zip(lengths, vecs), key=lambda x: x[0]) ]) if abs(lengths[2] - lengths[1]) < abs(lengths[1] - lengths[0]): rotation[0], rotation[2] = rotation[2], rotation[0].copy() rotation[1] = get_uvec(np.cross(rotation[2], rotation[0])) # IEEE rules: c<a<b; c,a || x3,x1 elif xtal_sys == "orthorhombic": rotation = [vec / mag for (mag, vec) in sorted(zip(lengths, vecs))] rotation = np.roll(rotation, 2, axis=0) # IEEE rules: c,a || x3,x1, c is threefold axis # Note this also includes rhombohedral crystal systems elif xtal_sys in ("trigonal", "hexagonal"): # find threefold axis: tf_index = np.argmin(abs(angles - 120.0)) non_tf_mask = np.logical_not(angles == angles[tf_index]) rotation[2] = get_uvec(vecs[tf_index]) rotation[0] = get_uvec(vecs[non_tf_mask][0]) rotation[1] = get_uvec(np.cross(rotation[2], rotation[0])) # IEEE rules: b,c || x2,x3; alpha=beta=90, c<a elif xtal_sys == "monoclinic": # Find unique axis u_index = np.argmax(abs(angles - 90.0)) n_umask = np.logical_not(angles == angles[u_index]) rotation[1] = get_uvec(vecs[u_index]) # Shorter of remaining lattice vectors for c axis c = [ vec / mag for (mag, vec) in sorted(zip(lengths[n_umask], vecs[n_umask])) ][0] rotation[2] = np.array(c) rotation[0] = np.cross(rotation[1], rotation[2]) # IEEE rules: c || x3, x2 normal to ac plane elif xtal_sys == "triclinic": rotation = [vec / mag for (mag, vec) in sorted(zip(lengths, vecs))] rotation[1] = get_uvec(np.cross(rotation[2], rotation[0])) rotation[0] = np.cross(rotation[1], rotation[2]) rotation = SquareTensor(rotation) if refine_rotation: rotation = rotation.refine_rotation() return rotation
def get_voronoi_nodes(structure, rad_dict=None, probe_rad=0.1): """ Analyze the void space in the input structure using voronoi decomposition Calls Zeo++ for Voronoi decomposition. Args: structure: pymatgen.core.structure.Structure rad_dict (optional): Dictionary of radii of elements in structure. If not given, Zeo++ default values are used. Note: Zeo++ uses atomic radii of elements. For ionic structures, pass rad_dict with ionic radii probe_rad (optional): Sampling probe radius in Angstroms. Default is 0.1 A Returns: voronoi nodes as pymatgen.core.structure.Strucutre within the unit cell defined by the lattice of input structure voronoi face centers as pymatgen.core.structure.Strucutre within the unit cell defined by the lattice of input structure """ with ScratchDir("."): name = "temp_zeo1" zeo_inp_filename = name + ".cssr" ZeoCssr(structure).write_file(zeo_inp_filename) rad_file = None rad_flag = False if rad_dict: rad_file = name + ".rad" rad_flag = True with open(rad_file, "w+") as fp: for el in rad_dict.keys(): fp.write(f"{el} {rad_dict[el].real}\n") atmnet = AtomNetwork.read_from_CSSR(zeo_inp_filename, rad_flag=rad_flag, rad_file=rad_file) ( vornet, vor_edge_centers, vor_face_centers, ) = atmnet.perform_voronoi_decomposition() vornet.analyze_writeto_XYZ(name, probe_rad, atmnet) voro_out_filename = name + "_voro.xyz" voro_node_mol = ZeoVoronoiXYZ.from_file(voro_out_filename).molecule species = ["X"] * len(voro_node_mol.sites) coords = [] prop = [] for site in voro_node_mol.sites: coords.append(list(site.coords)) prop.append(site.properties["voronoi_radius"]) lattice = Lattice.from_parameters(*structure.lattice.parameters) vor_node_struct = Structure( lattice, species, coords, coords_are_cartesian=True, to_unit_cell=True, site_properties={"voronoi_radius": prop}, ) # PMG-Zeo c<->a transformation for voronoi face centers rot_face_centers = [(center[1], center[2], center[0]) for center in vor_face_centers] rot_edge_centers = [(center[1], center[2], center[0]) for center in vor_edge_centers] species = ["X"] * len(rot_face_centers) prop = [0.0] * len(rot_face_centers) # Vor radius not evaluated for fc vor_facecenter_struct = Structure( lattice, species, rot_face_centers, coords_are_cartesian=True, to_unit_cell=True, site_properties={"voronoi_radius": prop}, ) species = ["X"] * len(rot_edge_centers) prop = [0.0] * len(rot_edge_centers) # Vor radius not evaluated for fc vor_edgecenter_struct = Structure( lattice, species, rot_edge_centers, coords_are_cartesian=True, to_unit_cell=True, site_properties={"voronoi_radius": prop}, ) return vor_node_struct, vor_edgecenter_struct, vor_facecenter_struct