Icon representing a recipe

Recipe: Bridge Wiggle v 1.2.3 - Brow42

created by Bruno Kestemont

Profile


Name
Bridge Wiggle v 1.2.3 - Brow42
ID
102777
Shared with
Public
Parent
Bridge Wiggle v 1.2.1 - Brow42
Children
Created on
June 12, 2018 at 21:07 PM UTC
Updated on
June 12, 2018 at 21:07 PM UTC
Description

Randomly wiggle cysteines with bands to improve bridge score. Also band them to form the bridges in the first palce. Auto detects close pairs.

Best for


Code


--[[ =================================== * Bridge Wiggle * Original Author: Brow42 * Version 1.0 Jan. 13 2012 * This uses bands-to-space to randomly move the ends * of your cysteines around, keeping any improvements * in the total disulfide score. * Can also perform an initial banding of cysteines to * create the bonds. * Version 1.0.1 Mar. 5 2012 Brow42 * Now autodetects cysteine pairings * Version 1.1.0 June 16 2012 Brow42 * Now handles first/last cysteine in protein properly * Removes wiggle bands on cancel * New option boxes: band S-S together, wiggle backbone, use sidechain score * Fixed incorrect random band (only did hemispheres) * Auto-selection of possible bridges expanded to 4.0 A * Version 1.2 May 26 2013 Brow42 * Adjacent cysteines are not considered for pairing * Initial bands are preserved * Version 1.2.1 May 26 2013 * Fixed GetSulfurAtom for unbonded cys * Version 1.2.2 Oct 29, 2013 * Fixed crash if cysteine is last segment * 1.2.1 NC adapted for NC (more iterations wiggle not banded) BK * 1.2.3 options.bgl try another one was 2.05, should be 2.02? --]] -- =================================== Begin Defaults options={} options.max_str = 10.0 options.max_len = 0.1 options.cysteines = '' -- '2,20 14,26, 24,32' -- in pairs, defaults for CASP ROLL 3 options.wiggles = 5 options.max_max_len = 0.5 -- for the slider options.ssband = true options.bbwiggle = true options.scscore = false options.bgl = 0.5 -- band goal length between cysteins (was 2.05 but it does not work anymore, try smaller ones ? 2.02?) -- some globals version = '1.2.3' title = 'Bridge Wiggle v'..version nseg = structure.GetCount() qsave = 3 -- quicksave to use ndigits = 5 -- digits to print energy = current.GetEnergyScore() -- the starting score nSeg = structure.GetCount() cut = 5.0 -- cutt off was 4, changed to 5 for first pairing (ver 1.2.3) -- ================================== End Defaults -- ================================== 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, buttontext2) 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 buttontext2 then d.button2 = dialog.AddButton(buttontext2,2) end 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 Bridge Wiggle Dialogs function DoInfoDialog() local msg = { ' This uses bands-to-space to randomly move the ends', 'of your cysteines around, keeping any improvements', 'in the total disulfide score.', ' Bands will have random strengths and lengths up to', 'the values you specify. You can also include an', 'unbanded wiggle periodically. Bridge wiggle runs until', 'canceled. You can get the best disulfide scoring', 'configuration from quicksave # '..tostring(qsave)..'.', ' You can also perform an initial banding (at specified', 'maximum strength) of cysteines to create the bridges.', ' This recipe will remove bands.' } dialog.MessageBox(msg,'About '..title,'Back') end function DoOptionsDialog(options,defaults) -- Short names for the dialog controls -- this is not an invitation to override, they are not used as functions local label = dialog.AddLabel -- no data member for this local box = dialog.AddCheckbox local slider = dialog.AddSlider -- 4 control options for this local text = dialog.AddTextbox -- Lay out the dialog here -- { { dialog control, textlabel, data member name, control options }, ... } -- all data member names must be unique local fields ={ {label,'Enter pairs of cysteines here:'}, {text, 'Cysteines:','cysteines'}, {label,''}, {slider,'Max Str:','max_str',0.1,10.0,1}, {slider,'Max Len:','max_len',0.0,options.max_max_len,2}, {slider,'Bridge goal length:','bgl',0.00,2.05,2}, {label,'Unbanded'}, {slider,'Wiggle Every:','wiggles',0,20,0}, {box,'Add band between sulfurs','ssband'}, {box,'Wiggle backbone too','bbwiggle'}, {box,'Include sidechain scoring','scscore'} } local d = dialog.CreateDialog(title) dialog.AddFromTable(d,fields,options) -- last arg = table to read from -- You can add more items from a different table here as long -- as there is no name conflict in the two tables -- Add the button now d.ok = dialog.AddButton('Start!',1) -- 1 = okay d.cancel = dialog.AddButton('Cancel',0) -- 0 = quit d.help = dialog.AddButton('About',-1) -- < 0 = show options again, optional button d.more = dialog.AddButton('More',-3) if defaults then d.reset = dialog.AddButton('Reset',-2) end -- if defaults provided, allow reset local button = dialog.Show(d) if button == 0 then return 0 end -- 0 = cancel dialog.ReadFields(d,fields,options) -- last arg = table to save to return button end -- This is for the intial banding function DoInitDialog() local msg = { 'You can perform an initial banding here. Click "Band"', 'to do this. This will create a squashed pyramid of', 'bands of fixed length to try to force the cysteine pairs', 'you gave into bridges with only right angles. Click', '"B & W" to also wiggle after banding. Existing bands', 'will not be removed for this step.', '', 'Check below to add the fourth band that will force', 'the two cysteines to be at right angles to each other', 'or clear it to allow them to rotate freely around the', 'disulfide bridge.' } local d = dialog.CreateDialog('Initial Banding') for i = 1,#msg do d[tostring(i)] = dialog.AddLabel(msg[i]) end d.okay = dialog.AddButton('Band',1) d.b2 = dialog.AddButton('B & W',2) d.b0 = dialog.AddButton('Cancel',0) d.force = dialog.AddCheckbox('Force all right-angle cysteines?',false) local rc = dialog.Show(d) return rc,d.force.value end function DupeCheck(tab) -- return 1 if dupes local tmp = {} for i,v in pairs(tab) do if tmp[v] then return 1 end tmp[v] = true end return 0 end function ErrorMessage(msg) print(msg) dialog.MessageBox(msg,'','Oops') end -- This check for input errors in the text box -- ensure pairs of cysteines now so we don't have to check later function CheckInput(txt) local rc = 1 cys = {} for v in string.gfind(txt,'-?%d+') do -- find numbers v = tonumber(v) if (v < 1 or v > nseg) then ErrorMessage('Segment out of range 1-'..tostring(nseg)) return 0,{} end if structure.GetAminoAcid(v) ~= 'c' then ErrorMessage('Segment '..tostring(v)..' is not a Cys') return 0,{} end table.insert(cys,tonumber(v)) end if rc == 1 and (#cys == 0 or #cys % 2 == 1) then ErrorMessage('Not an even number of Cys entered.') rc = 0 end if rc == 1 and DupeCheck(cys) == 1 then ErrorMessage('Duplicate segment entered') rc = 0 end return rc,cys end -- ================================== End user input -- =============================== Begin Banding Routines -- Return the atom number of the sulfur in a cysteine, or nil if not cys function GetSulfurAtom(iSeg) if structure.GetSecondaryStructure(iSeg) == 'M' then return nil end if structure.GetAminoAcid(iSeg) ~= 'c' then return nil end local diff = structure.GetAtomCount(iSeg) - 11 if current.GetSegmentEnergySubscore(iSeg,'disulfides') ~= tonumber('-0') then diff = diff + 1 end if diff == 1 or diff == 3 then return 7 end return 6 end -- -500 if there's no bond function GetDisulfideScore(iSeg) local x = current.GetSegmentEnergySubscore(iSeg,'disulfides') local y = 0 if options.scscore then y = current.GetSegmentEnergySubscore(iSeg,'sidechain') end return (tostring(x) == '-0') and -500 or x+y end -- score each pair and also the sum function GetScores(tab) sc = {} s = 0 for i = 1, #tab-1,2 do table.insert(sc, GetDisulfideScore(tab[i]) + GetDisulfideScore(tab[i+1])) s = s + sc[#sc] end return s,sc end -- Combining 2 functions into one function band.SetParams(iBand, len, str) band.SetGoalLength(iBand,len) band.SetStrength(iBand,str) end -- Make a random band to space function band.RandomBand(iSeg,rho,atom) local theta = math.acos(2 * math.random() - 1) local phi = 2 * math.pi * math.random() local s2,s3 if iSeg == 1 then s2,s3 = 2,3 elseif iSeg == nSeg then s2,s3 = nSeg-1,nSeg-2 else s2,s3 = iSeg-1,iSeg+1 end return band.Add(iSeg,s2,s3,rho,theta,phi,atom) end -- Make a random band and randomize the goal and weight function RandomParamBand(iSeg,atom) rho = options.max_len * math.random()^(1/3) +0.01 w = math.random() * options.max_str if w < 0.1 then w = 0.1 end local iBand = band.RandomBand(iSeg,rho,atom) band.SetParams(iBand,0.0,w) end -- Make a random short band at last C and S in each cysteine function BandCys(seg1) local s = GetSulfurAtom(seg1) RandomParamBand(seg1,s-1) RandomParamBand(seg1,s) if options.bbwiggle then RandomParamBand(seg1,0) end end function BandSS(seg1,seg2) local s1,s2 = GetSulfurAtom(seg1), GetSulfurAtom(seg2) iBand = band.AddBetweenSegments(seg1,seg2,s1,s2) if (iBand) then band.SetStrength(iBand,options.max_str) band.SetGoalLength(iBand,options.bgl) end end function structure.GetCysteines() local n = structure.GetCount() local cys = {} for i = 1,n do if structure.GetAminoAcid(i) == 'c' then cys[#cys+1] = i end end return cys end -- Get all pairwise distances between sulfur atoms -- returns a function function GetPairwiseDistances() local cys = structure.GetCysteines() local d = {} for i = 1,#cys-1 do d[cys[i]] = {} local s1 = GetSulfurAtom(cys[i]) for j = i+1,#cys do local s2 = GetSulfurAtom(cys[j]) iBand = band.AddBetweenSegments(cys[i],cys[j],s1,s2) if iBand > 0 then d[cys[i]][cys[j]] = band.GetLength(iBand) band.Delete(iBand) end end end return function(seg1,seg2) if seg1 > seg2 then seg1,seg2 = seg2,seg1 end return d[seg1] and d[seg1][seg2] or math.huge end end -- Band two cysteines in a way that forces right angles everywhere function DoInitialBanding(tab) local rc, force rc,force = DoInitDialog() if rc == 0 then return end print('Creating initial bands of strength',options.max_str) local SS = options.bgl local SC = 1.8 local d2 = math.sqrt(SS*SS+SC*SC) -- SC x dir + SS y dir local d3 = math.sqrt(d2*d2+SC*SC) -- SC x dir + SS y dir + SC z dir for i=1,#tab-1,2 do local s1,s2, iBand s1,s2 = tab[i],tab[i+1] local sulfur1,sulfur2 = GetSulfurAtom(s1), GetSulfurAtom(s2) iBand = band.AddBetweenSegments(s1,s2,sulfur1,sulfur2) band.SetParams(iBand,SS,options.max_str) iBand = band.AddBetweenSegments(s1,s2,sulfur1,sulfur2-1) band.SetParams(iBand,d2,options.max_str) iBand = band.AddBetweenSegments(s1,s2,sulfur1-1,sulfur2) band.SetParams(iBand,d2,options.max_str) if force then iBand = band.AddBetweenSegments(s1,s2,sulfur1-1,sulfur2-1) band.SetParams(iBand,d3,options.max_str) end end if rc == 1 then return end local s, score s,score = GetScores(tab) print('Selected pairs:') PrintCys(tab,score,s) local nwiggle = 30 -- 23/02/2014 print('Wiggling for',nwiggle,'iterations...') structure.WiggleAll(nwiggle) s,scor2 = GetScores(tab) PrintCys(tab,score,s) energy = current.GetEnergyScore() end -- =============================== End Banding Routines -- ================================ Begin Utility Routines -- Recursive table printer function PrintTable(tab,indent) indent = indent or '' for i,v in pairs(tab) do if type(v) == 'table' then do print(indent,i) PrintTable(v,indent..' ') end else print(indent,i,v) end end end function round(x,n) n = n or ndigits return math.floor(0.5 + x * 10^n)/10^n end function PrintCys(tab,sc,tot) local fmtstr = '%3d %3d %8.5f' for i = 1, #sc do print(fmtstr:format(tab[2*i-1],tab[2*i],round(sc[i]))) end print('Total: ',tot) print('Overall Score:',round(current.GetEnergyScore())) print('Overall Change:',round(current.GetEnergyScore()-energy)) print('------------------------') end -- brute force optimization of pairs -- Simple greed algorithm, not optimal function FindPairs() local pairs = {} local cys = structure.GetCysteines() local distance = GetPairwiseDistances() local n = math.floor(#cys/2) -- # pairs local d,p,min for k = 1,n do min = math.huge p = nil for i = 1,#cys-1 do for j = i+1,#cys do d = distance(cys[i],cys[j]) if d < min and math.abs(cys[i] - cys[j]) > 1 then min,p = d,{cys[i],cys[j],i,j} end end end if p == nil then return pairs,cys end pairs[#pairs+1] = {p[1], p[2], distance(p[1],p[2])} table.remove(cys,p[4]) -- 4 is after 3 so remove first table.remove(cys,p[3]) end return pairs,cys end _NBands = band.GetCount() function DeleteBands() while band.GetCount() > _NBands do band.Delete(band.GetCount()) end end -- ================================= End Utility Routines -- ================================= Begin Main --defaults={} --for i,v in pairs(options) do defaults[i]=v end -- save the initial options as defaults, shallow copy print('Finding cysteines...(a greedy algorithm, shortest bonds, non-optimal)...') cpairs, unpaired = FindPairs() print('Selected pairing:') print('Seg1 Seg2 S-S Distance') options.cysteines = '' for i = 1,#cpairs do print(cpairs[i][1],cpairs[i][2],cpairs[i][3]) if cpairs[i][3] < cut then options.cysteines = string.format('%s %d,%d',options.cysteines,cpairs[i][1],cpairs[i][2]) end end print('Unpaired cys:',table.concat(unpaired,', ')) print('You should cut out the longest distances. (Already cut >',cut,')') cys = {} repeat -- Loop if something other than okay or cancel local rc = DoOptionsDialog(options,defaults) if rc == 0 then print('Canceled') return end -- cancel local rc2 rc2,cys = CheckInput(options.cysteines) if rc == -1 then DoInfoDialog() end -- if rc == -2 then for i,v in pairs(defaults) do options[i] = v end end if rc2 == 1 and rc == -3 then DoInitialBanding(cys) end until rc == 1 and rc2 == 1-- okay --print('Printing Option Table') --PrintTable(options) print(puzzle.GetName(),os.date(),current.GetScore()) if options.scscore then print('Scoring on disulfide + sidechain scores.') else print('Scoring on disulfide score part.') end s1,score1 = GetScores(cys) print('Selected pairs:') PrintCys(cys,score1,s1) print('----------------------') seed = (os.time() * 5779) % 100000 -- that's a prime -- seed = 81944 -- for debugging, set to value where the error was print('Random seed = ',seed) math.randomseed(seed) print('Saves are in Quicksave '..tostring(qsave)) print('Resetting recent best...') recentbest.Save() -- Repeat forever function WiggleForever() iLoop = 1 repeat DeleteBands() s1,score1 = GetScores(cys) save.Quicksave(qsave) for i = 1,#cys do BandCys(cys[i]) end if options.ssband then for i = 1,#cys,2 do BandSS(cys[i],cys[i+1]) end end structure.WiggleAll(2)-- 23/02/2014 s2,score2 = GetScores(cys) if s2 < s1 then save.Quickload(qsave) else print('Loop',iLoop) PrintCys(cys,score2,s2) end iLoop = iLoop + 1 if options.wiggles > 0 and (iLoop % options.wiggles +1 == options.wiggles) then -- print('Doing an unbanded wiggle') DeleteBands() structure.WiggleAll(30) -- 23/02/2014 end until false end -- Routine called on cancel or error function OnError(err) if err:find('Cancel') then print('Cancelled') DeleteBands() else print('Error: ',err) end return err end rc,err = xpcall(WiggleForever,OnError)

Comments


Bruno Kestemont Lv 1

Several adaptations in order to work with latest Foldit update. Mainly: optional goal length of the bands between cysteins (2.05 didn't work anymore as goal length).
Cut off pairs 5A instead of 4A
Default bands between pairs and wiggle backbones.