Документ взят из кэша поисковой машины. Адрес оригинального документа : http://kodomo.fbb.msu.ru/hg/allpy/annotate/c0a4223dd4c4/allpy/base.py
Дата изменения: Unknown
Дата индексирования: Sun Mar 2 07:05:35 2014
Кодировка:
allpy: allpy/base.py annotate

allpy

annotate allpy/base.py @ 296:c0a4223dd4c4

Moved allpy.base.Alignment.secstr_has to allpy.pdb
author Daniil Alexeyevsky <me.dendik@gmail.com>
date Thu, 16 Dec 2010 18:30:43 +0300
parents 8b4195912e56
children be9c3cacd009
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@295 187 class Alignment(object):
me@295 188 """Alignment. It is 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@295 200 self.columns = []
me@282 201
me@294 202 def append_sequence(self, sequence):
me@294 203 """Add sequence to alignment.
me@294 204
me@294 205 If sequence is too short, pad it with gaps on the right.
me@294 206 """
me@294 207 self.sequences.append(sequence)
me@294 208 for i, monomer in enumerate(sequence):
me@295 209 self.columns[i][sequence] = monomer
me@294 210
me@294 211 def append_gapped_line(self, line, name='', description='', source=''):
me@287 212 """Add row from a line of one-letter codes and gaps."""
me@287 213 Sequence = cls.sequence_type
me@287 214 not_gap = lambda (i, char): char != "-"
me@287 215 no_gaps = line.replace("-", "")
me@287 216 sequence = Sequence(no_gaps, name, description, source)
me@287 217 for i, (j, char) in enumerate(filter(not_gap, enumerate(line))):
me@295 218 self.columns[j][seq] = sequence[i]
me@287 219 self.sequences.append(sequence)
me@287 220
me@287 221 @classmethod
me@287 222 def from_fasta(cls, file):
me@287 223 """Create new alignment from FASTA file."""
me@287 224 self = cls()
me@287 225 for ((name, description), body) in fasta.parse_file(file):
me@294 226 self.append_gapped_line(body, name, description)
me@287 227 return self
bnagaev@249 228
me@292 229 def to_fasta(self, file):
me@292 230 """Write alignment in FASTA file as sequences with gaps."""
me@292 231 def char(monomer):
me@292 232 if monomer:
me@292 233 return monomer.code1
me@292 234 return "-"
me@292 235 for row in self.rows_as_lists():
me@292 236 seq = row.sequence
me@292 237 line = "".join(map(char, row))
me@292 238 fasta.save_file(file, line, seq.name, seq.description)
me@292 239
me@292 240 ## Unclean code follows
me@292 241
bnagaev@249 242 def length(self):
me@287 243 """Return width, ie length of each sequence with gaps."""
bnagaev@249 244 return max([len(line) for line in self.body.values()])
bnagaev@249 245
bnagaev@249 246 def height(self):
bnagaev@249 247 """ The number of sequences in alignment (it's thickness). """
bnagaev@249 248 return len(self.body)
bnagaev@249 249
bnagaev@249 250 def identity(self):
bnagaev@249 251 """ Calculate the identity of alignment positions for colouring.
bnagaev@249 252
bnagaev@249 253 For every (row, column) in alignment the percentage of the exactly
bnagaev@249 254 same residue in the same column in the alignment is calculated.
me@270 255 The data structure is just like the Alignment.body, but istead of
bnagaev@249 256 monomers it contains float percentages.
bnagaev@249 257 """
bnagaev@249 258 # Oh, God, that's awful! Absolutely not understandable.
bnagaev@249 259 # First, calculate percentages of amino acids in every column
bnagaev@249 260 contribution = 1.0 / len(self.sequences)
bnagaev@249 261 all_columns = []
bnagaev@249 262 for position in range(len(self)):
bnagaev@249 263 column_percentage = {}
bnagaev@249 264 for seq in self.body:
bnagaev@249 265 if self.body[seq][position] is not None:
bnagaev@249 266 aa = self.body[seq][position].code
bnagaev@249 267 else:
bnagaev@249 268 aa = None
bnagaev@249 269 if aa in allpy.data.amino_acids:
bnagaev@249 270 if aa in column_percentage.keys():
bnagaev@249 271 column_percentage[aa] += contribution
bnagaev@249 272 else:
bnagaev@249 273 column_percentage[aa] = contribution
bnagaev@249 274 all_columns.append(column_percentage)
bnagaev@249 275 # Second, map these percentages onto the alignment
bnagaev@249 276 self.identity_percentages = {}
bnagaev@249 277 for seq in self.sequences:
bnagaev@249 278 self.identity_percentages[seq] = []
bnagaev@249 279 for seq in self.identity_percentages:
bnagaev@249 280 line = self.identity_percentages[seq]
bnagaev@249 281 for position in range(len(self)):
bnagaev@249 282 if self.body[seq][position] is not None:
bnagaev@249 283 aa = self.body[seq][position].code
bnagaev@249 284 else:
bnagaev@249 285 aa = None
bnagaev@249 286 line.append(all_columns[position].get(aa))
bnagaev@249 287 return self.identity_percentages
bnagaev@249 288
bnagaev@249 289 def muscle_align(self):
bnagaev@249 290 """ Simple align ths alignment using sequences (muscle)
me@270 291
bnagaev@249 292 uses old Monomers and Sequences objects
bnagaev@249 293 """
bnagaev@249 294 tmp_file = NamedTemporaryFile(delete=False)
bnagaev@249 295 self.save_fasta(tmp_file)
bnagaev@249 296 tmp_file.close()
bnagaev@249 297 os.system("muscle -in %(tmp)s -out %(tmp)s" % {'tmp': tmp_file.name})
bnagaev@249 298 sequences, body = Alignment.from_fasta(open(tmp_file.name))
bnagaev@249 299 for sequence in self.sequences:
bnagaev@249 300 try:
bnagaev@249 301 new_sequence = [i for i in sequences if sequence==i][0]
bnagaev@249 302 except:
bnagaev@249 303 raise Exception("Align: Cann't find sequence %s in muscle output" % \
bnagaev@249 304 sequence.name)
bnagaev@249 305 old_monomers = iter(sequence.monomers)
bnagaev@249 306 self.body[sequence] = []
bnagaev@249 307 for monomer in body[new_sequence]:
bnagaev@249 308 if not monomer:
bnagaev@249 309 self.body[sequence].append(monomer)
bnagaev@249 310 else:
bnagaev@249 311 old_monomer = old_monomers.next()
bnagaev@249 312 if monomer != old_monomer:
bnagaev@249 313 raise Exception("Align: alignment errors")
bnagaev@249 314 self.body[sequence].append(old_monomer)
bnagaev@249 315 os.unlink(tmp_file.name)
me@270 316
bnagaev@249 317 def column(self, sequence=None, sequences=None, original=None):
bnagaev@249 318 """ returns list of columns of alignment
me@270 319
bnagaev@249 320 sequence or sequences:
me@282 321 * if sequence is given, then column is (original_monomer, monomer)
me@282 322 * if sequences is given, then column is (original_monomer, {sequence: monomer})
me@282 323 * if both of them are given, it is an error
me@282 324
bnagaev@249 325 original (Sequence type):
me@282 326 * if given, this filters only columns represented by original sequence
bnagaev@249 327 """
bnagaev@249 328 if sequence and sequences:
bnagaev@249 329 raise Exception("Wrong usage. read help")
bnagaev@249 330 indexes = dict([(v, k) for( k, v) in enumerate(self.sequences)])
bnagaev@249 331 alignment = self.body.items()
bnagaev@249 332 alignment.sort(key=lambda i: indexes[i[0]])
bnagaev@249 333 alignment = [monomers for seq, monomers in alignment]
bnagaev@249 334 for column in zip(*alignment):
bnagaev@249 335 if not original or column[indexes[original]]:
bnagaev@249 336 if sequence:
bnagaev@249 337 yield (column[indexes[original]], column[indexes[sequence]])
bnagaev@249 338 else:
me@270 339 yield (column[indexes[original]],
bnagaev@249 340 dict([(s, column[indexes[s]]) for s in sequences]))
me@270 341
bnagaev@249 342 class Block(object):
me@261 343 """ Block of alignment
me@270 344
me@261 345 Mandatory data:
me@266 346
me@261 347 * self.alignment -- alignment object, which the block belongs to
me@261 348 * self.sequences - set of sequence objects that contain monomers
me@261 349 and/or gaps, that constitute the block
me@261 350 * self.positions -- list of positions of the alignment.body that
me@261 351 are included in the block; position[i+1] is always to the right from position[i]
me@270 352
me@261 353 Don't change self.sequences -- it may be a link to other block.sequences
me@270 354
me@261 355 How to create a new block:
me@282 356
me@261 357 >>> import alignment
me@261 358 >>> import block
me@261 359 >>> proj = alignment.Alignment(open("test.fasta"))
me@261 360 >>> block1 = block.Block(proj)
me@261 361 """
me@270 362
me@261 363 def __init__(self, alignment, sequences=None, positions=None):
me@261 364 """ Builds new block from alignment
me@270 365
me@261 366 if sequences==None, all sequences are used
me@261 367 if positions==None, all positions are used
me@261 368 """
me@261 369 if sequences == None:
me@261 370 sequences = set(alignment.sequences) # copy
me@261 371 if positions == None:
me@261 372 positions = range(len(alignment))
me@261 373 self.alignment = alignment
me@261 374 self.sequences = sequences
me@261 375 self.positions = positions
me@270 376
me@261 377 def save_fasta(self, out_file, long_line=70, gap='-'):
me@270 378 """ Saves alignment to given file in fasta-format
me@270 379
me@261 380 No changes in the names, descriptions or order of the sequences
me@261 381 are made.
me@261 382 """
me@261 383 for sequence in self.sequences:
me@261 384 alignment_monomers = self.alignment.body[sequence]
me@261 385 block_monomers = [alignment_monomers[i] for i in self.positions]
me@261 386 string = ''.join([m.type.code1 if m else '-' for m in block_monomers])
me@261 387 save_fasta(out_file, string, sequence.name, sequence.description, long_line)
me@270 388
me@270 389 def geometrical_cores(self, max_delta=config.delta,
me@270 390 timeout=config.timeout, minsize=config.minsize,
me@261 391 ac_new_atoms=config.ac_new_atoms,
me@261 392 ac_count=config.ac_count):
me@261 393 """ Returns length-sorted list of blocks, representing GCs
me@270 394
me@282 395 * max_delta -- threshold of distance spreading
me@282 396 * timeout -- Bron-Kerbosh timeout (then fast O(n ln n) algorithm)
me@282 397 * minsize -- min size of each core
me@282 398 * ac_new_atoms -- min part or new atoms in new alternative core
me@282 399 current GC is compared with each of already selected GCs if
me@282 400 difference is less then ac_new_atoms, current GC is skipped
me@261 401 difference = part of new atoms in current core
me@282 402 * ac_count -- max number of cores (including main core)
me@261 403 -1 means infinity
me@282 404
me@261 405 If more than one pdb chain for some sequence provided, consider all of them
me@270 406 cost is calculated as 1 / (delta + 1)
me@282 407
me@261 408 delta in [0, +inf) => cost in (0, 1]
me@261 409 """
me@261 410 nodes = self.positions
me@261 411 lines = {}
me@261 412 for i in self.positions:
me@261 413 for j in self.positions:
me@261 414 if i < j:
me@261 415 distances = []
me@261 416 for sequence in self.sequences:
me@261 417 for chain in sequence.pdb_chains:
me@261 418 m1 = self.alignment.body[sequence][i]
me@261 419 m2 = self.alignment.body[sequence][j]
me@261 420 if m1 and m2:
me@261 421 r1 = sequence.pdb_residues[chain][m1]
me@261 422 r2 = sequence.pdb_residues[chain][m2]
me@261 423 ca1 = r1['CA']
me@261 424 ca2 = r2['CA']
me@261 425 d = ca1 - ca2 # Bio.PDB feature
me@261 426 distances.append(d)
me@261 427 if len(distances) >= 2:
me@261 428 delta = max(distances) - min(distances)
me@261 429 if delta <= max_delta:
me@261 430 lines[Graph.line(i, j)] = 1.0 / (1.0 + max_delta)
me@261 431 graph = Graph(nodes, lines)
me@261 432 cliques = graph.cliques(timeout=timeout, minsize=minsize)
me@261 433 GCs = []
me@261 434 for clique in cliques:
me@261 435 for GC in GCs:
me@261 436 if len(clique - set(GC.positions)) < ac_new_atoms * len(clique):
me@261 437 break
me@261 438 else:
me@261 439 GCs.append(Block(self.alignment, self.sequences, clique))
me@261 440 if ac_count != -1 and len(GCs) >= ac_count:
me@261 441 break
me@261 442 return GCs
me@270 443
me@261 444 def xstring(self, x='X', gap='-'):
me@261 445 """ Returns string consisting of gap chars and chars x at self.positions
me@270 446
me@261 447 Length of returning string = length of alignment
me@261 448 """
me@261 449 monomers = [False] * len(self.alignment)
me@261 450 for i in self.positions:
me@261 451 monomers[i] = True
me@261 452 return ''.join([x if m else gap for m in monomers])
me@270 453
me@261 454 def save_xstring(self, out_file, name, description='', x='X', gap='-', long_line=70):
me@261 455 """ Save xstring and name in fasta format """
me@261 456 save_fasta(out_file, self.xstring(x=x, gap=gap), name, description, long_line)
me@270 457
me@261 458 def monomers(self, sequence):
me@261 459 """ Iterates monomers of this sequence from this block """
me@261 460 alignment_sequence = self.alignment.body[sequence]
me@261 461 return (alignment_sequence[i] for i in self.positions)
me@270 462
me@261 463 def ca_atoms(self, sequence, pdb_chain):
me@261 464 """ Iterates Ca-atom of monomers of this sequence from this block """
me@261 465 return (sequence.pdb_residues[pdb_chain][monomer] for monomer in self.monomers())
me@270 466
me@261 467 def sequences_chains(self):
me@261 468 """ Iterates pairs (sequence, chain) """
me@261 469 for sequence in self.alignment.sequences:
me@261 470 if sequence in self.sequences:
me@261 471 for chain in sequence.pdb_chains:
me@261 472 yield (sequence, chain)
me@270 473
me@261 474 def superimpose(self):
me@261 475 """ Superimpose all pdb_chains in this block """
me@261 476 sequences_chains = list(self.sequences_chains())
me@261 477 if len(sequences_chains) >= 1:
me@261 478 sup = Superimposer()
me@261 479 fixed_sequence, fixed_chain = sequences_chains.pop()
me@261 480 fixed_atoms = self.ca_atoms(fixed_sequence, fixed_chain)
me@261 481 for sequence, chain in sequences_chains:
me@261 482 moving_atoms = self.ca_atoms(sequence, chain)
me@261 483 sup.set_atoms(fixed_atoms, moving_atoms)
me@261 484 # Apply rotation/translation to the moving atoms
me@261 485 sup.apply(moving_atoms)
me@270 486
me@261 487 def pdb_save(self, out_file):
me@270 488 """ Save all sequences
me@270 489
me@261 490 Returns {(sequence, chain): CHAIN}
me@261 491 CHAIN is chain letter in new file
me@261 492 """
me@261 493 tmp_file = NamedTemporaryFile(delete=False)
me@261 494 tmp_file.close()
me@270 495
me@261 496 for sequence, chain in self.sequences_chains():
me@261 497 sequence.pdb_save(tmp_file.name, chain)
me@261 498 # TODO: read from tmp_file.name
me@261 499 # change CHAIN
me@261 500 # add to out_file
me@270 501
me@261 502 os.unlink(NamedTemporaryFile)
bnagaev@239 503
me@260 504 # vim: set ts=4 sts=4 sw=4 et: