Iteratory¶
Celem tego zadania jest dodanie brakujących iteratorów do klas Chain i Structure.
Klasy opisujące biomakromolekuły¶
Makromolekuły z których zbudowane są organizmy żywe: białka oraz kwasy nukleinowe
składają się z oddzielnych łańcuchów. Każdy z nich składa się z reszt aminokwasowych
(w przypadku białek) bądź nukleotydów (kwasy nukleinowe). Każda z reszt zaś zbudowana
jest z atomów. Poniższy program jest odtworzeniem tej biochemicznej konstrukcji w postaci klas
Structure, Chain, Residue i Atom. Struktura biomolekuły (Structure)
przechowuje w prywatnym słowniku swoje łańcuchy. Każdy z łańcuchów (Chain) zawiera słownik
reszt. Wreszcie, każda z reszt (Residue) zawiera listę jej atomów. Kompletny kod tych klas
znajduje się na dole strony.
Iteratory¶
W przyjętej konstrukcji odwiedzenie wszystkich atomów ze struktury wymaga de facto trzech petli:
1from protein_structure import *
2
3strctr = read_pdb_content("2GB1", open("2gb1.pdb"))
4for chain in strctr.chains():
5 for residue in chain.residues():
6 for atom in residue.atoms():
7 print(atom.name)
Zaimplementuj brakujące metody: atoms() i residues() w klasie Structure,
oraz atoms() w klasie Chain tak, aby możliwe było iterowanie
po atomach i resztach w jednej pętli. Dla przykładu, poniższy kod powinien prawidłowo policzyć
środek masy całego białka.
1from protein_structure import *
2
3strctr = read_pdb_content("2GB1", open("2gb1.pdb"))
4n, cm_x, cm_y, cm_z = 0, 0.0, 0.0, 0.0
5for a in strctr.atoms():
6 cm_x += a.x
7 cm_y += a.y
8 cm_z += a.z
9 n += 1
10cm_x /= n
11cm_y /= n
12cm_z /= n
Zaimplementowanie iteratorów przyniesie także inne korzyści. Dla przykładu, użytkownicy
będą mogli wykorzystać bibliotekę itertools w analizie struktur.
Poniżej, oprócz klas reprezentujących biomolekuły, dołączono prosty test
obliczający mapę odległości pomiędzy węglami alfa (atomy o nazwie ” CA “) wczytanego białka.
1from __future__ import annotations
2
3import itertools
4import math
5from dataclasses import dataclass, field
6from typing import Dict
7
8
9@dataclass
10class Structure:
11 id_code: str
12 __chains: Dict[str, Chain] = field(default_factory=dict)
13
14 def get_chain(self, chain_id: str):
15 return self.__chains.get(chain_id, None)
16
17 def add_chain(self, chain: Chain):
18 self.__chains[chain.code] = chain
19
20 def chains(self):
21 return self.__chains.values()
22
23 def atoms(self):
24 raise NotImplemented
25
26 def residues(self):
27 raise NotImplemented
28
29@dataclass
30class Chain:
31 code: str
32 __residues: Dict[str, Residue] = field(default_factory=dict)
33
34 def get_residue(self, res_seq: int, i_code: str):
35 key = str(res_seq) + i_code
36 return self.__residues.get(key, None)
37
38 def add_residue(self, res: Residue):
39 key = str(res.res_seq) + res.i_code
40 self.__residues[key] = res
41
42 def residues(self):
43 return self.__residues.values()
44
45 def atoms(self):
46 raise NotImplemented
47
48
49@dataclass
50class Residue:
51 res_name: str
52 res_seq: int
53 i_code: str
54 __atoms: list[Atom] = field(default_factory=list)
55
56 def add_atom(self, atom):
57 self.__atoms.append(atom)
58
59 def ca(self):
60 """Returns the alpha carbon of this residue or None when not present"""
61 return next((a for a in self.__atoms if a.name == " CA "), None)
62
63 def atoms(self):
64 return self.__atoms
65
66 def __str__(self):
67 return f"{self.res_name} {self.res_seq}{self.i_code}"
68
69@dataclass
70class Atom:
71 serial: int
72 name: str
73 x: float
74 y: float
75 z: float
76
77 def distance_to(self, a):
78 d2 = (self.x - a.x) * (self.x - a.x)
79 d2 += (self.y - a.y) * (self.y - a.y)
80 return math.sqrt(d2 + (self.z - a.z) * (self.z - a.z))
81
82def read_pdb_content(code, input_lines):
83
84 strctr = Structure(code)
85
86 for line in input_lines:
87 if not line.startswith("ATOM") and not line.startswith("HETAT"): continue
88 name = line[12:16]
89 serial = int(line[6:11].strip())
90 res_name = line[17:20]
91 res_seq = int(line[22:26].strip())
92 i_code = line[26]
93 chain_id = line[21]
94 x = float(line[30:38].strip())
95 y = float(line[38:46].strip())
96 z = float(line[46:54].strip())
97 chain = strctr.get_chain(chain_id)
98 if chain is None:
99 chain = Chain(chain_id)
100 strctr.add_chain(chain)
101 resid = chain.get_residue(res_seq, i_code)
102 if resid is None:
103 resid = Residue(res_name, res_seq, i_code)
104 chain.add_residue(resid)
105 atom = Atom(serial, name, x, y, z)
106 resid.add_atom(atom)
107
108 return strctr
109
110
111if __name__ == "__main__":
112 strctr = read_pdb_content("2GB1",open("2gb1.pdb"))
113 for c in strctr.chains():
114 print(c.code)
115
116 chain_A = strctr.get_chain("A")
117 for ri, rj in itertools.combinations(chain_A.residues(), 2):
118 print(ri, rj, ri.ca().distance_to(rj.ca()))
Przykładowy plik w formacie PDB znajduje się tutaj.
Poniżej zaś znajduje się szkic rozwiązania tego zadania:
(rozwiń szkic rozwiązania)
1from protein_structure import Structure, Chain
2
3
4class AtomsInChainIterator:
5 def __init__(self, chain: Chain):
6 self.__residues = list(chain.residues())
7 self.__current_residue = 0
8 self.__current_atom = 0
9
10 def __next__(self):
11 # sprawdź, czy możesz dać (__current_atom+1) atom z __current_residue
12 # TAK:
13 # __current_atom+=1
14 # zwróć __current_atom-1
15 # NIE:
16 # __current_residue += 1
17 # sprawdź, czy nie wyszliśmy poza łańcuch
18 # NIE:
19 # __current_residue += 1
20 # __current_atom = 1
21 # zwróć atom 0
22 # TAK:
23 # raise StopIteration
24 pass
25
26
27class AtomsInStructureIterator:
28 def __init__(self, strctr: Structure):
29 self.__chains = list(strctr.chains())
30 self.__current_chain = 0
31 self.__current_chain_it = AtomsInChainIterator(self.__chains[0])
32
33 def __next__(self):
34 try:
35 a = self.__current_chain_it.__next__()
36 return a
37 except:
38 # przesuń AtomsInChainIterator o jeden łańcuch
39 # zwróć atom
40 pass
41
42
43class Structure:
44
45 def atoms(self):
46 return AtomsInStructureIterator(self)