1#
2# * This library is free software; you can redistribute it and/or
3# * modify it under the terms of the GNU Lesser General Public
4# * License as published by the Free Software Foundation; either
5# * version 2.1 of the License, or (at your option) any later version.
6# *
7# * This library is distributed in the hope that it will be useful,
8# * but WITHOUT ANY WARRANTY; without even the implied warranty of
9# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10# * Lesser General Public License for more details.
11#
12
13#propka3.0, revision 182                                                                      2011-08-09
14#-------------------------------------------------------------------------------------------------------
15#--                                                                                                   --
16#--                                   PROPKA: A PROTEIN PKA PREDICTOR                                 --
17#--                                                                                                   --
18#--                              VERSION 3.0,  01/01/2011, COPENHAGEN                                 --
19#--                              BY MATS H.M. OLSSON AND CHRESTEN R. SONDERGARD                       --
20#--                                                                                                   --
21#-------------------------------------------------------------------------------------------------------
22#
23#
24#-------------------------------------------------------------------------------------------------------
25# References:
26#
27#   Very Fast Empirical Prediction and Rationalization of Protein pKa Values
28#   Hui Li, Andrew D. Robertson and Jan H. Jensen
29#   PROTEINS: Structure, Function, and Bioinformatics 61:704-721 (2005)
30#
31#   Very Fast Prediction and Rationalization of pKa Values for Protein-Ligand Complexes
32#   Delphine C. Bas, David M. Rogers and Jan H. Jensen
33#   PROTEINS: Structure, Function, and Bioinformatics 73:765-783 (2008)
34#
35#   PROPKA3: Consistent Treatment of Internal and Surface Residues in Empirical pKa predictions
36#   Mats H.M. Olsson, Chresten R. Sondergard, Michal Rostkowski, and Jan H. Jensen
37#   Journal of Chemical Theory and Computation, 7, 525-537 (2011)
38#-------------------------------------------------------------------------------------------------------
39import sys
40import string
41import math
42import copy
43import lib
44from pdb import Atom
45pka_print = lib.pka_print
46
47class Residue:
48    """
49        Residue class - contains atoms and properties of the residue
50    """
51
52    def __init__(self, atoms, resName=None, resNumb=None, chainID=None, resInfo=None, options=None):
53        """
54        Constructer of the residue object.
55        """
56        self.label   = None
57        self.atoms   = []
58        if chainID == None:
59            self.chainID = atoms[0].chainID
60        else:
61            self.chainID = chainID
62        if resNumb == None:
63            self.resNumb = atoms[0].resNumb
64        else:
65            self.resNumb = resNumb
66        if resName == None:
67            self.resName = atoms[0].resName
68        else:
69            self.resName = resName
70        self.resType = None                         # determins the interaction parameters, e.g. 'COO'
71        self.Q       = None                         # residue charge
72        self.type    = None                         # 'amino-acid' / 'ion' / 'ligand' / 'N-terminus' / 'C-terminus'
73        self.buried  = None
74        self.location= None
75        self.determinants = [[], [], []]
76        self.Nmass   = 0
77        self.Nlocl   = 0
78        self.Emass   = 0.0
79        self.Vmass   = 0.0
80        self.Elocl   = 0.0
81        self.Vlocl   = 0.0
82        self.x       = 0.00
83        self.y       = 0.00
84        self.z       = 0.00
85        self.pKa_mod = None
86        self.pKa_pro = None
87        self.pKas    = []                           # list with several pKa-objects: not used yet
88        self.center_atoms   = []                    # list of atoms constituting the 'residue center', needed!
89        self.default_key    = None                  # the 'default' configuration if not all are there
90        self.configurations = []                    # keys to the atom configurations belonging to this residue
91        self.coupled_residues = []
92
93        # setting residue information i.e. resType, type, Q, pKa_mod & pKa_pro from resInfo dictionary
94        self.setResidueInformation(resInfo=resInfo)
95
96        # setting residue label of the residue to create
97        self.setResidueLabel()
98
99        # setting up the atom labels used to calculate the 'residue center'
100        residue_center_atom_labels = lib.residueCenterAtomList(self.resName)
101
102        # adding/setting various atom-related properties such as atoms, configurations etc.
103        for atom in atoms:
104
105            # checking if this residue has 'HETATM' atoms; good chance it's a ligand then
106            if atom.type == "hetatm" and self.type == None:
107                self.type = "ligand"
108
109            # setting the number of configurations for this residue
110            for key in atom.configurations.keys():
111                if key not in self.configurations:
112                    self.configurations.append(key)
113
114            #if atom.name[0] != 'H':
115            if True:
116                atom.set_residue(self)
117                self.atoms.append(atom)
118
119            # setting 'center atoms'; needs to be on self since it is reset when you switch configurations
120            if len(residue_center_atom_labels) == 0:
121                self.center_atoms.append(atom)
122            elif atom.name in residue_center_atom_labels:
123                self.center_atoms.append(atom)
124
125        # set residue center, i.e. give 'x, y, z' values
126        if len(self.center_atoms) > 0:
127            self.setResidueCenter()
128
129
130    def setResidueInformation(self, resInfo=None):
131        """
132        set residue information based on resName - it is set here since it is a convenience thing
133        """
134        # resType - determines interaction parameters
135        if self.resName in resInfo['resType']:
136            self.resType = resInfo['resType'][self.resName]
137
138        # Q - group charge
139        if self.resName in resInfo['Q']:
140            self.Q       = resInfo['Q'][self.resName]
141        else:
142            self.Q       = 0.00
143
144        # type - 'amino-acid' / 'ion' / 'ligand' / 'N-terminus' / 'C-terminus'
145        if   self.resName in lib.residueList("standard"):
146            self.type    = "amino-acid"
147        elif self.resName in resInfo['type']:
148            self.type    = resInfo['type'][self.resName]
149
150        # pKa_mod - model or water pKa value
151        if self.resName in resInfo['pKa']:
152            self.pKa_mod = resInfo['pKa'][self.resName]
153            self.pKa_pro = resInfo['pKa'][self.resName]
154        else:
155            self.pKa_mod = resInfo['pKa']['default']
156            self.pKa_pro = resInfo['pKa']['default']
157
158
159    def setConfiguration(self, key=None):
160        """
161        set the 'current possition' to a specific 'configuration', or default if it doesn't exist
162        """
163        self.cleanupPKA()
164        if key in self.configurations:
165            configuration = key
166        else:
167            configuration = self.default_key
168
169        for atom in self.atoms:
170            atom.setConfiguration(key=configuration)
171        self.setResidueCenter()
172
173
174    def cleanupPKA(self):
175        """
176        Initializing/cleaning up residue!
177        """
178        self.Nmass   = 0
179        self.Nlocl   = 0
180        self.Emass   = 0.00
181        self.Elocl   = 0.00
182        self.buried  = 0.00
183        self.pKa_pro = self.pKa_mod
184        self.determinants = [[], [], []]
185
186
187    def setResidueCenter(self):
188        """
189        sets the center of the residue based on center_atoms
190        """
191        number_of_atoms = len(self.center_atoms)
192        self.x = 0.00
193        self.y = 0.00
194        self.z = 0.00
195        for atom in self.center_atoms:
196            self.x += atom.x
197            self.y += atom.y
198            self.z += atom.z
199        if number_of_atoms > 0:
200            self.x = self.x/number_of_atoms
201            self.y = self.y/number_of_atoms
202            self.z = self.z/number_of_atoms
203
204
205    def getThirdAtomInAngle(self, atom=None):
206        """
207        finds and returns the third atom in angular dependent interactions
208        expecting one of ["HIS", "ARG", "AMD", "TRP"]
209        """
210        if   self.resName == "HIS":
211            if   atom.name == "HD1":
212                return self.getAtom(name="ND1")
213            elif atom.name == "HE2":
214                return self.getAtom(name="NE2")
215        elif self.resName == "ARG":
216            if   atom.name in ["HE"]:
217                return self.getAtom(name="NE")
218            elif atom.name in ["1HH1", "2HH1"]:
219                return self.getAtom(name="NH1")
220            elif atom.name in ["1HH2", "2HH2"]:
221                return self.getAtom(name="NH2")
222        elif self.resName == "ASN":
223            return self.getAtom(name="ND2")
224        elif self.resName == "GLN":
225            return self.getAtom(name="NE2")
226        elif self.resName == "TRP":
227            return self.getAtom(name="NE1")
228
229
230    def getAtom(self, name=None):
231        """
232        finds and returns the specified atom in this residue.
233        """
234        for atom in self.atoms:
235            if atom.name == name:
236                return  atom
237                break
238
239        return  None
240
241
242    def checkOXT(self):
243        """
244        Checks that OXT is present or creates it.
245        """
246        O   = self.getAtom(name="O")
247        OXT = self.getAtom(name="OXT")
248
249        # NMR Xplor over-write
250        if O == None:
251            O = self.getAtom(name="OT1")
252            if O != None:
253                O.setProperty(name="O")
254        if OXT == None:
255            OXT = self.getAtom(name="OT2")
256            if OXT != None:
257                OXT.setProperty(name="OXT")
258
259        # continuing after 'NMR exception'; creating OXT if not found
260        if OXT == None:
261            # did not find OXT, creating it 'on the fly'
262            CA = self.getAtom(name="CA")
263            C  = self.getAtom(name="C")
264            if O == None or CA == None or C == None:
265                pka_print("ERROR: cannot create OXT atom - missing CA, C, or O atoms; please correct pdbfile"); sys.exit(8)
266            dX = -((CA.x-C.x) + (O.x-C.x))
267            dY = -((CA.y-C.y) + (O.y-C.y))
268            dZ = -((CA.z-C.z) + (O.z-C.z))
269            distance = math.sqrt( dX*dX + dY*dY + dZ*dZ )
270            x = C.x + 1.23*dX/distance
271            y = C.y + 1.23*dY/distance
272            z = C.z + 1.23*dZ/distance
273            OXT = C.makeCopy(name="OXT", x=x, y=y, z=z)
274            self.atoms.append(OXT)
275            pka_print("creating %s atom" % (OXT.name))
276
277        return [O, OXT]
278
279
280
281
282    def fillUnknownConfigurations(self, keys=None, options=None):
283        """
284        Fills in  the configurations that have not been read
285        """
286        # getting default key as the first OK element in the sorted protein keys
287        for key in keys:
288            if key in self.configurations:
289                self.default_key = key
290                break
291
292        # enforcing all residue keys on each atom
293        for atom in self.atoms:
294            for configuration in self.configurations:
295                if key not in atom.configurations:
296                    atom.configurations[configuration] = atom.configurations[self.default_key]
297
298
299    def checked(self, options=None):
300        """
301        Checks that I understand all residues.
302        """
303        excluded_resNames = lib.residueList("excluded")
304        if self.resName in excluded_resNames:
305            return False
306        else:
307            return True
308
309
310    def checkResidue(self, options=None):
311        """
312        Checks that I understand all residues.
313        """
314
315        residue_list = lib.residueList("all")
316        rename = {"LYP": "LYS",
317                  "CYX": "CYS",
318                  "HSD": "HIS",
319                 }
320
321        # renaming residue if in rename dictionary
322        if self.resName in rename:
323            newName = rename[self.resName]
324            if options.verbose == True:
325                pka_print("Warning: renaming %s to %s" % (self.resName, newName))
326            self.resName = newName
327            for atom in self.atoms:
328                atom.resName = newName
329        else:
330            """OK - lets rock"""
331
332        # after rename residue cases, check heavy atoms
333        if self.resName in residue_list:
334            # OK - lets rock
335            self.checkAtoms(options=options)
336        # Chresten's stuff
337        elif self.type == "ion":
338            outstr  = "%s%4d -  OK %s" % (self.resName, self.resNumb, self.type)
339            pka_print(outstr)
340        #elif self.resName in version.ions.keys():
341        #    str  = "%-3s%4d - %s with charge %+d" % (self.resName,
342        #                                             self.resNumb,
343        #                                             version.ions_long_names[self.resName],
344        #                                             version.ions[self.resName])
345        #    pka_print(str)
346        else:
347            outstr  = "%s%4d - unidentified residue" % (self.resName, self.resNumb)
348            pka_print(outstr)
349
350
351    def checkAtoms(self, options=None):
352        """
353        Checks that all heavy atoms are there
354        """
355        outstr  = "%s%4d - " % (self.resName, self.resNumb)
356        atom_list = lib.atomList(self.resName)
357        OK = True
358        for name in atom_list:
359            FOUND = False
360            for atom in self.atoms:
361                if atom.name == name:
362                    FOUND = True
363            if FOUND == False:
364                outstr += " %s" % (name)
365                OK = False
366
367        if OK == True:
368            self.checkConfigurations(verbose=False)
369            outstr += " OK (%2d: %2d)" % (len(self.atoms), len(self.configurations))
370            if options.verbose == True:
371                pka_print(outstr)
372        else:
373            outstr += " missing"
374            pka_print(outstr)
375
376
377    def checkConfigurations(self, verbose=False):
378        """
379        checks that all atoms in this residue has the same number of configurations
380        """
381        for atom in self.atoms:
382            for key in self.configurations:
383                if key not in atom.configurations:
384                    atom.configurations[key] = atom.configurations[self.default_key]
385
386
387    def printLabel(self):
388        """
389        prints the residue ID
390        """
391        outstr = "%s%4d %s" % (self.resName, self.resNumb, self.chainID)
392        pka_print(outstr)
393
394
395    def __str__(self):
396        return self.label
397
398    def extractBackBoneAtoms(self):
399        """
400        Returns the back-bone atoms
401        """
402        N = None
403        H = None
404        C = None
405        O = None
406        for atom in self.atoms:
407            if   atom.name == "N":
408                N = atom
409            elif atom.name == "H":
410                H = atom
411            elif atom.name == "C":
412                C = atom
413            elif atom.name == "O":
414                O = atom
415
416        return  N, H, C, O
417
418
419    def makeDeterminantAtomList(self, resType=None, type=None, version=None):
420        """
421        Extracting reference to determinant atom - test stage still
422        """
423        if type == 'base' or type == 'acid':
424            pair_type = type
425        else:
426            if resType in ["HIS", "LYS", "ARG", "N+ "]:
427                pair_type = 'base'
428            else:
429                pair_type = 'acid'
430
431        if self.resName not in version.atomInteractionList[pair_type]:
432            pka_print("cannot find atomInteractionList for residue %s in residue.makeDeterminantAtomList()" % (self.resName))
433            sys.exit(9)
434
435        # Searching for determinant atom
436        atoms = []
437        for atom in self.atoms:
438            if atom.name in version.atomInteractionList[pair_type][self.resName]:
439                atoms.append(atom)
440
441        return atoms
442
443
444    def calculateTotalPKA(self):
445        """
446        Calculates the total pKa values from the desolvation and determinants
447        """
448        back_bone  = 0.00
449        for determinant in self.determinants[0]:
450            value = determinant.value
451            back_bone += value
452
453        side_chain = 0.00
454        for determinant in self.determinants[1]:
455            value = determinant.value
456            side_chain += value
457
458        coulomb    = 0.00
459        for determinant in self.determinants[2]:
460            value = determinant.value
461            coulomb    += value
462
463        self.pKa_pro = self.pKa_mod + self.Emass + self.Elocl + back_bone + side_chain + coulomb
464
465
466    def calculateIntrinsicPKA(self):
467        """
468        Calculates the intrinsic pKa values from the desolvation determinants, back-bone hydrogen bonds,
469        and side-chain hydrogen bond to non-titratable residues
470        """
471        back_bone  = 0.00
472        for determinant in self.determinants[1]:
473            value = determinant.value
474            back_bone += value
475
476        side_chain = 0.00
477        for determinant in self.determinants[0]:
478            if determinant.label[0:3] not in ['ASP','GLU','LYS','ARG','HIS','CYS','TYR','C- ','N+ ']:
479                value = determinant.value
480                side_chain += value
481
482        self.intrinsic_pKa = self.pKa_mod + self.Emass + self.Elocl + back_bone + side_chain
483
484        return
485
486
487
488    def calculateDesolvation(self, atoms, version=None, options=None):
489        """
490        Calculates the desolvation contribution
491        """
492        version.calculateDesolvation(self, atoms, options=options)
493
494
495    def setChain(self, chainID):
496        """
497        Set a chainID
498        """
499        self.chainID = chainID
500        for atom in self.atoms:
501            atom.chainID = chainID
502
503
504    def setResidueNumber(self, resNumb):
505        """
506        Set the residue numbers to 'resNumb'
507        """
508        self.resNumb = resNumb
509
510
511    def setResidueLabel(self, label=None):
512        """
513        Set the residue label to e.g. 'GLU 145 A'
514        """
515        if label == None:
516            self.label = lib.makeResidueLabel(self.resName, self.resNumb, self.chainID)
517        else:
518            self.label = label
519
520
521    def shiftResidueNumber(self, shift):
522        """
523        Shift the residue numbers with 'shift'
524        """
525        self.resNumb = self.resNumb + shift
526
527
528    def calculateFoldingEnergy(self, pH=None, reference=None, options=None):
529        """
530        returning the electrostatic energy of this residue at pH 'pH'
531        """
532        if pH == None:
533            pH = options.pH
534        if reference == None:
535            reference = options.reference
536
537        # calculating the ddG(neutral --> low-pH) contribution
538        if self.resType not in ["COO", "HIS", "N+ ", "CYS", "TYR", "LYS", "ARG"]:
539            ddG = 0.00
540        else:
541            if reference == "low-pH":
542                ddG_neutral = 0.00
543            else:
544                if self.Q > 0.00:
545                    pKa_prime = self.pKa_pro
546                    coulomb_determinants = self.determinants[2]
547                    for determinant in coulomb_determinants:
548                        if determinant.value > 0.00:
549                            pKa_prime -= determinant.value
550                    ddG_neutral = -1.36*(pKa_prime - self.pKa_mod)
551                else:
552                    ddG_neutral = 0.00
553
554            # calculating the ddG(low-pH --> pH) contribution
555            # folded
556            x =  pH - self.pKa_pro
557            y = 10**x
558            Q_pro = math.log10(1+y)
559
560            # unfolded
561            x =  pH - self.pKa_mod
562            y = 10**x
563            Q_mod = math.log10(1+y)
564
565            ddG_low = -1.36*(Q_pro - Q_mod)
566            ddG = ddG_neutral + ddG_low
567
568        return ddG
569
570
571    def getCharge(self, pH, state):
572        """
573        returning the charge of this residue at pH 'pH'
574        """
575        if state == "mod" or state == "unfolded":
576            x =  self.Q*(self.pKa_mod - pH)
577        else:
578            x =  self.Q*(self.pKa_pro - pH)
579            #x =  pH - self.pKa_pro
580        y = 10**x
581        charge = self.Q*(y/(1.0+y))
582        #charge = math.log10(1+y)
583
584        return charge
585
586
587    def calculateTitrationCurve(self, grid=[0., 14., 0.10]):
588        """
589        calculates the titration curve of this residue
590        """
591        if grid == None:
592            grid = [self.pKa_pro-2.5, self.pKa_pro+2.5, 0.10]
593        state = "folded"
594        titration_curve = []
595        pH   = grid[0]
596        stop = grid[1] + grid[2]/2.0
597        while pH < stop:
598            Q  = self.getCharge(pH, state)
599            titration_curve.append([pH, Q])
600            pH += grid[2]
601
602        return titration_curve
603
604
605    def getSummaryString(self):
606        """
607        Writing the summary string
608        """
609        outstr = "   %s%8.2lf%10.2lf" % (self.label, self.pKa_pro, self.pKa_mod)
610        return outstr
611
612
613    def mutateToAla(self):
614        """
615        mutating residue to 'ALA'
616        Note, if you mutate a ionizable residue to Ala it will remain in 'ionizable_residues list'
617        and propka will try to calcualte the pKa of it !!!
618        """
619        keep_atoms = lib.atomList("ALA")
620        self.printLabel()
621        self.resName = "ALA"
622        self.setResidueLabel()
623        self.printLabel()
624        new_atoms = []
625        for atom in self.atoms:
626            if atom.name in keep_atoms:
627                new_atoms.append(atom)
628        pka_print(self.atoms)
629        pka_print(new_atoms)
630        self.cleanupResidue
631        self.pKa_mod = pKa_mod(self.resName)
632        self.pKa_pro = self.pKa_mod
633        self.atoms = new_atoms
634
635
636    def replaceWithResidue(self, new_residue):
637        """
638        replacing current residue with incoming residue
639        """
640        self.cleanupResidue()
641        self.resName = new_residue.resName
642        self.pKa_mod = new_residue.pKa_mod
643        self.setResidueLabel()
644        exclude_atoms = ["N", "CA", "C", "O"]
645        tmp_atoms = []
646        for atom in self.atoms:
647            if atom.name in exclude_atoms:
648                tmp_atoms.append(atom)
649        self.atoms = tmp_atoms
650        for new_atom in new_residue.atoms:
651            if new_atom.name in exclude_atoms:
652                """ do nothing """
653            else:
654                self.atoms.append(new_atom)
655
656
657    def getDeterminantString(self):
658        """
659        Everything should be calculated, now, let's print the darn thing and be done!
660        """
661        #if self.location == "SURFACE" or self.location == "BURIED ":
662        BURIED_RATIO = True
663
664        empty_determinant = "%s%4d%2s" % ("XXX", 0, "X")
665        number_of_sidechain = len(self.determinants[0])
666        number_of_backbone  = len(self.determinants[1])
667        number_of_coulomb   = len(self.determinants[2])
668        number_of_determinants = number_of_sidechain + number_of_backbone + number_of_coulomb
669        number_of_lines     = max(1, number_of_sidechain, number_of_backbone, number_of_coulomb)
670        outsting  = ""
671        #outsting += " number_of_sidechain = %d" % (number_of_sidechain)
672        #outsting += " number_of_backbone  = %d" % (number_of_backbone )
673        #outsting += " number_of_coulomb   = %d" % (number_of_coulomb  )
674        #outsting += " number_of_lines     = %d" % (number_of_lines    )
675        #print outsting
676
677        for line_number in range(1, number_of_lines+1):
678            outsting += "%s" % (self.label)
679            if line_number == 1:
680                outsting += " %6.2lf" % (self.pKa_pro)
681                if len(self.coupled_residues)>0:
682                    outsting+='*'
683                else:
684                    outsting+=' '
685
686                if BURIED_RATIO == True:
687                    if self.type == "BONDED":
688                        outsting += " BONDED "
689                    else:
690                        outsting += " %4d%2s " % (int(100.0*self.buried), "%")
691                else:
692                    outsting += "%8s" % (self.type)
693                outsting += " %6.2lf %4d" % (self.Emass, self.Nmass)
694                outsting += " %6.2lf %4d" % (self.Elocl, self.Nlocl)
695            else:
696                outsting += "%40s" % (" ")
697
698            # Side-chain determinants
699            if line_number > number_of_sidechain :
700                outsting += "%8.2lf %s" % (0.0, empty_determinant)
701            else:
702                determinant = self.determinants[0][line_number-1]
703                outsting += "%8.2lf %s" % (determinant.value, determinant.label)
704
705            # Back-bone determinants
706            if line_number > number_of_backbone:
707                outsting += "%8.2lf %s" % (0.0, empty_determinant)
708            else:
709                determinant = self.determinants[1][line_number-1]
710                outsting += "%8.2lf %s" % (determinant.value, determinant.label)
711
712            # Coulomb determinants
713            if line_number > number_of_coulomb:
714                outsting += "%8.2lf %s" % (0.0, empty_determinant)
715            else:
716                determinant = self.determinants[2][line_number-1]
717                outsting += "%8.2lf %s" % (determinant.value, determinant.label)
718
719            # adding end-of-line
720            outsting += "\n"
721
722        return outsting
723
724
725    def printResult(self):
726        """
727        Everything should be calculated, now, let's print the darn thing and be done!
728        """
729        #if self.location == "SURFACE" or self.location == "BURIED ":
730        BURIED_RATIO = True
731
732        empty_determinant = "%s%4d%2s" % ("XXX", 0, "X")
733        number_of_sidechain = len(self.determinants[0])
734        number_of_backbone  = len(self.determinants[1])
735        number_of_coulomb   = len(self.determinants[2])
736        number_of_determinants = number_of_sidechain + number_of_backbone + number_of_coulomb
737        number_of_lines     = max(1, number_of_sidechain, number_of_backbone, number_of_coulomb)
738        outstr  = ""
739        outstr += " number_of_sidechain = %d" % (number_of_sidechain)
740        outstr += " number_of_backbone  = %d" % (number_of_backbone )
741        outstr += " number_of_coulomb   = %d" % (number_of_coulomb  )
742        outstr += " number_of_lines     = %d" % (number_of_lines    )
743        #print outstr
744
745        if True:
746            for line_number in range(1, number_of_lines+1):
747                outstr  = "%s%4d%2s" % (self.resName, self.resNumb, self.chainID)
748                if line_number == 1:
749                    outstr += " %6.2lf" % (self.pKa_pro)
750                    if BURIED_RATIO == True:
751                        outstr += "  %4d%2s " % (int(100.0*self.buried), "%")
752                    else:
753                        outstr += " %8s" % (self.type)
754                    outstr += " %6.2lf %4d" % (self.Emass, self.Nmass)
755                    outstr += " %6.2lf %4d" % (self.Elocl, self.Nlocl)
756                else:
757                    outstr += "%40s" % (" ")
758
759                # Side-chain determinant
760                if line_number > number_of_sidechain :
761                    outstr += "%8.2lf %s" % (0.0, empty_determinant)
762                else:
763                    determinant = self.determinants[0][line_number-1]
764                    outstr += "%8.2lf %s" % (determinant.value, determinant.label)
765
766                # Back-bone determinant
767                if line_number > number_of_backbone:
768                    outstr += "%8.2lf %s" % (0.0, empty_determinant)
769                else:
770                    determinant = self.determinants[1][line_number-1]
771                    outstr += "%8.2lf %s" % (determinant.value, determinant.label)
772
773                # Coulomb determinant
774                if line_number > number_of_coulomb:
775                    outstr += "%8.2lf %s" % (0.0, empty_determinant)
776                else:
777                    determinant = self.determinants[2][line_number-1]
778                    outstr += "%8.2lf %s" % (determinant.value, determinant.label)
779                pka_print('%s' % (outstr))
780        else:
781            outstr  = "%s%4d%2s%4d%2d" % (self.resName, self.resNumb, self.chainID, number_of_lines, number_of_determinants)
782            pka_print('%s' % (outstr))
783
784        pka_print('')
785
786
787    def translate(self, translation):
788        """
789        translate residue according to 'translation'
790        """
791        for atom in self.atoms:
792          atom.x += translation[0]
793          atom.y += translation[1]
794          atom.z += translation[2]
795          for key in atom.configurations.keys():
796            for i in range(3):
797              atom.configurations[key][i] += translation[i]
798
799
800    def rotate(self, axis, theta, center=None):
801        """
802        rotate residue theta radians around axis with center=center
803        """
804        from rotate import generalRotationMatrix
805        translate = [0.00, 0.00, 0.00]
806        number_of_atoms = 0
807        for atom in self.atoms:
808          if atom.name in center or center == None:
809            number_of_atoms += 1
810            translate[0] += atom.x/len(self.atoms)
811            translate[1] += atom.y/len(self.atoms)
812            translate[2] += atom.z/len(self.atoms)
813        for atom in self.atoms:
814          for i in range(3):
815            translate[i] = translate[i]/number_of_atoms
816
817        # translate to rotation center
818        for atom in self.atoms:
819          atom.x -= translate[0]
820          atom.y -= translate[1]
821          atom.z -= translate[2]
822
823        # get rotation matrix
824        rotation_matrix = generalRotationMatrix(axis, theta)
825
826        # rotating
827        new_position = [None, None, None]
828        for atom in self.atoms:
829
830          # rotate actual position
831          old_position = [atom.x, atom.y, atom.z]
832          for xyz in range(3):
833            new_position[xyz] = translate[xyz]
834            for i in range(3):
835              new_position[xyz] += rotation_matrix[xyz][i]*old_position[i]
836          # update position
837          atom.x = new_position[0]
838          atom.y = new_position[1]
839          atom.z = new_position[2]
840
841          # rotate configuration
842          for key in atom.configurations.keys():
843            for xyz in range(3):
844              new_position[xyz] = translate[xyz]
845              for i in range(3):
846                new_position[xyz] += rotation_matrix[xyz][i]*atom.configurations[key][i]
847            for xyz in range(3):
848              atom.configurations[key][xyz] = new_position[xyz]
849
850
851    def makeCopy(self,
852                    chainID = None,
853                    resNumb = None,
854                ):
855        """
856        making a copy of this residue
857        """
858        from protein import getResidueParameters
859        if chainID == None: chainID = self.chainID
860        if resNumb == None: resNumb = self.resNumb
861
862        newAtoms = []
863        for atom in self.atoms:
864          newAtoms.append( atom.makeCopy(chainID=chainID, resNumb=resNumb) )
865
866        resInfo = getResidueParameters()
867        newResidue = Residue(newAtoms, resInfo=resInfo)
868        newResidue.resType = self.resType
869        newResidue.Q       = self.Q
870        newResidue.type    = self.type
871
872        return  newResidue
873
874
875