Icon representing a recipe

Recipe: Zero Length Bands v 2.0.1 -- Brow42

created by brow42

Profile


Name
Zero Length Bands v 2.0.1 -- Brow42
ID
37147
Shared with
Public
Parent
None
Children
Created on
January 28, 2013 at 06:18 AM UTC
Updated on
January 28, 2013 at 06:18 AM UTC
Description

Creates zero-length bands on the backbone and sidechains, best for QTTN or guided puzzles. Can also randomize backbone to break frozen proteins.

Best for


Code


--[[ ==================================== * Zero Length Bands * Original Author: Brow42 * Version 1.2 Jan. 13 2013 Brow42 * Added options to only band certain things * Moved some options to a second menu, removed reset menu button * Ranges are now expressed as num-num * Random bands actually have a non-zero length finally. * Version 1.0.4 Jun. 10 2012 Brow42 * Added option to band glycine even if backbone is not selected * Version 1.0.3 Apr. 24 2012 Brow42 * Fixed silly bugs in 1.0.2...always band sidechains, band wrong sc atom * Version 1.0.2 Apr. 23 2012 Brow42 * Added slider to not band every single one * Now bands the O-terminus (last seg) sidechain atom correctly * Version 1.0.1 Jan 7 2012 Brow42 * Restricted U-Turn detection to loops only * Made band length and goal different when randomized so it actually pulls * Version 1.0 Jan. 5 2012 * Applies zero-length bands to freeze the overall position or to get started * in a QTTN or guide match. * Can randomized to unfreeze a protein --]] -- =================================== Begin Options option={} option.backbone = true-- Lock down the backbone option.sidechains = false-- Best option for QTTN -- option.extrema = false -- Lock distant points -- not implemented option.uturn = false -- Lock what seems like U-Turns option.bandstr = 3.0 option.random = 0.0 option.uradius = 6.5 option.sidechainatom = 1 option.range_text = '' option.ranges = {} -- table of ranges option.skip_offset = 0 option.skip = 0 option.pin_glycine = false option.do_loop = false option.do_sheet = false option.do_helix = false option.do_frozen = false option.do_notfrozen = false option.do_selected = false -- Globals version = '2.0.1' title='Zero Length Bands v'..version max_skip = 5 -- for the skip sliders nseg = structure.GetCount() -- ================================== 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) title = title or '' local d = dialog.CreateDialog(title) if type(msg) == 'string' then d['1'] = dialog.AddLabel(msg) else for i = 1,#msg do d[tostring(i)] = dialog.AddLabel(msg[i]) end end buttontext1 = buttontext1 or 'Ok' d.button = dialog.AddButton(buttontext1,1) if buttontext0 then d.button0 = dialog.AddButton(buttontext0,0) end return d end function dialog.ShowMessageBox(msg,title,buttontext1,buttontext0) return dialog.Show(dialog.MessageBox(msg,title,buttontext1,buttontext0)) 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 if d[fields[i][3]] == nil then error('Field '..tostring(i)..' is missing from dialog',2) end if object[fields[i][3]] == nil then error('Field '..tostring(i)..' is missing from object',2) end object[fields[i][3]] = d[fields[i][3]].value end end end -- ================================== End New Dialog Library Functions -- ================================== Begin Zero Length Bands -- ==================== Begin Dialog Code -- This function defines our dialog and calls the functions to set and read values -- First arg is the current option state and optional second arg is a remembered -- default. function ShowMenu() -- 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 local msg local dialogs = {} -- Lay out the dialog here -- { { dialog control, textlabel, data member name, control options }, ... } -- all data member names must be unique local fields1 ={ {label,'Pick one or more options:'}, {box, 'Backbone', 'backbone'}, {box,'Sidechains','sidechains'}, {label,'Check what you want banded, or leave all unchecked.'}, {box, 'Band Loops', 'do_loop'}, {box, 'Band Sheets', 'do_sheet'}, {box, 'Band Helices', 'do_helix'}, {text,'Segments:', 'range_text'}, {slider,'Band Strength:','bandstr',0.1,10.0,1}, {slider,'Seg. Skip:','skip',0,max_skip,0}, {slider,'Skip offset:','skip_offset',0,max_skip,0}, {label,'Uncommon options moved to "More"'} } dialogs.main = dialog.CreateDialog(title) -- Generate the dialog in order of the table -- You can still do this by hand for a custom layout if you wish dialog.AddFromTable(dialogs.main,fields1,option) -- last arg = table to read from -- Add the button now dialogs.main.ok = dialog.AddButton('Start!',1) -- 1 = okay dialogs.main.cancel = dialog.AddButton('Cancel',0) -- 0 = quit dialogs.main.help = dialog.AddButton('Help',-1) -- < 0 = show options again, optional button dialogs.main.more = dialog.AddButton('More',-2) -- extra options local fields2 = { {slider,' SC Atom:','sidechainatom',1,30,0}, {box,'Pin glycines even if no backbone bands','pin_glycine'}, {box,'U-Turns (set SS to exclude helices)','uturn'}, {slider,'U-Turn Detection:','uradius',3.5,10.0,1}, {slider,'Randomize:','random',0.0,1.5,2}, } if _nfrozen > 0 then fields2[#fields2+1] = {box, 'Frozen only', 'do_frozen'} fields2[#fields2+1] = {box, 'Not-frozen only', 'do_notfrozen'} else fields2[#fields2+1] = {label, 'No frozen or locked segments'} end if _nselected > 0 then fields2[#fields2+1] = {box, 'Selected only', 'do_selected'} else fields2[#fields2+1] = {label, 'No selected segments'} end dialogs.more = dialog.CreateDialog(title) dialog.AddFromTable(dialogs.more,fields2,option) dialogs.more.ok = dialog.AddButton('Back',1) msg = { ' This recipe is meant to assist with QTTN or other', 'guided puzzles. You can band all the backbone', 'and/or all the sidechains to their current position.', 'You still have to drag the other ends where you', 'want them.', '', ' Range one or more <num> or <num-num> where', 'num is a segment. Change skip to band every-Nth', 'for, fewer segments and offset to change which are', 'skipped. Optionally select which structures are', 'banded, (default is all structures.)' } dialogs.help1 = dialog.MessageBox(msg,title..' Help Page 1','Main') dialogs.help1.next = dialog.AddButton('Next',2) msg = { ' Alternatively you can use the U-Turn detector', 'which bands U-turns...this helps keep the protein', 'exploding or wandering off without restricting the', 'wiggle. Set helix structure so they are not detected.', '', ' The atom slider picks the side chain atom, default', 'is the 1st carbon (glycine doesn\'t have one). The', 'random slider gives the bands a non-zero length.', 'The U-Turn slider makes more-twisted turns detected.', '', ' If you have frozen, locked, or selected segements,', 'you can optionally band only locked/frozen, free,', 'or selected segments.' } dialogs.help2 = dialog.MessageBox(msg,title..' Help Page 2','Main') dialogs.help2.next = dialog.AddButton('Previous',3) repeat local button = dialog.Show(dialogs.main) if button == 0 then return 0 -- 0 = cancel elseif button == 1 then dialog.ReadFields(dialogs.main, fields1, option) option.ranges = RangeStringToTable(option.range_text) if ValidateInput1() then break end -- validate input and print errors elseif button == -1 then -- help menu 2 pages local d = dialogs.help1 repeat button = dialog.Show(d) if button == 2 then d = dialogs.help2 -- next elseif button == 3 then d = dialogs.help1 -- previous elseif button == 0 then return 0 end until button == 1 elseif button == -2 then -- more menu repeat local button = dialog.Show(dialogs.more) if button == 0 then return 0 -- 0 = cancel elseif button == 1 then dialog.ReadFields(dialogs.more, fields2, option) if ValidateInput2() then break end -- validate input and print errors end until false end until false return 1 end -- Returns true if all the 1st page options are valid function ValidateInput1() if option.skip_offset > option.skip then dialog.ShowMessageBox('The offset setting must be less than/equal # skipped.','Input Error','Okay') return false end return option.ranges ~= nil -- parse errors were already reported so no message end -- Returns true if all the 2nd page options are valid function ValidateInput2() if option.do_notfrozen and not option.do_frozen and _nfrozen == nseg then dialog.ShowMessageBox('Everything is frozen/locked, none will be banded.','Input Error','Okay') return false end return true end function RangeStringToTable(str) local tab = {} local entry local state = 1 local N = structure.GetCount() for val in str:gmatch('-? *%d+') do val = tonumber(val) if (val < 0 and state == 1) or val == 0 then dialog.ShowMessageBox("Only positive values >1 are allowed in the range","Input Error","Okay") return nil end if math.abs(val) > N then dialog.ShowMessageBox("Segment number bigger than number of segments","Input Error","Okay") return nil end if val > 0 then tab[#tab+1] = entry entry = {val} state = 2 else entry[#entry+1] = -val tab[#tab+1] = entry entry = nil state = 1 end end if entry then tab[#tab+1] = entry end return tab end -- 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 IsUTurn(i) if i >= 1 and i+3 <= nseg and structure.GetDistance(i,i+3) < option.uradius and structure.GetSecondaryStructure(i+1) == 'L' then return true end return false end -- True if nothing is specified function AllSegments() return AllStructures() and allFrozen() and not (option.do_selected or option.uturn) and #_seglist == 0 end -- True if no structures or all structures specified function AllStructures() return not (option.do_loop or option.do_sheet or option.do_helix) or (option.do_loop and option.do_sheet and option.do_helix) end -- True of neither or both are checked function AllFrozen() return (option.do_frozen and option.do_notfrozen) or not (option.do_frozen or option.do_notfrozen) end -- Returns true if a segment satisfies all conditions (except range) function DoSegment(i) local s = structure.GetSecondaryStructure(i) local f = freeze.IsFrozen(i) or structure.IsLocked(i) local retval = true retval = retval and s ~= 'M' retval = retval and (_allstructures or ( option.do_loop and s == 'L') or (option.do_sheet and s == 'E') or (option.do_helix and s == 'H')) retval = retval and (_allfrozen or (option.do_frozen and f) or (option.do_notfrozen and not f)) retval = retval and (not option.do_selected or selection.IsSelected(i)) retval = retval and (not option.uturn or IsUTurn(i)) retval = retval and ( (i - option.skip_offset -1) % (1+option.skip) == 0) return retval end fsl = fsl or {} fsl.atom = fsl.atom or {} fsl.atom.atomcount = { -- shorter table just for identifying terminals a=10, c=11, d=12, e=15, f=20, g=7, h=17, i=19, k=22, l=19, m=17, n=14, p=15, q=17, r=24, s=11, t=14, v=16, w=24, y=21 } function fsl.atom._IsDisulfideBonded(iSeg) local s = current.GetSegmentEnergySubscore(iSeg,'disulfides') return tostring(s) ~= '-0' end function fsl.atom._IsTerminalTest(aa,count,disulfide) local diff = count - fsl.atom.atomcount[aa] if disulfide then diff = diff + 1 end -- because count is one less because a H was removed if diff == 0 then return false, false, disulfide elseif diff == 1 then return false, true, disulfide elseif diff == 2 then return true, false, disulfide elseif diff == 3 then return true, true, disulfide end error('Strange atom count, report this!') end -- Return random phi,theta that is uniformly distributed over the sphere -- phi between 0 and Pi, theta between 0 and 2 Pi -- You can pass these straight into band.Add function RandomSphere() local u = 2* math.random() - 1 return math.acos(u), math.random()*2*math.pi end -- Make a band in a random direction -- default length is 0.001 -- default strength is 1.0 -- default atom is 0 function RandomBand(iSeg,atom,goal,weight) local nSeg = structure.GetCount() local seg2, seg3 local phi =0 local theta = 0 local minlen = 0.01 atom = atom or 0 goal = goal or minlen weight = weight or 1.0 if (iSeg == 1) then seg2,seg3 = 2,3 elseif iSeg == nSeg then seg2,seg3 = nSeg-1,nSeg-2 else seg2,seg3 = iSeg-1, iSeg+1 end if goal < 0 then goal = -goal end if goal < minlen then goal = minlen end if goal > 0 then theta,phi = RandomSphere() end local iBand = band.Add(iSeg,seg2,seg3,goal,theta,phi,atom) if iBand > 0 then band.SetGoalLength(iBand,goal) band.SetStrength(iBand,weight) end return iBand end function MakeBBSCBand(iSeg) local aa = structure.GetAminoAcid(iSeg) if structure.GetSecondaryStructure(iSeg) ~= 'M' then if option.backbone or ( aa == 'g' and option.pin) then RandomBand(iSeg,2,option.random,option.bandstr) end if option.sidechains and aa ~= 'g' then local first,last,disulfide = fsl.atom._IsTerminalTest( structure.GetAminoAcid(iSeg), structure.GetAtomCount(iSeg), fsl.atom._IsDisulfideBonded(iSeg)) local offset = last and 5 or 4 -- number slider is added to (min 1) to get C-Beta atom if option.sidechainatom+offset <= structure.GetAtomCount(iSeg) then RandomBand(iSeg,option.sidechainatom+offset,option.random,option.bandstr) end end end end function round(x,n) return math.floor(0.5 + x * 10^n) / 10^n end function TableToRangeString() tmp = '' for i =1,#option.ranges do if #option.ranges[i] == 1 then tmp = tmp..tonumber(option.ranges[i])..' ' else tmp = tmp..tonumber(option.ranges[i][1])..'-'..tonumber(option.ranges[i][2])..' ' end end return tmp end function PrintOptions() print(title) print('Only segments that meet all these restrictions') print('will be banded:') print('Range',TableToRangeString()) if not _allstructures then tmp='Structure = ' if option.do_loop then tmp = tmp..'Loop ' end if option.do_sheet then tmp = tmp..'Sheet ' end if option.do_helix then tmp = tmp..'Helix' end print(tmp) end if not _allfrozen then if option.do_frozen then print('Is frozen or locked') else print('Is not frozen or locked') end end if option.do_selected then print('Is selected') end if option.uturn then print('Is detected as a U-Turn with cutoff',option.uradius) end if option.skip > 0 then print('Every 1 out of',1+option.skip,'starting with number',option.skip_offset+1) end print('Options:') tmp = 'Banding ' if option.backbone then tmp = tmp..' Backbone' end if option.sidechains then tmp = tmp..' Sidechains atom '..tostring(option.sidechainatom) end print(tmp) if not option.backbone and option.pin_glycine then print ('Glycines will be banded.') end print('Band Length =',option.bandstr,'length =',option.random) end -- ==================== Begin Main seed = (os.time() * 5779) % 10000 math.randomseed(seed) _nselected,_nfrozen = 0,0 for i=1,nseg do if selection.IsSelected(i) then _nselected = _nselected + 1 end if freeze.IsFrozen(i) or structure.IsLocked(i) then _nfrozen = _nfrozen + 1 end end repeat local rc = ShowMenu() if rc == 0 then print('Cancelled') return end _allstructures = AllStructures() _allfrozen = AllFrozen() _seglist = {} if #option.ranges == 0 then option.ranges = {{1,nseg}} end _nbands = 0 for i =1,#option.ranges do if #option.ranges[i] == 1 then _seglist[i] = DoSegment(option.ranges[i][1]) else for j = option.ranges[i][1],option.ranges[i][2] do _seglist[j] = DoSegment(j) end end end for i = 1,nseg do if _seglist[i] then _nbands = _nbands + 1 end end if _nbands == 0 then dialog.ShowMessageBox('No segments will be banded, check your inputs.','Input Error','Okay') end until _nbands > 0 PrintOptions() for i = 1,nseg do if _seglist[i] then MakeBBSCBand(i) end end print("Done.")

Comments


brow42 Lv 1

This recipe is meant to assist with QTTN or other guided puzzles. You can band all the backbone and/or all the sidechains to their current position. You still have to drag the other ends where you want them.

Alternatively you can use the U-Turn detector which bands U-turns…this helps keep the protein exploding or wandering off without restricting the wiggle. Only loops will be detected as U-Turns, so be sure to mark your helices.

The atom slider picks the side chain atom, default is the 1st carbon on the sidechain (glycine doesn't have one).

The random slider is experimental, it will BREAK your protein, hopefully unfreezing it, but fixable by a fuze.

The U-Turn slider relaxes the detection so you can find where a U-turn connects two sheets that aren't level.

Range is 0,1, or 2 numbers to select segments.

Updated on Jan 7, to make randomize actually work.

Defaults:
option.backbone = true– Lock down the backbone
option.sidechains = false– Best option for QTTN
option.uturn = false – Lock what seems like U-Turns
option.bandstr = 3.0
option.random = 0.0
option.uradius = 6.5
option.sidechainatom = 1
option.range = {} – only segments in this range will be banded { low, high }

brow42 Lv 1

Not huge changes to the operation, but the menus have been expanded. The script now offers many ways to to select which segments are banded. Some menu options have been moved to a second menu panel. (The reset to defaults button has been removed).

By default, all segments will be banded. Setting options restricts which segments are banded…only segments that meet all the restrictions will be banded.

Options are:

Range list: either individual segments or ranges of segments.
Structures: Loops, Sheets, or Helices (default all 3 if blank)
Either only Frozen/Locked, or Not Frozen or Locked (default is both)
Selected
U-Turn detection
Every Nth segment (now with an offset so it doesn't have to start at 1).

The script will warn you if nothing will be banded because of restrictions.

The randomize, FINALLY, seems to work (before it was always length 0).