Code
--
--[[
* Region Scorer
* Original Author brow42
* Version 1.0.1 2/23/2012 brow42
* Bug Fix: Custom score was computed wrong for printing.
Ranking was unaffected.
* Now prints puzzle name.
* Version 1.0 2/14/2012
* This is like a printed version of relative score coloring.
* It ranks every segment from 0 to N-1, where N is the user
* selectable number of groups. If you have a multistart puzzle,
* it will rank each start, after an optional stabilization
* procedure. CI during stabilization will never exceed the
* CI behavior slider value at the time you start the recipe.
* Any or all subscores may be used in the ranking. Ranking
* is shown first as a fixed-width list for visual comparison,
* and then as ranges. Segment scores are averaged over an
* adjustable sliding window.
*
* The intent of this recipe is to help you identify the worst
* parts for work, or the best and worst parts of each start
* for partial threading.
--]]
-- ================================== Begin Options and Globals
options = {
multistart = false,
wiggle = false,
qstab = false,
smoothing = 3,
quantiles = 5,
minlen = 1,
keepbest = true,
keepworst = true
}
options.score = { all = true } -- overrides checking individual scores
version = '1.0'
title = 'Region Scorer v. '..version
subscores = { 'clashing', 'packing', 'hiding', 'bonding', 'backbone', 'sidechain', 'disulfides', 'reference', 'other' }
for i=1,#subscores do options.score[subscores[i]] = true end
-- qstab options
qstab = {
max_ci = behavior.GetClashImportance(),
min_ci = 0.1,
early_stop = 0.001 -- don't do more iterations if < this
}
-- ================================== End Options and Globals
-- ================================== Begin New Dialog Library Functions
-- Trap a crasher
dialog._CreateDialog = dialog.CreateDialog
dialog.CreateDialog = function(x)
if x == nil or type(x) ~= 'string' then
error('Bad arg 1 to CreateDialog() (expected string)')
end
return dialog._CreateDialog(x)
end
--[[
Throws up a dialog containing just text, provided as a string or table
of strings in the first argument. Title and buttons are optional. Return
value is 1 if the first button is clicked and 0 if the second button is clicked
or the window is closed.
--]]
function dialog.MessageBox(msg,title,buttontext1, buttontext0)
title = title or ''
local d = dialog.CreateDialog(title)
if type(msg) == 'string' then d['1'] = dialog.AddLabel(msg)
else for i = 1,#msg do
d[tostring(i)] = dialog.AddLabel(msg[i])
end
end
buttontext1 = buttontext1 or 'Ok'
d.button = dialog.AddButton(buttontext1,1)
if buttontext0 then d.button0 = dialog.AddButton(buttontext0,0) end
return dialog.Show(d)
end
--[[
Add items to the dialog from a table in the order given. The each table entry
is the dialog control function, the control label, the data key name, and the control options.
If this function fails, it gives an error saying which table item it failed on.
d = a created dialog, fields = table of dialog controls as described, object = data table
--]]
function dialog.AddFromTable(d,fields,object)
local func, label, id, value -- renamed field parameters for clarity
-- function wrappers to catch errors, explicitly named for clarity
function _AddLabel(args) d[id] = dialog.AddLabel(label) end
function _AddCheckbox(args) d[id] = dialog.AddCheckbox(label,value) end
function _AddSlider(args) d[id] = dialog.AddSlider(label,value,args[4],args[5],args[6]) end
function _AddTextbox(args) d[id] = dialog.AddTextbox(label,value) end
for i = 1, #fields do
local rc = true
local err
rc,err = pcall( function() func,label,id = fields[i][1],fields[i][2],fields[i][3] end)
value = object[id]
if func == dialog.AddLabel then id='_AL'..tostring(i) rc,err = pcall(_AddLabel,fields[i])
else
-- this is a crasher for AddTextbox but not an error for AddLabel!
if value == nil then error('Missing data field for field #'..tostring(i)..'\n(Did you forget to pass a string?)') end
if func == dialog.AddCheckbox then rc,err = pcall(_AddCheckbox,fields[i])
elseif func == dialog.AddSlider then rc,err = pcall(_AddSlider,fields[i])
elseif func == dialog.AddTextbox then rc,err = pcall(_AddTextbox,fields[i])
else error('Unknown control in dialog field #'..tostring(i)) end
end -- if AddLabel
if rc == false then
error('Error setting dialog field #'..tostring(i)..(err and ('\n'..err) or ''))
end
end
end
--[[
This just copies the data from the dialog to the data table.
--]]
function dialog.ReadFields(d,fields,object)
for i = 1,#fields do
if fields[i][1]~= dialog.AddLabel then
object[fields[i][3]] = d[fields[i][3]].value
end
end
end
-- ================================== End New Dialog Library Functions
-- ================================== Begin Dialogs
function DoAboutDialog()
dialog.MessageBox({
'This divides the protein segment scores into',
'up to 10 groups, like a printable relative',
'score coloring. This let\'s you find the best',
'and worst parts.',
'',
'If you check "Multistart" then it will cycle',
'through all the starts, optionally doing a',
'wiggle or full stabilization of each start.',
'(Your start # and structure will be restored.)',
'',
'Smoothing averages scores over "X" neighbors',
'to each side. Advanced scoring let\'s you',
'pick score parts to score on.'
}, 'About '..title)
end
function DoAdvScoreDialog()
local d = dialog.CreateDialog('Advanced Scoring')
d.label = dialog.AddLabel('Select one or more:')
for i =1,#subscores do
d[subscores[i]] = dialog.AddCheckbox(subscores[i], options.score.all or options.score[subscores[i]])
end
d.okay = dialog.AddButton('Accept',1)
d.cancel = dialog.AddButton('Cancel',0)
d.clear = dialog.AddButton('Clear', 2)
d.all = dialog.AddButton('All', 3)
local ntrue
while true do -- exits on cancel or valid accept
local rc = dialog.Show(d)
if rc == 0 then return -- exit no changes
elseif rc == 1 then
ntrue = 0
for i = 1,#subscores do
ntrue = d[subscores[i]].value and ntrue + 1 or ntrue
end
if ntrue > 0 then break end -- accept only if at least 1 score field
elseif rc == 2 then for i = 1,#subscores do d[subscores[i]].value = false end
else for i = 1,#subscores do d[subscores[i]].value = true end
end
end
-- save changes
for i = 1,#subscores do
options.score[subscores[i]] = d[subscores[i]].value
end
options.score.all = ntrue == #subscores
end
function DoMainDialog()
local max_smooth = 10 > structure.GetCount() -1 and structure.GetCount() -1 or 10
local main = {
{ dialog.AddLabel, 'Check this to analyze all starts' },
{ dialog.AddCheckbox, 'Multistart', 'multistart'},
{ dialog.AddCheckbox, ' Do a wiggle (good starts)', 'wiggle'},
{ dialog.AddCheckbox, ' Do a full band/wiggle/qstab (low starts)', 'qstab' },
{ dialog.AddSlider, 'Smoothing', 'smoothing', 0, max_smooth, 0},
{ dialog.AddSlider,'Quantiles', 'quantiles', 1, 10, 0},
{ dialog.AddLabel, 'Check these to ony show best and worst parts' },
{ dialog.AddCheckbox, 'Ignore less-than-best', 'keepbest'},
{ dialog.AddCheckbox, 'Ignore better-than-worst', 'keepworst'}
}
local d = dialog.CreateDialog(title)
dialog.AddFromTable(d,main,options)
d.okay = dialog.AddButton('Okay',1)
d.cancel = dialog.AddButton('Cancel',0)
d.adv = dialog.AddButton('Adv. Scoring', 2)
d.help = dialog.AddButton('About',3)
repeat
local rc = dialog.Show(d)
if rc ~= 0 then dialog.ReadFields(d,main,options) end
if rc == 2 then DoAdvScoreDialog()
elseif rc == 3 then DoAboutDialog() end
until rc == 1 or rc == 0
return rc
end
-- ================================== End Dialogs
-- ================================== Begin Utility Functions
function PrintTable(tab,indent,seen)
indent = indent or ''
if tab == nil or type(tab) ~= 'table' then return end
seen = seen or {}
if seen[tab] then print(indent..'<cycle or dupe>') return end
seen[tab] = true
for i,v in pairs(tab) do
if type(v) == 'table' then print(indent,i) PrintTable(v,indent..' ',seen)
else print(indent,i,v) end
end
end
function puzzle.GetCount()
puzzle.StartOver()
local first_score = first_score or current.GetScore()
local i = 0
repeat
puzzle.StartOver()
i = i + 1
until current.GetScore() == first_score
return i
end
function GetScoreArray()
local s = {}
local min, max
min = math.huge
max = - min
for i = 1, structure.GetCount() do
local e = GetSegmentScore(i)
s[#s+1] = e
min = e < min and e or min
max = e > max and e or max
end
return s, min, max
end
function MinMax(tab)
local min, max
min = math.huge
max = - min
for i = 1, #tab do
local e = tab[i]
min = e < min and e or min
max = e > max and e or max
end
return min, max
end
function Smooth(list,w)
result = {}
if w > #list -1 then w = #list - 1 end -- max offset reflects to other endpoint
for i = 1,#list do
for j = -w,w do
k = i + j
if k < 1 then k = 1 + (1-k)
elseif k > #list then k = #list - (k - #list) end
result[i] = (result[i] or 0) + list[k]
end
end
local n = 2 * w + 1
for i = 1,#result do result[i] = result[i] / n end
return result
end
function Quantize(tab, n, min1, max1)
local min,max = min1, max1
if min == nil or max == nil then
min1, max1 = MinMax(tab)
if min == nil then min = min1 end
if max == nil then max = max1 end
end
local q = (max - min) / n
if q == 0 then q = math.huge end
local result = {}
for i = 1,#tab do
local v = math.floor((tab[i] - min)/ q)
if v >= n then v = n-1 end
result[i] = v
end
if options.keepbest or options.keepworst then
for i = 1,#result do
local keep = false
if result[i] == n-1 and options.keepbest then keep = true
elseif result[i] == 0 and options.keepworst then keep = true
end
result[i] = keep and result[i] or '_'
end
end
return result
end
-- identify ranges with the same value
function Summarize(ss)
local c = ''
local count = 0
local result = {}
for i = 1,#ss+1 do
if ss[i] == c then count = count + 1
else
if count > 0 then
local x = ', '
if result[c] == nil then result[c] = '' x='' end
if count == 1 then result[c] = result[c]..string.format('%s%d',x,i-1)
else result[c]= result[c]..string.format('%s%d-%d',x,i-count,i-1)
end
end
count = 1
c = ss[i]
end
end
local resultstr = ''
for i = 1,options.quantiles do
if result[i-1] ~= nil then
resultstr = resultstr .. string.format('%d: ',i-1) .. result[i-1] .. (i < options.quantiles and '; ' or '')
end
end
return resultstr
end
-- Customizable score
function GetScore()
if options.score.all == true then return current.GetEnergyScore() end
tmp = {}
for i = 1, #subscores do
if options.score[subscores[i]] then tmp[#tmp+1] = subscores[i] end
end
local e = 0
for j = 1, structure.GetCount() do
for i = 1,#tmp do
e = tmp[i] and current.GetSegmentEnergySubscore(j,tmp[i]) + e
end
end
return e
end
function GetSegmentScore(iSeg)
if options.score.all == true then return current.GetSegmentEnergyScore(iSeg) end
local e = 0
for i = 1,#subscores do
if options.score[subscores[i]] then e = e + current.GetSegmentEnergySubscore(iSeg,subscores[i]) end
end
return e
end
function GetScoreForPrinting() -- print energy and custom score
if options.score.all then return string.format('%f',current.GetEnergyScore()) end
return string.format('%f (%f)',current.GetEnergyScore(),GetScore())
end
-- ================================== End Utility Functions
-- ================================== Begin QStab Functions
-- set the clash between min and max but no less than min regardless of max
function qstab.CI(x)
x = x > 1 and 1 or x
x = x < 0 and 0 or x
x = x * (qstab.max_ci - qstab.min_ci)
x = x < qstab.min_ci and qstab.min_ci or x
return x
end
-- call functin f n times unless nothing is happening
function qstab.ws(n,stabfunc)
local e1, e2
while n > 0 do
e1 = GetScore()
stabfunc(1)
e2 = GetScore()
if math.abs(e1-e2) < qstab.early_stop then break end
n = n - 1
end
end
function qstab.WA(n) qstab.ws(n, structure.WiggleAll) end
function qstab.SS(n) qstab.ws(n, structure.ShakeSidechainsAll) end
function qstab.BandAll()
local len = 0.001
local n = structure.GetCount()
band.DeleteAll()
for i = 2,n-1 do band.Add(i,i-1,i+1,0.001,0,0) end
band.Add(1,2,3,len,0,0)
band.Add(n,n-1,n-2,len,0,0)
for i = 1,band.GetCount() do
band.SetGoalLength(i,len)
band.SetStrength(i,3.0)
end
end
function QStab()
qstab.BandAll()
qstab.CI(1.0)
qstab.SS(5)
qstab.CI(0.1)
qstab.WA(5)
band.DeleteAll()
qstab.CI(0.4)
qstab.WA(1)
qstab.CI(1.0)
qstab.SS(1)
qstab.WA(5)
end
-- ================================== End QStab Functions
-- ================================== Begin Main
if DoMainDialog() == 0 then return end
print(puzzle.GetName())
if options.score.all == false then
print('Custom Scoring:')
PrintTable(options.score, ' ')
end
qslot = 1
if options.multistart then
print('Saving puzzle to slot',qslot)
save.Quicksave(qslot)
puzzle.StartOver()
print('Individual start analyses (independent scaling)')
if options.qstab or options.wiggle then print('Max. CI for this run is',qstab.max_ci) end
end
first_score = current.GetEnergyScore()
iStart = 1
results = {} -- to print at the end
repeat
local s1, s2 = GetScoreForPrinting(), '' -- scores for printing
-- stabilize multistarts
if options.multistart then
if options.qstab then QStab() s2 = 'QStab: '..tostring(GetScoreForPrinting())
elseif options.wiggle then qstab.WA(10) s2 = 'Wiggle: '..tostring(GetScoreForPrinting()) end
end
scores = GetScoreArray()
smoothed = Smooth(scores,options.smoothing)
local min,max = MinMax(smoothed)
quantized = Quantize(smoothed,options.quantiles,min,max)
summary = Summarize(quantized)
print('Start #',iStart,s1,s2)
print('Segment Rank:',table.concat(quantized,''))
print('Summary:',summary)
-- save results
results[iStart] = {
s1 = s1, s2 = s2, scores = scores, smoothed = smoothed, quantized = quantized, min = min, max = max
}
-- Move on to the next start
if options.multistart then
puzzle.StartOver()
iStart = iStart + 1
end
until current.GetEnergyScore() == first_score
-- istart contains # starts + 1 (assuming no duplicate scores)
if options.multistart then
print('Looping through to return to original start...')
for i=1,iStart-2 do puzzle.StartOver() end
print('Restoring saved puzzle.')
save.Quickload(qslot)
end
-- print multistart summary
if options.multistart then
local min, max = math.huge,-math.huge
for i = 1,#results do
if results[i].min < min or i == 1 then min = results[i].min end
if results[i].max > max or i == 1 then max = results[i].max end
end
print('Combined start summary (uniform scaling)')
for i = 1,#results do
local blah = i == # results and '<- this is where you started' or ''
print('Start:',i,results[i].s1,results[i].s2,blah)
end
print('Segment Rankings:')
for i = 1,#results do
quantized = Quantize(results[i].smoothed,options.quantiles,min,max)
print('Start:',string.format('%2d',i),table.concat(quantized,''))
results[i].quantized2 = quantized
end
print('Summaries:')
for i = 1,#results do
summary = Summarize(results[i].quantized2)
print('Start:',i,summary)
end
end