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 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
def _cart_dists(self, s1, s2, l1, l2, mask): """ Finds the cartesian distances normalized by (V/Natom) ^ 1/3 between s1 and s2 on the average lattice of l1 and l2 s1 and s2 are lists of fractional coordinates. Minimizes the RMS distance of the matching with an additional translation (but doesn't change the mapping) returns distances, fractional_translation vector """ #create the average lattice avg_params = (np.array(l1.lengths_and_angles) + np.array(l2.lengths_and_angles)) / 2 avg_lattice = Lattice.from_lengths_and_angles(*avg_params) norm_length = (avg_lattice.volume / len(s1)) ** (1 / 3) mask_val = 1e20 * norm_length * self.stol all_d_2 = np.zeros([len(s1), len(s1)]) vec_matrix = np.zeros([len(s1), len(s1), 3]) vecs = pbc_shortest_vectors(avg_lattice, s2, s1) vec_matrix[:len(s2)] = vecs vec_matrix[mask] = mask_val d_2 = (np.sum(vecs ** 2, axis=-1)) all_d_2[:len(s2)] = d_2 all_d_2[mask] = mask_val lin = LinearAssignment(all_d_2) inds = np.arange(len(s2)) shortest_vecs = vec_matrix[inds, lin.solution[:len(s2)], :] translation = np.average(shortest_vecs, axis=0) f_translation = avg_lattice.get_fractional_coords(translation) shortest_distances = np.sum((shortest_vecs - translation) ** 2, -1) ** 0.5 return shortest_distances / norm_length, f_translation
def get_lattice( self, data, length_strings=("a", "b", "c"), angle_strings=("alpha", "beta", "gamma"), lattice_type=None ): """ Generate the lattice from the provided lattice parameters. In the absence of all six lattice parameters, the crystal system and necessary parameters are parsed """ try: lengths = [str2float(data["_cell_length_" + i]) for i in length_strings] angles = [str2float(data["_cell_angle_" + i]) for i in angle_strings] if not lattice_type: return Lattice.from_lengths_and_angles(lengths, angles) else: return getattr(Lattice, lattice_type)(*(lengths + angles)) except KeyError: # Missing Key search for cell setting for lattice_lable in ["_symmetry_cell_setting", "_space_group_crystal_system"]: if data.data.get(lattice_lable): lattice_type = data.data.get(lattice_lable).lower() try: required_args = getargspec(getattr(Lattice, lattice_type)).args lengths = (l for l in length_strings if l in required_args) angles = (a for a in angle_strings if a in required_args) return self.get_lattice(data, lengths, angles, lattice_type=lattice_type) except AttributeError as exc: warnings.warn(exc) else: return None
def test_find_all_mappings(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, -1, 3], 40) rot = op.rotation_matrix scale = np.array([[0, 2, 0], [1, 1, 0], [0,0,1]]) latt2 = Lattice(np.dot(rot, np.dot(scale, m).T).T) for (aligned_out, rot_out, scale_out) in latt.find_all_mappings(latt2): self.assertArrayAlmostEqual(np.inner(latt2.matrix, rot_out), aligned_out.matrix, 5) self.assertArrayAlmostEqual(np.dot(scale_out, latt.matrix), aligned_out.matrix) self.assertArrayAlmostEqual(aligned_out.lengths_and_angles, latt2.lengths_and_angles) self.assertFalse(np.allclose(aligned_out.lengths_and_angles, latt.lengths_and_angles)) latt = Lattice.orthorhombic(9, 9, 5) self.assertEqual(len(list(latt.find_all_mappings(latt))), 16) #catch the singular matrix error latt = Lattice.from_lengths_and_angles([1,1,1], [10,10,10]) for l, _, _ in latt.find_all_mappings(latt, ltol=0.05, atol=11): self.assertTrue(isinstance(l, Lattice))
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=["tet", "oct", "tetoct"], op_targets=[1.0, 1.0, 1.0], op_threshs=[0.5, 0.5, 0.5], dl=0.4, fac_max_radius=2.5, drel_overlap=0.5, write_timings=False, )
def test_interpolate_lattice(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]) l2 = Lattice.from_lengths_and_angles([3,4,4], [100,100,70]) struct2 = IStructure(l2, ["Si"] * 2, coords2) int_s = struct.interpolate(struct2, 2, interpolate_lattices=True) self.assertArrayAlmostEqual(struct.lattice.abc, int_s[0].lattice.abc) self.assertArrayAlmostEqual(struct.lattice.angles, int_s[0].lattice.angles) self.assertArrayAlmostEqual(struct2.lattice.abc, int_s[2].lattice.abc) self.assertArrayAlmostEqual(struct2.lattice.angles, int_s[2].lattice.angles) int_angles = [110.3976469, 94.5359731, 64.5165856] self.assertArrayAlmostEqual(int_angles, int_s[1].lattice.angles) # Assert that volume is monotonic self.assertTrue(struct2.lattice.volume >= int_s[1].lattice.volume) self.assertTrue(int_s[1].lattice.volume >= struct.lattice.volume)
def from_string(string): """ Reads a string representation to a Cssr object. Args: string (str): A string representation of a CSSR. Returns: Cssr object. """ lines = string.split("\n") toks = lines[0].split() lengths = [float(i) for i in toks] toks = lines[1].split() angles = [float(i) for i in toks[0:3]] latt = Lattice.from_lengths_and_angles(lengths, angles) sp = [] coords = [] for l in lines[4:]: m = re.match( r"\d+\s+(\w+)\s+([0-9\-\.]+)\s+([0-9\-\.]+)\s+([0-9\-\.]+)", l.strip()) if m: sp.append(m.group(1)) coords.append([float(m.group(i)) for i in range(2, 5)]) return Cssr(Structure(latt, sp, coords))
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 """ temp_dir = tempfile.mkdtemp() current_dir = os.getcwd() name = "temp_zeo1" zeo_inp_filename = name + ".cssr" os.chdir(temp_dir) 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(): print >>fp, "{} {}".format(el, rad_dict[el].real) atmnet = AtomNetwork.read_from_CSSR(zeo_inp_filename, rad_flag=rad_flag, rad_file=rad_file) vornet = atmnet.perform_voronoi_decomposition() vornet.analyze_writeto_XYZ(name, probe_rad, atmnet) voronoi_out_filename = name + '_voro.xyz' voronoi_node_mol = ZeoVoronoiXYZ.from_file(voronoi_out_filename).molecule #print voronoi_node_mol species = ["X"] * len(voronoi_node_mol.sites) coords = [] prop = [] for site in voronoi_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 ) voronoi_node_struct = Structure( lattice, species, coords, coords_are_cartesian=True, site_properties={"voronoi_radius": prop} ) os.chdir(current_dir) shutil.rmtree(temp_dir) return voronoi_node_struct
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_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 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. """ lines = tuple(clean_lines(header_str.split("\n"), False)) comment = lines[0] feffpmg = comment.find("pymatgen") if feffpmg > 0: source = lines[1].split()[2] natoms = int(lines[7].split()[2]) basis_vec = lines[5].split() a = float(basis_vec[2]) b = float(basis_vec[3]) c = float(basis_vec[4]) lengths = [a, b, c] basis_ang = lines[6].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(8, 8 + natoms): atomic_symbols.append(lines[i].split()[2]) # read the atomic coordinates coords = [] for i in xrange(natoms): toks = lines[i + 8].split() coords.append([float(s) for s in toks[3:]]) struct_fromfile = Structure(lattice, atomic_symbols, coords, False, False, False) struct_fromfile.compound = lines[3].split()[2] h = Header(struct_fromfile, comment) h.set_source(source) return h else: return "Header not generated by pymatgen, " \ "cannot return header object"
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)
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)
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 from_string(data): """ Reads a Res from a string. Args: data (str): String containing Res data. Returns: Res object. """ abc = [] ang = [] sp = [] coords = [] info = dict() coord_patt = re.compile( "(\w+)\s+([0-9]+)\s+([0-9\-\.]+)\s+([0-9\-\.]+)\s+([0-9\-\.]+)\s+([0-9\-\.]+)" ) lines = data.splitlines() line_no = 0 while line_no < len(lines): line = lines[line_no] tokens = line.split() if tokens: if tokens[0] == 'TITL': try: info = Res.parse_title(line) except ValueError: info = dict() elif tokens[0] == 'CELL' and len(tokens) == 8: abc = map(float, tokens[2:5]) ang = map(float, tokens[5:8]) elif tokens[0] == 'SFAC': for atom_line in lines[line_no:]: if line.strip() == 'END': break else: match = coord_patt.search(atom_line) if match: sp.append(match.group(1)) # 1-indexed coords.append(map(float, match.groups()[2:5])) # 0-indexed line_no += 1 # Make sure the global is updated line_no += 1 return Res(Structure(Lattice.from_lengths_and_angles(abc, ang), sp, coords), info.get('name'), info.get('pressure'), info.get('energy'), info.get('spacegroup'), info.get('times_found'))
def test_interpolate_lattice(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]) l2 = Lattice.from_lengths_and_angles([3, 4, 4], [100, 100, 70]) struct2 = IStructure(l2, ["Si"] * 2, coords2) int_s = struct.interpolate(struct2, 2, interpolate_lattices=True) self.assertArrayAlmostEqual(struct.lattice.abc, int_s[0].lattice.abc) self.assertArrayAlmostEqual(struct.lattice.angles, int_s[0].lattice.angles) self.assertArrayAlmostEqual(struct2.lattice.abc, int_s[2].lattice.abc) self.assertArrayAlmostEqual(struct2.lattice.angles, int_s[2].lattice.angles) int_angles = [(a + struct2.lattice.angles[i]) / 2 for i, a in enumerate(struct.lattice.angles)] self.assertArrayAlmostEqual(int_angles, int_s[1].lattice.angles)
def test_get_all_distances(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], [3.245, 4.453, 1.788, 3.852, 0.000]]) output = lattice.get_all_distances(fcoords, fcoords) self.assertArrayAlmostEqual(output, expected, 3) #test just one input point output2 = lattice.get_all_distances(fcoords[0], fcoords) self.assertArrayAlmostEqual(output2, [expected[0]], 2) #test distance when initial points are not in unit cell f1 = [0, 0, 17] f2 = [0, 0, 10] self.assertEqual(lattice.get_all_distances(f1, f2)[0, 0], 0)
def _cmp_cartesian_struct(self, s1, s2, l1, l2): """ Once a fit is found, a rms minimizing fit is done to ensure the fit is correct. To do this, 1) The structures are placed into an average lattice 2) All sites are shifted by the mean displacement vector between matched sites. 3) calculate distances 4) return rms distance normalized by (V/Natom) ^ 1/3 and the maximum distance found """ nsites = sum(map(len, s1)) avg_params = (np.array(l1.lengths_and_angles) + np.array(l2.lengths_and_angles)) / 2 avg_lattice = Lattice.from_lengths_and_angles(avg_params[0], avg_params[1]) dist = np.zeros([nsites, nsites]) + 100 * nsites vec_matrix = np.zeros([nsites, nsites, 3]) i = 0 for s1_coords, s2_coords in zip(s1, s2): j = len(s1_coords) vecs = pbc_shortest_vectors(avg_lattice, s1_coords, s2_coords) distances = (np.sum(vecs**2, axis=-1))**0.5 dist[i:i + j, i:i + j] = distances vec_matrix[i:i + j, i:i + j] = vecs i += j lin = LinearAssignment(dist) inds = np.arange(nsites) shortest_vecs = vec_matrix[inds, lin.solution, :] shortest_vec_square = np.sum( (shortest_vecs - np.average(shortest_vecs, axis=0))**2, -1) norm_length = (avg_lattice.volume / nsites)**(1 / 3) rms = np.average(shortest_vec_square)**0.5 / norm_length max_dist = np.max(shortest_vec_square)**0.5 / norm_length return rms, max_dist
def _cart_dists(self, s1, s2, l1, l2, mask): """ Finds the cartesian distances normalized by (V/Natom) ^ 1/3 between two structures on the average lattice of l1 and l2 s_superset and s_subset are lists of fractional coordinates. Minimizes the RMS distance of the matching with an additional translation (but doesn't change the mapping) returns distances, fractional_translation vector """ #ensure that we always calculate distances from the subset #to the superset if len(s1) > len(s2): s_superset, s_subset, mult = s1, s2, 1 else: s_superset, s_subset, mult = s2, s1, -1 mask = mask.T #create the average lattice avg_params = (np.array(l1.lengths_and_angles) + np.array(l2.lengths_and_angles)) / 2 avg_lattice = Lattice.from_lengths_and_angles(*avg_params) norm_length = (avg_lattice.volume / len(s_superset)) ** (1 / 3) mask_val = 1e20 * norm_length * self.stol all_d_2 = np.zeros([len(s_superset), len(s_superset)]) vec_matrix = np.zeros([len(s_superset), len(s_superset), 3]) #vectors from subset to superset #1st index subset, 2nd index superset vecs = pbc_shortest_vectors(avg_lattice, s_subset, s_superset) vec_matrix[:len(s_subset), :len(s_superset)] = vecs vec_matrix[mask] = mask_val d_2 = (np.sum(vecs ** 2, axis=-1)) all_d_2[:len(s_subset), :len(s_superset)] = d_2 all_d_2[mask] = mask_val lin = LinearAssignment(all_d_2) inds = np.arange(len(s_subset)) #shortest vectors from the subset to the superset shortest_vecs = vec_matrix[inds, lin.solution[:len(s_subset)], :] translation = np.average(shortest_vecs, axis=0) f_translation = avg_lattice.get_fractional_coords(translation) shortest_distances = np.sum((shortest_vecs - translation) ** 2, -1) ** 0.5 return shortest_distances / norm_length, f_translation * mult
def _cmp_cartesian_struct(self, s1, s2, l1, l2): """ Once a fit is found, a rms minimizing fit is done to ensure the fit is correct. To do this, 1) The structures are placed into an average lattice 2) All sites are shifted by the mean displacement vector between matched sites. 3) calculate distances 4) return rms distance normalized by (V/Natom) ^ 1/3 and the maximum distance found """ nsites = sum(map(len, s1)) avg_params = (np.array(l1.lengths_and_angles) + np.array(l2.lengths_and_angles)) / 2 avg_lattice = Lattice.from_lengths_and_angles(avg_params[0], avg_params[1]) dist = np.zeros([nsites, nsites]) + 100 * nsites vec_matrix = np.zeros([nsites, nsites, 3]) i = 0 for s1_coords, s2_coords in zip(s1, s2): j = len(s1_coords) vecs = pbc_shortest_vectors(avg_lattice, s1_coords, s2_coords) distances = (np.sum(vecs ** 2, axis=-1)) ** 0.5 dist[i: i + j, i: i + j] = distances vec_matrix[i: i + j, i: i + j] = vecs i += j lin = LinearAssignment(dist) inds = np.arange(nsites) shortest_vecs = vec_matrix[inds, lin.solution, :] shortest_vec_square = np.sum( (shortest_vecs - np.average(shortest_vecs, axis=0)) ** 2, -1) norm_length = (avg_lattice.volume / nsites) ** (1 / 3) rms = np.average(shortest_vec_square) ** 0.5 / norm_length max_dist = np.max(shortest_vec_square) ** 0.5 / norm_length return rms, max_dist
def _cart_dists(self, s1, s2, l1, l2, mask): """ Finds the cartesian distances normalized by (V/Natom) ^ 1/3 between two structures on the average lattice of l1 and l2 s_superset and s_subset are lists of fractional coordinates. Minimizes the RMS distance of the matching with an additional translation (but doesn't change the mapping) returns distances, fractional_translation vector """ #ensure that we always calculate distances from the subset #to the superset if len(s1) > len(s2): s_superset, s_subset, mult = s1, s2, 1 else: s_superset, s_subset, mult = s2, s1, -1 mask = mask.T #create the average lattice avg_params = (np.array(l1.lengths_and_angles) + np.array(l2.lengths_and_angles)) / 2 avg_lattice = Lattice.from_lengths_and_angles(*avg_params) norm_length = (avg_lattice.volume / len(s_superset))**(1 / 3) mask_val = 1e20 * norm_length * self.stol all_d_2 = np.zeros([len(s_superset), len(s_superset)]) vec_matrix = np.zeros([len(s_superset), len(s_superset), 3]) #vectors from subset to superset #1st index subset, 2nd index superset vecs = pbc_shortest_vectors(avg_lattice, s_subset, s_superset) vec_matrix[:len(s_subset), :len(s_superset)] = vecs vec_matrix[mask] = mask_val d_2 = (np.sum(vecs**2, axis=-1)) all_d_2[:len(s_subset), :len(s_superset)] = d_2 all_d_2[mask] = mask_val lin = LinearAssignment(all_d_2) inds = np.arange(len(s_subset)) #shortest vectors from the subset to the superset shortest_vecs = vec_matrix[inds, lin.solution[:len(s_subset)], :] translation = np.average(shortest_vecs, axis=0) f_translation = avg_lattice.get_fractional_coords(translation) shortest_distances = np.sum((shortest_vecs - translation)**2, -1)**0.5 return shortest_distances / norm_length, f_translation * mult
def get_lattice(self, data, length_strings=("a", "b", "c"), angle_strings=("alpha", "beta", "gamma"), lattice_type=None): """ Generate the lattice from the provided lattice parameters. In the absence of all six lattice parameters, the crystal system and necessary parameters are parsed """ try: lengths = [str2float(data["_cell_length_" + i]) for i in length_strings] angles = [str2float(data["_cell_angle_" + i]) for i in angle_strings] if not lattice_type: return Lattice.from_lengths_and_angles(lengths, angles) else: return getattr(Lattice, lattice_type)(*(lengths + angles)) except KeyError: # Missing Key search for cell setting for lattice_lable in ["_symmetry_cell_setting", "_space_group_crystal_system"]: if data.data.get(lattice_lable): lattice_type = data.data.get(lattice_lable).lower() try: required_args = getargspec( getattr(Lattice, lattice_type)).args lengths = (l for l in length_strings if l in required_args) angles = (a for a in angle_strings if a in required_args) return self.get_lattice(data, lengths, angles, lattice_type=lattice_type) except AttributeError as exc: warnings.warn(exc) else: return None
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=["tet", "oct"], op_threshs=[0.3, 0.5], dl=0.4, doverlap=1.0, facmaxdl=1.01)
def test_static_methods(self): lengths_c = [3.840198, 3.84019885, 3.8401976] angles_c = [119.99998575, 90, 60.00000728] mat_c = [[3.840198, 0.000000, 0.0000], [1.920099, 3.325710, 0.000000], [0.000000, -2.217138, 3.135509]] #should give the lengths and angles above newlatt = Lattice(mat_c) (lengths, angles) = newlatt.lengths_and_angles for i in range(0, 3): self.assertAlmostEqual(lengths[i], lengths_c[i], 5, "Lengths incorrect!") self.assertAlmostEqual(angles[i], angles_c[i], 5, "Angles incorrect!") (lengths, angles) = \ Lattice.from_lengths_and_angles(lengths, angles).lengths_and_angles for i in range(0, 3): self.assertAlmostEqual(lengths[i], lengths_c[i], 5, "Lengths incorrect!") self.assertAlmostEqual(angles[i], angles_c[i], 5, "Angles incorrect!")
def test_interpolate_lattice(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]) l2 = Lattice.from_lengths_and_angles([3, 4, 4], [100, 100, 70]) struct2 = IStructure(l2, ["Si"] * 2, coords2) int_s = struct.interpolate(struct2, 2, interpolate_lattices=True) self.assertArrayAlmostEqual(struct.lattice.abc, int_s[0].lattice.abc) self.assertArrayAlmostEqual(struct.lattice.angles, int_s[0].lattice.angles) self.assertArrayAlmostEqual(struct2.lattice.abc, int_s[2].lattice.abc) self.assertArrayAlmostEqual(struct2.lattice.angles, int_s[2].lattice.angles) int_angles = [110.3976469, 94.5359731, 64.5165856] self.assertArrayAlmostEqual(int_angles, int_s[1].lattice.angles) # Assert that volume is monotonic self.assertTrue(struct2.lattice.volume >= int_s[1].lattice.volume) self.assertTrue(int_s[1].lattice.volume >= struct.lattice.volume)
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 prev_threshold = coord.LOOP_THRESHOLD coord.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.LOOP_THRESHOLD = prev_threshold
def from_string(string): """ Reads a string representation to a ZeoCssr object. Args: string: A string representation of a ZeoCSSR. Returns: ZeoCssr object. """ lines = string.split("\n") toks = lines[0].split() lengths = map(float, toks) toks = lines[1].split() angles = map(float, toks[0:3]) # Zeo++ takes x-axis along a and pymatgen takes z-axis along c a = lengths.pop(-1) lengths.insert(0, a) alpha = angles.pop(-1) angles.insert(0, alpha) latt = Lattice.from_lengths_and_angles(lengths, angles) sp = [] coords = [] chrg = [] for l in lines[4:]: m = re.match( "\d+\s+(\w+)\s+([0-9\-\.]+)\s+([0-9\-\.]+)\s+" + "([0-9\-\.]+)\s+(?:0\s+){8}([0-9\-\.]+)", l.strip()) if m: sp.append(m.group(1)) #coords.append([float(m.group(i)) for i in xrange(2, 5)]) # Zeo++ takes x-axis along a and pymatgen takes z-axis along c coords.append([float(m.group(i)) for i in [3, 4, 2]]) chrg.append(m.group(5)) return ZeoCssr( Structure(latt, sp, coords, site_properties={'charge': chrg}))
def from_string(string): """ Reads a string representation to a ZeoCssr object. Args: string: A string representation of a ZeoCSSR. Returns: ZeoCssr object. """ lines = string.split("\n") toks = lines[0].split() lengths = map(float, toks) toks = lines[1].split() angles = map(float, toks[0:3]) # Zeo++ takes x-axis along a and pymatgen takes z-axis along c a = lengths.pop(-1) lengths.insert(0, a) alpha = angles.pop(-1) angles.insert(0, alpha) latt = Lattice.from_lengths_and_angles(lengths, angles) sp = [] coords = [] chrg = [] for l in lines[4:]: m = re.match("\d+\s+(\w+)\s+([0-9\-\.]+)\s+([0-9\-\.]+)\s+" + "([0-9\-\.]+)\s+(?:0\s+){8}([0-9\-\.]+)", l.strip()) if m: sp.append(m.group(1)) #coords.append([float(m.group(i)) for i in xrange(2, 5)]) # Zeo++ takes x-axis along a and pymatgen takes z-axis along c coords.append([float(m.group(i)) for i in [3, 4, 2]]) chrg.append(m.group(5)) return ZeoCssr( Structure(latt, sp, coords, site_properties={'charge': chrg}) )
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.matrix(p_tot) lattices = [s.lattice for s in self.structures] volumes = np.matrix([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.matrix(volumes) units *= e_to_muC * cm2_to_A2 # Convert the total polarization p_tot = np.multiply(units.T, 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.A1[i], a) d_structs = [] sites = [] for i in range(L): l = lattices[i] frac_coord = np.divide(np.matrix(p_tot[i]), np.matrix([l.a, l.b, l.c])) d = PolarizationLattice(l, ["C"], [np.matrix(frac_coord).A1]) 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.matrix([l.a, l.b, l.c])).A1) adjust_pol = np.matrix(adjust_pol) return adjust_pol
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)
def get_voronoi_percolate_nodes(structure, rad_dict=None, probe_rad=0.1): """ This function is used to get voronoi percolate nodes. Different from get_percolated_node_edge, the vor_accessible_node_struct returned by this one does not contain neighbor information. So if you need neighboring information of each accessible node, you may use get_percolated_node_edge function. Args: structure (Structure): Structure object for analysis rad_dict (dict): optional, dictionary of radii of elements in structures. 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: Returns: vor_node_struct, vor_accessible_node_struct, vor_edgecenter_struct, vor_facecenter_struct (Structure): """ with ScratchDir('.'): name = "temp_zeo2" 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("{} {}\n".format(el, rad_dict[el].real)) 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(name + '_voro.xyz').molecule voro_accessible_node_mol = ZeoVoronoiXYZ.from_file( name + '_voro_accessible.xyz').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=False, site_properties={"voronoi_radius": prop}) # percolate node struct species = ["X"] * len(voro_accessible_node_mol.sites) coords = [] prop = [] for site in voro_accessible_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_accessible_node_struct = Structure( lattice, species, coords, coords_are_cartesian=True, to_unit_cell=False, 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_origin = Structure( lattice, species, rot_face_centers, coords_are_cartesian=True, to_unit_cell=False, site_properties={"voronoi_radius": prop}) vor_facecenter_struct = Structure.from_sites( list(set([i for i in vor_facecenter_struct_origin]))) species = ["X"] * len(rot_edge_centers) prop = [0.0] * len(rot_edge_centers) # Vor radius not evaluated for fc vor_edgecenter_struct_origin = Structure( lattice, species, rot_edge_centers, coords_are_cartesian=True, to_unit_cell=False, site_properties={"voronoi_radius": prop}) vor_edgecenter_struct = Structure.from_sites( list(set([i for i in vor_edgecenter_struct_origin]))) return vor_node_struct, vor_accessible_node_struct, vor_edgecenter_struct, vor_facecenter_struct
def _get_structure(self, data, primitive): """ Generate structure from part of the cif. """ lengths = [float_from_str(data["_cell_length_" + i]) for i in ["a", "b", "c"]] angles = [float_from_str(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 = parse_symmetry_operations(sympos) def parse_symbol(sym): m = re.search("([A-Z][a-z]*)", sym) if m: return m.group(1) return "" try: oxi_states = {data["_atom_type_symbol"][i]: float_from_str(data["_atom_type_oxidation_number"][i]) for i in xrange(len(data["_atom_type_symbol"]))} except (ValueError, KeyError): oxi_states = None coord_to_species = OrderedDict() for i in xrange(len(data["_atom_site_type_symbol"])): symbol = parse_symbol(data["_atom_site_type_symbol"][i]) if oxi_states is not None: el = Specie(symbol, oxi_states[data["_atom_site_type_symbol"][i]]) else: el = Element(symbol) x = float_from_str(data["_atom_site_fract_x"][i]) y = float_from_str(data["_atom_site_fract_y"][i]) z = float_from_str(data["_atom_site_fract_z"][i]) try: occu = float_from_str(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 species.iteritems(): species[key] = value / totaloccu struct = Structure(lattice, allspecies, allcoords) if primitive: struct = struct.get_primitive_structure() return struct.get_sorted_structure()
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(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 structure(self): # Get lattice vectors if "lattice_cart" in self.blocks: lattice_cart = self.blocks["lattice_cart"].values if lattice_cart[0][0].lower() in ("ang", "nm", "cm", "m", "bohr", "a0"): unit = lattice_cart[0][0].lower() vectors = lattice_cart[1:] else: unit = "ang" vectors = lattice_cart vectors = np.array([list(map(float, row)) for row in vectors]) if vectors.shape != (3, 3): raise ValueError("lattice_cart should contain a 3x3 matrix") vectors *= to_angstrom[unit] lattice = Lattice(vectors) elif "lattice_abc" in self.blocks: lattice_abc = self.blocks["lattice_abc"].values if lattice_abc[0][0].lower() in ("ang", "nm", "cm", "m", "bohr", "a0"): unit = lattice_abc[0][0].lower() lengths_and_angles = lattice_abc[1:] else: unit = "ang" lengths_and_angles = lattice_abc[1:] if len(lengths_and_angles) != 2: raise ValueError("lattice_abc should have two rows") lengths_and_angles = [ list(map(float, row)) for row in lengths_and_angles ] lengths_and_angles[0] = [ x * to_angstrom[unit] for x in lengths_and_angles[0] ] lattice = Lattice.from_lengths_and_angles(*lengths_and_angles) else: raise ValueError("Couldn't find a lattice in cell file") if "positions_frac" in self.blocks: elements_coords = [(row[0], list(map(float, row[1:4]))) for row in self.blocks["positions_frac"].values] elements, coords = zip(*elements_coords) return Structure(lattice, elements, coords, coords_are_cartesian=False) elif "positions_abs" in self.blocks: positions_abs = self.blocks["positions_abs"].values if positions_abs[0][0].lower() in ("ang", "nm", "cm", "m", "bohr", "a0"): unit = positions_abs[0][0].lower() positions_abs = positions_abs[1:] else: unit = "ang" elements_coords = [(row[0], list(map(float, row[1:4]))) for row in positions_abs] elements, coords = zip(*elements_coords) return Structure(lattice, elements, coords, coords_are_cartesian=True) else: raise ValueError("Couldn't find any atom positions in cell file")
def _get_structure(self, data, primitive): """ Generate structure from part of the cif. """ spacegroup = data['_symmetry_space_group_name_H-M'] if len(spacegroup) == 0: latt_type = "P" else: latt_type = spacegroup[0] lengths = [float_from_string(data['_cell_length_' + i]) for i in ['a', 'b', 'c']] angles = [float_from_string(data['_cell_angle_' + i]) for i in ['alpha', 'beta', 'gamma']] lattice = Lattice.from_lengths_and_angles(lengths, angles) primlattice = lattice.get_primitive_lattice(latt_type) try: sympos = data['_symmetry_equiv_pos_as_xyz'] except: try: sympos = data['_symmetry_equiv_pos_as_xyz_'] except: warnings.warn("No _symmetry_equiv_pos_as_xyz type key found. Defaulting to P1.") sympos def parse_symbol(sym): m = re.search("([A-Z][a-z]*)", sym) if m: return m.group(1) return '' #oxi_states = None try: oxi_states = dict() for i in xrange(len(data['_atom_type_symbol'])): oxi_states[data['_atom_type_symbol'][i]] = float_from_string(data['_atom_type_oxidation_number'][i]) except: oxi_states = None coord_to_species = OrderedDict() for i in xrange(len(data['_atom_site_type_symbol'])): symbol = parse_symbol(data['_atom_site_type_symbol'][i]) if oxi_states != None: el = Specie(symbol, oxi_states[data['_atom_site_type_symbol'][i]]) else: el = Element(symbol) x = float_from_string(data['_atom_site_fract_x'][i]) y = float_from_string(data['_atom_site_fract_y'][i]) z = float_from_string(data['_atom_site_fract_z'][i]) try: occu = float_from_string(data['_atom_site_occupancy'][i]) except: occu = 1 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 = list() allcoords = list() for coord, species in coord_to_species.items(): coords = self._unique_coords(coord, sympos, primitive, lattice, primlattice) allcoords.extend(coords) allspecies.extend(len(coords) * [species]) if primitive: return Structure(primlattice, allspecies, allcoords).get_sorted_structure() else: return Structure(lattice, allspecies, allcoords).get_sorted_structure()
def get_high_accuracy_voronoi_nodes_alt(structure, rad_dict, probe_rad=0.1): """ Function to replace high_accuracy_voronoi_nodes function. In testing mode. 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. 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 >>fp, "{} {}".format(el, rad_dict[el].real) atmnet = AtomNetwork.read_from_CSSR( zeo_inp_filename, rad_flag=rad_flag, rad_file=rad_file) vornet, voronoi_face_centers = atmnet.perform_voronoi_decomposition() red_ha_vornet = \ generate_simplified_highaccuracy_voronoi_network(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) voronoi_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 voronoi_face_centers] species = ["X"] * len(rot_face_centers) # Voronoi radius not evaluated for fc. Fix in future versions prop = [0.0] * len(rot_face_centers) voronoi_facecenter_struct = Structure( lattice, species, rot_face_centers, coords_are_cartesian=True, to_unit_cell=True, site_properties={"voronoi_radius": prop}) return voronoi_node_struct, voronoi_facecenter_struct
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 range(9, 9 + natoms): atomic_symbols.append(lines[i].split()[2]) # read the atomic coordinates coords = [] for i in range(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 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_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("{} {}\n".format(el, rad_dict[el].real)) 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_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}) # 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
def get_voronoi_node_edge(structure, rad_dict, write_nt2_file=False): """ This function obtain the voronoi nodes and the edge information with 0 probe radius. It will contain neighboring information of each nodes. !!!Notice the nt2 file change the xyz to zxy. Thus the result need rotate. What a strange setting!!! Args: structure: orginal Structure rad_dict: {'Na':10,...} Returns: Structure: the voronoi node structure. For each site, there are voronoi_radius, neighbor_atoms, neighbor_nodes in the properties. The neighbor_nodes in the format : [[node_No,channel_size,node_image]...] node image is list of int, not array The neighbor_atoms in the format: [1,2,3,4] the number is the sequence number of sites in structure. So take care of the structure, don't do any change on the sequence order. """ with ScratchDir('.'): name = "temp_zeo3" cssr_file = name + ".cssr" rad_file = name + ".rad" mass_file = name + ".mass" out_file = name + ".nt2" cssr = ZeoCssr(structure) with open(cssr_file, 'w+') as fp: fp.write(str(cssr)) # CifWriter(structure).write_file(cif_file) the xyz need to rotate to zxy, so just use existing ZeoCssr # rad_file with open(rad_file, 'w+') as fp: for el in rad_dict.keys(): fp.write("{} {}\n".format(el, rad_dict[el].real)) # mass_file struct_element_set = set(structure.composition.elements) with open(mass_file, 'w+') as fp: for el in struct_element_set: fp.write("{} {}\n".format(el, float(el.atomic_mass))) bashcmd = "network -r {} -mass {} -nt2 {} {}".format( rad_file, mass_file, out_file, cssr_file) process = subprocess.Popen(bashcmd.split(), stdout=subprocess.PIPE) zeo_plus_plus_output = process.communicate()[0] with open(out_file, 'r') as fp: nt2_file = fp.readlines() if write_nt2_file != False: with open(write_nt2_file, 'w+') as fp: for i in nt2_file: fp.write(i) node_info_string_list = nt2_file[1:nt2_file.index('\n')] edge_info_string_list = nt2_file[nt2_file.index('\n') + 2:] # node info string to Structure object species = ["X"] * len(node_info_string_list) coords = [] voronoi_radius = [] neighbor_atoms_num = [ ] # the four closest atoms, the number corresponding to the sequence number in structure neighbor_nodes = [list() for _ in range(len(node_info_string_list))] # the connected neighbor voronoi nodes, [node_No,channel_size,node_image], node image: [-1,0,0] # means the actual connected neighbor will have a transition in -a direction. # final_frac_coords = node_frac_coords+node_image for site in node_info_string_list: coords_temp = map(float, site.split()[1:4]) coords.append([coords_temp[1], coords_temp[2], coords_temp[0]]) voronoi_radius.append(float(site.split()[4])) neighbor_atoms_num.append(map(int, site.split()[5:])) for neighbor in edge_info_string_list: valid_info = neighbor.split() valid_info.pop(1) # there is a arrow in the output string image_temp = map(int, valid_info[3:6]) valid_info = [ int(valid_info[0]), int(valid_info[1]), float(valid_info[2]), [image_temp[1], image_temp[2], image_temp[0]] ] neighbor_nodes[valid_info[0]].append(copy.deepcopy(valid_info[1:])) 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=False, site_properties={ "voronoi_radius": voronoi_radius, "neighbor_atoms": neighbor_atoms_num, "neighbor_nodes": neighbor_nodes }) # notice, to_unit_cell must be False, # or the image of neighbor_nodes need change return vor_node_struct
def structure(self): # Get lattice vectors if 'lattice_cart' in self.blocks: lattice_cart = self.blocks['lattice_cart'].values if lattice_cart[0][0].lower() in ('ang', 'nm', 'cm', 'm', 'bohr', 'a0'): unit = lattice_cart[0][0].lower() vectors = lattice_cart[1:] else: unit = 'ang' vectors = lattice_cart vectors = np.array([list(map(float, row)) for row in vectors]) if vectors.shape != (3, 3): raise ValueError('lattice_cart should contain a 3x3 matrix') vectors *= to_angstrom[unit] lattice = Lattice(vectors) elif 'lattice_abc' in self.blocks: lattice_abc = self.blocks['lattice_abc'].values if lattice_abc[0][0].lower() in ('ang', 'nm', 'cm', 'm', 'bohr', 'a0'): unit = lattice_abc[0][0].lower() lengths_and_angles = lattice_abc[1:] else: unit = 'ang' lengths_and_angles = lattice_abc[1:] if len(lengths_and_angles) != 2: raise ValueError('lattice_abc should have two rows') lengths_and_angles = [ list(map(float, row)) for row in lengths_and_angles ] lengths_and_angles[0] = [ x * to_angstrom[unit] for x in lengths_and_angles[0] ] lattice = Lattice.from_lengths_and_angles(*lengths_and_angles) else: raise ValueError("Couldn't find a lattice in cell file") if 'positions_frac' in self.blocks: elements_coords = [(row[0], list(map(float, row[1:4]))) for row in self.blocks['positions_frac'].values] elements, coords = zip(*elements_coords) return Structure(lattice, elements, coords, coords_are_cartesian=False) elif 'positions_abs' in self.blocks: positions_abs = self.blocks['positions_abs'].values if positions_abs[0][0].lower() in ('ang', 'nm', 'cm', 'm', 'bohr', 'a0'): unit = positions_abs[0][0].lower() positions_abs = positions_abs[1:] else: unit = 'ang' elements_coords = [(row[0], list(map(float, row[1:4]))) for row in positions_abs] elements, coords = zip(*elements_coords) return Structure(lattice, elements, coords, coords_are_cartesian=True) else: raise ValueError("Couldn't find any atom positions in cell file")
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 get_high_accuracy_voronoi_nodes_alt(structure, rad_dict, probe_rad=0.1): """ Function to replace high_accuracy_voronoi_nodes function. In testing mode. 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. 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 >> fp, "{} {}".format(el, rad_dict[el].real) atmnet = AtomNetwork.read_from_CSSR(zeo_inp_filename, rad_flag=rad_flag, rad_file=rad_file) vornet, voronoi_face_centers = atmnet.perform_voronoi_decomposition() red_ha_vornet = \ generate_simplified_highaccuracy_voronoi_network(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) voronoi_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 voronoi_face_centers] species = ["X"] * len(rot_face_centers) # Voronoi radius not evaluated for fc. Fix in future versions prop = [0.0] * len(rot_face_centers) voronoi_facecenter_struct = Structure( lattice, species, rot_face_centers, coords_are_cartesian=True, to_unit_cell=True, site_properties={"voronoi_radius": prop}) return voronoi_node_struct, voronoi_facecenter_struct
def get_same_branch_polarization_data(self, convert_to_muC_per_cm2=True, all_in_polar=True): """ 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. Note, using convert_to_muC_per_cm2=True and all_in_polar=True calculates the "proper polarization" (meaning the change in polarization does not depend on the choice of polarization branch) while convert_to_muC_per_cm2=True and all_in_polar=False calculates the "improper polarization" (meaning the change in polarization does depend on the choice of branch). As one might guess from the names. We recommend calculating the "proper polarization". convert_to_muC_per_cm2: convert polarization from electron * Angstroms to microCoulomb per centimeter**2 all_in_polar: convert polarization to be in polar (final structure) polarization lattice """ 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) 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: # 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) # convert polarizations to polar lattice elif convert_to_muC_per_cm2 and all_in_polar: abc = [lattice.abc for lattice in lattices] abc = np.array(abc) # [N, 3] p_tot /= abc # e * Angstroms to e p_tot *= abc[-1] / volumes[-1] * e_to_muC * cm2_to_A2 # to muC / cm^2 for i in range(L): lattice = lattices[-1] # Use polar lattice l, a = lattice.lengths_and_angles lattices[i] = Lattice.from_lengths_and_angles( np.array(l) * units.ravel()[-1], a) # Use polar units (volume) 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 get_same_branch_polarization_data(self, convert_to_muC_per_cm2=True, all_in_polar=True): """ 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. Note, using convert_to_muC_per_cm2=True and all_in_polar=True calculates the "proper polarization" (meaning the change in polarization does not depend on the choice of polarization branch) while convert_to_muC_per_cm2=True and all_in_polar=False calculates the "improper polarization" (meaning the change in polarization does depend on the choice of branch). As one might guess from the names. We recommend calculating the "proper polarization". convert_to_muC_per_cm2: convert polarization from electron * Angstroms to microCoulomb per centimeter**2 all_in_polar: convert polarization to be in polar (final structure) polarization lattice """ 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) 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: # 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) # convert polarizations to polar lattice elif convert_to_muC_per_cm2 and all_in_polar: abc = [lattice.abc for lattice in lattices] abc = np.array(abc) # [N, 3] p_tot /= abc # e * Angstroms to e p_tot *= abc[-1] / volumes[ -1] * e_to_muC * cm2_to_A2 # to muC / cm^2 for i in range(L): lattice = lattices[-1] # Use polar lattice l, a = lattice.lengths_and_angles lattices[i] = Lattice.from_lengths_and_angles( np.array(l) * units.ravel()[-1], a) # Use polar units (volume) 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 _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 = parse_symmetry_operations(sympos) def parse_symbol(sym): m = re.search("([A-Z][a-z]*)", sym) if m: return m.group(1) return "" try: oxi_states = { data["_atom_type_symbol"][i]: str2float(data["_atom_type_oxidation_number"][i]) for i in xrange(len(data["_atom_type_symbol"])) } except (ValueError, KeyError): oxi_states = None coord_to_species = OrderedDict() for i in xrange(len(data["_atom_site_type_symbol"])): symbol = parse_symbol(data["_atom_site_type_symbol"][i]) if oxi_states is not None: el = Specie(symbol, oxi_states[data["_atom_site_type_symbol"][i]]) 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 species.iteritems(): species[key] = value / totaloccu struct = Structure(lattice, allspecies, allcoords) if primitive: struct = struct.get_primitive_structure().get_reduced_structure() return struct.get_sorted_structure()