Exemplo n.º 1
0
    def __init__(self, molecule, edges=None, depth=1, open_rings=True, opt_steps=10000):
        """
        Standard constructor for molecule fragmentation

        Args:
            molecule (Molecule): The molecule to fragment
            edges (list): List of index pairs that define graph edges, aka molecule bonds. If not
                          set, edges will be determined with OpenBabel.
            depth (int): The number of levels of iterative fragmentation to perform, where each
                         level will include fragments obtained by breaking one bond of a fragment
                         one level up. Defaults to 1. However, if set to 0, instead all possible
                         fragments are generated using an alternative, non-iterative scheme.
            open_rings (bool): Whether or not to open any rings encountered during fragmentation.
                               Defaults to False. If true, any bond that fails to yield disconnected
                               graphs when broken is instead removed and the entire structure is
                               optimized with OpenBabel in order to obtain a good initial guess for
                               an opened geometry that can then be put back into QChem to be
                               optimized without the ring just reforming.
            opt_steps (int): Number of optimization steps when opening rings. Defaults to 1000.

        """

        self.open_rings = open_rings
        self.opt_steps = opt_steps

        if edges is None:
            self.mol_graph = build_MoleculeGraph(molecule, strategy=OpenBabelNN,
                                            reorder=False, extend_structure=False)
        else:
            edges = [(e[0], e[1], {}) for e in edges]
            self.mol_graph = build_MoleculeGraph(molecule, edges=edges)

        self.unique_fragments = []
        self.unique_fragments_from_ring_openings = []

        if depth == 0: # Non-iterative, find all possible fragments:

            # Find all unique fragments besides those involving ring opening
            self.unique_fragments = self.mol_graph.build_unique_fragments()

            # Then, if self.open_rings is True, open all rings present in self.unique_fragments
            # in order to capture all unique fragments that require ring opening.
            if self.open_rings:
                self._open_all_rings()

        else: # Iterative fragment generation:
            self.fragments_by_level = {}

            # Loop through the number of levels,
            for level in range(depth):
                # If on the first level, perform one level of fragmentation on the principle molecule graph:
                if level == 0:
                    self.fragments_by_level["0"] = self._fragment_one_level([self.mol_graph])
                else:
                    if len(self.fragments_by_level[str(level-1)]) == 0:
                        # Nothing left to fragment, so exit the loop:
                        break
                    else: # If not on the first level, and there are fragments present in the previous level, then
                          # perform one level of fragmentation on all fragments present in the previous level:
                        self.fragments_by_level[str(level)] = self._fragment_one_level(self.fragments_by_level[str(level-1)])
Exemplo n.º 2
0
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)
    return build_MoleculeGraph(obmol.pymatgen_mol, strategy=OpenBabelNN, reorder=False, extend_structure=False)
Exemplo n.º 3
0
 def test_babel_PC_defaults(self):
     fragmenter = Fragmenter(molecule=self.pc)
     self.assertEqual(fragmenter.open_rings, True)
     self.assertEqual(fragmenter.opt_steps, 10000)
     default_mol_graph = build_MoleculeGraph(self.pc,
                                             strategy=OpenBabelNN,
                                             reorder=False,
                                             extend_structure=False)
     self.assertEqual(fragmenter.mol_graph, default_mol_graph)
     self.assertEqual(len(fragmenter.unique_fragments), 13)
     self.assertEqual(len(fragmenter.unique_fragments_from_ring_openings),
                      5)
Exemplo n.º 4
0
 def test_edges_given_PC_not_defaults(self):
     fragmenter = Fragmenter(molecule=self.pc,
                             edges=self.pc_edges,
                             depth=2,
                             open_rings=False,
                             opt_steps=0)
     self.assertEqual(fragmenter.open_rings, False)
     self.assertEqual(fragmenter.opt_steps, 0)
     edges = [(e[0], e[1], {}) for e in self.pc_edges]
     default_mol_graph = build_MoleculeGraph(self.pc, edges=edges)
     self.assertEqual(fragmenter.mol_graph, default_mol_graph)
     self.assertEqual(len(fragmenter.unique_fragments), 20)
     self.assertEqual(len(fragmenter.unique_fragments_from_ring_openings),
                      0)
Exemplo n.º 5
0
 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"] = build_MoleculeGraph(
             Molecule.from_dict(entry["initial_molecule"]),
             strategy=OpenBabelNN,
             reorder=False,
             extend_structure=False)
         entry["final_molgraph"] = build_MoleculeGraph(
             Molecule.from_dict(entry["final_molecule"]),
             strategy=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]
Exemplo n.º 6
0
    def __init__(self,
                 molecule_entry,
                 fragment_entries,
                 allow_additional_charge_separation=False,
                 multibreak=False):
        """
        Standard constructor for bond dissociation energies. All bonds in the principle molecule are
        looped through and their dissociation energies are calculated given the energies of the resulting
        fragments, or, in the case of a ring bond, from the energy of the molecule obtained from breaking
        the bond and opening the ring. This class should only be called after the energies of the optimized
        principle molecule and all relevant optimized fragments have been determined, either from quantum
        chemistry or elsewhere. It was written to provide the analysis after running an Atomate fragmentation
        workflow.

        Note that the entries passed by the user must have the following keys: formula_pretty, initial_molecule,
        final_molecule. If a PCM is present, all entries should also have a pcm_dielectric key.

        Args:
            molecule_entry (dict): Entry for the principle molecule. Should have the keys mentioned above.
            fragment_entries (list of dicts): List of fragment entries. Each should have the keys mentioned above.
            allow_additional_charge_separation (bool): If True, consider larger than normal charge separation
                                                       among fragments. Defaults to False. See the definition
                                                       of self.expected_charges below for more specific information.
            multibreak (bool): If True, additionally attempt to break pairs of bonds. Defaults to False.

        """

        self.molecule_entry = molecule_entry
        self.filter_fragment_entries(fragment_entries)
        print(str(len(self.filtered_entries)) + " filtered entries")
        self.bond_dissociation_energies = []
        self.done_frag_pairs = []
        self.done_RO_frags = []
        self.ring_bonds = []

        required_keys = [
            "formula_pretty", "initial_molecule", "final_molecule"
        ]
        if "pcm_dielectric" in self.molecule_entry:
            required_keys.append("pcm_dielectric")
        for key in required_keys:
            if key not in self.molecule_entry:
                raise RuntimeError(
                    key + " must be present in molecule entry! Exiting...")
            for entry in self.filtered_entries:
                if key not in entry:
                    raise RuntimeError(
                        key +
                        " must be present in all fragment entries! Exiting...")

        # Define expected charges
        if not allow_additional_charge_separation:
            if molecule_entry["final_molecule"]["charge"] == 0:
                self.expected_charges = [-1, 0, 1]
            elif molecule_entry["final_molecule"]["charge"] < 0:
                self.expected_charges = [
                    molecule_entry["final_molecule"]["charge"],
                    molecule_entry["final_molecule"]["charge"] + 1
                ]
            else:
                self.expected_charges = [
                    molecule_entry["final_molecule"]["charge"] - 1,
                    molecule_entry["final_molecule"]["charge"]
                ]
        else:
            if molecule_entry["final_molecule"]["charge"] == 0:
                self.expected_charges = [-2, -1, 0, 1, 2]
            elif molecule_entry["final_molecule"]["charge"] < 0:
                self.expected_charges = [
                    molecule_entry["final_molecule"]["charge"] - 1,
                    molecule_entry["final_molecule"]["charge"],
                    molecule_entry["final_molecule"]["charge"] + 1,
                    molecule_entry["final_molecule"]["charge"] + 2
                ]
            else:
                self.expected_charges = [
                    molecule_entry["final_molecule"]["charge"] - 2,
                    molecule_entry["final_molecule"]["charge"] - 1,
                    molecule_entry["final_molecule"]["charge"],
                    molecule_entry["final_molecule"]["charge"] + 1
                ]

        # Build principle molecule graph
        self.mol_graph = build_MoleculeGraph(Molecule.from_dict(
            molecule_entry["final_molecule"]),
                                             strategy=OpenBabelNN,
                                             reorder=False,
                                             extend_structure=False)
        # Loop through bonds, aka graph edges, and fragment and process:
        for bond in self.mol_graph.graph.edges:
            bonds = [(bond[0], bond[1])]
            self.fragment_and_process(bonds)
        # If mulitbreak, loop through pairs of ring bonds.
        if multibreak:
            print(
                "Breaking pairs of ring bonds. WARNING: Structure changes much more likely, meaning dissociation values are less reliable! This is a bad idea!"
            )
            self.bond_pairs = []
            for ii, bond in enumerate(self.ring_bonds):
                for jj in range(ii + 1, len(self.ring_bonds)):
                    bond_pair = [bond, self.ring_bonds[jj]]
                    self.bond_pairs += [bond_pair]
            for bond_pair in self.bond_pairs:
                self.fragment_and_process(bond_pair)