def radical_site_test(self): """Test that a charged molecule isn't filtered if it introduces new radical site""" adj1 = """ multiplicity 2 1 O u1 p2 c0 {3,S} 2 O u0 p2 c0 {3,D} 3 N u0 p1 c0 {1,S} {2,D} """ adj2 = """ multiplicity 2 1 O u0 p3 c-1 {3,S} 2 O u0 p2 c0 {3,D} 3 N u1 p0 c+1 {1,S} {2,D} """ adj3 = """ multiplicity 2 1 O u1 p2 c0 {3,S} 2 O u0 p3 c-1 {3,S} 3 N u0 p1 c+1 {1,S} {2,S} """ mol_list = [ Molecule().from_adjacency_list(adj1), Molecule().from_adjacency_list(adj2), Molecule().from_adjacency_list(adj3) ] for mol in mol_list: mol.update( ) # the charge_filtration uses the atom.sorting_label attribute filtered_list = charge_filtration(mol_list, get_charge_span_list(mol_list)) self.assertEqual(len(filtered_list), 2) self.assertTrue( any([mol.get_charge_span() == 1 for mol in filtered_list])) for mol in filtered_list: if mol.get_charge_span() == 1: for atom in mol.vertices: if atom.charge == -1: self.assertTrue(atom.is_oxygen()) if atom.charge == 1: self.assertTrue(atom.is_nitrogen())
def radical_site_test(self): """Test that a charged molecule isn't filtered if it introduces new radical site""" adj1 = """ multiplicity 2 1 O u1 p2 c0 {3,S} 2 O u0 p2 c0 {3,D} 3 N u0 p1 c0 {1,S} {2,D} """ adj2 = """ multiplicity 2 1 O u0 p3 c-1 {3,S} 2 O u0 p2 c0 {3,D} 3 N u1 p0 c+1 {1,S} {2,D} """ adj3 = """ multiplicity 2 1 O u1 p2 c0 {3,S} 2 O u0 p3 c-1 {3,S} 3 N u0 p1 c+1 {1,S} {2,S} """ mol_list = [Molecule().fromAdjacencyList(adj1), Molecule().fromAdjacencyList(adj2), Molecule().fromAdjacencyList(adj3)] for mol in mol_list: mol.update() # the charge_filtration uses the atom.sortingLabel attribute filtered_list = charge_filtration(mol_list, get_charge_span_list(mol_list)) self.assertEqual(len(filtered_list), 2) self.assertTrue(any([mol.getChargeSpan() == 1 for mol in filtered_list])) for mol in filtered_list: if mol.getChargeSpan() == 1: for atom in mol.vertices: if atom.charge == -1: self.assertTrue(atom.isOxygen()) if atom.charge == 1: self.assertTrue(atom.isNitrogen())
def electronegativity_test(self): """Test that structures with charge separation are only kept if they obey the electronegativity rule (If a structure must have charge separation, negative charges will be assigned to more electronegative atoms, whereas positive charges will be assigned to less electronegative atoms) In this test, only the three structures with no charge separation and the structure where both partial charges are on the nitrogen atoms should be kept.""" adj1 = """ multiplicity 2 1 N u0 p1 c0 {2,S} {3,D} 2 N u1 p1 c0 {1,S} {4,S} 3 S u0 p1 c0 {1,D} {5,D} 4 H u0 p0 c0 {2,S} 5 O u0 p2 c0 {3,D} """ adj2 = """ multiplicity 2 1 N u0 p1 c0 {2,D} {3,S} 2 N u0 p1 c0 {1,D} {4,S} 3 S u1 p1 c0 {1,S} {5,D} 4 H u0 p0 c0 {2,S} 5 O u0 p2 c0 {3,D} """ adj3 = """ multiplicity 2 1 N u0 p1 c0 {2,D} {3,S} 2 N u0 p1 c0 {1,D} {4,S} 3 S u0 p2 c0 {1,S} {5,S} 4 H u0 p0 c0 {2,S} 5 O u1 p2 c0 {3,S} """ adj4 = """ multiplicity 2 1 N u1 p0 c+1 {2,S} {3,D} 2 N u0 p2 c-1 {1,S} {4,S} 3 S u0 p1 c0 {1,D} {5,D} 4 H u0 p0 c0 {2,S} 5 O u0 p2 c0 {3,D} """ adj5 = """ multiplicity 2 1 N u1 p0 c+1 {2,D} {3,S} 2 N u0 p1 c0 {1,D} {4,S} 3 S u0 p2 c-1 {1,S} {5,D} 4 H u0 p0 c0 {2,S} 5 O u0 p2 c0 {3,D} """ adj6 = """ multiplicity 2 1 N u1 p1 c0 {2,S} {3,S} 2 N u0 p2 c-1 {1,S} {4,S} 3 S u0 p1 c+1 {1,S} {5,D} 4 H u0 p0 c0 {2,S} 5 O u0 p2 c0 {3,D} """ adj7 = """ multiplicity 2 1 N u1 p0 c+1 {2,D} {3,S} 2 N u0 p1 c0 {1,D} {4,S} 3 S u0 p2 c0 {1,S} {5,S} 4 H u0 p0 c0 {2,S} 5 O u0 p3 c-1 {3,S} """ mol_list = [ Molecule().from_adjacency_list(adj1), Molecule().from_adjacency_list(adj2), Molecule().from_adjacency_list(adj3), Molecule().from_adjacency_list(adj4), Molecule().from_adjacency_list(adj5), Molecule().from_adjacency_list(adj6), Molecule().from_adjacency_list(adj7) ] for mol in mol_list: mol.update( ) # the charge_filtration uses the atom.sorting_label attribute filtered_list = charge_filtration(mol_list, get_charge_span_list(mol_list)) self.assertEqual(len(filtered_list), 4) self.assertTrue( any([mol.get_charge_span() == 1 for mol in filtered_list])) for mol in filtered_list: if mol.get_charge_span() == 1: for atom in mol.vertices: if abs(atom.charge) == 1: self.assertTrue(atom.is_nitrogen())
def _generate_resonance_structures(mol_list, method_list, keep_isomorphic=False, copy=False, filter_structures=True): """ Iteratively generate all resonance structures for a list of starting molecules using the specified methods. Args: mol_list starting list of molecules method_list list of resonance structure algorithms keep_isomorphic if False, removes any structures that give is_isomorphic=True (default) if True, only remove structures that give is_identical=True copy if False, append new resonance structures to input list (default) if True, make a new list with all of the resonance structures """ cython.declare(index=cython.int, molecule=Molecule, new_mol_list=list, new_mol=Molecule, mol=Molecule) if copy: # Make a copy of the list so we don't modify the input list mol_list = mol_list[:] min_octet_deviation = min(filtration.get_octet_deviation_list(mol_list)) min_charge_span = min(filtration.get_charge_span_list(mol_list)) # Iterate over resonance structures index = 0 while index < len(mol_list): molecule = mol_list[index] new_mol_list = [] # On-the-fly filtration: Extend methods only for molecule that don't deviate too much from the octet rule # (a +2 distance from the minimal deviation is used, octet deviations per species are in increments of 2) # Sometimes rearranging the structure requires an additional higher charge span structure, so allow # structures with a +1 higher charge span compared to the minimum, e.g., [O-]S#S[N+]#N # This is run by default even if filter_structures=False. octet_deviation = filtration.get_octet_deviation(molecule) charge_span = molecule.get_charge_span() if octet_deviation <= min_octet_deviation + 2 and charge_span <= min_charge_span + 1: for method in method_list: new_mol_list.extend(method(molecule)) if octet_deviation < min_octet_deviation: # update min_octet_deviation to make this criterion tighter min_octet_deviation = octet_deviation if charge_span < min_charge_span: # update min_charge_span to make this criterion tighter min_charge_span = charge_span for new_mol in new_mol_list: # Append to structure list if unique for mol in mol_list: if not keep_isomorphic and mol.is_isomorphic(new_mol): break elif keep_isomorphic and mol.is_identical(new_mol): break else: mol_list.append(new_mol) # Move to the next resonance structure index += 1 # check net charge for mol in mol_list: if mol.get_net_charge() != 0: raise ResonanceError('Resonance generation gave a net charged molecule:\n{0}' 'Ions are not yet supported in RMG.'.format( mol.to_adjacency_list())) return mol_list
def electronegativity_test(self): """Test that structures with charge separation are only kept if they obey the electronegativity rule (If a structure must have charge separation, negative charges will be assigned to more electronegative atoms, whereas positive charges will be assigned to less electronegative atoms) In this test, only the three structures with no charge separation and the structure where both partial charges are on the nitrogen atoms should be kept.""" adj1 = """ multiplicity 2 1 N u0 p1 c0 {2,S} {3,D} 2 N u1 p1 c0 {1,S} {4,S} 3 S u0 p1 c0 {1,D} {5,D} 4 H u0 p0 c0 {2,S} 5 O u0 p2 c0 {3,D} """ adj2 = """ multiplicity 2 1 N u0 p1 c0 {2,D} {3,S} 2 N u0 p1 c0 {1,D} {4,S} 3 S u1 p1 c0 {1,S} {5,D} 4 H u0 p0 c0 {2,S} 5 O u0 p2 c0 {3,D} """ adj3 = """ multiplicity 2 1 N u0 p1 c0 {2,D} {3,S} 2 N u0 p1 c0 {1,D} {4,S} 3 S u0 p2 c0 {1,S} {5,S} 4 H u0 p0 c0 {2,S} 5 O u1 p2 c0 {3,S} """ adj4 = """ multiplicity 2 1 N u1 p0 c+1 {2,S} {3,D} 2 N u0 p2 c-1 {1,S} {4,S} 3 S u0 p1 c0 {1,D} {5,D} 4 H u0 p0 c0 {2,S} 5 O u0 p2 c0 {3,D} """ adj5 = """ multiplicity 2 1 N u1 p0 c+1 {2,D} {3,S} 2 N u0 p1 c0 {1,D} {4,S} 3 S u0 p2 c-1 {1,S} {5,D} 4 H u0 p0 c0 {2,S} 5 O u0 p2 c0 {3,D} """ adj6 = """ multiplicity 2 1 N u1 p1 c0 {2,S} {3,S} 2 N u0 p2 c-1 {1,S} {4,S} 3 S u0 p1 c+1 {1,S} {5,D} 4 H u0 p0 c0 {2,S} 5 O u0 p2 c0 {3,D} """ adj7 = """ multiplicity 2 1 N u1 p0 c+1 {2,D} {3,S} 2 N u0 p1 c0 {1,D} {4,S} 3 S u0 p2 c0 {1,S} {5,S} 4 H u0 p0 c0 {2,S} 5 O u0 p3 c-1 {3,S} """ mol_list = [Molecule().fromAdjacencyList(adj1), Molecule().fromAdjacencyList(adj2), Molecule().fromAdjacencyList(adj3), Molecule().fromAdjacencyList(adj4), Molecule().fromAdjacencyList(adj5), Molecule().fromAdjacencyList(adj6), Molecule().fromAdjacencyList(adj7)] for mol in mol_list: mol.update() # the charge_filtration uses the atom.sortingLabel attribute filtered_list = charge_filtration(mol_list, get_charge_span_list(mol_list)) self.assertEqual(len(filtered_list), 4) self.assertTrue(any([mol.getChargeSpan() == 1 for mol in filtered_list])) for mol in filtered_list: if mol.getChargeSpan() == 1: for atom in mol.vertices: if abs(atom.charge) == 1: self.assertTrue(atom.isNitrogen())