modules/pymol/wizard/nucmutagenesis.py (359 lines of code) (raw):
from __future__ import print_function
import collections
from pymol.wizard import Wizard
import pymol.cmd
import pymol
SRC_SELE = "_mutate_sel"
FRAG_NAME = "_tmp_mut"
DEFAULT_MODE = "Adenine"
DEFAULT_REP = "lines"
class Status:
NO_SELECTION = 0
MUTAGENIZING = 1
class Nucmutagenesis(Wizard):
_rep_name = {
'lines': "Show Lines",
'sticks': "Show Sticks",
'spheres': "Show Spheres",
'dots': "Show Dots",
}
_reps = list(_rep_name.keys())
_auto_center_str = ["ON", "OFF"]
# Nucleic Acid chi angle
_all_dihedral_atoms = {
"pur": ["O4'", "C1'", "N9", "C4"],
"pyr": ["O4'", "C1'", "N1", "C2"]
}
_base_types = {
"atp": "pur",
"da": "pur",
"a": "pur",
"gtp": "pur",
"dg": "pur",
"g": "pur",
"ctp": "pyr",
"dc": "pyr",
"c": "pyr",
"ttp": "pyr",
"dt": "pyr",
"t": "pyr", # non-canonical
"utp": "pyr",
"du": "pyr", # non-canonical
"u": "pyr"
}
_base3_to_1 = {"atp": "a", "ctp": "c", "gtp": "g", "ttp": "t", "utp": "u"}
_base_atoms = [
"N1", "HN1", "C2", "N2", "HN21", "HN22", "C2", "N3", "N4", "HN41",
"HN42", "C4", "C5", "C5M", "HM51", "HM52", "HM53", "C6", "O6", "N6",
"N7", "N8", "C8", "H8", "N9"
]
_sugar_phos_atoms = [
"C1'", "H1'", "O4'", "C2'", "H2'", "H2'1", "H2'2", "O2'", "HO2'",
"C3'", "C4'", "H4'", "C5'", "H3'", "O3'", "HO3'", "H5'", "H5''",
"H5'1", "O5", "PA", "O1A", "O2A", "HOA2", "O3A", "PB", "O1B", "O2B",
"O3B", "HOB2", "O1G", "PG", "03G", "HOG3", "O2G", "HOG2", "O5'", "O3G",
"P", "OP1", "OP2", "H1G", "H3G", "H1B", "H2A", "H5'2", "HB", "HA"
]
_mode_labels = collections.OrderedDict([
('Adenine', 'ATP'),
('Cytosine', 'CTP'),
('Guanine', 'GTP'),
('Thymine', 'TTP'),
('Uracil', 'UTP'),
])
def __init__(self, _self=pymol.cmd): #PyMOL pattern
Wizard.__init__(self, _self) #PyMOL pattern
if self.cmd.get_movie_length() > 0:
raise pymol.wizarding.WizardError(
'Mutagenesis Wizard cannot be used with Movie')
self.cmd.unpick()
self._stored = pymol.Scratch_Storage()
self._space = {'stored': self._stored}
self._status = Status.NO_SELECTION
self._auto_center = "ON"
self.mode = DEFAULT_MODE
self.rep = DEFAULT_REP
self.selection_mode = self.cmd.get_setting_int("mouse_selection_mode")
self.cmd.set("mouse_selection_mode", 1)
tmp_menu = [[2, 'Mutant', '']]
for mode in self._mode_labels:
tmp_menu.append(
[1, mode, 'cmd.get_wizard().set_mode("' + mode + '")'])
self.menu['mode'] = tmp_menu
tmp_menu2 = [[2, 'Representation', '']]
for rep in self._reps:
tmp_menu2.append([
1, self._rep_name[rep],
'cmd.get_wizard().set_rep("' + rep + '")'
])
self.menu['rep'] = tmp_menu2
tmp_menu = [[2, 'Auto Center',
''], [1, "ON", 'cmd.get_wizard().set_auto_center("ON")'],
[1, "OFF", 'cmd.get_wizard().set_auto_center("OFF")']]
self.menu['auto_center'] = tmp_menu
def get_prompt(self):
'''
Updates the upper-left prompt shown to guide user's next actions.
Overrides method from Wizard class
:return: Prompt text awaiting user's next wizard-related action
:rtype: List (of size 1) of type string.
'''
self.prompt = None
if self._status == Status.NO_SELECTION:
self.prompt = ['Pick a nucleotide position...']
elif self._status == Status.MUTAGENIZING:
self.prompt = [
'Select a nucleotide for %s or pick a new position...' %
self.res_text
]
return self.prompt
def get_panel(self):
'''
Updates right-side panel to set/modify wizard-related actions
Overrides method from Wizard class
:return: List of widgets available for the wizard
:rtype: List of [(widget-type), (widget text), (menu/command)] to be
displayed on panel
'''
cmd = self.cmd
if int(cmd.get_setting_int("mouse_selection_mode") != 1):
cmd.set("mouse_selection_mode", 1)
label = 'Mutate to ' + self.mode
return [
[1, 'Mutagenesis', ''],
[3, label, 'mode'],
[3, 'Auto Center: %s' % self._auto_center, 'auto_center'],
[3, self._rep_name[self.rep], 'rep'],
[2, 'Apply', 'cmd.get_wizard().apply()'],
[2, 'Clear', 'cmd.get_wizard().clear()'],
[2, 'Done', 'cmd.set_wizard()'],
]
def do_pick(self, bondFlag):
'''
Pick callback for three-button editing during wizard
Overrides method from Wizard class
:param bondFlag: True if a bond is selected
'''
if bondFlag:
self._error = "Please select an atom and not a bond."
print(self._error)
else:
self.do_select('byres pk1')
self.cmd.refresh_wizard()
def do_select(self, selection):
'''
Selection callback during wizard
Overrides method from Wizard class
:param selection: A PyMOL selection
:type selection: string
'''
cmd = self.cmd
cmd.select(SRC_SELE, selection)
cmd.unpick()
try:
self._do_mutation()
except pymol.wizarding.WizardError as e:
print(e)
cmd.delete(selection)
cmd.refresh_wizard()
cmd.deselect()
def _get_chi_dihedral_atoms(self, base_type_lower):
'''
Determines the atom types for the nucleic acid
that composes the chi dihedral angle
:param base_type_lower: base type in lower cases
:type base_type_lower: string
:return: a tuple of atom types which composes the
chi dihedral
:rtype: a tuple of four strings
'''
base_type = ""
if base_type_lower in self._base_types:
base_type = self._base_types[base_type_lower]
else:
if all(
self.cmd.count_atoms("(%s) & name %s" % (SRC_SELE,
name)) == 1
for name in self._all_dihedral_atoms["pur"]):
base_type = "pur"
else:
base_type = "pyr"
return tuple(self._all_dihedral_atoms[base_type])
def _update_reps(self):
'''
Update atom representation of the candidate fragment
'''
self.cmd.show_as(self.rep + ' lines', '?' + FRAG_NAME)
def _transfer_dihedral(self):
'''
Calculates the dihedral angle from the original residue
and applies it to the candidate fragment
'''
chi_dihedral_src = self.cmd.get_dihedral(
"(%s & name %s)" % (SRC_SELE, self._src_O_atom_name),
"(%s & name %s)" % (SRC_SELE, self._src_Cp_atom_name),
"(%s & name %s)" % (SRC_SELE, self._src_N_atom_name),
"(%s & name %s)" % (SRC_SELE, self._src_C_atom_name))
self.cmd.set_dihedral(
"(%s & name %s)" % (FRAG_NAME, self._frag_O_atom_name),
"(%s & name %s)" % (FRAG_NAME, self._frag_Cp_atom_name),
"(%s & name %s)" % (FRAG_NAME, self._frag_N_atom_name),
"(%s & name %s)" % (FRAG_NAME, self._frag_C_atom_name),
chi_dihedral_src)
def _do_mutation(self):
'''
After selection, propose what the fragment will look like where
applied.
Exception: _do_mutation should be wrapped in a try-exception block
when called
'''
cmd = self.cmd
cmd.delete(FRAG_NAME)
cmd.refresh_wizard()
if any(
cmd.count_atoms("(%s) & name %s" % (SRC_SELE, name)) != 1
for name in ("C1'", "C2'", "C3'", "C4'", "O4'")):
self.clear()
raise pymol.wizarding.WizardError(
'Improper selection of nucleic acid.')
frag_type_lower = self._mode_labels[self.mode].lower()
cmd.fragment(frag_type_lower, FRAG_NAME, origin=0)
self._update_reps()
cmd.color("white", "%s & elem C" % FRAG_NAME)
cmd.iterate(
"first (%s)" % SRC_SELE,
'stored.name = model+"/"+segi+"/"+chain+"/"+resn+"`"+resi',
space=self._space)
self.res_text = self._space['stored'].name
cmd.alter(
"?%s & name C1'" % SRC_SELE,
"stored.identifiers = (segi, chain, resi, ss, color)",
space=self._space)
self._status = Status.MUTAGENIZING
self.get_prompt()
cmd.iterate(
"(%s & name C1')" % SRC_SELE,
"stored.resn = resn",
space=self._space)
src_type_lower = self._space['stored'].resn.lower()
self._src_O_atom_name, self._src_Cp_atom_name, self._src_N_atom_name, \
self._src_C_atom_name = self._get_chi_dihedral_atoms(src_type_lower)
self._frag_O_atom_name, self._frag_Cp_atom_name, self._frag_N_atom_name, \
self._frag_C_atom_name = self._get_chi_dihedral_atoms(frag_type_lower)
cmd.pair_fit("(%s & name %s)" % (FRAG_NAME, self._frag_O_atom_name),
"(%s & name %s)" % (SRC_SELE, self._src_O_atom_name),
"(%s & name %s)" % (FRAG_NAME, self._frag_Cp_atom_name),
"(%s & name %s)" % (SRC_SELE, self._src_Cp_atom_name),
"(%s & name %s)" % (FRAG_NAME, self._frag_N_atom_name),
"(%s & name %s)" % (SRC_SELE, self._src_N_atom_name))
self._transfer_dihedral()
for a in self._sugar_phos_atoms:
cmd.remove("(%s & name %s)" % (FRAG_NAME, a))
if self._auto_center == "ON":
cmd.center(FRAG_NAME, animate=1)
def clear(self):
'''
Clear selections and objects created by this wizard
in PyMOL.
Overrides method from Wizard class
'''
cmd = self.cmd
self._status = Status.NO_SELECTION
self.mode = DEFAULT_MODE
self.rep = DEFAULT_REP
cmd.delete(FRAG_NAME)
cmd.refresh_wizard()
def set_mode(self, mode):
'''
Modifies the wizard's mode aka chooses which fragment becomes
the candidate nucleic acid fragment
:param mode: Wizard-specific mode. Here, modes are any of the
nucleic acid fragments
:type mode: string
'''
cmd = self.cmd
#avoid case-sensitivity
mode_fixed = mode.title()
if mode_fixed in self._mode_labels:
self.mode = mode_fixed
else:
print('Improper Nucleic Acid')
if self._status == Status.MUTAGENIZING:
try:
self._do_mutation()
except pymol.wizarding.WizardError as e:
print(e)
cmd.refresh_wizard()
def set_rep(self, rep):
'''
Sets the atom representation of the candidate fragment
Overrides method from Wizard class
:param rep: atom representation
:rtype rep: string
'''
cmd = self.cmd
if self._status != Status.MUTAGENIZING:
return
if rep in self._reps:
self.rep = rep
self._update_reps()
cmd.refresh_wizard()
def set_auto_center(self, auto_center):
'''
Sets the flag for automatic centering upon prospective mutation
:param auto_center: string flag ("ON" or "OFF")
:rtype auto_center: string
'''
cmd = self.cmd
if auto_center not in self._auto_center_str:
print("Improper Auto Center setting. 'ON' or 'OFF' accepted only")
self._auto_center = auto_center
cmd.refresh_wizard()
def cleanup(self):
'''
Reverts the wizard to its original state
Overrides method from Wizard class
'''
cmd = self.cmd
cmd.set("mouse_selection_mode",
self.selection_mode) # restore selection mode
self.clear()
def apply(self):
'''
Permanently applies the mutation upon the selected residue
Overrides method from Wizard class
'''
cmd = self.cmd
if self._status == Status.NO_SELECTION:
return
#Remove all atoms that are not in the sugar/phosphate group
#Needed for non-canonical bases
cmd.select("_tmp_sele_invert", "(none)")
for a in self._sugar_phos_atoms:
cmd.select("_tmp_sele_invert",
"_tmp_sele_invert | (%s & name %s)" % (SRC_SELE, a))
cmd.select("_tmp_sele_invert",
"%s and not _tmp_sele_invert" % SRC_SELE)
cmd.remove("_tmp_sele_invert")
try:
new_name = cmd.get_object_list(SRC_SELE)[0]
except IndexError:
print(" Mutagenesis: object not found.")
return
frag_name_three = self._mode_labels[self.mode]
frag_name_one = self._base3_to_1[frag_name_three.lower()]
# FUSE
cmd.fuse(
"%s & name %s" % (FRAG_NAME, self._frag_N_atom_name),
"/%s/%s/%s/%s & name %s" %
(new_name, self._stored.identifiers[0],
self._stored.identifiers[1], self._stored.identifiers[2],
self._src_Cp_atom_name), 1)
#Check to see if DNA
dnaPrefix = ''
if cmd.count_atoms("%s & name O2'" % SRC_SELE) == 0:
dnaPrefix = 'D'
cmd.alter(
"/%s/%s/%s/%s" %
(new_name, self._stored.identifiers[0],
self._stored.identifiers[1], self._stored.identifiers[2]),
"(resn) = '%s%s'" % (dnaPrefix, frag_name_one.upper()),
space=self._space)
cmd.unpick()
self.clear()