Icon representing a recipe

Recipe: AA Copy Paste Compare v 1.1.1 -- Brow42

created by brow42


AA Copy Paste Compare v 1.1.1 -- Brow42
Shared with
Created on
September 23, 2012 at 03:03 AM UTC
Updated on
September 23, 2012 at 03:03 AM UTC

Read and set the primary and secondary structure in a copy/paste friendly dialog. Also can list changes you made.

Best for


--[[ =================================== * AA Copy Paste Compare * Original Author: Brow42 * Version 0.5 Feb. 1 2012 * Easy way to copy / paste a new primary sequence or * secondary structure assignment. Detects changes * Version 1.0 Mar. 13 2012 * Bugfix: error exiting Write if Undo disabled * Version 1.1 Aug. 4 2012 * Added a dialog for easy copying of compare results * instead of using read dialog. * Added checkbox to reverse initial/final for comparison * Version 1.1.1 Sep. 22 2012 * Change detection was broken, syntax errors, * if insert/delete were in the editlist --]] title = 'AA Copy Paste Compare v 1.1' qsave_slot = nil -- set on first menu qsave_err = 100 --beta_date = os.time({year=2012, month=3, day=15}) -- ================================== Begin Function Wrappers -- These must always be at the top of a file -- Remove the problematic molecule e.g. puzzle 471 structure._GetCount = structure.GetCount function structure.HasMolecule() return structure.GetSecondaryStructure(structure._GetCount())=='M' end function structure.GetCount() local n = structure._GetCount() while structure.GetSecondaryStructure(n) == 'M' do n = n - 1 end return n end -- ================================== End Function Wrappers -- ================================== Begin New Dialog Library Functions -- Add a wall of text from a table function dialog.AddLabels(d,msg,nlabels) -- need to manually pass in # of existing autolabels nlabels = nlabels or 0 if type(msg) == 'string' then msg = { msg } end for i = 1,#msg do d['autolabel'..tostring(i+nlabels)] = dialog.AddLabel(msg[i]) end end -- Create but don't display a wall of text and 1 or 2 buttons function dialog.CreateMessageBox(msg,title,buttontext1,buttontext0) title = title or '' local d = dialog.CreateDialog(title) dialog.AddLabels(d,msg) buttontext1 = buttontext1 or 'Ok' d.button = dialog.AddButton(buttontext1,1) if buttontext0 ~= nil then d.button0 = dialog.AddButton(buttontext0,0) end return d end -- Display a dialog box function dialog.ShowMessageBox(msg,title,buttontext1,buttontext0) return dialog.Show(dialog.CreateMessageBox(msg,title,buttontext1,buttontext0)) end -- Removes a control named 'control' created as d.control = dialog.AddControl() function dialog.DeleteControl(d,control) local err = string.format('No such control \'%s\'',control) if d[control] == nil then error(err) end d[control] = nil for i = 1,#d._Order do if d._Order[i] == control then table.remove(d._Order,i) -- renumbers arrays return end end error(err) end -- ================================== End New Dialog Library Functions -- ================================== Begin Dialogs -- Create all the dialogs used by the program in table 'dialogs' function CreateDialogs() dialogs = {} -- multipage help local pages = { { 'Primary and secondary structure can be', 'compressed or verbose.', '', 'Verbose: a sequence of 1 letter codes representing', 'AAs(*) (primary) or EHL(**) (secondary). The range', 'field, if not blank, is the starting and ending AA to be', 'read, to only read part of the protein structure. When', 'setting the structure, provide the first AA to be', 'changed (if not 1).', '', '(*) AAs = ACDEFGHIKLMNPQRSTVWY', '(**) HG = Helix; E = Sheet; IBST = Loop;', ' <space> = no-change, M = ignored' }, { 'Compressed: For primary structure, you can just ', 'specify the AAs you want to change: "Y 10 K 11 P 20" ', 'would change 3 AAs, the rest is unchanged. For', 'secondary structure, everything in the range is option-', 'ally set to loop and then you specify 1 number to', 'change 1 AA or 2 numbers to change a range of AAs:', '"E 10 H 15-20 E 23-27 33-38" would make a helix,', '2 sheets, and an isolated sheet segment.' }, { 'Undo: Your original structure is always available', 'in a user-selected quicksave slot. This can also be', 'loaded by hitting Undo All in the write menu.', 'If an error occurs while writing PS or SS, it', 'should undo those changes. If you perform a compare,', 'your structure before the Compare will be loaded', 'when you return to the Read menu.' } } dialogs.about = {} for i = 1,#pages do local d = dialog.CreateDialog(title) dialog.AddLabels(d,pages[i]) if i > 1 then d.back = dialog.AddButton('Previous',-1) end if i < #pages then d.next = dialog.AddButton('Next',1) end d.exit = dialog.AddButton('Return',0) table.insert(dialogs.about,d) end -- Main dialog local main = { 'This recipe will read the current primary and secondary', 'structure and present them so that you can copy them', 'to your clipboard.', 'You can also enter (paste) a primary or secondary', 'structure. and the recipe will change the protein to', 'match.' } dialogs.main = dialog.CreateDialog(title) if beta_date then dialogs.main.beta = dialog.AddLabel(string.format( ' * This is a BETA recipe that expires %s.',os.date('%b. %d %Y',beta_date))) end dialog.AddLabels(dialogs.main,main) dialogs.main.undomsg1 = dialog.AddLabel('Pick a save slot now for Undo or 0 to disable Undo.') dialogs.main.undomsg2 = dialog.AddLabel('(Use one that is unused, 1-3 good in case of cancel.)') dialogs.main.slot = dialog.AddSlider('Save Slot:',1,0,10,0) dialogs.main.read = dialog.AddButton('Read',1) dialogs.main.write = dialog.AddButton('Write',2) dialogs.main.help = dialog.AddButton('Help',3) dialogs.main.exit = dialog.AddButton('Exit',0) -- Read Dialog local read = { 'Click Read to read the current PS & SS for export.', 'Enter a start segment or segment range (start-end)', 'to only read part of the protein.', 'Click Changes to compare against other structures', 'and find your changes.' } dialogs.read = dialog.CreateDialog('Export Structure') dialog.AddLabels(dialogs.read,read) dialogs.read.range = dialog.AddTextbox('Read Range:','') dialogs.read.onlym = dialog.AddCheckbox('Mutable Only:',false) dialogs.read.primary = dialog.AddTextbox('Primary S.:','') dialogs.read.sec1 = dialog.AddTextbox('Secondary S.:','') dialogs.read.sec2 = dialog.AddTextbox('Secondary S.:','') dialogs.read.read = dialog.AddButton('Read',1) dialogs.read.detect = dialog.AddButton('Changes',3) dialogs.read.back = dialog.AddButton('Back',2) -- Write Dialog dialogs.write = dialog.CreateDialog('Import Structure') dialogs.write.range = dialog.AddTextbox('Write Start Seg:','') dialogs.write.primary = dialog.AddTextbox('Primary:','') dialogs.write.setloops = dialog.AddCheckbox('Default to Loops:',false) dialogs.write.sec1 = dialog.AddTextbox('Secondary S.:','') dialogs.write.write = dialog.AddButton('Write',1) dialogs.write.back = dialog.AddButton('Back',2) dialogs.write.clear = dialog.AddButton('Clear',4) dialogs.write.undo = dialog.AddButton('Undo All',3) -- Changes Dialog dialogs.change = dialog.CreateDialog('List Structure Changes') local change = { '\'Next Start\' will reset the puzzle. You may have to', 'loop through several to compare against the start', 'you want. \'This Start\' will automatically loop', 'through all starts.' } dialog.AddLabels(dialogs.change,change) dialogs.change.label = dialog.AddLabel('Pick one compare method') dialogs.change.next = dialog.AddCheckbox('Compare against NEXT start (one reset)',false) dialogs.change.this = dialog.AddCheckbox('Compare against THIS start (by looping)',false) dialogs.change.ss9 = dialog.AddCheckbox('Compare against Save SS slot 9',false) dialogs.change.user = dialog.AddCheckbox('Compare against User slot #',false) -- dialogs.change.ssst = dialog.user('Compare against ST SS Save',false) dialogs.change.slot = dialog.AddSlider('',1,1,3,0) dialogs.change.reverse = dialog.AddCheckbox('REVERSE the comparison',false) dialogs.change.compare = dialog.AddButton('Compare',1) dialogs.change.back = dialog.AddButton('Back',0) -- Changes Result dialog dialogs.diff = dialog.CreateDialog('Compare Results') dialogs.diff.label = dialog.AddLabel('') dialogs.diff.primary = dialog.AddTextbox('Comp. Pri','') dialogs.diff.secondary = dialog.AddTextbox('Secondary','') dialogs.diff.sscompressed = dialog.AddTextbox('Comp. Sec.','') dialogs.diff.ok = dialog.AddButton('Okay',1) end -- ============================== End Dialogs -- ============================== Begin Work Functions -- Set the user-selected quicksave and delete uneeded controls function SetQuicksave() qsave_slot = dialogs.main.slot.value dialog.DeleteControl(dialogs.main,'undomsg1') dialog.DeleteControl(dialogs.main,'undomsg2') if qsave_slot > 0 then save.Quicksave(qsave_slot) print('Saving to slot',qsave_slot) dialogs.main.slot = dialog.AddLabel(string.format('Original structure available in slot #%d',qsave_slot)) else print('No undo slot selected...undo disabled') dialog.DeleteControl(dialogs.write,'undo') dialogs.main.slot = dialog.AddLabel('Undo has been disabled.') end end -- is there another way to do this? string.subs I guess function string.at(str,i) return string.char(string.byte(str,i)) end -- Recursive table printer (no cycles please) 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 -- Group up identical symbols into symbol,range table function Compress(str,start) local sym = '' local count = 0 local result = {} for i = 1,#str+1 do if string.at(str,i) == sym then count = count + 1 else if count > 0 then result[#result+1] = {sym,start+i-1-count,start+i-2} end count = 1 sym = string.at(str,i) end end return result end -- Turn compressed range string into table of ranges -- The was more complicated than I'd envisioned -- returns nil if error function ParseCompressed(str) -- First turn string into letter - number pairs local init = 1 local tab = {} local ranging = false -- true means range extension is disallowed local lower = 0 -- lower value of range, 0 illegal local sym = '' -- symbol being compressed, empty illegal while init <= #str do local a,b,c,d = string.find(str, ' *([%a%-]?) *(%d+) *',init) if a == nil then break end init = b+1 -- continuing a range, check if it's valid if c == '-' then if ranging == true then dialog.ShowMessageBox('Multiple range numbers (a range is number - number)','Input Error') return nil end if sym == '' or lower == 0 then dialog.ShowMessageBox('Initial range (a range continues a structure number pair)','Input Error') return nil end tab[#tab+1] = {sym, lower, tonumber(d)} ranging = true lower = 0 -- starting a new range with explicit symbol elseif c ~= '' then ranging = false if sym ~= '' and lower > 0 then tab[#tab+1] = {sym,lower,lower} end sym = c lower = tonumber(d) ranging = false -- starting a new range with same symbol elseif sym ~= '' then if lower > 0 then tab[#tab+1] = {sym,lower,lower} end lower = tonumber(d) ranging = false else dialog.ShowMessageBox({'Missing structure symbol (a range starts', 'with a structure number pair'},'Input Error') end end if lower > 0 then tab[#tab+1] = {sym,lower,lower} end return tab end function PrintCompressed(tab,exclude) exclude = exclude or {} exclude[''] = true local str,sep = '','' for i = 1, #tab do local x = tab[i] if exclude[x[1]] == nil then if x[3] ~= nil and x[3] > x[2] then str = string.format('%s%s%s %d-%d',str,sep,x[1],x[2],x[3]) else str = string.format('%s%s%s %d',str,sep,x[1],x[2]) end sep = ' ' end end return str end -- Turn a compressed table into a string with space padding function Uncompress(tab,exclude) local tmp = {} exclude = exclude or {} exclude[''] = true local max = 0 for i = 1, #tab do local x = tab[i] if exclude[x[1]] == nil then for j = x[2],x[3] do tmp[j] = x[1] end if x[3] > max then max = x[3] end end end for i = 1,max do tmp[i] = tmp[i] or ' ' end return table.concat(tmp,'') end -- Parse the user input for ranges, with error checking function ParseRangeString(str) local n = structure.GetCount() local tmp = {} for v in string.gfind(dialogs.read.range.value,'%d+') do tmp[#tmp+1] = tonumber(v) end local first , last = tmp[1] or 1, tmp[2] or n if first > last then first,last = last,first end if last > n then dialog.ShowMessageBox(string.format('Your range runs too high (max %d segments)',n),'Input Error') last = n end if first < 1 then dialog.ShowMessageBox('Your range runs too low (min is 1 of course)','Input Error') first = 1 end return first,last end -- Turn a string that may or may not be compressed into a symbol,range table function StringToTable(str,startstr) startstr = startstr or '' local compressed = string.find(str,'%d') ~= nil local tab if compressed then if startstr ~= '' then dialog.ShowMessageBox({'Reminder: the start/range value is ignored','when using compressed notation.'},'Info') end tab = ParseCompressed(str) if (tab == nil) then dialog.ShowMessageBox('Error parsing compressed string.') return nil end else local start = 1 if startstr ~= '' then start = tonumber(startstr) end tab = Compress(str,start) end return tab end -- Turn a string that may or may not be compressed into a symbol,range table function StringToTable(str,startstr) startstr = startstr or '' local compressed = string.find(str,'%d') ~= nil local tab if compressed then if startstr ~= '' then dialog.ShowMessageBox({'Reminder: the start/range value is ignored','when using compressed notation.'},'Info') end tab = ParseCompressed(str) if (tab == nil) then dialog.ShowMessageBox('Error parsing compressed string.') return nil end else local start = 1 if startstr ~= '' then start = tonumber(startstr) end tab = Compress(str,start) end return tab end -- returns Hamming distance and a sparse array of deltas containing only substitutions function string.hamming(initial,final) if #initial ~= #final then error('Strings of different length passed to hamming()') end local deltas = {} for i = 0,#initial do deltas[i] = {} end local changes = 0 deltas[0][0] = changes for i = 1,#final do if initial[i] ~= final[i] then changes = changes + 1 end deltas[i][i] = changes end return changes,deltas end function PrintDeltaTable(initial,final,deltas) local line = ' . .' for i = 1,#final do line = line..' '..final[i] end print(line) line = '' for j = 0,#initial do if j > 0 then line = ' '..initial[j] else line = ' .' end for i = 0,#final do local x = deltas[j][i] line = line..(x and string.format('%3d',x) or ' . ') end print(line) end end function LevenshteinDistance(initial,final) local deltas = {} for j = 0,#initial do deltas[j] = {} end local i,j = 0,0 for i = 1, #final do deltas[0][i] = i end for j = 1, #initial do deltas[j][0] = j end deltas[0][0] = 0 for i = 1, #final do for j = 1, #initial do if initial[j] == final[i] then deltas[j][i] = deltas[j-1][i-1] else deltas[j][i] = math.min(deltas[j-1][i],deltas[j-1][i-1],deltas[j][i-1]) + 1 end end end return deltas[#initial][#final],deltas end -- Get (an) optimal edit sequence from the delta table function EditSequence(initial,final,deltas) local subs = {} local insdels = {} local i,j = #final,#initial local op, v -- next step to take local NOP, SUB, INS ,DEL = 0,1,2,3 -- three possible steps to take local v1,v2,v3 -- score of possible steps while i > 0 and j > 0 do -- backtrack until origin v1, v2, v3 = deltas[j-1][i-1] or math.huge, deltas[j][i-1] or math.huge, deltas[j-1][i] or math.huge -- Find best route, prefering sub if best or equal score to others op, v = SUB, v1 if v2 < v then op,v = INS, v2 end if v3 < v then op,v = DEL, v3 end -- use 'i' the position in final string, which assumes all preceeding ins/dels have been executed if op == SUB then if final[i] ~= initial[j] then subs[#subs+1] = {final[i],i} end i,j = i-1,j-1 elseif op == INS then -- I swear this seems backwards subs[#subs+1] = {final[i],i} insdels[#insdels+1] = {'+',i} i,j = i-1,j else insdels[#insdels+1] = {'#',i} i,j = i,j-1 end end -- combine and reverse edits local edits = {} for i = 1,#insdels do edits[i] = insdels[#insdels - i + 1] end for i = 1,#subs do edits[i+#insdels] = subs[#subs - i + 1] end return edits end -- ============================== End Work Functions -- ============================== Begin Read Functions -- Read primary and secondary structure and populate dialog fields function ReadStructure() local n = structure.GetCount() local tmp = {} local first, last = ParseRangeString(dialogs.read.range.value) local tmp2 = {} tmp = {} local rc, aa for i = first,last do if dialogs.read.onlym.value == false or structure.IsMutable(i) == true then tmp[#tmp + 1] = structure.GetAminoAcid(i) tmp2[#tmp2 + 1] = structure.GetSecondaryStructure(i) else tmp[#tmp + 1] = ' ' tmp2[#tmp2 + 1] = ' ' end end -- if (#tmp < last) then last = #tmp end dialogs.read.range.value= string.format('%d - %d',first,last) dialogs.read.primary.value = table.concat(tmp,'') dialogs.read.sec1.value = table.concat(tmp2,'') local dontprintlist = {M=true} dontprintlist[' '] = true if dialogs.read.onlym.value == false then dontprintlist['L'] = true end dialogs.read.sec2.value = PrintCompressed(Compress(dialogs.read.sec1.value,first),dontprintlist) print('Structure read from range:',first,'to',last) print('Primary Structure (AA sequence):') print(dialogs.read.primary.value) print('Secondary Structure:') print(dialogs.read.sec1.value) print('Compressed Secondary Structure:') print(dialogs.read.sec2.value) end -- ============================== End Read Functions -- ============================== Begin Write Functions function WritePrimaryStructure() local n = structure.GetCount() local tab local input = string.lower(dialogs.write.primary.value) local aalist = string.lower('ACDEFGHIKLMNPQRSTVWY +#') local aa = {} -- valid aa lookup table for i = 1,#aalist do aa[string.at(aalist,i)] = true end tab = StringToTable(input,dialogs.write.range.value) if tab == nil then return false end -- Error check AA input for i = 1,#tab do local x = tab[i] if x[2] < 1 or x[2] > n or x[3] < 1 or x[3] > n then dialog.ShowMessageBox( {'Your primary structure is out of range','(check start and length)'} ,'Input Error') return false end if aa[x[1]] == nil then dialog.ShowMessageBox(string.format( 'Unrecognized AA symbol: %s at %d',x[1],x[2]),'Input Error') return false end if x[1] == '+' or x[1] == '#' then dialog.ShowMessageBox( {'Foldit has no way to insert or delete segments','in recipes. You must do it by hand.'}, 'Sorry') return false end end -- Set AA for i = 1,#tab do local x = tab[i] if x[1] ~=' ' then if x[3] == nil or x[2] == x[3] then structure.SetAminoAcid(x[2],x[1]) else selection.DeselectAll() selection.SelectRange(x[2],x[3]) structure.SetAminoAcidSelected(x[1]) end end end return true end function WriteSecondaryStructure() local n = structure.GetCount() local tab local input = string.upper(dialogs.write.sec1.value) local ss = { H='H', E='E', L='L', I='L', B='L', S='L', T='L', M=' ', G='H'} ss[' '] = ' ' -- table of allowed SS, reduced to 'EHL ' tab = StringToTable(input,dialogs.write.range.value) if tab == nil then return false end -- Error check SS input for i = 1,#tab do local x = tab[i] if x[2] < 1 or x[2] > n or x[3] < 1 or x[3] > n then dialog.ShowMessageBox( {'Your secondary structure is out of range','(check start and length)'} ,'Input Error') return false end x[1] = ss[x[1]] if x[1] == nil then dialog.ShowMessageBox(string.format( 'Unrecognized AA symbol: %s at %d',x[1],x[2]),'Input Error') return false end end -- Set SS if dialogs.write.setloops.value == true then selection.SelectAll() structure.SetSecondaryStructureSelected('L') end for i = 1,#tab do local x = tab[i] if x[1] ~= ' ' then -- no changes for ' ' if x[2] == x[3] then structure.SetSecondaryStructure(x[2],x[1]) else selection.DeselectAll() selection.SelectRange(x[2],x[3]) structure.SetSecondaryStructureSelected(x[1]) end end end return true end -- Error trapping wrapper for setting PS and SS function WriteStructure() local rc selection.DeselectAll() rc = WritePrimaryStructure() if rc == false then return false end rc = WriteSecondaryStructure() return rc end -- ============================== End Write Functions -- ============================== Begin Compare Functions -- Apply the PS edits to the SS so that length matches function ApplyEditsToSS(editlist,sequence) for i = 1,#editlist do if editlist[i][1] == '+' then table.insert(sequence,editlist[i][2],' ') elseif editlist[i][1] == '#' then table.remove(sequence,editlist[i][2]) end end end function CompareSecondary(initial,final) local diff = {} for i = 1,#initial do if initial[i] == ' ' or final[i] == initial[i] then diff[i] = ' ' else diff[i] = final[i] end end local difflist1, difflist2 local dontprintlist = {M=true} dontprintlist[' '] = true difflist1 = table.concat(diff,'') difflist2 = PrintCompressed(Compress(difflist1,1),dontprintlist) print('SS differences:') print(difflist1) print('Compressed Secondary Structure:') print(difflist2) dialogs.diff.secondary.value = difflist1 dialogs.diff.sscompressed.value = difflist2 end function ComparePrimary(initial,final) local edit_distance, edit_table = LevenshteinDistance(initial,final) print('Edit distance:',edit_distance) edit_sequence = EditSequence(initial,final,edit_table) local psdiff = PrintCompressed(edit_sequence) print('PS differences:') print(psdiff) dialogs.diff.primary.value = psdiff return edit_sequence end n_starts = 0 -- computed on first auto-loop resetscore = nil startcounter = 0 -- Work out what to compare against, load it, then do comparisons function CompareStructure(current_ss,current_ps) local function GetNextStart() puzzle.StartOver() print('Resetting...score =',current.GetScore()) if resetscore == nil then resetscore = current.GetScore() elseif n_starts == 0 and resetscore == current.GetScore() then n_starts = startcounter print('Determined the number of starts to be',n_starts) end startcounter = startcounter + 1 src = 'Next Reset' end local function GetThisStart() print('Looping through all starts to get back to current start...') if (n_starts == 0) then print('First need to determine the number of starts...') end local i = 1 GetNextStart() while n_starts == 0 do GetNextStart() i = i + 1 end while i % n_starts > 0 do GetNextStart() i = i + 1 end src = 'This Reset' end src = '' local function GetSS9() print('Loading SS from Saved Structure') src = 'Saved Structure' save.LoadSecondaryStructure() return true end local function GetSSST() dialog.ShowMessageBox('ST Save SS not implemented','Sorry') src = 'ST Save SS' return false end local function GetUser() print('Loading PS & SS from save #',dialogs.change.slot.value) src = 'Quicksave Slot #'..tostring(dialogs.change.slot.value) save.Quickload(dialogs.change.slot.value) return true end local options = { 'next', 'this', 'ss9', 'ssst', 'user' } -- checkboxes local functions = { GetNextStart, GetThisStart, GetSS9, GetSSST, GetUser } local selection = 0 local n_selected = 0 for i,v in ipairs(options) do if dialogs.change[v] ~= nil then -- in case I delete a box if dialogs.change[v].value == true then selection = i n_selected = n_selected + 1 end end end if n_selected ~= 1 then dialog.ShowMessageBox('Pick one save to compare against.','Input Error') return end local rc = functions[selection]() -- Load the desired comparison structure if rc == false then print('Unable to load comparison structures') return end first, last = 1,structure.GetCount() local initial_ss, initial_ps = {},{} for i = first,last do initial_ss[i] = structure.GetSecondaryStructure(i) initial_ps[i] = structure.GetAminoAcid(i) end if dialogs.change.reverse.value then dialogs.diff.label.label = 'Computing changes from Current to '..src current_ss,initial_ss = initial_ss,current_ss current_ps,initial_ps = initial_ps,current_ps else dialogs.diff.label.label = 'Computing changes from '..src..' to Current' end print(dialogs.diff.label.label) local editlist = {} editlist = ComparePrimary(initial_ps,current_ps) ApplyEditsToSS(editlist,initial_ss) if (#initial_ss ~= #current_ss) then print(table.concat(initial_ss)) print(table.concat(current_ss)) error('Initial and current SS aren\'t the same length after applying edits!') end CompareSecondary(initial_ss,current_ss) dialog.Show(dialogs.diff) end -- ============================== End Compare Functions -- ===================== Begin Dialog Logic -- Dialog to test the compress/decompress functions function Test() local d = dialog.CreateDialog('Test') d.input = dialog.AddTextbox('Input','') d.out1 = dialog.AddTextbox('Uncomp.','') d.out2 = dialog.AddTextbox('Comp.','') d.b1 = dialog.AddButton('Test',1) d.b2 = dialog.AddButton('Exit',0) while true do local rc = dialog.Show(d) if rc == 0 then return end local tab = StringToTable(d.input.value) if tab ~= nil then d.out2.value = PrintCompressed(tab) d.out1.value = Uncompress(tab) end end end function DoDetectMenu() local d = dialogs.change -- save the structure for later in case we use reset save.Quicksave(qsave_err) -- get the current ss now in case we reset local current_ss, current_ps = {}, {} for i = 1,structure.GetCount() do current_ss[i] = structure.GetSecondaryStructure(i) current_ps[i] = structure.GetAminoAcid(i) end repeat local rc = dialog.Show(d) if rc == d.compare.value then CompareStructure(current_ss,current_ps) end until rc == d.back.value save.Quickload(qsave_err) end function DoReadMenu() local d = dialogs.read repeat local rc = dialog.Show(d) if rc == d.read.value then ReadStructure() elseif rc == d.detect.value then DoDetectMenu() end until rc == d.back.value end function DoWriteMenu() local d = dialogs.write repeat local rc = dialog.Show(d) if rc == d.write.value then save.Quicksave(qsave_err) local rc1,rc2 = pcall(WriteStructure) if rc1 == false then print('Error message:',rc2) end if rc1 == false or rc2 == false then print('Undoing attempted changes.') save.Quickload(qsave_err) end selection.DeselectAll() elseif d.undo and rc == d.undo.value then save.Quickload(qsave_slot) print('All changes to primary and secondary structure undone.') dialog.ShowMessageBox('All changes to primary and sec. structure undone!') elseif rc == d.clear.value then d.primary.value = '' d.sec1.value = '' d.range.value = '' end until rc == d.back.value end function ShowAbout() local i = 1 repeat local rc = dialog.Show(dialogs.about[i]) -- +- 1 or 0 i = i + rc until rc == 0 end function DoMainMenu() local d = dialogs.main repeat local rc = dialog.Show(d) if rc ~= d.exit.value and qsave_slot == nil then SetQuicksave() end if rc == d.help.value then ShowAbout() -- Read Submenu elseif rc == d.read.value then DoReadMenu() -- Write Submenu elseif rc == d.write.value then DoWriteMenu() end until rc == d.exit.value end -- ===================== End Dialog Logic -- ===================== Begin Main --Test() if beta_date and beta_date < os.time() then dialog.ShowMessageBox({'This beta software has expired.','Please download a new copy today!'},'Recipe Update') end if structure.HasMolecule() then print('Note: structure has a molecule, which will be ignored.') end print(puzzle.GetName(),os.date(),ui.GetTrackName(),current.GetScore()) CreateDialogs() DoMainMenu()


brow42 Lv 1

This script makes it easy to read or set the primary structure (amino acid sequence) and secondary structure (loop, helix, sheet) using dialogs. Now, instead of making a script for each puzzle to set the structure, you can just paste it into the dialog box.


Reading SS from SAM predictions or FASTA file. The devs usually provide a SS prediction in the comments of each puzzle. Note that sometimes they want you to guess the SS, so don't use external sources.

Setting the initial state of a denovo freestyle or design puzzle. Very painful to do by hand, and annoying to script it.

Keeping track of all the changes you've made to a protein is hard. This will find the differences between your current protein, and another saved protein in a save slot or different reset. It will find mutations, insertions, deletions, and SS changes.

Once you've found your differences, you can share them with other players, and they can paste them in!

Output is both a string of letters, and letter-number or letter-range notation.


This was a huge script to write spread over many weeks. I'm releasing it now so that you can use it with the current flu design puzzle.

Looping through resets assumes that each reset is already threaded with a unique score.

The Compare function was written last and has seen the least testing.

The Compare function will detect insertions, deletions, and mutations, but only mutations are possible in Lua. You must do the insertions/deletions by hand, and then the script can do the mutations.

Bug Reports and Suggestions:

Please PM or post below bugs or obviously wrong answers. Use the 'Read' Function to give me PS and SS of your initial protein, and also what you were trying to write, or compare against.

Beta Notice:

The script will nag you to update until I release version 1.0.

brow42 Lv 1

Patch 1.1: Added a dialog to present the results of comparing the current structure to a saved structure. Previously it put the results into the read dialog, which was confusing. Remember you can copy text right out of dialog boxes.

Added a check box to reverse the roles of current and saved structure. Normally it prints the changes needed to go from the saved structure to the current structure. When checked, it gives the changes needed to from the current structure to saved structure.

Good for comparing SAM predictions with server folds or CASP provided fold.

brow42 Lv 1

A syntax error in reporting the changes between one primary sequence and another. It only appears if the edit list contains insertions and deletions. I've fixed this.

I'm not convinced its giving the shortest edit list. Also, players may want a pure substitution list instead. Expect an update in the near future.