Icon representing a recipe

Recipe: Mutate No Wiggle 1.3.1 -- Brow42

created by brow42

Profile


Name
Mutate No Wiggle 1.3.1 -- Brow42
ID
100042
Shared with
Public
Parent
None
Children
Created on
November 30, 2015 at 08:48 AM UTC
Updated on
November 30, 2015 at 08:48 AM 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. 1.2.1 bans alanines. 1.3 re-allows p,g,a for loops.

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 * * 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 May 14 2015 Brow42 * * Allowed mutation of locked segments as requested by LociOling * * Version 1.2 May 18 2015 Brow 42 * * Added options to quickly select only hydrophillics for bonding * Added more options to select/exclude polar and aliphatic AAs * Rearranged menus to accomodate more options * More error checking, such as empty mutate lists * * Version 1.21 July 30 2015 * * Added option to eliminate alanines * * Version 1.3 Nov 23 2015 * * Added option to allow aa's in loops * Recoded so each SS has it's own allow/deny. * * Version 1.3.1 Nov 30 2015 * * Bug fix to aas array. --]] NSeg = structure.GetCount() title = "Brute Force Mutate No-Wiggle 1.3.1" 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, lockedok=true, philliconly=false, phobiconly=false, phillic=true, phobic=true, anyloop=false} -- 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) -- Menu Button Enums OKAY=1 CANCEL=0 SELECT=2 ABOUT=3 AASELECT=4 CLEAR=5 ALL=6 REPEAT=-1 function MainMenu() local d = dialog.CreateDialog(title) if options.custom == '' then d["label1"] = dialog.AddLabel("AA Selection shortcuts (or use button below).") d["philliconly"] = dialog.AddCheckbox("Hydrophilic (blue) only (no phobics).",options.philliconly) d["phobiconly"] = dialog.AddCheckbox("Hydrophobic (orange) only (no phillics).",options.phobiconly) else d["label1"] = dialog.AddLabel("AAs have been selected on the AA Allowed page.") end d["lable2"] = dialog.AddLabel("Don't change these AAs:") 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["removeaa"] = dialog.AddCheckbox("Remove AAs not marked allowed or don't change",options.removeaa) d["keephydro"] = dialog.AddCheckbox("Preserve Hydrophobicity",options.keephydro) d["gui"] = dialog.AddTextbox("GUI Saver: Wait",options.gui) d["ok"] = dialog.AddButton("OK",OKAY) d["cancel"] = dialog.AddButton("Cancel",CANCEL) d["where"] = dialog.AddButton("Select",SELECT) d["aaselect"] = dialog.AddButton("Allow AA",AASELECT) 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 if options.philliconly and options.phobiconly then ErrorMessage("Don't check both phillic-only and phobic-only!","AA Selection") rc = REPEAT end segs,err = RangeToList(ParseRange(options.range)) if err ~= "" then ErrorMessage(err,"Range Input") rc = REPEAT end err = ParseWait(options.gui) if err then ErrorMessage(err,"GUI Saver Delay Parameters") rc = REPEAT end -- change how allowed AA panel looks if options.philliconly then options.cok = false options.pok = false options.gok = false options.aok = false options.phobic = false options.rings = false end if options.phobiconly then options.charged = false options.phillic = false end return rc, MainMenu end function AASelect() local d = dialog.CreateDialog(title) if options.custom == '' then d["label1"] = dialog.AddLabel("Include these as allowed AA (previous page overrides):") 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["anyloop"] = dialog.AddCheckbox("But allow ala, pro, gly in loops anyways",options.anyloop) d["charged"] = dialog.AddCheckbox("charged hydrophillics",options.charged) d["phillic"] = dialog.AddCheckbox("other (polar) hydrophillics",options.phillic) d["rings"] = dialog.AddCheckbox("rings (aromatic) hydrophobics: phen, tryp, tyr",options.rings) d["phobic"] = dialog.AddCheckbox("other (aliphatic, branched, not c,p,g) hydrophobics", options.phobic) d["label2"] = dialog.AddLabel("Or, specify AA (overrides all checkboxes):") else dialog.AddLabels(d,{"AAs have been selected below.","Clear to see options again."}) end d["custom"] = dialog.AddTextbox("AAs",options.custom) d["ok"] = dialog.AddButton("Accept",REPEAT) if options.custom ~= '' then d["clear"] = dialog.AddButton("Clear",CLEAR) else d["all"] = dialog.AddButton("Allow All",ALL) end d["help"] = dialog.AddButton("About",ABOUT) local rc = dialog.Show(d) if rc == CANCEL then return rc end if rc == CLEAR then options.custom = '' return REPEAT, AASelect end if rc == ALL then options.cok = true options.pok = true options.gok = true options.aok = true options.charged = true options.rings = true options.phillic = true options.phobic = true options.phobiconly = false options.philliconly = false return REPEAT, MainMenu end 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 if options.custom ~= "" and options.custom:find("[^"..all_aa..all_aa:upper().."]") then ErrorMessage("You gave something other than one of the 20 AAs.") return REPEAT, AASelect end return rc, MainMenu 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 == CANCEL then return CANCEL end -- cancel script return REPEAT, AASelect end -- Turn selection or frozen into ranges and range string RETURN=REPEAT function Where() local d = dialog.CreateDialog(title) dialog.AddLabels(d, {"Have you selected segments to muate with","the selection or freeze tool?"}) d.select = dialog.AddButton("Selection",1) d.freeze = dialog.AddButton("Freeze",2) d.nope = dialog.AddButton("Nope",RETURN) -- 0 is window close (cancel script) local rc = dialog.Show(d) if rc == CANCEL or rc == RETURN then return rc, MainMenu 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 REPEAT, MainMenu end -- sort by score, also filter by mutability function SortSegments(segs) local scores = {} local keys = {} for j = 1,#segs do i = segs[j] ss = structure.GetSecondaryStructure(i):upper() if structure.IsMutable(i) and (options.lockedok or not structure.IsLocked(i) and ( ss=='E' or ss=='H' or ss=='L') ) 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 -- 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" all_aa = charged..philic..phobic..rings Menu = MainMenu -- user will see variable name aas = '' while aas == '' do repeat rc,Menu = Menu() if rc == CANCEL then print ("User exit.") return end if rc == SELECT then Menu = Where end if rc == ABOUT then Menu = About end if rc == AASELECT then Menu = AASelect end if rc == CANCEL then return end -- cancels from child dialogs until rc == OKAY if options.custom ~= "" then aas = options.custom else if options.phillic and not options.phobiconly then aas = aas..philic end if options.phobic and not options.philliconly then aas = aas..phobic end if options.charged and not options.phobiconly then aas = aas..charged end if options.rings and not options.philliconly then aas = aas..rings end if not options.cok then aas = aas:gsub('c','') end if not options.pok then aas = aas:gsub('p','') end if not options.gok then aas = aas:gsub('g','') end if not options.aok then aas = aas:gsub('a','') end end if aas == '' then ErrorMessage("You've excluded all the AAs from your mutation list!","AA Selection") end end aas = { H= aas, E=aas, L=aas } if options.custom == '' and options.anyloop then print ("Re-enabling p,g,a for loops if they were disabled in general...") if not options.pok then aas['L'] = aas['L']..'p' end if not options.gok then aas['L'] = aas['L']..'g' end if not options.aok then aas['L'] = aas['L']..'a' end end 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 print("Allowed AAs:") print (" Helix:",aas['H']:upper()) print ("Sheet:",aas['E']:upper()) print (" Loop:",aas['L']:upper()) -- okay add in the subtypes, for checking type later philic = philic..charged phobic = phobic..rings -- build preserve list from the checkboxes preserve = "" if options.keepc then preserve = preserve.."c" end if options.keepp then preserve = preserve.."p" end if options.keepg then preserve = preserve.."g" 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 AAs in keep list first_mutation = true -- first mutation this segment ss = structure.GetSecondaryStructure(iSeg):upper() for j =1,aas[ss]:len() do aa = aas[ss]: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 hydrook then -- if correct type, else skip structure.SetAminoAcid(iSeg,aa) n_mutations = n_mutations + 1 if first_mutation and options.removeaa == true and aas[ss]:find(origaa) == nil then -- if removing non-allowed AAs 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 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(Main,OnError) if rc == true then print("Final score",current.GetScore(), current.GetScore()-start) PrintSpeedCheck(true) end

Comments


brow42 Lv 1

Q: What's the point of this script?

A: Back when the fragment filter could not be turned off, it took seconds to do ANYTHING. Now that you can turn it off, you might be better off using a brute force mutator like Rav3n's or the mutations built into TvdL's EDRW. However, I think this is the only public script that lets you easily select what to mutate to. I specifically wrote it because I did not want to create or remove cysteines from a puzzle that had cystine bridges. The creation of prolines wherever I had a kink (that I would later want removed) was always an annoyance to me.

Q: This script freezes up my screen!

A: It seems Foldit's graphics can get overloaded if it has to do too much at once. It has to do a redraw every time you mutate. The new pause instructions at the bottom put the script into a loop that does nothing to let the screen catch up. But it runs fastest if you minimize, this turns the GUI off completely (unless you force it to always run in the options somewhere)

Q: If I turn on the delay loop, the script runs FASTER!

A: INORITE?

Q: The puzzle penalizes glycines. I told it not to mutate to glycines. It left them in!

A: To change the puzzle over to only using approved amino acids, click the Remove residues (not in mutation list or keep list) box. Note that your score will probably go down when it mutates these away.

Q: I clicked the remove box and the glycine is still there!

A: The script has to actually be able to mutate the segment. So, if you restricted the aminos to hydrophilic, but said preserve hydrophobicity, then it won't do anything. Also Foldit might itself prevent mutations via the infamous layer mutation filter. Or it might be locked. Or you listed it in the "don't change list".

Changelog:

  • Version 1.02 – Republished under new id
    *
  • Version 1.1 Oct 31 2014 Brow42
    *
  • Added pause to allow GUI to function. Added timing info
  • Added option to remove unwanted residues (from Googol30)
  • Added trap for User Cancel

brow42 Lv 1

It will now mutate locked segments if the devs have marked them as mutable. This is required for puzzle 1087. You do not need to update if you have LociOling's 1.1.1.

Skippysk8s Lv 1

I didn't open this script or puzzle yet, though I worked the dev prev versions. Note that if there is too little space you get a glycine – nothing else will fit. So you might want to check and see if the glycines are showing up across from large tail AAs, like the aromatics

brow42 Lv 1

I've added shortcut checkbox to the front panel to only mutate hydrophillics, for bonding. I've moved all the AA type selection checkboxes to another panel. The help button also appears on this panel. Panel contents will change if you used one of the shortcut checkboxes or used the custom AA list. Added some more user input error checking.

Skippysk8s is absolutely correct about the glycines. My script only optimizes one AA at a time, so if nothing else will fit, it will insert a glycine or alanine. The built-in mutator (and scripts that use it) can mutate in a cloud, changing more than one at a time. They can replace the big-small pair with two medium-size AAs.

You should exclude glycines on the AA selection page if you don't want any created.

Vredeman Lv 1

Thanks Brow

Thanks for responding so quickly to this request. You really have something good going on here :)

Porky

brow42 Lv 1

Thank you for catching that. I did a search to make sure there weren't any more instances of that mistake.