Icon representing a recipe

Recipe: SelectoPro v1.3

created by LociOiling

Profile


Name
SelectoPro v1.3
ID
108954
Shared with
Public
Parent
SelectoPro v1.2
Children
Created on
January 05, 2025 at 23:24 PM UTC
Updated on
January 06, 2025 at 18:53 PM UTC
Description

Select segments by number, chain, amino acid, unfrozen, unlocked, hydrophobic, or hydrophilic. Existing selections can be preserved. New selections can be inverted, selecting the complement of the selection criteria. Version 1.3 selects faster and handles locked and unlocked more smartly.

Best for


Code


--[[ SelectoPro Version 1.2 allows selecting segments by segment number, chain, or amino acid. Segment number input uses the syntax of Timo van der Laan's segment set logic. Version 1.3 better implements segment set logic select segments based on criteria v1.0 - LociOiling - 2017/10/31 -- select mutable, locked, frozen, hydrophobic, hydrophilic, etc. v1.1 - LociOiling - 2022/01/23 -- select monomer or complex core, boundary, surface v1.2 - LociOiling - 2023/05/16 -- group global variables in tables -- select by chain (detect chains) -- select by segment number, or segment range -- select by amino acid -- cache selection info for performance -- capture existing selections as set -- trim down SLT (remove segment type functions) -- scriptlog output of final selections -- a ligand is just another segment v1.3 - LociOiling - 2024/01/ -- speed things up, work in segment sets instead of one-by-one -- remove protein design features: mutable, simplex core, and complex core, no more protein design puzzles, no reference to puzzle objectives now -- allow selecting *unlocked" -- split unlocked into backbone, sidechain -- allow selecting *unfrozen* -- split frozen into backbone, sidechain ]]-- -- -- globals section -- Recipe = "SelectoPro" Version = "1.3" ReVersion = Recipe .. " " .. Version PST = { segCnt = 0, segCnt2 = 0, sel = {}, -- selected segments selSet = {}, -- selected segment set selCnt = {}, -- initial selections segDist = {}, -- alpha carbon distances chain = {}, -- chain as a list chainSet = {}, -- chain as a set (ranges of segments) ACRF = 4.0, -- alpha carbon reference distance CHAINID = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" }, AAMaster = { a = { name = "alanine", code = 'ALA', sel = false, }, c = { name = "cysteine", code = 'CYS', sel = false, }, d = { name = "aspartate", code = 'ASP', sel = false, }, e = { name = "glutamate", code = 'GLU', sel = false, }, f = { name = "phenylalanine", code = 'PHE', sel = false, }, g = { name = "glycine", code = 'GLY', sel = false, }, h = { name = "histidine", code = 'HIS', sel = false, }, i = { name = "isoleucine", code = 'ILE', sel = false, }, k = { name = "lysine", code = 'LYS', sel = false, }, l = { name = "leucine", code = 'LEU', sel = false, }, m = { name = "methionine", code = 'MET', sel = false, }, n = { name = "asparagine", code = 'ASN', sel = false, }, p = { name = "proline", code = 'PRO', sel = false, }, q = { name = "glutamine", code = 'GLN', sel = false, }, r = { name = "arginine", code = 'ARG', sel = false, }, s = { name = "serine", code = 'SER', sel = false, }, t = { name = "threonine", code = 'THR', sel = false, }, v = { name = "valine", code = 'VAL', sel = false, }, y = { name = "tyrosine", code = 'TYR', sel = false, }, w = { name = "tryptophan", code = 'TRP', sel = false, }, }, } local SPX = { sUnlockedBB = false, sUnlockedSC = false, sUnfrozenBB = false, sUnfrozenSC = false, sPhobic = false, sPhilic = false, sLigand = false, sChain = false, -- implied dialog value, select by chain sAA = false, -- implied dialog value, select by amino acid sSeg = false, -- implied dialog value, select by segment/range ranges = "", -- ranges of segments as string rtab = {}, -- ranges as a table sKeep = false, sInvert = false, } SLT = { -- SLT--SLT--SLT--SLT--SLT--SLT--SLT--SLT--SLT--SLT-- --[[ SLT - Segment set, list, and type module v0.7 Includes the segment set and list module and the segment type module developed by Timo van der Laan. The following Foldit recipes contain the original code for these modules: * Tvdl enhanced DRW 3.1.1 - https://fold.it/portal/recipe/102840 * TvdL DRemixW 3.1.2 - https://fold.it/portal/recipe/102398 The "set and list" module performs logical operations and transformations on tables containing ranges of segment. The segment type module find lists and sets of segments with various properties, such as selected or frozen. A "list" is one-dimensional table containing segment numbers. A "set" is a two-dimensional table containing segment number ranges. For example, given a list of segments: list = { 1, 2, 3, 7, 8, 11, 13, 14, 15 } the corresponding set is: set = { { 1, 3 }, { 7, 8 }, { 11, 11 }, {13, 15 } } Most functions assume that the sets are well-formed, meaning they are ordered and have no overlaps. As an example, the method FindUnlocked returns a set of all the unlocked segments in a puzzle. The method can be called as follows: funlocked = SLT:FindUnlocked () The return value funlocked is a two-dimensional table containing ranges of unlocked segments. In source format, the table might look like this: funlocked = { { 27, 35, }, { 47, 62, }, { 78, 89, }, } The code to use this table would look like: -- -- for each range of segments -- for ii = 1, #funlocked do -- -- for each segment in the range, so something -- for jj = funlocked [ ii ] [ 1 ], funlocked [ ii ] [ 2 ] do ... something ... end end This psuedo-module is a table containing a mix of data fields and methods. This wiki article explains the packaging technique: https://foldit.fandom.com/wiki/Lua_packaging_for_Foldit Authorship ---------- Original by Timo van der Laan: 02-05-2012 TvdL Free to use for non commercial purposes French comments by Bruno Kestemont and perhaps others. v0.1 - LociOiling + extract and reformat code v0.2 - LociOiling - 2017/11/03 + add primary FindUnlocked function v0.3 - LociOiling + add FindRotamers function v0.4 - LociOiling - 2019/10/29 + package as table + remove dependencies on segCnt and segCnt2 v0.5 - LociOiling - 2019/12/17 + convert functions to methods, update internal references v0.6 - LociOiling - 2022/04/18 + add FindAAList v0.7 - LociOiling - 2023/05/02 + add FindWorkableList ]]-- -- -- variables -- segCnt = nil, -- segment count, not adjusted for ligands segCnt2 = nil, -- segment count, not including terminal ligands -- -- initializer - can be called externally, but invoked inline if segCnt or segCnt2 are nil -- Init = function ( self ) self.segCnt = structure.GetCount () self.segCnt2 = self.segCnt while structure.GetSecondaryStructure ( self.segCnt2 ) == "M" do self.segCnt2 = self.segCnt2 - 1 end end, -- -- segment set and list functions -- SegmentListToSet = function ( self, list ) -- retirer doublons local result = {} local ff = 0 local ll = -1 table.sort ( list ) for ii = 1, #list do if list [ ii ] ~= ll + 1 and list [ ii ] ~= ll then -- note: duplicates are removed if ll > 0 then result [ #result + 1 ] = { ff, ll } end ff = list [ ii ] end ll = list [ ii ] end if ll > 0 then result [ #result + 1 ] = { ff, ll } end return result end, SegmentSetToList = function ( self, set ) -- faire une liste a partir d'une zone local result = {} for ii = 1, #set do for kk = set [ ii ] [ 1 ], set [ ii ] [ 2 ] do result [ #result + 1 ] = kk end end return result end, SegmentCleanSet = function ( self, set ) -- Makes it well formed return self:SegmentListToSet ( self:SegmentSetToList ( set ) ) end, SegmentInvertSet = function ( self, set, maxseg ) -- -- Gives back all segments not in the set -- maxseg is added for ligand -- local result={} if maxseg == nil then maxseg = structure.GetCount () end if #set == 0 then return { { 1, maxseg } } end if set [ 1 ] [ 1 ] ~= 1 then result [ 1 ] = { 1, set [ 1 ] [ 1 ] - 1 } end for ii = 2, #set do result [ #result + 1 ] = { set [ ii - 1 ] [ 2 ] + 1, set [ ii ] [ 1 ] - 1, } end if set [ #set ] [ 2 ] ~= maxseg then result [ #result + 1 ] = { set [ #set ] [ 2 ] + 1, maxseg } end return result end, SegmentInvertList = function ( self, list ) if self.segCnt2 == nil then self:Init () end table.sort ( list ) local result = {} for ii = 1, #list - 1 do for jj = list [ ii ] + 1, list [ ii + 1 ] - 1 do result [ #result + 1 ] = jj end end for jj = list [ #list ] + 1, self.segCnt2 do result [ #result + 1 ] = jj end return result end, SegmentInList = function ( self, seg, list ) -- verifier si segment est dans la liste table.sort ( list ) for ii = 1, #list do if list [ ii ] == seg then return true elseif list [ ii ] > seg then return false end end return false end, SegmentInSet = function ( self, set, seg ) --verifie si segment est dans la zone for ii = 1, #set do if seg >= set [ ii ] [ 1 ] and seg <= set [ ii ] [ 2 ] then return true elseif seg < set [ ii ] [ 1 ] then return false end end return false end, SegmentJoinList = function ( self, list1, list2 ) -- fusionner 2 listes de segments local result = list1 if result == nil then return list2 end for ii = 1, #list2 do result [ #result + 1 ] = list2 [ ii ] end table.sort ( result ) return result end, SegmentJoinSet = function ( self, set1, set2 ) --fusionner (ajouter) 2 zones return self:SegmentListToSet ( self:SegmentJoinList ( self:SegmentSetToList ( set1 ), self:SegmentSetToList ( set2 ) ) ) end, SegmentCommList = function ( self, list1, list2 ) -- chercher intersection de 2 listes local result = {} table.sort ( list1 ) table.sort ( list2 ) if #list2 == 0 then return result end local jj = 1 for ii = 1, #list1 do while list2 [ jj ] < list1 [ ii ] do jj = jj + 1 if jj > #list2 then return result end end if list1 [ ii ] == list2 [ jj ] then result [ #result + 1 ] = list1 [ ii ] end end return result end, SegmentCommSet = function ( self, set1, set2 ) -- intersection de 2 zones return self:SegmentListToSet ( self:SegmentCommList ( self:SegmentSetToList ( set1 ), self:SegmentSetToList ( set2 ) ) ) end, SegmentSetMinus = function ( self, set1, set2 ) return self:SegmentCommSet ( set1, self:SegmentInvertSet ( set2 ) ) end, SegmentPrintSet = function ( self, set ) print ( self:SegmentSetToString ( set ) ) end, SegmentSetToString = function ( self, set ) -- pour pouvoir imprimer local line = "" for ii = 1, #set do if ii ~= 1 then line = line .. ", " end line = line .. set [ ii ] [ 1 ] .. "-" .. set [ ii ] [ 2 ] end return line end, SegmentSetInSet = function ( self, set, sub ) if sub == nil then return true end -- -- Checks if sub is a proper subset of set -- for ii = 1, #sub do if not self:SegmentRangeInSet ( set, sub [ ii ] ) then return false end end return true end, SegmentRangeInSet = function ( self, set, range ) -- verifier si zone est dans suite if range == nil or #range == 0 then return true end local bb = range [ 1 ] local ee = range [ 2 ] for ii = 1, #set do if bb >= set [ ii ] [ 1 ] and bb <= set [ ii ] [ 2 ] then return ( ee <= set [ ii ] [ 2 ] ) elseif ee <= set [ ii ] [ 1 ] then return false end end return false end, SegmentSetToBool = function ( self, set ) --vrai ou faux pour chaque segment utilisable ou non local result = {} for ii = 1, structure.GetCount () do result [ ii ] = self:SegmentInSet ( set, ii ) end return result end, -- -- End of Segment Set module -- -- -- Module Find Segment Types removed for space -- }-- SLT--SLT--SLT--SLT--SLT--SLT--SLT--SLT--SLT--SLT-- -- -- SegmentStringToSet - convert user input to a segment set -- -- This function is the logical inverse of SSL:SegmentSetToString. -- -- User input is converted to a table containing ranges of segments. -- -- User input is a comma-separated list of segment ranges or indivdual segments. -- -- For example: -- -- 12-23,47,65-67,69 -- -- or -- -- 12,47,60 -- -- Segment numbers are validated such that 1 <= segnum <= structure.GetCount -- -- Any stray characters are considered errors. -- -- The SegmentSetToString returns a table containing the segment set, and a separarate table -- containing error messages. -- -- If there are errors, the segment set table is empty. The error table is empty is there are -- no errors. -- -- function SegmentStringToSet ( range ) local segCnt = structure.GetCount () local table = {} local errz = {} local function chkSeg ( seg, word, xtra1, xtra2, errz ) local ok = true if seg == nil then errz [ #errz + 1 ] = "error in \"" .. word .. "\", segment number must be numeric" ok = false elseif seg <= 0 then errz [ #errz + 1 ] = "error in \"" .. word .. "\", segment number must be positive" ok = false elseif seg > segCnt then errz [ #errz + 1 ] = "error in \"" .. word .. "\", segment greater than max " .. segCnt ok = false elseif xtra1:len () > 0 then errz [ #errz + 1 ] = "error in \"" .. word .. "\", extra characters \"" .. xtra1 .. "\"" ok = false elseif xtra2:len () > 0 then errz [ #errz + 1 ] = "error in \"" .. word .. "\", extra characters \"" .. xtra2 .. "\"" ok = false end return ok, errz end -- -- pass 1 - comma-separated items -- for word in range:gmatch ( "([^,]+)" ) do word = word:gsub("%s+", "") -- remove spaces local rgt = {} -- -- pass 2a - hyphen-separated segment range -- -- In string.gmatch, patterns enclosed in parentheses are "captures", which are returned. -- -- Both wanted and unwanted items are captured. -- -- The pattern ([^%d]*) is used to capture any non-numerics before the first and second -- numerics in the range (extra0 and extra2). The square brackets indicate a custom character -- class, and the "^" inside the brackets indicates the inverse of what follows. "%d" means -- indicate the class of all decimal digits, so "[^%d] is the class of anything that's not a -- decimal digit. The "*" in the pattern matches zero or more occurences. -- -- The pattern (%d+) captures both the first and second numerics in the range (num1 and num2). -- -- The pattern ([^%-]-) captures anything that's not a hypen after the first numeric (extra2). -- -- The pattern %- represents the hyphen, and is not a capture. A missing hyphen means no match. -- -- Finally, the pattern (.*) captures anything after the second numeric (extra3). -- -- extra0 num1 extra1 - extra2 num2 extra3 for x0, r1, x1, x2, r2, x3 in word:gmatch ( "([^%d]*)(%d+)([^%-]-)%-([^%d]*)(%d+)(.*)" ) do local ok local r1x = tonumber ( r1 ) ok, errz = chkSeg ( tonumber ( r1 ), word, x0, x1, errz ) if ok then rgt [ #rgt + 1 ] = r1x end local r2x = tonumber ( r2 ) ok, errz = chkSeg ( r2x, word, x2, x3, errz ) if ok then rgt [ #rgt + 1 ] = r2x end end -- -- pass 2b - single segment if pass 2a fails -- -- If the first gmatch doesn't find a match, a second gmatch looks for just a numeric. -- -- Again, unwanted items are also captured, the captures are similar to phase 2. -- if #rgt == 0 and #errz == 0 then for x0, r1, x1 in word:gmatch ( "([^%d]*)(%d+)(.*)" ) do print ( "r1 = " .. r1 .. ", x1 = " .. x1 ) local r1x = tonumber ( r1 ) ok, errz = chkSeg ( r1x, word, x0, x1, errz ) if ok then rgt [ #rgt + 1 ] = r1x end end end -- -- validate and add to table -- if #rgt == 1 then -- double up, making a range of one segment rgt [ #rgt + 1 ] = rgt [ 1 ] end if #rgt > 0 then -- swap if segments out of order if rgt [ 1 ] > rgt [ 2 ] then rgt [ 1 ], rgt [ 2 ] = rgt [ 2 ], rgt [ 1 ] -- switch-a-rooney end table [ #table + 1 ] = rgt end end -- -- catch-all, error if nothing matched input -- if #table == 0 and #errz == 0 then errz [ #errz + 1 ] = "error in \"" .. range .. "\", invalid input" end if #errz > 0 then table = {} end return table, errz end function GetParameters () local uerror = {} local rc repeat local d = dialog.CreateDialog ( ReVersion ) d.selcnt = dialog.AddLabel ( PST.selCnt .. " segments selected initially" ) d.ranges = dialog.AddTextbox ( "Selections", SPX.ranges ) d.segmsg1 = dialog.AddLabel ( "Selections as segment ranges or single segments, " ) d.segmsg2 = dialog.AddLabel ( "specified as a comma-separated list, " ) d.segmsg3 = dialog.AddLabel ( "for example: 1-20,17,21,35-59" ) d.s0 = dialog.AddLabel ( "" ) d.l0 = dialog.AddLabel ( "Select segments if..." ) d.sUnlockedBB = dialog.AddCheckbox ( "Unlocked backbone?", SPX.sUnlockedBB ) d.sUnlockedSC = dialog.AddCheckbox ( "Unlocked sidechain?", SPX.sUnlockedSC ) d.sUnfrozenBB = dialog.AddCheckbox ( "Unfrozen backbone?", SPX.sUnfrozenBB ) d.sUnfrozenSC = dialog.AddCheckbox ( "Unfrozen sidechain?", SPX.sUnfrozenSC ) d.sPhobic = dialog.AddCheckbox ( "Hydrophobic?", SPX.sPhobic ) d.sPhilic = dialog.AddCheckbox ( "Hydrophilic?", SPX.sPhilic ) d.l5 = dialog.AddLabel ( "" ) d.sKeep = dialog.AddCheckbox ( "Keep existing selections?", SPX.sKeep ) d.sInvert = dialog.AddCheckbox ( "Invert new selections?", SPX.sInvert ) d.sLigand = dialog.AddCheckbox ( "Include ligand(s)?", SPX.sLigand ) if #uerror > 0 then d.lerr = dialog.AddLabel ( "" ) for ii = 1, #uerror do d [ "lerr" .. ii ] = dialog.AddLabel ( uerror [ ii ] ) end end d.ok = dialog.AddButton ( "OK" , 1 ) if #PST.chainSet > 1 then d.chains = dialog.AddButton ( "Chains" , 2 ) end d.AAs = dialog.AddButton ( "AAs", 3 ) d.cancel = dialog.AddButton ( "Cancel" , 0 ) rc = dialog.Show ( d ) uerror = {} if rc == 2 then GetChains ( 1 ) end if rc == 3 then GetAAs ( 1 ) end if rc > 0 then SPX.sUnlockedBB = d.sUnlockedBB.value SPX.sUnlockedSC = d.sUnlockedSC.value SPX.sUnfrozenBB = d.sUnfrozenBB.value SPX.sUnfrozenSC = d.sUnfrozenSC.value SPX.sPhobic = d.sPhobic.value SPX.sPhilic = d.sPhilic.value SPX.sKeep = d.sKeep.value SPX.sInvert = d.sInvert.value SPX.ranges = d.ranges.value SPX.sLigand = d.sLigand.value if SPX.ranges:len() > 0 then local terror = {} SPX.rtab, terror = SegmentStringToSet ( SPX.ranges ) if #terror == 0 then SPX.rtab = SLT:SegmentCleanSet ( SPX.rtab ) else for ii = 1, #terror do uerror [ #uerror + 1 ] = terror [ ii ] end end end end until rc <= 1 and #uerror == 0 return rc end function GetChains ( chndx ) if chndx == nil then chndx = 1 end local CHPAGE = 8 local rc = 0 local chmax = math.min ( #PST.chainSet, chndx + CHPAGE - 1 ) local d = dialog.CreateDialog ( ReVersion .. " Chains" ) d.l1 = dialog.AddLabel ( "Displaying chains " .. chndx .. "-" .. chmax .. " of " .. #PST.chainSet ) for ii = chndx, chmax do local cs = PST.chainSet [ ii ] d [ "chn" .. ii .. "l1" ] = dialog.AddCheckbox ( "Chain " .. cs.chain .. ": " .. cs.start .. "-" .. cs.stop .. ", length = " .. cs.len, cs.sel ) end d.ok = dialog.AddButton ( "OK" , 1 ) if chndx > 1 then d.prev = dialog.AddButton ( "Prev", 2 ) end if chmax < #PST.chainSet then d.next = dialog.AddButton ( "Next", 3 ) end d.cancel = dialog.AddButton ( "Cancel" , 0 ) repeat rc = dialog.Show ( d ) if rc > 0 then for ii = chndx, chmax do local cs = PST.chainSet [ ii ] cs.sel = d [ "chn" .. ii .. "l1" ].value end end if rc == 2 then rc = GetChains ( chndx - CHPAGE ) end if rc == 3 then rc = GetChains ( chndx + CHPAGE ) end until rc < 2 return rc end function GetAAs ( aaindx ) local AAMPAGE = 10 -- amino acids / page local AAMSIZE = 20 -- hardcode this value, #AAMaster not valid local rc = 0 local ask = dialog.CreateDialog ( ReVersion .. " Amino Acids" ) local aamax = math.min ( AAMSIZE, aaindx + AAMPAGE - 1 ) ask.AADisp = dialog.AddLabel ( "displaying " .. aaindx .. " - " .. aamax .. " of " .. AAMSIZE ) local aacnt = 0 for key, value in pairs ( PST.AAMaster ) do aacnt = aacnt + 1 if aacnt >= aaindx and aacnt <= aamax then local aalabel = "" .. string.upper ( key ) .. " (" .. PST.AAMaster [ key ].code .. ") - " .. PST.AAMaster [ key ].name ask [ "AASEL" .. aacnt ] = dialog.AddCheckbox ( aalabel, PST.AAMaster [ key ].sel ) end end ask.OK = dialog.AddButton ( "OK", 1 ) if aaindx > 1 then ask.prev = dialog.AddButton ( "Prev", 2 ) end if aamax < AAMSIZE then ask.next = dialog.AddButton ( "Next", 3 ) end ask.Cancel = dialog.AddButton ( "Cancel", 0 ) repeat rc = dialog.Show ( ask ) if rc > 0 then aacnt = 0 for key, value in pairs ( PST.AAMaster ) do aacnt = aacnt + 1 if aacnt >= aaindx and aacnt <= aamax then PST.AAMaster [ key ].sel = ask [ "AASEL" .. aacnt ].value end if aacnt > aamax then break end end end if rc == 2 then rc = GetAAs ( aaindx - AAMPAGE ) end if rc == 3 then rc = GetAAs ( aaindx + AAMPAGE ) end until rc < 2 return rc end function Init () PST.segCnt = structure.GetCount() PST.segCnt2 = PST.segCnt while structure.GetSecondaryStructure ( PST.segCnt2 ) == "M" do PST.segCnt2 = PST.segCnt2 - 1 end local tsel = {} for ii = 1, PST.segCnt do PST.sel [ #PST.sel + 1 ] = selection.IsSelected ( ii ) if PST.sel [ #PST.sel ] then tsel [ #tsel + 1 ] = ii end end PST.selCnt = #tsel if PST.selCnt > 0 then SPX.sKeep = true end PST.selSet = SLT:SegmentListToSet ( tsel ) SPX.ranges = SLT:SegmentSetToString ( PST.selSet ) -- "ranges" starts with existing selections -- -- measure distances between alpha carbons -- for ii = 1, PST.segCnt - 1 do PST.segDist [ #PST.segDist + 1 ] = structure.GetDistance ( ii, ii + 1 ) end PST.segDist [ #PST.segDist + 1 ] = 9999 -- large distance -- -- determine chains, first make a list -- local chndx = 1 for ii = 1, #PST.segDist do PST.chain [ #PST.chain + 1 ] = PST.CHAINID [ chndx ] if PST.segDist [ ii ] > PST.ACRF then chndx = chndx + 1 end end -- -- convert chain list to set (TODO: what would Timo think here?) -- local chainID, chStart, chStop chainID = PST.chain [ 1 ] chStart = 1 chStop = 1 for ii = 2, #PST.chain do if PST.chain [ ii ] ~= chainID then PST.chainSet [ #PST.chainSet + 1 ] = { chain = chainID, start = chStart, stop = chStop, len = chStop - chStart + 1, sel = false, } chainID = PST.chain [ ii ] chStart = ii end chStop = ii end PST.chainSet [ #PST.chainSet + 1 ] = { chain = chainID, start = chStart, stop = chStop, len = chStop - chStart + 1, sel = false, } if #PST.chainSet == 1 then print ( "single chain" ) else print ( #PST.chainSet .. " chains found" ) end for ii = 1, #PST.chainSet do print ( "chain " .. PST.chainSet [ ii ].chain .. ", " .. PST.chainSet [ ii ].start .. "-" .. PST.chainSet [ ii ].stop .. ", len = " .. PST.chainSet [ ii ].len ) end print ( "--" ) end function chainCheck ( seg ) -- returns true if seg is in a selected chain for ii = 1, #PST.chainSet do if PST.chainSet [ ii ].sel and seg >= PST.chainSet [ ii ].start and seg <= PST.chainSet [ ii ].stop then return true end end return false end function aaCheck ( seg ) -- returns true if seg is a selected amino acid local aax = PST.AAMaster [ structure.GetAminoAcid ( seg ) ] if aax ~= nil and aax.sel then return true end return false end function main () print ( ReVersion ) print ( "Puzzle: " .. puzzle.GetName () ) local trk = ui.GetTrackName () if trk ~= "default" then print ( "Track: " .. trk ) end Init () if not GetParameters () then return end print ( "options:" ) if SPX.sUnlockedBB then print ( "select unlocked backbone" ) end if SPX.sUnlockedSC then print ( "select unlocked sidechain" ) end if SPX.sUnfrozen then print ( "select frozen" ) end if SPX.sPhobic then print ( "select hyrdophobics" ) end if SPX.sPhilic then print ( "select hydrophilics" ) end if #PST.chainSet > 1 then local chsels = 0 local chainSel = "" for ii = 1, #PST.chainSet do if PST.chainSet [ ii ].sel then chsels = chsels + 1 if chsels > 1 then chainSel = chainSel .. ", " end chainSel = chainSel .. PST.chainSet [ ii ].chain end end if chsels > 0 then SPX.sChain = true cwd = "chain" if chsels > 1 then cwd = "chains" end print ( "select " .. chsels .. " " .. cwd .. " (" .. chainSel .. ")" ) end end local aacnt = 0 local aasel = 0 local aaStr = "" for key, value in pairs ( PST.AAMaster ) do aacnt = aacnt + 1 if PST.AAMaster [ key ].sel then aasel = aasel + 1 if aasel > 1 then aaStr = aaStr .. ", " end aaStr = aaStr .. key end end if aasel > 0 then SPX.sAA = true local aawd = "amino acid" if aasel > 1 then aawd = "amino acids" end print ( "select " .. aasel .. " " .. aawd .. " (" .. aaStr .. ")" ) end if #SPX.rtab > 0 then SPX.sSeg = true print ( "select segments " .. SLT:SegmentSetToString ( SPX.rtab ) ) end if SPX.sKeep then print ( "keep existing selections" ) end if SPX.sInvert then print ( "invert new selections" ) end if SPX.sLigand then print ( "include ligand(s)" ) end print ( "--" ) -- -- do it -- local sels = 0 if not SPX.sKeep then selection.DeselectAll () for ii = 1, PST.segCnt do PST.sel [ ii ] = false end print ( "existing selections cleared" ) else for ii = 1, PST.segCnt do if selection.IsSelected ( ii ) then sels = sels + 1 end end print ( sels .. " existing selections" ) end local fsels = {} -- fsels is a segment set list of what's to be selected for ii = 1, PST.segCnt do local sellit = false local lbb = false local lsc = false if SPX.sUnlockedBB or SPX.sUnlockedSC then lbb, lsc = structure.IsLocked ( ii ) end if SPX.sUnlockedBB and not lbb and not SPX.sInvert then sellit = true end if SPX.sUnlockedBB and lbb and SPX.sInvert then sellit = true end if SPX.sUnlockedSC and not lsc and not SPX.sInvert then sellit = true end if SPX.sUnlockedSC and lsc and SPX.sInvert then sellit = true end local fbb = false local fsc = false if SPX.sUnfrozenBB or SPX.sUnfrozenSC then fbb, fsc = freeze.IsFrozen ( ii ) end if SPX.sUnfrozenBB and not fbb and not SPX.sInvert then sellit = true end if SPX.sUnfrozenBB and fbb and SPX.sInvert then sellit = true end if SPX.sUnfrozenSC and not fsc and not SPX.sInvert then sellit = true end if SPX.sUnfrozenSC and fsc and SPX.sInvert then sellit = true end if SPX.sPhobic and structure.IsHydrophobic ( ii ) and not SPX.sInvert then sellit = true end if SPX.sPhobic and not structure.IsHydrophobic ( ii ) and SPX.sInvert then sellit = true end if SPX.sPhilic and not structure.IsHydrophobic ( ii ) and not SPX.sInvert then sellit = true end if SPX.sPhilic and structure.IsHydrophobic ( ii ) and SPX.sInvert then sellit = true end if SPX.sChain and chainCheck ( ii ) and not SPX.sInvert then sellit = true end if SPX.sChain and not chainCheck ( ii ) and SPX.sInvert then sellit = true end if SPX.sAA and aaCheck ( ii ) and not SPX.sInvert then sellit = true end if SPX.sAA and not aaCheck ( ii ) and SPX.sInvert then sellit = true end if SPX.sSeg and SLT:SegmentInSet ( SPX.rtab, ii ) and not SPX.sInvert then sellit = true end if SPX.sSeg and not SLT:SegmentInSet ( SPX.rtab, ii ) and SPX.sInvert then sellit = true end local isLigand = structure.GetSecondaryStructure ( ii ) == "M" if isLigand then if not SPX.sLigand then sellit = false end end if sellit then fsels [ #fsels + 1 ] = ii end end local iranges = SLT:SegmentSetToString ( PST.selSet ) if iranges:len () == 0 then iranges = "none" end print ( "initial selections: " .. iranges ) -- -- do the actual selecting here using ranges -- local frangez = SLT:SegmentListToSet ( fsels ) local franges = SLT:SegmentSetToString ( frangez ) if franges:len () == 0 then franges = "none" end print ( "final selections: " .. franges ) for ii = 1, #frangez do selection.SelectRange ( frangez [ ii ] [ 1 ], frangez [ ii ] [ 2 ] ) end -- -- determine number of changed segments -- local cnt = 0 for ii = 1, PST.segCnt do if selection.IsSelected ( ii ) and not PST.sel [ ii ] then cnt = cnt + 1 end end -- -- determine number of errors -- local errs = 0 for ii = 1, #fsels do if not selection.IsSelected ( fsels [ ii ] ) then errs = errs + 1 end end print ( cnt .. " selections added" ) if errs > 0 then print ( errs .. " selection errors (locked segments, etc.) " ) end print ( sels + cnt .. " total segments selected" ) 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 -- -- civilized error reporting, -- 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 print ( ReVersion .. " " .. reason ) print ( "Puzzle: " .. puzzle.GetName () ) local trk = ui.GetTrackName () if trk ~= "default" then print ( "Track: " .. trk ) end if reason == "error" then print ( "Unexpected error detected" ) print ( "Error line: " .. line ) print ( "Error: \"" .. errmsg .. "\"" ) end end xpcall ( main , cleanup )

Comments


LociOiling Lv 1

SelectoPro v1.3 builds on the features of SelectoPro v1.2.

New features in v1.3 include:

  • selections are done in ranges for better performance
  • frozen and locked options are now unfrozen and unlocked
  • unfrozen and unlocked have separate backbone and sidechain options
  • ligands can optionally be included in a selection

The protein design options "mutable", "simplex core", and "complex core" have been removed.

Main dialog

SelectPro displays an initial dialog which includes most selection options. If a puzzle has more than one chain, a Chains button allows selecting by chain. An AAs button allows selecting by amino acid.

Clicking OK on the main dialog performs any specified selections and ends the recipe. Clicking Cancel ends the recipe without making any new selections.

The main dialog includes the following items:

  • the number of segments currently selected is displayed
  • Selections displays any existing selections as segment ranges, and allows entering new ranges, see Syntax below
  • a brief explanation of segment range format follows Selections
  • checkboxes allow selecting classes of segments:
    • Unlocked backbone? selects segments which have backbone left unlocked by the puzzle designer
    • Unlocked sidechain? selects segments which have a sidechain left unlocked by the puzzle designer
    • Unfrozen backbone? selects segments where the user has not frozen the backbone
    • Unfrozen sidechain? selects segments where the user has not frozen the sidechain
    • Hydrophobic? selects segments with hydrophobic amino acids
    • Hydrophilic? selects segments with hydrophilic amino acids
  • Keep existing selections? is checked by default for an existing selection, uncheck to clear the selection before making a new selection
  • Invert new selections? when checked, selects the complement of a specified new selection - for example, if Unfrozen sidechain? is checked along with Invert new selections?, only segments with frozen sidechains would be selected. Invert also applies to chain and amino acid selections
  • Include ligand(s)? if checked, includes ligands which were selected by another option; otherwise ligands are skipped

If the input from Selections contains errors, the error messages appear below Include ligand(s)?and above OK and the other buttons. Errors must be resolved before the OK button can make new selections and end the recipe.

Chains dialog

When a puzzle has multiple chains, the "Chains" button appears on the main dialog, and displays the Chains dialog when clicked.

The Chains dialog shows a checkbox for each chain detected. If there are more than four chains, a Next button allows paging forward through the chains, and a Prev button allows paging backward after the first page.

Any chains checked in the Chains dialog will be selected when OK is clicked in the main dialog.

Clicking OK in the Chains dialog applies updates from the current page and returns to the main dialog. Clicking Cancel in the chains dialog returns to the main dialog without applying any new selections on the current page.

Amino Acids dialog

The AAs button on the main dialog opens the Amino Acids dialog when clicked.

The Amino Acids dialog shows a checkbox for each of the 20 amino acids. The amino acids are listed in order of their one-letter, codes, so alanine (code: A) appears first.

Only ten amino acids are shown at a time. The Next button advances to the next ten amino acids, and the Prev button allows
returning to the first ten.

Segments containing any of the amino acids checked in the AAs dialog will be selected when OK is clicked in the main dialog.

Clicking OK in the Chains dialog applies updates from the current page and returns to the main dialog. Clicking Cancel in the chains dialog returns to the main dialog without applying any new selections on the current page.

Syntax

The Selections box on the main dialog allows entering numeric ranges of segments to be selected.

The ranges are specified as a comma-separated list, using the syntax of Timo van der Laan's segment set logic.

Selections also displays any existing selections in the same format.

Ranges are specified as two segment numbers separated by a hyphen, for example 1-20 to select segments 1 through 20.

For input, single segment numbers can be specified as comma-separated items, for example 17,21 to select segments 17 and 21. in The same selections can also be specified as 17-17 and 21-21, which is the format used internally.

Initial selections are displayed as a "clean" set, with segments listed in ascending order and any overlapping selections combined. For example, selecting 35-59,1-20,17,21 would result in 1-21, 35-59 being displayed in the Selections box the next time SelectoPro runs.

If a segment range is specified out of order, SelectoPro silently corrects it. For example, the input 21-17 becomes 17-21.

SelectoPro is also somewhat flexible about extra spaces appearing in the input. Otherwise, the syntax if fairly rigid, and error messages appear if problems are detected.

Limitations

The Selections textbox has a fixed size and the text doesn't scroll, so it may not be possible to see all the selections. The selections can be selected and copied using control+a and control-c, and then pasted into another tool.

Some options may attempt invalid selections. For example, Unlocked backbone?, if combined with Invert new selections?, attempts to select locked backbone, which fails.

SelectoPro doesn't attempt to determine how segments of a given type exist, and how many are already selected. For example, the Unlocked backbone? option appears even when all segments are unlocked.

Invert new selections? applies to all selection types, so some logical operations may not be possible. It may be possible to use multiple runs to build a complex selection. A separate "invert" option for each selection type would make the dialog too large and cluttered.