Lab 5: Proteinfolding#

I moderne biokjemi er beregningsbaserte verktøy blitt veldig viktige. Denne datalaben gir deg erfaring med to svært ulike tilnærminger til proteinstruktur: atomær simulering (her: Monte Carlo-simulering) og KI-drevet prediksjon. Du vil se hva en Monte Carlo-simulering faktisk gjør – og hva den ikke klarer – og bruke dette som utgangspunkt for å forstå hvorfor metoder basert på kunstig intelligens, som AlphaFold, representerer et gjennombrudd. Gjennom praktiske analyser i PyMOL og AlphaFold ska du i denne laben arbeide med proteinstrukturer, legemiddelbinding, mutasjonseffekter og prediksjonsusikkerhet.

Del 1: Monte-Carlo-simuleringer#

I denne simuleringen bruker vi en Monte Carlo-metode (MC) for å utforske hvordan et protein kan folde seg. I stedet for å følge proteinets faktiske bevegelse over tid (slik molekylærdynamikk gjør), foreslår vi tilfeldige endringer i proteinets form — her ved å rotere en del av kjeden rundt et tilfeldig punkt. For hvert forslag beregner vi om den nye konformasjonen har lavere eller høyere energi enn den forrige. Hvis energien går ned, aksepterer vi alltid det nye forslaget. Hvis energien går opp, aksepterer vi det likevel med en viss sannsynlighet gitt av Metropolis-kriteriet:

\[p=e^{−ΔE/Tp} = e^{-\Delta E / T}\]

ΔE er energiøkningen og T er temperaturen. Dette betyr at små forverringer aksepteres oftere enn store, og at høy temperatur gjør systemet mer «eventyrlystent». Denne mekanismen hindrer simuleringen i å sette seg fast i en dårlig konformasjon tidlig, og lar den utforske et bredt landskap av mulige strukturer. Etter hvert senkes temperaturen gradvis — dette kalles simulert annealing — slik at systemet til slutt konvergerer mot en stabil, lavenergikonformasjon.

Kjør koden nedenfor. Denne implementerer en enkel foldealgoritme basert på MC med hydrofobe residuer (H) og hydrofile/polare residuer (P). Se animasjonen og beskriv hva du observerer:

  • Observer foldingen. Hvordan endrer proteinets konformasjon seg over tid?

  • Hvordan varierer energien under simuleringen? Legg merke til både den nåværende energien og den beste energien som oppnås underveis.

  • Hvilken rolle spiller tilfeldige bevegelser i prosessen?

  • Hvilke faktorer tar modellen hensyn til?

  • Hva mangler for å gjøre simuleringen mer realistisk?

Endre parameteren “sequence” systematisk og kjør simuleringen. Prøv ulike sekvenser: En med flere/bare hydrofobe (H) aminosyrer, en med flere/bare polare (P) aminosyrer, og blandede sekvenser.

  • Diskuter hvordan aminosyresekvensen påvirker den endelige foldingen. Hvilke faktorer er viktige i denne modellen, og hvilke effekter er viktige i virkeligheten? Hvordan påvirker antall H og P den endelige energien?

Tips: Det kan være lurt å bruke plot_best_configuration-funksjonen (som plottes til slutt) for å visualisere den beste konformasjonen for hver sekvens du tester.

import random
import math
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.animation import FuncAnimation
from IPython.display import HTML

# ─────────────────────────────────────────────────────────────
#  HP-MODELL FOR PROTEINFOLDING (Monte Carlo / simulert annealing)
#
#  Energifunksjon: Gaussisk brønnpotensial mellom H–H-par.
#    E_ij = –exp(–(r – 1)² / 0.5)  for r < 3.0
#  Minimum ved r = 1 (E = –1), avtar jevnt mot 0 med avstand.
#  Energien er et kontinuerlig (ikke-heltalls) tall.
#
#  Trekk: KUN pivot-rotasjoner (90°/180°/270° om én akse).
#  Pivot-trekk garanterer at alle bindingslengder forblir = 1,
#  slik at kjeden aldri brytes.
#
#  Algoritme: simulert annealing med eksponentiell kjøleplan.
# ─────────────────────────────────────────────────────────────


### ENERGIFUNKSJON ###

def beregn_energi(posisjoner, sekvens):
    """
    Beregner total energi ved hjelp av et Gaussisk brønnpotensial
    mellom alle ikke-bundne H–H-par med avstand < 3.0.

    Potensial: E_ij = –exp(–(r – 1)² / 0.5)
    Minimum (–1) når r = 1, avtar jevnt mot 0 ved større avstand.
    """
    energi = 0.0
    n      = len(sekvens)
    cutoff = 3.0
    for i in range(n):
        for j in range(i + 2, n):
            if sekvens[i] == 'H' and sekvens[j] == 'H':
                dx = posisjoner[i][0] - posisjoner[j][0]
                dy = posisjoner[i][1] - posisjoner[j][1]
                dz = posisjoner[i][2] - posisjoner[j][2]
                r  = math.sqrt(dx*dx + dy*dy + dz*dz)
                if r < cutoff:
                    energi += -math.exp(-((r - 1)**2) / 0.5)
    return energi


### STARTKONFIGURASJON ###

def generer_startposisjoner(sekvens):
    """
    Legger alle residuer på rad langs x-aksen (utstrakt kjede).
    Bindingslengde mellom naboer er 1 overalt.
    """
    return [(float(i), 0.0, 0.0) for i in range(len(sekvens))]


### PIVOT-TREKK ###

def roter_vektor(v, matrise):
    x, y, z = v
    return (
        matrise[0][0]*x + matrise[0][1]*y + matrise[0][2]*z,
        matrise[1][0]*x + matrise[1][1]*y + matrise[1][2]*z,
        matrise[2][0]*x + matrise[2][1]*y + matrise[2][2]*z,
    )

ROTASJONER = [
    [[1,0,0],[0,0,-1],[0,1,0]],    # x  90°
    [[1,0,0],[0,-1,0],[0,0,-1]],   # x 180°
    [[1,0,0],[0,0,1],[0,-1,0]],    # x 270°
    [[0,0,1],[0,1,0],[-1,0,0]],    # y  90°
    [[-1,0,0],[0,1,0],[0,0,-1]],   # y 180°
    [[0,0,-1],[0,1,0],[1,0,0]],    # y 270°
    [[0,-1,0],[1,0,0],[0,0,1]],    # z  90°
    [[-1,0,0],[0,-1,0],[0,0,1]],   # z 180°
    [[0,1,0],[-1,0,0],[0,0,1]],    # z 270°
]

def forsøk_pivot(posisjoner, sekvens, temperatur, grenser):
    """
    Pivot-trekk:
      1. Velg et tilfeldig indre residuum som pivot.
      2. Roter alle residuer på den ene siden av pivoten med en
         tilfeldig 90°/180°/270°-rotasjon om én akse.
      3. Avvis trekket dersom det oppstår overlapp eller grensebrudd.
      4. Aksepter/forkast etter Metropolis-kriteriet:
           – Alltid aksepter dersom ny energi ≤ gammel energi.
           – Aksepter med sannsynlighet exp(–ΔE / T) ellers.

    Pivot-trekk bevarer alltid bindingslengde = 1 for alle nabopar.
    """
    n = len(posisjoner)
    if n < 3:
        return posisjoner, False

    pivot     = random.randint(1, n - 2)
    pivot_pos = posisjoner[pivot]
    rotasjon  = random.choice(ROTASJONER)

    nye_pos = list(posisjoner)
    for i in range(pivot + 1, n):
        rel = (
            posisjoner[i][0] - pivot_pos[0],
            posisjoner[i][1] - pivot_pos[1],
            posisjoner[i][2] - pivot_pos[2],
        )
        rotert = roter_vektor(rel, rotasjon)
        nye_pos[i] = (
            pivot_pos[0] + rotert[0],
            pivot_pos[1] + rotert[1],
            pivot_pos[2] + rotert[2],
        )

    # Avvis ved overlapp (to residuer på samme posisjon)
    if len(set(nye_pos)) < n:
        return posisjoner, False

    # Avvis ved grensebrudd
    for pos in nye_pos:
        if not (grenser['x'][0] <= pos[0] <= grenser['x'][1] and
                grenser['y'][0] <= pos[1] <= grenser['y'][1] and
                grenser['z'][0] <= pos[2] <= grenser['z'][1]):
            return posisjoner, False

    gammel_E = beregn_energi(posisjoner, sekvens)
    ny_E     = beregn_energi(nye_pos, sekvens)
    delta_E  = ny_E - gammel_E

    if delta_E <= 0 or random.random() < math.exp(-delta_E / temperatur):
        return nye_pos, True
    return posisjoner, False


### SIMULERT ANNEALING ###

def kjør_simulering(
    sekvens          = "HPPPHHHHPPHHHH",
    antall_steg      = 20000,
    starttemperatur  = 2.0,
    slutttemperatur  = 0.05,
    opptaksintervall = 50,
    grenser          = None,
):
    """
    Kjører MC-simuleringen med simulert annealing.

    Temperaturen kjøles ned eksponentielt fra starttemperatur til
    slutttemperatur: T(steg) = T_start · (T_slutt/T_start)^(steg/N)
    Dette gir god utforskning tidlig og fin konvergens mot slutten.

    Parametre:
    ----------
    sekvens          : Aminosyresekvens med 'H' og 'P'
    antall_steg      : Totalt antall pivot-forsøk
    starttemperatur  : Høy T → mange trekk aksepteres (utforskning)
    slutttemperatur  : Lav T → nesten bare forbedringer aksepteres
    opptaksintervall : Lagre konformasjon for animasjon hvert N-te steg
    """
    if grenser is None:
        grenser = {
            'x': (-10, len(sekvens) + 10),
            'y': (-10, 10),
            'z': (-10, 10),
        }

    posisjoner    = generer_startposisjoner(sekvens)
    energi        = beregn_energi(posisjoner, sekvens)
    beste_pos     = list(posisjoner)
    beste_energi  = energi
    pos_historikk = [list(posisjoner)]
    E_historikk   = [energi]

    kjølefaktor = (slutttemperatur / starttemperatur) ** (1.0 / antall_steg)
    T = starttemperatur

    for steg in range(1, antall_steg + 1):
        T *= kjølefaktor
        posisjoner, _ = forsøk_pivot(posisjoner, sekvens, T, grenser)
        energi = beregn_energi(posisjoner, sekvens)

        if energi < beste_energi:
            beste_energi = energi
            beste_pos    = list(posisjoner)

        if steg % opptaksintervall == 0:
            pos_historikk.append(list(posisjoner))
            E_historikk.append(energi)

    return beste_pos, beste_energi, pos_historikk, E_historikk, grenser


### ANIMASJON ###

def animer_folding(
    sekvens          = "HPPPHHHHPPHHHH",
    antall_steg      = 20000,
    starttemperatur  = 2.0,
    slutttemperatur  = 0.05,
    opptaksintervall = 50,
    grenser          = None,
    intervall_ms     = 100,
):
    """
    Kjører simuleringen og animerer foldingsprosessen.
    Røde kuler = H (hydrofobe), blå kuler = P (polare).

    Returner animasjonsobjektet. Vis det med:
        HTML(anim.to_jshtml())
    som siste linje i Jupyter-cellen.
    """
    beste_pos, beste_energi, pos_historikk, E_historikk, grenser = kjør_simulering(
        sekvens, antall_steg, starttemperatur, slutttemperatur,
        opptaksintervall, grenser,
    )

    fig = plt.figure(figsize=(6, 6))
    ax  = fig.add_subplot(111, projection='3d')
    fargekart = {'H': 'crimson', 'P': 'steelblue'}

    def oppdater(bilde):
        ax.clear()
        pos    = pos_historikk[bilde]
        xs     = [p[0] for p in pos]
        ys     = [p[1] for p in pos]
        zs     = [p[2] for p in pos]
        farger = [fargekart.get(r, 'gray') for r in sekvens]
        ax.scatter(xs, ys, zs, c=farger, s=120)
        for i in range(len(pos) - 1):
            ax.plot(
                [pos[i][0], pos[i+1][0]],
                [pos[i][1], pos[i+1][1]],
                [pos[i][2], pos[i+1][2]],
                color='gray', linewidth=2,
            )

        # Tette aksegrenser rundt kjeden så den alltid fyller plottet
        margin     = 2
        cx         = (max(xs) + min(xs)) / 2
        cy         = (max(ys) + min(ys)) / 2
        cz         = (max(zs) + min(zs)) / 2
        rekkevidde = max(max(xs)-min(xs), max(ys)-min(ys), max(zs)-min(zs), 4) / 2 + margin
        ax.set_xlim(cx - rekkevidde, cx + rekkevidde)
        ax.set_ylim(cy - rekkevidde, cy + rekkevidde)
        ax.set_zlim(cz - rekkevidde, cz + rekkevidde)

        beste_hittil = min(E_historikk[:bilde + 1])
        ax.set_title(
            f"Steg: {bilde * opptaksintervall}\n"
            f"Nåværende energi: {E_historikk[bilde]:.3f}   "
            f"Beste energi: {beste_hittil:.3f}",
            fontsize=9,
        )
        ax.set_xlabel("X")
        ax.set_ylabel("Y")
        ax.set_zlabel("Z")

    anim = FuncAnimation(
        fig, oppdater, frames=len(pos_historikk),
        interval=intervall_ms, blit=False, repeat=False,
    )
    print(f"Beste energi funnet: {beste_energi:.3f}")
    plt.close()
    return anim, beste_pos, beste_energi


### PLOT AV BESTE KONFIGURASJON ###

def plot_beste_konfigurasjon(beste_pos, beste_energi, sekvens):
    """
    Viser den beste konformasjonen som ble funnet under simuleringen.
    Kjør denne i en egen celle etter animasjonscellen.
    """
    fig = plt.figure(figsize=(6, 6))
    ax  = fig.add_subplot(111, projection='3d')
    fargekart = {'H': 'crimson', 'P': 'steelblue'}
    xs     = [p[0] for p in beste_pos]
    ys     = [p[1] for p in beste_pos]
    zs     = [p[2] for p in beste_pos]
    farger = [fargekart.get(r, 'gray') for r in sekvens]
    ax.scatter(xs, ys, zs, c=farger, s=100)
    for i in range(len(beste_pos) - 1):
        ax.plot(
            [beste_pos[i][0], beste_pos[i+1][0]],
            [beste_pos[i][1], beste_pos[i+1][1]],
            [beste_pos[i][2], beste_pos[i+1][2]],
            color='gray', linewidth=2,
        )
    ax.set_xlim((-10, len(sekvens) + 10))
    ax.set_ylim((-10, 10))
    ax.set_zlim((-10, 10))
    ax.set_xlabel("X")
    ax.set_ylabel("Y")
    ax.set_zlabel("Z")
    ax.set_title(f"Beste konfigurasjon\nEnergi: {beste_energi:.3f}")
    plt.show()
# ═══════════════════════════════════════════════════════════════
#  KJØR SIMULERINGEN HER – endre sekvens og parametere etter ønske
# ═══════════════════════════════════════════════════════════════

sekvens = "HPPPHHHHPPHHHH"

anim, beste_pos, beste_energi = animer_folding(
    sekvens          = sekvens,
    antall_steg      = 20000,
    starttemperatur  = 2.0,
    slutttemperatur  = 0.05,
    opptaksintervall = 50,
    intervall_ms     = 100,
)

# HTML(anim.to_jshtml()) må være siste linje i cellen for å vise animasjonen
HTML(anim.to_jshtml())
Beste energi funnet: -18.890
Animation size has reached 20994873 bytes, exceeding the limit of 20971520.0. If you're sure you want a larger animation embedded, set the animation.embed_limit rc parameter to a larger value (in MB). This and further frames will be dropped.

Del 2: Proteinfolding med kunstig intelligens#

Simuleringen ovenfor illustrerer proteinfolding ved hjelp av en sterkt forenklet modell. Å modellere folding av store proteiner er langt mer utfordrende, blant annet fordi det krever presis beskrivelse av en rekke ikke-kovalente interaksjoner – som hydrogenbindinger, hydrofobe effekter, van der Waals-krefter og elektrostatiske interaksjoner – samt en vurdering av hvilke som dominerer i ulike kontekster. Selve foldingen skjer gjennom en sekvens av konformasjonsendringer, hvor tidligere strukturelle valg begrenser de påfølgende mulighetene. Denne prosessen beskrives ofte som en bevegelse gjennom et komplekst konformasjons- eller energilandskap. Til tross for betydelige fremskritt, er mekanismene bak hvordan proteiner finner sin biologisk aktive struktur fortsatt et aktivt forskningsfelt.

Kunstig intelligens (AI) har revolusjonert feltet proteinfolding, spesielt med utviklingen av AlphaFold, som bruker dype læringsmetoder for å forutsi proteinstrukturer med høy nøyaktighet. AlphaFold er trent på store datasett av kjente proteinstrukturer og deres sekvenser, og bruker denne informasjonen til å lage prediksjoner om hvordan nye proteiner vil folde seg. Dette har ført til betydelige fremskritt innen biomedisinsk forskning, legemiddelutvikling og forståelse av sykdomsmekanismer.

AlphaFold er en AI-modell utviklet av DeepMind som har gjort betydelige fremskritt innen proteinfolding. Den bruker dype nevrale nettverk for å forutsi proteinstrukturer basert på aminosyresekvenser. AlphaFold er i stand til å forutsi proteinstrukturer med høy nøyaktighet, selv for store og komplekse proteiner. Dette er en betydelig forbedring sammenlignet med tidligere metoder, som ofte var tidkrevende og krevde mye eksperimentering. AlphaFold bruker en kombinasjon av dype nevrale nettverk og fysikkbaserte modeller for å lage prediksjoner om proteinfolding. Dette gjør det mulig å forutsi strukturer på en raskere og mer effektiv måte enn tidligere metoder.

AlphaFold har blitt brukt til å forutsi strukturer av mange kjente proteiner, og har også vært nyttig i legemiddelutvikling og forståelse av sykdomsmekanismer. Det er imidlertid viktig å merke seg at AlphaFold ikke er en perfekt løsning, og det er fortsatt utfordringer knyttet til å forutsi strukturer av store og komplekse proteiner. I tillegg er det fortsatt behov for eksperimentelle metoder for å validere prediksjonene gjort av AlphaFold.

Tenk tilbake på simuleringen du nettopp kjørte. Et protein på bare 14 aminosyrer trengte tusenvis av tilfeldige forsøk for å finne en stabil konformasjon – og modellen var sterkt forenklet. Et reelt protein med 300 aminosyrer har omtrent 10\(^600\) mulige konformasjoner (dette kalles gjerne Levinthal-paradokset). Likevel folder proteiner seg spontant på mikro- til millisekunder i cellen.

Diskuter følgende:

  • Hvorfor er det umulig å løse proteinfolding ved uttømmende søk gjennom alle konformasjoner, og hva forteller dette om begrensningene til atomære simuleringer, slik som den du brukte ovenfor?

  • AlphaFold bruker ikke atomære/fysikkbaserte simuleringer i det hele tatt – den er trent på kjente strukturer og lærer mønstre direkte fra data. Hva er fordelene og ulempene med denne tilnærmingen sammenlignet med simulering?

Oppgave A: Influensavirus og legemiddelutvikling#

Influensaviruset har to glykoproteiner på overflaten: hemagglutinin og neuraminidase. Hemagglutinin er ansvarlig for binding til sialinsyrereseptorer og initiering av infeksjon, mens neuraminidase kløyver av sialinsyre og gjør at viruset kan spre seg videre i kroppen.

Vi skal bruke AlphaFold til å forutsi strukturen til neuraminidase, et protein som er viktig for influensavirusets evne til å infisere celler. Neuraminidase er et enzym som bryter ned sialinsyre, en sukkerart som finnes på overflaten av celler. Dette enzymet er avgjørende for virusets evne til å spre seg i vertscellen. Studiet av neuraminidase er viktig for forståelsen av influensavirusets biologi og utviklingen av antivirale legemidler. Neuraminidase er også et mål for vaksiner mot influensa, og forståelsen av proteinets struktur og funksjon kan bidra til utviklingen av mer effektive vaksiner.

Du skal bruke Pymol til å visualisere molekylene i denne oppgaven, og du skal bruke alphafoldserver.com til å gjøre strukturprediksjone AlphaFold. Siden AlphaFold tar utgangspunkt i aminosyresekvensen, skal vi bruke FASTA-formatet for å representere sekvensen. FASTA er en vanlig måte å representere biologiske sekvenser på, og det brukes ofte i bioinformatikk for å lagre og dele sekvenser av DNA, RNA og proteiner. En FASTA-fil inneholder en eller flere sekvenser, hver med en beskrivelse som begynner med et “>”-tegn etterfulgt av en identifikator og eventuelt annen informasjon. Sekvensen følger deretter på neste linje med enbokstavekodene som representerer aminosyrene i proteinet.

Kopier FASTA-sekvensen for neuraminidase: https://www.rcsb.org/fasta/entry/2HT8/display. Merk deg hvordan du finner FASTA-sekvensen fra PDB-strukturen. Du kan bruke RCSB PDB-nettstedet til å søke etter strukturen og deretter laste ned sekvensen i FASTA-format.

Gå til alphafoldserver.com og lim inn FASTA-sekvensen. Trykk på “Continue and preview job” og deretter “Confirm and submit”. Vent til jobben er fullført. Last deretter ned prediksjonsdataene. Det inneholder flere modeller som Alphafold har prediktert, og data om prediksjonen (i .json-format). Vi er her interesserte i strukturfilene (i .cif-format). Åpne den første modellen (modell 0) i Pymol og gi den det nye navnet “prediksjon”. Vi skal sammenligne denne med den eksperimentelt bestemte strukturen (PDB 2HT8). Last ned PDB-filen fra RCSB PDB-nettstedet og åpne den også i Pymol. Gi dem ulike navn i Pymol, slik at du kan skille dem fra hverandre.

Vi ønsker kun å sammenligne de to strukturene i proteinet, uten alle tilleggsmolekyler. For å gjøre dette, må vi velge ut de atomene vi er interessert i. I dette tilfellet ønsker vi å sammenligne alfa-karbonatomene (CA) i de to strukturene. Dette kan gjøres ved å bruke seleksjonsverktøyet i Pymol:

align 2ht8 and polymer and name CA, prediksjon and polymer and name CA, object=alnObj, cycles=5, transform=1

Her betyr “name CA” at du kun velger atomer med navnet “CA” (alfa-karbonatomer). Alfa-karbonatomene er standarden når vi skal beregne gjennomsnittlig avstand mellom prediksjon og eksperimentelle data, siden de finnes i alle aminosyrer og gir et konsistent mål på strukturell likhet.

Selve kommandoen “align” brukes til å sammenligne og overlappe to strukturer basert på posisjonene til alfa-karbonatomene. I dette tilfellet sammenlignes strukturen med PDB-ID 2ht8 (eksperimentell struktur) med prediksjon (en modellert eller beregnet struktur). Begge begrenses til kun å inkludere polymerkjeder og alfa-karbonatomer. Argumentet object=alnObj angir at resultatet av justeringen skal lagres i et nytt PyMOL-objekt kalt alnObj, slik at man visuelt kan se overlappet etterpå. Videre sier cycles=5 at PyMOL skal gjøre fem iterasjoner med superposisjonering og utvalg av samsvarende atomer for å optimalisere justeringen. Til slutt betyr transform=1 at prediksjon-strukturen faktisk blir flyttet (transformert) slik at den best overlapper med 2ht8. Hvis transform=0, ville ingen strukturer blitt flyttet, og vi ville kun fått RMSD-verdien (avstanden mellom strukturene) uten visuell justering.

Når du bruker align-funksjonen i Pymol, får du automatisk en beregning av RMSD (Root Mean Square Deviation). Noter denne verdien. Dette er et mål på den gjennomsnittlige avstanden (i Å) mellom tilsvarende atomer i to overlappende strukturene. RMSD blir beregnet som kvadratroten av gjennomsnittet av de kvadrerte avstandene mellom hvert par tilsvarende atomer:

\[ \text{RMSD} = \sqrt{ \frac{1}{N} \sum_{i=1}^{N} \left\| \mathbf{x}_i - \mathbf{y}_i \right\|^2 } \]

Her er \(N\) antall atomer (for eksempel Cα-atomer), og \(\mathbf{x}_i\) og \(\mathbf{y}_i\) er posisjonsvektorene til det \(i\)-te atomet i henholdsvis den eksperimentelle strukturen og modellen. Dobbeltstrekene \(\left\| \cdot \right\|\) markerer euklidisk norm, altså vanlig avstand i rommet mellom to punkter.

Normen \(\left\| \mathbf{x}_i - \mathbf{y}_i \right\|\) er avstanden mellom to punkter i rommet, og kan skrives som:

\[ \left\| \mathbf{x}_i - \mathbf{y}_i \right\| = \sqrt{(x_{ix} - y_{ix})^2 + (x_{iy} - y_{iy})^2 + (x_{iz} - y_{iz})^2} \]

hvor \(x_{ix}, x_{iy}, x_{iz}\) er x-, y- og z-koordinatene til det \(i\)-te atomet i den ene strukturen, og \(y_{ix}, y_{iy}, y_{iz}\) er koordinatene til det tilsvarende atomet i den andre strukturen. Vi regner først ut forskjellen i hver retning, kvadrerer dem, summerer, og tar kvadratroten.

RMSD-verdien uttrykker hvor godt to strukturer overlapper etter superposisjon – jo lavere verdi, desto mer lik er den modellerte strukturen den eksperimentelle.

  • Hva er RMSD-verdien for neuraminidase? Hvordan tolker du denne verdien?

  • Hvordan kan du bruke RMSD til å vurdere kvaliteten på en proteinmodell?

Oppgave B: Influensamedisin#

Når har vi sett hvordan vi kan forutsi strukturer med kunstig intelligens og hvordan vi kan validere dette med eksperimentelle data. Vi kan selvsagt også bruke de samme modellene for å forutsi strukturen på proteiner vi ikke kjenner strukturen til, eller undersøke hvordan små endringer i sekvensen – som en mutasjon i én enkelt aminosyre – kan påvirke den tredimensjonale formen. Dette gir oss et kraftig verktøy for å utforske sammenhengen mellom proteinets primærstruktur og dets funksjon, og for å forstå hvordan mutasjoner kan føre til tap av funksjon, sykdom eller endret legemiddelrespons.

Vi skal nå se hvordan en enkelt mutasjon i influensavirusets overflateprotein gjør at legemiddelet Tamiflu ikke lenger binder seg effektivt. Ved hjelp av AlphaFold kan vi sammenligne den muterte strukturen med den opprinnelige, og analysere hvordan bindingslommen endres som følge av mutasjonen.

Neruaminidase binder seg som nevnt til sialinsyre i kroppen vår, som er en type sukker som finnes på celleoverflater i kroppen vår, blant annet som en del av glykoproteiner og glykolipider. Sialinsyre bidrar til å gi cellene en negativ ladning og spiller en viktig rolle i cellecellekommunikasjon/signalering og immunsystemfunksjoner. Neuraminidase kløyver sialinsyre fra disse strukturene, noe som hjelper viruset å unngå immunsystemet og spre seg effektivt.

Strukturen til sialinsyre er vist nedenfor. I Pymol-strukturen som du har lastet ned, finnes istedenfor oseltamivir (ofte under merkenavnet “Tamiflu”), som er et legemiddel som hemmer enzymet slik at det ikke kan kløyve sialinsyre. Vis den i Pymol og se hvordan den binder seg til neuraminidase, gjerne ved å visualisere alle residuer som befinner seg innen 4 Å fra sialinsyre. Du kan bruke disse kommandoene for å svare på spørsmålene nedenfor:

# Vis oseltamivir (vanligvis heteroatom OSE eller G39 i 2HT8)
select oseltamivir, resn OSE
show sticks, oseltamivir
color yellow, oseltamivir

# Vis alle residuer innen 4 Å
select binding_residues, byres (polymer within 4 of oseltamivir)
show sticks, binding_residues
color cyan, binding_residues

# Vis polare kontakter (H-bindinger og saltbroer)
distance polar_contacts, oseltamivir, binding_residues, 3.5, mode=2

# Mål avstand manuelt mellom to atomer:
# Wizard > Measurement i GUI, eller:
# distance navn, /objekt//kjede/resnum/atom1, /objekt//kjede/resnum/atom2
  • Identifiser minst to aminosyreresiduer i bindingslommen som danner hydrogenbindinger med oseltamivir. Hvilke atomer er involvert?

  • Identifiser minst ett residu som bidrar til hydrofob kontakt. Hva er den kjemiske grunnen til at dette stabiliserer bindingen?

  • Arg292 er positivt ladet ved fysiologisk pH. Hvilken del av oseltamivir interagerer med denne aminosyren, og hva slags interaksjon er det?

  • Hva betyr det at oseltamivir er en substratanalog? Sammenlign strukturen til oseltamivir med sialinsyre og pek på konkrete likheter.

  • Når Arg292 muteres til Lys292, er begge positivt ladd, men bindingen svekkes likevel. Forklar hvorfor ut fra de strukturelle forskjellene mellom arginin og lysin som sidekjeder.

Hide code cell source
import py3Dmol

sialinsyre = py3Dmol.view(query='cid:129878003')
sialinsyre.setStyle({"stick": {"color":"spectrum"}})
#sialinsyre.zoomTo()
sialinsyre.show()

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

Mutasjoner#

Virus og andre patogener muterer og tilpasser seg miljøet, noe som kan gjøre dem resistente overfor naturlig forsvar eller legemidler. Vi skal nå se på hva som skjer med bindingen av legemidlet oseltamivir til neuraminidase dersom vi får en mutasjon i influensaviruset.

Nedenfor finner du Python-funksjoner som henter aminosyresekvensen til et protein fra en PDB-fil og endrer en spesifikk aminosyre til en annen. Du kan bruke disse funksjonene til å lage en ny sekvens med en mutasjon i neuraminidase-strukturen. Kjør funksjonene. De returnerer en FASTA-sekvens som du kan bruke videre i AlphaFold.

from Bio.PDB import PDBParser

trebokstav_til_enbokstav = {
    'ALA': 'A', 'CYS': 'C', 'ASP': 'D', 'GLU': 'E',
    'PHE': 'F', 'GLY': 'G', 'HIS': 'H', 'ILE': 'I',
    'LYS': 'K', 'LEU': 'L', 'MET': 'M', 'ASN': 'N',
    'PRO': 'P', 'GLN': 'Q', 'ARG': 'R', 'SER': 'S',
    'THR': 'T', 'VAL': 'V', 'TRP': 'W', 'TYR': 'Y'
}

def hent_modellert_sekvens_og_kobling(pdb_fil, kjede_id):
    parser = PDBParser(QUIET=True)
    struktur = parser.get_structure("protein", pdb_fil)
    kjede = struktur[0][kjede_id]

    sekvens = ""
    kobling = []  # (FASTA_indeks, PDB_posisjon, aa)

    for resid in kjede:
        if resid.id[0] == " ":
            atomer = [atom.get_id() for atom in resid]
            if all(atom in atomer for atom in ("N", "CA", "C")):  # modellerte residuer
                aa = trebokstav_til_enbokstav.get(resid.get_resname(), "X")
                if aa != "X":
                    sekvens += aa
                    posisjon = resid.id[1]
                    kobling.append((len(sekvens), posisjon, aa))  # 1-basert indeks

    return sekvens, kobling

def skriv_fasta(sekvens, filnavn, header):
    with open(filnavn, "w") as f:
        f.write(header + "\n")
        f.write(sekvens + "\n")

Nå skal vi bruke funksjonene til å lage en ny sekvens med en mutasjon i neuraminidase-strukturen. Finn pdb-fila “2ht8.pdb” på RCSB PDB-nettstedet og last den ned. Vi velger igjen å bare se på alfa-kjeden (A) i proteinet. Muter aminosyren Arg292 til Lys292. Du får da en ny sekvens lagret i FASTA-format som du kan bruke i AlphaFold. Denne finner du i samme mappe som programmet/notebooken din.

pdb_fil = "2ht8.pdb"
kjede_id = "A"
sekvens, kobling = hent_modellert_sekvens_og_kobling(pdb_fil, kjede_id)

# Finn hvilken FASTA-indeks som tilsvarer PDB-posisjon 292
for fasta_indeks, pdb_pos, aa in kobling:
    if pdb_pos == 292:
        print(f"PDB-posisjon 292 finnes på FASTA-indeks {fasta_indeks} og er {aa}")

# Muter aminosyre ved riktig FASTA-indeks (f.eks. til K)
fasta_indeks_292 = [f for f, p, a in kobling if p == 292][0] - 1
mutert = sekvens[:fasta_indeks_292] + "K" + sekvens[fasta_indeks_292+1:]

skriv_fasta(mutert, "mutert.fasta", ">mutert_protein | R292K")
PDB-posisjon 292 finnes på FASTA-indeks 211 og er R

Bruk FASTA-fila til det muterte proteinet i AlphaFold og sammenlign med den opprinnelige strukturen ved å bruke Pymol på samme måte som beskrevet tidligere.

  • Hvordan endrer den muterte strukturen seg i forhold til den opprinnelige?

  • Hvorfor tror du at denne mutasjonen fører til redusert binding av legemiddelet?

  • Hvordan kan dette påvirke virusets evne til å spre seg i kroppen?

  • Hvordan kan dette påvirke utviklingen av antivirale legemidler?

Oppgave C: Kroppens naturlige forsvar#

Kroppens immunforsvar produserer antistoffer som gjenkjenner bestemte strukturer, for eksempel hemagglutinin på overflaten av influensavirus. For å unngå å bli oppdaget og nøytralisert, kan viruset endre deler av overflateproteinet sitt slik at antistoffene binder seg dårligere.

Antistoffet 2D1 (PDB ID: 3LZF) er i stand til å feste seg både til hemagglutininvarianten som fantes hos spanskesykeviruset i 1918, og til svineinfluensaviruset som sirkulerte i 2009. Nå skal vi undersøke hvordan en enkelt aminosyresubstitusjon kan påvirke denne bindingen. I følgende sekvens er aminosyren lysin (L, Lys) i posisjon 166 erstattet med glutamat (E, Glu), markert med asterisk i sekvensen nedenfor:

ADPGDTICIGYHANNSTDTVDTVLEKNVTVTHSVNLLEDSHNGKLCKLKGIAPLQLGKCNIAGWLLGNPECDLLLTASSWSYIVETSNSENGTCYPGDFIDYEELREQLSSVSSFEKFEIFPKTSSWPNHETTKGVTAACSYAGASSFYRNLLWLTKKGSSYPKLS*E*SYVNNKGKEVLVLWGVHHPPTGTDQQSLYQNADAYVSVGSSKYNRRFTPEIAARPKVRDQAGRMNYYWTLLEPGDTITFEATGNLIAPWYAFALNRGSGSGIITSDAPVHDCNTKCQTPHGAINSSLPFQNIHPVTIGECPKYVRSTKLRMATGLRNIPSIQSR
  • Formuler en hypotese om hvordan denne mutasjonen kan påvirke bindingen av antistoffet til hemagglutinin.

  • Gjør nødvendige analyser for å forutsi hvordan denne mutasjonen påvirker bindingen av antistoffet til hemagglutinin. Bruk det du har lært tidligere i laboppgaven.

  • Vurder hvordan denne mutasjonen kan påvirke virusets evne til å unngå immunresponsen ut fra de resultatene du får.

Oppgave D: Ukjente proteiner#

Når vi lager nye proteiner eller undersøker et protein vi ikke kjenner fra før, kan vi ikke beregne RMSD eller tilsvarende for å validere modellen vi får fra AlphaFold. I stedet bruker vi ofte såkalte “tillitsområder” (confidence regions) for å vurdere hvor sikre vi er på at modellen er korrekt. Dette er områder i proteinet der AlphaFold har høy tillit til at strukturen er riktig, og områder der den har lav tillit.

En vanlig måte å validere usikkerhet på, er å bruke pLDDT (Predicted Local Distance Difference Test). AlphaFold gir oss en pLDDT-score for hver aminosyre – et mål på hvor sikker modellen er på plasseringen av denne residuen. pLDDT er en verdi mellom 0 og 100:

  • Over 90: Høy konfidens – strukturen er sannsynligvis korrekt.

  • 70–90: God konfidens – brukbar modell.

  • 50–70: Lav konfidens – modellen er usikker.

  • Under 50: Svært lav konfidens – regionen kan være ustrukturert.

pLDDT er altså et mål AlphaFold bruker for å uttrykke hvor sikker modellen er på plasseringen av hver enkelt aminosyre i den tredimensjonale strukturen. Modellen estimerer pLDDT ved at den lærer statistisk hvilke typer aminosyrer, posisjoner, kontaktmønstre og evolusjonære homologer som gir stabil struktur. Den bruker disse mønstrene til å forutsi hvor mye avstandene mellom residuer sannsynligvis vil variere, hvis man hadde hatt mange eksperimentelle eller predikerte strukturer. Jo mindre forventet variasjon, desto høyere pLDDT. pLDDT er altså et mål på geometrisk troverdighet i modellen. Det er viktig å merke seg at pLDDT-verdier gir en indikasjon på modellens nøyaktighet, men er ikke et mål på om modellen er riktig.

Ta utgangspunkt i følgende aminsoyresekvens og last opp til AlphaFold:

ADPGDTICHANNYVLNDGGPTDTVDTVLEKNVTVTHSVNLLEDSHNGKLCKLKGIAPLQLGKCNIAGWLLGNPECDLLLTASSWSYIVETSNSENGTCYPGDFIDYEELREQLSSVSSFEKFEIFPKTSSWPNHETTKGVTAACSYAGASSFYRNLLWLTKKGSSYPKLSESYVNNKGKEVLVLWGVHHPPTGTDQQSLYQNADAYVSVGSSKAIEPPGDTIWYRRNGSIITLLEPGDTITFEATGNLIAPWYAFALNRGSGSGIITSDAPVHDCNTKCQTPHGAINSSLPFQNIHPVTIGECPKYVRSTKLRMATGLRNIPSIQSR

AlphaFold har innebygde metoder for å estimere og visualisere usikkerhet i prediksjonen, men vi skal bruke PyMOL til å gjøre dette. Last ned CIF-filen med prediksjonen og åpne den i PyMOL. Du kan bruke følgende kommandoer for å laste opp CIF-filen du får fra AlphaFold:

load alphafold_model.cif 

For å visualisere pLDDT-verdiene, kan du bruke følgende kommandoer i PyMOL:

spectrum b, blue_white_red, minimum=0, maximum=100
show cartoon
bg_color white
set ray_opaque_background, off

Nå kan du se hvordan pLDDT-verdiene er fordelt i strukturen. Du kan også bruke følgende kommandoer for å lage to utvalg av områder med høy og lav tillit:

select lav_tillit, b < 50
select hoy_tillit, b > 90

Bruk gjerne ulike representasjoner for å visualisere de ulike områdene. Du kan for eksempel bruke staver for lavtillitsområder og tegne dem i rødt, mens du kan bruke en mer detaljert representasjon (f.eks. “cartoon”) for høy tillit og tegne dem i blått:

show sticks, lav_tillit
color red, lav_tillit

show cartoon, hoy_tillit
color blue, hoy_tillit

Vi kan også visualisere hele proteinet med en overflate for å se hvordan det ser ut i sin helhet. Dette kan gjøres med følgende kommandoer:

show surface
set transparency, 0.5
  • Hvorfor kan vi ikke bruke RMSD for å validere modeller vi ikke kjenner strukturen til fra før?

  • Hva er pLDDT, og hvordan kan det brukes til å vurdere kvaliteten på en proteinmodell?

  • Hvor i proteinet finner du lavtillitsområder? Hvordan passer dette med det du vet om fleksibilitet og struktur i proteiner?

  • På hvilke områder av proteinet er det viktigst å ha høy tillit?

Oppsummering#

  • Hva er de viktigste faktorene som påvirker proteinfolding?

  • Hvilke utfordringer har vi når vi skal simuleringe proteinfolding?

  • Hvordan kan kunstig intelligens brukes til å forutsi proteinstrukturer?

  • Hvordan kan vi bruke simuleringer til legemiddelutvikling?