def test_from_molecule_graph(self): graph = MoleculeGraph.with_empty_graph(self.mol) adaptor = BabelMolAdaptor.from_molecule_graph(graph) obmol = adaptor.openbabel_mol self.assertEqual(obmol.NumAtoms(), 5) mol = adaptor.pymatgen_mol self.assertEqual(mol.formula, "H4 C1")
def test_coordination(self): molecule = Molecule(["C", "C"], [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]]) mg = MoleculeGraph.with_empty_graph(molecule) self.assertEqual(mg.get_coordination_of_site(0), 0) self.assertEqual(self.cyclohexene.get_coordination_of_site(0), 4)
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 _preprocess_input_to_graph( input: Union[Structure, StructureGraph, Molecule, MoleculeGraph], bonding_strategy: str = "CrystalNN", bonding_strategy_kwargs: Optional[Dict] = None, ) -> Union[StructureGraph, MoleculeGraph]: if isinstance(input, Structure): # ensure fractional co-ordinates are normalized to be in [0,1) # (this is actually not guaranteed by Structure) input = input.as_dict(verbosity=0) for site in input["sites"]: site["abc"] = np.mod(site["abc"], 1) input = Structure.from_dict(input) if not input.is_ordered: # calculating bonds in disordered structures is currently very flaky bonding_strategy = "CutOffDictNN" # we assume most uses of this class will give a structure as an input argument, # meaning we have to calculate the graph for bonding information, however if # the graph is already known and supplied, we will use that if isinstance(input, StructureGraph) or isinstance( input, MoleculeGraph): graph = input else: if (bonding_strategy not in StructureMoleculeComponent. available_bonding_strategies.keys()): raise ValueError( "Bonding strategy not supported. Please supply a name " "of a NearNeighbor subclass, choose from: {}".format( ", ".join(StructureMoleculeComponent. available_bonding_strategies.keys()))) else: bonding_strategy_kwargs = bonding_strategy_kwargs or {} if bonding_strategy == "CutOffDictNN": if "cut_off_dict" in bonding_strategy_kwargs: # TODO: remove this hack by making args properly JSON serializable bonding_strategy_kwargs["cut_off_dict"] = { (x[0], x[1]): x[2] for x in bonding_strategy_kwargs["cut_off_dict"] } bonding_strategy = StructureMoleculeComponent.available_bonding_strategies[ bonding_strategy](**bonding_strategy_kwargs) try: if isinstance(input, Structure): graph = StructureGraph.with_local_env_strategy( input, bonding_strategy) else: graph = MoleculeGraph.with_local_env_strategy( input, bonding_strategy) except: # for some reason computing bonds failed, so let's not have any bonds(!) if isinstance(input, Structure): graph = StructureGraph.with_empty_graph(input) else: graph = MoleculeGraph.with_empty_graph(input) return graph
def test_get_disconnected(self): disconnected = Molecule( ["C", "H", "H", "H", "H", "He"], [ [0.0000, 0.0000, 0.0000], [-0.3633, -0.5138, -0.8900], [1.0900, 0.0000, 0.0000], [-0.3633, 1.0277, 0.0000], [-0.3633, -0.5138, -0.8900], [5.0000, 5.0000, 5.0000], ], ) no_he = Molecule( ["C", "H", "H", "H", "H"], [ [0.0000, 0.0000, 0.0000], [-0.3633, -0.5138, -0.8900], [1.0900, 0.0000, 0.0000], [-0.3633, 1.0277, 0.0000], [-0.3633, -0.5138, -0.8900], ], ) just_he = Molecule(["He"], [[5.0000, 5.0000, 5.0000]]) dis_mg = MoleculeGraph.with_empty_graph(disconnected) dis_mg.add_edge(0, 1) dis_mg.add_edge(0, 2) dis_mg.add_edge(0, 3) dis_mg.add_edge(0, 4) fragments = dis_mg.get_disconnected_fragments() self.assertEqual(len(fragments), 2) self.assertEqual(fragments[0].molecule, no_he) self.assertEqual(fragments[1].molecule, just_he) con_mg = MoleculeGraph.with_empty_graph(no_he) con_mg.add_edge(0, 1) con_mg.add_edge(0, 2) con_mg.add_edge(0, 3) con_mg.add_edge(0, 4) fragments = con_mg.get_disconnected_fragments() self.assertEqual(len(fragments), 1)
def build_MoleculeGraph(molecule, edges=None): if edges == None: edges = edges_from_babel(molecule) mol_graph = MoleculeGraph.with_empty_graph(molecule) for edge in edges: mol_graph.add_edge(edge[0], edge[1]) mol_graph.graph = mol_graph.graph.to_undirected() species = {} coords = {} for node in mol_graph.graph: species[node] = mol_graph.molecule[node].specie.symbol coords[node] = mol_graph.molecule[node].coords nx.set_node_attributes(mol_graph.graph, species, "specie") nx.set_node_attributes(mol_graph.graph, coords, "coords") return mol_graph
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 test_split(self): bonds = [(0, 1), (4, 5)] alterations = { (2, 3): {"weight": 1.0}, (0, 5): {"weight": 2.0}, (1, 2): {"weight": 2.0}, (3, 4): {"weight": 2.0}, } # Perform retro-Diels-Alder reaction - turn product into reactants reactants = self.cyclohexene.split_molecule_subgraphs(bonds, allow_reverse=True, alterations=alterations) self.assertTrue(isinstance(reactants, list)) reactants = sorted(reactants, key=len) # After alterations, reactants should be ethylene and butadiene self.assertEqual(reactants[0], self.ethylene) self.assertEqual(reactants[1], self.butadiene) with self.assertRaises(MolGraphSplitError): self.cyclohexene.split_molecule_subgraphs([(0, 1)]) # Test naive charge redistribution hydroxide = Molecule(["O", "H"], [[0, 0, 0], [0.5, 0.5, 0.5]], charge=-1) oh_mg = MoleculeGraph.with_empty_graph(hydroxide) oh_mg.add_edge(0, 1) new_mgs = oh_mg.split_molecule_subgraphs([(0, 1)]) for mg in new_mgs: if str(mg.molecule[0].specie) == "O": self.assertEqual(mg.molecule.charge, -1) else: self.assertEqual(mg.molecule.charge, 0) # Trying to test to ensure that remapping of nodes to atoms works diff_species = Molecule( ["C", "I", "Cl", "Br", "F"], [ [0.8314, -0.2682, -0.9102], [1.3076, 1.3425, -2.2038], [-0.8429, -0.7410, -1.1554], [1.9841, -1.7636, -1.2953], [1.0098, 0.1231, 0.3916], ], ) diff_spec_mg = MoleculeGraph.with_empty_graph(diff_species) diff_spec_mg.add_edge(0, 1) diff_spec_mg.add_edge(0, 2) diff_spec_mg.add_edge(0, 3) diff_spec_mg.add_edge(0, 4) for i in range(1, 5): bond = (0, i) split_mgs = diff_spec_mg.split_molecule_subgraphs([bond]) for split_mg in split_mgs: species = nx.get_node_attributes(split_mg.graph, "specie") for j in range(len(split_mg.graph.nodes)): atom = split_mg.molecule[j] self.assertEqual(species[j], str(atom.specie))
def setUp(self): cyclohexene = Molecule.from_file( os.path.join( PymatgenTest.TEST_FILES_DIR, "graphs/cyclohexene.xyz", ) ) self.cyclohexene = MoleculeGraph.with_empty_graph( cyclohexene, edge_weight_name="strength", edge_weight_units="" ) self.cyclohexene.add_edge(0, 1, weight=1.0) self.cyclohexene.add_edge(1, 2, weight=1.0) self.cyclohexene.add_edge(2, 3, weight=2.0) self.cyclohexene.add_edge(3, 4, weight=1.0) self.cyclohexene.add_edge(4, 5, weight=1.0) self.cyclohexene.add_edge(5, 0, weight=1.0) self.cyclohexene.add_edge(0, 6, weight=1.0) self.cyclohexene.add_edge(0, 7, weight=1.0) self.cyclohexene.add_edge(1, 8, weight=1.0) self.cyclohexene.add_edge(1, 9, weight=1.0) self.cyclohexene.add_edge(2, 10, weight=1.0) self.cyclohexene.add_edge(3, 11, weight=1.0) self.cyclohexene.add_edge(4, 12, weight=1.0) self.cyclohexene.add_edge(4, 13, weight=1.0) self.cyclohexene.add_edge(5, 14, weight=1.0) self.cyclohexene.add_edge(5, 15, weight=1.0) butadiene = Molecule.from_file( os.path.join( PymatgenTest.TEST_FILES_DIR, "graphs/butadiene.xyz", ) ) self.butadiene = MoleculeGraph.with_empty_graph(butadiene, edge_weight_name="strength", edge_weight_units="") self.butadiene.add_edge(0, 1, weight=2.0) self.butadiene.add_edge(1, 2, weight=1.0) self.butadiene.add_edge(2, 3, weight=2.0) self.butadiene.add_edge(0, 4, weight=1.0) self.butadiene.add_edge(0, 5, weight=1.0) self.butadiene.add_edge(1, 6, weight=1.0) self.butadiene.add_edge(2, 7, weight=1.0) self.butadiene.add_edge(3, 8, weight=1.0) self.butadiene.add_edge(3, 9, weight=1.0) ethylene = Molecule.from_file( os.path.join( PymatgenTest.TEST_FILES_DIR, "graphs/ethylene.xyz", ) ) self.ethylene = MoleculeGraph.with_empty_graph(ethylene, edge_weight_name="strength", edge_weight_units="") self.ethylene.add_edge(0, 1, weight=2.0) self.ethylene.add_edge(0, 2, weight=1.0) self.ethylene.add_edge(0, 3, weight=1.0) self.ethylene.add_edge(1, 4, weight=1.0) self.ethylene.add_edge(1, 5, weight=1.0) self.pc = Molecule.from_file(os.path.join(PymatgenTest.TEST_FILES_DIR, "graphs", "PC.xyz")) self.pc_edges = [ [5, 10], [5, 12], [5, 11], [5, 3], [3, 7], [3, 4], [3, 0], [4, 8], [4, 9], [4, 1], [6, 1], [6, 0], [6, 2], ] self.pc_frag1 = Molecule.from_file(os.path.join(PymatgenTest.TEST_FILES_DIR, "graphs", "PC_frag1.xyz")) self.pc_frag1_edges = [[0, 2], [4, 2], [2, 1], [1, 3]] self.tfsi = Molecule.from_file(os.path.join(PymatgenTest.TEST_FILES_DIR, "graphs", "TFSI.xyz")) self.tfsi_edges = ( [14, 1], [1, 4], [1, 5], [1, 7], [7, 11], [7, 12], [7, 13], [14, 0], [0, 2], [0, 3], [0, 6], [6, 8], [6, 9], [6, 10], ) warnings.simplefilter("ignore")
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 site_layout(symmetrizer: StructureSymmetrizer): columns = [] distance_cutoff = 2.0 angle_cutoff = 0.3 cnn = CrystalNN() nn_info = cnn.get_all_nn_info(structure=symmetrizer.structure) for name, site in symmetrizer.sites.items(): wyckoff_contents = [] data = dict() data["Site Symmetry"] = site.site_symmetry data["Wyckoff Letter"] = site.wyckoff_letter wyckoff_contents.append(html.Label(f"{name}", className="mpc-label")) site_data = [] repr_idx = site.equivalent_atoms[0] for idx in site.equivalent_atoms: coords = symmetrizer.structure[idx].frac_coords site_data += [(pretty_frac_format(coords[0]), pretty_frac_format(coords[1]), pretty_frac_format(coords[2]))] # lgf = LocalGeometryFinder() # lgf.setup_structure(structure=symmetrizer.structure) # se = lgf.compute_structure_environments( # maximum_distance_factor=distance_cutoff + 0.01) # strategy = SimplestChemenvStrategy( # distance_cutoff=distance_cutoff, angle_cutoff=angle_cutoff # ) # lse = LightStructureEnvironments.from_structure_environments( # strategy=strategy, structure_environments=se) # represent the local environment as a molecule mol = Molecule.from_sites([symmetrizer.structure[repr_idx]] + [i["site"] for i in nn_info[repr_idx]]) 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"{symmetrizer.structure.composition.reduced_formula}_site_{repr_idx}", scene_settings={ "enableZoom": False, "defaultZoom": 0.6 }, )._sub_layouts["struct"] ], style={ "width": "300px", "height": "300px" }, ) # env = lse.coordination_environments[repr_idx] # all_ce = AllCoordinationGeometries() # co = all_ce.get_geometry_from_mp_symbol(env[0]["ce_symbol"]) # name = co.name data.update({ # "Environment": name, # "IUPAC Symbol": co.IUPAC_symbol_str, "Structure": view, }) wyckoff_contents.append(get_data_list(data)) wyckoff_contents.append( Reveal(get_table(site_data), title=f"Positions ({len(site_data)})", id=f"{name}-positions")) columns.append(Column(html.Div(wyckoff_contents))) _layout = Columns(columns) return Columns([Column([H4("Sites"), html.Div(_layout)])])
def get_m_graph_from_mol(mol): mg = MoleculeGraph.with_empty_graph(mol) for i in range(1, len(mg)): mg.add_edge(0, i) return mg