Exemple #1
0
def saltbridge(poscenter, negcenter, protispos):
    """Detect all salt bridges (pliprofiler between centers of positive and negative charge)"""
    data = namedtuple(
        'saltbridge',
        'positive negative distance protispos resnr restype reschain resnr_l restype_l reschain_l'
    )
    pairings = []
    for pc, nc in itertools.product(poscenter, negcenter):
        if not config.MIN_DIST < euclidean3d(
                pc.center, nc.center) < config.SALTBRIDGE_DIST_MAX:
            continue
        resnr = pc.resnr if protispos else nc.resnr
        resnr_l = whichresnumber(
            nc.orig_atoms[0]) if protispos else whichresnumber(
                pc.orig_atoms[0])
        restype = pc.restype if protispos else nc.restype
        restype_l = whichrestype(
            nc.orig_atoms[0]) if protispos else whichrestype(pc.orig_atoms[0])
        reschain = pc.reschain if protispos else nc.reschain
        reschain_l = whichchain(nc.orig_atoms[0]) if protispos else whichchain(
            pc.orig_atoms[0])
        contact = data(positive=pc,
                       negative=nc,
                       distance=euclidean3d(pc.center, nc.center),
                       protispos=protispos,
                       resnr=resnr,
                       restype=restype,
                       reschain=reschain,
                       resnr_l=resnr_l,
                       restype_l=restype_l,
                       reschain_l=reschain_l)
        pairings.append(contact)
    return filter_contacts(pairings)
Exemple #2
0
def hydrophobic_interactions(atom_set_a, atom_set_b):
    """Detection of hydrophobic pliprofiler between atom_set_a (binding site) and atom_set_b (ligand).
    Definition: All pairs of qualified carbon atoms within a distance of HYDROPH_DIST_MAX
    """
    data = namedtuple(
        'hydroph_interaction',
        'bsatom bsatom_orig_idx ligatom ligatom_orig_idx '
        'distance restype resnr reschain restype_l, resnr_l, reschain_l')
    pairings = []
    for a, b in itertools.product(atom_set_a, atom_set_b):
        if a.orig_idx == b.orig_idx:
            continue
        e = euclidean3d(a.atom.coords, b.atom.coords)
        if not config.MIN_DIST < e < config.HYDROPH_DIST_MAX:
            continue
        restype, resnr, reschain = whichrestype(a.atom), whichresnumber(
            a.atom), whichchain(a.atom)
        restype_l, resnr_l, reschain_l = whichrestype(
            b.orig_atom), whichresnumber(b.orig_atom), whichchain(b.orig_atom)
        contact = data(bsatom=a.atom,
                       bsatom_orig_idx=a.orig_idx,
                       ligatom=b.atom,
                       ligatom_orig_idx=b.orig_idx,
                       distance=e,
                       restype=restype,
                       resnr=resnr,
                       reschain=reschain,
                       restype_l=restype_l,
                       resnr_l=resnr_l,
                       reschain_l=reschain_l)
        pairings.append(contact)
    return filter_contacts(pairings)
Exemple #3
0
def halogen(acceptor, donor):
    """Detect all halogen bonds of the type Y-O...X-C"""
    data = namedtuple('halogenbond', 'acc acc_orig_idx don don_orig_idx distance don_angle acc_angle restype '
                                     'resnr reschain restype_l resnr_l reschain_l donortype acctype sidechain')
    pairings = []
    for acc, don in itertools.product(acceptor, donor):
        dist = euclidean3d(acc.o.coords, don.x.coords)
        if not config.MIN_DIST < dist < config.HALOGEN_DIST_MAX:
            continue
        vec1, vec2 = vector(acc.o.coords, acc.y.coords), vector(acc.o.coords, don.x.coords)
        vec3, vec4 = vector(don.x.coords, acc.o.coords), vector(don.x.coords, don.c.coords)
        acc_angle, don_angle = vecangle(vec1, vec2), vecangle(vec3, vec4)
        is_sidechain_hal = acc.o.OBAtom.GetResidue().GetAtomProperty(acc.o.OBAtom, 8)  # Check if sidechain atom
        if not config.HALOGEN_ACC_ANGLE - config.HALOGEN_ANGLE_DEV < acc_angle \
               < config.HALOGEN_ACC_ANGLE + config.HALOGEN_ANGLE_DEV:
            continue
        if not config.HALOGEN_DON_ANGLE - config.HALOGEN_ANGLE_DEV < don_angle \
               < config.HALOGEN_DON_ANGLE + config.HALOGEN_ANGLE_DEV:
            continue
        restype, reschain, resnr = whichrestype(acc.o), whichchain(acc.o), whichresnumber(acc.o)
        restype_l, reschain_l, resnr_l = whichrestype(don.orig_x), whichchain(don.orig_x), whichresnumber(don.orig_x)
        contact = data(acc=acc, acc_orig_idx=acc.o_orig_idx, don=don, don_orig_idx=don.x_orig_idx,
                       distance=dist, don_angle=don_angle, acc_angle=acc_angle,
                       restype=restype, resnr=resnr,
                       reschain=reschain, restype_l=restype_l,
                       reschain_l=reschain_l, resnr_l=resnr_l, donortype=don.x.OBAtom.GetType(), acctype=acc.o.type,
                       sidechain=is_sidechain_hal)
        pairings.append(contact)
    return filter_contacts(pairings)
Exemple #4
0
def hbonds(acceptors, donor_pairs, protisdon, typ):
    """Detection of hydrogen bonds between sets of acceptors and donor pairs.
    Definition: All pairs of hydrogen bond acceptor and donors with
    donor hydrogens and acceptor showing a distance within HBOND DIST MIN and HBOND DIST MAX
    and donor angles above HBOND_DON_ANGLE_MIN
    """
    data = namedtuple('hbond', 'a a_orig_idx d d_orig_idx h distance_ah distance_ad angle type protisdon resnr '
                               'restype reschain resnr_l restype_l reschain_l sidechain atype dtype')
    pairings = []

    # DEBUG
    if protisdon:
        logger.info(f'Ligand has {len(acceptors)} acceptors')
        logger.info(f'Prot has {len(donor_pairs)} donor pairs of type {typ}')
    else:
        logger.info(f'Prot has {len(acceptors)} acceptors')
        logger.info(f'Lig has {len(donor_pairs)} donor pairs of type {typ}')

    for acc, don in itertools.product(acceptors, donor_pairs):
        #if not typ == 'strong':
            #continue # keep only the strong hydrogen bonds (default plip version)
        # Regular (strong) hydrogen bonds

        dist_ah = euclidean3d(acc.a.coords, don.h.coords)  # acceptor to H dist
        dist_ad = euclidean3d(acc.a.coords, don.d.coords)  # acceptor to donor dist
        if not config.MIN_DIST < dist_ad < config.HBOND_DIST_MAX:
            continue
        vec1, vec2 = vector(don.h.coords, don.d.coords), vector(don.h.coords, acc.a.coords)
        v = vecangle(vec1, vec2)
        if not v > config.HBOND_DON_ANGLE_MIN:
            continue
        protatom = don.d.OBAtom if protisdon else acc.a.OBAtom
        ligatom = don.d.OBAtom if not protisdon else acc.a.OBAtom
        is_sidechain_hbond = protatom.GetResidue().GetAtomProperty(protatom, 8)  # Check if sidechain atom
        resnr = whichresnumber(don.d) if protisdon else whichresnumber(acc.a)
        resnr_l = whichresnumber(acc.a_orig_atom) if protisdon else whichresnumber(don.d_orig_atom)
        restype = whichrestype(don.d) if protisdon else whichrestype(acc.a)
        restype_l = whichrestype(acc.a_orig_atom) if protisdon else whichrestype(don.d_orig_atom)
        reschain = whichchain(don.d) if protisdon else whichchain(acc.a)
        rechain_l = whichchain(acc.a_orig_atom) if protisdon else whichchain(don.d_orig_atom)
        # Next line prevents H-Bonds within amino acids in intermolecular interactions
        if config.INTRA is not None and whichresnumber(don.d) == whichresnumber(acc.a):
            continue
        # Next line prevents backbone-backbone H-Bonds
        if config.INTRA is not None and protatom.GetResidue().GetAtomProperty(protatom,
                                                                              8) and ligatom.GetResidue().GetAtomProperty(
                ligatom, 8):
            continue
        contact = data(a=acc.a, a_orig_idx=acc.a_orig_idx, d=don.d, d_orig_idx=don.d_orig_idx, h=don.h,
                       distance_ah=dist_ah, distance_ad=dist_ad, angle=v, type=typ, protisdon=protisdon,
                       resnr=resnr, restype=restype, reschain=reschain, resnr_l=resnr_l,
                       restype_l=restype_l, reschain_l=rechain_l, sidechain=is_sidechain_hbond,
                       atype=acc.a.type, dtype=don.d.type)
        pairings.append(contact)
    return filter_contacts(pairings)
Exemple #5
0
def pistacking(rings_bs, rings_lig):
    """Return all pi-stackings between the given aromatic ring systems in receptor and ligand."""
    data = namedtuple(
        'pistack',
        'proteinring ligandring distance angle offset type restype resnr reschain restype_l resnr_l reschain_l'
    )
    pairings = []
    for r, l in itertools.product(rings_bs, rings_lig):
        # DISTANCE AND RING ANGLE CALCULATION
        d = euclidean3d(r.center, l.center)
        b = vecangle(r.normal, l.normal)
        a = min(b, 180 - b if not 180 - b < 0 else
                b)  # Smallest of two angles, depending on direction of normal

        # RING CENTER OFFSET CALCULATION (project each ring center into the other ring)
        proj1 = projection(l.normal, l.center, r.center)
        proj2 = projection(r.normal, r.center, l.center)
        offset = min(euclidean3d(proj1, l.center),
                     euclidean3d(proj2, r.center))

        # RECEPTOR DATA
        resnr, restype, reschain = whichresnumber(r.atoms[0]), whichrestype(
            r.atoms[0]), whichchain(r.atoms[0])
        resnr_l, restype_l, reschain_l = whichresnumber(
            l.orig_atoms[0]), whichrestype(l.orig_atoms[0]), whichchain(
                l.orig_atoms[0])

        # SELECTION BY DISTANCE, ANGLE AND OFFSET
        passed = False
        if not config.MIN_DIST < d < config.PISTACK_DIST_MAX:
            continue
        if 0 < a < config.PISTACK_ANG_DEV and offset < config.PISTACK_OFFSET_MAX:
            ptype = 'P'
            passed = True
        if 90 - config.PISTACK_ANG_DEV < a < 90 + config.PISTACK_ANG_DEV and offset < config.PISTACK_OFFSET_MAX:
            ptype = 'T'
            passed = True
        if passed:
            contact = data(proteinring=r,
                           ligandring=l,
                           distance=d,
                           angle=a,
                           offset=offset,
                           type=ptype,
                           resnr=resnr,
                           restype=restype,
                           reschain=reschain,
                           resnr_l=resnr_l,
                           restype_l=restype_l,
                           reschain_l=reschain_l)
            pairings.append(contact)
    return filter_contacts(pairings)
Exemple #6
0
def pication(rings, pos_charged, protcharged):
    """Return all pi-Cation interaction between aromatic rings and positively charged groups.
    For tertiary and quaternary amines, check also the angle between the ring and the nitrogen.
    """
    data = namedtuple(
        'pication', 'ring charge distance offset type restype resnr reschain restype_l resnr_l reschain_l protcharged')
    pairings = []
    if len(rings) == 0 or len(pos_charged) == 0:
        return pairings
    for ring in rings:
        c = ring.center
        for p in pos_charged:
            d = euclidean3d(c, p.center)
            # Project the center of charge into the ring and measure distance to ring center
            proj = projection(ring.normal, ring.center, p.center)
            offset = euclidean3d(proj, ring.center)
            if not config.MIN_DIST < d < config.PICATION_DIST_MAX or not offset < config.PISTACK_OFFSET_MAX:
                continue
            if type(p).__name__ == 'lcharge' and p.fgroup == 'tertamine':
                # Special case here if the ligand has a tertiary amine, check an additional angle
                # Otherwise, we might have have a pi-cation interaction 'through' the ligand
                n_atoms = [a_neighbor for a_neighbor in OBAtomAtomIter(p.atoms[0].OBAtom)]
                n_atoms_coords = [(a.x(), a.y(), a.z()) for a in n_atoms]
                amine_normal = np.cross(vector(n_atoms_coords[0], n_atoms_coords[1]),
                                        vector(n_atoms_coords[2], n_atoms_coords[0]))
                b = vecangle(ring.normal, amine_normal)
                # Smallest of two angles, depending on direction of normal
                a = min(b, 180 - b if not 180 - b < 0 else b)
                if not a > 30.0:
                    resnr, restype = whichresnumber(ring.atoms[0]), whichrestype(ring.atoms[0])
                    reschain = whichchain(ring.atoms[0])
                    resnr_l, restype_l = whichresnumber(p.orig_atoms[0]), whichrestype(p.orig_atoms[0])
                    reschain_l = whichchain(p.orig_atoms[0])
                    contact = data(ring=ring, charge=p, distance=d, offset=offset, type='regular',
                                   restype=restype, resnr=resnr, reschain=reschain,
                                   restype_l=restype_l, resnr_l=resnr_l, reschain_l=reschain_l,
                                   protcharged=protcharged)
                    pairings.append(contact)
                break
            resnr = whichresnumber(p.atoms[0]) if protcharged else whichresnumber(ring.atoms[0])
            resnr_l = whichresnumber(ring.orig_atoms[0]) if protcharged else whichresnumber(p.orig_atoms[0])
            restype = whichrestype(p.atoms[0]) if protcharged else whichrestype(ring.atoms[0])
            restype_l = whichrestype(ring.orig_atoms[0]) if protcharged else whichrestype(p.orig_atoms[0])
            reschain = whichchain(p.atoms[0]) if protcharged else whichchain(ring.atoms[0])
            reschain_l = whichchain(ring.orig_atoms[0]) if protcharged else whichchain(p.orig_atoms[0])
            contact = data(ring=ring, charge=p, distance=d, offset=offset, type='regular', restype=restype,
                           resnr=resnr, reschain=reschain, restype_l=restype_l, resnr_l=resnr_l,
                           reschain_l=reschain_l, protcharged=protcharged)
            pairings.append(contact)
    return filter_contacts(pairings)
Exemple #7
0
def metal_complexation(metals, metal_binding_lig, metal_binding_bs):
    """Find all metal complexes between metals and appropriate groups in both protein and ligand, as well as water"""
    data = namedtuple(
        'metal_complex',
        'metal metal_orig_idx metal_type target target_orig_idx target_type '
        'coordination_num distance resnr restype '
        'reschain  restype_l reschain_l resnr_l location rms, geometry num_partners complexnum'
    )
    pairings_dict = {}
    pairings = []
    # #@todo Refactor
    metal_to_id = {}
    metal_to_orig_atom = {}
    for metal, target in itertools.product(
            metals, metal_binding_lig + metal_binding_bs):
        distance = euclidean3d(metal.m.coords, target.atom.coords)
        if not distance < config.METAL_DIST_MAX:
            continue
        if metal.m not in pairings_dict:
            pairings_dict[metal.m] = [
                (target, distance),
            ]
            metal_to_id[metal.m] = metal.m_orig_idx
            metal_to_orig_atom[metal.m] = metal.orig_m
        else:
            pairings_dict[metal.m].append((target, distance))
    for cnum, metal in enumerate(pairings_dict):
        rms = 0.0
        excluded = []
        # cnum +1 being the complex number
        contact_pairs = pairings_dict[metal]
        num_targets = len(contact_pairs)
        vectors_dict = defaultdict(list)
        for contact_pair in contact_pairs:
            target, distance = contact_pair
            vectors_dict[target.atom.idx].append(
                vector(metal.coords, target.atom.coords))

        # Listing of coordination numbers and their geometries
        configs = {
            2: [
                'linear',
            ],
            3: ['trigonal.planar', 'trigonal.pyramidal'],
            4: ['tetrahedral', 'square.planar'],
            5: ['trigonal.bipyramidal', 'square.pyramidal'],
            6: [
                'octahedral',
            ]
        }

        # Angle signatures for each geometry (as seen from each target atom)
        ideal_angles = {
            'linear': [[180.0]] * 2,
            'trigonal.planar': [[120.0, 120.0]] * 3,
            'trigonal.pyramidal': [[109.5, 109.5]] * 3,
            'tetrahedral': [[109.5, 109.5, 109.5, 109.5]] * 4,
            'square.planar': [[90.0, 90.0, 90.0, 90.0]] * 4,
            'trigonal.bipyramidal':
            [[120.0, 120.0, 90.0, 90.0]] * 3 + [[90.0, 90.0, 90.0, 180.0]] * 2,
            'square.pyramidal':
            [[90.0, 90.0, 90.0, 180.0]] * 4 + [[90.0, 90.0, 90.0, 90.0]],
            'octahedral': [[90.0, 90.0, 90.0, 90.0, 180.0]] * 6
        }
        angles_dict = {}

        for target in vectors_dict:
            cur_vector = vectors_dict[target]
            other_vectors = []
            for t in vectors_dict:
                if not t == target:
                    [other_vectors.append(x) for x in vectors_dict[t]]
            angles = [
                vecangle(pair[0], pair[1])
                for pair in itertools.product(cur_vector, other_vectors)
            ]
            angles_dict[target] = angles

        all_total = []  # Record fit information for each geometry tested
        gdata = namedtuple(
            'gdata',
            'geometry rms coordination excluded diff_targets')  # Geometry Data
        # Can't specify geometry with only one target
        if num_targets == 1:
            final_geom = 'NA'
            final_coo = 1
            excluded = []
            rms = 0.0
        else:
            for coo in sorted(
                    configs,
                    reverse=True):  # Start with highest coordination number
                geometries = configs[coo]
                for geometry in geometries:
                    signature = ideal_angles[
                        geometry]  # Set of ideal angles for geometry, from each perspective
                    geometry_total = 0
                    geometry_scores = [
                    ]  # All scores for one geometry (from all subsignatures)
                    used_up_targets = [
                    ]  # Use each target just once for a subsignature
                    not_used = []
                    coo_diff = num_targets - coo  # How many more observed targets are there?

                    # Find best match for each subsignature
                    for subsignature in signature:  # Ideal angles from one perspective
                        best_target = None  # There's one best-matching target for each subsignature
                        best_target_score = 999

                        for k, target in enumerate(angles_dict):
                            if target not in used_up_targets:
                                observed_angles = angles_dict[
                                    target]  # Observed angles from perspective of one target
                                single_target_scores = []
                                used_up_observed_angles = []
                                for i, ideal_angle in enumerate(subsignature):
                                    # For each angle in the signature, find the best-matching observed angle
                                    best_match = None
                                    best_match_diff = 999
                                    for j, observed_angle in enumerate(
                                            observed_angles):
                                        if j not in used_up_observed_angles:
                                            diff = abs(ideal_angle -
                                                       observed_angle)
                                            if diff < best_match_diff:
                                                best_match_diff = diff
                                                best_match = j
                                    if best_match is not None:
                                        used_up_observed_angles.append(
                                            best_match)
                                        single_target_scores.append(
                                            best_match_diff)
                                # Calculate RMS for target angles
                                target_total = sum([
                                    x**2 for x in single_target_scores
                                ])**0.5  # Tot. score targ/sig
                                if target_total < best_target_score:
                                    best_target_score = target_total
                                    best_target = target

                        used_up_targets.append(best_target)
                        geometry_scores.append(best_target_score)
                        # Total score is mean of RMS values
                        geometry_total = np.mean(geometry_scores)
                    # Record the targets not used for excluding them when deciding for a final geometry
                    [
                        not_used.append(target) for target in angles_dict
                        if target not in used_up_targets
                    ]
                    all_total.append(
                        gdata(geometry=geometry,
                              rms=geometry_total,
                              coordination=coo,
                              excluded=not_used,
                              diff_targets=coo_diff))

        # Make a decision here. Starting with the geometry with lowest difference in ideal and observed partners ...
        # Check if the difference between the RMS to the next best solution is not larger than 0.5
        if not num_targets == 1:  # Can't decide for any geoemtry in that case
            all_total = sorted(all_total, key=lambda x: abs(x.diff_targets))
            for i, total in enumerate(all_total):
                next_total = all_total[i + 1]
                this_rms, next_rms = total.rms, next_total.rms
                diff_to_next = next_rms - this_rms
                if diff_to_next > 0.5:
                    final_geom, final_coo, rms, excluded = total.geometry, total.coordination, total.rms, total.excluded
                    break
                elif next_total.rms < 3.5:
                    final_geom, final_coo, = next_total.geometry, next_total.coordination
                    rms, excluded = next_total.rms, next_total.excluded
                    break
                elif i == len(all_total) - 2:
                    final_geom, final_coo, rms, excluded = "NA", "NA", float(
                        'nan'), []
                    break

        # Record all contact pairing, excluding those with targets superfluous for chosen geometry
        only_water = set([x[0].location for x in contact_pairs]) == {'water'}
        if not only_water:  # No complex if just with water as targets
            logger.info(
                f'metal ion {metal.type} complexed with {final_geom} geometry (coo. number {final_coo}/ {num_targets} observed)'
            )
            for contact_pair in contact_pairs:
                target, distance = contact_pair
                if target.atom.idx not in excluded:
                    metal_orig_atom = metal_to_orig_atom[metal]
                    restype_l, reschain_l, resnr_l = whichrestype(
                        metal_orig_atom), whichchain(
                            metal_orig_atom), whichresnumber(metal_orig_atom)
                    contact = data(metal=metal,
                                   metal_orig_idx=metal_to_id[metal],
                                   metal_type=metal.type,
                                   target=target,
                                   target_orig_idx=target.atom_orig_idx,
                                   target_type=target.type,
                                   coordination_num=final_coo,
                                   distance=distance,
                                   resnr=target.resnr,
                                   restype=target.restype,
                                   reschain=target.reschain,
                                   location=target.location,
                                   rms=rms,
                                   geometry=final_geom,
                                   num_partners=num_targets,
                                   complexnum=cnum + 1,
                                   resnr_l=resnr_l,
                                   restype_l=restype_l,
                                   reschain_l=reschain_l)
                    pairings.append(contact)
    return filter_contacts(pairings)
Exemple #8
0
def water_bridges(bs_hba, lig_hba, bs_hbd, lig_hbd, water):
    """Find water-bridged hydrogen bonds between ligand and protein. For now only considers bridged of first degree."""
    data = namedtuple(
        'waterbridge',
        'a a_orig_idx atype d d_orig_idx dtype h water water_orig_idx distance_aw '
        'distance_dw d_angle w_angle type resnr restype reschain resnr_l restype_l reschain_l protisdon'
    )
    pairings = []
    # First find all acceptor-water pairs with distance within d
    # and all donor-water pairs with distance within d and angle greater theta
    lig_aw, prot_aw, lig_dw, prot_hw = [], [], [], []
    for w in water:
        for acc1 in lig_hba:
            dist = euclidean3d(acc1.a.coords, w.oxy.coords)
            if config.WATER_BRIDGE_MINDIST <= dist <= config.WATER_BRIDGE_MAXDIST:
                lig_aw.append((acc1, w, dist))
        for acc2 in bs_hba:
            dist = euclidean3d(acc2.a.coords, w.oxy.coords)
            if config.WATER_BRIDGE_MINDIST <= dist <= config.WATER_BRIDGE_MAXDIST:
                prot_aw.append((acc2, w, dist))
        for don1 in lig_hbd:
            dist = euclidean3d(don1.d.coords, w.oxy.coords)
            d_angle = vecangle(vector(don1.h.coords, don1.d.coords),
                               vector(don1.h.coords, w.oxy.coords))
            if config.WATER_BRIDGE_MINDIST <= dist <= config.WATER_BRIDGE_MAXDIST \
                    and d_angle > config.WATER_BRIDGE_THETA_MIN:
                lig_dw.append((don1, w, dist, d_angle))
        for don2 in bs_hbd:
            dist = euclidean3d(don2.d.coords, w.oxy.coords)
            d_angle = vecangle(vector(don2.h.coords, don2.d.coords),
                               vector(don2.h.coords, w.oxy.coords))
            if config.WATER_BRIDGE_MINDIST <= dist <= config.WATER_BRIDGE_MAXDIST \
                    and d_angle > config.WATER_BRIDGE_THETA_MIN:
                prot_hw.append((don2, w, dist, d_angle))

    for l, p in itertools.product(lig_aw, prot_hw):
        acc, wl, distance_aw = l
        don, wd, distance_dw, d_angle = p
        if not wl.oxy == wd.oxy:
            continue
        # Same water molecule and angle within omega
        w_angle = vecangle(vector(acc.a.coords, wl.oxy.coords),
                           vector(wl.oxy.coords, don.h.coords))
        if not config.WATER_BRIDGE_OMEGA_MIN < w_angle < config.WATER_BRIDGE_OMEGA_MAX:
            continue
        resnr, reschain, restype = whichresnumber(don.d), whichchain(
            don.d), whichrestype(don.d)
        resnr_l, reschain_l, restype_l = whichresnumber(
            acc.a_orig_atom), whichchain(acc.a_orig_atom), whichrestype(
                acc.a_orig_atom)
        contact = data(a=acc.a,
                       a_orig_idx=acc.a_orig_idx,
                       atype=acc.a.type,
                       d=don.d,
                       d_orig_idx=don.d_orig_idx,
                       dtype=don.d.type,
                       h=don.h,
                       water=wl.oxy,
                       water_orig_idx=wl.oxy_orig_idx,
                       distance_aw=distance_aw,
                       distance_dw=distance_dw,
                       d_angle=d_angle,
                       w_angle=w_angle,
                       type='first_deg',
                       resnr=resnr,
                       restype=restype,
                       reschain=reschain,
                       restype_l=restype_l,
                       resnr_l=resnr_l,
                       reschain_l=reschain_l,
                       protisdon=True)
        pairings.append(contact)
    for p, l in itertools.product(prot_aw, lig_dw):
        acc, wl, distance_aw = p
        don, wd, distance_dw, d_angle = l
        if not wl.oxy == wd.oxy:
            continue
        # Same water molecule and angle within omega
        w_angle = vecangle(vector(acc.a.coords, wl.oxy.coords),
                           vector(wl.oxy.coords, don.h.coords))
        if not config.WATER_BRIDGE_OMEGA_MIN < w_angle < config.WATER_BRIDGE_OMEGA_MAX:
            continue
        resnr, reschain, restype = whichresnumber(acc.a), whichchain(
            acc.a), whichrestype(acc.a)
        resnr_l, reschain_l, restype_l = whichresnumber(
            don.d_orig_atom), whichchain(don.d_orig_atom), whichrestype(
                don.d_orig_atom)
        contact = data(a=acc.a,
                       a_orig_idx=acc.a_orig_idx,
                       atype=acc.a.type,
                       d=don.d,
                       d_orig_idx=don.d_orig_idx,
                       dtype=don.d.type,
                       h=don.h,
                       water=wl.oxy,
                       water_orig_idx=wl.oxy_orig_idx,
                       distance_aw=distance_aw,
                       distance_dw=distance_dw,
                       d_angle=d_angle,
                       w_angle=w_angle,
                       type='first_deg',
                       resnr=resnr,
                       restype=restype,
                       reschain=reschain,
                       restype_l=restype_l,
                       reschain_l=reschain_l,
                       resnr_l=resnr_l,
                       protisdon=False)
        pairings.append(contact)
    return filter_contacts(pairings)