Icon representing a recipe

Recipe: Mutate No Wiggle 2.5

created by LociOiling

Profile


Name
Mutate No Wiggle 2.5
ID
102570
Shared with
Public
Parent
Mutate No Wiggle 2.1
Children
None
Created on
July 10, 2022 at 21:43 PM UTC
Updated on
July 10, 2022 at 21:43 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 2.4 enforces secondary structure design rules by default. Version 2.5 has a shorter main manu.

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 Version 1.6 2015/06/12 LociOiling * add "polar only" option for "ekqdhrtsn" * add model recipe stuff * violently restructure Version 1.7 2015/06/22 LociOiling * add "y" to "polar only" option Version 2.0 2015/07/01 LociOiling * remove timing logic Version 2.1 2016/09/02 - 2017/09/03 LociOiling * add option for "design rules", currently: + No CYS residues anywhere + No GLY, ALA residues in sheets + No GLY, ALA, SER, THR in helices * add more complete printing of options Version 2.2 2017/12/07 LociOiling * add ligand option, restrict to sphere around ligand * add standard Timo ligand logic, changed NSeg to segCnt2 Version 2.3 2019/09/27-2020/12/10 LociOiling * add dialog for fine-tuning design rules * revived 2020: add metric support (migrate chunquePoint to CPT) * never released Version 2.4 2021/01/10, 2022/07/07 LociOiling * selection options * update structure rules * update to latest Ident Version 2.5 2022/07/08 LociOiling * fix the too tall menu thing ]]-- -- -- globals section -- -- Recipe = "Mutate No Wiggle" Version = "2.5" ReVersion = Recipe .. " " .. Version -- different classes of AAs charged = "rkdeh" philic = "qnst" -- excluding charged phobic = "cmailvpg" -- excluding rings rings = "fwy" polars = "ekqdhrtsny" -- polars only all_aa = charged .. philic .. phobic .. rings -- -- string things -- -- phobic - string containing hydrophobic AAs -- philic - string containing hydrophilic AAs -- -- deny - string containing disallowed AAs -- preserve - string containing AAs to be preserved (not changed) -- aas - string containing valid AAs according to dialog -- deny = "" preserve = "" aas = "" -- -- able tables -- Phobes = { c = { "cysteine", }, m = { "methionine", }, a = { "alanine", }, i = { "isoleucine", }, l = { "leucine", }, v = { "valine", }, p = { "proline", }, g = { "glycine", }, f = { "phenylalanine", }, w = { "tryptophan", }, y = { "tyrosine", }, } Phils = { q = { "glutamine", }, n = { "asparagine", }, s = { "serine", }, t = { "threonine", }, r = { "arginine", }, k = { "lysine", }, d = { "aspartate", }, e = { "glutamate", }, h = { "histidine", }, } Deny = {} Preserve = {} AAs = {} -- -- fed up with all these rules -- -- -- Illegal is a list of all possible banned AAs, -- needed to make sure the AAs come out in a -- predictable order in the dialog -- Illegal = { "c", "p", "g", "a", "s", "t", } LoopRule = { c = false, p = true, } HelixRule = { c = false, p = true, g = true, a = true, s = true, t = true, } SheetRule = { c = false, p = true, g = true, a = true, } -- -- AANames is a table containing the user-friendly versions of each AA -- AANames = { w = { "tryptophan", "TRP", }, g = { "glycine", "GLY", }, a = { "alanine", "ALA", }, c = { "cysteine", "CYS", }, v = { "valine", "VAL", }, l = { "leucine", "LEU", }, i = { "isoleucine", "ILE", }, m = { "methionine", "MET", }, p = { "proline", "PRO", }, f = { "phenylalanine", "PHE", }, y = { "tyrosine", "TYR", }, r = { "arginine", "ARG", }, s = { "serine", "SER", }, t = { "threonine", "THR", }, n = { "asparagine", "ASN", }, d = { "aspartate", "ASP", }, q = { "glutamine", "GLN", }, e = { "glutamate", "GLU", }, h = { "histidine", "HIS", }, k = { "lysine", "LYS", }, } options = { desrules = true, cok = true, pok = true, gok = true, aok = true, keepc = false, keepp = false, keepg = false, khydro = false, charged = true, rings = true, polar = false, custom = "", range = "", removeaa = false, locked = true, repeats = 3, maxbad = 2, mingain = 2, pause = 1000, delay = 0.1, debug = false, } -- -- timekeeping -- start_time = os.time() n_mutations = 0 speedcheck_n = 100 -- every 100 mutations (5 segments?) speedcheck_s = 300 -- every 5 minutes lastspeedcheck_s = start_time lastspeedcheck_n = 0 -- -- ligand and sphere -- segCnt = 0 segCnt2 = 0 esfera = 8.5 -- sphere size ligmode = false -- process only segments within ligand sphere -- -- tables of primary and secondary structure -- originalAA = {} originalSS = {} selecteds = 0 frozens = 0 -- -- score reporting -- startChunque = nil -- initial chunquePoint -- -- end of globals section -- function round ( x )--cut all afer 3-rd place return x - x % 0.001 end CPT = { -- CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT-- --[[ CPT package version 0.8 For a given section of code, the CPT package tracks the time used and the gain produced. The package provides two routines, CPT:start and CPT:stop, to be used at the start and end of the code section. Calls may also be nested. A second call to begin in effect creates a new nested level. A remix recipe is a typical example of nesting chunque point calls. The recipe would call CPT:start somewhere near the start. Such a recipe might remix multiple lengths. The recipe would call CPT:start at the start of each length. Within a length, the recipe might remix multiple ranges of segments. For each range, the recipe would call CPT:start at the start, and CPT:stop at the end of the remix process. The CPT:stop would report the time and gain for that range of segments. When all ranges of segments are remixed, the length is complete, and the recipe would call CPT:stop. The CPT:stop would report the time and gain for the entire length. When all lengths are complete, the recipe would call CPT:stop one last time, reporting the time and gain for the entire recipe. Each CPT:start returns a "chunque", a table containing information such as the starting time and score. The chunque is used to link calls. For example, the chunque returned by a CPT:start call should be used on the corresponding CPT:stop call to report results. For nested calls, the chunque from one CPT:start call is passed to the nested CPT:start. In the remix example, to chunque from the initial CPT:start call is passed to the CPT:start calls for each length. The chunques from the length-level beginChunques are in turn passed to the beginChunques for each range of segments. For long-running recipes, the package also prints status reports at intervals of an hour or more. These reports show the total time elapsed and the gain since the start of the recipe. The first CPT:start call sets the internal "root chunque" used to calculate the reports. See this wiki page for an explanation of how this package is constructed: https://foldit.fandom.com/wiki/Lua_packaging_for_Foldit Each chunque contains the following fields: prefix - text identifying the chunque, output in messages score - score returned by the score function bonus - bonus of chunque, from score function runDate - date of chunque, from os.date runTime - time of chunque, from os.time version history --------------- 0.5 - LociOiling - 2019/11/03 - 2019/12/08 * rename to CPT (was chunquePoint) * align with new packaging strategy * add internal documentation * eliminate CSV output option * include bonus detection and tracking * convert chunques to named fields * remove runClock (os.clock), never used * eliminate pose parm, always use "current" * change external function names to "start" and "stop" * eliminate printChunque, add fmtGain for gain formatting * eliminate second line of start message * include gain or loss for each condition (filter) in status reports 0.6 - LociOiling - 2019/12/11 * fixed format of condition reporting in CPT:stop 0.7 - LociOiling - 2020/02/18 * handle "MoveCountLimit" and similar binary conditions/filters 0.8a- LociOiling - 2020/11/11 * track metrics * include metric bonus in overall score ]]-- segCnt = 0, -- segment count repInterval = 60 * 60, -- seconds between status reports lastStatusRep = 0, -- time of last status report rootChunque = nil, -- chunque to use as basis for status reports filters = false, -- whether filters active fnames = {}, -- filter names metrics = false, -- whether metrics active mnames = {}, -- metric names density = false, -- whether this is a density puzzle -- -- start - start tracking time, points, and bonus -- -- the start and stop functions are the external interface -- -- parameters: -- -- startChunque - if nil, set the internal rootChunque -- if not nil, nested call, report the gain -- prefix - text to identify the chunque -- scoreFunc - (optional) function to use for score checking -- -- calls self:newChunque to create a new chunque -- -- returns the new chunque -- start = function ( self, startChunque, prefix, scoreFunc ) -- -- initialize if startChunque omitted -- if startChunque == nil then self.segCnt = structure.GetCount () -- -- determine if filters apply -- local fnames = filter.GetNames () if #fnames > 0 then self.filters = true self.fnames = fnames end -- -- determine if metrics apply -- local mnames = metric.GetNames () if #mnames > 0 then self.metrics = true self.mnames = mnames end -- -- check a few segments to determine if this is an ED puzzle -- if current.GetSegmentEnergySubscore ( 1, "Density" ) ~= 0 or current.GetSegmentEnergySubscore ( self.segCnt / 2, "Density" ) ~= 0 or current.GetSegmentEnergySubscore ( self.segCnt, "Density" ) ~= 0 then self.density = true end end -- -- create the chunque -- local chunque = self:newChunque ( prefix, scoreFunc ) -- -- set the root chunque if needed -- if startChunque == nil then self.rootChunque = chunque self.lastStatusRep = os.time () -- last status message end local sscr = self:fmtScore ( chunque ) if startChunque == nil then -- no gain yet print ( chunque.prefix .. ", score: " .. sscr .. ", " .. chunque.runDate ) else -- report gain relative to start chunque local gstr, hours, pph = self:fmtGain ( startChunque, chunque ) print ( chunque.prefix .. ", score: " .. sscr .. ", total gain: " .. gstr .. ", " .. chunque.runDate ) end return chunque end, -- -- stop - stop tracking time, points, and bonus, then report result -- -- startChunque - the chunque returned by the corresponding CPT:start call -- prefix - text to identify the chunque -- scoreFunc - (optional) function to use for score checking -- stop = function ( self, startChunque, prefix, scoreFunc ) local chunque = self:newChunque ( prefix, scoreFunc ) self:chunqueGain ( startChunque, chunque ) -- -- if print overall progress at hourly interval -- local Clocktime = os.difftime ( os.time (), self.lastStatusRep ) if Clocktime > self.repInterval then self:chunqueReport ( self.rootChunque, chunque, "--- " ) -- bonus/density since start self.lastStatusRep = os.time () else -- report just bonus changes in this chunque self:prtExtra ( startChunque, chunque, prefix .. ", " ) end end, -- -- scorePlus - determine score and bonus from conditions/filters, -- and also metrics, -- used if user doesnt' provide a score function -- -- turn filters on, -- use current.GetEnergyScore to determine score, -- filter.GetBonusTotal to determine bonus for conditions, -- and metric.GetBonusTotal to determine bonus for metrics -- restore previous status of filters -- scorePlus = function ( self ) local filts = behavior.GetFiltersDisabled () -- remember setting behavior.SetFiltersDisabled ( false ) -- always enable filters local score = current.GetEnergyScore () local bonus = filter.GetBonusTotal () if bonus == nil then bonus = 0 end local mbonus = metric.GetBonusTotal () if mbonus == nil then mbonus = 0 end behavior.SetFiltersDisabled ( filts ) -- restore setting -- -- include metric bonus in overall score -- score = score + mbonus return score, bonus, mbonus end, -- -- get filter scores -- getFilters = function ( self, fnames ) local filttab = {} for ii = 1, #fnames do local hasbonus = filter.HasBonus ( fnames [ ii ] ) local score = nil if hasbonus then score = filter.GetBonus ( fnames [ ii ] ) else score = nil end local satisfied = filter.ConditionSatisfied ( fnames [ ii ] ) filttab [ #filttab + 1 ] = { name = fnames [ ii ], hasbonus = hasbonus, score = score, satisfied = satisfied, } end return filttab end, -- -- get metric scores -- getMetrics = function ( self, mnames ) local metrtab = {} for ii = 1, #mnames do local score = nil score = metric.GetBonus ( mnames [ ii ] ) metrtab [ #metrtab + 1 ] = { name = mnames [ ii ], score = score, } end return metrtab end, -- -- get total density scores across all segments -- getDensity = function ( self ) local density = 0 for ii = 1, self.segCnt do density = density + current.GetSegmentEnergySubscore ( ii, "Density" ) end return density end, -- -- newChunque - create a new chunque -- -- used internally by CPT:start and CPT:stop -- newChunque = function ( self, prefix, scoreFunc ) if prefix == nil then prefix = "" end if scoreFunc == nil then scoreFunc = self.scorePlus end local score = 0 local bonus = 0 local mbonus = 0 score, bonus, mbonus = scoreFunc () if score == nil then score = 0 end if bonus == nil then bonus = 0 end if mbonus == nil then mbonus = 0 end local runDate local runTime runDate = os.date () runTime = os.time () local ch = { prefix = prefix, score = score, bonus = bonus, mbonus = mbonus, runDate = runDate, runTime = runTime } if self.filters then ch.filters = self:getFilters ( self.fnames ) end if self.metrics then ch.metrics = self:getMetrics ( self.mnames ) end if self.density then ch.density = self:getDensity () end return ch end, -- -- fmtScore - return formatted score, showing bonus, metrics, and ED, if applicable -- fmtScore = function ( self, chunque ) local scout = self:round ( chunque.score ) if self.filters then scout = scout .. " (Bonus: " .. self:round ( chunque.bonus ) .. ")" end if self.metrics then scout = scout .. " (Metrics: " .. self:round ( chunque.mbonus ) .. ")" end if self.density then scout = scout .. " (ED: " .. self:round ( chunque.density ) .. ")" end return scout end, -- -- fmtGain - return formatted gain, hours, and points per hour -- fmtGain = function ( self, startChunque, stopChunque ) local Clocktime = os.difftime ( stopChunque.runTime , startChunque.runTime ) local gain = self:round ( stopChunque.score - startChunque.score ) local bgain = self:round ( stopChunque.bonus - startChunque.bonus ) local mgain = self:round ( stopChunque.mbonus - startChunque.mbonus ) local hours = Clocktime / ( 60 * 60 ) if hours == 0 then hours = 0.0036 -- nothing happens in no time at all end local pph = gain / hours local gstr = gain if self.filters then local bgstr = "" if bgain > 0 then bgstr = "+" .. bgain elseif bgain == 0 then bgstr = "0" else bgstr = tostring ( bgain ) end gstr = gstr .. " (Bonus: " .. bgstr .. ")" end if self.metrics then local mgstr = "" if mgain > 0 then mgstr = "+" .. mgain elseif mgain == 0 then mgstr = "0" else mgstr = tostring ( mgain ) end gstr = gstr .. " (Metrics: " .. mgstr .. ")" end if self.density then local dgain = self:round ( stopChunque.density - startChunque.density ) local dgstr = "" if dgain > 0 then dgstr = "+" .. dgain elseif dgain == 0 then dgstr = "0" else dgstr = tostring ( dgain ) end gstr = gstr .. " (ED: " .. dgstr .. ")" end return gstr, hours, pph end, -- -- chunqueGain - print gain and time for a chunque -- -- used internally -- chunqueGain = function ( self, startChunque, stopChunque ) local sstr = self:fmtScore ( stopChunque ) local gstr, hours, pph = self:fmtGain ( startChunque, stopChunque ) print ( stopChunque.prefix .. ", score: " .. sstr .. ", gain: " .. gstr .. ", " .. stopChunque.runDate ) print ( stopChunque.prefix .. ", hours: " .. self:round ( hours ) .. ", points per hour: " .. self:round ( pph ) ) end, -- -- chunqueReport - issue periodic progress report -- -- used internally, similar to chunqueGain -- chunqueReport = function ( self, startChunque, stopChunque, prefix ) local gstr, hours, pph = self:fmtGain ( startChunque, stopChunque ) local statuz = "--- status: total hours = " .. self:round ( hours ) .. ", gain = " .. gstr .. ", points per hour = " .. self:round ( pph ) if self.lastPPH ~= nil then statuz = statuz .. ", change = " .. self:round ( pph - self.lastPPH ) .. " points per hour" end self.lastPPH = pph print ( statuz ) self:prtExtra ( startChunque, stopChunque, "--- " ) end, prtExtra = function ( self, startChunque, stopChunque, prefix ) if prefix == nil then prefix = "" end -- -- detailed report on filters -- if self.filters then local chg = 0 local condstr = "" for ii = 1, #stopChunque.filters do if stopChunque.filters [ ii ].hasbonus then local delta = stopChunque.filters [ ii ].score - startChunque.filters [ ii ].score if delta ~= 0 then chg = chg + 1 condstr = condstr .. prefix .. "condition \'" .. stopChunque.filters [ ii ].name .. "\', bonus = " .. self:round ( stopChunque.filters [ ii ].score ) .. ", change = " .. self:round ( delta ) .. "\n" end else if stopChunque.filters [ ii ].satisfied ~= startChunque.filters [ ii ].satisfied then chg = chg + 1 condstr = condstr .. prefix .. "condition \'" .. stopChunque.filters [ ii ].name .. "\', satisfied = " .. tostring ( stopChunque.filters [ ii ].satisfied ) .. ", previously satisfied = " .. tostring ( startChunque.filters [ ii ].satisfied ) .. "\n" end end end if chg > 0 then print ( condstr ) end end -- -- detailed report on metrics -- if self.metrics then local chg = 0 local condstr = "" for ii = 1, #stopChunque.metrics do local delta = stopChunque.metrics [ ii ].score - startChunque.metrics [ ii ].score if delta ~= 0 then chg = chg + 1 condstr = condstr .. prefix .. "metric \'" .. stopChunque.metrics [ ii ].name .. "\', bonus = " .. self:round ( stopChunque.metrics [ ii ].score ) .. ", change = " .. self:round ( delta ) .. "\n" end end if chg > 0 then print ( condstr ) end end -- -- overall change in density already reported, nothing to add here -- end, round = function ( self, ii ) return ii - ii % 0.001 end, } -- CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT--CPT-- function Menu() local d = dialog.CreateDialog ( ReVersion ) d [ "desrules" ] = dialog.AddCheckbox ( "Enforce design rules", options.desrules ) d [ "l2a" ] = dialog.AddLabel ( "If selected, Foldit design rules apply," ) d [ "l2c" ] = dialog.AddLabel ( "See Rules for details." ) 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 [ "debug" ] = dialog.AddCheckbox ( "Verbose output", options.debug ) d.l01 = dialog.AddLabel ( "GUI Saver options" ) d.l02 = dialog.AddLabel ( "after specified mutations, a brief pause" ) d [ "pause" ] = dialog.AddSlider ( "mutations", options.pause, 0, 1000, 0 ) d [ "delay" ] = dialog.AddSlider ( "delay length", options.delay, 0.01, 0.25, 2 ) d [ "ok" ] = dialog.AddButton ( "OK", 1 ) d [ "cancel" ] = dialog.AddButton ( "Cancel", 0 ) d [ "aaopts" ] = dialog.AddButton ( "AAs", 6 ) d [ "rules" ] = dialog.AddButton ( "Rules", 5 ) d [ "where" ] = dialog.AddButton ( "Select", 2 ) if segCnt ~= segCnt2 then d [ "ligand" ] = dialog.AddButton ( "Ligand", 3 ) end d [ "help" ] = dialog.AddButton ( "About", 4 ) local rc = dialog.Show(d) for u,v in pairs(options) do -- -- using scoping so I don't have to pass any variable names -- -- pcall used because this method would throw errors for -- the label entries, which don't have a .value -- pcall ( function () options [ u ] = d [ u ].value end ) end return rc end -- -- Begin New Dialog Library Functions -- -- -- dialog.AddLabels - 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 -- -- dialog.CreateMessageBox - 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 -- -- dialog.ShowMessageBox - Display a dialog box -- function dialog.ShowMessageBox(msg,title,buttontext1,buttontext0,buttontext2) return dialog.Show(dialog.CreateMessageBox(msg,title,buttontext1,buttontext0,buttontext2)) end -- -- ErrorMessage: 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 New Dialog Library Functions -- function Rules () local d = dialog.CreateDialog ( ReVersion ) d [ "over0" ] = dialog.AddLabel ( "Foldit design rules" ) d [ "over1" ] = dialog.AddLabel ( "see the SS Design objective for the current puzzle" ) d [ "over9" ] = dialog.AddLabel ( "" ) d [ "L_lab" ] = dialog.AddLabel ( "Loop rules:" ) for ii = 1, #Illegal do local kk = Illegal [ ii ] local vv = LoopRule [ kk ] if vv ~= nil then d [ "L_" .. kk ] = dialog.AddCheckbox ( AANames [ kk ] [ 1 ] .. " (" .. kk .. ")", vv ) end end d [ "E_lab" ] = dialog.AddLabel ( "Sheet rules:" ) for ii = 1, #Illegal do local kk = Illegal [ ii ] local vv = SheetRule [ kk ] if vv ~= nil then d [ "E_" .. kk ] = dialog.AddCheckbox ( AANames [ kk ] [ 1 ] .. " (" .. kk .. ")", vv ) end end d [ "H_lab" ] = dialog.AddLabel ( "Helix rules:" ) for ii = 1, #Illegal do local kk = Illegal [ ii ] local vv = HelixRule [ kk ] if vv ~= nil then d [ "H_" .. kk ] = dialog.AddCheckbox ( AANames [ kk ] [ 1 ] .. " (" .. kk .. ")", vv ) end end d [ "capt1" ] = dialog.AddLabel ( "" ) d [ "capt2" ] = dialog.AddLabel ( "checked means allowed, unchecked not allowed" ) d.OK = dialog.AddButton ( "OK", 1 ) d.Cancel = dialog.AddButton ( "Cancel", 0 ) local rc = dialog.Show ( d ) if rc == 1 then for kk, vv in pairs ( LoopRule ) do LoopRule [ kk ] = d [ "L_" .. kk ].value end for kk, vv in pairs ( SheetRule ) do SheetRule [ kk ] = d [ "E_" .. kk ].value end for kk, vv in pairs ( HelixRule ) do HelixRule [ kk ] = d [ "H_" .. kk ].value end end return 2 end 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." }, ReVersion, "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 ( ReVersion ) dialog.AddLabels ( d, { "Have you selected segments to mutate with", "the selection or freeze tool?" } ) d.selz = dialog.AddLabel ( selecteds .. " segments selected" ) d.frzz = dialog.AddLabel ( frozens .. " segments frozen" ) 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 range local list = {} for ii = 1, segCnt2 do if UserSelect ( ii ) then if appending then range [ 2 ] = ii else range = { ii, ii } list [ #list + 1 ] = range appending = true end else if appending then appending = false end end end for ii = 1, #list do if list [ ii ] [ 1 ] == list [ ii ] [ 2 ] then list [ ii ] = tostring ( list [ ii ] [ 1 ] ) else list [ ii ] = tostring ( list [ ii ] [ 1 ] ) .. "-" .. tostring ( list [ ii ] [ 2 ] ) end end options.range = table.concat ( list, " " ) return 2 end -- -- special ligand mode -- function Ligand () local d = dialog.CreateDialog ( ReVersion ) d.ligandseg = dialog.AddLabel ( "Ligand segment: " .. segCnt ) d.ligandmode = dialog.AddLabel ( "Mutate within sphere around the ligand" ) d.esfera = dialog.AddSlider ( "Sphere size", esfera, 2, 15, 1 ) d.ligand = dialog.AddButton ( "OK", 1 ) d.cancel = dialog.AddButton ( "Cancel", 0 ) -- 0 is window close (cancel script) local rc = dialog.Show ( d ) if rc == 0 then ligmode = false return rc end ligmode = true esfera = d.esfera.value local slist = {} for ii = 1, segCnt2 do if structure.GetDistance ( ii, segCnt ) <= esfera then slist [ #slist + 1 ] = true else slist [ #slist + 1 ] = false end end local appending = false local range local list = {} for ii = 1, #slist do if slist [ ii ] then if appending then range [ 2 ] = ii else range = { ii, ii } list [ #list + 1 ] = range appending = true end else if appending then appending = false end end end for ii = 1, #list do if list [ ii ] [ 1 ] == list [ ii ] [ 2 ] then list [ ii ] = tostring ( list [ ii ] [ 1 ] ) else list [ ii ] = tostring ( list [ ii ] [ 1 ] ) .. "-" .. tostring ( list [ ii ] [ 2 ] ) end end options.range = table.concat ( list, " " ) return 2 end function AAOpts() local d = dialog.CreateDialog ( ReVersion ) d [ "l1" ] = 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: rkdeh", options.charged ) d [ "rings" ] = dialog.AddCheckbox ( "rings: fwy",options.rings ) d [ "polar" ] = dialog.AddCheckbox ( "polar: ekqdhrtsny (overrides above)", options.polar ) d [ "l3" ] = dialog.AddLabel ( "Custom AA list (overrides above)" ) d [ "custom" ] = dialog.AddTextbox ( "custom AAs", options.custom ) d [ "l4" ] = 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 [ "khydro" ] = dialog.AddCheckbox ( "Preserve Hydrophobicity", options.khydro ) d [ "removeaa" ] = dialog.AddCheckbox ( "Remove AAs not in either list above (if possible )", options.removeaa) d [ "ok" ] = dialog.AddButton ( "OK", 1 ) d [ "cancel" ] = dialog.AddButton ( "Cancel", 0 ) local rc = dialog.Show(d) if rc == 1 then for u,v in pairs(options) do -- -- using scoping so I don't have to pass any variable names -- -- pcall used because this method would throw errors for -- the label entries, which don't have a .value -- pcall ( function () options [ u ] = d [ u ].value end ) end end return 6 end -- -- sort by score, also filter by mutability -- function SortSegments ( segs ) local scores = {} local keys = {} for jj = 1,#segs do local ii = segs [ jj ] if structure.IsMutable ( ii ) and ( options.locked or not structure.IsLocked ( ii ) ) then local ss = current.GetSegmentEnergyScore ( ii ) - current.GetSegmentEnergySubscore ( ii, 'reference' ) scores [ ss ] = ii -- scores map to segments keys [ #keys + 1 ] = ss -- keys are scores end end table.sort(keys) for ii = 1, #keys do keys [ ii ] = scores [ keys [ ii ] ] -- replace score with segment end return keys end -- -- Turn a range string into a list of ranges (allows negatives, skips non-numeric) -- 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 -- -- Turn list of ranges into a list of segments -- function RangeToList(ranges) if #ranges == 0 then ranges = {{1,segCnt2}} end local list = {} local err = "" for i = 1,#ranges do local a,b = unpack(ranges[i]) if a < 1 or a > segCnt2 then return list,tostring(a).." is out of range." end if b < 1 or b > segCnt2 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 function PrintSpeedCheck ( force, msgx ) if msgx == nil then msgx = "" end 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 ( msgx .. "mutation rate: " .. round ( n_mutations / dt ) .. " mutations/second" ) end end function DesignOK ( aa, iSeg, ss ) if ss == "L" then local dok = LoopRule [ aa ] if dok ~= nil and not dok then local errmsg = "\"" .. aa .. "\"" .. "not allowed in loop" return false, errmsg end elseif ss == "E" then local dok = SheetRule [ aa ] if dok ~= nil and not dok then local errmsg = "\"" .. aa .. "\"" .. "not allowed in sheet" return false, errmsg end elseif ss == "H" then local dok = HelixRule [ aa ] if dok ~= nil and not dok then local errmsg = "\"" .. aa .. "\"" .. "not allowed in helix" return false, errmsg end end return true end function MNW ( order ) local gain = 0 local s = 0 local iSeg = 0 local origaa = "" local sstruct = "" for ii = 1, #order do recentbest.Save () s = current.GetScore () iSeg = order [ ii ] local segPnt = "segment " .. iSeg .. " (" .. ii .. "/" .. #order .. ") " if options.debug then print ( "processing " .. segPnt ) end -- -- string things -- -- phobic - string containing hydrophobic AAs -- philic - string containing hydrophilic AAs -- -- deny - string containing disallowed AAs -- preserve - string containing AAs to be preserved (not changed) -- aas - string containing valid AAs according to dialog -- origaa = originalAA [ iSeg ] sstruct = originalSS [ iSeg ] local skipper = nil if Preserve [ origaa ] ~= nil then skipper = AANames [ origaa ] [ 1 ] .. " on preserve list" end if skipper == nil then local first_mutation = true -- first mutation this segment -- -- try each aa in the allowed list -- for aa, aan in pairs ( AAs ) do if options.debug then print ( "trying mutation to " .. AANames [ aa ] [ 1 ] ) end local skipaa = nil if Deny [ aa ] ~= nil then skipaa = "on deny list" end if skipaa == nil and options.khydro then if structure.IsHydrophobic ( iSeg ) then if Phobes [ aa ] == nil then skipaa = "not hydrophobic to preserve " .. AANames [ origaa ] [ 1 ] end else if Phils [ aa ] == nil then skipaa = "not hydrophilic to preserve " .. AANames [ origaa ] [ 1 ] end end end if skipaa == nil and options.desrules then local good, errmsg = DesignOK ( aa, iSeg, sstruct ) if not good then skipaa = errmsg end end -- -- if checks ok, mutate to this AA -- if skipaa == nil then structure.SetAminoAcid ( iSeg, aa ) n_mutations = n_mutations + 1 if first_mutation and options.removeaa == true and ( Deny [ origaa ] or Deny [ origaa ] == nil ) then -- -- forget the original score of the unwanted aa, use this one -- recentbest.Save () end first_mutation = false else if options.debug then print ( segPnt .. "mutation to " .. AANames [ aa ] [ 1 ] .. " skipped, " .. skipaa ) end end --[[ if options.delay > 0 and options.pause > 0 and n_mutations > 0 and n_mutations % options.pause == 0 then t = os.clock() repeat dt = os.clock() - t -- watch out, clock() can wrap to 0 until dt > options.delay or dt < 0 end PrintSpeedCheck ( false, segPnt ) ]]-- end recentbest.Restore() gain = current.GetScore () - s local newaa = structure.GetAminoAcid ( iSeg ) if gain > 0 then gainstr = round ( gain ) if tonumber ( gainstr ) == 0 then gainstr = "tiny" end print ( segPnt .. "\'" .. origaa .. "\' -> \'" .. newaa .. "\', change = " .. gainstr ) originalAA [ iSeg ] = newaa end else if options.debug then print ( segPnt .. "skipped, " .. skipper ) end end -- -- test: move the speedcheck to the end of the segment -- if options.delay > 0 and options.pause > 0 and n_mutations > 0 and n_mutations % options.pause == 0 then t = os.clock() repeat dt = os.clock() - t -- watch out, clock() can wrap to 0 until dt > options.delay or dt < 0 end PrintSpeedCheck ( false, segPnt ) end end function GetOptions () repeat rc = Menu() if rc == 0 then return rc end segs, err = RangeToList ( ParseRange ( options.range ) ) if err ~= "" then print ( "bad range" ) ErrorMessage ( err, "Range Input" ) rc = -1 end if options.custom ~= "" and options.custom:find ( "[^" .. all_aa..all_aa:upper () .. "]" ) then print ( "bad aa code" ) ErrorMessage ( "Invalid amino acid code specified." ) rc = -1 end if rc == 2 then rc = Where () end if rc == 3 then rc = Ligand () end if rc == 4 then rc = About () end if rc == 5 then rc = Rules () end if rc == 6 then rc = AAOpts () end until rc == 1 return rc end function LigandCheck () segCnt = structure.GetCount() segCnt2 = segCnt while structure.GetSecondaryStructure ( segCnt2 ) == "M" do segCnt2 = segCnt2 - 1 end print ( "initial segment count: " .. segCnt ) if segCnt ~= segCnt2 then options.desrules = false print ( "ligand found, adjusted segment count: " .. segCnt2 ) end end -- -- Ident - print identifying information at beginning and end of recipe -- -- slugline - first line to print - normally recipe name and version -- -- v0.2 - LociOiling - 20191118 -- + streamline the format -- + user.GetGroupName not working, remove -- v0.3 - LociOiling - 20201012 -- + combine user name and rank in one line -- + don't report on group at all -- function Ident ( slugline ) local function round ( ii ) return ii - ii % 0.001 end print ( slugline ) print ( "Puzzle: " .. puzzle.GetName () .. " (" .. puzzle.GetPuzzleID () .. ")" ) print ( "Track: " .. ui.GetTrackName () ) --[[ user.GetGroupName not working local gname = user.GetGroupName () if gname ~= nil then gname = " (" .. gname .. ")" else gname = "" end print ( "User: " .. user.GetPlayerName () .. gname ) ]]-- local luser = user.GetPlayerName () local scoretype = scoreboard.GetScoreType () local scort = "" if scoretype == 0 then scort = "soloist" elseif scoretype == 1 then scort = "evolver" elseif scoretype == 2 then scort = "all hands" elseif scoretype == 3 then scort = "no score" else scort = "unknown/error" end print ( "User: " .. luser .. " (" .. scort .. " #" .. scoreboard.GetRank ( scoretype ) .. ")" ) --[[ -- -- report group rank/score -- local sGroup = scoreboard.GetGroupScore () if sGroup ~= nil then print ( "Group rank / score: " .. scoreboard.GetGroupRank () .. " / " .. round ( 10 * ( 800 - sGroup ) ) ) end ]]-- end function Initialize () LigandCheck () for ii = 1, segCnt do originalAA [ #originalAA + 1 ] = structure.GetAminoAcid ( ii ) originalSS [ #originalSS + 1 ] = structure.GetSecondaryStructure ( ii ) if selection.IsSelected ( ii ) then selecteds = selecteds + 1 end if freeze.IsFrozen ( ii ) then frozens = frozens + 1 end end end function main () Ident ( ReVersion ) Initialize () if GetOptions () == 0 then return end print ( "--" ) -- print ( "options:" ) print ( "enforce design rules: " .. tostring ( options.desrules ) ) print ( "preserve hydrophobicity: " .. tostring ( options.khydro ) ) print ( "include locked segments: " .. tostring ( options.locked ) ) if options.custom ~= "" then print ( "custom AA list specified" ) aas = options.custom elseif options.polar then aas = polars else aas = philic .. phobic if options.charged then aas = aas .. charged end if options.rings then aas = aas .. rings end end print ( "selected " .. aas:len() .. " amino acids: " .. aas ) for jj = 1, aas:len() do local aa = aas:sub ( jj, jj ) AAs [ aa ] = AANames [ aa ] [ 1 ] end if options.debug then print ( "selected AAs" ) for uu, vv in pairs ( AAs ) do print ( uu .. " = " .. vv ) 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 preserve:len() > 0 then print ( "preserved amino acids: " .. preserve ) end for jj = 1, preserve:len() do local aa = preserve:sub ( jjAANames [ aa ] [ 1 ], jj ) Preserve [ aa ] = AANames [ aa ] [ 1 ] end if options.debug and #Preserve > 0 then print ( "preserved AAs" ) for uu, vv in pairs ( Preserve ) do print ( uu .. " = " .. vv ) end 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 if deny:len() > 0 then print ( "denied amino acids:" .. deny ) end for jj = 1, deny:len() do local aa = deny:sub ( jj, jj ) Deny [ aa ] = AANames [ aa ] [ 1 ] end if options.debug and #Deny > 0 then print ( "denied AAs" ) for uu, vv in pairs ( Deny ) do print ( uu .. " = " .. vv ) end end if ligmode then print ( "ligand mode: ligand segment " .. segCnt .. ", sphere size = " .. esfera ) end if type ( options.range ) == "table" then if #options.range > 0 then print ( "segment range = " ) for ii = 1, #options.range do if type ( options.range [ ii ] ) == "string" or type ( options.range [ ii ] ) == "number" then print ( options.range [ ii ] ) elseif type ( options.range [ ii ] ) == "table" then for jj = 1, #options.range [ ii ] do if type ( options.range [ jj ] ) == "string" or type ( options.range [ jj ] ) == "number" then print ( options.range [ jj ] ) end end end end end elseif type ( options.range ) == "string" then if options.range:len() > 0 then print ( "segment range = " .. options.range ) end end print ( "number of runs: " .. options.repeats ) print ( "max runs with no gain: " .. options.maxbad ) print ( "minimum gain per run: " .. options.mingain ) print ( "verbose output: " .. tostring ( options.debug ) ) if options.delay == 0 or options.pause == 0 then print ( "No delay between mutations." ) else print ( "Waiting " .. options.delay .. " seconds every " .. options.pause .. " mutations" ) print ( "Maximum possible mutation rate: " .. options.pause / options.delay .. " mutations/second" ) end print ( "--" ) start = current.GetScore() local order = SortSegments ( segs ) startChunque = CPT:start ( nil, "Start" ) local badreps = 0 for ii = 1, options.repeats do local loopstart = current.GetScore () local runName = "MNW run " .. ii local begChunque = CPT:start ( startChunque, runName ) MNW ( order ) CPT:stop ( begChunque, "end " .. runName ) local loopend = current.GetScore () if loopend - loopstart < options.mingain then badreps = badreps + 1 else badreps = 0 end if badreps >= options.maxbad then break end order = ShuffleTable ( order ) end cleanup () end function cleanup ( errmsg ) if CLEANUPENTRY ~= nil then return end CLEANUPENTRY = true print ( "---" ) local reason local start, stop, line, msg if errmsg == nil then reason = "complete" else -- -- thanks to Bruno K. and Jean-Bob -- start, stop, line, msg = errmsg:find ( ":(%d+):%s()" ) if msg ~= nil then errmsg = errmsg:sub ( msg, #errmsg ) end if errmsg:find ( "Cancelled" ) ~= nil then reason = "cancelled" else reason = "error" end end Ident ( ReVersion .. " " .. reason ) if reason == "error" then print ( "Unexpected error detected" ) print ( "Error line: " .. line ) print ( "Error: \"" .. errmsg .. "\"" ) end if errmsg ~= nil then recentbest.Restore() end behavior.SetFiltersDisabled ( false ) if startChunque ~= nil then CPT:stop ( startChunque, "Final" ) end PrintSpeedCheck ( true ) end xpcall ( main, cleanup )

Comments


LociOiling Lv 1

For ligand puzzles, there a new "Ligand" button in the bottom row. It lets you specify a sphere size. Only segments within the specified distance of the ligand are mutated.

As with the existing "Select" dialog, after the "Ligand" dialog, the segments to be processed are shown as ranges in the "Where" field of the main dialog. The list of segments may get cut off due to the fixed size of "Where", but you can copy and paste to see the full list.

This version of the recipe use structure.GetDistance to measure the distances. It doesn't attempt to find the nearest atoms of the segment and the ligand.

This version also assumes that the interesting ligand is the very last segment. We had a recent puzzle where this was not the case, with an interesting small molecule ligand followed by three boring single-atom ligands. This recipe will need updating if that situation arises again.

LociOiling Lv 1

As suggested by jeff101, some long-dormant updates have been released for testing by an unsuspecting public.

This version of MNW enforces the most recent secondary structure design rules by default. The very latest version of these rules is simply "no cysteine". The "Rules" button lets you adjust the rules as needed for a particular puzzle.

This version still suffers from a menu which may be too tall for some screen sizes.

LociOiling Lv 1

Version 2.5 of MNW has fewer options on the main menu. The many amino acid options have been moved to the "AAs" button.

A couple of notes on using the "design rules" option are in order. First, this option uses the current secondary structure assignments. Use auto structures first to make sure the assignments match the actual hydrogen bonding.

To apply auto structures, select all with control + a, then click the "modify secondary structure" icon or use the hotkey l. Select "auto" from the secondary structure menu.

Second, the only secondary structure rule in recent puzzles has been no cysteine, anywhere. The "Rules" button lets you adjust the rules MNW applies if things tighten up again.