Icon representing a recipe

Recipe: Local Quake 4.0

created by spvincent

Profile


Name
Local Quake 4.0
ID
45320
Shared with
Public
Parent
None
Children
None
Created on
February 04, 2013 at 21:07 PM UTC
Updated on
February 04, 2013 at 21:07 PM UTC
Description

See first comment

Best for


Code


-- Bands radiate from nodes to spatially close neighbours -- Foregoes Fuses and generally tries to optimize for speed -- Some default values, modifiable by the initial dialog max_bands_per_residue = 6 -- Increase this for more bands at each segment n_nodes = 2 distance_between_nodes = 3 target_wiggle_reduction = 150 -- On wiggle, try and reduce the score by this amount after creating the bands. -- If it's wildly out, reduce/increase band strength -- for the next try (don't try to adjust for the current scramble). min_residue = 1 -- Change these if you want to rebuild part of a protein only. For example, to -- rebuild 53-64, set min_residue to 53 and max_residue to 64. To restore, set to -- 1 and 9999 (or any large number) respectively max_residue = 999 max_band_length = 20 -- Don't create bands greater than this length. Set to 999 or something if you're -- not worried about the potential for long bands creeping in, min_band_length = 2 -- Don't create bands less than this length. mutate_not_shake = false -- for design puzzles time_for_a_change = 10 -- After doing this number of quakes without any improvement, change the value of -- second_offset to something random. scale_band_strength = true -- If true, band strength will be scaled inversely with length max_allowable_band_strength = 3 --- Some parameters you'll probably not want to change use_ranked_score = 1 -- if 1, use the score being used for ranking (best normally). If 0, use the -- old style scoring system band_to_minima = true -- Band to minima or maxima in the distance. See bands_from_segment for meaning. start_idx = 0 -- Set to a valid residue to start with that residue: otherwise -- start with a semi-random one, monit = false -- prints out performance info ---------- A few more globals candidate_ids_start = {} candidate_ids_end = {} candidate_distances = {} n_candidate_bands = 0 best_score = 0 gain_after_shake = 0 band_strength = 0 prev_band_strength = 0 total_band_length = 0 prev_total_band_length = 0 loss_after_banded_wiggle = 0 prev_loss_after_banded_wiggle = 0 score_after_banded_wiggle = 0 default_band_strength = 0.3 wiggle_accuracy = 3.0 ---------- Helix data helix_starts = {} helix_ends = {} use_helix = {} n_helices = 0 kHelixThreshold = 4 -- doesn't matter if you mangle helices of this length or less kMaxAllowableAngle = 30 -- max allowable angle in degrees from perpendicular disallow_bands_parallel_to_helix = true kTemp = 5 -- quicksave slots function r3 ( x ) -- Round to 3 decimal places t = 10 ^ 3 return math.floor ( x*t + 0.5 ) / t end function GetScore () if ( use_ranked_score == 1 ) then return current.GetScore () else return current.GetEnergyScore () end 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 < 40% of the residue count and which is coprime with the number of residues while ( i >= 1 ) do if ( primes [ i ] < n*0.4 and n % primes [ i ] ~= 0 ) then return primes [ i ] end i = i - 1 end return 1 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 IsPuzzleMutable () for i = 1 , n_residues do if ( structure.IsMutable ( i ) ) then return true end end return false end function GetHelices () within_helix = false for i = 1, n_residues do if ( structure.GetSecondaryStructure ( i ) == "H" ) then if ( within_helix == false ) then -- start of a new helix within_helix = true n_helices = n_helices + 1 helix_starts [ n_helices ] = i end elseif ( within_helix == true ) then -- end of a helix within_helix = false helix_ends [ n_helices ] = i -1 end end -- for i if ( within_helix == true ) then helix_ends [ n_helices ] = n_residues end end function TestBandAgainstHelices ( band_a , band_b ) -- Tests the putative band to see, should band_a lie on a helix, whether the band is reasonably perpendicular to the helix. if ( disallow_bands_parallel_to_helix == false ) then return true end end_in_helix = false local i = 1 while ( ( i <= n_helices ) and ( end_in_helix == false ) ) do if ( ( band_a >= helix_starts [ i ] ) and ( band_a <= helix_ends [ i ] ) and ( use_helix [ i ] == true ) ) then end_in_helix = true -- Compute an approximate angle with the helix based on the cosine rule if ( ( band_a - helix_starts [ i ] ) > ( helix_ends [ i ] - band_a ) ) then -- The point lies closest to the helix end. Compare against the start of the helix. a = structure.GetDistance ( band_a , helix_starts [ i ] ) b = structure.GetDistance ( band_a , band_b ) c = structure.GetDistance ( helix_starts [ i ] , band_b ) else -- The point lies closest to the helix start. Compare against the end of the helix. a = structure.GetDistance ( band_a , helix_ends [ i ] ) b = structure.GetDistance ( band_a , band_b ) c = structure.GetDistance ( helix_ends [ i ] , band_b ) end cos_angle = ( a*a + b*b - c*c ) / ( 2*a*b ) -- We're looking for an angle of about 90 degrees give or take a few: other angles will be rejected angle = math.deg ( math.acos ( cos_angle ) ) if ( ( angle < 90 - kMaxAllowableAngle ) or ( angle > 90 + kMaxAllowableAngle ) ) then return false else return true end end i = i + 1 end return true end function ShellSort ( ids , distances , n ) -- Adapted from Numerical Recipes in C local inc = 1 repeat inc = inc * 3 + 1 until inc > n repeat inc = inc / 3 inc = inc - inc % 1 for i = inc + 1 , n do v = distances [ i ] w = ids [ i ] j = i flag = false while ( flag == false and distances [ j - inc ] > v ) do distances [ j ] = distances [ j - inc ] ids [ j ] = ids [ j - inc ] j = j - inc if ( j <= inc ) then flag = true end end distances [ j ] = v ids [ j ] = w end until inc <= 1 end function test_for_minimum ( i , seg , seg_distances ) if ( ( i ~= seg ) and ( seg_distances [ i ] <= seg_distances [ i - 1 ] ) and ( seg_distances [ i ] <= seg_distances [ i - 2 ] ) and ( seg_distances [ i ] <= seg_distances [ i + 1 ] ) and ( seg_distances [ i ] <= seg_distances [ i + 2 ] ) ) then return true else return false end end function test_for_maximum ( i , seg , seg_distances ) if ( ( seg_distances [ i ] >= seg_distances [ i - 1 ] ) and ( seg_distances [ i ] >= seg_distances [ i - 2 ] ) and ( seg_distances [ i ] >= seg_distances [ i + 1 ] ) and ( seg_distances [ i ] >= seg_distances [ i + 2 ] ) ) then return true else return false end end function is_segment_moveable ( seg ) seg_backbone_frozen, seg_sidechain_frozen = freeze.IsFrozen ( seg ) if ( seg_backbone_frozen == true ) then return false end s = structure.IsLocked ( seg ) if ( s == true ) then return false -- greyed out as in a design puzzle else return true end end function GetBandSegmentData ( seg ) seg_distances = {} -- Get the function that looks like this: -- x = residue number -- y = distance of that segment from seg -- ignore the degenerate minimum -- Then look for minima in that function as candidates for banding. n_segments = 0 segment_ids = {} segment_distances = {} seg_ok = is_segment_moveable ( seg ) if ( seg_ok == false ) then return end for i = 1, n_residues do segment_distances [ i ] = structure.GetDistance ( seg , i ) end for i = 3 , n_residues - 3 do k = segment_distances [ i ] if ( ( band_to_minima == true ) and ( test_for_minimum ( i , seg , segment_distances ) == true ) ) then n_segments = n_segments + 1 segment_ids [ n_segments ] = i segment_distances [ n_segments ] = segment_distances [ i ] elseif ( ( band_to_minima == false ) and ( test_for_maximum ( i , seg , segment_distances ) == true ) ) then n_segments = n_segments + 1 segment_ids [ n_segments ] = i segment_distances [ n_segments ] = segment_distances [ i ] end end ShellSort ( segment_ids , segment_distances , n_segments ) if ( n_segments >= max_bands_per_residue ) then n_c = max_bands_per_residue else n_c = n_segments end for i = 1 , n_c do if ( ( segment_distances [ i ] <= max_band_length ) and ( segment_distances [ i ] >= min_band_length ) and ( TestBandAgainstHelices ( seg , segment_ids [ i ] ) == true ) and ( TestBandAgainstHelices ( segment_ids [ i ] , seg ) == true ) ) then n_candidate_bands = n_candidate_bands + 1 candidate_ids_start [ n_candidate_bands ] = seg candidate_ids_end [ n_candidate_bands ] = segment_ids [ i ] candidate_distances [ n_candidate_bands ] = segment_distances [ i ] end end end function CreateBands () total_band_length = 0 for i = 1 , n_candidate_bands do total_band_length = total_band_length + candidate_distances [ i ] end if ( scale_band_strength == true ) then average_band_length = total_band_length / n_candidate_bands end -- Adaptively change band strength based principally on how much the previous wiggle overshot/undershot the target. if ( prev_total_band_length > 0 and total_band_length > 0 ) then if ( ( target_wiggle_reduction / prev_loss_after_banded_wiggle ) > 1 ) then band_strength = prev_band_strength * ( 1 + 0.2 * pseudorandom ( score_after_banded_wiggle ) ) band_strength = math.min ( band_strength , max_allowable_band_strength ) else band_strength = prev_band_strength * ( 1 - 0.2 *pseudorandom ( score_after_banded_wiggle ) ) end else band_strength = default_band_strength end for i = 1 , n_candidate_bands do band.AddBetweenSegments ( candidate_ids_start [ i ] , candidate_ids_end [ i ] ) if ( scale_band_strength == true ) then band.SetStrength ( i , band_strength * average_band_length / candidate_distances [ i ] ) else band.SetStrength ( i , band_strength ) end end end function ReadjustBandStrength ( factor ) save.Quickload ( kTemp ) n_bands = band.GetCount () for i = 1 , n_bands do strength = band.GetStrength ( i ) band.SetStrength ( i , strength * factor ) end band_strength = band_strength * factor end function Quake ( seg ) selection.SelectAll () init_score = GetScore () -- behavior.SetWiggleAccuracy ( 2.0 ) save.Quicksave ( kTemp ) structure.WiggleSelected ( 1 ) score_after_banded_wiggle = GetScore () loss_after_banded_wiggle = init_score - score_after_banded_wiggle if ( loss_after_banded_wiggle > 2* target_wiggle_reduction ) then -- If the score is wildly out, take a quick stab at changing the band strength ReadjustBandStrength ( 0.8 ) structure.WiggleSelected ( 1 ) score_after_banded_wiggle = GetScore () loss_after_banded_wiggle = init_score - score_after_banded_wiggle elseif ( loss_after_banded_wiggle < 0.5 * target_wiggle_reduction ) then ReadjustBandStrength ( 1.3 ) structure.WiggleSelected ( 1 ) score_after_banded_wiggle = GetScore () loss_after_banded_wiggle = init_score - score_after_banded_wiggle end band.DeleteAll () if ( loss_after_banded_wiggle < 8*target_wiggle_reduction ) then if ( mutate_not_shake == true ) then structure.MutateSidechainsSelected ( 1 ) else structure.WiggleSelected ( 1 , false , true ) --structure.ShakeSidechainsSelected ( 1 ) end gain_after_shake = GetScore () - score_after_banded_wiggle -- behavior.SetWiggleAccuracy ( wiggle_accuracy ) structure.WiggleSelected ( 8 ) end end function GetParameters () local dlog = dialog.CreateDialog ( "Local Quake 4.0" ) dlog.min_residue = dialog.AddSlider ( "Min residue" , 1 , 1 , n_residues , 0 ) dlog.max_residue = dialog.AddSlider ( "Max residue" , n_residues , 1 , n_residues , 0 ) dlog.target_reduction = dialog.AddSlider ( "Target reduction" , target_wiggle_reduction , 1 , 1000 , 0 ) dlog.n_bands = dialog.AddSlider ( "Max bands" , max_bands_per_residue , 1 , 12 , 0 ) dlog.min_seg_length = dialog.AddSlider ( "Min band length" , min_band_length , 1 , 80 , 1 ) dlog.max_seg_length = dialog.AddSlider ( "Max band length" , max_band_length , 1 , 80 , 1 ) -- dialog.AddLabel ( "--------------------" ) dlog.n_nodes = dialog.AddSlider ( "N nodes" , n_nodes , 1 , 4 , 0 ) dlog.distance_between_nodes = dialog.AddSlider ( "Distance between nodes" , distance_between_nodes , 1 , n_residues/2 , 0 ) dlog.change_after = dialog.AddSlider ( "Change after" , time_for_a_change , 1 , 40 , 0 ) dlog.inv_scale= dialog.AddCheckbox ( "Inverse scale" , scale_band_strength ) dlog.helix_bands = dialog.AddCheckbox ( "Disallow helix ll bands" , disallow_bands_parallel_to_helix ) design_puzzle = IsPuzzleMutable () if ( design_puzzle == true ) then dlog.mutate= dialog.AddCheckbox ( "Mutate" , false ) end dlog.ok = dialog.AddButton ( "OK" , 1 ) dlog.cancel = dialog.AddButton ( "Cancel" , 0 ) if ( dialog.Show ( dlog ) > 0 ) then min_residue = dlog.min_residue.value max_residue = dlog.max_residue.value target_wiggle_reduction = dlog.target_reduction.value max_bands_per_residue = dlog.n_bands.value min_band_length = dlog.min_seg_length.value max_band_length = dlog.max_seg_length.value n_nodes = dlog.n_nodes.value distance_between_nodes = dlog.distance_between_nodes.value time_for_a_change = dlog.change_after.value scale_band_strength = dlog.inv_scale.value disallow_bands_parallel_to_helix = dlog.helix_bands.value if ( design_puzzle == true ) then mutate_not_shake = dlog.mutate.value end return true else return false end end function main () n_residues = structure.GetCount () band_strength = 0.5 behavior.SetClashImportance ( 1 ) -- wiggle_accuracy = behavior.GetWiggleAccuracy () if ( GetParameters () == false ) then return -- graceful exit end recentbest.Save () best_score = GetScore () GetHelices () for i = 1 , n_helices do if ( helix_ends [ i ] - helix_starts [ i ] > kHelixThreshold ) then use_helix [ i ] = true else use_helix [ i ] = false end end if ( max_band_length < min_band_length ) then print ( "Swapping min/max band lengths" ) temp = max_band_length max_band_length = min_band_length min_band_length = temp end if ( max_residue < min_residue ) then print ( "Rebuild range error." ) print ( "Max residue (" .. max_residue .. ") must be greater than or equal to Min residue (" .. min_residue .. ")" ) return end multiplier = math.max ( current.GetExplorationMultiplier () , 1 ) inc = Coprime ( n_residues ) print ( " init score ".. r3 ( best_score ) ) 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 idx = start_idx n_quakes_without_improvement = 1 score_after_quake = 0 n_candidate_bands = 0 i = 1 while true do if ( n_quakes_without_improvement >= time_for_a_change ) then r = pseudorandom ( score_after_quake ) distance_between_nodes = 1 + r * ( n_residues - 2 ) distance_between_nodes = distance_between_nodes - distance_between_nodes % 1 n_quakes_without_improvement = 1 end recentbest.Restore () -- Need to record any improvements from the previous iteration score = GetScore () if ( score > best_score ) then best_score = score print ( "Improvement to " .. r3 ( score ) ) n_quakes_without_improvement = 0 elseif ( n_candidate_bands > 0 ) then n_quakes_without_improvement = n_quakes_without_improvement + 1 end band.DeleteAll () n_candidate_bands = 0 for j = 1 , n_nodes do node = idx + ( j -1 ) *distance_between_nodes while ( node > n_residues ) do node = node - n_residues end if ( node >= min_residue and node <= max_residue ) then GetBandSegmentData ( node ) end end if ( n_candidate_bands > 0 ) then if ( monit == false ) then print ( "i: " .. i .. " seg ".. idx ) end CreateBands () Quake ( idx ) score_after_quake = GetScore () if ( monit ) then print ( idx .. " , " .. r3 ( loss_after_banded_wiggle ) .. " , " .. r3 ( gain_after_shake ) .. " , " .. r3 ( band_strength ) .. " , " .. math.floor ( total_band_length ) ) end prev_band_strength = band_strength prev_total_band_length = total_band_length prev_loss_after_banded_wiggle = loss_after_banded_wiggle end idx = idx + inc if ( idx > n_residues ) then idx = idx - n_residues end i = i + 1 end end function cleanup () print ( "Cleaning up" ) behavior.SetClashImportance ( 1.0 ) recentbest.Restore () end xpcall ( main , cleanup )

Comments


spvincent Lv 1

Changes since 3.1:

1) Algorithmic improvements and speed up
2) Option added to ensure bands to a helix are also reasonably perpendicular to that helix, in an attempt to avoid the common problem with banding scripts of helix mangling,
3) Dialog control over many other parameters.

Algorithm outline:

1) Pick an amino acid (call it a node)

2) Create bands from that node to its nearest neighbours (see comments in script for how this is done)

3) Wiggle

4) Delete bands and wiggle sidechains (used to be a shake in previous versions but wiggle seems to work just as well and is a lot faster)

5) Wiggle and hope for an improvement from the starting score

Doesn't do any fuzing and generally tries to optimize for speed.

Notes on dialog settings:

Min/Max residue: Nodes will only be created within this region of the protein but bands radiating from this node may extend anywhere.

Target reduction: The band strength is set so that after creating the bands, wiggle will occur until the score drops by approximately this amount. Supreme precision is not attempted here: the script readjusts band strength based on whether the wiggle in the previous quake overshot or undershot this value although large errors are corrected on the fly.

Max bands: The maximum number of bands allowable from each node.

Min/Max band length: To aid in controlling/localizing the effects of the Quake.

N nodes: the number of nodes to be created.

Distance between nodes: If more than one node will be this far apart along the protein. A value of 0 is allowed and will result in a single centre. The default value of 3 seems to work well: because the number of residues in a helix turn is 3.6 this means that in the case of a helix both quake centres will be on approximately the same side and less likely to mangle it: a bit of a general problem with Quake-style scripts.

Change after: After this number of quakes without any improvement in the score, change the distance between nodes to something random. Default 10.
Inverse scale: If true (default), then inversely scale the band strength with length: in other words long bands will be weaker. The idea is to help localize the effects of the quake.

Disallow helix   bands:</b> (   = parallel : there's not enough room to spell it out in the dialog box). When checked, bands that are bound to a helix at either end will only be allowed if they are reasonably perpendicular to the helix: reasonably being defined in terms of a specified angle (kMaxAllowableAngle) Small helices (4 residues or less) are unaffected. It doesn't tend to matter if such helices become distorted and the angle calculation becomes even more inexact anyway.

Mutate not shake: This checkbox will appear if and only if the puzzle contains mutable residues. If set, the side chain wiggle in the algorithm will be replaced by a mutate. Not sure how useful this is.