from openmm.app import *
from openmm import *
from openmm.unit import *

def validateConstraints(self, topology, system, constraints_value, rigidWater_value):
    """ Given a Topology, System, a value for 'constraints' and a value for
    'rigidWater', verify that the System contains the correct bonds and constraints.    

    """    
    
    # Build the set of expected constraints.
    expected_constraint_set=set()
    if rigidWater_value:
        for residue in topology.residues():
            if residue.name=="HOH":
                indices = [atom.index for atom in residue.atoms()]
                expected_constraint_set.add(tuple(sorted([indices[2],
                                                          indices[0]])))
                expected_constraint_set.add(tuple(sorted([indices[2],
                                                          indices[1]])))   
                expected_constraint_set.add(tuple(sorted([indices[1],
                                                          indices[0]])))
    if constraints_value==HBonds:
        for bond in topology.bonds():
            if bond[0].element.symbol=='H' or bond[1].element.symbol=='H':
                expected_constraint_set.add(tuple(sorted([bond[0].index, 
                                                          bond[1].index])))
    if constraints_value in set([AllBonds, HAngles]):
        bonds=[b for b in topology.bonds()]
        for b in bonds:
            expected_constraint_set.add(tuple(sorted([b[0].index, 
                                                      b[1].index])))
    if constraints_value==HAngles:
        expected_constraint_set=expected_constraint_set.union(constraintsHAngles(bonds))

    # Check that the size of the expected constraint set is the same as the
    # number of actual constraints in the system.
    self.assertEqual(len(expected_constraint_set), system.getNumConstraints())
    
    # Check each constraint in the system to see that it appears in the 
    # expected constraint set
    constraints = [system.getConstraintParameters(i) for i 
                   in range(system.getNumConstraints())]
    for c in constraints:
        self.assertTrue((c[0],c[1]) in expected_constraint_set or 
                        (c[1],c[0]) in expected_constraint_set)

    # Build the list of bonds in the topology.
    tbonds = [tuple(sorted([bond[0].index, bond[1].index])) for bond in topology.bonds()]

    # Build set of harmonic bond forces in the system.
    harmonic_bond_set = set()
    forces = system.getForces()
    for f in forces:
        if isinstance(f, HarmonicBondForce):
            for i in range(f.getNumBonds()):
                bond = f.getBondParameters(i)
                harmonic_bond_set.add(tuple(sorted([bond[0],bond[1]])))

    # Build set of constraints in the system.
    constraints = [system.getConstraintParameters(i) for i 
                   in range(system.getNumConstraints())]
    constraint_set = set()
    for c in constraints:
        constraint_set.add(tuple(sorted([c[0], c[1]])))
        
    # Check that each bond in the topology is either in the harmonic bond set or in 
    # the constraint set, but not both.
    for bond in tbonds:
        self.assertNotEqual(bond in harmonic_bond_set, bond in constraint_set)

def constraintsHAngles(bonds):
    """Given a set of bonds from a Topology, return the set of angle constraints that 
    you expect to appear in the system.  An angle is constrained if the bond sequence 
    is H-O-X or H-X-H where X is any element.

    """
    expected_constraint_set = set()

    for bond in bonds:
        if bond[0].element.symbol=='H' and bond[1].element.symbol=='O':
            indexH = bond[0].index
            indexO = bond[1].index
            for bond2 in bonds:
                if bond2[0].index==indexO and bond2[1].index!=indexH:
                    expected_constraint_set.add(tuple(sorted([indexH, bond2[1].index])))
                elif bond2[1].index==indexO and bond2[0].index!=indexH:
                    expected_constraint_set.add(tuple(sorted([indexH, bond2[0].index])))
        elif bond[1].element.symbol=='H' and bond[0].element.symbol=='O':
            indexH = bond[1].index
            indexO = bond[0].index
            for bond2 in bonds:
                if bond2[0].index==indexO and bond2[1].index!=indexH:
                    expected_constraint_set.add(tuple(sorted([indexH, bond2[1].index])))
                elif bond2[1].index==indexO and bond2[0].index!=indexH:
                    expected_constraint_set.add(tuple(sorted([indexH, bond2[0].index])))
        elif bond[0].element.symbol=='H' and bond[1].element.symbol!='O':
            indexH = bond[0].index
            indexX = bond[1].index
            for bond2 in bonds:
                if bond2[0].index==indexX and (bond2[1].index!=indexH 
                                               and bond2[1].element.symbol=='H'):
                    expected_constraint_set.add(tuple(sorted([indexH, bond2[1].index])))
                elif bond2[1].index==indexX and (bond2[0].index!=indexH 
                                                 and bond2[0].element.symbol=='H'):
                    expected_constraint_set.add(tuple(sorted([indexH, bond2[0].index])))
        elif bond[1].element.symbol=='H' and bond[0].element.symbol!='O':
            indexH = bond[1].index
            indexX = bond[0].index
            for bond2 in bonds:
                if bond2[0].index==indexX and (bond2[1].index!=indexH 
                                               and bond2[1].element.symbol=='H'):
                    expected_constraint_set.add(tuple(sorted([indexH, bond2[1].index])))
                elif bond2[1].index==indexX and (bond2[0].index!=indexH 
                                                 and bond2[0].element.symbol=='H'):
                    expected_constraint_set.add(tuple(sorted([indexH, bond2[0].index])))

    return expected_constraint_set
