def _in_database(self, molecule): """ Check if a molecule is already present in the database, which has already been queried on relevant formulae and narrowed to self.all_relevant_docs. If no docs present, assume fragment is not present """ if len(self.all_relevant_docs) == 0: return False # otherwise, look through the docs for an entry with an isomorphic molecule with # equivalent charge and multiplicity else: new_mol_graph = MoleculeGraph.with_local_env_strategy( molecule, OpenBabelNN(), reorder=False, extend_structure=False) for doc in self.all_relevant_docs: if molecule.composition.reduced_formula == doc[ "formula_pretty"]: old_mol = Molecule.from_dict( doc["input"]["initial_molecule"]) old_mol_graph = MoleculeGraph.with_local_env_strategy( old_mol, OpenBabelNN(), reorder=False, extend_structure=False) # If such an equivalent molecule is found, return true if new_mol_graph.isomorphic_to( old_mol_graph ) and molecule.charge == old_mol_graph.molecule.charge and molecule.spin_multiplicity == old_mol_graph.molecule.spin_multiplicity: return True # Otherwise, return false return False
def test_assimilate_unstable_opt(self): drone = QChemDrone( runs=[ "opt_0", "freq_0", "opt_1", "freq_1", "opt_2", "freq_2", "opt_3", "freq_3" ], additional_fields={"special_run_type": "frequency_flattener"}) doc = drone.assimilate(path=os.path.join(module_dir, "..", "test_files", "2620_complete"), input_file="mol.qin", output_file="mol.qout", multirun=False) self.assertEqual(doc["input"]["job_type"], "opt") self.assertEqual(doc["output"]["job_type"], "opt") self.assertEqual(doc["output"]["final_energy"], "unstable") self.assertEqual(doc["smiles"], "[S](=O)[N]S[C]") self.assertEqual(doc["state"], "unsuccessful") self.assertEqual(doc["walltime"], None) self.assertEqual(doc["cputime"], None) self.assertEqual(doc["formula_pretty"], "CS2NO") self.assertEqual(doc["formula_anonymous"], "ABCD2") self.assertEqual(doc["chemsys"], "C-N-O-S") self.assertEqual(doc["pointgroup"], "C1") self.assertEqual(doc["orig"]["rem"], doc["calcs_reversed"][-1]["input"]["rem"]) self.assertEqual(doc["orig"]["molecule"], doc["calcs_reversed"][-1]["input"]["molecule"]) orig_molgraph = MoleculeGraph.with_local_env_strategy( Molecule.from_dict(doc["orig"]["molecule"]), OpenBabelNN()) initial_molgraph = MoleculeGraph.with_local_env_strategy( Molecule.from_dict(doc["input"]["initial_molecule"]), OpenBabelNN()) self.assertEqual(orig_molgraph.isomorphic_to(initial_molgraph), True)
def _check_for_structure_changes(self): initial_mol_graph = MoleculeGraph.with_local_env_strategy( self.data["initial_molecule"], OpenBabelNN(), reorder=False, extend_structure=False) initial_graph = initial_mol_graph.graph last_mol_graph = MoleculeGraph.with_local_env_strategy( self.data["molecule_from_last_geometry"], OpenBabelNN(), reorder=False, extend_structure=False) last_graph = last_mol_graph.graph if initial_mol_graph.isomorphic_to(last_mol_graph): self.data["structure_change"] = "no_change" else: if nx.is_connected( initial_graph.to_undirected()) and not nx.is_connected( last_graph.to_undirected()): self.data["structure_change"] = "unconnected_fragments" elif last_graph.number_of_edges() < initial_graph.number_of_edges( ): self.data["structure_change"] = "fewer_bonds" elif last_graph.number_of_edges() > initial_graph.number_of_edges( ): self.data["structure_change"] = "more_bonds" else: self.data["structure_change"] = "bond_change"
def test_assimilate_opt_with_hidden_changes_from_handler(self): drone = QChemDrone( additional_fields={"special_run_type": "frequency_flattener"}) doc = drone.assimilate(path=os.path.join(module_dir, "..", "test_files", "1746_complete"), input_file="mol.qin", output_file="mol.qout", multirun=False) self.assertEqual(doc["input"]["job_type"], "opt") self.assertEqual(doc["output"]["job_type"], "freq") self.assertEqual(doc["output"]["final_energy"], -303.835532370106) self.assertEqual(doc["smiles"], "O1C(=CC1=O)[CH]") self.assertEqual(doc["state"], "successful") self.assertEqual(doc["num_frequencies_flattened"], 0) self.assertEqual(doc["walltime"], 631.54) self.assertEqual(doc["cputime"], 7471.17) self.assertEqual(doc["formula_pretty"], "HC2O") self.assertEqual(doc["formula_anonymous"], "ABC2") self.assertEqual(doc["chemsys"], "C-H-O") self.assertEqual(doc["pointgroup"], "C1") self.assertEqual(doc["orig"]["rem"], doc["calcs_reversed"][-1]["input"]["rem"]) orig_molgraph = MoleculeGraph.with_local_env_strategy( Molecule.from_dict(doc["orig"]["molecule"]), OpenBabelNN(), reorder=False, extend_structure=False) initial_molgraph = MoleculeGraph.with_local_env_strategy( Molecule.from_dict(doc["input"]["initial_molecule"]), OpenBabelNN(), reorder=False, extend_structure=False) self.assertEqual(orig_molgraph.isomorphic_to(initial_molgraph), False)
def test_generate(self): autots_input = TSInput([self.reactant_1, self.reactant_2], [self.product], self.autots_variables, self.gen_variables) self.assertEqual( metal_edge_extender( MoleculeGraph.with_local_env_strategy( self.reactant_1, OpenBabelNN() ) ), autots_input.reactants[0]) self.assertEqual( metal_edge_extender( MoleculeGraph.with_local_env_strategy( self.reactant_2, OpenBabelNN() ) ), autots_input.reactants[1]) self.assertEqual( metal_edge_extender( MoleculeGraph.with_local_env_strategy( self.product, OpenBabelNN() ) ), autots_input.products[0]) self.assertDictEqual(self.autots_variables, autots_input.autots_variables) self.assertDictEqual(self.gen_variables, autots_input.gen_variables)
def setUpClass(cls): if ob: EC_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "EC.xyz")), OpenBabelNN()) cls.EC_mg = metal_edge_extender(EC_mg) LiEC_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "LiEC.xyz")), OpenBabelNN()) cls.LiEC_mg = metal_edge_extender(LiEC_mg) LEDC_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "LEDC.xyz")), OpenBabelNN()) cls.LEDC_mg = metal_edge_extender(LEDC_mg) LEMC_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "LEMC.xyz")), OpenBabelNN()) cls.LEMC_mg = metal_edge_extender(LEMC_mg) cls.LiEC_reextended_entries = [] entries = loadfn( os.path.join(test_dir, "LiEC_reextended_entries.json")) for entry in entries: if "optimized_molecule" in entry["output"]: mol = entry["output"]["optimized_molecule"] else: mol = entry["output"]["initial_molecule"] E = float(entry["output"]["final_energy"]) H = float(entry["output"]["enthalpy"]) S = float(entry["output"]["entropy"]) mol_entry = MoleculeEntry( molecule=mol, energy=E, enthalpy=H, entropy=S, entry_id=entry["task_id"], ) if mol_entry.formula == "Li1": if mol_entry.charge == 1: cls.LiEC_reextended_entries.append(mol_entry) else: cls.LiEC_reextended_entries.append(mol_entry) # dumpfn(cls.LiEC_reextended_entries, "unittest_input_molentries.json") with open(os.path.join(test_dir, "unittest_RN_build.pkl"), "rb") as input: cls.RN_build = pickle.load(input) with open(os.path.join(test_dir, "unittest_RN_pr_solved.pkl"), "rb") as input: cls.RN_pr_solved = pickle.load(input)
def run_task(self, fw_spec): input_file = os.path.join(self.get("write_to_dir", ""), self.get("input_file", "mol.qin")) # if a full QChemDictSet object was provided if hasattr(self["qchem_input_set"], "write_file"): qcin = self["qchem_input_set"] # if a molecule is being passed through fw_spec elif fw_spec.get("prev_calc_molecule"): prev_calc_mol = fw_spec.get("prev_calc_molecule") # if a molecule is also passed as an optional parameter if self.get("molecule"): mol = self.get("molecule") # check if mol and prev_calc_mol are isomorphic mol_graph = MoleculeGraph.with_local_env_strategy( mol, OpenBabelNN(), reorder=False, extend_structure=False) prev_mol_graph = MoleculeGraph.with_local_env_strategy( prev_calc_molecule, OpenBabelNN(), reorder=False, extend_structure=False, ) # If they are isomorphic, aka a previous FW has not changed bonding, # then we will use prev_calc_mol. If bonding has changed, we will use mol. if mol_graph.isomorphic_to(prev_mol_graph): mol = prev_calc_mol elif self["qchem_input_set"] != "OptSet": print( "WARNING: Molecule from spec is not isomorphic to passed molecule!" ) mol = prev_calc_mol else: print( "Not using prev_calc_mol as it is not isomorphic to passed molecule!" ) else: mol = prev_calc_mol qcin_cls = load_class("pymatgen.io.qchem.sets", self["qchem_input_set"]) qcin = qcin_cls(mol, **self.get("qchem_input_params", {})) # if a molecule is only included as an optional parameter elif self.get("molecule"): qcin_cls = load_class("pymatgen.io.qchem.sets", self["qchem_input_set"]) qcin = qcin_cls(self.get("molecule"), **self.get("qchem_input_params", {})) # if no molecule is present raise an error else: raise KeyError( "No molecule present, add as an optional param or check fw_spec" ) qcin.write(input_file)
def test_nn_orders(self): strat = OpenBabelNN() acetylene = strat.get_nn_info(self.acetylene, 0) self.assertEqual(acetylene[0]["weight"], 3) self.assertEqual(acetylene[1]["weight"], 1) # Currently, benzene bonds register either as double or single, # not aromatic # Instead of searching for aromatic bonds, we check that bonds are # detected in the same way from both sides self.assertEqual(strat.get_nn_info(self.benzene, 0)[0]["weight"], strat.get_nn_info(self.benzene, 1)[0]["weight"])
def test_nn_length(self): strat = OpenBabelNN(order=False) benzene_bonds = strat.get_nn_info(self.benzene, 0) c_bonds = [b for b in benzene_bonds if str(b["site"].specie) == "C"] h_bonds = [b for b in benzene_bonds if str(b["site"].specie) == "H"] self.assertAlmostEqual(c_bonds[0]["weight"], 1.41, 2) self.assertAlmostEqual(h_bonds[0]["weight"], 1.02, 2) self.assertAlmostEqual( strat.get_nn_info(self.acetylene, 0)[0]["weight"], 1.19, 2)
def test_nn_length(self): strat = OpenBabelNN(order=False) benzene_bonds = strat.get_nn_info(self.benzene, 0) c_bonds = [b for b in benzene_bonds if str(b["site"].specie) == "C"] h_bonds = [b for b in benzene_bonds if str(b["site"].specie) == "H"] self.assertAlmostEqual(c_bonds[0]["weight"], 1.41, 2) self.assertAlmostEqual(h_bonds[0]["weight"], 1.02, 2) self.assertAlmostEqual(strat.get_nn_info(self.acetylene, 0)[0]["weight"], 1.19, 2)
def setUpClass(cls) -> None: if ob: cls.LiEC_reextended_entries = [] entries = loadfn( os.path.join(test_dir, "LiEC_reextended_entries.json")) for entry in entries: if "optimized_molecule" in entry["output"]: mol = entry["output"]["optimized_molecule"] else: mol = entry["output"]["initial_molecule"] E = float(entry["output"]["final_energy"]) H = float(entry["output"]["enthalpy"]) S = float(entry["output"]["entropy"]) mol_entry = MoleculeEntry( molecule=mol, energy=E, enthalpy=H, entropy=S, entry_id=entry["task_id"], ) if mol_entry.formula == "Li1": if mol_entry.charge == 1: cls.LiEC_reextended_entries.append(mol_entry) else: cls.LiEC_reextended_entries.append(mol_entry) LiEC_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "LiEC.xyz")), OpenBabelNN()) cls.LiEC_mg = metal_edge_extender(LiEC_mg) LiEC_RO_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "LiEC_RO.xyz")), OpenBabelNN()) cls.LiEC_RO_mg = metal_edge_extender(LiEC_RO_mg) cls.LiEC_entry = None cls.LiEC_RO_entry = None for entry in cls.LiEC_reextended_entries: if (entry.formula == "C3 H4 Li1 O3" and entry.charge == 0 and entry.num_bonds == 12 and cls.LiEC_mg.isomorphic_to(entry.mol_graph)): cls.LiEC_entry = entry elif (entry.formula == "C3 H4 Li1 O3" and entry.charge == 0 and entry.num_bonds == 11 and cls.LiEC_RO_mg.isomorphic_to(entry.mol_graph)): cls.LiEC_RO_entry = entry if cls.LiEC_entry is not None and cls.LiEC_RO_entry is not None: break
def find_mol_entry_from_xyz_and_charge(mol_entries, xyz_file_path, charge): """ given a file 'molecule.xyz', find the mol_entry corresponding to the molecule graph with given charge """ target_mol_graph = MoleculeGraph.with_local_env_strategy( Molecule.from_file(xyz_file_path), OpenBabelNN()) # correction to the molecule graph target_mol_graph = metal_edge_extender(target_mol_graph) match = False index = -1 while not match: index += 1 mol_entry = mol_entries[index] species_mol_graph = mol_entry.mol_graph if mol_entry.charge == charge: match = target_mol_graph.isomorphic_to(species_mol_graph) if match: return mol_entry else: return None
def test_mol_graph_to_schrodinger_struct(self): self.molecule.set_charge_and_spin(charge=-1) mg = MoleculeGraph.with_local_env_strategy(self.molecule, OpenBabelNN()) struct = mol_graph_to_schrodinger_struct(mg) self.assertListEqual([a.element for a in struct.molecule[1].atom], [str(s) for s in mg.molecule.species]) self.assertEqual(struct.formal_charge, -1) for ii in range(len(mg.molecule)): self.assertListEqual(list(mg.molecule.cart_coords[ii]), list(struct.molecule[1].atom[ii + 1].xyz)) struct_bonds = set() for bond in struct.bond: struct_bonds.add( tuple(sorted([bond.atom1.index - 1, bond.atom2.index - 1]))) mg_bonds = set() for bond in mg.graph.edges(): mg_bonds.add(tuple(sorted([bond[0], bond[1]]))) self.assertSetEqual(struct_bonds, mg_bonds)
def __init__( self, molecule: Molecule, energy: float, correction: float = 0.0, enthalpy: Optional[float] = None, entropy: Optional[float] = None, parameters: Optional[Dict] = None, entry_id: Optional[Any] = None, attribute=None, mol_graph: Optional[MoleculeGraph] = None, ): self.uncorrected_energy = energy self.correction = correction self.enthalpy = enthalpy self.entropy = entropy self.parameters = parameters if parameters else {} self.entry_id = entry_id self.attribute = attribute if not mol_graph: mol_graph = MoleculeGraph.with_local_env_strategy( molecule, OpenBabelNN()) self.mol_graph = metal_edge_extender(mol_graph) else: self.mol_graph = mol_graph
def test_mol_to_mol_graph(self): mol = Molecule.from_file( (test_dir / "molecules" / "li2co3_1.xyz").as_posix()) mg = MoleculeGraph.with_local_env_strategy(mol, OpenBabelNN()) mg = metal_edge_extender(mg) self.assertEqual(mg, mol_to_mol_graph(mol))
def test_construction(self): edges_frag = {(e[0], e[1]): {"weight":1.0} for e in self.pc_frag1_edges} mol_graph = MoleculeGraph.with_edges(self.pc_frag1, edges_frag) #dumpfn(mol_graph.as_dict(), os.path.join(module_dir,"pc_frag1_mg.json")) ref_mol_graph = loadfn(os.path.join(module_dir, "pc_frag1_mg.json")) self.assertEqual(mol_graph, ref_mol_graph) self.assertEqual(mol_graph.graph.adj, ref_mol_graph.graph.adj) for node in mol_graph.graph.nodes: self.assertEqual(mol_graph.graph.node[node]["specie"], ref_mol_graph.graph.node[node]["specie"]) for ii in range(3): self.assertEqual( mol_graph.graph.node[node]["coords"][ii], ref_mol_graph.graph.node[node]["coords"][ii]) edges_pc = {(e[0], e[1]): {"weight":1.0} for e in self.pc_edges} mol_graph = MoleculeGraph.with_edges(self.pc, edges_pc) #dumpfn(mol_graph.as_dict(), os.path.join(module_dir,"pc_mg.json")) ref_mol_graph = loadfn(os.path.join(module_dir, "pc_mg.json")) self.assertEqual(mol_graph, ref_mol_graph) self.assertEqual(mol_graph.graph.adj, ref_mol_graph.graph.adj) for node in mol_graph.graph: self.assertEqual(mol_graph.graph.node[node]["specie"], ref_mol_graph.graph.node[node]["specie"]) for ii in range(3): self.assertEqual( mol_graph.graph.node[node]["coords"][ii], ref_mol_graph.graph.node[node]["coords"][ii]) mol_graph_edges = MoleculeGraph.with_edges(self.pc, edges=edges_pc) mol_graph_strat = MoleculeGraph.with_local_env_strategy(self.pc, OpenBabelNN(), reorder=False, extend_structure=False) self.assertTrue(mol_graph_edges.isomorphic_to(mol_graph_strat))
def __init__(self, db_entry, use_metal_edge_extender=True, optimized=True): # id identifier = str(db_entry["_id"]) # pymatgen mol if optimized: if db_entry["state"] != "successful": raise UnsuccessfulEntryError try: pymatgen_mol = pymatgen.Molecule.from_dict( db_entry["output"]["optimized_molecule"]) except KeyError: pymatgen_mol = pymatgen.Molecule.from_dict( db_entry["output"]["initial_molecule"]) print("use initial_molecule for id: {}; job type:{} ".format( db_entry["_id"], db_entry["output"]["job_type"])) else: pymatgen_mol = pymatgen.Molecule.from_dict( db_entry["input"]["initial_molecule"]) # mol graph mol_graph = MoleculeGraph.with_local_env_strategy( pymatgen_mol, OpenBabelNN(order=True)) if use_metal_edge_extender: mol_graph = metal_edge_extender(self.mol_graph) # free energy free_energy = self._get_free_energy(db_entry, self.id, self.formula) # init superclass super(MoleculeWrapperTaskCollection, self).__init__(mol_graph, free_energy, identifier)
def test_babel_PC_defaults(self): pytest.importorskip("openbabel", reason="OpenBabel not installed") fragmenter = Fragmenter(molecule=self.pc) self.assertEqual(fragmenter.open_rings, False) self.assertEqual(fragmenter.opt_steps, 10000) default_mol_graph = MoleculeGraph.with_local_env_strategy(self.pc, OpenBabelNN()) self.assertEqual(fragmenter.mol_graph, default_mol_graph) self.assertEqual(fragmenter.total_unique_fragments, 8)
def test_babel_PC_defaults(self): fragmenter = Fragmenter(molecule=self.pc) self.assertEqual(fragmenter.open_rings, False) self.assertEqual(fragmenter.opt_steps, 10000) default_mol_graph = MoleculeGraph.with_local_env_strategy( self.pc, OpenBabelNN(), reorder=False, extend_structure=False) self.assertEqual(fragmenter.mol_graph, default_mol_graph) self.assertEqual(fragmenter.total_unique_fragments, 8)
def setUp(self): warnings.simplefilter("ignore") self.file = os.path.join(test_dir, "func_group_test.mol") self.mol = Molecule.from_file(self.file) self.strat = OpenBabelNN() self.mg = MoleculeGraph.with_local_env_strategy(self.mol, self.strat) self.extractor = FunctionalGroupExtractor(self.mg)
def test_babel_PC_old_defaults(self): fragmenter = Fragmenter(molecule=self.pc, open_rings=True) self.assertEqual(fragmenter.open_rings, True) self.assertEqual(fragmenter.opt_steps, 10000) default_mol_graph = MoleculeGraph.with_local_env_strategy( self.pc, OpenBabelNN()) self.assertEqual(fragmenter.mol_graph, default_mol_graph) self.assertEqual(fragmenter.total_unique_fragments, 13)
def from_molecule_document( cls, mol_doc: Dict, correction: float = 0.0, parameters: Optional[Dict] = None, attribute=None, ): """ Initialize a MoleculeEntry from a molecule document. Args: mol_doc: MongoDB molecule document (nested dictionary) that contains the molecule information. correction: A correction to be applied to the energy. This is used to modify the energy for certain analyses. Defaults to 0.0. parameters: An optional dict of parameters associated with the molecule. Defaults to None. attribute: Optional attribute of the entry. This can be used to specify that the entry is a newly found compound, or to specify a particular label for the entry, or else ... Used for further analysis and plotting purposes. An attribute can be anything but must be MSONable. """ try: if isinstance(mol_doc["molecule"], Molecule): molecule = mol_doc["molecule"] else: molecule = Molecule.from_dict(mol_doc["molecule"]) energy = mol_doc["energy_Ha"] enthalpy = mol_doc["enthalpy_kcal/mol"] entropy = mol_doc["entropy_cal/molK"] entry_id = mol_doc["task_id"] except KeyError as e: raise MoleculeEntryError( "Unable to construct molecule entry from molecule document; missing " f"attribute {e} in `mol_doc`.") if "mol_graph" in mol_doc: if isinstance(mol_doc["mol_graph"], MoleculeGraph): mol_graph = mol_doc["mol_graph"] else: mol_graph = MoleculeGraph.from_dict(mol_doc["mol_graph"]) else: mol_graph = MoleculeGraph.with_local_env_strategy( molecule, OpenBabelNN()) mol_graph = metal_edge_extender(mol_graph) return cls( molecule=molecule, energy=energy, correction=correction, enthalpy=enthalpy, entropy=entropy, parameters=parameters, entry_id=entry_id, attribute=attribute, mol_graph=mol_graph, )
def filter_fragment_entries(self,fragment_entries): self.filtered_entries = [] for entry in fragment_entries: # Check and make sure that PCM dielectric is consistent with principle: if "pcm_dielectric" in self.molecule_entry: if "pcm_dielectric" not in entry: raise RuntimeError("Principle molecule has a PCM dielectric of " + str(self.molecule_entry["pcm_dielectric"]) + " but a fragment entry has no PCM dielectric! Please only pass fragment entries with PCM details consistent with the principle entry. Exiting...") elif entry["pcm_dielectric"] != self.molecule_entry["pcm_dielectric"]: raise RuntimeError("Principle molecule has a PCM dielectric of " + str(self.molecule_entry["pcm_dielectric"]) + " but a fragment entry has a different PCM dielectric! Please only pass fragment entries with PCM details consistent with the principle entry. Exiting...") # Build initial and final molgraphs: entry["initial_molgraph"] = MoleculeGraph.with_local_env_strategy(Molecule.from_dict(entry["initial_molecule"]), OpenBabelNN(), reorder=False, extend_structure=False) entry["final_molgraph"] = MoleculeGraph.with_local_env_strategy(Molecule.from_dict(entry["final_molecule"]), OpenBabelNN(), reorder=False, extend_structure=False) # Classify any potential structural change that occured during optimization: if entry["initial_molgraph"].isomorphic_to(entry["final_molgraph"]): entry["structure_change"] = "no_change" else: initial_graph = entry["initial_molgraph"].graph final_graph = entry["final_molgraph"].graph if nx.is_connected(initial_graph.to_undirected()) and not nx.is_connected(final_graph.to_undirected()): entry["structure_change"] = "unconnected_fragments" elif final_graph.number_of_edges() < initial_graph.number_of_edges(): entry["structure_change"] = "fewer_bonds" elif final_graph.number_of_edges() > initial_graph.number_of_edges(): entry["structure_change"] = "more_bonds" else: entry["structure_change"] = "bond_change" found_similar_entry = False # Check for uniqueness for ii,filtered_entry in enumerate(self.filtered_entries): if filtered_entry["formula_pretty"] == entry["formula_pretty"]: if filtered_entry["initial_molgraph"].isomorphic_to(entry["initial_molgraph"]) and filtered_entry["final_molgraph"].isomorphic_to(entry["final_molgraph"]) and filtered_entry["initial_molecule"]["charge"] == entry["initial_molecule"]["charge"]: found_similar_entry = True # If two entries are found that pass the above similarity check, take the one with the lower energy: if entry["final_energy"] < filtered_entry["final_energy"]: self.filtered_entries[ii] = entry # Note that this will essentially choose between singlet and triplet entries assuming both have the same structural details break if not found_similar_entry: self.filtered_entries += [entry]
def run_task(self, fw_spec): input_file = os.path.join(self.get("write_to_dir", ""), self.get("input_file", "mol.qin")) # if a molecule is being passed through fw_spec if fw_spec.get("prev_calc_molecule"): prev_calc_mol = fw_spec.get("prev_calc_molecule") # if a molecule is also passed as an optional parameter if self.get("molecule"): mol = self.get("molecule") # check if mol and prev_calc_mol are isomorphic mol_graph = MoleculeGraph.with_local_env_strategy( mol, OpenBabelNN(), reorder=False, extend_structure=False) prev_mol_graph = MoleculeGraph.with_local_env_strategy( prev_calc_molecule, OpenBabelNN(), reorder=False, extend_structure=False, ) if mol_graph.isomorphic_to(prev_mol_graph): mol = prev_calc_mol else: print( "WARNING: Molecule from spec is not isomorphic to passed molecule!" ) else: mol = prev_calc_mol elif self.get("molecule"): mol = self.get("molecule") else: raise KeyError( "No molecule present, add as an optional param or check fw_spec" ) # in the current structure there needs to be a statement for every optional QChem section # the code below defaults the section to None if the variable is not passed opt = self.get("opt", None) pcm = self.get("pcm", None) solvent = self.get("solvent", None) qcin = QCInput(molecule=mol, rem=self["rem"], opt=opt, pcm=pcm, solvent=solvent) qcin.write_file(input_file)
def __init__( self, molecule: Molecule, energy: float, correction: float = 0.0, enthalpy: Optional[float] = None, entropy: Optional[float] = None, parameters: Optional[Dict] = None, entry_id: Optional[Any] = None, attribute=None, mol_doc: Optional[Dict] = None, mol_graph: Optional[MoleculeGraph] = None, ): self.uncorrected_energy = energy self.correction = correction self.enthalpy = enthalpy self.entropy = entropy self.parameters = parameters if parameters else {} self.entry_id = entry_id self.attribute = attribute self.mol_doc = mol_doc if mol_doc else {} self.mol_graph = mol_graph if self.mol_doc != {}: self.enthalpy = self.mol_doc["enthalpy_kcal/mol"] self.entropy = self.mol_doc["entropy_cal/molK"] self.entry_id = self.mol_doc["task_id"] if "mol_graph" in self.mol_doc: if isinstance(self.mol_doc["mol_graph"], MoleculeGraph): self.mol_graph = self.mol_doc["mol_graph"] else: self.mol_graph = MoleculeGraph.from_dict( self.mol_doc["mol_graph"]) else: mol_graph = MoleculeGraph.with_local_env_strategy( molecule, OpenBabelNN()) self.mol_graph = metal_edge_extender(mol_graph) else: if self.mol_graph is None: mol_graph = MoleculeGraph.with_local_env_strategy( molecule, OpenBabelNN()) self.mol_graph = metal_edge_extender(mol_graph)
def open_ring(mol_graph, bond, opt_steps): """ Function to actually open a ring using OpenBabel's local opt. Given a molecule graph and a bond, convert the molecule graph into an OpenBabel molecule, remove the given bond, perform the local opt with the number of steps determined by self.steps, and then convert the resulting structure back into a molecule graph to be returned. """ obmol = BabelMolAdaptor.from_molecule_graph(mol_graph) obmol.remove_bond(bond[0][0] + 1, bond[0][1] + 1) obmol.localopt(steps=opt_steps, forcefield='uff') return MoleculeGraph.with_local_env_strategy(obmol.pymatgen_mol, OpenBabelNN())
def test_construction(self): edges_frag = {(e[0], e[1]): { "weight": 1.0 } for e in self.pc_frag1_edges} mol_graph = MoleculeGraph.with_edges(self.pc_frag1, edges_frag) # dumpfn(mol_graph.as_dict(), os.path.join(module_dir,"pc_frag1_mg.json")) ref_mol_graph = loadfn(os.path.join(module_dir, "pc_frag1_mg.json")) self.assertEqual(mol_graph, ref_mol_graph) self.assertEqual(mol_graph.graph.adj, ref_mol_graph.graph.adj) for node in mol_graph.graph.nodes: self.assertEqual( mol_graph.graph.nodes[node]["specie"], ref_mol_graph.graph.nodes[node]["specie"], ) for ii in range(3): self.assertEqual( mol_graph.graph.nodes[node]["coords"][ii], ref_mol_graph.graph.nodes[node]["coords"][ii], ) edges_pc = {(e[0], e[1]): {"weight": 1.0} for e in self.pc_edges} mol_graph = MoleculeGraph.with_edges(self.pc, edges_pc) # dumpfn(mol_graph.as_dict(), os.path.join(module_dir,"pc_mg.json")) ref_mol_graph = loadfn(os.path.join(module_dir, "pc_mg.json")) self.assertEqual(mol_graph, ref_mol_graph) self.assertEqual(mol_graph.graph.adj, ref_mol_graph.graph.adj) for node in mol_graph.graph: self.assertEqual( mol_graph.graph.nodes[node]["specie"], ref_mol_graph.graph.nodes[node]["specie"], ) for ii in range(3): self.assertEqual( mol_graph.graph.nodes[node]["coords"][ii], ref_mol_graph.graph.nodes[node]["coords"][ii], ) mol_graph_edges = MoleculeGraph.with_edges(self.pc, edges=edges_pc) mol_graph_strat = MoleculeGraph.with_local_env_strategy( self.pc, OpenBabelNN()) self.assertTrue(mol_graph_edges.isomorphic_to(mol_graph_strat)) # Check inappropriate strategy with self.assertRaises(ValueError): MoleculeGraph.with_local_env_strategy(self.pc, VoronoiNN())
def mol_to_mol_graph(molecule: Union[Molecule, MoleculeGraph]): """ Convert a Molecule to a MoleculeGraph using a default connectivity algorithm. Args: molecule (Molecule): Molecule to be converted Returns: mol_graph: MoleculeGraph """ if isinstance(molecule, MoleculeGraph): return molecule else: mol_graph = MoleculeGraph.with_local_env_strategy(molecule, OpenBabelNN()) return metal_edge_extender(mol_graph)
def test_verify_with_graphs(self): ethane = Molecule.from_file(os.path.join(test_dir, "ethane.mol")) # Test bad bond formed with self.assertRaises(ValueError): bad_bond = GSMIsomerInput(molecule=ethane, bonds_formed=[(0, 1)], use_graph=True) # Test bad bond broken with self.assertRaises(ValueError): bad_bond = GSMIsomerInput(molecule=ethane, bonds_broken=[(1, 2)], use_graph=True) # Test bad angle with self.assertRaises(ValueError): bad_angle = GSMIsomerInput(molecule=ethane, angles=[(0, 1, 2)], use_graph=True) # Test bad torsion with self.assertRaises(ValueError): bad_torsion = GSMIsomerInput(molecule=ethane, torsions=[(0, 1, 2, 3)], use_graph=True) # Test bad out of plane bend with self.assertRaises(ValueError): bad_out_of_plane = GSMIsomerInput(molecule=ethane, out_of_planes=[(0, 1, 2, 3)], use_graph=True) # Test good good = GSMIsomerInput(molecule=ethane, bonds_formed=[(0, 7)], angles=[(1, 0, 4)], torsions=[(2, 0, 1, 6)], use_graph=True) mg = MoleculeGraph.with_local_env_strategy(ethane, OpenBabelNN()) self.assertEqual(mg, good.molecule_graph)
def test_union_molgraph(self): with self.assertRaises(ValueError): _ = union_molgraph([]) # Test "good", well-behaved case good_one = MoleculeGraph.with_local_env_strategy( Molecule.from_file((test_dir / "molecules" / "union" / "good" / "1.xyz").as_posix()), OpenBabelNN()) good_one = metal_edge_extender(good_one) good_two = MoleculeGraph.with_local_env_strategy( Molecule.from_file((test_dir / "molecules" / "union" / "good" / "2.xyz").as_posix()), OpenBabelNN()) good_two = metal_edge_extender(good_two) good_union = MoleculeGraph.with_local_env_strategy( Molecule.from_file((test_dir / "molecules" / "union" / "good" / "union.xyz").as_posix()), OpenBabelNN()) good_union = metal_edge_extender(good_union) good = union_molgraph([good_one, good_two]) self.assertTrue(good_union.isomorphic_to(good)) # Test "bad" case where proximity might be an issue bad_one = MoleculeGraph.with_local_env_strategy( Molecule.from_file((test_dir / "molecules" / "union" / "bad" / "1.xyz").as_posix()), OpenBabelNN()) bad_one = metal_edge_extender(bad_one) bad_two = MoleculeGraph.with_local_env_strategy( Molecule.from_file((test_dir / "molecules" / "union" / "bad" / "2.xyz").as_posix()), OpenBabelNN()) bad_two = metal_edge_extender(bad_two) bad_union = MoleculeGraph.with_local_env_strategy( Molecule.from_file( (test_dir / "molecules" / "union" / "bad" / "union.xyz")), OpenBabelNN()) bad_union = metal_edge_extender(bad_union) bad = union_molgraph([bad_one, bad_two]) self.assertTrue(bad_union.isomorphic_to(bad)) with self.assertRaises(ValueError): _ = union_molgraph([bad_one, bad_two], validate_proximity=True)
def __init__(self, molecule, optimize=False): """ Instantiation method for FunctionalGroupExtractor. :param molecule: Either a filename, a pymatgen.core.structure.Molecule object, or a pymatgen.analysis.graphs.MoleculeGraph object. :param optimize: Default False. If True, then the input molecule will be modified, adding Hydrogens, performing a simple conformer search, etc. """ self.molgraph = None if isinstance(molecule, str): try: if optimize: obmol = BabelMolAdaptor.from_file(molecule, file_format="mol") # OBMolecule does not contain pymatgen Molecule information # So, we need to wrap the obmol in a BabelMolAdapter obmol.add_hydrogen() obmol.make3d() obmol.localopt() self.molecule = obmol.pymatgen_mol else: self.molecule = Molecule.from_file(molecule) except OSError: raise ValueError("Input must be a valid molecule file, a " "Molecule object, or a MoleculeGraph object.") elif isinstance(molecule, Molecule): if optimize: obmol = BabelMolAdaptor(molecule) obmol.add_hydrogen() obmol.make3d() obmol.localopt() self.molecule = obmol.pymatgen_mol else: self.molecule = molecule elif isinstance(molecule, MoleculeGraph): if optimize: obmol = BabelMolAdaptor(molecule.molecule) obmol.add_hydrogen() obmol.make3d() obmol.localopt() self.molecule = obmol.pymatgen_mol else: self.molecule = molecule.molecule self.molgraph = molecule else: raise ValueError("Input to FunctionalGroupExtractor must be" "str, Molecule, or MoleculeGraph.") if self.molgraph is None: self.molgraph = MoleculeGraph.with_local_env_strategy( self.molecule, OpenBabelNN(), reorder=False, extend_structure=False) # Assign a specie and coordinates to each node in the graph, # corresponding to the Site in the Molecule object self.molgraph.set_node_attributes() self.species = nx.get_node_attributes(self.molgraph.graph, "specie")
def get_basic_functional_groups(self, func_groups=None): """ Identify functional groups that cannot be identified by the Ertl method of get_special_carbon and get_heteroatoms, such as benzene rings, methyl groups, and ethyl groups. TODO: Think of other functional groups that are important enough to be added (ex: do we need ethyl, butyl, propyl?) :param func_groups: List of strs representing the functional groups of interest. Default to None, meaning that all of the functional groups defined in this function will be sought. :return: list of sets of ints, representing groups of connected atoms """ strat = OpenBabelNN() hydrogens = {n for n in self.molgraph.graph.nodes if str(self.species[n]) == "H"} carbons = [n for n in self.molgraph.graph.nodes if str(self.species[n]) == "C"] if func_groups is None: func_groups = ["methyl", "phenyl"] results = [] if "methyl" in func_groups: for node in carbons: neighbors = strat.get_nn_info(self.molecule, node) hs = {n["site_index"] for n in neighbors if n["site_index"] in hydrogens} # Methyl group is CH3, but this will also catch methane if len(hs) >= 3: hs.add(node) results.append(hs) if "phenyl" in func_groups: rings_indices = [set(sum(ring, ())) for ring in self.molgraph.find_rings()] possible_phenyl = [r for r in rings_indices if len(r) == 6] for ring in possible_phenyl: # Phenyl group should have only one (0 for benzene) member whose # neighbors are not two carbons and one hydrogen num_deviants = 0 for node in ring: neighbors = strat.get_nn_info(self.molecule, node) neighbor_spec = sorted([str(self.species[n["site_index"]]) for n in neighbors]) if neighbor_spec != ["C", "C", "H"]: num_deviants += 1 if num_deviants <= 1: for node in ring: neighbors = self.molgraph.graph[node] # Add hydrogens to the functional group for neighbor in neighbors.keys(): if neighbor in hydrogens: ring.add(neighbor) results.append(ring) return results