Icon representing a recipe

Recipe: Loop Remix 11.0

created by spvincent

Profile


Name
Loop Remix 11.0
ID
104760
Shared with
Public
Parent
None
Children
Created on
April 22, 2021 at 03:08 AM UTC
Updated on
April 22, 2021 at 03:08 AM UTC
Description

See first comment

Best for


Code


-- Copyright notice. -- You may copy and paste at will. Plagiarize shamelessly! -- End copyright notice -- Mostly user-configurable globals remix_length = 4 -- Length of sections to remix. number_of_remixes = 4 -- It'll look for the best score from this number of remixes. ss_criterion = 4 -- Controls whether to remix a section based on secondary structure wiggle_with_filters_off = true -- for speedup test_for_stability = true keep_bands = true min_residue = 1 max_residue = 999 inner_sphere = {} outer_sphere = {} kInnerSphereRadius = 10 kOuterSphereRadius = 16 kDefaultWiggleDuration = 12 kLowCiWiggle = 0.5 score_type = 1 verbose = true local_mutate_threshold = 80 -- Use of quicksave slots kOriginalStructureOrNewBest = 1 kTemp = 2 kRemixQuicksaveStart = 3 -- Globals original_secondary_structure = {} fixed_residues = {} n_residues = 0 best_score = 0 filter_list = {} -- For monitoring loss_from_low_ci_wiggle = {} original_residues = {} new_residues = {} start_time = 0 scores = {} ids = {} -- 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"} -- Begin function r3 ( x ) -- Round to 3 decimal places t = 10 ^ 3 return math.floor ( x*t + 0.5 ) / t end function r1 ( x ) -- Round to 1 decimal place t = 10 ^ 1 return math.floor ( x*t + 0.5 ) / t end function GetScore () if ( score_type == 1 ) then score = current.GetScore () end return score end function aa1_to_aa3 ( k ) for j = 1, 20 do if ( k == single_letter_codes [ j ] ) then return three_letter_codes [ j ] end end return "Unk" end function Coprime ( n ) local primes = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101} i = #primes -- find the highest prime < 70% of the residue count and which is coprime with the number of residues while ( i >= 1 ) do if ( primes [ i ] < n*0.7 and n % primes [ i ] ~= 0 ) then return primes [ i ] end i = i - 1 end return 1 end function GetFilterList () filter_list = filter.GetEnabledNames () end function TurnFiltersOn () for i = 1 , #filter_list do filter.Enable ( filter_list [ i ] ) end end function TurnFiltersOff () for i = 1 , #filter_list do filter.Disable ( filter_list [ i ] ) end end function SegmentComposition ( first , last ) -- Returns number of residues of each secondary structure type n_loop_residues = 0 n_helix_residues = 0 n_sheet_residues = 0 for i = first , last do ss = original_secondary_structure [ i ] if ( ss == "L" ) then n_loop_residues = n_loop_residues + 1 elseif ( ss == "H" ) then n_helix_residues = n_helix_residues + 1 elseif ( ss == "E" ) then n_sheet_residues = n_sheet_residues + 1 end end return n_loop_residues , n_helix_residues , n_sheet_residues end function IsSegmentRemixable ( first , last ) -- Remixable in the sense that the secondary structure satisfies the specified criteria n_loop_residues , n_helix_residues , n_sheet_residues = SegmentComposition ( first , last ) if ( ss_criterion == 1 ) then -- Loops only if ( n_helix_residues > 0 or n_sheet_residues > 0 ) then return false else return true end elseif ( ss_criterion == 2 ) then -- Loops with one non-loop residue if ( n_helix_residues + n_sheet_residues > 1 ) then return false else return true end elseif ( ss_criterion == 3 ) then -- Loops or sheets if ( n_helix_residues > 0 ) then return false else return true end elseif ( ss_criterion == 4 ) then return true 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 GetListOfFixedResidues () for i = 1 , n_residues do fixed_residues [ i ] = false end for i = 1 , n_residues do seg_backbone_frozen, seg_sidechain_frozen = freeze.IsFrozen ( i ) if ( seg_backbone_frozen == true ) then fixed_residues [ i ] = true end s = structure.IsLocked ( i ) if ( s == true ) then fixed_residues [ i ] = true end end end function DoesSectionContainFixedResidues ( start_idx , end_idx ) for i = start_idx , end_idx do if ( fixed_residues [ i ] == true ) then return true end end return false end function ShakeLowCIWiggle ( c_score ) save.Quicksave ( kTemp ) behavior.SetClashImportance ( 0.10 ) structure.ShakeSidechainsSelected ( 1 ) behavior.SetClashImportance ( 1 ) structure.WiggleSelected ( kDefaultWiggleDuration ) score = GetScore () if ( score < c_score ) then save.Quickload ( kTemp ) score = c_score end end function Nudge ( c_score ) save.Quicksave ( kTemp ) behavior.SetClashImportance ( kLowCiWiggle ) structure.WiggleSelected ( 1 ) table.insert ( loss_from_low_ci_wiggle , c_score - GetScore () ) behavior.SetClashImportance ( 1 ) structure.WiggleSelected ( kDefaultWiggleDuration ) score = GetScore () if ( score < c_score ) then save.Quickload ( kTemp ) end end function RecordImprovement () best_score = GetScore () -- save.LoadSecondaryStructure () save.Quicksave ( kOriginalStructureOrNewBest ) print ( "Improvement to ".. r3 ( best_score ) ) end function ConstructListOfResiduesWithinInnerSphere ( start_idx , end_idx ) for i = 1 , n_residues do inner_sphere [ i ] = false end for i = 1 , n_residues do for j = start_idx , end_idx do if ( structure.GetDistance ( i , j ) < kInnerSphereRadius ) then inner_sphere [ i ] = true end end end n = 0 for i = 1 , n_residues do if ( inner_sphere [ i ] == true ) then n = n + 1 end end return n end function SelectInnerSphere () selection.DeselectAll () for i = 1 , n_residues do if ( inner_sphere [ i ] == true ) then selection.Select ( i ) end end end function ConstructListOfResiduesWithinOuterSphere ( start_idx , end_idx ) for i = 1 , n_residues do outer_sphere [ i ] = false end for i = 1 , n_residues do for j = start_idx , end_idx do if ( structure.GetDistance ( i , j ) < kOuterSphereRadius ) then outer_sphere [ i ] = true end end end n = 0 for i = 1 , n_residues do if ( outer_sphere [ i ] == true ) then n = n + 1 end end return n end function SelectOuterSphere () selection.DeselectAll () for i = 1 , n_residues do if ( outer_sphere [ i ] == true ) then selection.Select ( i ) end end end function RemixPreamble ( start_idx , end_idx ) save.Quickload ( kOriginalStructureOrNewBest ) selection.DeselectAll () selection.SelectRange ( start_idx , end_idx ) end function ShellSort () -- Adapted from Numerical Recipes in C inc = 1 repeat inc = inc * 3 + 1 until inc > n_residues repeat inc = inc / 3 inc = inc - inc % 1 for i = inc + 1 , n_residues do v = scores [ i ] w = ids [ i ] j = i flag = false while ( flag == false and scores [ j - inc ] > v ) do scores [ j ] = scores [ j - inc ] ids [ j ] = ids [ j - inc ] j = j - inc if ( j <= inc ) then flag = true end end scores [ j ] = v ids [ j ] = w end until inc <= 1 end function TestForStability () -- Slightly inelegant this if ( score_after_local_mutate > best_score ) then print ( " Testing for stability" ) print ( " score with filters " .. r1 ( score_after_local_mutate ) ) filter.DisableAll () s1 = GetScore () structure.WiggleSelected ( kDefaultWiggleDuration ) s2 = GetScore () filter.EnableAll () score_after_local_mutate = GetScore () print ( " score without filters " .. r1 ( score_after_local_mutate ) ) if ( score_after_local_mutate < best_score ) then print ( " Improvement rejected" ) end end end function LocalMutate ( idx ) local score_after_w local start_score = GetScore () save.Quicksave ( kTemp ) for j = 1 , n_residues do scores [ j ] = structure.GetDistance ( idx , j ) ids [ j ] = j end ShellSort () selection.DeselectAll () for j = 1 , 10 do selection.Select ( ids [ j ] ) end behavior.SetClashImportance ( 0.09 ) structure.MutateSidechainsSelected ( 1 ) behavior.SetClashImportance ( 1.0 ) SelectOuterSphere () structure.WiggleSelected ( 15 ) score_after_w = GetScore () if ( score_after_w < start_score ) then save.Quickload ( kTemp ) return start_score else return score_after_w end end function RemixSeg ( start_idx , end_idx ) local i local n_remixes local n_inner local n_outer local nudge_ref local local_mutate_ref RemixPreamble ( start_idx , end_idx ) n_inner = ConstructListOfResiduesWithinInnerSphere ( start_idx , end_idx ) n_outer = ConstructListOfResiduesWithinOuterSphere ( start_idx , end_idx ) init_score = GetScore () if ( verbose ) then print ( "init score : " .. r1 ( init_score ) .. " (" .. n_outer .. "," .. n_inner .. ")" ) end n_remixes = structure.RemixSelected ( kRemixQuicksaveStart , number_of_remixes ) for i = 1 , n_remixes do save.Quickload ( kRemixQuicksaveStart - 1 + i ) score_after_remix = GetScore () if ( math.abs ( score_after_remix - best_score ) >= 1e-5 ) then SelectOuterSphere () if ( wiggle_with_filters_off == true ) then TurnFiltersOff () end structure.ShakeSidechainsSelected ( 1 ) if ( wiggle_with_filters_off == true ) then TurnFiltersOn () end score_after_shake = GetScore () if ( wiggle_with_filters_off == true ) then TurnFiltersOff () end structure.WiggleSelected ( kDefaultWiggleDuration ) if ( wiggle_with_filters_off == true ) then TurnFiltersOn () end score_after_wiggle = GetScore () SelectInnerSphere () structure.MutateSidechainsSelected ( 1 ) score_after_mutate = GetScore () gain_from_mutate = score_after_mutate - score_after_wiggle SelectOuterSphere () structure.WiggleSelected ( kDefaultWiggleDuration ) score_after_2nd_wiggle = GetScore () if ( gain_from_mutate > 1 and math.abs ( score_after_2nd_wiggle - score_after_mutate ) < 1 ) then Nudge ( score_after_2nd_wiggle ) score_after_nudge = GetScore () nudge_ref = r1 ( init_score - score_after_nudge ) else score_after_nudge = score_after_2nd_wiggle nudge_ref = "x" end if ( wiggle_with_filters_off == true ) then -- filter.EnableAll () end if ( init_score - score_after_nudge < local_mutate_threshold ) then score_after_local_mutate = LocalMutate ( ( start_idx + end_idx ) / 2 ) local_mutate_ref = r1 ( init_score - score_after_local_mutate ) else local_mutate_ref = "x" score_after_local_mutate = -1e10 end if ( verbose ) then print ( "i : " .. i .. " " .. r1 ( init_score - score_after_remix ) .. " " .. r1 ( init_score - score_after_shake ) .. " " .. r1 ( init_score - score_after_wiggle ) .. " " .. r1 ( init_score - score_after_mutate ) .. " " .. r1 ( init_score - score_after_2nd_wiggle ) .. " " .. nudge_ref .. " " .. local_mutate_ref ) end if ( test_for_stability == true ) then TestForStability () end if ( score_after_local_mutate > best_score ) then RecordImprovement () return end end end -- for i end function RemixAll ( ) local inc max_possible_residue = max_residue - remix_length + 1 n_possible_segs = max_possible_residue - min_residue + 1 r = pseudorandom ( best_score ) start_idx = min_residue + n_possible_segs * r start_idx = start_idx - start_idx % 1 inc = Coprime ( n_possible_segs ) -- The point of this Coprime, start_idx business is so we go through the protein in a -- nonuniform way. Rather than remixing 1-4, 2-5 , 3-6 for example, we might do 21-24, 53-57, 3-6 -- or something like that candidate_section_found = false for i = 1 , n_possible_segs do end_idx = start_idx + remix_length - 1 assert ( start_idx >= min_residue and end_idx <= max_residue , "Range error" ) if ( ( DoesSectionContainFixedResidues ( start_idx , end_idx ) == false ) and ( IsSegmentRemixable ( start_idx , end_idx ) == true ) ) then candidate_section_found = true rb = rb + 1 print ( "rb ".. rb .. " " .. start_idx .."-" .. end_idx .. " (" ..i .. "/" .. n_possible_segs .. ")" ) RemixSeg ( start_idx , end_idx ) end start_idx = start_idx + inc if ( start_idx > max_possible_residue ) then start_idx = start_idx - n_possible_segs end end -- for i if ( candidate_section_found == false ) then print ( "No remixable sections found" ) exit (0) end end function PrintParameters () print ( "Score type : Normal" ) print ( "Start score : " .. r3 ( best_score ) ) print ( "Remix length : " .. remix_length ) if ( ss_criterion == 1 ) then print ( "Loops only" ) elseif ( ss_criterion == 2 ) then print ( "Loops + 1" ) elseif ( ss_criterion == 3 ) then print ( "Loops + sheets" ) elseif ( ss_criterion == 4 ) then print ( "Any ss" ) end print ( "Remix range " .. min_residue .. " to " .. max_residue ) print ( "N remixes " .. number_of_remixes ) print ( "Local mutate threshold " .. r1 ( local_mutate_threshold ) ) end function GetOptionalParameters () local dlog = dialog.CreateDialog ( "Options" ) dlog.lm_threshold = dialog.AddSlider ( "Local mutate threshold" , local_mutate_threshold , 0 , 200 , 0 ) dlog.keep_bands = dialog.AddCheckbox ( "Keep bands enabled" , keep_bands ) dlog.wwfo = dialog.AddCheckbox ( "Filters off for wiggle" , wiggle_with_filters_off ) dlog.tfs = dialog.AddCheckbox ( "Test for stability" , test_for_stability ) dlog.verbose = dialog.AddCheckbox ( "Verbose" , verbose ) dlog.ok = dialog.AddButton ( "OK" , 1 ) dlog.cancel = dialog.AddButton ( "Cancel" , 0 ) if ( dialog.Show ( dlog ) > 0 ) then local_mutate_threshold = dlog.lm_threshold.value keep_bands = dlog.keep_bands.value wiggle_with_filters_off = dlog.wwfo.value test_for_stability = dlog.tfs.value verbose = dlog.verbose.value return true else return false end end function GetParameters () local dlog = dialog.CreateDialog ( "Loop remix 11.0b" ) dlog.rb_length = dialog.AddSlider ( "Remix length" , remix_length , 3 , 8 , 0 ) dlog.min_residue = dialog.AddSlider ( "Min residue" , min_residue , 1 , n_residues , 0 ) dlog.max_residue = dialog.AddSlider ( "Max residue" , max_residue , 1 , n_residues , 0 ) dlog.remix_criterion = dialog.AddSlider ( "Remix criterion" , ss_criterion , 1 , 4 , 0 ) dlog.rc = dialog.AddLabel ( "1 = L only : 2 = L only + 1 : 3 = L + E : 4 = Any" ) dlog.n_remixes = dialog.AddSlider ( "N remixes" , number_of_remixes , 1 , 10 , 0 ) dlog.ok = dialog.AddButton ( "OK" , 1 ) dlog.cancel = dialog.AddButton ( "Cancel" , 0 ) dlog.other_options = dialog.AddButton ( "Other options" , 2 ) return_code = dialog.Show ( dlog ) if ( return_code > 0 ) then remix_length = dlog.rb_length.value min_residue = dlog.min_residue.value max_residue = dlog.max_residue.value ss_criterion = dlog.remix_criterion.value number_of_remixes = dlog.n_remixes.value end return return_code end function main() n_residues = structure.GetCount () min_residue = 1 max_residue = n_residues -- Trim off locked terminal sequences as a UI convenience min_residue = 1 while ( ( structure.IsLocked ( min_residue ) == true ) and ( min_residue <= n_residues ) ) do min_residue = min_residue + 1 end max_residue = n_residues while ( ( structure.IsLocked ( max_residue ) == true ) and ( max_residue >= 1 ) ) do max_residue = max_residue - 1 end rb = 0 save.Quicksave ( kOriginalStructureOrNewBest ) behavior.SetClashImportance ( 1 ) repeat dialog_code = GetParameters () if ( dialog_code == 2 ) then GetOptionalParameters () end until ( dialog_code < 2 ) if ( dialog_code == 0 ) then return end print ( "Loop remix 10.0a" ) best_score = GetScore () max_residue = math.min ( n_residues , max_residue ) min_residue = math.max ( 1 , min_residue ) if ( max_residue < min_residue + remix_length - 1 ) then print ( "Remix range error." ) print ( "Max residue (" .. max_residue .. ") must be greater than or equal to Min residue (" .. min_residue .. ")" ) print ( "plus Remix Length (" .. remix_length .. ")" ) return end for i = 1, n_residues do original_secondary_structure [ i ] = structure.GetSecondaryStructure ( i ) end PrintParameters () start_time = os.time () if ( keep_bands == false ) then band.DisableAll () end if ( wiggle_with_filters_off == true ) then GetFilterList () end GetListOfFixedResidues () for i = 1 , n_residues do original_residues [ i ] = structure.GetAminoAcid ( i ) end while true do RemixAll () end save.Quickload ( kOriginalStructureOrNewBest ) if ( keep_bands == false ) then band.EnableAll () end end function cleanup () print ( "Cleaning up" ) behavior.SetClashImportance ( 1.0 ) save.Quickload ( kOriginalStructureOrNewBest ) if ( wiggle_with_filters_off == true ) then TurnFiltersOn () end selection.SelectAll () if ( keep_bands == false ) then band.EnableAll () end end_time = os.time () print ( "Elapsed time : " .. os.difftime ( end_time , start_time ) .. " secs" ) if ( #loss_from_low_ci_wiggle > 1 ) then table.sort ( loss_from_low_ci_wiggle ) mid_pt = math.ceil ( #loss_from_low_ci_wiggle / 2 ) print ( "Median loss from low ci (" .. kLowCiWiggle .. ") wiggle " .. r3 ( loss_from_low_ci_wiggle [ mid_pt ] ) ) end for i = 1 , n_residues do new_residues [ i ] = structure.GetAminoAcid ( i ) end for i = 1 , n_residues do if ( new_residues [ i ] ~= original_residues [ i ] ) then print ( i .. " : " .. aa1_to_aa3 ( original_residues [ i ] ) .. " -> " .. aa1_to_aa3 ( new_residues [ i ] ) ) end end end --main () xpcall ( main , cleanup )

Comments


spvincent Lv 1

Loop Remix 10 is intended as a replacement for Loop Rebuild. It no longer supports Rebuild and has been significantly simplified: a lot of obsolete functionality has been removed. At least I hope it's obsolete and Foldit doesn't suddenly reintroduce old features. It's oriented towards design puzzles.

The default settings work pretty well early: later on in the middle/late game the Remix Criterion slider can be set to 4 to make sure all possible sections are processed: it might be worth converting to loops too.

One thing that has changed in the algorithm is that the old Loop Rebuild attempted to pick the most likely remixes to succeed by doing a shake after each remix and selecting the two top-scoring solutions to wiggle after which the highest scoring pose only would be used for subsequent processing. This is a bad way of doing it: its very hard to judge that early how well a particular remix will turn out. This version doesn't attempt to short-circuit processing until the very end when it does a mini-Local Mutate if the score is close enough to the pre-remix score.

Algorithm outline


For each section to remix
   Remix ( N Remixes) times
   For each remix
      Shake
      Wiggle
      Mutate
      Wiggle
      If Mutate gained something but Wiggle didn't
         Nudge (Wiggle at low CI: Wiggle at CI 1.0)
      If we're close enough to the current best solution 
         Do a mini-Local Mutate centred around the section midpoint
         If there's an overall improvement accept it and go onto the next section.
   end
   

end

Remix Length

How big a section to remix. Its probably best to leave this at 4 since design puzzles are normally built up from Idealized elements.

Min residue, Max residue

Only remix sections that lie within this range: the default setting is the entire protein with terminal locked residues trimmed off. These values can be usefully changed to whatever region of the protein you're working on: it's often desirable to set them to the start and end of a region you're working on.

N remixes

Probably best left at 4: might be reduced if anything.

Remix criterion

Slider with 4 values that controls, within the range defined above, which sections will get rebuilt based on their secondary structure.

1) Loops only
2) Loops allowing one non-loop residue.
3) Loops or sheets (no more than one helix residue)
4) Anything

Local mutate threshold

Defines how close scorewise we have to be before doing the final Local Mutate. The default is set at 80 which is plenty unless you happen to get a big gain in filters.

Keep bands enabled

If false, disables bands and reenables them on script termination.

Filters off for wiggle

In the interests of performance.

Test for stability

On recording a gain, attempt to determine whether the resultant pose is stable by turning off filters, wiggling, and then turning filters back on again to see if the gain still holds.

Verbose

Prints extra info about progress