Code
--[[
* Missing Ligand
*
* Residues should be bonding to a ligand, which is missing
* The ligand could be replaced by constraints
* If constraints are missing, then make bands
*
* Version 0.1 June 28 2013 brow42
* Version 1.0 Nov 28 2013 brow42
* Added tetrahedral symmetry
* Will now work witih O-terminal cysteine
* Added Help
--]]
title = "Missing Ligand 1.0.1"
-- ================================== Begin New Dialog Library Functions
-- Add a wall of text from a table
function dialog.AddLabels(d,msg,nlabels) -- pass in # of existing autolabels
local nlabels = nlabels or #(d._Order or {}) -- default, valid if never delete dialog elements
if type(msg) == 'string' then
msg = { msg }
end
for i = 1,#msg do
d['autolabel'..tostring(i+nlabels)] = dialog.AddLabel(msg[i])
end
end
-- Create but don't display a wall of text and 1 or 2 buttons
function dialog.CreateMessageBox(msg,title,buttontext1,buttontext0)
title = title or ''
local d = dialog.CreateDialog(title)
dialog.AddLabels(d,msg)
buttontext1 = buttontext1 or 'Ok'
d.button = dialog.AddButton(buttontext1,1)
if buttontext0 ~= nil then d.button0 = dialog.AddButton(buttontext0,0) end
return d
end
-- Display a dialog box
function dialog.ShowMessageBox(msg,title,buttontext1,buttontext0)
return dialog.Show(dialog.CreateMessageBox(msg,title,buttontext1,buttontext0))
end
dialog.T_CHECKBOX = 1
function dialog.FirstCheckedBox(d)
for u,v in pairs(d) do
if v.controlType and v.controlType == dialog.T_CHECKBOX and v.value == true then return u end
end
end
function dialog.CountCheckedBoxes(d)
n = 0
for u,v in pairs(d) do
if v.controlType and v.controlType == dialog.T_CHECKBOX and v.value == true then n = n + 1 end
end
return n
end
function dialog.ListCheckedBoxes(d)
l = { }
for u,v in pairs(d) do
if v.controlType and v.controlType == dialog.T_CHECKBOX and v.value == true then l[#l+1] = u end
end
return l
end
-- ================================== End New Dialog Library Functions
-- ================================== Begin Residue Grouping Functions
function BuildCluster(aas) -- minimize perimeter, won't actually find optimum because it's order dependent
-- find first two
local cluster,score,n
cluster = {}
score = math.huge
n = structure.GetCount()
for i = 1, n-1 do
for j = i+1,n do
if structure.GetAminoAcid(i) == aas[1] and structure.GetAminoAcid(j) == aas[2] and structure.GetDistance(i,j) < score then
cluster = { i, j}
score = structure.GetDistance(i,j)
end
end
end
for k = 3,#aas do
score = math.huge
local index = 0
for i = 1,n do
if structure.GetAminoAcid(i) == aas[k] then
local tmp = 0
for j = 1,#cluster do
if i == cluster[j] then
tmp = math.huge
break
end
tmp = tmp + structure.GetDistance(i,cluster[j])
end
if tmp < score then
score = tmp
index = i
end
end -- if aa
end -- score,index now have closest aa of the correct type
if index == 0 then
print ("No cluster")
return {}
end
cluster[#cluster+1] = index
end
print("Cluster:")
for i = 1,#cluster do print(i,cluster[i],structure.GetAminoAcid(cluster[i])) end
return cluster
end
function BestTwoPair(cluster) -- can't be bothered to generalize this yet
local best, pairs, len
best = structure.GetDistance(cluster[1],cluster[2]) + structure.GetDistance(cluster[3],cluster[4])
len = best
pairs = {{cluster[1],cluster[2]},{cluster[3],cluster[4]}}
len = structure.GetDistance(cluster[1],cluster[3]) + structure.GetDistance(cluster[2],cluster[4])
if len < best then
len = best
pairs = {{cluster[1],cluster[3]},{cluster[2],cluster[4]}}
end
len = structure.GetDistance(cluster[1],cluster[4]) + structure.GetDistance(cluster[2],cluster[3])
if len < best then
len = best
pairs = {{cluster[1],cluster[4]},{cluster[2],cluster[3]}}
end
best = structure.GetDistance(pairs[1][1],pairs[2][1]) + structure.GetDistance(pairs[2][1],pairs[2][2])
len = structure.GetDistance(pairs[1][1],pairs[2][2]) + structure.GetDistance(pairs[2][1],pairs[2][1])
if len < best then
pairs[2] = { pairs[2][2] , pairs[2][1] }
end
return pairs
end
-- ================================== End Residue Grouping Functions
-- ================================== Begin Ligand Functions
-- Return the atom number of the sulfur in a cysteine, or nil if not cys
function GetSulfurAtom(iSeg)
if structure.GetSecondaryStructure(iSeg) == 'M' then return nil end
if structure.GetAminoAcid(iSeg) ~= 'c' then return nil end
local diff = structure.GetAtomCount(iSeg) - 11
if current.GetSegmentEnergySubscore(iSeg,'disulfides') ~= tonumber('-0') then diff = diff + 1 end
if diff == 1 or diff == 3 then return 7 end
return 6
end
function GetNitrogenAtom(iSeg)
if structure.GetSecondaryStructure(iSeg) == 'M' then return nil end
if structure.GetAminoAcid(iSeg) ~= 'h' then return nil end
local diff = structure.GetAtomCount(iSeg) - 17
if diff == 1 or diff == 3 then return 11 end
return 10
end
function M2S2Cys4()
print "Fe2S2Cys4 Ferredoxin-like"
cluster = BuildCluster({'c','c','c','c'})
if #cluster == 0 then return end
-- Find best pair closest
cluster = BestTwoPair(cluster)
d = dialog.CreateDialog('M2S2Cys4 Positioning')
d.label = dialog.AddLabel("If these are the wrong pairs, re-arrange the AAs")
d.pair1 = dialog.AddLabel(string.format('First Pair: %d %d',cluster[1][1],cluster[1][2]))
d.pair2 = dialog.AddLabel(string.format('Second Pair: %d %d',cluster[2][1],cluster[2][2]))
-- d.label2 = dialog.AddLabel("")
d.Label3 = dialog.AddLabel("Pick Metal Atom (the distances)")
d.Fe = dialog.AddCheckbox("Fe",true)
d.label4 = dialog.AddLabel("")
d.bandatoms = dialog.AddCheckbox("Band Atoms",true)
-- d.bandspace = dialog.AddCheckbox("Band To Space",false)
d.str = dialog.AddSlider("Band Str",1.0,0.1,10,1)
d.okay = dialog.AddButton("Band",1)
d.cancel = dialog.AddButton("Cancel",0)
local rc
repeat
rc = dialog.Show(d)
until rc == 1 or rc == 0
if rc == 0 then
print("User cancel")
return
end
if d.Fe.value then
dxFe = 1.38
dxS = 2.7488
dyS = 1.8488
end
local nbands,iBand
nbands = band.GetCount()
if d.bandatoms.value and dxFe then
local bandfunc = function(aa1,aa2,d)
iBand = band.AddBetweenSegments(aa1,aa2,GetSulfurAtom(aa1),GetSulfurAtom(aa2))
band.SetGoalLength(iBand, d)
end
print("Banding cysteine sulfur distances")
bandfunc(cluster[1][1],cluster[1][2],2 * dyS)
bandfunc(cluster[2][1],cluster[2][2],2 * dyS)
bandfunc(cluster[1][1],cluster[2][1],2 * dxS)
bandfunc(cluster[1][2],cluster[2][2],2 * dxS)
bandfunc(cluster[1][1],cluster[2][2],2 * math.sqrt(dyS*dyS+dxS*dxS))
bandfunc(cluster[2][1],cluster[1][2],2 * math.sqrt(dyS*dyS+dxS*dxS))
for iBand = band.GetCount()-5,band.GetCount() do
band.SetStrength(iBand,d.str.value)
end
else
print ("No action selected")
end
end
function SideLength(r1,r2,a) -- law of cosines
return math.sqrt( r1*r1 + r2*r2 - 2 *r1 *r2 * math.cos(a) )
end
function Tetrahedral()
print "M2cys4 Tetrahedral"
cluster = BuildCluster({'c','c','c','c'})
if #cluster == 0 then return end
-- Find best pair closest
d = dialog.CreateDialog('MCys4 Positioning')
d.label = dialog.AddLabel("If these are the wrong AAs, re-arrange the AAs")
d.pair1 = dialog.AddLabel(string.format('Smallest Cluster: %d %d %d %d',unpack(cluster)))
d.Label3 = dialog.AddLabel("Pick Metal Atom (the distances)")
d.Fe = dialog.AddCheckbox("Fe",false)
d.Zn = dialog.AddCheckbox("Zn",false)
d.label2 = dialog.AddLabel("")
d.bandatoms = dialog.AddCheckbox("Band Atoms",true)
-- d.bandspace = dialog.AddCheckbox("Band To Space",false)
d.str = dialog.AddSlider("Band Str",1.0,0.1,10,1)
d.okay = dialog.AddButton("Band",1)
d.cancel = dialog.AddButton("Cancel",0)
local rc, MS, n
repeat
rc = dialog.Show(d)
if rc == 0 then
print("User cancel")
return
end
n = 0
if d.Fe.value then
MS = 2.35
n = n + 1
end
if d.Zn.value then
MS = 2.25
n = n + 1
end
local nbands,iBand
nbands = band.GetCount()
if n > 1 then
dialog.ShowMessageBox("Only pick one metal type","Oops")
rc = -1
end
until rc == 1
if d.bandatoms.value and n == 1 then
local SS = math.sqrt(8./3.) * MS
print("Banding cysteine sulfur distances")
for i = 1,3 do
for j = i+1,4 do
iBand = band.AddBetweenSegments(cluster[i],cluster[j],GetSulfurAtom(cluster[i]),GetSulfurAtom(cluster[j]))
if iBand == 0 then print("Could not band",cluster[i],cluster[j]," index",i,j) end
band.SetGoalLength(iBand, SS)
end
end
for iBand = band.GetCount()-5,band.GetCount() do
band.SetStrength(iBand,d.str.value)
end
else
print ("No action selected")
end
end
function ZincFinger()
print "Zn Cys2 Hist2 Zinc FInger"
clustertype = {
{'c','c','c','c'},
{'c','c','c','h'},
{'c','c','h','h'}
}
clusters = { BuildCluster(clustertype[1]), BuildCluster(clustertype[2]), BuildCluster(clustertype[3]) }
if #clusters[1] == 0 and #clusters[2] == 0 and #clusters[3] == 0 then
dialog.ShowMessageBox('No cluster found.','Awww.','Quit')
print('No cluster found.')
return
end
-- Find best pair closest
d = dialog.CreateDialog('Zn Cys/His Positioning')
d.label = dialog.AddLabel("If these are the wrong AAs, re-arrange the AAs")
for i = 1,3 do
if #clusters[i] > 0 then
d['cluster'..tostring(i)] = dialog.AddLabel(string.format("Type %s: %d %d %d %d",table.concat(clustertype[i],''),unpack(clusters[i])))
end
end
d.Label3 = dialog.AddLabel("Pick cluster")
for i = 1,3 do
if #clusters[i] > 0 then
d['type'..tostring(i)] = dialog.AddCheckbox(string.format("Type %s",table.concat(clustertype[i],'')),false)
end
end
d.label2 = dialog.AddLabel("")
d.bandatoms = dialog.AddCheckbox("Band Atoms",true)
-- d.bandspace = dialog.AddCheckbox("Band To Space",false)
d.str = dialog.AddSlider("Band Str",1.0,0.1,10,1)
d.okay = dialog.AddButton("Band",1)
d.cancel = dialog.AddButton("Cancel",0)
local rc, MS, n, MH, cluster,angle
repeat
rc = dialog.Show(d)
if rc == 0 then
print("User cancel")
return
end
n = 0
for i = 1, 3 do
if d['type'..tostring(i)].value then
cluster = clusters[i]
n = n + 1
end
end
local nbands,iBand
nbands = band.GetCount()
if n > 1 then
dialog.ShowMessageBox("Only pick one metal type","Oops")
rc = -1
end
until rc == 1
MS = 2.25
MH = 1.95
angle = math.acos(-1./3.)
distances = {c={},h={}}
distances['c']['c'] = SideLength(MS,MS,angle)
distances['h']['h'] = SideLength(MH,MH,angle)
distances['c']['h'] = SideLength(MS,MH,angle)
distances['h']['c'] = distances['c']['h']
if d.bandatoms.value and n == 1 then
local iBand
print("Banding cysteine sulfur /histidine nitrogen distances")
local atom = function(iseg)
if structure.GetAminoAcid(iseg) == 'c' then return GetSulfurAtom(iseg) end
return GetNitrogenAtom(iseg)
end
for i = 1,3 do
for j = i+1,4 do
iBand = band.AddBetweenSegments(cluster[i],cluster[j],atom(cluster[i]),atom(cluster[j]))
if iBand == 0 then print("Could not band",cluster[i],cluster[j]," index",i,j) end
band.SetGoalLength(iBand, distances[structure.GetAminoAcid(cluster[i])][structure.GetAminoAcid(cluster[j])])
end
end
for iBand = band.GetCount()-5,band.GetCount() do
band.SetStrength(iBand,d.str.value)
end
else
print ("No action selected")
end
end
function Help()
text = {
"Sometimes the atoms of a protein bond to a metal",
"ligand. Unlike the puzzles with large organic",
"ligands, these small metal clusters are usually",
"left out of the puzzle.",
"",
"These metal atoms usually bond to the sulfer in",
"cysteines in simple geometric patterns. This",
"recipe lets you pick a shape, and lets you",
"band the sulfurs into the shape they would have",
"if they were bonded to the metal.",
"",
"The script picks the tightest group. If you want",
"a different group, you'll have to rearrange",
"your protein."
}
return dialog.ShowMessageBox(text,title)
end
function PickLigand()
d = dialog.CreateDialog(title)
d.title = dialog.AddLabel('Pick Ligand Type (the shape):')
d.label1 = dialog.AddLabel('M = metal ion')
d.M2S2Cys4 = dialog.AddCheckbox('M2S2Cys4 ("twisted bowtie")',false)
d.tetrahedral = dialog.AddCheckbox('MCys4 ("4-sided die")',false)
d.zincfinger = dialog.AddCheckbox('ZnCys_4-n,Hist_n n=0,1,2 ("Zinc Finger")',false)
d.Select = dialog.AddButton('Select',1)
d.Cancel = dialog.AddButton('Cancel',0)
d.Help = dialog.AddButton('Help',2)
local rc
repeat
rc = dialog.Show(d)
if rc == 0 then return end
if rc == 1 and dialog.CountCheckedBoxes(d) ~= 1 then
dialog.ShowMessageBox('Check exactly 1 ligand type','Oops','Okay')
print(dialog.CountCheckedBoxes(d),"checked boxes")
rc = -1
end
if rc == 2 then
if Help() == 0 then return end
end
until rc == 1
return dialog.FirstCheckedBox(d)
end
-- ================================== End Ligand Functions
ligand = PickLigand()
if ligand == nil then
print ("No ligand selected or user cancelled")
return
end
ligandfuncs = {}
ligandfuncs.M2S2Cys4 = M2S2Cys4
ligandfuncs.tetrahedral = Tetrahedral
ligandfuncs.zincfinger = ZincFinger
ligandfuncs[ligand]()