def setUp(self): """ A method called before each unit test in this class. """ self.bond = GroupBond(None, None, order=['D']) self.orderList = [['S'], ['D'], ['T'], ['B'], ['S', 'D'], ['D', 'S'], ['D', 'T'], ['S', 'D', 'T']]
def setUp(self): """ A method called before each unit test in this class. """ self.bond = GroupBond(None, None, order=[2]) self.orderList = [[1], [2], [3], [1.5], [1, 2], [2, 1], [2, 3], [1, 2, 3]]
def testApplyActionDecrementBond(self): """ Test the GroupBond.applyAction() method for a CHANGE_BOND action. """ action = ["CHANGE_BOND", "*1", -1, "*2"] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) except ActionError: self.assertTrue("S" in order0 or "B" in order0)
def testIsSpecificCaseOf(self): """ Test the GroupBond.isSpecificCaseOf() method. """ for order1 in self.orderList: for order2 in self.orderList: bond1 = GroupBond(None, None, order=order1) bond2 = GroupBond(None, None, order=order2) if order1 == order2 or all([o in order2 for o in order1]): self.assertTrue(bond1.isSpecificCaseOf(bond2)) else: self.assertFalse(bond1.isSpecificCaseOf(bond2))
def testApplyActionDecrementBond(self): """ Test the GroupBond.applyAction() method for a CHANGE_BOND action. """ action = ['CHANGE_BOND', '*1', -1, '*2'] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) except ActionError: self.assertTrue(1 in order0 or 1.5 in order0)
def testApplyActionLoseRadical(self): """ Test the GroupBond.applyAction() method for a LOSE_RADICAL action. """ action = ['LOSE_RADICAL', '*1', 1] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail('GroupBond.applyAction() unexpectedly processed a LOSE_RADICAL action.') except ActionError: pass
def testApplyActionFormBond(self): """ Test the GroupBond.applyAction() method for a FORM_BOND action. """ action = ['FORM_BOND', '*1', 1, '*2'] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail('GroupBond.applyAction() unexpectedly processed a FORM_BOND action.') except ActionError: pass
def testApplyActionGainRadical(self): """ Test the GroupBond.applyAction() method for a GAIN_RADICAL action. """ action = ["GAIN_RADICAL", "*1", 1] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail("GroupBond.applyAction() unexpectedly processed a GAIN_RADICAL action.") except ActionError: pass
def testApplyActionBreakBond(self): """ Test the GroupBond.applyAction() method for a BREAK_BOND action. """ action = ["BREAK_BOND", "*1", "S", "*2"] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail("GroupBond.applyAction() unexpectedly processed a BREAK_BOND action.") except ActionError: pass
def testApplyActionFormBond(self): """ Test the GroupBond.applyAction() method for a FORM_BOND action. """ action = ['FORM_BOND', '*1', 1, '*2'] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail( 'GroupBond.applyAction() unexpectedly processed a FORM_BOND action.' ) except ActionError: pass
def testApplyActionLoseRadical(self): """ Test the GroupBond.applyAction() method for a LOSE_RADICAL action. """ action = ['LOSE_RADICAL', '*1', 1] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail( 'GroupBond.applyAction() unexpectedly processed a LOSE_RADICAL action.' ) except ActionError: pass
def testEquivalent(self): """ Test the GroupBond.equivalent() method. """ for order1 in self.orderList: for order2 in self.orderList: bond1 = GroupBond(None, None, order=order1) bond2 = GroupBond(None, None, order=order2) if order1 == order2 or (all([o in order2 for o in order1]) and all([o in order1 for o in order2])): self.assertTrue(bond1.equivalent(bond2)) self.assertTrue(bond2.equivalent(bond1)) else: self.assertFalse(bond1.equivalent(bond2)) self.assertFalse(bond2.equivalent(bond1))
def setUp(self): """ A method called before each unit test in this class. """ self.bond = GroupBond(None, None, order=["D"]) self.orderList = [["S"], ["D"], ["T"], ["B"], ["S", "D"], ["D", "S"], ["D", "T"], ["S", "D", "T"]]
def testGetOrderStr(self): """ test the Bond.getOrderStr() method """ bond = GroupBond(None, None, order=[1, 2, 3, 1.5]) self.assertEqual(bond.getOrderStr(), ['S', 'D', 'T', 'B'])
def setUp(self): """ A method called before each unit test in this class. """ self.bond = GroupBond(None, None, order=[2]) self.orderList = [[1], [2], [3], [1.5], [1,2], [2,1], [2,3], [1,2,3]]
class TestGroupBond(unittest.TestCase): """ Contains unit tests of the GroupBond class. """ def setUp(self): """ A method called before each unit test in this class. """ self.bond = GroupBond(None, None, order=[2]) self.orderList = [[1], [2], [3], [1.5], [1, 2], [2, 1], [2, 3], [1, 2, 3]] def testGetOrderStr(self): """ test the Bond.getOrderStr() method """ bond = GroupBond(None, None, order=[1, 2, 3, 1.5]) self.assertEqual(bond.getOrderStr(), ['S', 'D', 'T', 'B']) def testSetOrderStr(self): """ test the Bond.setOrderStr() method """ self.bond.setOrderStr(["B", 'T']) self.assertEqual(set(self.bond.order), set([3, 1.5])) def testGetOrderNum(self): """ test the Bond.getOrderNum() method """ self.assertEqual(self.bond.getOrderNum(), [2]) def testSetOrderNum(self): """ test the Bond.setOrderNum() method """ self.bond.setOrderNum([3, 1, 2]) self.assertEqual(self.bond.getOrderStr(), ['T', 'S', 'D']) def testApplyActionBreakBond(self): """ Test the GroupBond.applyAction() method for a BREAK_BOND action. """ action = ['BREAK_BOND', '*1', 1, '*2'] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail( 'GroupBond.applyAction() unexpectedly processed a BREAK_BOND action.' ) except ActionError: pass def testApplyActionFormBond(self): """ Test the GroupBond.applyAction() method for a FORM_BOND action. """ action = ['FORM_BOND', '*1', 1, '*2'] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail( 'GroupBond.applyAction() unexpectedly processed a FORM_BOND action.' ) except ActionError: pass def testApplyActionIncrementBond(self): """ Test the GroupBond.applyAction() method for a CHANGE_BOND action. """ action = ['CHANGE_BOND', '*1', 1, '*2'] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) except ActionError: self.assertTrue(3 in order0 or 1.5 in order0) def testApplyActionDecrementBond(self): """ Test the GroupBond.applyAction() method for a CHANGE_BOND action. """ action = ['CHANGE_BOND', '*1', -1, '*2'] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) except ActionError: self.assertTrue(1 in order0 or 1.5 in order0) def testApplyActionGainRadical(self): """ Test the GroupBond.applyAction() method for a GAIN_RADICAL action. """ action = ['GAIN_RADICAL', '*1', 1] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail( 'GroupBond.applyAction() unexpectedly processed a GAIN_RADICAL action.' ) except ActionError: pass def testApplyActionLoseRadical(self): """ Test the GroupBond.applyAction() method for a LOSE_RADICAL action. """ action = ['LOSE_RADICAL', '*1', 1] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail( 'GroupBond.applyAction() unexpectedly processed a LOSE_RADICAL action.' ) except ActionError: pass def testEquivalent(self): """ Test the GroupBond.equivalent() method. """ for order1 in self.orderList: for order2 in self.orderList: bond1 = GroupBond(None, None, order=order1) bond2 = GroupBond(None, None, order=order2) if order1 == order2 or (all([o in order2 for o in order1]) and all([o in order1 for o in order2])): self.assertTrue(bond1.equivalent(bond2)) self.assertTrue(bond2.equivalent(bond1)) else: self.assertFalse(bond1.equivalent(bond2)) self.assertFalse(bond2.equivalent(bond1)) def testIsSpecificCaseOf(self): """ Test the GroupBond.isSpecificCaseOf() method. """ for order1 in self.orderList: for order2 in self.orderList: bond1 = GroupBond(None, None, order=order1) bond2 = GroupBond(None, None, order=order2) if order1 == order2 or all([o in order2 for o in order1]): self.assertTrue(bond1.isSpecificCaseOf(bond2)) else: self.assertFalse(bond1.isSpecificCaseOf(bond2)) def testCopy(self): """ Test the GroupBond.copy() method. """ bond = self.bond.copy() self.assertEqual(len(self.bond.order), len(bond.order)) self.assertEqual(self.bond.order, bond.order) def testPickle(self): """ Test that a GroupBond object can be successfully pickled and unpickled with no loss of information. """ import cPickle bond = cPickle.loads(cPickle.dumps(self.bond)) self.assertEqual(len(self.bond.order), len(bond.order)) self.assertEqual(self.bond.order, bond.order)
def testGetOrderStr(self): """ test the Bond.getOrderStr() method """ bond = GroupBond(None,None,order = [1,2,3,1.5]) self.assertEqual(bond.getOrderStr(),['S','D','T','B'])
atom.props['inRing'] = True # Label atoms atoms[0].label = '*1' if a > 1: atoms[1].label = '*4' if a > 2: atoms[-1].label = '*5' if a > 3: for i, atom in enumerate(atoms[2:-1]): atom.label = '*{0}'.format(i + 6) # Add to group for atom in reversed(atoms): new_group.addAtom(atom) # Add bond to newly added atoms new_group.addBond( GroupBond(new_group.atoms[1], atoms[-1], order=[1, 2, 3, 1.5])) # Create bonds if a > 1: bonds = [ GroupBond(atoms[i], atoms[i + 1], order=[1, 2, 3, 1.5]) for i in range(a - 1) ] for bond in bonds: new_group.addBond(bond) # Add remaining atoms if b > 0: # Create atoms atoms = [ GroupAtom(atomType=['R!H'], radicalElectrons=None,
class TestGroupBond(unittest.TestCase): """ Contains unit tests of the GroupBond class. """ def setUp(self): """ A method called before each unit test in this class. """ self.bond = GroupBond(None, None, order=[2]) self.orderList = [[1], [2], [3], [1.5], [1,2], [2,1], [2,3], [1,2,3]] def testGetOrderStr(self): """ test the Bond.getOrderStr() method """ bond = GroupBond(None,None,order = [1,2,3,1.5]) self.assertEqual(bond.getOrderStr(),['S','D','T','B']) def testSetOrderStr(self): """ test the Bond.setOrderStr() method """ self.bond.setOrderStr(["B",'T']) self.assertEqual(set(self.bond.order), set([3,1.5])) def testGetOrderNum(self): """ test the Bond.getOrderNum() method """ self.assertEqual(self.bond.getOrderNum(),[2]) def testSetOrderNum(self): """ test the Bond.setOrderNum() method """ self.bond.setOrderNum([3,1,2]) self.assertEqual(self.bond.getOrderStr(),['T','S','D']) def testApplyActionBreakBond(self): """ Test the GroupBond.applyAction() method for a BREAK_BOND action. """ action = ['BREAK_BOND', '*1', 1, '*2'] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail('GroupBond.applyAction() unexpectedly processed a BREAK_BOND action.') except ActionError: pass def testApplyActionFormBond(self): """ Test the GroupBond.applyAction() method for a FORM_BOND action. """ action = ['FORM_BOND', '*1', 1, '*2'] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail('GroupBond.applyAction() unexpectedly processed a FORM_BOND action.') except ActionError: pass def testApplyActionIncrementBond(self): """ Test the GroupBond.applyAction() method for a CHANGE_BOND action. """ action = ['CHANGE_BOND', '*1', 1, '*2'] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) except ActionError: self.assertTrue(3 in order0 or 1.5 in order0) def testApplyActionDecrementBond(self): """ Test the GroupBond.applyAction() method for a CHANGE_BOND action. """ action = ['CHANGE_BOND', '*1', -1, '*2'] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) except ActionError: self.assertTrue(1 in order0 or 1.5 in order0) def testApplyActionGainRadical(self): """ Test the GroupBond.applyAction() method for a GAIN_RADICAL action. """ action = ['GAIN_RADICAL', '*1', 1] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail('GroupBond.applyAction() unexpectedly processed a GAIN_RADICAL action.') except ActionError: pass def testApplyActionLoseRadical(self): """ Test the GroupBond.applyAction() method for a LOSE_RADICAL action. """ action = ['LOSE_RADICAL', '*1', 1] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail('GroupBond.applyAction() unexpectedly processed a LOSE_RADICAL action.') except ActionError: pass def testEquivalent(self): """ Test the GroupBond.equivalent() method. """ for order1 in self.orderList: for order2 in self.orderList: bond1 = GroupBond(None, None, order=order1) bond2 = GroupBond(None, None, order=order2) if order1 == order2 or (all([o in order2 for o in order1]) and all([o in order1 for o in order2])): self.assertTrue(bond1.equivalent(bond2)) self.assertTrue(bond2.equivalent(bond1)) else: self.assertFalse(bond1.equivalent(bond2)) self.assertFalse(bond2.equivalent(bond1)) def testIsSpecificCaseOf(self): """ Test the GroupBond.isSpecificCaseOf() method. """ for order1 in self.orderList: for order2 in self.orderList: bond1 = GroupBond(None, None, order=order1) bond2 = GroupBond(None, None, order=order2) if order1 == order2 or all([o in order2 for o in order1]): self.assertTrue(bond1.isSpecificCaseOf(bond2)) else: self.assertFalse(bond1.isSpecificCaseOf(bond2)) def testCopy(self): """ Test the GroupBond.copy() method. """ bond = self.bond.copy() self.assertEqual(len(self.bond.order), len(bond.order)) self.assertEqual(self.bond.order, bond.order) def testPickle(self): """ Test that a GroupBond object can be successfully pickled and unpickled with no loss of information. """ import cPickle bond = cPickle.loads(cPickle.dumps(self.bond)) self.assertEqual(len(self.bond.order), len(bond.order)) self.assertEqual(self.bond.order, bond.order)
def from_old_adjacency_list(adjlist, group=False, saturate_h=False): """ Convert a pre-June-2014 string adjacency list `adjlist` into a set of :class:`Atom` and :class:`Bond` objects. It can read both "old style" that existed for years, an the "intermediate style" that existed for a few months in 2014, with the extra column of integers for lone pairs. """ atoms = [] atomdict = {} bonds = {} try: adjlist = adjlist.strip() lines = adjlist.splitlines() if adjlist == '' or len(lines) == 0: raise InvalidAdjacencyListError('Empty adjacency list.') # Skip the first line if it contains a label if len(lines[0].split()) == 1: label = lines.pop(0) if len(lines) == 0: raise InvalidAdjacencyListError( """Error in adjacency list\n{0}\nNo atoms specified.""". format(adjlist)) mistake1 = re.compile(r'\{[^}]*\s+[^}]*\}') atomic_multiplicities = { } # these are no longer stored on atoms, so we make a separate dictionary # Iterate over the remaining lines, generating Atom or GroupAtom objects for line in lines: # Sometimes people put spaces after commas, which messes up the # parse-by-whitespace. Examples include '{Cd, Ct}'. if mistake1.search(line): raise InvalidAdjacencyListError( "Error in adjacency list: \n{1}\nspecies shouldn't have spaces inside " "braces: {0}".format( mistake1.search(line).group(), adjlist)) # Sometimes commas are used to delimit bonds in the bond list, # so replace them just in case line = line.replace('},{', '} {') data = line.split() # Skip if blank line if len(data) == 0: continue # First item is index for atom # Sometimes these have a trailing period (as if in a numbered list), # so remove it just in case aid = int(data[0].strip('.')) # If second item starts with '*', then atom is labeled label = '' index = 1 if data[1][0] == '*': label = data[1] index += 1 # Next is the element or functional group element # A list can be specified with the {,} syntax atom_type = data[index] if atom_type[0] == '{': atom_type = atom_type[1:-1].split(',') else: atom_type = [atom_type] index += 1 # Next is the electron state radical_electrons = [] additional_lone_pairs = [] elec_state = data[index].upper() if elec_state[0] == '{': elec_state = elec_state[1:-1].split(',') else: elec_state = [elec_state] if len(elec_state) == 0: raise InvalidAdjacencyListError( "Error in adjacency list:\n{0}\nThere must be some electronic state defined for an " "old adjlist".format(adjlist)) for e in elec_state: if e == '0': radical_electrons.append(0) additional_lone_pairs.append(0) elif e == '1': radical_electrons.append(1) additional_lone_pairs.append(0) elif e == '2': if not group: raise InvalidAdjacencyListError( "Error in adjacency list:\n{0}\nNumber of radical electrons = 2 is not specific enough. " "Please use 2S or 2T.".format(adjlist)) # includes 2S and 2T radical_electrons.append(0) additional_lone_pairs.append(1) radical_electrons.append(2) additional_lone_pairs.append(0) elif e == '2S': radical_electrons.append(0) additional_lone_pairs.append(1) elif e == '2T': radical_electrons.append(2) additional_lone_pairs.append(0) elif e == '3': if not group: raise InvalidAdjacencyListError( "Error in adjacency list:\n{0}\nNumber of radical electrons = 3 is not specific enough. " "Please use 3D or 3Q.".format(adjlist)) # includes 3D and 3Q radical_electrons.append(1) additional_lone_pairs.append(1) radical_electrons.append(3) additional_lone_pairs.append(0) elif e == '3D': radical_electrons.append(1) additional_lone_pairs.append(1) elif e == '3Q': radical_electrons.append(3) additional_lone_pairs.append(0) elif e == '4': if not group: raise InvalidAdjacencyListError( "Error in adjacency list:\n{0}\nNumber of radical electrons = 4 is not specific enough. " "Please use 4S, 4T, or 4V.".format(adjlist)) # includes 4S, 4T, and 4V radical_electrons.append(0) additional_lone_pairs.append(2) radical_electrons.append(2) additional_lone_pairs.append(1) radical_electrons.append(4) additional_lone_pairs.append(0) elif e == '4S': radical_electrons.append(0) additional_lone_pairs.append(2) elif e == '4T': radical_electrons.append(2) additional_lone_pairs.append(1) elif e == '4V': radical_electrons.append(4) additional_lone_pairs.append(0) elif e == 'X': if not group: raise InvalidAdjacencyListError( "Error in adjacency list:\n{0}\nNumber of radical electrons = X is not specific enough. " "Wildcards should only be used for groups.".format( adjlist)) radical_electrons = [] index += 1 # Next number defines the number of lone electron pairs (if provided) lone_pairs_of_electrons = None if len(data) > index: lp_state = data[index] if lp_state[0] == '{': # this is the start of the chemical bonds - no lone pair info was provided lone_pairs_of_electrons = None else: if lp_state == '0': lone_pairs_of_electrons = 0 if lp_state == '1': lone_pairs_of_electrons = 1 if lp_state == '2': lone_pairs_of_electrons = 2 if lp_state == '3': lone_pairs_of_electrons = 3 if lp_state == '4': lone_pairs_of_electrons = 4 index += 1 else: # no bonds or lone pair info provided. lone_pairs_of_electrons = None # Create a new atom based on the above information if group: if lone_pairs_of_electrons is not None: lone_pairs_of_electrons = [ additional + lone_pairs_of_electrons for additional in additional_lone_pairs ] atom = GroupAtom( atomtype=atom_type, radical_electrons=sorted(set(radical_electrons)), charge=None, label=label, lone_pairs=lone_pairs_of_electrons, # Assign lone_pairs_of_electrons as None if it is not explicitly provided ) else: if lone_pairs_of_electrons is not None: # Intermediate adjlist representation lone_pairs_of_electrons = lone_pairs_of_electrons + additional_lone_pairs[ 0] else: # Add the standard number of lone pairs with the additional lone pairs lone_pairs_of_electrons = PeriodicSystem.lone_pairs[ atom_type[0]] + additional_lone_pairs[0] atom = Atom( element=atom_type[0], radical_electrons=radical_electrons[0], charge=0, label=label, lone_pairs=lone_pairs_of_electrons, ) # Add the atom to the list atoms.append(atom) atomdict[aid] = atom # Process list of bonds bonds[aid] = {} for datum in data[index:]: # Sometimes commas are used to delimit bonds in the bond list, # so strip them just in case datum = datum.strip(',') aid2, comma, order = datum[1:-1].partition(',') aid2 = int(aid2) if aid == aid2: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{1}\nAttempted to create a bond between ' 'atom {0:d} and itself.'.format(aid, adjlist)) if order[0] == '{': order = order[1:-1].split(',') else: order = [order] bonds[aid][aid2] = order # Check consistency using bonddict for atom1 in bonds: for atom2 in bonds[atom1]: if atom2 not in bonds: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{1}\nAtom {0:d} not in bond dictionary.' .format(atom2, adjlist)) elif atom1 not in bonds[atom2]: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{2}\nFound bond between {0:d} and {1:d}, ' 'but not the reverse'.format(atom1, atom2, adjlist)) elif bonds[atom1][atom2] != bonds[atom2][atom1]: raise InvalidAdjacencyListError( 'Error in adjacency list: \n{4}\nFound bonds between {0:d} and {1:d}, but of different orders ' '"{2}" and "{3}".'.format(atom1, atom2, bonds[atom1][atom2], bonds[atom2][atom1], adjlist)) # Convert bonddict to use Atom[group] and Bond[group] objects atomkeys = list(atomdict.keys()) atomkeys.sort() for aid1 in atomkeys: atomkeys2 = list(bonds[aid1].keys()) atomkeys2.sort() for aid2 in atomkeys2: if aid1 < aid2: atom1 = atomdict[aid1] atom2 = atomdict[aid2] order = bonds[aid1][aid2] if group: bond = GroupBond(atom1, atom2, order) elif len(order) == 1: bond = Bond(atom1, atom2, order[0]) else: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{0}\nMultiple bond orders specified ' 'for an atom.'.format(adjlist)) atom1.edges[atom2] = bond atom2.edges[atom1] = bond if not group: if saturate_h: # Add explicit hydrogen atoms to complete structure if desired new_atoms = [] for atom in atoms: try: valence = PeriodicSystem.valences[atom.symbol] except KeyError: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{1}\nCannot add hydrogens: Unknown ' 'valence for atom "{0}".'.format( atom.symbol, adjlist)) radical = atom.radical_electrons order = atom.get_total_bond_order() count = valence - radical - int( order) - 2 * (atom.lone_pairs - PeriodicSystem.lone_pairs[atom.symbol]) for i in range(count): a = Atom(element='H', radical_electrons=0, charge=0, label='', lone_pairs=0) b = Bond(atom, a, 'S') new_atoms.append(a) atom.bonds[a] = b a.bonds[atom] = b atoms.extend(new_atoms) # Calculate the multiplicity for the molecule and update the charges on each atom n_rad = 0 # total number of radical electrons for atom in atoms: atom.update_charge() n_rad += atom.radical_electrons multiplicity = n_rad + 1 # 2 s + 1, where s is the combined spin of unpaired electrons (s = 1/2 per unpaired electron) else: # Don't set a multiplicity for groups when converting from an old adjlist multiplicity = None except InvalidAdjacencyListError: logging.error("Troublesome adjacency list:\n" + adjlist) raise return atoms, multiplicity
def from_adjacency_list(adjlist, group=False, saturate_h=False): """ Convert a string adjacency list `adjlist` into a set of :class:`Atom` and :class:`Bond` objects. """ atoms = [] atom_dict = {} bonds = {} multiplicity = None adjlist = adjlist.strip() lines = adjlist.splitlines() if adjlist == '' or len(lines) == 0: raise InvalidAdjacencyListError('Empty adjacency list.') # Detect old-style adjacency lists by looking at the last line's syntax last_line = lines[-1].strip() while not last_line: # Remove any empty lines from the end lines.pop() last_line = lines[-1].strip() if re_intermediate_adjlist.match(last_line): logging.debug( "adjacency list:\n{1}\nline '{0}' looks like an intermediate style " "adjacency list".format(last_line, adjlist)) return from_old_adjacency_list(adjlist, group=group, saturate_h=saturate_h) if re_old_adjlist.match(last_line): logging.debug( "Adjacency list:\n{1}\nline '{0}' looks like an old style adjacency list" .format(last_line, adjlist)) if not group: logging.debug("Will assume implicit H atoms") return from_old_adjacency_list(adjlist, group=group, saturate_h=(not group)) # Interpret the first line if it contains a label if len(lines[0].split()) == 1: label = lines.pop(0) if len(lines) == 0: raise InvalidAdjacencyListError( 'No atoms specified in adjacency list.') # Interpret the second line if it contains a multiplicity if lines[0].split()[0] == 'multiplicity': line = lines.pop(0) if group: match = re.match( r'\s*multiplicity\s+\[\s*(\d(?:,\s*\d)*)\s*\]\s*$', line) if not match: rematch = re.match(r'\s*multiplicity\s+x\s*$', line) if not rematch: raise InvalidAdjacencyListError( "Invalid multiplicity line '{0}'. Should be a list like " "'multiplicity [1,2,3]' or a wildcard 'multiplicity x'" .format(line)) else: # should match "multiplicity [1]" or " multiplicity [ 1, 2, 3 ]" or " multiplicity [1,2,3]" # and whatever's inside the [] (excluding leading and trailing spaces) should be captured as group 1. # If a wildcard is desired, this line can be omitted or replaced with 'multiplicity x' # Multiplicities must be only one digit (i.e. less than 10) # The (?:,\s*\d)* matches patters like ", 2" 0 or more times, but doesn't capture them (because of the leading ?:) multiplicities = match.group(1).split(',') multiplicity = [int(i) for i in multiplicities] else: match = re.match(r'\s*multiplicity\s+\d+\s*$', line) if not match: raise InvalidAdjacencyListError( "Invalid multiplicity line '{0}'. Should be an integer like " "'multiplicity 2'".format(line)) multiplicity = int(line.split()[1]) if len(lines) == 0: raise InvalidAdjacencyListError( 'No atoms specified in adjacency list: \n{0}'.format(adjlist)) mistake1 = re.compile(r'\{[^}]*\s+[^}]*\}') # Iterate over the remaining lines, generating Atom or GroupAtom objects for line in lines: # Sometimes people put spaces after commas, which messes up the # parse-by-whitespace. Examples include '[Cd, Ct]'. if mistake1.search(line): raise InvalidAdjacencyListError( "{1} Shouldn't have spaces inside braces:\n{0}".format( mistake1.search(line).group(), adjlist)) # Sometimes commas are used to delimit bonds in the bond list, # so replace them just in case line = line.replace('},{', '} {') data = line.split() # Skip if blank line if len(data) == 0: continue # First item is index for atom # Sometimes these have a trailing period (as if in a numbered list), # so remove it just in case aid = int(data[0].strip('.')) # If second item starts with '*', then atom is labeled label = '' index = 1 if data[1][0] == '*': label = data[1] index += 1 # Next is the element or functional group element # A list can be specified with the {,} syntax atom_type = data[index] if atom_type[0] == '[': if not group: raise InvalidAdjacencyListError( "Error on:\n{0}\nA molecule should not assign more than one " "atomtype per atom.".format(adjlist)) atom_type = atom_type[1:-1].split(',') else: atom_type = [atom_type] index += 1 # Next the number of unpaired electrons unpaired_electrons = [] u_state = data[index] if u_state[0] == 'u': if u_state[1] == '[': u_state = u_state[2:-1].split(',') else: u_state = [u_state[1]] for u in u_state: if u == '0': unpaired_electrons.append(0) elif u == '1': unpaired_electrons.append(1) elif u == '2': unpaired_electrons.append(2) elif u == '3': unpaired_electrons.append(3) elif u == '4': unpaired_electrons.append(4) elif u == 'x': if not group: raise InvalidAdjacencyListError( "Error on:\n{0}\nA molecule should not assign a wildcard to " "number of unpaired electrons.".format(adjlist)) else: raise InvalidAdjacencyListError( 'Number of unpaired electrons not recognized on\n{0}.'. format(adjlist)) index += 1 else: raise InvalidAdjacencyListError( 'Number of unpaired electrons not defined on\n{0}.'.format( adjlist)) # Next the number of lone electron pairs (if provided) lone_pairs = [] if len(data) > index: lp_state = data[index] if lp_state[0] == 'p': if lp_state[1] == '[': lp_state = lp_state[2:-1].split(',') else: lp_state = [lp_state[1]] for lp in lp_state: if lp == '0': lone_pairs.append(0) elif lp == '1': lone_pairs.append(1) elif lp == '2': lone_pairs.append(2) elif lp == '3': lone_pairs.append(3) elif lp == '4': lone_pairs.append(4) elif lp == 'x': if not group: raise InvalidAdjacencyListError( "Error in adjacency list:\n{0}\nA molecule should not have " "a wildcard assigned to number of lone pairs.". format(adjlist)) else: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{0}\nNumber of lone electron pairs ' 'not recognized.'.format(adjlist)) index += 1 else: if not group: lone_pairs.append(0) else: if not group: lone_pairs.append(0) # Next the number of partial charges (if provided) partial_charges = [] if len(data) > index: e_state = data[index] if e_state[0] == 'c': if e_state[1] == '[': e_state = e_state[2:-1].split(',') else: e_state = [e_state[1:]] for e in e_state: if e == '0': partial_charges.append(0) elif e == '+1': partial_charges.append(1) elif e == '+2': partial_charges.append(2) elif e == '+3': partial_charges.append(3) elif e == '+4': partial_charges.append(4) elif e == '-1': partial_charges.append(-1) elif e == '-2': partial_charges.append(-2) elif e == '-3': partial_charges.append(-3) elif e == '-4': partial_charges.append(-4) elif e == 'x': if not group: raise InvalidAdjacencyListError( "Error on adjacency list:\n{0}\nA molecule should not have " "a wildcard assigned to number of charges.". format(adjlist)) else: raise InvalidAdjacencyListError( 'Error on adjacency list:\n{0}\nNumber of partial charges ' 'not recognized.'.format(adjlist)) index += 1 else: if not group: partial_charges.append(0) else: if not group: partial_charges.append(0) # Next the isotope (if provided) isotope = -1 if len(data) > index: i_state = data[index] if i_state[0] == 'i': isotope = int(i_state[1:]) index += 1 # Next ring membership info (if provided) props = {} if len(data) > index: r_state = data[index] if r_state[0] == 'r': props['inRing'] = bool(int(r_state[1])) index += 1 # Create a new atom based on the above information if group: atom = GroupAtom(atom_type, unpaired_electrons, partial_charges, label, lone_pairs, props) else: atom = Atom(atom_type[0], unpaired_electrons[0], partial_charges[0], label, lone_pairs[0]) if isotope != -1: atom.element = get_element(atom.number, isotope) # Add the atom to the list atoms.append(atom) atom_dict[aid] = atom # Process list of bonds bonds[aid] = {} for datum in data[index:]: # Sometimes commas are used to delimit bonds in the bond list, # so strip them just in case datum = datum.strip(',') aid2, comma, order = datum[1:-1].partition(',') aid2 = int(aid2) if aid == aid2: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{1}\nAttempted to create a bond between ' 'atom {0:d} and itself.'.format(aid, adjlist)) if order[0] == '[': order = order[1:-1].split(',') else: order = [order] bonds[aid][aid2] = order # Check consistency using bonddict for atom1 in bonds: for atom2 in bonds[atom1]: if atom2 not in bonds: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{1}\nAtom {0:d} not in bond ' 'dictionary.'.format(atom2, adjlist)) elif atom1 not in bonds[atom2]: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{2}\nFound bond between {0:d} and {1:d}, ' 'but not the reverse.'.format(atom1, atom2, adjlist)) elif bonds[atom1][atom2] != bonds[atom2][atom1]: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{4}\nFound bonds between {0:d} and {1:d}, but of different orders ' '"{2}" and "{3}".'.format(atom1, atom2, bonds[atom1][atom2], bonds[atom2][atom1], adjlist)) # Convert bonddict to use Atom[group] and Bond[group] objects atomkeys = list(atom_dict.keys()) atomkeys.sort() for aid1 in atomkeys: atomkeys2 = list(bonds[aid1].keys()) atomkeys2.sort() for aid2 in atomkeys2: if aid1 < aid2: atom1 = atom_dict[aid1] atom2 = atom_dict[aid2] order = bonds[aid1][aid2] if group: bond = GroupBond(atom1, atom2, order) elif len(order) == 1: bond = Bond(atom1, atom2, order[0]) else: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{0}\nMultiple bond orders specified for ' 'an atom in a Molecule.'.format(adjlist)) atom1.edges[atom2] = bond atom2.edges[atom1] = bond if saturate_h: # Add explicit hydrogen atoms to complete structure if desired if not group: Saturator.saturate(atoms) # Consistency checks if not group: # Molecule consistency check # Electron and valency consistency check for each atom for atom in atoms: ConsistencyChecker.check_partial_charge(atom) n_rad = sum([atom.radical_electrons for atom in atoms]) absolute_spin_per_electron = 1 / 2. if multiplicity is None: multiplicity = 2 * (n_rad * absolute_spin_per_electron) + 1 ConsistencyChecker.check_multiplicity(n_rad, multiplicity) for atom in atoms: ConsistencyChecker.check_hund_rule(atom, multiplicity) return atoms, multiplicity else: # Currently no group consistency check return atoms, multiplicity
class TestGroupBond(unittest.TestCase): """ Contains unit tests of the GroupBond class. """ def setUp(self): """ A method called before each unit test in this class. """ self.bond = GroupBond(None, None, order=["D"]) self.orderList = [["S"], ["D"], ["T"], ["B"], ["S", "D"], ["D", "S"], ["D", "T"], ["S", "D", "T"]] def testApplyActionBreakBond(self): """ Test the GroupBond.applyAction() method for a BREAK_BOND action. """ action = ["BREAK_BOND", "*1", "S", "*2"] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail("GroupBond.applyAction() unexpectedly processed a BREAK_BOND action.") except ActionError: pass def testApplyActionFormBond(self): """ Test the GroupBond.applyAction() method for a FORM_BOND action. """ action = ["FORM_BOND", "*1", "S", "*2"] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail("GroupBond.applyAction() unexpectedly processed a FORM_BOND action.") except ActionError: pass def testApplyActionIncrementBond(self): """ Test the GroupBond.applyAction() method for a CHANGE_BOND action. """ action = ["CHANGE_BOND", "*1", 1, "*2"] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) except ActionError: self.assertTrue("T" in order0 or "B" in order0) def testApplyActionDecrementBond(self): """ Test the GroupBond.applyAction() method for a CHANGE_BOND action. """ action = ["CHANGE_BOND", "*1", -1, "*2"] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) except ActionError: self.assertTrue("S" in order0 or "B" in order0) def testApplyActionGainRadical(self): """ Test the GroupBond.applyAction() method for a GAIN_RADICAL action. """ action = ["GAIN_RADICAL", "*1", 1] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail("GroupBond.applyAction() unexpectedly processed a GAIN_RADICAL action.") except ActionError: pass def testApplyActionLoseRadical(self): """ Test the GroupBond.applyAction() method for a LOSE_RADICAL action. """ action = ["LOSE_RADICAL", "*1", 1] for order0 in self.orderList: bond0 = GroupBond(None, None, order=order0) bond = bond0.copy() try: bond.applyAction(action) self.fail("GroupBond.applyAction() unexpectedly processed a LOSE_RADICAL action.") except ActionError: pass def testEquivalent(self): """ Test the GroupBond.equivalent() method. """ for order1 in self.orderList: for order2 in self.orderList: bond1 = GroupBond(None, None, order=order1) bond2 = GroupBond(None, None, order=order2) if order1 == order2 or (all([o in order2 for o in order1]) and all([o in order1 for o in order2])): self.assertTrue(bond1.equivalent(bond2)) self.assertTrue(bond2.equivalent(bond1)) else: self.assertFalse(bond1.equivalent(bond2)) self.assertFalse(bond2.equivalent(bond1)) def testIsSpecificCaseOf(self): """ Test the GroupBond.isSpecificCaseOf() method. """ for order1 in self.orderList: for order2 in self.orderList: bond1 = GroupBond(None, None, order=order1) bond2 = GroupBond(None, None, order=order2) if order1 == order2 or all([o in order2 for o in order1]): self.assertTrue(bond1.isSpecificCaseOf(bond2)) else: self.assertFalse(bond1.isSpecificCaseOf(bond2)) def testCopy(self): """ Test the GroupBond.copy() method. """ bond = self.bond.copy() self.assertEqual(len(self.bond.order), len(bond.order)) self.assertEqual(self.bond.order, bond.order) def testPickle(self): """ Test that a GroupBond object can be successfully pickled and unpickled with no loss of information. """ import cPickle bond = cPickle.loads(cPickle.dumps(self.bond)) self.assertEqual(len(self.bond.order), len(bond.order)) self.assertEqual(self.bond.order, bond.order)
def setUp(self): """ A method called before each unit test in this class. """ self.bond = GroupBond(None, None, order=['D']) self.orderList = [['S'], ['D'], ['T'], ['B'], ['S','D'], ['D','S'], ['D','T'], ['S','D','T']]