Icon representing a recipe

Recipe: Sidechain Flipper 2.4

created by Wipf

Profile


Name
Sidechain Flipper 2.4
ID
103133
Shared with
Public
Parent
None
Children
Created on
July 24, 2019 at 15:35 PM UTC
Updated on
July 24, 2019 at 15:35 PM UTC
Description

Applies the sequence Flip, Freeze, Shake, Unfreeze, Wiggle, Shake, Wiggle to the different rotamer positions of the sidechains to find better sidechain configurations.
Version 2.4 offers a user interface to configure different options.

Best for


Code


---- ---- For each rotamer position of each sidechain, does a flip, freeze, shake, unfreeze, wiggle, shake, wiggle. ---- Skips the rest of the operation sequence and continues with the next rotamer position if the first shake does not improve the score. ---- behaviour = behavior -- Amino acids. Hydrophobics first. single_letter_codes = { "g","a","v","l","i","m","f","w","p","s","t","c","y","n","q","d","e","k","r","h"} three_letter_codes = { "Gly","Ala","Val","Leu","Ile","Met","Phe","Trp","Pro","Ser","Thr","Cys","Tyr","Asn","Gln","Asp","Glu","Lys","Arg","His"} ---- option default values ---- options = { -- post_scramble = false: no sidechain scrambling; won't let walk juice. -- post_scramble = true: brief scramble iff close to the best score ["post_scramble"] = true, ["scrambleThreshold"] = 2, ["min_snap_loss"] = 60, -- not a valid segment index => pseudorandom start segment computed from the current score ["start_segment"] = 0, ["quicksaves"] = { -- stores the recent best structure ["best"] = 1, -- stores a second best structure ["closest"] = 10, }, -- verbosity level: -- 0: no progress output (not recommended) -- 1: prints the segment whose sidechain is being flipped -- 2: also prints the rotamer snap indices ["verbosity"] = 2, } defaults = { -- Mode 1 (fast): Flip only Leucine and Isoleucine sidechains. Flipping these tends to be the most productive, although Glutamate is a close third. { ["xle_only"] = true, ["max_attempts"] = 3, ["min_shake_gain"] = 0.5, ["max_shake_loss"] = 3000, ["skip_freeze_shake_unfreeze"] = false, }, -- Mode 2 (default): good trade-off between speed and potential gain { ["xle_only"] = false, ["max_attempts"] = 3, ["min_shake_gain"] = 0.5, ["max_shake_loss"] = 3000, ["skip_freeze_shake_unfreeze"] = false, }, -- Mode 3 (extensive): Tries more rotamer positions. Does not require an additional sidechain to be flipped on the first shake. { ["xle_only"] = false, ["max_attempts"] = 6, ["min_shake_gain"] = -0.5, ["max_shake_loss"] = 100000, ["skip_freeze_shake_unfreeze"] = false, }, -- Mode 4 (disruptive): Foregoes the freeze-shake-unfreeze. This can result in severe disruption to the protein structure. Not terribly useful except perhaps in the opening. { ["xle_only"] = false, ["max_attempts"] = 3, ["min_shake_gain"] = -0.5, ["max_shake_loss"] = 100000, ["skip_freeze_shake_unfreeze"] = true, }, } function SetOptions( new_options ) for name,value in pairs(new_options) do options[name] = value end end function Dialog_Options() -- assemble the option dialog local ask = dialog.CreateDialog("Options") ask.level_1 = dialog.AddButton( "fast", -1 ) ask.level_2 = dialog.AddButton( "default", -2 ) ask.level_3 = dialog.AddButton( "extensive", -3 ) ask.level_4 = dialog.AddButton( "disruptive", -4 ) ask.label_1b = dialog.AddLabel( "Chooses a pseudorandom start segment, " ) ask.label_1c = dialog.AddLabel( "unless you set a valid segment index:" ) ask.start_segment = dialog.AddTextbox( "start segment", "???" ) ask.separator1 = dialog.AddLabel("") ask.leu_ile_only = dialog.AddCheckbox( "flip only Leucine and Isoleucine sidechains", false ) ask.separator2 = dialog.AddLabel("") ask.label_3d = dialog.AddLabel( "maximal number of rotamers to try for each sidechain:" ) ask.max_attempts = dialog.AddTextbox( "attempts", "???" ) ask.label_3b = dialog.AddLabel( "Rotamers too close to the current sidechain position" ) ask.label_3c = dialog.AddLabel( "are skipped automatically and not counted." ) ask.separator3 = dialog.AddLabel("") ask.label_4a = dialog.AddLabel( "If the freeze-shake-unfreeze gains fewer points," ) ask.label_4b = dialog.AddLabel( "the algorithm skips to the next rotamer:" ) ask.min_shake_gain = dialog.AddTextbox( "minimal gain", "???" ) ask.label_5a = dialog.AddLabel( "Same if the score after the freeze-shake-unfreeze" ) ask.label_5b = dialog.AddLabel( "is further away from the best than:" ) ask.max_shake_loss = dialog.AddTextbox( "maximal gap", "???" ) ask.separator5 = dialog.AddLabel("") ask.post_scramble = dialog.AddCheckbox( "do a brief scramble iff close to the best score", true ) ask.scramble_gap = dialog.AddTextbox( "threshold:", "???" ) ask.separator7 = dialog.AddLabel("") ask.label_8a = dialog.AddLabel( "The following option can result in severe disruption" ) ask.label_8b = dialog.AddLabel( "to the protein structure:" ) ask.skip_first_shake = dialog.AddCheckbox( "skip freeze-shake-unfreeze", false ) ask.separator8 = dialog.AddLabel("") ask.label_9 = dialog.AddLabel("Choose a mode to load pre-defined options:") ask.ok = dialog.AddButton( "START", 1 ) while( true ) do -- fill in the options ask.start_segment.value = tostring( options.start_segment ) ask.leu_ile_only.value = options.xle_only ask.max_attempts.value = tostring( options.max_attempts ) ask.min_shake_gain.value = tostring( options.min_shake_gain ) ask.max_shake_loss.value = tostring( options.max_shake_loss ) ask.post_scramble.value = options.post_scramble ask.scramble_gap.value = tostring( options.scrambleThreshold ) ask.skip_first_shake.value = options.skip_freeze_shake_unfreeze -- show dialog response = dialog.Show(ask) print( "Setting options...\n" ) SetOptions({ ["start_segment"] = tonumber( ask.start_segment.value ), ["max_attempts"] = tonumber( ask.max_attempts.value ), ["min_shake_gain"] = tonumber( ask.min_shake_gain.value ), ["max_shake_loss"] = tonumber( ask.max_shake_loss.value ), ["scrambleThreshold"] = tonumber( ask.scramble_gap.value ), ["xle_only"] = ask.leu_ile_only.value, ["skip_freeze_shake_unfreeze"] = ask.skip_first_shake.value, ["post_scramble"] = ask.post_scramble.value, }) if( response == 1 ) then -- start recipe return true elseif( response < 0 ) then -- set mode SetOptions( defaults[ -response ] ) else -- cancel recipe return false end -- if end -- while end function get_aa3 ( i ) k = structure.GetAminoAcid ( i ) for j = 1, 20 do if ( k == single_letter_codes [ j ] ) then return three_letter_codes [ j ] end end return "Unk" end -- -- returns a positive integer which has no common prime factor with the given integer -- more mathematically: for each integer n: Coprime(n) > 0 such that gcd(n,Coprime(n)) == 1 -- something of a hack, but it seems unlikely we'll ever have puzzles with more than 1146 residues -- function Coprime( n ) if( n == 0 ) then -- every integer is a factor of 0 -- but we only need gcd(0,Coprime(0)) == 1 return 1 elseif( n < 0 ) then n = -n end if( n%31 ~= 0 ) then return 31 elseif( n%37 ~= 0 ) then return 37 else return 1 end end function pseudorandom ( score ) -- returns a pseudorandom number >= 0 and < 1 -- based on the fractional part of the score if ( score >= 0 ) then return score % 1 else return (-score ) % 1 end end function nudge( score ) selection.SelectAll () recentbest.Save () behavior.SetClashImportance ( 0.2 + 0.2 * pseudorandom ( score ) ) structure.WiggleAll ( 1 ) behavior.SetClashImportance ( 1 ) structure.WiggleAll ( 8 ) recentbest.Restore () end function quick_nudge( score ) selection.SelectAll () recentbest.Save () behavior.SetClashImportance ( 0.1 + 0.1 * pseudorandom ( score ) ) structure.ShakeSidechainsAll ( 1 ) behavior.SetClashImportance ( 1 ) structure.WiggleAll ( 8 ) structure.ShakeSidechainsAll ( 1 ) structure.WiggleAll (1,false,true) recentbest.Restore() end function FlipAllSidechains() local segment_count = structure.GetCount() if( nil == start_segment or start_segment < 1 or start_segment > segment_count ) then start_segment = math.floor( 1 + segment_count * pseudorandom( best_score ) ) end -- go through the protein in a less than regular way increment = Coprime( segment_count ) while( increment >= segment_count ) do increment = increment - segment_count end segment = start_segment for i = 1,segment_count do -- flip the sidechain amino_acid = get_aa3( segment ) if( options.verbosity >= 1 ) then print( i, "/", segment_count .. ":", amino_acid, segment ) end if( options.xle_only == false or amino_acid == "Leu" or amino_acid == "Ile" ) then FlipSidechain( segment ) end -- move on to next segment segment = segment + increment if( segment > segment_count ) then segment = segment - segment_count end end end function FlipSidechain( segment ) r = 1 improvement_made = false -- Lysines and arginines can have 40 or rotamers and its wasteful to go through them all: flipping the -- sidechains of those amino acids rarely works anyway -- Till there's something in the API that allows identification of the residue, restrict the number of attempts n_attempts = 0 while ( r <= rotamer.GetCount(segment) and improvement_made == false and n_attempts < options.max_attempts ) do rotamer.SetRotamer( segment, r ) score_after_snap = current.GetEnergyScore() snap_loss = best_score - score_after_snap if( snap_loss < 0 ) then -- can conceivably happen save.Quicksave( options.quicksaves.best ) best_score = score_after_snap improvement_made = true print ( "Immediate improvement at segment", segment ) print ( "best score:", best_score ) n_attempts = n_attempts + 1 elseif( snap_loss > options.min_snap_loss ) then -- if snapping changes it by less than 100, this technique is unlikely to produce a gain n_attempts = n_attempts + 1 if( options.verbosity >= 2 ) then print( "", "attempt " .. n_attempts .. ":", "rotamer", r, "/", rotamer.GetCount( segment ) ) end -- freeze-shake-unfreeze -- gain_from_shake = 0 if( not options.skip_freeze_shake_unfreeze ) then freeze.Freeze( segment, false, true ) structure.ShakeSidechainsAll( 1 ) freeze.Unfreeze( segment, false, true ) score_after_shake = current.GetEnergyScore() gain_from_shake = score_after_shake - score_after_snap else score_after_shake = score_after_snap gain_from_shake = 0 end if( gain_from_shake >= options.min_shake_gain and best_score - score_after_shake < options.max_shake_loss ) then -- wiggle-shake -- structure.WiggleAll( 15 ) score_after_wiggle = current.GetEnergyScore() structure.ShakeSidechainsAll( 1 ) score_after_second_shake = current.GetEnergyScore() if( score_after_second_shake - score_after_wiggle > 0.5 ) then -- wiggle -- structure.WiggleAll( 10 ) score_after_second_wiggle = current.GetEnergyScore() if( score_after_second_wiggle - score_after_second_shake < 1 and best_score - score_after_second_wiggle < 30 ) then nudge( score_after_second_wiggle ) score = current.GetEnergyScore() else score = score_after_second_wiggle end else score = score_after_second_shake end if( best_score - score < options.scrambleThreshold and options.post_scramble ) then quick_nudge( score ) score = current.GetEnergyScore() end if( score > best_score ) then structure.ShakeSidechainsAll( 1 ) save.Quicksave( options.quicksaves.best ) best_score = current.GetEnergyScore() --print( "best score ", best_score , " snap loss " , snap_loss , " shake gain " , gain_from_shake ) print( "new best score:", best_score ) improvement_made = true; end if( improvement_made == false ) then -- might also be an immediate improvement from above -- Wipf 2019-Mar-10 if( score > score_closest ) then save.Quicksave( options.quicksaves.closest ) score_closest = score end end end -- if shake improved things end -- if not current rotamer position r = r + 1 save.Quickload( options.quicksaves.best ) end -- loop over all rotamers end ---- main script ---- -- tidy up the protein freeze.UnfreezeAll() band.DisableAll() behaviour.SetClashImportance( 1 ) -- initialize SetOptions( defaults[2] ) -- mode 2 is default mode save.Quicksave( options.quicksaves.best ) best_score = current.GetEnergyScore() score_closest = 0 print( "best score:", best_score ) -- gives the user the choice to cancel the recipe here if( Dialog_Options() ) then FlipAllSidechains() end

Comments


Wipf Lv 1

This new version of the Sidechain Flipper recipe offers a user interface to configure some options. The four buttons next to the start button allow loading different sets of default settings. These 'modes' (as I like to call them) have been in the source code (probably for a long time) as 'level 1' to 'level 4', with 'level 2' being the default.

The option dialog got quite high, because I wanted to include some explanation, what the options do. It takes up more than half the height of my screen and I guess it might be a problem, if someone is playing with a really small screen. If so please let me know.
This is the first user interface I wrote for a foldit recipe, so if you have suggestions for improving it, feel free to write them here as a comment (or as a message directly to me). I will consider them when I find time for it (be warned that that may take a few month).

==== CHANGELOG ====
July 2019: – Version 2.4, release 1 –
July 2019: a few minor improvementi
May 2019: made mode selection in option dialog work
May 2019: cleaned up main script to use new function SetOptions for setting the options May 2019: moved options into table 'options'
Mar 2019: made post_scramble a boolean instead of an integer
Mar 2019: replaced UnfreezeAll by a selective Unfreeze
Mar 2019: replaced select-freeze for single segment by a call to freeze.Freeze()
Mar 2019: reordered main script
Mar 2019: added Dialog_Options()
Mar 2019: – Version 2.3, release 1 –