Profile
- Name
- Mutate No Wiggle 1.5
- ID
- 101107
- Shared with
- Public
- Parent
- Mutate No Wiggle 1.4
- Children
- Created on
- June 11, 2015 at 21:53 PM UTC
- Updated on
- June 11, 2015 at 21:53 PM UTC
- Description
Brute Force Mutator, but doesn't wiggle, stabilize, or fuze. Keeps best AA for each spot. Good for slow design puzzles. Has options for restricting mutations. Version 1.2 lets you exclude alanine. Version 1.3 works on locked segments and lets you repeat a specified number of times. Version 1.4 shuffles the order of segments after the first round. The first round is in segment score order. Version 1.5 sets an adjustable minimum gain threshold to avoid wasting time on miniscule gains.
Best for
Code
--[[
Mutate No Wiggle -- Brow42
Original Author: Brow42
Version 1.0 Sept 6 2014
Brute-force mutator without the wiggle...for
puzzles where wiggle is too slow.
Also has options to restrict which amino acids
are mutated, and to what. For example, prolines
and glycines won't be added, and cysteines won't
be touched.
Automatically does all mutables from worst to best
scoring, but range can be set manually, or via
selection/freeze before you run the script.
This script uses strings instead of tables for AAs.
Version 1.02 -- Republished under new id
Version 1.1 Oct 31 2014 Brow42
Version 1.1 Dec 18 2014 LociOiling
Added pause to allow GUI to function. Added timing info
Added option to remove unwanted residues (from Googol30)
Added trap for User Cancel
Version 1.1.1 Apr 19 2015 LociOiling
* Allowed mutation of locked segments.
Version 1.3 2015/05/16 LociOiling
* loop
Version 1.4 2015/05/16 LociOiling
* shuffle order of segments each loop
Version 1.5 2015/06/10 LociOiling
* add minimum gain setting
]]--
NSeg = structure.GetCount()
title = "Mutate No Wiggle 1.5"
options = {
cok=true,
pok=true,
gok=true,
aok=true,
keepc=false,
keepp=false,
keepg = false,
keephydro=false,
charged=true,
rings=true,
custom="",
range="",
removeaa=false,
locked=true,
repeats=10,
maxbad=2,
mingain=2,
}
-- Gah. All the options have to be in the dialog, so these options are not in the options table
guitext_template = "%0.2f seconds every %d mutations"
pause = 5
delay = 0.1
options["gui"] = guitext_template:format(delay,pause)
function Menu()
local d = dialog.CreateDialog(title)
d["label1"] = dialog.AddLabel("Include these as allowed AA")
d["cok"] = dialog.AddCheckbox("cysteine",options.cok)
d["pok"] = dialog.AddCheckbox("proline",options.pok)
d["gok"] = dialog.AddCheckbox("glycine",options.gok)
d["aok"] = dialog.AddCheckbox("alanine",options.aok)
d["charged"] = dialog.AddCheckbox("charged AA",options.charged)
d["rings"] = dialog.AddCheckbox("rings: phen, tryp, tyr",options.rings)
d["label2"] = dialog.AddLabel("Or, specify AA (overrides above)")
d["custom"] = dialog.AddTextbox("AAs",options.custom)
d["lable2"] = dialog.AddLabel("Don't change these AA")
d["keepc"] = dialog.AddCheckbox("cysteine",options.keepc)
d["keepp"] = dialog.AddCheckbox("proline",options.keepp)
d["keepg"] = dialog.AddCheckbox("glycine",options.keepg)
d["range"] = dialog.AddTextbox("Where:",options.range)
-- d["label3"] = dialog.AddLabel("")
d["keephydro"] = dialog.AddCheckbox("Preserve Hydrophobicity",options.keephydro)
d["removeaa"] = dialog.AddCheckbox("Remove AAs not in either list above (if possible)",options.removeaa)
d["locked"] = dialog.AddCheckbox("Include locked",options.locked)
d["repeats"] = dialog.AddSlider ( "Repeats", options.repeats, 1, 10, 0 )
d["maxbad"] = dialog.AddSlider ( "Max reps no gain", options.maxbad, 1, 10, 0)
d["mingain"] = dialog.AddSlider ( "Min gain per rep", options.mingain, 1, 25, 0)
d["gui"] = dialog.AddTextbox("GUI Saver: Wait",options.gui)
d["ok"] = dialog.AddButton("OK",1)
d["cancel"] = dialog.AddButton("Cancel",0)
d["where"] = dialog.AddButton("Select",2)
d["help"] = dialog.AddButton("About",3)
local rc = dialog.Show(d)
for u,v in pairs(options) do
pcall(function() options[u] = d[u].value end) -- using scoping so I don't have to pass any variable names
end
return rc
end
-- ================================== 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,buttontext2)
title = title or ''
local d = dialog.CreateDialog(title)
dialog.AddLabels(d,msg)
buttontext1 = buttontext1 or 'Ok'
d.button = dialog.AddButton(buttontext1,1)
if buttontext2 ~= nil then d.button2 = dialog.AddButton(buttontext2,2) end
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,buttontext2)
return dialog.Show(dialog.CreateMessageBox(msg,title,buttontext1,buttontext0,buttontext2))
end
-- Display a box AND print to the output
function ErrorMessage(msg,title)
if type(msg) == 'string' then
msg = { msg }
end
for i = 1,#msg do
print(msg[i])
end
return dialog.ShowMessageBox(msg,title)
end
-- ======================================= End Dialog Functions
function About()
local rc = dialog.ShowMessageBox( {
"This recipe mutates all mutables to all amino acids",
"keeping the best score for each segment. It does this",
"from worst score to best score. NO WIGGLE IS DONE.",
"",
"Skipping wiggle allows this to work on puzzles with",
"very slow wiggle, but probably won't find the same",
"combination as one of the regular mutation scripts.",
"",
"You can control which segments are mutated, and to",
"what types of amino acids. In particular, you can",
"prevent prolines and glycines from being added. You",
"can also prevent cysteines from being mutated to save",
"bridges. You can restrict mutations to the same",
"hydrophobicity."
},title,"Back")
if rc == 0 then return 0 end -- cancel script
return 3
end
-- Turn selection or frozen into ranges and range string
function Where()
local d = dialog.CreateDialog(title)
dialog.AddLabels(d,
{"Have you selected segments to mutate with","the selection or freeze tool?"})
d.select = dialog.AddButton("Selection",1)
d.freeze = dialog.AddButton("Freeze",2)
d.nope = dialog.AddButton("Nope",3)
-- 0 is window close (cancel script)
local rc = dialog.Show(d)
if rc == 0 or rc == 3 then return rc end
local tmp = {selection.IsSelected, freeze.IsFrozen}
UserSelect = tmp[rc] -- pretty name because it shows up in the GUI
local appending = false
local s
local range
local list = {}
for i = 1, NSeg do
s = UserSelect(i)
if s then
if appending then range[2] = i
else
range = {i,i}
list[#list+1] = range
appending = true
end
else
if appending then appending = false end
end
end
for i = 1,#list do
if list[i][1] == list[i][2] then list[i] = tostring(list[i][1])
else list[i] = tostring(list[i][1]).."-"..tostring(list[i][2]) end
end
options.range = table.concat(list," ")
return 2
end
-- sort by score, also filter by mutability
function SortSegments(segs)
local scores = {}
local keys = {}
for j = 1,#segs do
i = segs[j]
if structure.IsMutable(i) and ( options.locked or not structure.IsLocked(i) ) then
s = current.GetSegmentEnergyScore(i) - current.GetSegmentEnergySubscore(i,'reference')
scores[s] = i -- scores map to segments
keys[#keys+1] = s -- keys are scores
end
end
table.sort(keys)
for i = 1,#keys do
keys[i] = scores[keys[i]] -- replace score with segment
end
return keys
end
-- Turn a range string into a list of ranges (allows negatives, skips non-numerica)
function ParseRange(str)
local ranges = {}
local index
while true do
local a,b,x = str:find("(%-?%d+)") -- next number
local c,d,y,z = str:find("(%d+)%s*%-%s*(%d+)") -- next range
if a and ( c == nil or a < c ) then
x = tonumber(x)
ranges[#ranges+1] = { x, x }
index = b+1
elseif c then
y = tonumber(y)
z = tonumber(z)
ranges[#ranges+1] = { y, z }
index = d+1
else
break
end
str = str:sub(index+1,str:len())
end
return ranges
end
-- Thanks too Rav4pl
function ShuffleTable(tab) --randomize order of elements
local cnt=#tab
for i=1,cnt do
local r=math.random(cnt)
tab[i],tab[r]=tab[r],tab[i]
end
return tab
end
-- Parse the GUI Saver text options
function ParseWait(str)
local numpat = "(%-?%d*%.?%d*)"
local notnumpat = "[^%d-.]*"
local _,_,a,b = str:find(notnumpat..numpat..notnumpat..numpat)
if options.gui == '' then
pause,delay = 0,0
return
end
options["gui"] = guitext_template:format(delay,pause)
local errstate = a == '' or a == nil or b == '' or b == nil
if errstate ~= false then return
{"To give the GUI a chance to draw, you can wait a",
"certain amount of time after a fixed number of",
"mutations. Just specify either 0 seconds or 0 mutations",
"to go as fast as possible."
}
end
a,b = tonumber(a),tonumber(b) -- doesn't seem like this can crash on any input matching numpat
errstate = a == '' or a == nil or b == '' or b == nil -- Something a little goofy having to check this twice
if errstate ~= false then
return
{"To give the GUI a chance to draw, you can wait a",
"certain amount of time after a fixed number of",
"mutations. Just specify either 0 seconds or 0 mutations",
"to go as fast as possible."
}
end
if a < 0 or b < 0 then
return {"Looks like you entered a negative number.","Zero or greater only please."}
end
if a < 0.01 and b > 0 then
return {"Did I say zero? Actually, 0.01 seconds is the","shortest delay."}
end
-- all good
pause,delay = b,a
options["gui"] = guitext_template:format(delay,pause)
return
end
-- Turn list of ranges into a list of segments
function RangeToList(ranges)
if #ranges == 0 then ranges = {{1,NSeg}} end
local list = {}
local err = ""
for i = 1,#ranges do
local a,b = unpack(ranges[i])
if a < 1 or a > NSeg then return list,tostring(a).." is out of range." end
if b < 1 or b > NSeg then return list,tostring(b).." is out of range." end
for j = a,b do list[j] = true end
end
local list2 = {}
for u,v in pairs(list) do list2[#list2+1] = u end
table.sort(list2)
return list2,err
end
-- =================== Begin Main ==========================
print(title)
print (puzzle.GetName(),current.GetScore())
-- different classes of AAs
charged = "rkdeh"
philic = "qnst" -- excluding charged
phobic = "cmailvpg" -- excluding rings
rings = "fwy"
deny = ""
preserve = ""
all_aa = charged..philic..phobic..rings
repeat
rc = Menu()
if rc == 0 then return end
segs,err = RangeToList(ParseRange(options.range))
if err ~= "" then ErrorMessage(err,"Range Input") rc = -1 end
if options.custom ~= "" and options.custom:find("[^"..all_aa..all_aa:upper().."]") then
ErrorMessage("You gave something other than one of the 20 AAs.")
rc = -1
end
err = ParseWait(options.gui)
if err then ErrorMessage(err,"GUI Saver Delay Parameters") rc = -1 end
if rc == 2 then rc = Where() end
if rc == 3 then rc = About() end
if rc == 0 then return end -- cancels from child dialogs
until rc == 1
if delay == 0 or pause == 0 then
print ("No delay between mutations.")
else
print ("Waiting "..options.gui)
print ("Maximum possible mutation rate:",pause/delay,"Mutations/second")
end
if options.custom ~= "" then
aas = options.custom
else
aas = philic..phobic
if options.charged then aas = aas..charged end
if options.rings then aas = aas..rings end
end
-- okay add in the subtypes, for checking type later
philic = philic..charged
phobic = phobic..rings
-- build preserve and deny lists from the checkboxes
if options.keepc then preserve = preserve.."c" end
if options.keepp then preserve = preserve.."p" end
if options.keepg then preserve = preserve.."g" end
if not options.cok then deny = deny.."c" end
if not options.pok then deny = deny.."p" end
if not options.gok then deny = deny.."g" end
if not options.aok then deny = deny.."a" end
start = current.GetScore()
order = SortSegments(segs)
start_time = os.time()
n_mutations = 0
speedcheck_n = 100 -- every 100 mutations (5 segments?)
speedcheck_s = 120 -- every 2 minutes
lastspeedcheck_s = start_time
lastspeedcheck_n = 0
function PrintSpeedCheck(force)
local t = os.time() -- probably won't wrap around
local dt = t - start_time
if force or t-lastspeedcheck_s > speedcheck_s or n_mutations-lastspeedcheck_n > speedcheck_n then
lastspeedcheck_s = t
lastspeedcheck_n = n_mutations
print ("Average mutation rate:",n_mutations/dt,"mutations/second")
end
end
function Main()
for i = 1,#order do
recentbest.Save()
s = current.GetScore()
iSeg = order[i] -- Not Local
origaa = structure.GetAminoAcid(iSeg) -- Not Local
if preserve:find(origaa) == nil then -- skip keep
first_mutation = true -- first mutation this segment
for j =1,aas:len() do
aa = aas:sub(j,j)
hydrook = not options.keephydro or
(structure.IsHydrophobic(iSeg) and phobic:find(aa) ~= nil) or
(not structure.IsHydrophobic(iSeg) and philic:find(aa) ~= nil)
if deny:find(aa) == nil and hydrook then -- if not in deny and correct type, else skip
structure.SetAminoAcid(iSeg,aa)
n_mutations = n_mutations + 1
if first_mutation and options.removeaa == true and (deny:find(origaa) or aas:find(origaa) == nil) then
recentbest.Save() -- forget the original score of the unwanted aa, use this one
end
first_mutation = false
end
-- GUI pause here
if delay > 0 and pause > 0 and n_mutations > 0 and n_mutations % pause == 0 then
t = os.clock()
repeat
dt = os.clock() - t -- watch out, clock() can wrap to 0
until dt > delay or dt < 0
end
PrintSpeedCheck(false)
end
recentbest.Restore()
print( i, "of", #order, "Segment #", iSeg, origaa, "->", structure.GetAminoAcid(iSeg), "change", current.GetScore()-s )
else
print( i, "of", #order, "Segment #", iSeg, origaa, "skipped" )
end
end
end
function Mainline ()
local badreps = 0
for ii = 1, options.repeats do
local loopstart = current.GetScore()
print ( "begin run " .. ii .. ", score = " .. loopstart )
Main()
local loopend = current.GetScore()
print ( "end run " .. ii .. ", gain = " .. loopend - loopstart )
if loopend - loopstart < options.mingain then
badreps = badreps + 1
else
badreps = 0
end
if badreps >= options.maxbad then
break
end
order = ShuffleTable ( order )
end
end
function OnError(errmsg)
if string.find(errmsg,"Cancelled") then
print("User cancel. Loading recent best.")
recentbest.Restore()
else -- it's a real error, not a cancel
print(errmsg)
end
return errmsg
end
rc, err = xpcall(Mainline,OnError)
if rc == true then
print("Final score",current.GetScore(), current.GetScore()-start)
PrintSpeedCheck(true)
end