def test_c5h6o(self): inchi = "InChI=1S/C5H6O/c6-5-3-1-2-4-5/h1-3,5H,4H2" mol = Molecule().from_inchi(inchi) start = mol.atoms[1] path = find_allyl_end_with_charge(start)[0] idx_path = [mol.atoms.index(atom) + 1 for atom in path[0::2]] expected_idx_path = [2, 1, 3] self.assertEquals(idx_path, expected_idx_path)
def test_c3h4o4(self): inchi = "InChI=1S/C3H4O4/c4-3(5)1-2-7-6/h1-3,6H" mol = Molecule().from_inchi(inchi) start = mol.atoms[6] path = find_allyl_end_with_charge(start)[0] idx_path = [mol.atoms.index(atom) + 1 for atom in path[0::2]] expected_idx_path = [7, 2, 1] self.assertEquals(idx_path, expected_idx_path)
def test_c3h4(self): inchi = "InChI=1S/C3H4/c1-3-2/h1,3H,2H2" mol = Molecule().from_inchi(inchi) start = mol.atoms[0] path = find_allyl_end_with_charge(start)[0] idx_path = [mol.atoms.index(atom) + 1 for atom in path[0::2]] expected_idx_path = [1, 3, 2] self.assertEquals(idx_path, expected_idx_path)
def fix_oxygen_unsaturated_bond(mol, u_indices): """ Searches for a radical or a charged oxygen atom connected to a closed-shell carbon via an unsatured bond. Decrements the unsatured bond, transfers the unpaired electron from O to C or converts the charge from O to an unpaired electron on C, increases the lone pair count of O to 2. Only do this once per molecule. """ for at in mol.atoms: if at.isOxygen() and at.radicalElectrons == 1 and at.lonePairs == 1: bonds = mol.getBonds(at) oxygen = at for atom2, bond in bonds.iteritems(): if bond.isTriple(): bond.decrementOrder() oxygen.radicalElectrons -= 1 atom2.radicalElectrons += 1 oxygen.lonePairs += 1 return elif at.isOxygen() and at.charge == 1 and at.lonePairs == 1: bonds = mol.getBonds(at) oxygen = at start = oxygen # search for 3-atom-2-bond [X=X-X] paths paths = pathfinder.find_allyl_end_with_charge(start) for path in paths: end = path[-1] start.charge += 1 if start.charge < 0 else -1 end.charge += 1 if end.charge < 0 else -1 start.lonePairs += 1 # filter bonds from path and convert bond orders: bonds = path[1::2]#odd elements for bond in bonds[::2]:# even bonds assert isinstance(bond, Bond) bond.decrementOrder() for bond in bonds[1::2]:# odd bonds assert isinstance(bond, Bond) bond.incrementOrder() return else: for atom2, bond in bonds.iteritems(): if not bond.isSingle() and atom2.charge == 0: oxygen.charge -= 1 if (mol.atoms.index(atom2) + 1) in u_indices: bond.decrementOrder() atom2.radicalElectrons += 1 u_indices.remove(mol.atoms.index(atom2) + 1) oxygen.lonePairs += 1 return
def fix_oxygen_unsaturated_bond(mol, u_indices): """ Searches for a radical or a charged oxygen atom connected to a closed-shell carbon via an unsatured bond. Decrements the unsatured bond, transfers the unpaired electron from O to C or converts the charge from O to an unpaired electron on C, increases the lone pair count of O to 2. Only do this once per molecule. """ for at in mol.atoms: if at.isOxygen() and at.radicalElectrons == 1 and at.lonePairs == 1: bonds = mol.getBonds(at) oxygen = at for atom2, bond in bonds.iteritems(): if bond.isTriple(): bond.decrementOrder() oxygen.radicalElectrons -= 1 atom2.radicalElectrons += 1 oxygen.lonePairs += 1 return elif at.isOxygen() and at.charge == 1 and at.lonePairs == 1: bonds = mol.getBonds(at) oxygen = at start = oxygen # search for 3-atom-2-bond [X=X-X] paths paths = pathfinder.find_allyl_end_with_charge(start) for path in paths: end = path[-1] start.charge += 1 if start.charge < 0 else -1 end.charge += 1 if end.charge < 0 else -1 start.lonePairs += 1 # filter bonds from path and convert bond orders: bonds = path[1::2]#odd elements for bond in bonds[::2]:# even bonds assert isinstance(bond, Bond) bond.decrementOrder() for bond in bonds[1::2]:# odd bonds assert isinstance(bond, Bond) bond.incrementOrder() return else: for atom2, bond in bonds.iteritems(): if not bond.isSingle() and atom2.charge == 0: oxygen.charge -= 1 if (mol.atoms.index(atom2) + 1) in u_indices: bond.decrementOrder() atom2.radicalElectrons += 1 u_indices.remove(mol.atoms.index(atom2) + 1) oxygen.lonePairs += 1 return
def test_c2h2o3(self): adjlist = """ 1 C u0 p0 c0 {5,D} {6,S} {7,S} 2 C u0 p0 c0 {3,D} {4,S} {5,S} 3 O u0 p2 c0 {2,D} 4 O u0 p3 c-1 {2,S} 5 O u0 p1 c+1 {1,D} {2,S} 6 H u0 p0 c0 {1,S} 7 H u0 p0 c0 {1,S} """ mol = Molecule().from_adjacency_list(adjlist) start = mol.atoms[2] paths = find_allyl_end_with_charge(start) idx_path = sorted([[mol.atoms.index(atom) + 1 for atom in path[0::2]] for path in paths]) expected_idx_path = [[3, 2, 4], [3, 2, 5]] self.assertEquals(idx_path, expected_idx_path)
def test_c3h2o3(self): adjlist = """ 1 C u0 p0 c0 {2,D} {7,S} {8,S} 2 C u0 p0 c0 {1,D} {3,D} 3 C u0 p0 c0 {2,D} {4,S} {6,S} 4 O u0 p3 c-1 {3,S} 5 O u0 p2 c0 {6,D} 6 O u0 p1 c+1 {3,S} {5,D} 7 H u0 p0 c0 {1,S} 8 H u0 p0 c0 {1,S} """ mol = Molecule().from_adjacency_list(adjlist) start = mol.atoms[1] paths = find_allyl_end_with_charge(start) idx_paths = sorted([[mol.atoms.index(atom) + 1 for atom in path[0::2]] for path in paths]) idx_paths = sorted(idx_paths) expected_idx_paths = [[2, 3, 4], [2, 3, 6]] self.assertEquals(idx_paths, expected_idx_paths)
def _convert_3_atom_2_bond_path(start, mol): """ Searches for 3-atom-2-bond [X=X-X+] paths paths starting from the parameter atom. If a correct path is found, the starting atom receives an unpaired electron while the bonds in the delocalization path are "inverted". A unit of charge on the end atom is neutralized and a lone pair is added. If it turns out the path was invalid, the actions are reverted, and another path is tried instead. To facilitate reverting the changes, we use a reaction recipe and populate it with a number of actions that reflect the changes in bond orders and unpaired electrons that the molecule should undergo. """ from rmgpy.data.kinetics.family import ReactionRecipe def is_valid(mol): """Check if total bond order of oxygen atoms is smaller than 4.""" for at in mol.atoms: if at.number == 8: order = at.get_total_bond_order() not_correct = order >= 4 if not_correct: return False return True paths = pathfinder.find_allyl_end_with_charge(start) for path in paths: # label atoms so that we can use the labels in the actions of the recipe for i, at in enumerate(path[::2]): at.label = str(i) # we have found the atom we are looking for recipe = ReactionRecipe() recipe.add_action(['GAIN_RADICAL', start.label, 1]) end = path[-1] end_original_charge = end.charge # filter bonds from path and convert bond orders: bonds = path[1::2] # odd elements for bond in bonds[::2]: # even recipe.add_action( ['CHANGE_BOND', bond.atom1.label, -1, bond.atom2.label]) for bond in bonds[1::2]: # odd recipe.add_action( ['CHANGE_BOND', bond.atom1.label, 1, bond.atom2.label]) end.charge += 1 if end.charge < 0 else -1 recipe.apply_forward(mol) if is_valid(mol): # unlabel atoms so that they never cause trouble downstream for i, at in enumerate(path[::2]): at.label = '' return True else: recipe.apply_reverse(mol) end.charge = end_original_charge # unlabel atoms so that they never cause trouble downstream for i, at in enumerate(path[::2]): assert isinstance(at, Atom) at.label = '' return False
def convert_3_atom_2_bond_path(start, mol): """ Searches for 3-atom-2-bond [X=X-X+] paths paths starting from the parameter atom. If a correct path is found, the starting atom receives an unpaired electron while the bonds in the delocalization path are "inverted". A unit of charge on the end atom is neutralized and a lone pair is added. If it turns out the path was invalid, the actions are reverted, and another path is tried instead. To facilitate reverting the changes, we use a reaction recipe and populate it with a number of actions that reflect the changes in bond orders and unpaired electrons that the molecule should undergo. """ from rmgpy.data.kinetics.family import ReactionRecipe def is_valid(mol): """Check if total bond order of oxygen atoms is smaller than 4.""" for at in mol.atoms: if at.number == 8: order = sum([bond_orders[b.order] for _, b in at.bonds.iteritems()]) not_correct = order >= 4 if not_correct: return False return True index = mol.atoms.index(start) + 1 paths = pathfinder.find_allyl_end_with_charge(start) for path in paths: # label atoms so that we can use the labels in the actions of the recipe for i, at in enumerate(path[::2]): at.label = str(i) # we have found the atom we are looking for recipe = ReactionRecipe() recipe.addAction(['GAIN_RADICAL', start.label, 1]) end = path[-1] end_original_charge = end.charge # filter bonds from path and convert bond orders: bonds = path[1::2]#odd elements for bond in bonds[::2]:# even recipe.addAction(['CHANGE_BOND', bond.atom1.label, -1, bond.atom2.label]) for bond in bonds[1::2]:# odd recipe.addAction(['CHANGE_BOND', bond.atom1.label, 1, bond.atom2.label]) end.charge += 1 if end.charge < 0 else -1 recipe.applyForward(mol) if is_valid(mol): # unlabel atoms so that they never cause trouble downstream for i, at in enumerate(path[::2]): at.label = '' return True else: recipe.applyReverse(mol) end.charge = end_original_charge # unlabel atoms so that they never cause trouble downstream for i, at in enumerate(path[::2]): assert isinstance(at, Atom) at.label = '' return False