From 5a343ac34985a8ca30c6ec0f6d737eb30620da18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Gon=C3=A7alves?= Date: Wed, 12 Mar 2025 09:40:53 +0100 Subject: [PATCH] rendu David GONCALVES --- BINF2025_TP3.ipynb | 1470 ++++++++++++++++++++++++++++++-------------- 1 file changed, 1001 insertions(+), 469 deletions(-) diff --git a/BINF2025_TP3.ipynb b/BINF2025_TP3.ipynb index 61e87c2..43e04e5 100644 --- a/BINF2025_TP3.ipynb +++ b/BINF2025_TP3.ipynb @@ -1,481 +1,1013 @@ { - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "provenance": [], - "authorship_tag": "ABX9TyNSXnqaXAUgZK9rmJ1TWbGo" - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - }, - "language_info": { - "name": "python" + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "V09wQ1WIOmgn" + }, + "source": [ + "# BINF TP3 - Algorithmes d'alignement par paire" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "er6CtAyOxC6F" + }, + "source": [ + "Dans ce TP nous allons manipuler les algorithmes d'alignement par paire." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "kCJGGGYQ2GNi" + }, + "source": [ + "_________________________________________________________________________\n", + "\n", + "Hello Professor,\n", + "\n", + "Yes, the code of the few exercises showed underneath is made through chatGPT.\n", + "I was to lazy to write them and to be fair, we have a few project to do at the same time and I don't want redo a ING1.\n", + "Futhermore I would not be sure of finishing the tp elsewise because I code pretty slowly.\n", + "\n", + "But all the non code question where made by hand.\n", + "\n", + "And I always re-read the code to make sure I understand everything, make sure there is no weird things happening and that I understand everything.\n", + "\n", + "Still, I want to point out I didn't understand how to traduce those sequences x = \"𝐮𝑌678264∗\". And thus those parts are wrong.\n", + "\n", + "Sincerly,\n", + "\n", + "David GONCALVES\n", + "\n", + "_________________________________________________________________________" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BqEa3BJ1xICM" + }, + "source": [ + "# Exercice 0 - Echauffement" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "qqiiq5bcxYvM" + }, + "source": [ + "Q1. Donnez le score de la superposition :\n", + "\n", + "| | |\n", + "| :---: | :---: |\n", + "x | ATGTCATGA---TAC |\n", + "y | AT--CTAAATGTTAC |\n", + "\n", + "\n", + "Ă©tant donne le schĂ©ma d'Ă©valuation :\n", + "\n", + "| | A | T | G | C |\n", + "| :---: | :---: | :---: | :---: | :---: |\n", + "| **A** | 1 | -1 | -1 | -1 |\n", + "| **T** | -1 | 1 | -1 | -1 |\n", + "| **G** | -1 | -1 | 1 | -1 |\n", + "| **C** | -1 | -1 | -1 | 1 |\n", + "\n", + "et\n", + "\n", + "$\\gamma(g) = 0.5 |g| + 0.5$" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "kCJGGGYQ2GNi" + }, + "source": [ + "```markdown\n", + "We have 3 gaps for x and 2 gaps for y:\n", + "\n", + "y(5) = 0.5 * 5 = 2.5 (not affine, linear, so it's the same than doing them separately)\n", + "\n", + "and for the association we have:\n", + "\n", + "7 - 3 = 4\n", + "\n", + "so the score is:\n", + "\n", + "score(x, y) = 4 - 2.5 = 1.5\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XyhXAhK-2NKJ" + }, + "source": [ + "Q2. Alignez les sĂ©quences suivantes avec l'algorithme de Levenshtein : x = ATG et y = ACTG." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "b9iovhyZ2bXw" + }, + "source": [ + "```markdown\n", + "\n", + " O A T G\n", + "O 0 1 2 3\n", + "A 1 0 1 2\n", + "C 2 1 1 2\n", + "T 3 2 1 2\n", + "G 4 3 2 1\n", + "\n", + " O A T G\n", + "O 0 - - -\n", + "A | \\ - -\n", + "C | | \\ -\n", + "T | | \\ \\\n", + "G | | | \\\n", + "\n", + "So the we do:\n", + "\n", + "GG - TT - CA - AA - 00\n", + "GTCA\n", + "Insertion of C\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OV_YaQHr2elB" + }, + "source": [ + "Q3.\tAlignez les sĂ©quences suivantes avec l'algorithme de Needleman-Wunsch global x = TAT et y = ATGAC en considĂ©rant le schĂ©ma d'Ă©valuation suivant\n", + "\n", + "| | A | T | G | C |\n", + "| :---: | :---: | :---: | :---: | :---: |\n", + "| **A** | 1 | -0.5 | -0.5 | -0.5 |\n", + "| **T** | -0.5 | 1 | -0.5 | -0.5 |\n", + "| **G** | -0.5 | -0.5 | 1 | -0.5 |\n", + "| **C** | -0.5 | -0.5 | -0.5 | 1 |\n", + "\n", + "et\n", + "\n", + "$\\gamma(g) = 0.5 |g|$\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "g_MrecVs3Nrw" + }, + "source": [ + "```markdown\n", + " 0 A T G A C\n", + "0 0 -0.5 -1 -1.5 -2 -2.5\n", + "T -0.5 -0.5 0.5 0 -0.5 -1\n", + "A -1 0.5 0 0 1 -0.5\n", + "T -1.5 0 1.5 1 0.5 0.5 \n", + "\n", + " 0 A T G A C\n", + "0 0 - - - - -\n", + "T | \\ \\ - - -\n", + "A | \\ \\ - \\ -\n", + "T | | \\ - - \\\n", + "\n", + "_T_AT\n", + "ATGAC\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "y1YF-G6E3Qoo" + }, + "source": [ + "Q4. Alignez les sĂ©quences suivantes avec l'algorithme de Smith-Waterman x = TTGG y = ATGAC en utilisant le schĂ©ma d'Ă©valuation de la question prĂ©cĂ©dente.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "LLMECocb3pgI" + }, + "source": [ + "```markdown\n", + " 0 A T G A C\n", + "0 0 -0.5 -1 -1.5 -2 -2.5\n", + "T -0.5 -0.5 0.5 0 -0.5 -1\n", + "T -1 -1 0.5 0 -0.5 -1\n", + "G -1.5 -1.5 0 1.5 1 0.5\n", + "G -2 -2 -0.5 1 1 0.5\n", + "\n", + " 0 A T G A C\n", + "0 0 - - - - -\n", + "T | \\ \\ - - -\n", + "T | \\ \\ \\ \\ \\\n", + "G | \\ | \\ - -\n", + "G | \\ | \\ \\ \\\n", + "\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "46gw0avh3wGw" + }, + "source": [ + "# Exercice 1 : Algorithme de Levenshtein - version rĂ©cursive" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ZKc09Kyg4a6v" + }, + "source": [ + "Q1. Ecrivez une fonction\n", + "\n", + "levenshtein(x: str, y: str) -> int\n", + "\n", + "qui retourne la distance de Levenshtein entre les sĂ©quences x et y en utilisant la version rĂ©cursive de l'algorithme." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "id": "FJR69IEQ4aHv" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Levenshtein Distance: 4\n", + "Aligned X: ATGT-CATGATAC\n", + "Aligned Y: ATCTAAATGTTAC\n" + ] } + ], + "source": [ + "import numpy as np\n", + "\n", + "def levenshtein(x: str, y: str):\n", + " m, n = len(x), len(y)\n", + " \n", + " # Initialize matrices\n", + " S = np.zeros((m + 1, n + 1), dtype=int)\n", + " B = np.empty((m + 1, n + 1), dtype=object)\n", + " \n", + " # Fill first row and first column\n", + " for i in range(1, m + 1):\n", + " S[i, 0] = i\n", + " B[i, 0] = (i - 1, 0) # Up\n", + " \n", + " for j in range(1, n + 1):\n", + " S[0, j] = j\n", + " B[0, j] = (0, j - 1) # Left\n", + " \n", + " # Fill matrices\n", + " for i in range(1, m + 1):\n", + " for j in range(1, n + 1):\n", + " if x[i - 1] == y[j - 1]: # Match\n", + " S[i, j] = S[i - 1, j - 1]\n", + " B[i, j] = (i - 1, j - 1) # Diagonal\n", + " else:\n", + " options = [(S[i - 1, j - 1], (i - 1, j - 1)), # Substitution\n", + " (S[i, j - 1], (i, j - 1)), # Insertion\n", + " (S[i - 1, j], (i - 1, j))] # Deletion\n", + " \n", + " S[i, j], B[i, j] = min(options, key=lambda t: t[0])\n", + " S[i, j] += 1\n", + " \n", + " # Traceback to find alignment\n", + " i, j = m, n\n", + " sx, sy = \"\", \"\"\n", + " \n", + " while (i, j) != (0, 0):\n", + " prev_i, prev_j = B[i, j]\n", + " if prev_i == i - 1 and prev_j == j - 1:\n", + " sx = x[i - 1] + sx\n", + " sy = y[j - 1] + sy\n", + " elif prev_i == i - 1:\n", + " sx = x[i - 1] + sx\n", + " sy = \"-\" + sy\n", + " else:\n", + " sx = \"-\" + sx\n", + " sy = y[j - 1] + sy\n", + " i, j = prev_i, prev_j\n", + " \n", + " return S[m, n], sx, sy\n", + "\n", + "# Example usage\n", + "x = \"ATGTCATGA---TAC\".replace(\"-\", \"\") # Removing gaps from input\n", + "y = \"AT--CTAAATGTTAC\".replace(\"-\", \"\")\n", + "distance, aligned_x, aligned_y = levenshtein(x, y)\n", + "\n", + "print(\"Levenshtein Distance:\", distance)\n", + "print(\"Aligned X:\", aligned_x)\n", + "print(\"Aligned Y:\", aligned_y)\n" + ] }, - "cells": [ - { - "cell_type": "markdown", - "source": [ - "# BINF TP3 - Algorithmes d'alignement par paire" - ], - "metadata": { - "id": "V09wQ1WIOmgn" - } - }, - { - "cell_type": "markdown", - "source": [ - "Dans ce TP nous allons manipuler les algorithmes d'alignement par paire." - ], - "metadata": { - "id": "er6CtAyOxC6F" - } - }, - { - "cell_type": "markdown", - "source": [ - "# Exercice 0 - Echauffement" - ], - "metadata": { - "id": "BqEa3BJ1xICM" - } - }, - { - "cell_type": "markdown", - "source": [ - "Q1. Donnez le score de la superposition :\n", - "\n", - "| | |\n", - "| :---: | :---: |\n", - "x | ATGTCATGA---TAC |\n", - "y | AT--CTAAATGTTAC |\n", - "\n", - "\n", - "Ă©tant donne le schĂ©ma d'Ă©valuation :\n", - "\n", - "| | A | T | G | C |\n", - "| :---: | :---: | :---: | :---: | :---: |\n", - "| **A** | 1 | -1 | -1 | -1 |\n", - "| **T** | -1 | 1 | -1 | -1 |\n", - "| **G** | -1 | -1 | 1 | -1 |\n", - "| **C** | -1 | -1 | -1 | 1 |\n", - "\n", - "et\n", - "\n", - "$\\gamma(g) = 0.5 |g| + 0.5$" - ], - "metadata": { - "id": "qqiiq5bcxYvM" - } - }, - { - "cell_type": "markdown", - "source": [ - "```markdown\n", - "Votre rĂ©ponse ici\n", - "```" - ], - "metadata": { - "id": "kCJGGGYQ2GNi" - } - }, - { - "cell_type": "markdown", - "source": [ - "Q2. Alignez les sĂ©quences suivantes avec l'algorithme de Levenshtein : x = ATG et y = ACTG." - ], - "metadata": { - "id": "XyhXAhK-2NKJ" - } - }, - { - "cell_type": "markdown", - "source": [ - "```markdown\n", - "Votre rĂ©ponse ici\n", - "```" - ], - "metadata": { - "id": "b9iovhyZ2bXw" - } - }, - { - "cell_type": "markdown", - "source": [ - "Q3.\tAlignez les sĂ©quences suivantes avec l'algorithme de Needleman-Wunsch global x = TAT et y = ATGAC en considĂ©rant le schĂ©ma d'Ă©valuation suivant\n", - "\n", - "| | A | T | G | C |\n", - "| :---: | :---: | :---: | :---: | :---: |\n", - "| **A** | 1 | -0.5 | -0.5 | -0.5 |\n", - "| **T** | -0.5 | 1 | -0.5 | -0.5 |\n", - "| **G** | -0.5 | -0.5 | 1 | -0.5 |\n", - "| **C** | -0.5 | -0.5 | -0.5 | 1 |\n", - "\n", - "et\n", - "\n", - "$\\gamma(g) = 0.5 |g|$\n" - ], - "metadata": { - "id": "OV_YaQHr2elB" - } - }, - { - "cell_type": "markdown", - "source": [ - "```markdown\n", - "Votre rĂ©ponse ici\n", - "```" - ], - "metadata": { - "id": "g_MrecVs3Nrw" - } - }, - { - "cell_type": "markdown", - "source": [ - "Q4. Alignez les sĂ©quences suivantes avec l'algorithme de Smith-Waterman x = TTGG y = ATGAC en utilisant le schĂ©ma d'Ă©valuation de la question prĂ©cĂ©dente.\n" - ], - "metadata": { - "id": "y1YF-G6E3Qoo" - } - }, - { - "cell_type": "markdown", - "source": [ - "```markdown\n", - "Votre rĂ©ponse ici\n", - "```" - ], - "metadata": { - "id": "LLMECocb3pgI" - } - }, - { - "cell_type": "markdown", - "source": [ - "# Exercice 1 : Algorithme de Levenshtein - version rĂ©cursive" - ], - "metadata": { - "id": "46gw0avh3wGw" - } - }, - { - "cell_type": "markdown", - "source": [ - "Q1. Ecrivez une fonction\n", - "\n", - "levenshtein(x: str, y: str) -> int\n", - "\n", - "qui retourne la distance de Levenshtein entre les sĂ©quences x et y en utilisant la version rĂ©cursive de l'algorithme." - ], - "metadata": { - "id": "ZKc09Kyg4a6v" - } - }, - { - "cell_type": "code", - "source": [ - "#Votre code ici" - ], - "metadata": { - "id": "FJR69IEQ4aHv" - }, - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "source": [ - "Q2. Vous pouvez tester votre code sur les exemples suivants:\n", - "\n", - "\n", - "* $L('CCAG', 'CA') = 2$\n", - "* $L('CCGT', 'CGTCA') = 3$\n", - "* $L(AY678264^*, OQ870305^*) = 310$\n", - "\n", - "$^*$ ids genbank de deux sequences." - ], - "metadata": { - "id": "arFVwA6E5NWn" - } - }, - { - "cell_type": "markdown", - "source": [ - "# Exercice 2 : Algorithme de Smith-Waterman - version itĂ©rative" - ], - "metadata": { - "id": "erCpfG7O7BV-" - } - }, - { - "cell_type": "markdown", - "source": [ - "Q1. Ecrivez la fonction\n", - "\n", - "sw_fwd(x: str, y: str, cmap: dict, sigma: array, (go, ge): list) -> (array, array)\n", - "\n", - "qui construit les matrices $S$ et $B$ en utilisant l'algorithme de Smith-Waterman pour aligner les sĂ©quences x et y suivant le schĂ©ma d'Ă©valuation donnĂ© par la matrice de substitution $\\Sigma$ et la fonction d'Ă©valuation des trous $\\gamma(n)= g_o + g_e \\times n$. Le dictionnaire cmap donne la position des diffĂ©rents nuclĂ©otides dans la matrice $\\Sigma$. La fonction retourne la paire de matrices de score $S$ et de retour $B$." - ], - "metadata": { - "id": "rv2Y78y37IOd" - } - }, - { - "cell_type": "code", - "source": [ - "#Votre code ici" - ], - "metadata": { - "id": "njn3JB0b-WHj" - }, - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "source": [ - "Q2. Ecrivez la fonction\n", - "\n", - "sw_bwd(x: str, y: str, S: array, B: array) -> (str, str, float)\n", - "\n", - "qui effectue l'etape de retour de l'algorithme de Smith-Waterman etant donnĂ© les sĂ©quences $x$ et $y$ et les matrices de score $S$ et de retour $B$. La fonction retourne un tuple contenant les alignements des sĂ©quences x et y et le score de l'alignement." - ], - "metadata": { - "id": "55n8mt9U-Wai" - } - }, - { - "cell_type": "code", - "source": [ - "#Votre code ici" - ], - "metadata": { - "id": "ij9JDpBm_UZ7" - }, - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "source": [ - "Q3. Vous pouvez tester votre code en utilisant le schĂ©ma d'Ă©valuation suivant :" - ], - "metadata": { - "id": "kwmxg2dxAiwS" - } - }, - { - "cell_type": "code", - "source": [ - "cmap = {\"A\": 0, \"T\": 1, \"G\": 2, \"C\": 3}\n", - "m = np.array([[1, -0.5, -0.5, -0.5],\n", - " [-0.5, 1, -0.5, -0.5],\n", - " [-0.5, -0.5, 1, -0.5],\n", - " [-0.5, -0.5, -0.5, 1]])\n", - "go = 0\n", - "ge = 0.5" - ], - "metadata": { - "id": "JUtYRFTBAwwZ" - }, - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "source": [ - "* $SW('TCGC', 'CTTAG')$ retourne un score de $1.5$ Ă  la position $(3,5)$ et l'alignement" - ], - "metadata": { - "id": "eMGh4K5aIFxE" - } + { + "cell_type": "markdown", + "metadata": { + "id": "arFVwA6E5NWn" + }, + "source": [ + "Q2. Vous pouvez tester votre code sur les exemples suivants:\n", + "\n", + "\n", + "* $L('CCAG', 'CA') = 2$\n", + "* $L('CCGT', 'CGTCA') = 3$\n", + "* $L(AY678264^*, OQ870305^*) = 310$\n", + "\n", + "$^*$ ids genbank de deux sequences." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Levenshtein Distance: 2\n", + "Aligned X: CCAG\n", + "Aligned Y: -CA-\n" + ] + } + ], + "source": [ + "x = \"CCAG\"\n", + "y = \"CA\"\n", + "distance, aligned_x, aligned_y = levenshtein(x, y)\n", + "print(\"Levenshtein Distance:\", distance)\n", + "print(\"Aligned X:\", aligned_x)\n", + "print(\"Aligned Y:\", aligned_y)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Levenshtein Distance: 3\n", + "Aligned X: CCGT--\n", + "Aligned Y: -CGTCA\n" + ] + } + ], + "source": [ + "x = \"CCGT\"\n", + "y = \"CGTCA\"\n", + "distance, aligned_x, aligned_y = levenshtein(x, y)\n", + "print(\"Levenshtein Distance:\", distance)\n", + "print(\"Aligned X:\", aligned_x)\n", + "print(\"Aligned Y:\", aligned_y)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Levenshtein Distance: 7\n", + "Aligned X: 𝐮𝑌678264∗\n", + "Aligned Y: 𝑂𝑄870305∗\n" + ] + } + ], + "source": [ + "x = \"𝐮𝑌678264∗\"\n", + "y = \"𝑂𝑄870305∗\"\n", + "distance, aligned_x, aligned_y = levenshtein(x, y)\n", + "print(\"Levenshtein Distance:\", distance)\n", + "print(\"Aligned X:\", aligned_x)\n", + "print(\"Aligned Y:\", aligned_y)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "erCpfG7O7BV-" + }, + "source": [ + "# Exercice 2 : Algorithme de Smith-Waterman - version itĂ©rative" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "rv2Y78y37IOd" + }, + "source": [ + "Q1. Ecrivez la fonction\n", + "\n", + "sw_fwd(x: str, y: str, cmap: dict, sigma: array, (go, ge): list) -> (array, array)\n", + "\n", + "qui construit les matrices $S$ et $B$ en utilisant l'algorithme de Smith-Waterman pour aligner les sĂ©quences x et y suivant le schĂ©ma d'Ă©valuation donnĂ© par la matrice de substitution $\\Sigma$ et la fonction d'Ă©valuation des trous $\\gamma(n)= g_o + g_e \\times n$. Le dictionnaire cmap donne la position des diffĂ©rents nuclĂ©otides dans la matrice $\\Sigma$. La fonction retourne la paire de matrices de score $S$ et de retour $B$." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "id": "njn3JB0b-WHj" + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "def sw_fwd(x: str, y: str, cmap: dict, sigma: np.ndarray, penalties: list):\n", + " \"\"\"\n", + " Implements the Smith-Waterman algorithm for local sequence alignment.\n", + "\n", + " Parameters:\n", + " - x (str): First sequence\n", + " - y (str): Second sequence\n", + " - cmap (dict): Mapping of nucleotide to index in sigma\n", + " - sigma (np.ndarray): Substitution matrix\n", + " - penalties (list): [gap opening (go), gap extension (ge)]\n", + "\n", + " Returns:\n", + " - S (np.ndarray): Score matrix\n", + " - B (np.ndarray): Backtracking matrix\n", + " \"\"\"\n", + " go, ge = penalties\n", + " len_x, len_y = len(x), len(y)\n", + " \n", + " # Initialize matrices\n", + " S = np.zeros((len_x + 1, len_y + 1))\n", + " B = np.zeros((len_x + 1, len_y + 1), dtype=int)\n", + " \n", + " # Fill matrices\n", + " for i in range(1, len_x + 1):\n", + " for j in range(1, len_y + 1):\n", + " match = S[i - 1, j - 1] + sigma[cmap[x[i - 1]], cmap[y[j - 1]]]\n", + " delete = S[i - 1, j] + (go if B[i - 1, j] != 2 else ge) # Gap penalty\n", + " insert = S[i, j - 1] + (go if B[i, j - 1] != 3 else ge)\n", + " S[i, j] = max(0, match, delete, insert)\n", + "\n", + " # Assign backtracking directions\n", + " if S[i, j] == 0:\n", + " B[i, j] = 0 # Stop (local alignment)\n", + " elif S[i, j] == match:\n", + " B[i, j] = 1 # Diagonal (match/mismatch)\n", + " elif S[i, j] == delete:\n", + " B[i, j] = 2 # Up (gap in y)\n", + " elif S[i, j] == insert:\n", + " B[i, j] = 3 # Left (gap in x)\n", + "\n", + " return S, B" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Score Matrix (S):\n", + "[[0. 0. 0. 0. 0. 0. ]\n", + " [0. 0. 1. 1. 1. 1.5]\n", + " [0. 1. 1. 1. 1. 1.5]\n", + " [0. 1. 1.5 1.5 1.5 2. ]\n", + " [0. 1.5 2. 2. 2. 2. ]]\n", + "\n", + "Backtracking Matrix (B):\n", + "[[0 0 0 0 0 0]\n", + " [0 0 1 1 3 3]\n", + " [0 1 2 2 2 2]\n", + " [0 2 2 2 2 1]\n", + " [0 2 2 2 2 2]]\n" + ] + } + ], + "source": [ + "# Define cmap and sigma for DNA sequences\n", + "cmap = {\"A\": 0, \"T\": 1, \"G\": 2, \"C\": 3}\n", + "m = np.array([[1, -0.5, -0.5, -0.5],\n", + " [-0.5, 1, -0.5, -0.5],\n", + " [-0.5, -0.5, 1, -0.5],\n", + " [-0.5, -0.5, -0.5, 1]])\n", + "go = 0\n", + "ge = 0.5\n", + "\n", + "# Example sequences\n", + "# Sequences\n", + "x = \"TCGC\"\n", + "y = \"CTTAG\"\n", + "\n", + "# Run Smith-Waterman\n", + "S, B = sw_fwd(x, y, cmap, sigma, (go, ge)) # Gap opening = -2, gap extension = -1\n", + "\n", + "print(\"Score Matrix (S):\")\n", + "print(S)\n", + "print(\"\\nBacktracking Matrix (B):\")\n", + "print(B)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "55n8mt9U-Wai" + }, + "source": [ + "Q2. Ecrivez la fonction\n", + "\n", + "sw_bwd(x: str, y: str, S: array, B: array) -> (str, str, float)\n", + "\n", + "qui effectue l'etape de retour de l'algorithme de Smith-Waterman etant donnĂ© les sĂ©quences $x$ et $y$ et les matrices de score $S$ et de retour $B$. La fonction retourne un tuple contenant les alignements des sĂ©quences x et y et le score de l'alignement." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "id": "ij9JDpBm_UZ7" + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "def sw_bwd(x: str, y: str, S: np.ndarray, B: np.ndarray) -> (str, str, float):\n", + " \"\"\"\n", + " Performs the traceback step of the Smith-Waterman algorithm.\n", + "\n", + " Parameters:\n", + " - x (str): First sequence\n", + " - y (str): Second sequence\n", + " - S (np.ndarray): Score matrix\n", + " - B (np.ndarray): Backtracking matrix\n", + "\n", + " Returns:\n", + " - (str, str, float): Aligned sequences and the final alignment score\n", + " \"\"\"\n", + " # Find the maximum score in S (starting point for traceback)\n", + " i, j = np.unravel_index(np.argmax(S), S.shape)\n", + " max_score = S[i, j]\n", + "\n", + " aligned_x = []\n", + " aligned_y = []\n", + "\n", + " # Traceback\n", + " while B[i, j] != 0 and i > 0 and j > 0:\n", + " if B[i, j] == 1: # Diagonal (match/mismatch)\n", + " aligned_x.append(x[i - 1])\n", + " aligned_y.append(y[j - 1])\n", + " i -= 1\n", + " j -= 1\n", + " elif B[i, j] == 2: # Up (gap in y)\n", + " aligned_x.append(x[i - 1])\n", + " aligned_y.append('-')\n", + " i -= 1\n", + " elif B[i, j] == 3: # Left (gap in x)\n", + " aligned_x.append('-')\n", + " aligned_y.append(y[j - 1])\n", + " j -= 1\n", + "\n", + " # Reverse alignments (since we built them backwards)\n", + " aligned_x = ''.join(aligned_x[::-1])\n", + " aligned_y = ''.join(aligned_y[::-1])\n", + "\n", + " return aligned_x, aligned_y, max_score\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "kwmxg2dxAiwS" + }, + "source": [ + "Q3. Vous pouvez tester votre code en utilisant le schĂ©ma d'Ă©valuation suivant :" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "id": "JUtYRFTBAwwZ" + }, + "outputs": [], + "source": [ + "cmap = {\"A\": 0, \"T\": 1, \"G\": 2, \"C\": 3}\n", + "m = np.array([[1, -0.5, -0.5, -0.5],\n", + " [-0.5, 1, -0.5, -0.5],\n", + " [-0.5, -0.5, 1, -0.5],\n", + " [-0.5, -0.5, -0.5, 1]])\n", + "go = 0\n", + "ge = 0.5" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Alignment Score: 2.0\n", + "Aligned x: T-CG\n", + "Aligned y: TA-G\n", + "Max Score: 2.0 at Position: (3, 5)\n" + ] + } + ], + "source": [ + "# Sequences\n", + "x = \"TCGC\"\n", + "y = \"CTTAG\"\n", + "\n", + "# Step 1: Compute S and B matrices\n", + "S, B = sw_fwd(x, y, cmap, sigma, (go, ge))\n", + "\n", + "# Step 2: Perform traceback to get alignment and score\n", + "aligned_x, aligned_y, score = sw_bwd(x, y, S, B)\n", + "\n", + "# Step 3: Print results\n", + "print(f\"Alignment Score: {score}\")\n", + "print(f\"Aligned x: {aligned_x}\")\n", + "print(f\"Aligned y: {aligned_y}\")\n", + "\n", + "# Step 4: Check best score position\n", + "max_score = np.max(S)\n", + "best_position = np.unravel_index(np.argmax(S), S.shape)\n", + "print(f\"Max Score: {max_score} at Position: {best_position}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "eMGh4K5aIFxE" + }, + "source": [ + "* $SW('TCGC', 'CTTAG')$ retourne un score de $1.5$ Ă  la position $(3,5)$ et l'alignement" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 60 }, + "id": "joHNwJ9AIf6F", + "outputId": "a9206810-a083-4d86-8b14-38183f1dd80c" + }, + "outputs": [ { - "cell_type": "code", - "source": [ - "HTML(\"
x:TCG
y:TAG
\")" + "data": { + "text/html": [ + "
x:TCG
y:TAG
" ], - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 60 - }, - "id": "joHNwJ9AIf6F", - "outputId": "a9206810-a083-4d86-8b14-38183f1dd80c" - }, - "execution_count": null, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
x:TCG
y:TAG
" - ] - }, - "metadata": {}, - "execution_count": 18 - } + "text/plain": [ + "" ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.core.display import HTML\n", + "\n", + "HTML(\"
x:TCG
y:TAG
\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "JJlU5yvZI43D" + }, + "source": [ + "* $SW(AY678264^*, OQ870305^*)$ retourne un score de $342.1$ Ă  la position $(708,717)$ et l'alignement" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "ename": "KeyError", + "evalue": "'O'", + "output_type": "error", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mKeyError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m~\\AppData\\Local\\Temp\\ipykernel_7144\\39445628.py\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 5\u001b[0m \u001b[1;31m# Step 1: Compute S and B matrices\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 6\u001b[1;33m \u001b[0mS\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mB\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0msw_fwd\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0my\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcmap\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0msigma\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m(\u001b[0m\u001b[0mgo\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mge\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 7\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 8\u001b[0m \u001b[1;31m# Step 2: Perform traceback to get alignment and score\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m~\\AppData\\Local\\Temp\\ipykernel_7144\\321429888.py\u001b[0m in \u001b[0;36msw_fwd\u001b[1;34m(x, y, cmap, sigma, penalties)\u001b[0m\n\u001b[0;32m 26\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mlen_x\u001b[0m \u001b[1;33m+\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 27\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mj\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mlen_y\u001b[0m \u001b[1;33m+\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 28\u001b[1;33m \u001b[0mmatch\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mS\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m \u001b[1;33m-\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mj\u001b[0m \u001b[1;33m-\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m+\u001b[0m \u001b[0msigma\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mcmap\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m \u001b[1;33m-\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mcmap\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mj\u001b[0m \u001b[1;33m-\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 29\u001b[0m \u001b[0mdelete\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mS\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m \u001b[1;33m-\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mj\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m+\u001b[0m \u001b[1;33m(\u001b[0m\u001b[0mgo\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mB\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m \u001b[1;33m-\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mj\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m!=\u001b[0m \u001b[1;36m2\u001b[0m \u001b[1;32melse\u001b[0m \u001b[0mge\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;31m# Gap penalty\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 30\u001b[0m \u001b[0minsert\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mS\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mj\u001b[0m \u001b[1;33m-\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m+\u001b[0m \u001b[1;33m(\u001b[0m\u001b[0mgo\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mB\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mj\u001b[0m \u001b[1;33m-\u001b[0m \u001b[1;36m1\u001b[0m\u001b[1;33m]\u001b[0m \u001b[1;33m!=\u001b[0m \u001b[1;36m3\u001b[0m \u001b[1;32melse\u001b[0m \u001b[0mge\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mKeyError\u001b[0m: 'O'" + ] + } + ], + "source": [ + "# Sequences\n", + "x = \"AY678264*\"\n", + "y = \"OQ870305*\"\n", + "\n", + "# Step 1: Compute S and B matrices\n", + "S, B = sw_fwd(x, y, cmap, sigma, (go, ge))\n", + "\n", + "# Step 2: Perform traceback to get alignment and score\n", + "aligned_x, aligned_y, score = sw_bwd(x, y, S, B)\n", + "\n", + "# Step 3: Print results\n", + "print(f\"Alignment Score: {score}\")\n", + "print(f\"Aligned x: {aligned_x}\")\n", + "print(f\"Aligned y: {aligned_y}\")\n", + "\n", + "# Step 4: Check best score position\n", + "max_score = np.max(S)\n", + "best_position = np.unravel_index(np.argmax(S), S.shape)\n", + "print(f\"Max Score: {max_score} at Position: {best_position}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 80 }, + "id": "HUELvWKMFtIO", + "outputId": "976bab6f-f1fc-4c5a-c69c-8de02fc838d0" + }, + "outputs": [ { - "cell_type": "markdown", - "source": [ - "* $SW(AY678264^*, OQ870305^*)$ retourne un score de $342.1$ Ă  la position $(708,717)$ et l'alignement" - ], - "metadata": { - "id": "JJlU5yvZI43D" - } - }, - { - "cell_type": "code", - "source": [ - "from IPython.display import HTML\n", - "HTML(\"
x:ATGGTGAGCAAGGGCGAGGAGGATAACATGGCCATCATCAAGGAGTTCATGCGCTTCAAGGTGC-A-CATGGAGGGCTCCGTGAACGGCCACGAGTTCGAGATCGAG---GGCGAGGGCGAGGGC--CGCC-CCTACGAGGGCACCCAGACCGC-CAAGCTGAAGGTG-ACCA-AGG---G-TGGCC---CCCT-GCCCTTCGCCT-GGGA-CATCCTGTCC--C--C-T-CAGTTCATGT-A-CGGCT-CCAAGGCCTACGTG-A--AGCAC--C--C--C--G-CCGACATCCCCG-A--CTAC-T--TGAAGCTG-TCCTTC--C--C-----CGA-GG--GCTTCAAGTGGGAGCG-CGTGATGAACTTCGAGGACGGCGGCGTGGTG-ACCG--T-GA-C-CCAGGAC-TC--CTCCCTGCAGGACGGCGAGTTCATCTACAAGGTG---AAGCTGCGCGGCACCAACTTCCCCT-CCGACGGCCCCGTA-ATGCA-GAAGAAGACCATGGGCTG--GGA-GGCCTCCTCCGAGCGGATGTACCCCGAGGA-CGGCGCC-CTGAAGGGCGAGATCAAGCAGA-GGCTGAAGC-TGAAGGACGGCGGCCACTACGACGCTGAGGTCAAGACCACCTACA-AGGCCAAGAAG-CCCGTGCAGCTGCCCGGC-GCCTACAACGTCAACATCAAGT-TG----GA-CATCACCTCCCACAACGAGGA-CTAC-A-C-CA---T-C-G-TGGAACAGTACG-AACGCGCCGAGGGCCGCCACTCCAC-CGGCGGCATGGACGAGCTGTACAAG
y:ATGGTGAGCAAGGGCGAGGA-G----C-T-G--TTCA-C-CGG-GGTGGTGCCCATCCTGGT-CGAGC-TGGACGGCGACGTAAACGGCCACAAGTTC-AG--CGTGTCCGGCGAGGGCGAGGGCGATGCCACCTAC---GGCAAGCTGACC-CTGAAG-TTCATTTGCACCACCGGCAAGCTGCCCGTGCCCTGGCCC-AC-CCTCGTGACCACCCTGACCTACGGCGTGCAGTGC-T-TCAGCCGCTACCCCGACC-ACATGAAGCAGCACGACTTCTTCAAGTCCGCCATGCCCGAAGGCTACGTCCAGGAGC-GCACCATCTTCTTCAAGGACGACGGCAACTACAAGA-CCCGCGCCGAGGTGAAGTTCGAGGGCGACACCCTGGTGAACCGCATCGAGCTGAAGGGCATCGACTTCAAGGAGGACGGC-A--ACATC--C-TGGGGCACAAGCTG-G-AGTA-CAACTACAACAGCC-ACAACGTC-TATAT-CATG--GCCGA-CAA--GCAGAAGAACGG-CA--T-C-A-AGG-TGAACTTC-AAGATC--CGCCAC--AA---C---ATCGAG--GACGGC---AGCGTGCAGCTCGCCGACCACTACCA-GC--A-G--AACACC-CC--CATCGGCGACG--GCCCCGTGCTGCTGCCCGACAACC-ACTACCTGAGCACCCAGTCCGCCCTGAGCAA-A-GACCC-CAACGAGAAGC-GCGATCACATGGTCCTGCTGG---AGTTCGTGAC-CGCC----GCCGGGA-T-CACTC-TCGGCATGGACGAGCTGTACAAG
\")" + "data": { + "text/html": [ + "
x:ATGGTGAGCAAGGGCGAGGAGGATAACATGGCCATCATCAAGGAGTTCATGCGCTTCAAGGTGC-A-CATGGAGGGCTCCGTGAACGGCCACGAGTTCGAGATCGAG---GGCGAGGGCGAGGGC--CGCC-CCTACGAGGGCACCCAGACCGC-CAAGCTGAAGGTG-ACCA-AGG---G-TGGCC---CCCT-GCCCTTCGCCT-GGGA-CATCCTGTCC--C--C-T-CAGTTCATGT-A-CGGCT-CCAAGGCCTACGTG-A--AGCAC--C--C--C--G-CCGACATCCCCG-A--CTAC-T--TGAAGCTG-TCCTTC--C--C-----CGA-GG--GCTTCAAGTGGGAGCG-CGTGATGAACTTCGAGGACGGCGGCGTGGTG-ACCG--T-GA-C-CCAGGAC-TC--CTCCCTGCAGGACGGCGAGTTCATCTACAAGGTG---AAGCTGCGCGGCACCAACTTCCCCT-CCGACGGCCCCGTA-ATGCA-GAAGAAGACCATGGGCTG--GGA-GGCCTCCTCCGAGCGGATGTACCCCGAGGA-CGGCGCC-CTGAAGGGCGAGATCAAGCAGA-GGCTGAAGC-TGAAGGACGGCGGCCACTACGACGCTGAGGTCAAGACCACCTACA-AGGCCAAGAAG-CCCGTGCAGCTGCCCGGC-GCCTACAACGTCAACATCAAGT-TG----GA-CATCACCTCCCACAACGAGGA-CTAC-A-C-CA---T-C-G-TGGAACAGTACG-AACGCGCCGAGGGCCGCCACTCCAC-CGGCGGCATGGACGAGCTGTACAAG
y:ATGGTGAGCAAGGGCGAGGA-G----C-T-G--TTCA-C-CGG-GGTGGTGCCCATCCTGGT-CGAGC-TGGACGGCGACGTAAACGGCCACAAGTTC-AG--CGTGTCCGGCGAGGGCGAGGGCGATGCCACCTAC---GGCAAGCTGACC-CTGAAG-TTCATTTGCACCACCGGCAAGCTGCCCGTGCCCTGGCCC-AC-CCTCGTGACCACCCTGACCTACGGCGTGCAGTGC-T-TCAGCCGCTACCCCGACC-ACATGAAGCAGCACGACTTCTTCAAGTCCGCCATGCCCGAAGGCTACGTCCAGGAGC-GCACCATCTTCTTCAAGGACGACGGCAACTACAAGA-CCCGCGCCGAGGTGAAGTTCGAGGGCGACACCCTGGTGAACCGCATCGAGCTGAAGGGCATCGACTTCAAGGAGGACGGC-A--ACATC--C-TGGGGCACAAGCTG-G-AGTA-CAACTACAACAGCC-ACAACGTC-TATAT-CATG--GCCGA-CAA--GCAGAAGAACGG-CA--T-C-A-AGG-TGAACTTC-AAGATC--CGCCAC--AA---C---ATCGAG--GACGGC---AGCGTGCAGCTCGCCGACCACTACCA-GC--A-G--AACACC-CC--CATCGGCGACG--GCCCCGTGCTGCTGCCCGACAACC-ACTACCTGAGCACCCAGTCCGCCCTGAGCAA-A-GACCC-CAACGAGAAGC-GCGATCACATGGTCCTGCTGG---AGTTCGTGAC-CGCC----GCCGGGA-T-CACTC-TCGGCATGGACGAGCTGTACAAG
" ], - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 80 - }, - "id": "HUELvWKMFtIO", - "outputId": "976bab6f-f1fc-4c5a-c69c-8de02fc838d0" - }, - "execution_count": null, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "" - ], - "text/html": [ - "
x:ATGGTGAGCAAGGGCGAGGAGGATAACATGGCCATCATCAAGGAGTTCATGCGCTTCAAGGTGC-A-CATGGAGGGCTCCGTGAACGGCCACGAGTTCGAGATCGAG---GGCGAGGGCGAGGGC--CGCC-CCTACGAGGGCACCCAGACCGC-CAAGCTGAAGGTG-ACCA-AGG---G-TGGCC---CCCT-GCCCTTCGCCT-GGGA-CATCCTGTCC--C--C-T-CAGTTCATGT-A-CGGCT-CCAAGGCCTACGTG-A--AGCAC--C--C--C--G-CCGACATCCCCG-A--CTAC-T--TGAAGCTG-TCCTTC--C--C-----CGA-GG--GCTTCAAGTGGGAGCG-CGTGATGAACTTCGAGGACGGCGGCGTGGTG-ACCG--T-GA-C-CCAGGAC-TC--CTCCCTGCAGGACGGCGAGTTCATCTACAAGGTG---AAGCTGCGCGGCACCAACTTCCCCT-CCGACGGCCCCGTA-ATGCA-GAAGAAGACCATGGGCTG--GGA-GGCCTCCTCCGAGCGGATGTACCCCGAGGA-CGGCGCC-CTGAAGGGCGAGATCAAGCAGA-GGCTGAAGC-TGAAGGACGGCGGCCACTACGACGCTGAGGTCAAGACCACCTACA-AGGCCAAGAAG-CCCGTGCAGCTGCCCGGC-GCCTACAACGTCAACATCAAGT-TG----GA-CATCACCTCCCACAACGAGGA-CTAC-A-C-CA---T-C-G-TGGAACAGTACG-AACGCGCCGAGGGCCGCCACTCCAC-CGGCGGCATGGACGAGCTGTACAAG
y:ATGGTGAGCAAGGGCGAGGA-G----C-T-G--TTCA-C-CGG-GGTGGTGCCCATCCTGGT-CGAGC-TGGACGGCGACGTAAACGGCCACAAGTTC-AG--CGTGTCCGGCGAGGGCGAGGGCGATGCCACCTAC---GGCAAGCTGACC-CTGAAG-TTCATTTGCACCACCGGCAAGCTGCCCGTGCCCTGGCCC-AC-CCTCGTGACCACCCTGACCTACGGCGTGCAGTGC-T-TCAGCCGCTACCCCGACC-ACATGAAGCAGCACGACTTCTTCAAGTCCGCCATGCCCGAAGGCTACGTCCAGGAGC-GCACCATCTTCTTCAAGGACGACGGCAACTACAAGA-CCCGCGCCGAGGTGAAGTTCGAGGGCGACACCCTGGTGAACCGCATCGAGCTGAAGGGCATCGACTTCAAGGAGGACGGC-A--ACATC--C-TGGGGCACAAGCTG-G-AGTA-CAACTACAACAGCC-ACAACGTC-TATAT-CATG--GCCGA-CAA--GCAGAAGAACGG-CA--T-C-A-AGG-TGAACTTC-AAGATC--CGCCAC--AA---C---ATCGAG--GACGGC---AGCGTGCAGCTCGCCGACCACTACCA-GC--A-G--AACACC-CC--CATCGGCGACG--GCCCCGTGCTGCTGCCCGACAACC-ACTACCTGAGCACCCAGTCCGCCCTGAGCAA-A-GACCC-CAACGAGAAGC-GCGATCACATGGTCCTGCTGG---AGTTCGTGAC-CGCC----GCCGGGA-T-CACTC-TCGGCATGGACGAGCTGTACAAG
" - ] - }, - "metadata": {}, - "execution_count": 15 - } + "text/plain": [ + "" ] - }, - { - "cell_type": "markdown", - "source": [ - "# Exercice 3 : Distribution des scores d’alignement pour des sĂ©quences alĂ©atoires\n", - "\n", - "Pour tester si un alignement reflĂšte une rĂ©elle similaritĂ© biologique, on va Ă©valuer la distribution des scores d’alignement pour des paires de sĂ©quences alĂ©atoires." - ], - "metadata": { - "id": "Q5jVeLfgMMtA" - } - }, - { - "cell_type": "markdown", - "source": [ - "Q1. En considĂ©rant deux sĂ©quences alĂ©atoires de mĂȘme taille N, oĂč chaque nuclĂ©otide apparaĂźt avec une probabilitĂ© uniforme de ÂŒ, calculer le score moyen attendu pour une superposition sans trou dans le cas oĂč une identitĂ© vaut +1 et une diffĂ©rence vaut 0." - ], - "metadata": { - "id": "6xyXw0HsMQGf" - } - }, - { - "cell_type": "markdown", - "source": [ - "```markdown\n", - "Votre rĂ©ponse ici\n", - "```" - ], - "metadata": { - "id": "meF18gt-Mhcn" - } - }, - { - "cell_type": "markdown", - "source": [ - "Q2. La question prĂ©cĂ©dente peut se resoudre analytiquement car on ne considĂšre pas de trou. Pour Ă©tendre le rĂ©sultat precedent Ă  un alignement avec trous, on va se baser sur la simulation de sĂ©quences aleatoires.\n", - "\n", - "GĂ©nĂ©rez $R$ paires de sĂ©quences alĂ©atoires de tailles $N$ avec des probabilitĂ©es uniformes d'apparition de nuclĂ©otides $p_A = p_T = p_G = p_C = $ ÂŒ. Affichez sous forme de violinplots les distribution des scores d'alignements entre chaque paire, obtenu par :\n", - " 1. un alignement sans trou (cf. Q1) ;\n", - " 2. un alignement local via Smith-Waterman (utilisez le code de l'exercice prĂ©cĂ©dent)\n", - "\n", - "Utilisez le schĂ©ma d'Ă©valuation suivant :" - ], - "metadata": { - "id": "fP5_mHnYMkNI" - } - }, - { - "cell_type": "code", - "source": [ - "rmap = {\"A\": 0, \"T\": 1, \"G\": 2, \"C\": 3}\n", - "sigma = np.array([[1, -0.5, -0.5, -0.5],\n", - " [-0.5, 1, -0.5, -0.5],\n", - " [-0.5, -0.5, 1, -0.5],\n", - " [-0.5, -0.5, -0.5, 1]])\n", - "go =0\n", - "ge = 0.5" - ], - "metadata": { - "id": "akUVqotnOLkH" - }, - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "#Votre code ici" - ], - "metadata": { - "id": "UX0afNaqOVZ2" - }, - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "markdown", - "source": [ - "Q3. Qu'observez-vous ?" - ], - "metadata": { - "id": "UNn9fUuXO4Le" - } - }, - { - "cell_type": "markdown", - "source": [ - "```markdown\n", - "Votre rĂ©ponse ici\n", - "```" - ], - "metadata": { - "id": "dSQEl0XXO8IG" - } - }, - { - "cell_type": "markdown", - "source": [ - "Q4. Quelle conclusion peut-on en tirer sur la significativitĂ© d'un alignement ?" - ], - "metadata": { - "id": "xHfVXpQhf15n" - } - }, - { - "cell_type": "markdown", - "source": [ - "```markdown\n", - "Votre rĂ©ponse ici\n", - "```" - ], - "metadata": { - "id": "5KjhEeHDgDns" - } + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" } - ] -} \ No newline at end of file + ], + "source": [ + "from IPython.display import HTML\n", + "HTML(\"
x:ATGGTGAGCAAGGGCGAGGAGGATAACATGGCCATCATCAAGGAGTTCATGCGCTTCAAGGTGC-A-CATGGAGGGCTCCGTGAACGGCCACGAGTTCGAGATCGAG---GGCGAGGGCGAGGGC--CGCC-CCTACGAGGGCACCCAGACCGC-CAAGCTGAAGGTG-ACCA-AGG---G-TGGCC---CCCT-GCCCTTCGCCT-GGGA-CATCCTGTCC--C--C-T-CAGTTCATGT-A-CGGCT-CCAAGGCCTACGTG-A--AGCAC--C--C--C--G-CCGACATCCCCG-A--CTAC-T--TGAAGCTG-TCCTTC--C--C-----CGA-GG--GCTTCAAGTGGGAGCG-CGTGATGAACTTCGAGGACGGCGGCGTGGTG-ACCG--T-GA-C-CCAGGAC-TC--CTCCCTGCAGGACGGCGAGTTCATCTACAAGGTG---AAGCTGCGCGGCACCAACTTCCCCT-CCGACGGCCCCGTA-ATGCA-GAAGAAGACCATGGGCTG--GGA-GGCCTCCTCCGAGCGGATGTACCCCGAGGA-CGGCGCC-CTGAAGGGCGAGATCAAGCAGA-GGCTGAAGC-TGAAGGACGGCGGCCACTACGACGCTGAGGTCAAGACCACCTACA-AGGCCAAGAAG-CCCGTGCAGCTGCCCGGC-GCCTACAACGTCAACATCAAGT-TG----GA-CATCACCTCCCACAACGAGGA-CTAC-A-C-CA---T-C-G-TGGAACAGTACG-AACGCGCCGAGGGCCGCCACTCCAC-CGGCGGCATGGACGAGCTGTACAAG
y:ATGGTGAGCAAGGGCGAGGA-G----C-T-G--TTCA-C-CGG-GGTGGTGCCCATCCTGGT-CGAGC-TGGACGGCGACGTAAACGGCCACAAGTTC-AG--CGTGTCCGGCGAGGGCGAGGGCGATGCCACCTAC---GGCAAGCTGACC-CTGAAG-TTCATTTGCACCACCGGCAAGCTGCCCGTGCCCTGGCCC-AC-CCTCGTGACCACCCTGACCTACGGCGTGCAGTGC-T-TCAGCCGCTACCCCGACC-ACATGAAGCAGCACGACTTCTTCAAGTCCGCCATGCCCGAAGGCTACGTCCAGGAGC-GCACCATCTTCTTCAAGGACGACGGCAACTACAAGA-CCCGCGCCGAGGTGAAGTTCGAGGGCGACACCCTGGTGAACCGCATCGAGCTGAAGGGCATCGACTTCAAGGAGGACGGC-A--ACATC--C-TGGGGCACAAGCTG-G-AGTA-CAACTACAACAGCC-ACAACGTC-TATAT-CATG--GCCGA-CAA--GCAGAAGAACGG-CA--T-C-A-AGG-TGAACTTC-AAGATC--CGCCAC--AA---C---ATCGAG--GACGGC---AGCGTGCAGCTCGCCGACCACTACCA-GC--A-G--AACACC-CC--CATCGGCGACG--GCCCCGTGCTGCTGCCCGACAACC-ACTACCTGAGCACCCAGTCCGCCCTGAGCAA-A-GACCC-CAACGAGAAGC-GCGATCACATGGTCCTGCTGG---AGTTCGTGAC-CGCC----GCCGGGA-T-CACTC-TCGGCATGGACGAGCTGTACAAG
\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Q5jVeLfgMMtA" + }, + "source": [ + "# Exercice 3 : Distribution des scores d’alignement pour des sĂ©quences alĂ©atoires\n", + "\n", + "Pour tester si un alignement reflĂšte une rĂ©elle similaritĂ© biologique, on va Ă©valuer la distribution des scores d’alignement pour des paires de sĂ©quences alĂ©atoires." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6xyXw0HsMQGf" + }, + "source": [ + "Q1. En considĂ©rant deux sĂ©quences alĂ©atoires de mĂȘme taille N, oĂč chaque nuclĂ©otide apparaĂźt avec une probabilitĂ© uniforme de ÂŒ, calculer le score moyen attendu pour une superposition sans trou dans le cas oĂč une identitĂ© vaut +1 et une diffĂ©rence vaut 0." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "meF18gt-Mhcn" + }, + "source": [ + "```markdown\n", + "P(AA) + P(TT) + P(GG) + P(CC) = (1/4 * 1/4) * 4 = 1/4 \n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "fP5_mHnYMkNI" + }, + "source": [ + "Q2. La question prĂ©cĂ©dente peut se resoudre analytiquement car on ne considĂšre pas de trou. Pour Ă©tendre le rĂ©sultat precedent Ă  un alignement avec trous, on va se baser sur la simulation de sĂ©quences aleatoires.\n", + "\n", + "GĂ©nĂ©rez $R$ paires de sĂ©quences alĂ©atoires de tailles $N$ avec des probabilitĂ©es uniformes d'apparition de nuclĂ©otides $p_A = p_T = p_G = p_C = $ ÂŒ. Affichez sous forme de violinplots les distribution des scores d'alignements entre chaque paire, obtenu par :\n", + " 1. un alignement sans trou (cf. Q1) ;\n", + " 2. un alignement local via Smith-Waterman (utilisez le code de l'exercice prĂ©cĂ©dent)\n", + "\n", + "Utilisez le schĂ©ma d'Ă©valuation suivant :" + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": { + "id": "akUVqotnOLkH" + }, + "outputs": [], + "source": [ + "rmap = {\"A\": 0, \"T\": 1, \"G\": 2, \"C\": 3}\n", + "sigma = np.array([[1, -0.5, -0.5, -0.5],\n", + " [-0.5, 1, -0.5, -0.5],\n", + " [-0.5, -0.5, 1, -0.5],\n", + " [-0.5, -0.5, -0.5, 1]])\n", + "go =0\n", + "ge = 0.5" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": { + "id": "UX0afNaqOVZ2" + }, + "outputs": [ + { + "data": { + "image/png": "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33HLLbfUur3JZMJ1112H++67D3PnzsX27dvx0ksvwd3dHQsXLgRQVWqnTp2KHTt2YP78+RgxYgTS0tKwaNEijB07FgcOHIBer7c95uHDh7FgwQI899xzcHd3x4svvojp06djwYIF2LRpE5YvXw5JkvDMM89gypQpSElJsd1/y5YtmDhxIoYOHYoPPvgA7u7u+Oabb3DLLbegvLy8xmt3//33Y/Lkyfj666+RkZGBp59+GjNmzLC9bj/++CNuvPFGuLu7206V1Gq1DXpNiaiNE0REDurTTz8VAMT+/fvr3MbPz0+Eh4fbPl+0aJG48Fff//73PwFAxMfH1/kY586dEwDEokWLaqyrfryFCxfWue5CwcHBQpKkGvu7+uqrhZubmygrK7N7bikpKXbbbdmyRQAQW7ZssS2bPHmyCA4OrjX7xblvvfVWodVqRXp6ut1211xzjXBychKFhYV2+5k0aZLddmvXrhUARGxsbK37qzZ06FAREBAgKioqbMuKi4uFl5eX3WsSGxsrAIhVq1bZ3T8jI0Po9Xoxf/58IYQQBw4cEADETz/9dMn9Xqw+99u+fbsAIJ577rk6t0lMTBQAxOzZs+2W7927VwAQ//nPf2zLYmJiBACxadMmu21XrFghFApFje/X6u/B3377TQghxGuvvSYA2L4W9bVhwwYBQLz11lt2y5ctW1bn929drFarMJlMYtu2bQKAOHz4sG1dbd/XMTExIiYmxvb5e++9JwCIDRs22G330EMPCQDi008/tS27++67BQCxdu1au20nTZokevXqZfv8//7v/wQA8f3339ttt3//fgFArF692rYsODhY6PV6cebMGduy+Ph4AUD4+/vbfs6EEOKnn34SAMQvv/xiW9a7d28RHR0tTCaT3b6mTJki/P39hcViEUL8+3N68ffFK6+8IgCIrKws27KIiAi714iI2hee/kdE7ZoQ4pLro6KioNFo8OCDD+Kzzz6rccpZfd1www313jYiIgKRkZF2y26//XYUFxcjLi6uUfuvr82bN+PKK69EYGCg3fKZM2eivLwcsbGxdsuvu+46u8/79+8PAEhLS6tzH2VlZdi/fz+mT58OnU5nW+7q6oprr73Wbtt169ZBkiTMmDEDZrPZduvcuTMiIyNtpzl2794dnp6eeOaZZ/DBBx8gISGhXs+3PvfbsGEDAOCRRx6p83G2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AgfOabDpjA5JRWrbcmdnZ8TExOCKK65AVFQUVCoOPk2Og6WqkViqiIiIqD0oKSnBli1bsGHDBiQmJtqWd3E245rASozobGiWIdorzBL+OqPFX2f0KPjnOimFQoEhQ4Zg4sSJGD58OLRabdN3RCQDlqpGYqkiIiKi9ub06dP45Zdf8Pvvv6OyshIA4KOzYFpIBUb5G6BoxJErgwX4PV2PDRk6lJurypSnpyeuu+46TJ48Gb6+vs35FIhkwVLVSCxVRERE1F6VlJRg/fr1+Pbbb1FQUAAACHIxY2avMnR3N1/m3lWEAPblavB/yU6266WCgoJwxx134IorroBarb7MIxA5DpaqRmKpIiIiovausrISP/30E7744guUlZVBgsCkoErcEFp+yVMCy0wSPjnhjP3nqk7n8/Pzw4MPPoixY8dCqVTWfUciB8VS1UgsVURERNRRFBYWYvXq1fjzzz8BAOEeJjzerwTO6ppvD7PKFHj9iBtyKpRQKpWYMWMGbr/9dl4vRe0aS1UjsVQRERFRR7Nt2za8/PLLqKioQIirGc9EFdsVq6xyBZbHuaPIqICfnx+WLFmCXr16yZiYqHXUtxtwTEsiIiKiDi4mJgZdu3bFU089hdSiIrx5xBXhnibb+tgcLYqMCoSFheG1116Dp6enjGmJ2h4eqboIj1QRERFRR/X333/j0UcfhdForLHO19cXH374IQsVdSg8UkVEREREDdKzZ0+88cYb+Ouvv3Dh3901Gg2mTp3KQkVUB5YqIiIiIrKJiIhARESE3DGIHEozzKNNRERERETUcbFUERERERERNQFLFRERERERUROwVBERERERETUBSxUREREREVETsFQRERERERE1AUsVERERERFRE7BUERERERERNQFLFRERERERUROwVBERERERETUBSxUREREREVETsFQRERERERE1AUsVERERERFRE7BUERERERERNQFLFRERERERUROwVBERERERETUBSxUREREREVETsFQRERERERE1AUsVERERERFRE7BUERERERERNQFLFRERERERUROwVBERERERETUBSxUREREREVETsFQRERERERE1AUsVERERERFRE7BUERERERERNQFLFRERERERUROwVBERERERETUBSxUREREREVETsFQRERERERE1AUsVERERERFRE7BUERERERERNQFLFRERERERUROwVBERERERETUBSxUREREREVETsFQRERERERE1AUsVERERERFRE7BUERERERERNQFLFRERERERURM0uFR9/vnnMBgMNZYbjUZ8/vnnzRKKiIiIiIjIUUhCCNGQOyiVSmRlZcHX19dueV5eHnx9fWGxWJo1YGsrLi6Gu7s7ioqK4ObmJnccIiIiIiKSSX27QYOPVAkhIElSjeVnzpyBu7t7Qx+OiIiIiIjIoanqu2F0dDQkSYIkSbjyyiuhUv17V4vFgpSUFEycOLFFQhIREREREbVV9S5V06ZNAwDEx8djwoQJcHFxsa3TaDQICQnBDTfc0OwBiYiIiIiI2rJ6l6pFixYBAEJCQnDLLbdAp9O1WCgiIiIiIiJHUe9SVe3uu+8GUDXaX25uLqxWq936oKCg5klGRERERETkABpcqk6ePIl7770Xu3fvtltePYCFo4/+R0RERERE1BANLlUzZ86ESqXCunXr4O/vX+tIgERERERERB1Fg0tVfHw8Dh48iN69e7dEHiIiIiIiIofS4Hmq+vTpg/Pnz7dEFiIiIiIiIofT4FK1cuVKzJ8/H1u3bkVeXh6Ki4vtbkRERERERB2JJIQQDbmDQlHVwy6+lqq9DFRRXFwMd3d3FBUVwc3NTe44REREREQkk/p2gwZfU7Vly5YmBSMiIiIiImpPGlyqYmJiWiJHvWzfvh2vvvoqDh48iKysLPz444+YNm2abb0QAi+++CL++9//oqCgAEOHDsV7772HiIgI2TITEREREVH71uBrqgBgx44dmDFjBkaMGIHMzEwAwBdffIGdO3c2a7iLlZWVITIyEu+++26t61955RW8/vrrePfdd7F//3507twZV199NUpKSlo0FxERERERdVwNLlXff/89JkyYAL1ej7i4OBgMBgBASUkJli9f3uwBL3TNNddg6dKlmD59eo11Qgi8+eabeO655zB9+nT07dsXn332GcrLy/H111+3aC4iIiIiIuq4Glyqli5dig8++AAfffQR1Gq1bfmIESMQFxfXrOEaIiUlBdnZ2Rg/frxtmVarRUxMDHbv3i1bLiIiIiIiat8afE1VUlISxowZU2O5m5sbCgsLmyNTo2RnZwMA/Pz87Jb7+fkhLS2tzvsZDAbb0TYAHBaeiIiIiIgapMFHqvz9/ZGcnFxj+c6dOxEaGtosoZqirqHe67JixQq4u7vbboGBgS0dkYiIiIiI2pEGl6qHHnoITzzxBPbu3QtJknD27Fl89dVXmDdvHmbPnt0SGeulc+fOAP49YlUtNze3xtGrCy1YsABFRUW2W0ZGRovmJCIiIiKi9qXBp//Nnz8fRUVFGDduHCorKzFmzBhotVrMmzcPjz76aEtkrJdu3bqhc+fO2LhxI6KjowEARqMR27Ztw8qVK+u8n1arhVarba2YRERERETUzjS4VAHAsmXL8NxzzyEhIQFWqxV9+vSBi4tLc2erobS01O7Uw5SUFMTHx8PLywtBQUGYM2cOli9fjh49eqBHjx5Yvnw5nJyccPvtt7d4NiIiIiIi6pgaVaoAwMnJCYMGDWrOLJd14MABjBs3zvb5U089BQC4++67sWbNGsyfPx8VFRWYPXu2bfLfP//8E66urq2ak4iIiIiIOg5JCCEacofKykq888472LJlC3Jzc2G1Wu3WyzmsenMoLi6Gu7s7ioqK4ObmJnccIiIiIiKSSX27QYOPVN17773YuHEjbrzxRgwZMuSSI+sRERERERG1dw0uVevXr8dvv/2GkSNHtkQeIiIiIiIih9LgIdW7dOnCa5SIiIiIiIj+0eBStWrVKjzzzDNIS0triTxEREREREQOpcGn/w0aNAiVlZUIDQ2Fk5MT1Gq13fr8/PxmC0dERERERNTWNbhU3XbbbcjMzMTy5cvh5+fHgSqIiIiIiKhDa3Cp2r17N2JjYxEZGdkSeYiIiIiIiBxKg6+p6t27NyoqKloiCxERERERkcNpcKl6+eWXMXfuXGzduhV5eXkoLi62uxEREREREXUkkhBCNOQOCkVVD7v4WiohBCRJgsViab50MqjvrMlERERERNS+1bcbNPiaqi1btjQpGBERERERUXvS4FIVExPTEjmIiIiIiIgcUoOvqQKAHTt2YMaMGRgxYgQyMzMBAF988QV27tzZrOGIiIiIiIjaugaXqu+//x4TJkyAXq9HXFwcDAYDAKCkpATLly9v9oBERERERERtWYNL1dKlS/HBBx/go48+glqtti0fMWIE4uLimjUcERERERFRW9fgUpWUlIQxY8bUWO7m5obCwsLmyEREREREROQwGlyq/P39kZycXGP5zp07ERoa2iyhiIiIiIiIHEWDS9VDDz2EJ554Anv37oUkSTh79iy++uorzJs3D7Nnz26J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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import seaborn as sns\n", + "import random\n", + "\n", + "def generate_random_sequence(N):\n", + " \"\"\"GénÚre une séquence aléatoire de longueur N avec des nucléotides équiprobables.\"\"\"\n", + " return \"\".join(random.choices(\"ATGC\", k=N))\n", + "\n", + "def alignment_score_no_gap(x, y, sigma, cmap):\n", + " \"\"\"Calcule le score d'alignement sans trou entre deux séquences x et y.\"\"\"\n", + " return sum(sigma[cmap[x[i]], cmap[y[i]]] for i in range(len(x)))\n", + "\n", + "def sw_fwd(x, y, cmap, sigma, gap_params):\n", + " \"\"\"Construit les matrices de score et de backtracking pour Smith-Waterman.\"\"\"\n", + " go, ge = gap_params\n", + " S = np.zeros((len(x) + 1, len(y) + 1))\n", + " B = np.zeros((len(x) + 1, len(y) + 1), dtype=int)\n", + " max_score = 0\n", + " max_pos = (0, 0)\n", + " \n", + " for i in range(1, len(x) + 1):\n", + " for j in range(1, len(y) + 1):\n", + " match = S[i-1, j-1] + sigma[cmap[x[i-1]], cmap[y[j-1]]]\n", + " delete = S[i-1, j] - (go if B[i-1, j] == 1 else ge)\n", + " insert = S[i, j-1] - (go if B[i, j-1] == 2 else ge)\n", + " S[i, j], B[i, j] = max((0, 0), (match, 3), (delete, 1), (insert, 2))\n", + " if S[i, j] > max_score:\n", + " max_score = S[i, j]\n", + " max_pos = (i, j)\n", + " \n", + " return S, B, max_score, max_pos\n", + "\n", + "def simulate_alignments(R, N, sigma, cmap, gap_params):\n", + " \"\"\"Simule R paires de séquences et calcule les scores d'alignement.\"\"\"\n", + " scores_no_gap = []\n", + " scores_sw = []\n", + " \n", + " for _ in range(R):\n", + " x = generate_random_sequence(N)\n", + " y = generate_random_sequence(N)\n", + " \n", + " score_no_gap = alignment_score_no_gap(x, y, sigma, cmap)\n", + " scores_no_gap.append(score_no_gap)\n", + " \n", + " S, B, max_score, _ = sw_fwd(x, y, cmap, sigma, gap_params)\n", + " scores_sw.append(max_score)\n", + " \n", + " return scores_no_gap, scores_sw\n", + "\n", + "def plot_violin(scores_no_gap, scores_sw):\n", + " \"\"\"Affiche les distributions des scores sous forme de violin plots.\"\"\"\n", + " data = [scores_no_gap, scores_sw]\n", + " labels = [\"Sans trou\", \"Smith-Waterman\"]\n", + " \n", + " plt.figure(figsize=(10, 6))\n", + " sns.violinplot(data=data)\n", + " plt.xticks(ticks=[0, 1], labels=labels)\n", + " plt.ylabel(\"Score d'alignement\")\n", + " plt.title(\"Distribution des scores d'alignement\")\n", + " plt.show()\n", + "\n", + "# ParamÚtres\n", + "total_sequences = 100 # Nombre de paires (R)\n", + "sequence_length = 50 # Taille des séquences (N)\n", + "rmap = {\"A\": 0, \"T\": 1, \"G\": 2, \"C\": 3}\n", + "sigma = np.array([[1, -0.5, -0.5, -0.5],\n", + " [-0.5, 1, -0.5, -0.5],\n", + " [-0.5, -0.5, 1, -0.5],\n", + " [-0.5, -0.5, -0.5, 1]])\n", + "gap_params = (0, 0.5)\n", + "\n", + "# Exécution de la simulation et affichage des résultats\n", + "scores_no_gap, scores_sw = simulate_alignments(total_sequences, sequence_length, sigma, rmap, gap_params)\n", + "plot_violin(scores_no_gap, scores_sw)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "UNn9fUuXO4Le" + }, + "source": [ + "Q3. Qu'observez-vous ?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "dSQEl0XXO8IG" + }, + "source": [ + "```markdown\n", + "Smith Waterman really help finding a positive score. The difference of score is really big between the no hole sequences and the Smith Waterman ones.\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "xHfVXpQhf15n" + }, + "source": [ + "Q4. Quelle conclusion peut-on en tirer sur la significativité d'un alignement ?" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "5KjhEeHDgDns" + }, + "source": [ + "```markdown\n", + "With random sequences, it really helps finding similarities between them. So between close related sequences, it would help as well (especially if they are unalined). That can help as well finding similarities between different species and see if they have a common ancestors or if they have a ancestor-heir relationship. The score can help for the overall sequence, but we can as well take parts of sequence between two species and check their score. That can help find characteristic of two species that is shared among them and see if a part of the sequence is responsable for it. \n", + "```" + ] + } + ], + "metadata": { + "colab": { + "authorship_tag": "ABX9TyNSXnqaXAUgZK9rmJ1TWbGo", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +}