def get_boxed_molecule(input_structure, box_size): """ Return a boxed molecule. Args: input_structure(Molecule/Structure): either Molecule of Structure object box_size (list): [[x_min, x_max], [y_min, y_max], [z_min, z_max]] Returns: Structure """ box_lengths = [min_max[1] - min_max[0] for min_max in box_size] # be defensive about the box size if isinstance(input_structure, Molecule): boxed_molecule = input_structure.get_boxed_structure(*box_lengths) elif isinstance(input_structure, Structure): max_length = max(input_structure.lattice.abc) max_box_size = max(box_lengths) boxed_molecule = Molecule.from_sites(input_structure.sites) if max_length < max_box_size: boxed_molecule = \ boxed_molecule.get_boxed_structure(*box_lengths) else: boxed_molecule = \ boxed_molecule.get_boxed_structure(max_length, max_length, max_length) else: raise ValueError("molecule must be an object of Molecule or " "Structure ") return boxed_molecule
def generate_Si_cluster(): from pymatgen.io.xyz import XYZ coords = [[0, 0, 0], [0.75, 0.5, 0.75]] lattice = Lattice.from_parameters(a=3.84, b=3.84, c=3.84, alpha=120, beta=90, gamma=60) struct = Structure(lattice, ['Si', 'Si'], coords) struct.make_supercell([2, 2, 2]) # Creating molecule for testing mol = Molecule.from_sites(struct) XYZ(mol).write_file(os.path.join(test_dir, "Si_cluster.xyz")) # Rorate the whole molecule mol_rotated = mol.copy() rotate(mol_rotated, seed=42) XYZ(mol_rotated).write_file(os.path.join(test_dir, "Si_cluster_rotated.xyz")) # Perturbing the atom positions mol_perturbed = mol.copy() perturb(mol_perturbed, 0.3, seed=42) XYZ(mol_perturbed).write_file(os.path.join(test_dir, "Si_cluster_perturbed.xyz")) # Permuting the order of the atoms mol_permuted = mol.copy() permute(mol_permuted, seed=42) XYZ(mol_permuted).write_file(os.path.join(test_dir, "Si_cluster_permuted.xyz")) # All-in-one mol2 = mol.copy() rotate(mol2, seed=42) perturb(mol2, 0.3, seed=42) permute(mol2, seed=42) XYZ(mol2).write_file(os.path.join(test_dir, "Si_cluster_2.xyz"))
def get_m_graph_from_site_data(s_data): mol = Molecule.from_sites([Site.from_dict(isite) for isite in s_data['local_graph']['sites']]) mg = MoleculeGraph.with_empty_graph(mol) for i in range(1, len(mg)): mg.add_edge(0, i) return mg
def unwrap_and_squeeze(pstructure: Structure): """ after unwrapping, the mols can be far away from each other, this tries to translate them s.t. they stay together :param pstructure: :return: """ mols, unwrap_structure, psiteblocks = PBCparser.unwrap(pstructure) if len(mols) > 1: refpoint = mols[0].center_of_mass refpoint = unwrap_structure.lattice.get_fractional_coords(refpoint) for i in range(1, len(mols)): varmol = mols[i] varpoint = varmol.center_of_mass varpoint = unwrap_structure.lattice.get_fractional_coords(varpoint) distance, fctrans = PBCparser.get_dist_and_trans(unwrap_structure.lattice, refpoint, varpoint) for j in range(len(psiteblocks[i])): psiteblocks[i][j]._frac_coords += fctrans squeeze_psites = [] squeeze_mols = [] pblock: [PeriodicSite] for pblock in psiteblocks: squeeze_block = [] for ps in pblock: squeeze_psites.append(ps) squeeze_block.append( Site(ps.species_string, ps.coords, properties=ps.properties)) # do we need deepcopy? mol = Molecule.from_sites(squeeze_block) squeeze_mols.append(mol) squeeze_unwrap_structure = Structure.from_sites(squeeze_psites) return squeeze_mols, squeeze_unwrap_structure, psiteblocks else: return mols, unwrap_structure, psiteblocks
def from_structure(cls, input_structure, box_size, set_charge=True, translate=True): """ Set LammpsData from the given structure. If the input structure is a Structure, it is converted to a molecule. TIf the molecule doesnt fit in the input box, the box size is updated based on the max and min site coordinates of the molecules. Args: input_structure (Molecule/Structure) box_size (list): [[x_min, x_max], [y_min, y_max], [z_min, z_max]] set_charge (bool): whether or not to set the charge field in Atoms. If true, the charge will be non-zero only if the input_structure has the "charge" site property set. translate (bool): if true move the molecule to the center of the new box(it that is required). Returns: LammpsData """ if isinstance(input_structure, Structure): input_structure = Molecule.from_sites(input_structure.sites) box_size = cls.check_box_size(input_structure, box_size, translate=translate) natoms, natom_types, atomic_masses_dict = cls.get_basic_system_info(input_structure.copy()) atoms_data = cls.get_atoms_data(input_structure, atomic_masses_dict, set_charge=set_charge) return cls(box_size, atomic_masses_dict.values(), atoms_data)
def get_scene(data, site_index): site_data = data['site_data'][site_index] mol = Molecule.from_sites([Site.from_dict(isite) for isite in site_data['local_graph']['sites']]) scene = get_m_graph_from_mol(mol).get_scene() scene.name = "ref-site" return scene
def input_gen_gauss(self, charge=None, spin_multiplicity=None, title=None, functional='HF', basis_set='6-31G(d)', route_parameters=SUGGESTED_route_parameters, input_parameters=None, link0_parameters=None, dieze_tag='#P', gen_basis=None): ginobj_mono_a = GaussianInput(self.mono_a, charge, spin_multiplicity, title, functional, basis_set, route_parameters, input_parameters, link0_parameters, dieze_tag, gen_basis) ginobj_mono_b = GaussianInput(self.mono_a, charge, spin_multiplicity, title, functional, basis_set, route_parameters, input_parameters, link0_parameters, dieze_tag, gen_basis) dimer_sites = self.mono_a.sites + self.mono_b.sites dimer_pmgmol = Molecule.from_sites(dimer_sites) ginobj_dimer = GaussianInput(dimer_pmgmol, charge, spin_multiplicity, title, functional, basis_set, route_parameters, input_parameters, link0_parameters, dieze_tag, gen_basis) return ginobj_mono_a.to_string( cart_coords=True), ginobj_mono_b.to_string( cart_coords=True), ginobj_dimer.to_string(cart_coords=True)
def extract_dummies(self) -> Molecule: """ Creates and returns a pymatgen Molecule containing only the dummies from the original Fragment.atoms object Returns ------- Molecule The extracted dummy atoms """ dummies_idx = self.atoms.indices_from_symbol("X") dummies = Molecule.from_sites([self.atoms[i] for i in dummies_idx]) return dummies
def from_labeled_clean_pstructure(cls, pstructure: Structure, occu=1.0): unwrap_structure = deepcopy(pstructure) psites = list(unwrap_structure.sites) k = lambda x: x.properties['imol'] psites.sort(key=k) mols = [] for imol, group in groupby(psites, key=k): mols.append(Molecule.from_sites(list(group))) molconformers = [] for m in mols: try: mc = MolConformer.from_pmgmol(m) molconformers.append(mc) except ConformerError: warnings.warn( 'conformer init failed for one molecule in the cell') return cls(molconformers, unwrap_structure, occu)
def conver2zindo(pmgmol): """ this will replace elements that are not in Zindo_elements with elements in the same group """ if pmgmol is None: return None sites = pmgmol.sites newsites = [] for s in sites: if not s.species_string in Zindo_elements: group = Element(s.species_string).group samegroup_symbols = [symbol for symbol in Zindo_elements if Element(symbol).group == group] replacement = sorted(samegroup_symbols, key=lambda x: Element(x).number, reverse=True)[0] newsites.append(Site(replacement, s.coords)) else: newsites.append(Site(s.species_string, s.coords)) return Molecule.from_sites(newsites)
def fit(self, p: Molecule): """Order, rotate and transform `p` molecule according to the best match. Args: p: a `Molecule` object what will be matched with the target one. Returns: p_prime: Rotated and translated of the `p` `Molecule` object rmsd: Root-mean-square-deviation between `p_prime` and the `target` """ inds, U, V, rmsd = self.match(p) # Translate and rotate `mol1` unto `mol2` using Kabsch algorithm. p_prime = Molecule.from_sites([p[i] for i in inds]) for site in p_prime: site.coords = np.dot(site.coords, U) + V return p_prime, rmsd
def analyze(topology: Structure, skin: float = 5e-3) -> List[Molecule]: # containers for the output fragments = [] # we iterate over non-dummies dmat = topology.distance_matrix dummies = numpy.array(topology.indices_from_symbol("X")) not_dummies = numpy.array( [i for i in range(len(topology)) if i not in dummies]) # initialize and set tags tags = numpy.zeros(len(topology), dtype=int) tags[dummies] = dummies + 1 topology.add_site_property(property_name="tags", values=tags) # TODO : site properties # get the distances between centers and connections distances = dmat[not_dummies][:, dummies] coordinations = numpy.array(topology.atomic_numbers)[not_dummies] partitions = numpy.argsort(distances, axis=1) for center_idx, best_dummies in enumerate(partitions): coordination = coordinations[center_idx] if coordination < len(best_dummies): best_dummies = best_dummies[:coordination] cutoff = distances[center_idx][best_dummies].max() + skin # now extract the corresponding fragment fragment_center = topology.sites[not_dummies[center_idx]] fragment_sites = topology.get_neighbors(fragment_center, r=cutoff) # some topologies in the RCSR have a crazy size # we ignore them with the heat of a thousand suns assert len( fragment_sites) <= 12, "Fragment size larger than limit of 12." # store as molecule to use the point group analysis fragment = Molecule.from_sites( fragment_sites) #, charge=1, spin_multiplicity=1) # this is needed because X have no mass, leading to a # symmetrization error. fragment.replace_species({"X": "He"}) pg = PointGroupAnalyzer(fragment, tolerance=0.1) # from pymatgen.io.ase import AseAtomsAdaptor # view(AseAtomsAdaptor.get_atoms(fragment)) if pg.sch_symbol == "C1": raise ("NoSymm") fragment.replace_species({"He": "X"}) fragments.append(Fragment(atoms=fragment, symmetry=pg, name="slot")) return fragments
def fit(self, p: Molecule, ignore_warning=False): """Order, rotate and transform `p` molecule according to the best match. A `ValueError` will be raised when the total number of possible combinations become unfeasible (more than a million combinations). Args: p: a `Molecule` object what will be matched with the target one. ignore_warning: ignoring error when the number of combination is too large Returns: p_prime: Rotated and translated of the `p` `Molecule` object rmsd: Root-mean-square-deviation between `p_prime` and the `target` """ inds, U, V, rmsd = self.match(p, ignore_warning=ignore_warning) p_prime = Molecule.from_sites([p[i] for i in inds]) for site in p_prime: site.coords = np.dot(site.coords, U) + V return p_prime, rmsd
def position_mols(self): """ position the center of masses of the molecules wrt each other first movement is in the x direction """ new_mol = self.mols[0] mov_vec = np.array([1, 0, 0]) for i in range(len(self.mols) - 1): # cm1 = new_mol.center_of_mass new_cm = new_mol.center_of_mass # cm2 = self.mols[i+1].center_of_mass new_cm = new_cm + self.cm_dist[i] * mov_vec mov_vec = self.get_perp_vec(self.mol_vecs[i], mov_vec) mov_vec = mov_vec / np.linalg.norm(mov_vec) new_coords = self.mols[i + 1].cart_coords + new_cm self.mols[i + 1] = Molecule( self.mols[i + 1].species_and_occu, new_coords, charge=self.mols[i + 1]._charge, spin_multiplicity=self.mols[i + 1]._spin_multiplicity, site_properties=self.mols[i + 1].site_properties) new_mol = Molecule.from_sites(self.mols[i].sites + self.mols[i + 1].sites, validate_proximity=True)
def position_mols(self): """ position the center of masses of the molecules wrt each other first movement is in the x direction """ new_mol = self.mols[0] mov_vec = np.array([1, 0, 0]) for i in range(len(self.mols) - 1): # cm1 = new_mol.center_of_mass new_cm = new_mol.center_of_mass # cm2 = self.mols[i+1].center_of_mass new_cm = new_cm + self.cm_dist[i] * mov_vec mov_vec = self.get_perp_vec(self.mol_vecs[i], mov_vec) mov_vec = mov_vec / np.linalg.norm(mov_vec) new_coords = self.mols[i + 1].cart_coords + new_cm self.mols[i + 1] = Molecule( self.mols[i + 1].species_and_occu, new_coords, charge=self.mols[i + 1]._charge, spin_multiplicity=self.mols[i + 1]._spin_multiplicity, site_properties=self.mols[i + 1].site_properties) new_mol = Molecule.from_sites( self.mols[i].sites + self.mols[i + 1].sites, validate_proximity=True)
def get_closest_matched(ref_data, site_index, all_random_sites): """ Find the 10 most similar sites Args: ref_data: site_index: all_random_sites: Returns: """ ref_soap_vec = ref_data['site_data'][site_index]['soap_vec'] def similarity(random_site_data): return np.abs(1 - np.dot(random_site_data, ref_soap_vec) / np.dot(ref_soap_vec, ref_soap_vec)) all_sites_with_sim = [(isite, similarity(isite['soap_vec'])) for isite in all_random_sites] all_sites_with_sim.sort(key=lambda x: x[1]) matched_res = [] for itr, (site_info, proj) in enumerate(all_sites_with_sim[:3]): mol = Molecule.from_sites([Site.from_dict(isite) for isite in site_info['local_graph']['sites']]) mg = get_m_graph_from_mol(mol) scene = mg.get_scene() scene.name = f"matched-site-{itr}" matched_res.append(site_info["task_id"] + " " + f"{proj:0.4f}") matched_res.append(Simple3DScene( sceneSize=210, inletSize=150, inletPadding=0, axisView='SW', data=scene )) return matched_res
def __init__( self, start_monomer, s_head, s_tail, monomer, head, tail, end_monomer, e_head, e_tail, n_units, link_distance=1.0, linear_chain=False, ): """ Args: start_monomer (Molecule): Starting molecule s_head (int): starting atom index of the start_monomer molecule s_tail (int): tail atom index of the start_monomer monomer (Molecule): The monomer head (int): index of the atom in the monomer that forms the head tail (int): tail atom index. monomers will be connected from tail to head end_monomer (Molecule): Terminal molecule e_head (int): starting atom index of the end_monomer molecule e_tail (int): tail atom index of the end_monomer n_units (int): number of monomer units excluding the start and terminal molecules link_distance (float): distance between consecutive monomers linear_chain (bool): linear or random walk polymer chain """ self.start = s_head self.end = s_tail self.monomer = monomer self.n_units = n_units self.link_distance = link_distance self.linear_chain = linear_chain # translate monomers so that head atom is at the origin start_monomer.translate_sites(range(len(start_monomer)), -monomer.cart_coords[s_head]) monomer.translate_sites(range(len(monomer)), -monomer.cart_coords[head]) end_monomer.translate_sites(range(len(end_monomer)), -monomer.cart_coords[e_head]) self.mon_vector = monomer.cart_coords[tail] - monomer.cart_coords[head] self.moves = { 1: [1, 0, 0], 2: [0, 1, 0], 3: [0, 0, 1], 4: [-1, 0, 0], 5: [0, -1, 0], 6: [0, 0, -1], } self.prev_move = 1 # places the start monomer at the beginning of the chain self.molecule = start_monomer.copy() self.length = 1 # create the chain self._create(self.monomer, self.mon_vector) # terminate the chain with the end_monomer self.n_units += 1 end_mon_vector = end_monomer.cart_coords[ e_tail] - end_monomer.cart_coords[e_head] self._create(end_monomer, end_mon_vector) self.molecule = Molecule.from_sites(self.molecule.sites)
def get_chemenv_analysis(struct, distance_cutoff, angle_cutoff): if not struct: raise PreventUpdate struct = self.from_data(struct) kwargs = self.reconstruct_kwargs_from_state( callback_context.inputs) distance_cutoff = kwargs["distance_cutoff"] angle_cutoff = kwargs["angle_cutoff"] # TODO: remove these brittle guard statements, figure out more robust way to handle multiple input types if isinstance(struct, StructureGraph): struct = struct.structure def get_valences(struct): valences = [ getattr(site.specie, "oxi_state", None) for site in struct ] valences = [v for v in valences if v is not None] if len(valences) == len(struct): return valences else: return "undefined" # decide which indices to present to user sga = SpacegroupAnalyzer(struct) symm_struct = sga.get_symmetrized_structure() inequivalent_indices = [ indices[0] for indices in symm_struct.equivalent_indices ] wyckoffs = symm_struct.wyckoff_symbols lgf = LocalGeometryFinder() lgf.setup_structure(structure=struct) se = lgf.compute_structure_environments( maximum_distance_factor=distance_cutoff + 0.01, only_indices=inequivalent_indices, valences=get_valences(struct), ) strategy = SimplestChemenvStrategy(distance_cutoff=distance_cutoff, angle_cutoff=angle_cutoff) lse = LightStructureEnvironments.from_structure_environments( strategy=strategy, structure_environments=se) all_ce = AllCoordinationGeometries() envs = [] unknown_sites = [] for index, wyckoff in zip(inequivalent_indices, wyckoffs): datalist = { "Site": unicodeify_species(struct[index].species_string), "Wyckoff Label": wyckoff, } if not lse.neighbors_sets[index]: unknown_sites.append( f"{struct[index].species_string} ({wyckoff})") continue # represent the local environment as a molecule mol = Molecule.from_sites( [struct[index]] + lse.neighbors_sets[index][0].neighb_sites) mol = mol.get_centered_molecule() mg = MoleculeGraph.with_empty_graph(molecule=mol) for i in range(1, len(mol)): mg.add_edge(0, i) view = html.Div( [ StructureMoleculeComponent( struct_or_mol=mg, disable_callbacks=True, id= f"{struct.composition.reduced_formula}_site_{index}", scene_settings={ "enableZoom": False, "defaultZoom": 0.6 }, )._sub_layouts["struct"] ], style={ "width": "300px", "height": "300px" }, ) env = lse.coordination_environments[index] co = all_ce.get_geometry_from_mp_symbol(env[0]["ce_symbol"]) name = co.name if co.alternative_names: name += f" (also known as {', '.join(co.alternative_names)})" datalist.update({ "Environment": name, "IUPAC Symbol": co.IUPAC_symbol_str, get_tooltip( "CSM", "The continuous symmetry measure (CSM) describes the similarity to an " "ideal coordination environment. It can be understood as a 'distance' to " "a shape and ranges from 0 to 100 in which 0 corresponds to a " "coordination environment that is exactly identical to the ideal one. A " "CSM larger than 5.0 already indicates a relatively strong distortion of " "the investigated coordination environment.", ): f"{env[0]['csm']:.2f}", "Interactive View": view, }) envs.append(get_data_list(datalist)) # TODO: switch to tiles? envs_grouped = [envs[i:i + 2] for i in range(0, len(envs), 2)] analysis_contents = [] for env_group in envs_grouped: analysis_contents.append( Columns([Column(e, size=6) for e in env_group])) if unknown_sites: unknown_sites = html.Strong( f"The following sites were not identified: {', '.join(unknown_sites)}. " f"Please try changing the distance or angle cut-offs to identify these sites, " f"or try an alternative algorithm such as LocalEnv.") else: unknown_sites = html.Span() return html.Div( [html.Div(analysis_contents), html.Br(), unknown_sites])
def gen_input(self, step_size, nrings, maxnbq, height=1.7, normaldirection=0, charge=None, spin_multiplicity=None, title=None, functional='HF', basis_set='6-31G(d)', route_parameters=None, input_parameters=None, link0_parameters=None, dieze_tag='#P', gen_basis=None): """ this will return two lists of gauss input string, xticks, xnumbers, pt_idx(ring idx) for plotting pts are cart coords for bqs return None if failed one problem is if the number of ghost atoms cannot be too large, so the param maxnbq is introduced as the max number of ghost atoms that is allowed to show in one input file for other param docs see nics_line_scan_path and pymatgen.io.gassian """ pts, pt_idx, xnumbers, xticks = self.nics_line_scan_path( step_size, nrings, height, normaldirection) sigma_mol_msites = self.nics_sigma_structure(normal_idx=1 - normaldirection) sigma_pmgmol = Molecule.from_sites(sigma_mol_msites) total_pmgmol = self.omol.pmgmol chunksize = maxnbq total_ginobj = GaussianInput(total_pmgmol, charge, spin_multiplicity, title, functional, basis_set, route_parameters, input_parameters, link0_parameters, dieze_tag, gen_basis) sigma_ginobj = GaussianInput(sigma_pmgmol, charge, spin_multiplicity, title, functional, basis_set, route_parameters, input_parameters, link0_parameters, dieze_tag, gen_basis) total_outs_list = [] sigma_outs_list = [] if len(pts) > maxnbq: pts_sep_list = [ pts[i * chunksize:(i + 1) * chunksize] for i in range((len(pts) + chunksize - 1) // chunksize) ] for idx in range(len(pts_sep_list)): total_outs = total_ginobj.to_string(cart_coords=True) total_outs = self.add_bqlines(total_outs, pts_sep_list[idx]) sigma_outs = sigma_ginobj.to_string(cart_coords=True) sigma_outs = self.add_bqlines(sigma_outs, pts_sep_list[idx]) total_outs_list.append(total_outs) sigma_outs_list.append(sigma_outs) else: # is this redundant? total_outs = total_ginobj.to_string(cart_coords=True) total_outs = self.add_bqlines(total_outs, pts) sigma_outs = sigma_ginobj.to_string(cart_coords=True) sigma_outs = self.add_bqlines(sigma_outs, pts) total_outs_list.append(total_outs) sigma_outs_list.append(sigma_outs) return sigma_outs_list, total_outs_list, xticks, xnumbers, pt_idx, pts
def dimerjob_from_two_molecules(cls, pmgmol1, pmgmol2, jobname='dimer'): dimerpmgmol = Molecule.from_sites(pmgmol1.sites + pmgmol2.sites) return cls(jobname, dimerpmgmol, isdimer=True, mol_A=pmgmol1, mol_D=pmgmol2)
def get_chemenv_analysis(struct, distance_cutoff, angle_cutoff): if not struct: raise PreventUpdate struct = self.from_data(struct) distance_cutoff = float(distance_cutoff) angle_cutoff = float(angle_cutoff) # decide which indices to present to user sga = SpacegroupAnalyzer(struct) symm_struct = sga.get_symmetrized_structure() inequivalent_indices = [ indices[0] for indices in symm_struct.equivalent_indices ] wyckoffs = symm_struct.wyckoff_symbols lgf = LocalGeometryFinder() lgf.setup_structure(structure=struct) se = lgf.compute_structure_environments( maximum_distance_factor=distance_cutoff + 0.01, only_indices=inequivalent_indices, ) strategy = SimplestChemenvStrategy(distance_cutoff=distance_cutoff, angle_cutoff=angle_cutoff) lse = LightStructureEnvironments.from_structure_environments( strategy=strategy, structure_environments=se) all_ce = AllCoordinationGeometries() envs = [] unknown_sites = [] for index, wyckoff in zip(inequivalent_indices, wyckoffs): datalist = { "Site": struct[index].species_string, "Wyckoff Label": wyckoff, } if not lse.neighbors_sets[index]: unknown_sites.append( f"{struct[index].species_string} ({wyckoff})") continue # represent the local environment as a molecule mol = Molecule.from_sites( [struct[index]] + lse.neighbors_sets[index][0].neighb_sites) mol = mol.get_centered_molecule() mg = MoleculeGraph.with_empty_graph(molecule=mol) for i in range(1, len(mol)): mg.add_edge(0, i) view = html.Div( [ StructureMoleculeComponent( struct_or_mol=mg, static=True, id= f"{struct.composition.reduced_formula}_site_{index}", scene_settings={ "enableZoom": False, "defaultZoom": 0.6 }, ).all_layouts["struct"] ], style={ "width": "300px", "height": "300px" }, ) env = lse.coordination_environments[index] co = all_ce.get_geometry_from_mp_symbol(env[0]["ce_symbol"]) name = co.name if co.alternative_names: name += f" (also known as {', '.join(co.alternative_names)})" datalist.update({ "Environment": name, "IUPAC Symbol": co.IUPAC_symbol_str, get_tooltip( "CSM", '"Continuous Symmetry Measure," a measure of how symmetrical a ' "local environment is from most symmetrical at 0% to least " "symmetrical at 100%", ): f"{env[0]['csm']:.2f}%", "Interactive View": view, }) envs.append(get_data_list(datalist)) # TODO: switch to tiles? envs_grouped = [envs[i:i + 2] for i in range(0, len(envs), 2)] analysis_contents = [] for env_group in envs_grouped: analysis_contents.append( Columns([Column(e, size=6) for e in env_group])) if unknown_sites: unknown_sites = html.Strong( f"The following sites were not identified: {', '.join(unknown_sites)}. " f"Please try changing the distance or angle cut-offs to identify these sites." ) else: unknown_sites = html.Span() return html.Div( [html.Div(analysis_contents), html.Br(), unknown_sites])
def unwrap(pstructure: Structure): """ unwrap the structure, extract isolated mols this will modify psites *in-place*, properties will be inherited and a new property 'imol' will be written psite with imol=x is an element of both mols[x] and unwrap_pblock_list[x] this method is not supposed to modify siteid! :param pstructure: periodic structure obj from pymatgen :return: mols, unwrap_str_sorted, unwrap_pblock_list """ psites = pstructure.sites cutoff_matrix = np.zeros((len(psites), len(psites))) for i in range(len(psites)): for j in range(i + 1, len(psites)): cutoff = AtomicRadius(psites[i]) + AtomicRadius(psites[j]) cutoff *= 1.3 cutoff_matrix[i][j] = cutoff cutoff_matrix[j][i] = cutoff if all('iasym' in s.properties for s in psites): sameiasym = True warnings.warn('if a percolating step involves hydrogens, only percolate to sites with the same iasym') else: sameiasym = False pindices = range(len(psites)) visited = [] block_list = [] # unwrap = [] unwrap_block_list = [] unwrap_pblock_list = [] while len(visited) != len(psites): # initialization unvisited = [idx for idx in pindices if idx not in visited] ini_idx = unvisited[0] block = [ini_idx] # unwrap.append(psites[ini_idx]) unwrap_block = [Site(psites[ini_idx].species_string, psites[ini_idx].coords, properties=deepcopy(psites[ini_idx].properties))] unwrap_pblock = [psites[ini_idx]] pointer = 0 while pointer != len(block): outside = [idx for idx in pindices if idx not in block and idx not in visited] for i in outside: si = psites[i] sj = psites[block[pointer]] if sameiasym and (si.species_string == 'H' or sj.species_string == 'H'): if si.properties['iasym'] != sj.properties['iasym']: continue distance, fctrans = PBCparser.get_dist_and_trans(pstructure.lattice, psites[block[pointer]].frac_coords, psites[i].frac_coords, ) fctrans = np.rint(fctrans) cutoff = cutoff_matrix[block[pointer]][i] if distance < cutoff: block.append(i) psites[i]._frac_coords += fctrans # psites[i] = PeriodicSite(psites[i].species_string, psites[i].frac_coords + fctrans, # pstructure.lattice, properties=deepcopy(psites[i].properties)) unwrap_block.append( # Site(psites[i].species_string, psites[i].coords, properties=deepcopy(psites[i].properties)) Site(psites[i].species_string, psites[i].coords, properties=psites[i].properties) ) unwrap_pblock.append(psites[i]) visited.append(block[pointer]) pointer += 1 unwrap_block_list.append(unwrap_block) unwrap_pblock_list.append(unwrap_pblock) block_list.append(block) unwrap = [] for i in range(len(unwrap_block_list)): for j in range(len(unwrap_block_list[i])): unwrap_block_list[i][j].properties['imol'] = i unwrap_pblock_list[i][j].properties['imol'] = i unwrap.append(unwrap_pblock_list[i][j]) mols = [Molecule.from_sites(sites) for sites in unwrap_block_list] # unwrap_structure = Structure.from_sites(sorted(unwrap, key=lambda x: x.species_string)) unwrap_structure = Structure.from_sites(unwrap, to_unit_cell=False) return mols, unwrap_structure, unwrap_pblock_list
def to_pymatgen_Molecule(mol): sites = [] for atom, coord in zip(mol.atoms, mol.coords): sites.append(Site(atom, coord)) return Molecule.from_sites(sites)
def __init__( self, site_collection: Union[SiteCollection, Site], color_scheme: str = "Jmol", radius_scheme: str = "uniform", cmap: str = "coolwarm", cmap_range: Optional[Tuple[float, float]] = None, ): """ Create a legend for a given SiteCollection to choose how to display colors and radii for the given sites and the species on those sites. If a site has a "display_color" or "display_radius" site property defined, this can be used to manually override the displayed colors and radii respectively. Args: site_collection: SiteCollection or, for convenience, a single site can be provided and this will be converted into a SiteCollection color_scheme: choose how to color-code species, one of "Jmol", "VESTA", "accessible" or a scalar site property (e.g. magnetic moment) or a categorical/string site property (e.g. Wyckoff label) radius_scheme: choose the radius for a species, one of "atomic", "specified_or_average_ionic", "covalent", "van_der_waals", "atomic_calculated", "uniform" cmap: only used if color_mode is set to a scalar site property, defines the matplotlib color map to use, by default is blue-white-red for negative to postive values cmap_range: only used if color_mode is set to a scalar site property, defines the minimum and maximum values of the color scape """ if isinstance(site_collection, Site): site_collection = Molecule.from_sites([site_collection]) site_prop_types = self.analyze_site_props(site_collection) self.allowed_color_schemes = ( ["VESTA", "Jmol", "accessible"] + site_prop_types.get("scalar", []) + site_prop_types.get("categorical", []) ) self.allowed_radius_schemes = ( "atomic", "specified_or_average_ionic", "covalent", "van_der_waals", "atomic_calculated", "uniform", ) if color_scheme not in self.allowed_color_schemes: warnings.warn( f"Color scheme {color_scheme} not available, " f"falling back to {self.default_color_scheme}." ) color_scheme = self.default_color_scheme # if color-coding by a scalar site property, determine minimum and # maximum values for color scheme, will default to be symmetric # about zero if color_scheme in site_prop_types.get("scalar", []) and not cmap_range: props = np.array( [ p for p in site_collection.site_properties[color_scheme] if p is not None ] ) prop_max = max([abs(min(props)), max(props)]) prop_min = -prop_max cmap_range = (prop_min, prop_max) el_colors = EL_COLORS.copy() el_colors.update( self.generate_accessible_color_scheme_on_the_fly(site_collection) ) self.categorical_colors = self.generate_categorical_color_scheme_on_the_fly( site_collection, site_prop_types ) self.el_colors = el_colors self.site_prop_types = site_prop_types self.site_collection = site_collection self.color_scheme = color_scheme self.radius_scheme = radius_scheme self.cmap = cmap self.cmap_range = cmap_range