Chapter 2: Hydrogen Bonds in Protein-Ligand Interaction#

1. Introduction#

Hydrogen bonds are the most important non-covalent interactions governing drug-target binding. While individual hydrogen bonds are relatively weak (1-5 kcal/mol), a drug typically forms 3-8 hydrogen bonds with its protein target, collectively contributing significant binding energy and exquisite specificity. Understanding hydrogen bonding patterns allows medicinal chemists to design molecules that bind tightly to the intended target while avoiding off-target effects.

You might need to load these packages first


2. Key Concepts and Definitions#

  • Hydrogen Bond (H-bond): A directional electrostatic interaction between a hydrogen atom covalently bonded to an electronegative atom (donor) and another electronegative atom (acceptor), typically 1.5-3.0 Å apart.

  • Hydrogen Bond Donor (HBD): A functional group with a hydrogen atom attached to an electronegative atom (N, O, or S) that can donate its hydrogen for bonding; examples include -OH, -NH, -NH₂, and -SH groups.

  • Hydrogen Bond Acceptor (HBA): A functional group with a lone pair of electrons on an electronegative atom that can accept a hydrogen bond; examples include C=O, -O-, -N-, and aromatic nitrogen atoms.

  • Hydrogen Bond Geometry: Optimal H-bonds are linear (donor-H-acceptor angle ~180°) with donor-acceptor distances of 2.5-3.2 Å; deviations reduce bond strength exponentially.

  • Water-Mediated H-bonds: Protein-ligand hydrogen bonds bridged by structural water molecules; can provide flexibility while maintaining binding affinity and are increasingly exploited in drug design.


3. Main Content#

3.1 Hydrogen Bond Fundamentals: Geometry and Energetics#

Hydrogen bonds form when a hydrogen atom covalently bonded to an electronegative donor atom (D-H) interacts with the lone pair electrons of an acceptor atom (A).

In protein-ligand complexes, hydrogen bond energies typically range from 1-5 kcal/mol in the protein environment (weaker than in vacuum due to competition with water).

The directional nature of hydrogen bonds makes them crucial for molecular recognition—a 30° deviation from optimal geometry can reduce binding affinity by 10-fold.

Parameter

Optimal Range

Effect of Deviation

D-H···A distance

2.5 - 3.2 Å

Energy drops exponentially beyond 3.5 Å

D-H···A angle

150° - 180°

30° deviation reduces strength by ~50%

Common hydrogen bonding patterns in drug-target complexes include:

  • Backbone H-bonds: Drug forms H-bonds with protein backbone C=O or N-H groups (kinase hinge binding)

  • Side chain H-bonds: Interactions with Ser/Thr/Tyr (OH), Asp/Glu (COO⁻), Lys/Arg (NH₃⁺/guanidinium), Asn/Gln (amide)

  • Bifurcated H-bonds: One donor interacts with two acceptors (or vice versa), common with carboxylates

  • Charge-assisted H-bonds: Strongest type, involving ionic interactions (e.g., drug amino group to Asp carboxylate)

3.2 Hydrogen Bonding in Protein Active Sites#

Protein binding sites are pre-organized to form specific hydrogen bonding networks with ligands. Active sites typically contain:

Polar Residues as H-bond Partners:

  • Serine/Threonine: Hydroxyl groups serve as both donors and acceptors; frequently mutated in enzyme active sites

  • Aspartate/Glutamate: Carboxylate acceptors (or donors if protonated); critical in catalysis and ion binding

  • Lysine/Arginine: Positively charged donors; bind phosphates, carboxylates, and sulfonates

  • Asparagine/Glutamine: Amide groups provide both donor and acceptor sites; common in specificity pockets

  • Histidine: Imidazole can be donor or acceptor depending on protonation state; pH-sensitive interactions

Many binding sites contain crystallographically ordered water molecules as well. These water molecules can also mediate protein-ligand interactions by forming hydrogen-bond bridges with both the protein and the ligand

Hide code cell source

import py3Dmol
from Bio.PDB import PDBList, PDBParser, NeighborSearch
from IPython.display import display, HTML
import numpy as np
import warnings
import json
import os

# Suppress PDB warnings
warnings.filterwarnings('ignore')

class ProteinLigandWaterVisualizer:
    def __init__(self, pdb_code, ligand_resname):
        self.pdb_code = pdb_code
        self.ligand_name = ligand_resname
        self.filename = None
        self.structure = self._fetch_and_parse()
        self.hbonds = []
        
    def _fetch_and_parse(self):
        pdbl = PDBList()
        # Download and store the filename
        self.filename = pdbl.retrieve_pdb_file(self.pdb_code, pdir='.', file_format='pdb')
        parser = PDBParser()
        return parser.get_structure(self.pdb_code, self.filename)

    def calculate_interactions(self, cutoff=3.5):
        """Finds H-bonds and calculates angles for both Protein and Water."""
        atom_list = [atom for atom in self.structure.get_atoms()]
        ns = NeighborSearch(atom_list)
        
        ligand_atoms = []
        protein_atoms = []
        water_atoms = []

        # 1. Categorize Atoms
        for model in self.structure:
            for chain in model:
                for residue in chain:
                    if residue.resname == self.ligand_name:
                        for atom in residue:
                            ligand_atoms.append(atom)
                    elif residue.resname == 'HOH':
                        for atom in residue:
                            water_atoms.append(atom)
                    else:
                        for atom in residue:
                            if atom.element in ['N', 'O']:
                                protein_atoms.append(atom)

        interactions = []
        target_ligand_atoms = [a for a in ligand_atoms if a.element in ['N', 'O']]
        
        # 2. Search for neighbors
        for latom in target_ligand_atoms:
            nearby = ns.search(latom.get_coord(), cutoff, level='A')
            
            for patom in nearby:
                interaction_type = None
                if patom in protein_atoms:
                    interaction_type = 'protein'
                elif patom in water_atoms:
                    interaction_type = 'water'
                
                if interaction_type:
                    dist = latom - patom
                    
                    # --- Angle Calculation ---
                    angle_str = ""
                    l_neighbors = list(latom.get_parent().get_atoms())
                    c_neighbor = next((x for x in l_neighbors if x.element == 'C' and (x - latom) < 1.6), None)
                    
                    if c_neighbor:
                        v_base = c_neighbor.get_vector() - latom.get_vector()
                        v_target = patom.get_vector() - latom.get_vector()
                        angle = np.degrees(v_base.angle(v_target))
                        angle_str = f"∠{angle:.1f}°"

                    res_name = patom.get_parent().resname
                    res_num = patom.get_parent().id[1]
                    
                    interactions.append({
                        'start': list(latom.get_coord().astype(float)),
                        'end': list(patom.get_coord().astype(float)),
                        'dist_label': f"{res_name}{res_num}: {dist:.2f}Å",
                        'angle_label': angle_str,
                        'type': interaction_type
                    })
        
        self.hbonds = interactions

    def render(self):
        """
        Generates a self-contained HTML/JS block.
        Includes toggles for Protein, Water, Distances, and Angles.
        """
        # 1. Read the PDB file content directly
        with open(self.filename, 'r') as f:
            pdb_content = f.read()

        # 2. Serialize data for JavaScript
        pdb_content_js = json.dumps(pdb_content) 
        hbond_data_js = json.dumps(self.hbonds)
        
        # Unique ID to allow multiple visualizers on one page
        uid = f"{self.pdb_code}_{np.random.randint(0, 10000)}"
        
        html = f"""
        <script src="https://3Dmol.org/build/3Dmol-min.js"></script>

        <style>
            .mol-btn {{
                padding: 10px 16px;
                border-radius: 8px;
                cursor: pointer;
                font-weight: 600;
                font-size: 14px;
                border: 2px solid;
                transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
                position: relative;
                overflow: hidden;
                box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            }}
            
            .mol-btn:hover {{
                transform: translateY(-2px);
                box-shadow: 0 4px 8px rgba(0,0,0,0.15);
            }}
            
            .mol-btn:active {{
                transform: translateY(0) scale(0.98);
                box-shadow: 0 1px 2px rgba(0,0,0,0.1);
            }}
            
            .mol-btn-protein {{
                background: linear-gradient(135deg, #FFF9C4 0%, #FFF59D 100%);
                border-color: #FBC02D;
                color: #F57F17;
            }}
            
            .mol-btn-protein:hover {{
                background: linear-gradient(135deg, #FFF59D 0%, #FFF176 100%);
                border-color: #F9A825;
            }}
            
            .mol-btn-water {{
                background: linear-gradient(135deg, #E0F7FA 0%, #B2EBF2 100%);
                border-color: #00BCD4;
                color: #006064;
            }}
            
            .mol-btn-water:hover {{
                background: linear-gradient(135deg, #B2EBF2 0%, #80DEEA 100%);
                border-color: #00ACC1;
            }}
            
            .mol-btn-toggle {{
                background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%);
                border-color: #bdbdbd;
                color: #424242;
            }}
            
            .mol-btn-toggle:hover {{
                background: linear-gradient(135deg, #e0e0e0 0%, #d0d0d0 100%);
                border-color: #9e9e9e;
            }}
            
            .mol-btn::before {{
                content: '';
                position: absolute;
                top: 50%;
                left: 50%;
                width: 0;
                height: 0;
                border-radius: 50%;
                background: rgba(255, 255, 255, 0.5);
                transform: translate(-50%, -50%);
                transition: width 0.6s, height 0.6s;
            }}
            
            .mol-btn:active::before {{
                width: 300px;
                height: 300px;
            }}
        </style>

        <div class="mol-container" style="font-family: 'Segoe UI', sans-serif; border: 1px solid #ddd; border-radius: 8px; padding: 15px; background: #fff; box-shadow: 0 2px 5px rgba(0,0,0,0.05);">
            
            <div style="margin-bottom: 12px; border-bottom: 1px solid #eee; padding-bottom: 8px;">
                <strong style="font-size: 1.1em; color: #333;">{self.pdb_code} Interaction Viewer</strong>
                <span style="color: #666; font-size: 0.9em; margin-left: 10px;">Ligand: {self.ligand_name}</span>
            </div>

            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 12px;">
                
                <button onclick="window.toggle_{uid}('protein')" class="mol-btn mol-btn-protein">
                    Toggle Protein H-Bonds
                </button>
                
                <button onclick="window.toggle_{uid}('water')" class="mol-btn mol-btn-water">
                    Toggle Water H-Bonds
                </button>

                <button onclick="window.toggle_{uid}('distances')" class="mol-btn mol-btn-toggle">
                    Toggle Distances
                </button>

                <button onclick="window.toggle_{uid}('angles')" class="mol-btn mol-btn-toggle">
                    Toggle Angles
                </button>
            </div>

            <div id="viewer_{uid}" style="width: 100%; height: 500px; position: relative; border-radius: 4px; overflow: hidden;"></div>

            <script>
            (function() {{
                let viewer = $3Dmol.createViewer("viewer_{uid}");
                let pdbData = {pdb_content_js};
                let hbonds = {hbond_data_js};
                
                // State to track what is visible
                let state = {{
                    protein: false,   // Protein bonds start hidden
                    water: false,     // Water bonds start hidden
                    distances: true,  // Distances enabled by default
                    angles: true      // Angles enabled by default
                }};

                // Initialize Scene
                viewer.addModel(pdbData, "pdb");
                viewer.setStyle({{}}, {{line: {{hidden: true}}}}); 
                
                // Ligand Style (Green)
                viewer.addStyle({{resn: '{self.ligand_name}'}}, {{stick: {{colorscheme: 'greenCarbon', radius: 0.4}}}});
                
                // Protein Pocket Style (Gray)
                viewer.addStyle({{within: {{distance: 5, sel: {{resn: '{self.ligand_name}'}}}}, elem: 'C', byres: true}}, 
                                {{stick: {{colorscheme: 'grayCarbon', radius: 0.15}}}});
                viewer.addStyle({{within: {{distance: 5, sel: {{resn: '{self.ligand_name}'}}}}, not: {{elem: 'C'}}, byres: true}}, 
                                {{stick: {{colorscheme: 'grayCarbon', radius: 0.15}}}});
                
                // Water Style (Red Spheres)
                viewer.addStyle({{within: {{distance: 5, sel: {{resn: '{self.ligand_name}'}}}}, resn: 'HOH'}}, 
                                {{sphere: {{color: 'red', radius: 0.5}}}});

                viewer.zoomTo({{resn: '{self.ligand_name}'}});
                viewer.render();

                // Function to draw interactions based on current state
                function drawInteractions() {{
                    // Clear existing shapes (cylinders/labels)
                    viewer.removeAllShapes();
                    viewer.removeAllLabels();

                    for (let i = 0; i < hbonds.length; i++) {{
                        let hb = hbonds[i];
                        let isVisible = false;

                        // Only show if the bond type (protein/water) is active
                        if (hb.type === 'protein' && state.protein) isVisible = true;
                        if (hb.type === 'water' && state.water) isVisible = true;

                        if (isVisible) {{
                            let color = (hb.type === 'protein') ? '#FBC02D' : '#00BCD4';
                            
                            // Draw Dashed Line
                            viewer.addCylinder({{
                                start: {{x: hb.start[0], y: hb.start[1], z: hb.start[2]}},
                                end: {{x: hb.end[0], y: hb.end[1], z: hb.end[2]}},
                                radius: 0.08,
                                color: color,
                                dashed: true,
                                gap: 0.2
                            }});

                            // 1. Draw Distance Label
                            if (state.distances) {{
                                viewer.addLabel(hb.dist_label, {{
                                    position: {{x: (hb.start[0]+hb.end[0])/2, y: (hb.start[1]+hb.end[1])/2, z: (hb.start[2]+hb.end[2])/2}},
                                    backgroundColor: 'rgba(255,255,255,0.8)',
                                    fontColor: 'black',
                                    fontSize: 10,
                                    showBackground: true,
                                    backgroundOpacity: 0.8
                                }});
                            }}
                            
                            // 2. Draw Angle Label (only if it exists and angles are enabled)
                            if (state.angles && hb.angle_label) {{
                                viewer.addLabel(hb.angle_label, {{
                                    position: {{x: hb.end[0], y: hb.end[1], z: hb.end[2]}},
                                    backgroundColor: color,
                                    fontColor: 'black',
                                    fontSize: 9,
                                    showBackground: true
                                }});
                            }}
                        }}
                    }}
                    viewer.render();
                }}

                // Expose toggle function to global window
                window.toggle_{uid} = function(type) {{
                    if (state.hasOwnProperty(type)) {{
                        state[type] = !state[type];
                        drawInteractions();
                    }}
                }};

                // Initial Draw
                drawInteractions();

            }})();
            </script>
        </div>
        """
        display(HTML(html))

# --- USAGE ---
viz = ProteinLigandWaterVisualizer('1STP', 'BTN')
viz.calculate_interactions(cutoff=3.3)
viz.render()
Structure exists: '.\pdb1stp.ent'
1STP Interaction Viewer Ligand: BTN

4. Practical Applications#

  • Structure-Based Drug Design (SBDD): In precision health, researchers use high-resolution crystal structures of a target protein to design new drugs. For example, when designing a novel kinase inhibitor for cancer therapy, chemists will computationally place a molecule in the ATP binding site and systematically modify its structure to add functional groups (like an amine or carbonyl) that can form new hydrogen bonds with specific residues (e.g., the “hinge region” backbone), thereby increasing the drug’s potency and selectivity.

  • Lead Optimization for Improved Potency: During the drug development process, a “lead” compound may have moderate activity. Medicinal chemists will analyze its binding mode and identify locations where an existing “bad” water molecule (one with poor H-bonds) can be displaced by a new functional group on the drug. By adding a hydroxyl or amide group to the drug that forms a strong H-bond with the protein, they can achieve a 10- to 100-fold improvement in binding affinity.

  • Fragment-Based Drug Discovery (FBDD): This technique is used to find starting points for new drugs against difficult targets. Scientists screen libraries of very small molecules (“fragments”) to find ones that bind weakly but efficiently. Often, a successful fragment is one that makes a single, high-quality hydrogen bond. For instance, a fragment might be found that H-bonds to a key aspartate residue in a protease active site. This fragment then serves as an anchor point to be “grown” into a larger, more potent drug molecule.


5. Summary and Key Takeaways#

In this section, we’ve explored the fundamental role of hydrogen bonds in defining the interaction between a drug and its protein target. We dissected the specific geometry and energetic considerations of these bonds and used the antiviral drug Oseltamivir as a case study to see how a network of conventional and charge-assisted hydrogen bonds creates a high-affinity interaction.

  • Hydrogen bonds are directional interactions between a donor (HBD) and an acceptor (HBA) with a defined distance and angle.

  • Protein side chains and the backbone can both act as HBDs and HBAs.

  • Charge-assisted hydrogen bonds (salt bridges) are particularly strong and serve as critical anchors for charged drug molecules.

  • Visualizing interactions with tools like PyMOL is essential for understanding and designing drugs.

  • The formation of a protein-ligand hydrogen bond must overcome the energetic cost of desolvating both surfaces.

Understanding how to identify and engineer these bonds is a cornerstone of modern drug design. In the next section, we will examine the subtler but collectively powerful Van der Waals forces that help shape the final fit and affinity of a drug.

Reflection Moment The arginine triad (ARG 118, 292, 371) is highly conserved across influenza A strains. How does this structural feature explain why Oseltamivir is a broad-spectrum antiviral? What might happen to the drug’s effectiveness if a mutation occurred in one of these arginine residues?