rev |
line source |
me@261
|
1 import sys |
me@261
|
2 import os |
me@262
|
3 import os.path |
me@261
|
4 from tempfile import NamedTemporaryFile |
me@262
|
5 import urllib2 |
me@261
|
6 |
me@261
|
7 import config |
me@284
|
8 import fasta |
me@261
|
9 from graph import Graph |
me@262
|
10 from Bio.PDB.DSSP import make_dssp_dict |
me@260
|
11 import data.codes |
me@260
|
12 |
me@260
|
13 class MonomerType(object): |
me@260
|
14 """Class of monomer types. |
me@260
|
15 |
me@260
|
16 Each MonomerType object represents a known monomer type, e.g. Valine, |
me@260
|
17 and is referenced to by each instance of monomer in a given sequence. |
me@260
|
18 |
me@260
|
19 - `name`: full name of monomer type |
me@260
|
20 - `code1`: one-letter code |
me@260
|
21 - `code3`: three-letter code |
me@260
|
22 - `is_modified`: either of True or False |
me@260
|
23 |
me@260
|
24 class atributes: |
me@260
|
25 |
me@260
|
26 - `by_code1`: a mapping from one-letter code to MonomerType object |
me@260
|
27 - `by_code3`: a mapping from three-letter code to MonomerType object |
me@260
|
28 - `by_name`: a mapping from monomer name to MonomerType object |
me@260
|
29 - `instance_type`: class of Monomer objects to use when creating new |
me@260
|
30 objects; this must be redefined in descendent classes |
me@260
|
31 |
me@260
|
32 All of the class attributes MUST be redefined when subclassing. |
me@260
|
33 """ |
me@260
|
34 |
me@260
|
35 by_code1 = {} |
me@260
|
36 by_code3 = {} |
me@260
|
37 by_name = {} |
me@260
|
38 instance_type = None |
me@260
|
39 |
me@260
|
40 def __init__(self, name="", code1="", code3="", is_modified=False): |
me@260
|
41 self.name = name.capitalize() |
me@260
|
42 self.code1 = code1.upper() |
me@260
|
43 self.code3 = code3.upper() |
me@260
|
44 self.is_modified = bool(is_modified) |
me@260
|
45 if not is_modified: |
me@260
|
46 self.by_code1[self.code1] = self |
me@260
|
47 self.by_code3[code3] = self |
me@260
|
48 self.by_name[name] = self |
me@260
|
49 # We duplicate distinguished long names into MonomerType itself, |
me@260
|
50 # so that we can use MonomerType.from_code3 to create the relevant |
me@260
|
51 # type of monomer. |
me@260
|
52 MonomerType.by_code3[code3] = self |
me@260
|
53 MonomerType.by_name[name] = self |
me@260
|
54 |
me@260
|
55 @classmethod |
me@260
|
56 def _initialize(cls, type_letter, codes=data.codes.codes): |
me@260
|
57 """Create all relevant instances of MonomerType. |
me@260
|
58 |
me@260
|
59 `type_letter` is either of: |
me@260
|
60 |
me@260
|
61 - 'p' for protein |
me@260
|
62 - 'd' for DNA |
me@260
|
63 - 'r' for RNA |
me@260
|
64 |
me@260
|
65 `codes` is a table of monomer codes |
me@260
|
66 """ |
me@260
|
67 for type, code1, is_modified, code3, name in codes: |
me@260
|
68 if type == type_letter: |
me@260
|
69 cls(name, code1, code3, is_modified) |
me@260
|
70 |
me@260
|
71 @classmethod |
me@260
|
72 def from_code1(cls, code1): |
me@260
|
73 """Return monomer type by one-letter code.""" |
me@260
|
74 return cls.by_code1[code1.upper()] |
me@260
|
75 |
me@260
|
76 @classmethod |
me@260
|
77 def from_code3(cls, code3): |
me@260
|
78 """Return monomer type by three-letter code.""" |
me@260
|
79 return cls.by_code3[code3.upper()] |
me@260
|
80 |
me@260
|
81 @classmethod |
me@260
|
82 def from_name(cls, name): |
me@260
|
83 """Return monomer type by name.""" |
me@260
|
84 return cls.by_name[name.capitalize()] |
me@260
|
85 |
me@260
|
86 def instance(self): |
me@260
|
87 """Create a new monomer of given type.""" |
me@260
|
88 return self.instance_type(self) |
me@260
|
89 |
me@260
|
90 def __eq__(self, other): |
me@260
|
91 if hasattr(other, "type"): |
me@260
|
92 return self is other.type |
me@260
|
93 return self is other |
me@260
|
94 |
me@260
|
95 class Monomer(object): |
me@260
|
96 """Monomer object. |
me@260
|
97 |
me@260
|
98 attributes: |
me@260
|
99 |
me@260
|
100 - `type`: type of monomer (a MonomerType object) |
me@260
|
101 |
me@282
|
102 class attributes: |
me@282
|
103 |
me@282
|
104 - `monomer_type`: either MonomerType or one of it's subclasses, it is used |
me@282
|
105 when creating new monomers. It SHOULD be redefined when subclassing |
me@282
|
106 Monomer. |
me@260
|
107 """ |
me@260
|
108 monomer_type = MonomerType |
me@260
|
109 |
me@260
|
110 def __init__(self, type): |
me@260
|
111 self.type = type |
me@260
|
112 |
me@260
|
113 @classmethod |
me@260
|
114 def from_code1(cls, code1): |
me@260
|
115 return cls(cls.monomer_type.by_code1[code1.upper()]) |
me@260
|
116 |
me@260
|
117 @classmethod |
me@260
|
118 def from_code3(cls, code3): |
me@260
|
119 return cls(cls.monomer_type.by_code3[code3.upper()]) |
me@260
|
120 |
me@260
|
121 @classmethod |
me@260
|
122 def from_name(cls, name): |
me@260
|
123 return cls(cls.monomer_type.by_name[name.capitalize()]) |
me@260
|
124 |
me@260
|
125 def __eq__(self, other): |
me@260
|
126 if hasattr(other, "type"): |
me@260
|
127 return self.type is other.type |
me@260
|
128 return self.type is other |
bnagaev@239
|
129 |
bnagaev@239
|
130 class Sequence(list): |
me@274
|
131 """Sequence of Monomers. |
bnagaev@243
|
132 |
me@274
|
133 This behaves like list of monomer objects. In addition to standard list |
me@274
|
134 behaviour, Sequence has the following attributes: |
me@270
|
135 |
me@274
|
136 * name -- str with the name of the sequence |
me@274
|
137 * description -- str with description of the sequence |
me@274
|
138 * source -- str denoting source of the sequence |
me@266
|
139 |
me@274
|
140 Any of them may be empty (i.e. hold empty string) |
me@275
|
141 |
me@275
|
142 Class attributes: |
me@282
|
143 |
me@275
|
144 * monomer_type -- type of monomers in sequence, must be redefined when |
me@275
|
145 subclassing |
me@274
|
146 """ |
me@270
|
147 |
me@275
|
148 monomer_type = Monomer |
me@270
|
149 |
me@275
|
150 name = '' |
me@275
|
151 description = '' |
me@275
|
152 source = '' |
me@275
|
153 |
me@275
|
154 def __init__(self, sequence=[], name=None, description=None, source=None): |
me@275
|
155 super(Sequence, self).__init__(sequence) |
me@275
|
156 if hasattr(sequence, 'name'): |
me@275
|
157 vars(self).update(vars(sequence)) |
me@275
|
158 if name: |
me@275
|
159 self.name = name |
me@275
|
160 if description: |
me@275
|
161 self.description = description |
me@275
|
162 if source: |
me@275
|
163 self.source = source |
me@270
|
164 |
me@262
|
165 def __str__(self): |
me@275
|
166 """Returns sequence in one-letter code.""" |
me@275
|
167 return ''.join(monomer.code1 for monomer in self) |
me@270
|
168 |
me@273
|
169 @classmethod |
me@273
|
170 def from_string(cls, string, name='', description=''): |
me@273
|
171 """Create sequences from string of one-letter codes.""" |
me@273
|
172 monomer = cls.monomer_type.from_code1 |
me@273
|
173 monomers = [monomer(letter) for letter in string] |
me@273
|
174 return cls(monomers, name, description) |
me@262
|
175 |
me@284
|
176 @classmethod |
me@284
|
177 def from_fasta(cls, file): |
me@284
|
178 """Read sequence from FASTA file. |
me@286
|
179 |
me@284
|
180 File must contain exactly one sequence. |
me@284
|
181 """ |
me@284
|
182 sequences = fasta.parse_file(file) |
me@284
|
183 assert len(sequences) == 1 |
me@287
|
184 name, description = sequences.keys()[0] |
me@284
|
185 return cls(sequences[header], name, description, file.name) |
me@284
|
186 |
me@287
|
187 class Alignment(list): |
me@289
|
188 """Alignment. Behaves like a list of Columns.""" |
bnagaev@249
|
189 |
me@287
|
190 sequence_type = Sequence |
me@289
|
191 """Type of sequences in alignment. SHOULD be redefined when subclassing.""" |
me@288
|
192 |
me@289
|
193 sequences = None |
me@289
|
194 """Ordered list of sequences in alignment. Read, but DO NOT FIDDLE!""" |
bnagaev@249
|
195 |
me@287
|
196 def __init__(self): |
me@287
|
197 """Initialize empty alignment.""" |
me@287
|
198 super(Alignment, self).__init__() |
me@287
|
199 self.sequences = [] |
me@282
|
200 |
me@287
|
201 def add_gapped_line(self, line, name='', description='', source=''): |
me@287
|
202 """Add row from a line of one-letter codes and gaps.""" |
me@287
|
203 Sequence = cls.sequence_type |
me@287
|
204 not_gap = lambda (i, char): char != "-" |
me@287
|
205 no_gaps = line.replace("-", "") |
me@287
|
206 sequence = Sequence(no_gaps, name, description, source) |
me@287
|
207 for i, (j, char) in enumerate(filter(not_gap, enumerate(line))): |
me@287
|
208 self[j][seq] = sequence[i] |
me@287
|
209 self.sequences.append(sequence) |
me@287
|
210 |
me@287
|
211 @classmethod |
me@287
|
212 def from_fasta(cls, file): |
me@287
|
213 """Create new alignment from FASTA file.""" |
me@287
|
214 self = cls() |
me@287
|
215 for ((name, description), body) in fasta.parse_file(file): |
me@287
|
216 self.add_gapped_line(body, name, description) |
me@287
|
217 return self |
bnagaev@249
|
218 |
me@292
|
219 def to_fasta(self, file): |
me@292
|
220 """Write alignment in FASTA file as sequences with gaps.""" |
me@292
|
221 def char(monomer): |
me@292
|
222 if monomer: |
me@292
|
223 return monomer.code1 |
me@292
|
224 return "-" |
me@292
|
225 for row in self.rows_as_lists(): |
me@292
|
226 seq = row.sequence |
me@292
|
227 line = "".join(map(char, row)) |
me@292
|
228 fasta.save_file(file, line, seq.name, seq.description) |
me@292
|
229 |
me@292
|
230 ## Unclean code follows |
me@292
|
231 |
bnagaev@249
|
232 def length(self): |
me@287
|
233 """Return width, ie length of each sequence with gaps.""" |
bnagaev@249
|
234 return max([len(line) for line in self.body.values()]) |
bnagaev@249
|
235 |
bnagaev@249
|
236 def height(self): |
bnagaev@249
|
237 """ The number of sequences in alignment (it's thickness). """ |
bnagaev@249
|
238 return len(self.body) |
bnagaev@249
|
239 |
bnagaev@249
|
240 def identity(self): |
bnagaev@249
|
241 """ Calculate the identity of alignment positions for colouring. |
bnagaev@249
|
242 |
bnagaev@249
|
243 For every (row, column) in alignment the percentage of the exactly |
bnagaev@249
|
244 same residue in the same column in the alignment is calculated. |
me@270
|
245 The data structure is just like the Alignment.body, but istead of |
bnagaev@249
|
246 monomers it contains float percentages. |
bnagaev@249
|
247 """ |
bnagaev@249
|
248 # Oh, God, that's awful! Absolutely not understandable. |
bnagaev@249
|
249 # First, calculate percentages of amino acids in every column |
bnagaev@249
|
250 contribution = 1.0 / len(self.sequences) |
bnagaev@249
|
251 all_columns = [] |
bnagaev@249
|
252 for position in range(len(self)): |
bnagaev@249
|
253 column_percentage = {} |
bnagaev@249
|
254 for seq in self.body: |
bnagaev@249
|
255 if self.body[seq][position] is not None: |
bnagaev@249
|
256 aa = self.body[seq][position].code |
bnagaev@249
|
257 else: |
bnagaev@249
|
258 aa = None |
bnagaev@249
|
259 if aa in allpy.data.amino_acids: |
bnagaev@249
|
260 if aa in column_percentage.keys(): |
bnagaev@249
|
261 column_percentage[aa] += contribution |
bnagaev@249
|
262 else: |
bnagaev@249
|
263 column_percentage[aa] = contribution |
bnagaev@249
|
264 all_columns.append(column_percentage) |
bnagaev@249
|
265 # Second, map these percentages onto the alignment |
bnagaev@249
|
266 self.identity_percentages = {} |
bnagaev@249
|
267 for seq in self.sequences: |
bnagaev@249
|
268 self.identity_percentages[seq] = [] |
bnagaev@249
|
269 for seq in self.identity_percentages: |
bnagaev@249
|
270 line = self.identity_percentages[seq] |
bnagaev@249
|
271 for position in range(len(self)): |
bnagaev@249
|
272 if self.body[seq][position] is not None: |
bnagaev@249
|
273 aa = self.body[seq][position].code |
bnagaev@249
|
274 else: |
bnagaev@249
|
275 aa = None |
bnagaev@249
|
276 line.append(all_columns[position].get(aa)) |
bnagaev@249
|
277 return self.identity_percentages |
bnagaev@249
|
278 |
bnagaev@249
|
279 @staticmethod |
bnagaev@249
|
280 def from_sequences(*sequences): |
bnagaev@249
|
281 """ Constructs new alignment from sequences |
me@270
|
282 |
me@270
|
283 Add None's to right end to make equal lengthes of alignment sequences |
bnagaev@249
|
284 """ |
bnagaev@249
|
285 alignment = Alignment() |
bnagaev@249
|
286 alignment.sequences = sequences |
bnagaev@249
|
287 max_length = max(len(sequence) for sequence in sequences) |
bnagaev@249
|
288 for sequence in sequences: |
bnagaev@249
|
289 gaps_count = max_length - len(sequence) |
bnagaev@249
|
290 alignment.body[sequence] = sequence.monomers + [None] * gaps_count |
bnagaev@249
|
291 return alignment |
me@270
|
292 |
bnagaev@249
|
293 def muscle_align(self): |
bnagaev@249
|
294 """ Simple align ths alignment using sequences (muscle) |
me@270
|
295 |
bnagaev@249
|
296 uses old Monomers and Sequences objects |
bnagaev@249
|
297 """ |
bnagaev@249
|
298 tmp_file = NamedTemporaryFile(delete=False) |
bnagaev@249
|
299 self.save_fasta(tmp_file) |
bnagaev@249
|
300 tmp_file.close() |
bnagaev@249
|
301 os.system("muscle -in %(tmp)s -out %(tmp)s" % {'tmp': tmp_file.name}) |
bnagaev@249
|
302 sequences, body = Alignment.from_fasta(open(tmp_file.name)) |
bnagaev@249
|
303 for sequence in self.sequences: |
bnagaev@249
|
304 try: |
bnagaev@249
|
305 new_sequence = [i for i in sequences if sequence==i][0] |
bnagaev@249
|
306 except: |
bnagaev@249
|
307 raise Exception("Align: Cann't find sequence %s in muscle output" % \ |
bnagaev@249
|
308 sequence.name) |
bnagaev@249
|
309 old_monomers = iter(sequence.monomers) |
bnagaev@249
|
310 self.body[sequence] = [] |
bnagaev@249
|
311 for monomer in body[new_sequence]: |
bnagaev@249
|
312 if not monomer: |
bnagaev@249
|
313 self.body[sequence].append(monomer) |
bnagaev@249
|
314 else: |
bnagaev@249
|
315 old_monomer = old_monomers.next() |
bnagaev@249
|
316 if monomer != old_monomer: |
bnagaev@249
|
317 raise Exception("Align: alignment errors") |
bnagaev@249
|
318 self.body[sequence].append(old_monomer) |
bnagaev@249
|
319 os.unlink(tmp_file.name) |
me@270
|
320 |
bnagaev@249
|
321 def column(self, sequence=None, sequences=None, original=None): |
bnagaev@249
|
322 """ returns list of columns of alignment |
me@270
|
323 |
bnagaev@249
|
324 sequence or sequences: |
me@282
|
325 * if sequence is given, then column is (original_monomer, monomer) |
me@282
|
326 * if sequences is given, then column is (original_monomer, {sequence: monomer}) |
me@282
|
327 * if both of them are given, it is an error |
me@282
|
328 |
bnagaev@249
|
329 original (Sequence type): |
me@282
|
330 * if given, this filters only columns represented by original sequence |
bnagaev@249
|
331 """ |
bnagaev@249
|
332 if sequence and sequences: |
bnagaev@249
|
333 raise Exception("Wrong usage. read help") |
bnagaev@249
|
334 indexes = dict([(v, k) for( k, v) in enumerate(self.sequences)]) |
bnagaev@249
|
335 alignment = self.body.items() |
bnagaev@249
|
336 alignment.sort(key=lambda i: indexes[i[0]]) |
bnagaev@249
|
337 alignment = [monomers for seq, monomers in alignment] |
bnagaev@249
|
338 for column in zip(*alignment): |
bnagaev@249
|
339 if not original or column[indexes[original]]: |
bnagaev@249
|
340 if sequence: |
bnagaev@249
|
341 yield (column[indexes[original]], column[indexes[sequence]]) |
bnagaev@249
|
342 else: |
me@270
|
343 yield (column[indexes[original]], |
bnagaev@249
|
344 dict([(s, column[indexes[s]]) for s in sequences])) |
me@270
|
345 |
bnagaev@249
|
346 def secstr(self, sequence, pdb_chain, gap='-'): |
bnagaev@249
|
347 """ Returns string representing secondary structure """ |
bnagaev@249
|
348 return ''.join([ |
me@270
|
349 (sequence.pdb_secstr[pdb_chain][m] if sequence.secstr_has(pdb_chain, m) else gap) |
bnagaev@249
|
350 for m in self.body[sequence]]) |
bnagaev@249
|
351 |
bnagaev@249
|
352 class Block(object): |
me@261
|
353 """ Block of alignment |
me@270
|
354 |
me@261
|
355 Mandatory data: |
me@266
|
356 |
me@261
|
357 * self.alignment -- alignment object, which the block belongs to |
me@261
|
358 * self.sequences - set of sequence objects that contain monomers |
me@261
|
359 and/or gaps, that constitute the block |
me@261
|
360 * self.positions -- list of positions of the alignment.body that |
me@261
|
361 are included in the block; position[i+1] is always to the right from position[i] |
me@270
|
362 |
me@261
|
363 Don't change self.sequences -- it may be a link to other block.sequences |
me@270
|
364 |
me@261
|
365 How to create a new block: |
me@282
|
366 |
me@261
|
367 >>> import alignment |
me@261
|
368 >>> import block |
me@261
|
369 >>> proj = alignment.Alignment(open("test.fasta")) |
me@261
|
370 >>> block1 = block.Block(proj) |
me@261
|
371 """ |
me@270
|
372 |
me@261
|
373 def __init__(self, alignment, sequences=None, positions=None): |
me@261
|
374 """ Builds new block from alignment |
me@270
|
375 |
me@261
|
376 if sequences==None, all sequences are used |
me@261
|
377 if positions==None, all positions are used |
me@261
|
378 """ |
me@261
|
379 if sequences == None: |
me@261
|
380 sequences = set(alignment.sequences) # copy |
me@261
|
381 if positions == None: |
me@261
|
382 positions = range(len(alignment)) |
me@261
|
383 self.alignment = alignment |
me@261
|
384 self.sequences = sequences |
me@261
|
385 self.positions = positions |
me@270
|
386 |
me@261
|
387 def save_fasta(self, out_file, long_line=70, gap='-'): |
me@270
|
388 """ Saves alignment to given file in fasta-format |
me@270
|
389 |
me@261
|
390 No changes in the names, descriptions or order of the sequences |
me@261
|
391 are made. |
me@261
|
392 """ |
me@261
|
393 for sequence in self.sequences: |
me@261
|
394 alignment_monomers = self.alignment.body[sequence] |
me@261
|
395 block_monomers = [alignment_monomers[i] for i in self.positions] |
me@261
|
396 string = ''.join([m.type.code1 if m else '-' for m in block_monomers]) |
me@261
|
397 save_fasta(out_file, string, sequence.name, sequence.description, long_line) |
me@270
|
398 |
me@270
|
399 def geometrical_cores(self, max_delta=config.delta, |
me@270
|
400 timeout=config.timeout, minsize=config.minsize, |
me@261
|
401 ac_new_atoms=config.ac_new_atoms, |
me@261
|
402 ac_count=config.ac_count): |
me@261
|
403 """ Returns length-sorted list of blocks, representing GCs |
me@270
|
404 |
me@282
|
405 * max_delta -- threshold of distance spreading |
me@282
|
406 * timeout -- Bron-Kerbosh timeout (then fast O(n ln n) algorithm) |
me@282
|
407 * minsize -- min size of each core |
me@282
|
408 * ac_new_atoms -- min part or new atoms in new alternative core |
me@282
|
409 current GC is compared with each of already selected GCs if |
me@282
|
410 difference is less then ac_new_atoms, current GC is skipped |
me@261
|
411 difference = part of new atoms in current core |
me@282
|
412 * ac_count -- max number of cores (including main core) |
me@261
|
413 -1 means infinity |
me@282
|
414 |
me@261
|
415 If more than one pdb chain for some sequence provided, consider all of them |
me@270
|
416 cost is calculated as 1 / (delta + 1) |
me@282
|
417 |
me@261
|
418 delta in [0, +inf) => cost in (0, 1] |
me@261
|
419 """ |
me@261
|
420 nodes = self.positions |
me@261
|
421 lines = {} |
me@261
|
422 for i in self.positions: |
me@261
|
423 for j in self.positions: |
me@261
|
424 if i < j: |
me@261
|
425 distances = [] |
me@261
|
426 for sequence in self.sequences: |
me@261
|
427 for chain in sequence.pdb_chains: |
me@261
|
428 m1 = self.alignment.body[sequence][i] |
me@261
|
429 m2 = self.alignment.body[sequence][j] |
me@261
|
430 if m1 and m2: |
me@261
|
431 r1 = sequence.pdb_residues[chain][m1] |
me@261
|
432 r2 = sequence.pdb_residues[chain][m2] |
me@261
|
433 ca1 = r1['CA'] |
me@261
|
434 ca2 = r2['CA'] |
me@261
|
435 d = ca1 - ca2 # Bio.PDB feature |
me@261
|
436 distances.append(d) |
me@261
|
437 if len(distances) >= 2: |
me@261
|
438 delta = max(distances) - min(distances) |
me@261
|
439 if delta <= max_delta: |
me@261
|
440 lines[Graph.line(i, j)] = 1.0 / (1.0 + max_delta) |
me@261
|
441 graph = Graph(nodes, lines) |
me@261
|
442 cliques = graph.cliques(timeout=timeout, minsize=minsize) |
me@261
|
443 GCs = [] |
me@261
|
444 for clique in cliques: |
me@261
|
445 for GC in GCs: |
me@261
|
446 if len(clique - set(GC.positions)) < ac_new_atoms * len(clique): |
me@261
|
447 break |
me@261
|
448 else: |
me@261
|
449 GCs.append(Block(self.alignment, self.sequences, clique)) |
me@261
|
450 if ac_count != -1 and len(GCs) >= ac_count: |
me@261
|
451 break |
me@261
|
452 return GCs |
me@270
|
453 |
me@261
|
454 def xstring(self, x='X', gap='-'): |
me@261
|
455 """ Returns string consisting of gap chars and chars x at self.positions |
me@270
|
456 |
me@261
|
457 Length of returning string = length of alignment |
me@261
|
458 """ |
me@261
|
459 monomers = [False] * len(self.alignment) |
me@261
|
460 for i in self.positions: |
me@261
|
461 monomers[i] = True |
me@261
|
462 return ''.join([x if m else gap for m in monomers]) |
me@270
|
463 |
me@261
|
464 def save_xstring(self, out_file, name, description='', x='X', gap='-', long_line=70): |
me@261
|
465 """ Save xstring and name in fasta format """ |
me@261
|
466 save_fasta(out_file, self.xstring(x=x, gap=gap), name, description, long_line) |
me@270
|
467 |
me@261
|
468 def monomers(self, sequence): |
me@261
|
469 """ Iterates monomers of this sequence from this block """ |
me@261
|
470 alignment_sequence = self.alignment.body[sequence] |
me@261
|
471 return (alignment_sequence[i] for i in self.positions) |
me@270
|
472 |
me@261
|
473 def ca_atoms(self, sequence, pdb_chain): |
me@261
|
474 """ Iterates Ca-atom of monomers of this sequence from this block """ |
me@261
|
475 return (sequence.pdb_residues[pdb_chain][monomer] for monomer in self.monomers()) |
me@270
|
476 |
me@261
|
477 def sequences_chains(self): |
me@261
|
478 """ Iterates pairs (sequence, chain) """ |
me@261
|
479 for sequence in self.alignment.sequences: |
me@261
|
480 if sequence in self.sequences: |
me@261
|
481 for chain in sequence.pdb_chains: |
me@261
|
482 yield (sequence, chain) |
me@270
|
483 |
me@261
|
484 def superimpose(self): |
me@261
|
485 """ Superimpose all pdb_chains in this block """ |
me@261
|
486 sequences_chains = list(self.sequences_chains()) |
me@261
|
487 if len(sequences_chains) >= 1: |
me@261
|
488 sup = Superimposer() |
me@261
|
489 fixed_sequence, fixed_chain = sequences_chains.pop() |
me@261
|
490 fixed_atoms = self.ca_atoms(fixed_sequence, fixed_chain) |
me@261
|
491 for sequence, chain in sequences_chains: |
me@261
|
492 moving_atoms = self.ca_atoms(sequence, chain) |
me@261
|
493 sup.set_atoms(fixed_atoms, moving_atoms) |
me@261
|
494 # Apply rotation/translation to the moving atoms |
me@261
|
495 sup.apply(moving_atoms) |
me@270
|
496 |
me@261
|
497 def pdb_save(self, out_file): |
me@270
|
498 """ Save all sequences |
me@270
|
499 |
me@261
|
500 Returns {(sequence, chain): CHAIN} |
me@261
|
501 CHAIN is chain letter in new file |
me@261
|
502 """ |
me@261
|
503 tmp_file = NamedTemporaryFile(delete=False) |
me@261
|
504 tmp_file.close() |
me@270
|
505 |
me@261
|
506 for sequence, chain in self.sequences_chains(): |
me@261
|
507 sequence.pdb_save(tmp_file.name, chain) |
me@261
|
508 # TODO: read from tmp_file.name |
me@261
|
509 # change CHAIN |
me@261
|
510 # add to out_file |
me@270
|
511 |
me@261
|
512 os.unlink(NamedTemporaryFile) |
bnagaev@239
|
513 |
me@260
|
514 # vim: set ts=4 sts=4 sw=4 et: |