Icon representing a recipe

Recipe: Sidechain Flipper 2.3

created by Wipf

Profile


Name
Sidechain Flipper 2.3
ID
103041
Shared with
Public
Parent
V2 Sidechain Flipper 2.2
Children
None
Created on
March 10, 2019 at 15:39 PM UTC
Updated on
March 10, 2019 at 15:39 PM UTC
Description

Based on V2 SidechainFlipper 2.2 by CharlieFortsConscience, this improved version determines the number of rotamers just before setting the rotamer in order to ensure that the snap index is always valid.

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. ---- -- if this is a residue id, use it as the start point. If not -- use a semi-random number computed from the score start_idx = 0 function Dialog_ChooseLevel() ask = dialog.CreateDialog("Level?") --ask.level = dialog.AddSlider( "Level", 2, 1, 4, 0 ) --ask.ok = dialog.AddButton( "OK", 0 ) ask.level1 = dialog.AddButton( "Level 1", 1 ) ask.level2 = dialog.AddButton( "Level 2", 2 ) ask.level3 = dialog.AddButton( "Level 3", 3 ) ask.level4 = dialog.AddButton( "Level 4", 4 ) return dialog.Show( ask ) end -- level 1: Flip Leu and Ile only. Flipping these tend to be the most productive, although Glu is a close third. -- level 2: Default. Best trade-off between potential gain and speed. -- level 3: Tries more positions. Unlike the lower 2 levels, does not require that on the first shake an additional side chain be flipped. -- level 4: Foregoes the freeze-shake-unfreeze bit of the algorithm entirely. This can result in severe disruption to the protein structure. -- Not terribly useful except perhaps in the opening. level = 2 manual_level_set = false -- post_scramble = 1: no sidechain scramble. Won't leak walk juice. -- post_scramble = 2. does a brief scramble iff a flip gets within 2 (see kScrambleThreshold) points of the original. post_scramble = 2 kScrambleThreshold = 2 -- verbosity level: -- 0: prints no progress output (not recommended) -- 1: prints the residue the recipe is working on -- 2: also prints the rotamer snap indices verbosity = 2 -- 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"} -- Use of save.Quicksave slots: change if desired kBest = 1 -- used to hold the initial structure and any improved structures kClosest = 10 -- holds the best scoring structure that does not exceed that in kBest 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() -- go through the protein in a less than regular way segment_count = structure.GetCount() increment = Coprime( segment_count ) while( increment >= segment_count ) do increment = increment - segment_count end segment = start_idx for i = 1,segment_count do -- flip the sidechain amino_acid = get_aa3( segment ) if( verbosity >= 1 ) then print( i, "/", segment_count .. ":", amino_acid, segment ) end if( leu_ile_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 < 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( kBest ) 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 > 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( verbosity >= 2 ) then print( "", "attempt " .. n_attempts .. ":", "rotamer", r, "/", rotamer.GetCount( segment ) ) end gain_from_shake = 0 if( level < 4 ) then selection.DeselectAll() selection.Select( segment ) freeze.FreezeSelected ( false , true ) selection.SelectAll() structure.ShakeSidechainsAll ( 1 ) score_after_shake = current.GetEnergyScore() gain_from_shake = score_after_shake - score_after_snap freeze.UnfreezeAll() else score_after_shake = score_after_snap gain_from_shake = 0 end if( gain_from_shake > lowest_allowable_shake_gain and best_score - score_after_shake < max_shake_loss ) then --print ( "score after shake " , best_score - score_after_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 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 < kScrambleThreshold and post_scramble == 2 ) then quick_nudge ( score ) score = current.GetEnergyScore() end if ( score > best_score ) then structure.ShakeSidechainsAll( 1 ) save.Quicksave ( kBest ) 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 if ( score > score_in_qs10 ) then save.Quicksave ( kClosest ) score_in_qs10 = score --print ( "Save.Quicksave 10 " , score ) end end end -- if shake improved things end -- if not current rotamer position r = r + 1 save.Quickload( kBest ) end -- loop over all rotamers end ---- main script ---- if( manual_level_set ) then level = Dialog_ChooseLevel() end n_residues = structure.GetCount() behavior.SetClashImportance( 1 ) -- tidy up the protein freeze.UnfreezeAll() band.DisableAll() --?? was there any reason for using this loop instead of band.DisableAll() -- Wipf, 08. Mar 2019 --band_count = band.GetCount() --for i = band_count, 1, -1 do -- band.Disable ( i ) --end save.Quicksave( kBest ) best_score = current.GetEnergyScore() score_in_qs10 = 0 print( "best score ", best_score ) if( level == 1 ) then leu_ile_only = true else leu_ile_only = false end if( level == 1 or level == 2 ) then lowest_allowable_shake_gain = 0.5 else lowest_allowable_shake_gain = -0.5 end if( level == 1 or level == 2 or level == 4 ) then max_attempts = 3 else max_attempts = 6 end min_snap_loss = 60 if( level == 1 or level == 2 ) then max_shake_loss = 3000 else max_shake_loss = 100000 end if( start_idx < 1 or start_idx > n_residues ) then r = pseudorandom ( best_score ) start_idx = 1 + n_residues * r start_idx = start_idx - start_idx % 1 end FlipAllSidechains()

Comments


Wipf Lv 1

Mar 2019: – Version 2.3, release 1 –
Mar 2019: reduced output verbosity
Mar 2019: improved the Coprime() function which makes the recipe work better for proteins with fewer than 31 residues and gave it a more mathematical description
Mar 2019: fixed a bug which would have crashed the script when used on a protein with exactly 31 residues
Mar 2019: made the verbosity adjustable
Mar 2019: made the output more verbose
Mar 2019: removed a stray call to selection.SelectAll()
Mar 2019: moved the call to rotamer.GetCount() directly before rotamer.SetRotamer() in order to ensure that the rotamer index is always valid. Therefore save.Quickload has been moved to the end of the loop.
Mar 2019: added Dialog_ChooseLevel
Mar 2019: removed residue count parameter from FlipAllSidechains
Mar 2019: moved code into new function "FlipSidechain"
Mar 2019: renamed main procedure from "Go" to "FlipAllSidechains"
Mar 2019: some description texts rewritten
Mar 2019: removed start_from_best: if you want to start from the best solution you shall restore the best solution before starting this recipe (Undo: restore very best)
Mar 2019: replaced a loop for disabling the bands by band.DisableAll()
Mar 2019: reformatted the code
Mar 2019: – Version 2.2 (recipe 31000) –