def test_ordered_primitive_to_disordered_supercell(self): sm_atoms = StructureMatcher(ltol=0.2, stol=0.3, angle_tol=5, primitive_cell=False, scale=True, attempt_supercell=True, allow_subset=True, supercell_size = 'num_atoms', comparator=OrderDisorderElementComparator()) sm_sites = StructureMatcher(ltol=0.2, stol=0.3, angle_tol=5, primitive_cell=False, scale=True, attempt_supercell=True, allow_subset=True, supercell_size = 'num_sites', comparator=OrderDisorderElementComparator()) lp = Lattice.orthorhombic(10, 20, 30) pcoords = [[0, 0, 0], [0.5, 0.5, 0.5]] ls = Lattice.orthorhombic(20,20,30) scoords = [[0, 0, 0], [0.5, 0, 0], [0.25, 0.5, 0.5], [0.75, 0.5, 0.5]] s1 = Structure(lp, ['Na', 'Cl'], pcoords) s2 = Structure(ls, [{'Na':0.5}, {'Na':0.5}, {'Cl':0.5}, {'Cl':0.5}], scoords) self.assertTrue(sm_sites.fit(s1, s2)) self.assertFalse(sm_atoms.fit(s1, s2))
def test_disordered_primitive_to_ordered_supercell(self): sm_atoms = StructureMatcher( ltol=0.2, stol=0.3, angle_tol=5, primitive_cell=False, scale=True, attempt_supercell=True, allow_subset=True, supercell_size='num_atoms', comparator=OrderDisorderElementComparator()) sm_sites = StructureMatcher( ltol=0.2, stol=0.3, angle_tol=5, primitive_cell=False, scale=True, attempt_supercell=True, allow_subset=True, supercell_size='num_sites', comparator=OrderDisorderElementComparator()) lp = Lattice.orthorhombic(10, 20, 30) pcoords = [[0, 0, 0], [0.5, 0.5, 0.5]] ls = Lattice.orthorhombic(20, 20, 30) scoords = [[0, 0, 0], [0.75, 0.5, 0.5]] prim = Structure(lp, [{'Na': 0.5}, {'Cl': 0.5}], pcoords) supercell = Structure(ls, ['Na', 'Cl'], scoords) supercell.make_supercell([[-1, 1, 0], [0, 1, 1], [1, 0, 0]]) self.assertFalse(sm_sites.fit(prim, supercell)) self.assertTrue(sm_atoms.fit(prim, supercell)) self.assertRaises(ValueError, sm_atoms.get_s2_like_s1, prim, supercell) self.assertEqual(len(sm_atoms.get_s2_like_s1(supercell, prim)), 4)
def get_framework_rms_plot(self, plt=None, granularity=200, matching_s=None): """ Get the plot of rms framework displacement vs time. Useful for checking for melting, especially if framework atoms can move via paddle-wheel or similar mechanism (which would show up in max framework displacement but doesn't constitute melting). Args: plt (matplotlib.pyplot): If plt is supplied, changes will be made to an existing plot. Otherwise, a new plot will be created. granularity (int): Number of structures to match matching_s (Structure): Optionally match to a disordered structure instead of the first structure in the analyzer. Required when a secondary mobile ion is present. Notes: The method doesn't apply to NPT-AIMD simulation analysis. """ from pymatgen.util.plotting import pretty_plot if self.lattices is not None and len(self.lattices) > 1: warnings.warn( "Note the method doesn't apply to NPT-AIMD simulation analysis!" ) plt = pretty_plot(12, 8, plt=plt) step = (self.corrected_displacements.shape[1] - 1) // (granularity - 1) f = (matching_s or self.structure).copy() f.remove_species([self.specie]) sm = StructureMatcher( primitive_cell=False, stol=0.6, comparator=OrderDisorderElementComparator(), allow_subset=True, ) rms = [] for s in self.get_drift_corrected_structures(step=step): s.remove_species([self.specie]) d = sm.get_rms_dist(f, s) if d: rms.append(d) else: rms.append((1, 1)) max_dt = (len(rms) - 1) * step * self.step_skip * self.time_step if max_dt > 100000: plot_dt = np.linspace(0, max_dt / 1000, len(rms)) unit = "ps" else: plot_dt = np.linspace(0, max_dt, len(rms)) unit = "fs" rms = np.array(rms) plt.plot(plot_dt, rms[:, 0], label="RMS") plt.plot(plot_dt, rms[:, 1], label="max") plt.legend(loc="best") plt.xlabel(f"Timestep ({unit})") plt.ylabel("normalized distance") plt.tight_layout() return plt
def get_framework_rms_plot(self, plt=None, granularity=200, matching_s=None): """ Get the plot of rms framework displacement vs time. Useful for checking for melting, especially if framework atoms can move via paddle-wheel or similar mechanism (which would show up in max framework displacement but doesn't constitute melting). Args: granularity (int): Number of structures to match matching_s (Structure): Optionally match to a disordered structure instead of the first structure in the analyzer. Required when a secondary mobile ion is present. """ from pymatgen.util.plotting_utils import get_publication_quality_plot plt = get_publication_quality_plot(12, 8, plt=plt) step = (self.corrected_displacements.shape[1] - 1) // (granularity - 1) f = (matching_s or self.structure).copy() f.remove_species([self.specie]) sm = StructureMatcher(primitive_cell=False, stol=0.6, comparator=OrderDisorderElementComparator(), allow_subset=True) rms = [] for s in self.get_drift_corrected_structures(step=step): s.remove_species([self.specie]) d = sm.get_rms_dist(f, s) if d: rms.append(d) else: rms.append((1, 1)) max_dt = (len(rms) - 1) * step * self.step_skip * self.time_step if max_dt > 100000: plot_dt = np.linspace(0, max_dt / 1000, len(rms)) unit = 'ps' else: plot_dt = np.linspace(0, max_dt, len(rms)) unit = 'fs' rms = np.array(rms) plt.plot(plot_dt, rms[:, 0], label='RMS') plt.plot(plot_dt, rms[:, 1], label='max') plt.legend(loc='best') plt.xlabel("Timestep ({})".format(unit)) plt.ylabel("normalized distance") plt.tight_layout() return plt
def _calculate_energies(self): energies = np.zeros((len(self.structure), len(self.structure))) dm = self.structure.distance_matrix non_dup_sites = [] blocked = [] for i, site in enumerate(self.structure): if i in blocked: continue non_dup_sites.append(site) blocked.extend(np.where(dm[i] < 0.001)[0]) cs = self.ce.supercell_from_structure( Structure.from_sites(non_dup_sites)) sm = StructureMatcher(primitive_cell=False, attempt_supercell=False, allow_subset=True, scale=True, comparator=OrderDisorderElementComparator()) aligned = sm.get_s2_like_s1(cs.supercell, self.structure) dists = aligned.lattice.get_all_distances(aligned.frac_coords, cs.supercell.frac_coords) lw_mapping = np.argmin(dists, axis=-1) bits = cs.bits exp_inds = np.where( np.sum(aligned.distance_matrix < 0.001, axis=-1) > 1)[0] for i in exp_inds: for j in exp_inds: if lw_mapping[i] == lw_mapping[j]: continue occu = np.copy(cs.nbits) occu[lw_mapping[i]] = bits[lw_mapping[i]].index( str(aligned[i].specie)) occu[lw_mapping[j]] = bits[lw_mapping[j]].index( str(aligned[j].specie)) energies[i][j] = np.dot(cs.corr_from_occupancy(occu), self.ecis) return energies
def occu_from_structure(self, structure, return_mapping=False): """ Calculates the correlation vector. Structure must be on this supercell """ #calculate mapping to supercell sm_no_sc = StructureMatcher(primitive_cell=False, attempt_supercell=False, allow_subset=True, comparator=OrderDisorderElementComparator(), supercell_size=self.cluster_expansion.supercell_size, scale=True, ltol=self.cluster_expansion.ltol, stol=self.cluster_expansion.stol, angle_tol=self.cluster_expansion.angle_tol) #print('sc:\n',self.supercell_matrix,\ # '\nstr:\n',self.cluster_expansion.supercell_matrix_from_structure(structure)) mapping = sm_no_sc.get_mapping(self.supercell, structure) if mapping is None: raise ValueError('Structure cannot be mapped to this supercell. Structure:{}\nSupercell:{}'.format(structure,self.supercell)) mapping = mapping.tolist() #cs.supercell[mapping] = structure occu = np.zeros(len(self.supercell), dtype=np.int) for i, bit in enumerate(self.bits): # print('i=',i) # print('bit=', bit) #rather than starting with all vacancies and looping #only over mapping, explicitly loop over everything to #catch vacancies on improper sites if i in mapping: sp = str(structure[mapping.index(i)].specie) else: sp = 'Vacancy' occu[i] = bit.index(sp) if not return_mapping: return occu else: return occu, mapping
def __init__(self, structure, expansion_structure, symops, clusters, \ sm_type='pmg_sm', ltol=0.2, stol=0.1, angle_tol=5,\ supercell_size='num_sites', use_ewald=False, use_inv_r=False, eta=None, basis = '01'): """ Args: structure: disordered structure to build a cluster expansion for. Typically the primitive cell radii: dict of {cluster_size: max_radius}. Radii should be strictly decreasing. Typically something like {2:5, 3:4} sm_type: The structure matcher type that you wish to use in structure matching. Can choose from pymatgen default (pmg_sm), anion framework (an_frame) ltol, stol, angle_tol, supercell_size: parameters to pass through to the StructureMatcher, when sm_type == 'pmg_sm' or 'an_frame' Structures that don't match to the primitive cell under these tolerances won't be included in the expansion. Easiest option for supercell_size is usually to use a species that has a constant amount per formula unit. use_ewald: whether to calculate the ewald energy of each structure and use it as a feature. Typically a good idea for ionic materials. use_inv_r: experimental feature that allows fitting to arbitrary 1/r interactions between specie-site combinations. eta: parameter to override the EwaldSummation default eta. Usually only necessary if use_inv_r=True basis: Basis to use in cluster expansion. Currently can be 'ortho' or '01', plan to add 'chebyshev'. """ if use_inv_r and eta is None: warn("Be careful, you might need to change eta to get properly " "converged electrostatic energies. This isn't well tested") self.structure = structure self.expansion_structure = expansion_structure self.symops = symops # test that all the found symmetry operations map back to the input structure # otherwise you can get weird subset/superset bugs fc = self.structure.frac_coords for op in self.symops: if not is_coord_subset_pbc(op.operate_multi(fc), fc, SITE_TOL): raise SYMMETRY_ERROR self.supercell_size = supercell_size self.use_ewald = use_ewald self.eta = eta self.use_inv_r = use_inv_r self.sm_type=sm_type self.stol = stol self.ltol = ltol #self.vor_tol = vor_tol self.basis = basis if self.sm_type == 'pmg_sm' or self.sm_type == 'an_frame': self.angle_tol = angle_tol self.sm = StructureMatcher(primitive_cell=False, attempt_supercell=True, allow_subset=True, scale=True, supercell_size=self.supercell_size, comparator=OrderDisorderElementComparator(), stol=self.stol, ltol=self.ltol, angle_tol=self.angle_tol) # elif self.sm_type == 'an_dmap': # print("Warning: Delaunay matcher only applicable for close packed anion framework!") # try: # from delaunay_matcher import DelaunayMatcher # self.sm = DelauneyMatcher() # # At leaset three methods are required in Delauney Matcher: match, mapping and supercell matrix finding. # except: # pass # I abandoned delaunay because it is not stable with respect to distortion. else: raise ValueError('Structure matcher not implemented!') self.clusters = clusters # assign the cluster ids n_clusters = 1 n_bit_orderings = 1 n_sclusters = 1 for k in sorted(self.clusters.keys()): for y in self.clusters[k]: n_sclusters, n_bit_orderings, n_clusters = y.assign_ids(n_sclusters, n_bit_orderings, n_clusters) self.n_sclusters = n_sclusters self.n_clusters = n_clusters self.n_bit_orderings = n_bit_orderings self._supercells = {}