Code
--[[
===================================
* BiS Wiggle nee ZLB Wiggle
* Original Author: Brow42
* Version 1.0 Jan. 15 2012
* This does random small bands and then wiggles
* restoring best periodically. This may find a few
* 0.001s of a point in the end game.
* Version 1.0.1
* New defaults, fixed random sphere to be correct (not hemisphere)
* Version 1.0.2
* Added option to reverse bands and wiggle
* Version 1.1 Apr. 27 2012 Brow42
* Now checks for locked segments and skips them
* Now bands terminal segments correctly
* Removes bands on cancel
* Version 1.1.1 Sep 14 2012 Brow42
* Checkbox to allow banding to locked segments
* Name changed to BiS Wiggle to avoid confusion with Zero Length Bands
* WiggleAll changed to 2 iterations as per jflat advice
* Version 1.2 Sept. 15 2012 Brow42
* Added slider for # of wiggles
* Added unbanded wiggle before score check
* Band removal on cancel actually works
* Auto detects all-locked puzzles
--]]
NSeg = structure.GetCount() -- Will be recomputed later
options = {
band_str = 2.5,
max_len = 0.3,
do_bb = true,
do_sc = false,
nbands = 1,
restore_every=1,
flip = false,
lockedok = false,
nwiggle = 1,
unbanded = false
}
title = 'BiS WIggle'
version='1.1.2'
update_every = 10
-- ================================== Begin New Dialog Library Functions
--[[
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
function DoInfoDialog()
dialog.MessageBox( {'This does random small bands and then wiggles',
'restoring best periodically. This may find a few',
' 0.001s of a point in the end game.'},'About '..title,'Okay')
end
function DoOptionsDialog()
fields = {
{ dialog.AddSlider,'Band Str:', 'band_str', 0.1,10,1 },
{ dialog.AddSlider,'Max Len:', 'max_len', 0.01,1.,2 },
{ dialog.AddSlider, '# Bands:', 'nbands', 1,NSeg, 0 },
{ dialog.AddSlider, 'Restore Every', 'restore_every', 1,20, 0 },
{ dialog.AddSlider, '# Wiggle', 'nwiggle', 1, 10, 0},
{ dialog.AddCheckbox, 'Band Backbone', 'do_bb' },
{ dialog.AddCheckbox, 'Band Sidechain C-b', 'do_sc'},
{ dialog.AddCheckbox, 'Wiggle then Reverse and Wiggle again','flip'},
{ dialog.AddCheckbox, 'Locked/Froze Okay','lockedok'},
{ dialog.AddCheckbox, 'Unbanded wiggle before restore','unbanded'}
}
d = dialog.CreateDialog(title..' v'..version)
dialog.AddFromTable(d,fields,options)
d.okay = dialog.AddButton('Start',1)
d.cancel = dialog.AddButton('Cancel',0)
d.about = dialog.AddButton('About',-1)
rc = dialog.Show(d)
dialog.ReadFields(d,fields,options)
return rc
end
fsl = fsl or {}
fsl.atom = fsl.atom or {}
fsl.atom.atomcount = { -- shorter table just for identifying terminals
a=10, c=11, d=12, e=15, f=20, g=7, h=17, i=19, k=22, l=19,
m=17, n=14, p=15, q=17, r=24, s=11, t=14, v=16, w=24, y=21
}
function fsl.atom._IsDisulfideBonded(iSeg)
local s = current.GetSegmentEnergySubscore(iSeg,'disulfides')
return tostring(s) ~= '-0'
end
function fsl.atom._IsTerminalTest(aa,count,disulfide)
local diff = count - fsl.atom.atomcount[aa]
if disulfide then diff = diff + 1 end -- because count is one less because a H was removed
if diff == 0 then return false, false, disulfide
elseif diff == 1 then return false, true, disulfide
elseif diff == 2 then return true, false, disulfide
elseif diff == 3 then return true, true, disulfide
end
error('Strange atom count, report this!')
end
function band.SetParams(iBand, goal, str)
band.SetGoalLength(iBand, goal)
band.SetStrength(iBand, str)
end
-- Pick a random combination from 1 - N
function math.Pick(k,n)
local l = {}
local m = {}
for i=1,n do l[i] = i end
for i = 1,k do
local j = math.random(n)
m[i] = l[j]
l[j] = l[n]
n = n - 1
end
table.sort(m)
return m
end
ValidSegments = {}
function DoBanding(seglist, do_bb,bandlist)
local nseg = structure.GetCount()
bandlist = bandlist or {}
for i = 1,#seglist do
local phi,rho,theta,s1,s2,s3
s1 = ValidSegments[seglist[i]]
if s1 == 1 then s2,s3 = 2,3
elseif s1 == nseg then s2,s3 = nseg-1,nseg-2
else s2,s3 = s1-1,s1+1 end
if do_bb then
phi,theta,rho = RandomSphere()
iBand = band.Add(s1,s2,s3,rho,theta,phi,0)
band.SetParams(iBand,0.0,options.band_str)
bandlist[#bandlist+1] = {s1,s2,s3,rho,theta,phi,0,0.0,options.band_str}
elseif structure.GetSecondaryStructure(s1) ~= 'M' and structure.GetAminoAcid(s1) ~= 'g' then
local first,last,disulfide = fsl.atom._IsTerminalTest(
structure.GetAminoAcid(s1), structure.GetAtomCount(s1), fsl.atom._IsDisulfideBonded(s1))
phi,theta,rho = RandomSphere()
iBand = band.Add(s1,s2,s3,rho,theta,phi,last and 6 or 5)
band.SetParams(iBand,0.0,options.band_str)
bandlist[#bandlist+1] = {s1,s2,s3,rho,theta,phi,5,0.0,options.band_str}
end
end
return bandlist
end
function ReverseBands(bandlist)
unpack = unpack or table.unpack
local s1,s2,s3,rho,theta,phi,atom,len,str
for i = 1,#bandlist do
s1,s2,s3,rho,theta,phi,atom,len,str = unpack(bandlist[i])
theta = math.pi - theta
phi = (phi + math.pi) % (2 * math.pi)
iBand = band.Add(s1,s2,s3,rho,theta,phi,atom)
band.SetParams(iBand,len,str)
end
end
function RandomSphere()
return 2 * math.pi * math.random(),
math.acos(2*math.random()-1),
math.random()^(1/3) * options.max_len +0.01
end
function PrintTable(tab,indent)
indent = indent or ''
for i,v in pairs(tab) do
if type(v) == 'table' then print(i) PrintTable(v,indent..' - ')
else print(i,v) end
end
end
function round(x,n) return math.floor(0.5 + x * 10^n) / 10^n end
function IsValidSegment(iSeg)
return options.lockedok or not structure.IsLocked(iSeg)
end
NBands = band.GetCount()
function ResetBands()
while band.GetCount() > NBands do
band.Delete(band.GetCount())
end
end
-- ===================== Begin Main =====================
print(puzzle.GetName(), os.date())
seed = (os.time() * 5779) % 10000
math.randomseed(seed)
if options.lockedok == false then
print ('Checking for all-locked puzzle')
options.lockedok = true
for i = 1,NSeg do
if structure.IsLocked(i) == false then
options.lockedok = false
break
end
end
end
repeat
local rc = DoOptionsDialog()
if rc == 0 then print('Canceled.') return end
if rc == -1 then DoInfoDialog() end
until rc == 1
for i=1,NSeg do
if IsValidSegment(i) then table.insert(ValidSegments,i) end
end
NSeg = #ValidSegments
if NSeg == 0 then
print('Couldn\'t find any wiggle-able segments.')
dialog.MessageBox('No segments to wiggle! (all are locked maybe?)','Can\'t Wiggle!','Quit')
return
end
print('seed =',seed)
print('Options:')
PrintTable(options)
recentbest.Save()
score = current.GetScore()
print('Start Score:',score)
print(' Round Change Energy')
function Main() -- wrapper for cancel catch
iLoop = 0
repeat
ResetBands()
bandlist = {}
if options.do_bb then bandlist = DoBanding(math.Pick(options.nbands, NSeg),true, bandlist) end
if options.do_sc then bandlist = DoBanding(math.Pick(options.nbands, NSeg),false, bandlist) end
structure.WiggleAll(2 * options.nwiggle)
if options.flip then
ResetBands()
ReverseBands(bandlist)
structure.WiggleAll(2 * options.nwiggle)
end
if options.unbanded then
ResetBands()
structure.WiggleAll(2 * options.nwiggle)
end
local current_score = recentbest.GetScore()
iLoop = iLoop + 1
if iLoop % options.restore_every == 0 then recentbest.Restore() end
if iLoop % update_every == 0 then
print('Round #',iLoop,round(current_score-score,5),round(current_score,3))
end
until false
end
function OnError(err) -- Thanks to Tlaloc for example
if err:find('Cancel') then
print('User Cancel -- Removing bands')
ResetBands()
return false
end
print('ERROR',err)
return err
end
xpcall(Main, OnError)