Le module pdb.chain est un outil de base qui permet de lire un fichier PDB puis de créer les chaines polypeptidiques correspondantes, sans utiliser la colonne correspondant à la lettre de la chaine. Une colonne qui peut être vide, ou associée à des identifiants qui posent problème. Avant de statuer sur cette question explorons d’abord le contenu du module chain et les fonctionnalités de base qu’il apporte. Peut être nous aurons alors une autre conception de la lecture d’un fichier PDB mais du point de vue de ceux qui utilisent ce type de fichiers, avant tout pour de la modélisation.
Nous allons utiliser la structure 1P45 [Kuo_2003]. Il s’agit de l’enzyme InhA (1), une Enoyl ACP (Acyl Carrier Protein) reductase qui est impliquée dans la biosynthèse des acides mycoliques des mycobactéries, via leur complexe FAS-II. Ces acides gras particuliers se trouvent dans la paroi des mycobactéries et leur donnent des propriétés de résistance, leur biosynthèse est donc une cible pour la conception de molécules à visée thérapeutique (Tuberculose, Malaria). Cette structure inclue 2 chaines (A, B) et est ligandée dans chaque chaine par le cofacteur NAD (Nicotinamide-Adénine-Dinucléotide) et un inhibiteur direct, le triclosan (TCL) qui apparaît un (chaine B) ou deux (chaine A) exemplaires dans dans le site de liaison.
Publication initiale sur buidez.net (2014) – Article mis à jour en Avril 2025.
Navigation dans le guide
Guide [ Composants : buildez.pdb ]
1. Calculs initiaux
Les premières lignes de code consistent à calculer une matrice pdbm et une resmap. La ligne d’indice zéro de la matrice pdbm correspondra à la première ligne du fichier PDB.
|
1 2 3 4 5 6 7 |
s = pdb_file2str(file_cleanpath(site_package + "/test/1p45.pdb")) print("*** computing a pdbm matrix and a resmap") pdbm = pdb_str2pdbm(s) print(pdbm[602:605]) resmap = pdb_pdbm2resmap(pdbm) print(resmap[0:5]) print("found %d residues in this PDB file" % (len(resmap))) |
Si on regarde le fichier PDB au niveau des lignes 601 à 606 nous aurons :
|
1 2 3 4 5 6 |
SCALE1 0.010546 0.000000 0.000000 0.00000 SCALE2 0.000000 0.009604 0.000000 0.00000 SCALE3 0.000000 0.000000 0.005270 0.00000 ATOM 1 N THR A 2 -21.979 -5.864 25.433 1.00 71.17 N ATOM 2 CA THR A 2 -21.056 -5.594 24.267 1.00 71.52 C ATOM 3 C THR A 2 -19.947 -4.539 24.552 1.00 71.25 C |
Alors qu’une partie du résultat produit par le code précédent donnera:
|
1 2 3 4 |
*** computing a pdbm matrix and a resmap [['-', 603], ['ATOM', 1, 'N', '', 'THR', 'A', 2, '', -21.979, -5.864, 25.433, 1.0, 71.17, '', 'N', '', 604], ['ATOM', 2, 'CA', '', 'THR', 'A', 2, '', -21.056, -5.594, 24.267, 1.0, 71.52, '', 'C', '', 605]] |
Nous constatons que pdbm[602] correspond (indice) bien à la ligne 603 (index) du fichier PDB. Comme il ne s’agit pas d’une ligne de coordonnées moléculaires, la ligne ne contient que deux éléments: '-' et le numéro 603 qui correspond au numéro de ligne (index) dans le fichier PDB.
Les lignes suivantes de la matrice pdbm correspondent à des coordonnées moléculaires. Nous retrouvons tous les champs, avec le type de données adéquat (ce qui est numérique est entier ou réel, ce qui est texte est de type string). A la fin de chaque ligne de la matrice nous retrouvons en dernière colonne la valeur de l’index correspondant dans le fichier PDB.
L’affichage des 5 premières lignes de la resmap calculée à la suite, donnera:
[['THR', 2, 604, 608, -1, ''],['GLY', 3, 609, 612, -1, ''],['LEU', 4, 613, 620, -1, ''],['LEU', 5, 621, 628, -1, ''],['ASP', 6, 629, 636, -1, '']]found 586 residues in this PDB file |
Il s’agit des 5 premiers résidus, le premier résidu THR2 dont on voyait les deux premiers atomes dans l’affichage de la matrice pdbm, commence à la ligne 604 et finit à la ligne 608 (index) du fichier PDB, soit 603 et 607 (indices) de la matrice pdbm. La longueur de la resmap donnera le nombre de résidus (acides aminés, ligands, eau) total (sur plusieurs chaines/sous-unités) dans le fichier PDB.
2. Extraction d’un polypeptide (chaine)
Une fois les structures de données mises en place, nous pouvons calculer une liste de résidus correspondant à la première chaîne polypeptidique, la chaine d’indice zéro:
|
1 2 3 4 5 6 7 |
print("*** output of the (first) polypeptidic chain starting at resmap[0]") chain, code, endres = pdb_pdbm2chain(pdbm, resmap, 0) print("=> code = %d ; last resmap indice = %d" % (code, endres)) print("=> chain length is %d residues (length of resmap indices list)" % (len(chain))) print("list of resmap indices corresponding to the residues of this chain:\n", chain) print(resmap[0:3]) print(resmap[endres-2:endres+1]) |
On utilise la fonction pdb_pdbm2chain qui va utiliser la resmap et la matrice pdbm ainsi que le numéro d’indice de la chaîne souhaitée. Ce qui va donner comme résultat partiel:
*** output of the (first) polypeptidic chain starting at resmap[0]=> code = 1 ; last resmap indice = 267=> chain length is 268 residues (length of resmap indices list)list of resmap indices corresponding to the residues of this chain:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ..., 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267][['THR', 2, 604, 608, -1, ''], ['GLY', 3, 609, 612, -1, ''], ['LEU', 4, 613, 620, -1, '']][['GLN', 267, 2572, 2580, -1, ''], ['LEU', 268, 2581, 2588, -1, ''], ['LEU', 269, 2589, 2597, -1, '']] |
L’affichage des indices de résidus est tronqué (‘…’) pour éviter une liste trop longue. Nous avons donc une première chaine qui est constituée par les résidus d’indice 0 à 267 dans la resmap (globale). Cette chaine est constituée de de 268 résidus (longueur de la liste précédente).
Nous pouvons faire afficher les 3 premiers résidus de la resmap [0 .. 3[ qui correspondront (attention aux bornes ouvertes/fermées des intervalles) aux 3 premiers résidus [1 .. 3[ de la structure PDB pour cette chaine, soit:
[['THR', 2, 604, 608, -1, ''],['GLY', 3, 609, 612, -1, ''],['LEU', 4, 613, 620, -1, '']] |
La fonction pdb_pdbm2chain renvoie aussi l’indice du dernier résidu (la variable endres dans le code précédent). On peut aussi faire afficher les 3 derniers résidus de la chaine, soit [endres-2 .. endres+1[ ce qui va donner:
[['GLN', 267, 2572, 2580, -1, ''],['LEU', 268, 2581, 2588, -1, ''],['LEU', 269, 2589, 2597, -1, '']] |
Surprise … Nous trouvons un résidu LEU269, la chaine de longueur 268 ne s’arrête pas à cette valeur d’index, vérifions dans le fichier à la limite de la chaine A et de la chaine B:
|
1 2 3 4 5 6 7 |
2595: ATOM 1992 CD1 LEU A 269 -5.783 29.100 44.335 1.00 53.94 C 2596: ATOM 1993 CD2 LEU A 269 -3.805 27.806 43.685 1.00 49.77 C 2597: ATOM 1994 OXT LEU A 269 -7.473 26.313 42.046 1.00 49.34 O 2598: TER 1995 LEU A 269 2599: ATOM 1996 N THR B 2 23.209 54.177 22.847 1.00 84.14 N 2600: ATOM 1997 CA THR B 2 23.876 53.466 23.993 1.00 83.90 C 2601: ATOM 1998 C THR B 2 25.150 52.692 23.599 1.00 82.41 C |
C’est bien ce que l’on constate, la raison est simple la chaine démarre au résidu numéro 2 (THR2) et non au premier résidu de la séquence protéique, cela arrive souvent dans les fichiers PDB.
C’est un des intérêts de ce découpage en chaines qui est indépendant de ce qui est codé dans le fichier PDB: nous travaillerons sur des listes de résidus contigus sans être perturbés par la numérotation utilisée dans le fichier PDB.
3. Une approche structurale au cœur du module
Avant de continuer prenons le temps de regarder comment la fonction pdb_pdbm2chain fonctionne. Elle va itérer pour chaque résidu et examiner sa capacité à établir une liaison peptidique avec le résidu suivant, en examinant la présence des atomes impliqués dans chaque résidus et en testant la possibilité de la liaison peptidique:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
resmap_n = len(resmap) i = indice chain = [] while (i < resmap_n): hit = 0 linked = 0 if (pdb_bond_restype(pdbm,resmap,i,2) == 'aa'): hit += 1 if (i < (resmap_n-1)): if (pdb_bond_restype(pdbm,resmap,i+1,2) == 'aa'): hit += 1 if (hit == 2): pdbm1 = pdb_reslist2pdbm(pdbm, resmap, [i], 0) pdbm2 = pdb_reslist2pdbm(pdbm, resmap, [i+1], 0) try: linked = pdb_pdbm_reslinked(pdbm1, pdbm2) except: msg_str(ms, "error: %s %d %s %d\n" % (str(resmap[i]), len(pdbm1), str(resmap[i+1]), len(pdbm2),)) if (hit == 1): if (pdb_bond_restype(pdbm,resmap,i,2) != 'aa'): hit -= 1 if ((hit == 1) and (linked == 0)): chain.append(i) return(chain, 1, i) if ((hit == 2) and (linked == 0)): chain.append(i) return(chain, 1, i) if ((hit == 2) and (linked == 1)): chain.append(i) i += 1 |
La capacité a pouvoir réaliser une liaison peptidique se base sur l’identité du résidu, elle utilise la fonction pdb_bond_restype du module pdb.residue. Cette fonction teste la présence des atomes de type ‘CA’ (carbone alpha) et ‘N’, ‘C’ qui peuvent être impliqués dans une liaison peptidique entre deux résidus.
Si c’est le cas, un calcul de distance est effectué en espérant trouver une distance C-CA et CA-N intra résidu qui soit inférieure ou égale à 1.6 Å (une valeur non canonique mais qui s’avère bien fonctionner dans le cas de fichiers PDB).
![]() |
La liaison inter résidus est testée avec la fonction pdb_pdbm_reslinked du module pdb.residue. Cette fonction récupère les blocs de matrice pdbm correspondant à chaque résidu et vérifie la liaison C-N coté carbone et coté azote, avec une valeur de 1.55 Å. Comme dans le cas précédent, des essais ont montré que cette valeur constituait un bon compromis pour ce calcul précis, dans le cas de protéines/structures PDB, même si la longueur de la liaison ne correspond pas aux valeurs canoniques.
Si linked=1 on ajoute le résidu à la chaine et on passe au suivant, si ce n’est pas le cas, on finit la chaîne avec ce résidu.
Au niveau de la construction de la chaine, nous avons raisonné en biochimistes, en reconstruisant le polypeptide, juste sur la base du type d’atome et des coordonnées atomiques. Il n’y a pas de filtre textuel (GREP, expression régulières …) qui trieraient des données et nous n’avons pas besoin d’une lettre pour identifier une chaine dans la structure PDB.
4. Analyse de l’ensemble des chaines
Si nous voulons extraire une liste des chaines, nous utilisons simplement la fonction pdb_pdbm2chains :
|
1 2 3 4 5 6 7 8 |
print("*** output of all polypeptidic chains") chains = pdb_pdbm2chains(pdbm, resmap) chains_n = len(chains) print("number polypeptidic of chains = %d" % (chains_n)) print("matrix of resmap indices corresponding to the residues of all chains (a row = a chain)") for i in range (0, chains_n, 1): print("chain index = %d" % (i+1)) print(chains[i]) |
Ce qui va donner comme résultat (l’affichage a été ‘…’ simplifié) :
*** output of all polypeptidic chainsnote: we don't use PDB chain letter info, because this field could not existsnumber polypeptidic of chains = 2matrix of resmap indices corresponding to the residues of all chains (a row = a chain)chain index = 1[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ..., 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267]chain index = 2[268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, ..., 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535] |
A partir du moment ou nous disposons de l’information sur les chaines, nous pouvons utiliser ces listes d’indices de résidus dans d’autres calculs ou utiliser des fonctions qui permettent un affichage plus lisible. Par exemple les fonctions pdb_chain2rnames, pdb_chain2rseqs :
|
1 2 3 4 5 6 7 8 9 10 |
print("*** fancy output of chains") print("converting resmap indices for all chains in residues indexes or names") print("note: sometimes chains have not the same number of residues") print("note: we add an offset of 1000 last chains to differentiate") for i in range (0, chains_n, 1): print() print("chain indice = %d" % (i)) print("chain length = %d AA" % (len(chains[i]))) print("resname =", pdb_chain2rnames(resmap, chains, i)) print("resseq =", pdb_chain2rseqs(resmap, chains, i, i*1000)) |
Ce qui donnera:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
*** fancy output of chains converting resmap indices for all chains in residues indexes or names notr: sometimes chains have not the same number of residues note: we add an offset of 1000 last chains to differentiate chain indice = 0 chain length = 268 AA resname = ['THR', 'GLY', 'LEU', 'LEU', 'ASP', ..., 'HIS', 'THR', 'GLN', 'LEU', 'LEU'] resseq = [2, 3, 4, 5, 6, ..., 265, 266, 267, 268, 269] chain indice = 1 chain length = 268 AA resname = ['THR', 'GLY', 'LEU', 'LEU', 'ASP', ..., 'HIS', 'THR', 'GLN', 'LEU', 'LEU'] resseq = [1002, 1003, 1004, 1005, 1006, ..., 1265, 1266, 1267, 1268, 1269] |
Nous remarquerons que à partir du moment ou on n’utilise plus les numéros de résidus comme clé principale pour identifier des résidus, nous pouvons afficher ce que l’on veut, voire renuméroter les résidus, comme dans la seconde chaine.
5. Attribution des identifiants de chaines
En ce qui concerne les chaines, au sens PDB du terme, c’est à dire un groupe de résidus à qui on a attribué la même lettre (CHAIN ID), le module chain permet également de les calculer. Les informations sont simplement extraites des matrices pdbm ou des resmap.
Il ne faut pas se méprendre sur l’apparente simplicité de ce concept, car une chaine dans l’espace buildez.pdb (polypeptide) peut être différente d’une chaine telle qu’elle est définie par la lettre dans le fichier PDB. En effet, si pour une chaine donnée, une (pseudo) délétion est présente dans la structure, la fonction pdb_pdbm2chains verra 2 polypeptides donc deux chaines, alors que la fonction pdb_pdbm2chains_pdb ne verra qu’une seule chaine.
Si nous voulons sortir les lettres PDB correspondant aux chaines déjà calculées, on peut utiliser la fonction pdb_chains2letters :
|
1 2 3 |
print("*** get PDB letters for the chains (polypeptides)") pdb_letters = pdb_chains2letters(pdbm, resmap, chains) print("letters =", pdb_letters) |
Ce qui va donner :
*** get PDB letters for the chains (polypeptides)letters = ['A', 'B'] |
Dans le cas de 1P45 il n’y a pas d’irrégularités telles que des résidus incomplets ou des (pseudo) délétions, donc le résultat est identique nous avons deux chaines.
6. Traitement des irrégularités
Cette question est partiellement traitée (en ce qui concerne les ‘pseudo délétions’ et l’impact de résidus tronqués) dans l’article [ buildez.pdb : module chain et ‘pseudo délétions’ ].
7. Chaines uniques
Dans le cas d’une structure régulière, la fonction pdb_pdbm2chains_pdb donnera le même résultat que sa contre partie pdb_pdbm2chains. Cette fonction produit une liste de chaines, au sens de la PDB, s’il y a plusieurs polypeptides elle les analyse en fonction des numéros de résidus (RESSEQ) et fournit une liste de chaines, comme un parseur ‘positif’. Par exemple le code suivant :
|
1 2 3 4 5 6 7 8 9 |
print("*** search for letters and PDB chains") pdb_chains = pdb_pdbm2chains_pdb(pdbm, resmap) pdb_chains_n = len(pdb_chains) for i in range (0, pdb_chains_n, 1): print("chain indice = %d" % (i)) print("chain length = %d AA" % (len(pdb_chains[i]))) print("*** get PDB letters for the PDB chains (polypeptides)") pdb_letters = pdb_chains2letters(pdbm, resmap, pdb_chains) print("letters =", pdb_letters) |
Nous donnera :
*** search for PDB chainschain indice = 0chain length = 268 AAchain indice = 1chain length = 268 AA*** get PDB letters for the PDB chains (polypeptides)letters = ['A', 'B'] |
A noter que l’ordre des lettres obtenues avec la fonction pdb_chains2letters correspond strictement a celui obtenu avec pdb_pdbm2chains_pdb ou pdb_pdbm2chains.
Le paquetage buildez.pdb comporte le module pdbcheck qui est plus spécialisé dans la détection et l’analyse des fichiers PDB. Le module chain.py pour sa part ne fournit que des outils de base liés à la transformation de matrices pdbm et resmap en chaines dans l’espace buildez.pdb (les ‘irrégularités’ sont prises en compte) ou PDB (les pseudos délétions et résidus incorrects ne sont pas pris en compte).
8. Référentiel
Fonctions du module chain.py en mode abrégé (avril 2025).
La plupart des fonction nécessitent en argument une pdbm et une resmap.
| Fonction | Utilisation |
pdb_pdbm2chain |
La fonction analyse une pdbm et une resmap et retourne une liste d’indices de resmap correspondant à la chaine dont l’indice est donné en argument, utilise le système logfile/loglevel de buildez. |
pdb_pdbm2chains |
La fonction utilise pdb_pdbm2chain jusqu’à épuisement des possibilités dans la structure PDB, renvoie une liste de chaines, utilise le système logfile/loglevel. |
pdb_chain2resmap |
Pour une pdbm, resmap, la liste de chaines, un indice de chaine, la fonction renvoie une resmap correspondant à la chaine. |
pdb_chains2resmaps |
Utilise pdb_chain2resmap pour produire une liste de resmap dont les items correspondent à un bloc resmap pour une chaine. |
pdb_chain2pdbm |
Analogue à pdb_chain2resmap mais produit un bloc pdbm correspondant à la chaine dont l’indice est donné en argument. |
pdb_chains2pdbms |
Itère pdb_chain2pdbm sur l’ensemble de la structure (analogue à pdb_chains2resmaps) de manière à produire une liste de blocs pdbm correspondant chacun à une chaine. |
pdb_resmap_getchains |
A partir d’une resmap et d’une liste de chaines, la fonction annote la resmap pour les chaines, la resmap augmentée inclura l’indice de chaine en position (indice) 4 et le type de résidu en position 5. Le type de résidu (ex: ‘aa’, ‘alt’, ‘wat’, ‘ion’ …) est déterminé par la fonction pdb_isaatype. |
pdb_pdbm2chains_pdb |
Interface à pdb_chains_pdb et retourne une liste de chaines au sens PDB. |
pdb_chains_pdb |
Utilise la liste de chaines calculée par pdb_pdbm2chains et tente un réassemblage en utilisant les identifiants de chaines fournis sous la forme d’une liste de lettre cohérente avec la liste de chaines. Les résidus doivent être ordonnées dans les chaines, si ce n’est pas le cas, il faut trier les données initiales avec la fonction pdb_pdbm_sortm de pdb.pdbm. |
pdb_chain2rnames |
A partir d’une resmap, d’une liste de chaine, d’un indice de chaine, la fonction utilise la resmap pour produire une liste de résidus pour la chaine. |
pdb_chain2rseqs |
Analogue a pdb_chain2rnames mais va produire des numéros d’ordres de résidus (RESSEQ). |
pdb_chains2letters |
Utilise la resmap, pdbm et liste de chaines pour produire une liste de lettres (identifiants de chaine) cohérente avec la liste de chaines fournie en argument. |
pdb_chains2augresmap |
La fonction regroupe toute l’information à partir des listes de chaines, resmap, pdbm et effectue une attribution basée sur une mesure de distance. Cette fonction est capable d’attribuer des molécules d’eau, des ions, des ligands à une chaine, même si ces blocs HETATM n’ont pas d’identifiant (lettre) de chaine dans le fichier PDB, ou que celui ci soit différent des chaines polypeptidiques (ex: lettre W pour les molécules d’eau). Produit une resmap augmentée, avec indice de chaine (position 4) et type de résidu (position 5). Utilise le système logfile/loglevel. |
9. Conclusion
Le module pdb.chain permet d’analyser la structure protéique d’une manière indépendante à beaucoup de données du fichier PDB et peut donc prendre en charge sans modification, des résidus non standards pour peu qu’ils soient engagés dans une liaison peptidique avec le reste de la chaine polypeptidique.
Par rapport à la question posée au début de l’article, on constate au fur et à mesure de la description des fonctions que le module permet d’aller vers quelques subtilités qui risquent de passer au travers d’une analyse textuelle. Nous verrons dans les articles suivants, que des structures de données aussi triviales qu’une matrice pdbm, une resmap et une liste d’indice de résidus par chaine permettent d’obtenir des choses étonnantes. Avec peu de programmation supplémentaire mais en jouant sur une bonne combinaison de ces structures de données très spécialisées.
Liens et lectures
- Structure 1P45[ https://www.rcsb.org/structure/1P45 ].
- Ligand Expo NAD [ https://www.rcsb.org/ligand/NAD ].
- Ligand expo: Triclosan/TCL [ https://www.rcsb.org/ligand/TCL ].
- Uniprot P9WGR1 · INHA_MYCTU [ https://www.uniprot.org/uniprotkb/P9WGR1/entry ].
- (1) Ne pas confondre InhA (gène bactérien) avec l’Inhibin A/alpha (gène INHA humain).
- Acides Mycoliques [ https://fr.wikipedia.org/wiki/Acide_mycolique ].
- Biosynthèse des acides gras [ https://fr.wikipedia.org/wiki/Biosynth%C3%A8se_des_acides_gras ].
- Mycobacteriaceae [ https://fr.wikipedia.org/wiki/Mycobacteriaceae ].
