Icon representing a recipe

Recipe: Local Quake 5.0 (NC)

created by spvincent

Profile


Name
Local Quake 5.0 (NC)
ID
47960
Shared with
Public
Parent
None
Children
Created on
January 30, 2014 at 02:23 AM UTC
Updated on
January 30, 2014 at 02:23 AM 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). kMaxIterations = 10 -- Sanity check for the new wiggle loop 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 = 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 kInitBandStrength = 1.0 kMaxAllowableBandStrength = 3 kSlackTolerance = 1.8 -- The idea is to compress the protein until a certain target reduction is met. In practice it's -- expedient to allow quite a bit of "wiggle room" in this value and kSlackTolerance defines -- how much. See code for usage but high values allow for more wiggle room. --- Some parameters you'll probably not want to change 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 test_for_credit = false ---------- A few more globals candidate_ids_start = {} candidate_ids_end = {} candidate_distances = {} n_candidate_bands = 0 best_score = 0 gain_from_wiggle_or_mutate = 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 ---------- 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 () return current.GetEnergyScore () 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 CanAcceptImprovement () -- Done this way to work round a crashing bug in -- creditbest.AreConditionsMet () if ( test_for_credit == false ) then return true elseif ( creditbest.AreConditionsMet () == true ) then return true else return false end 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 , kMaxAllowableBandStrength ) 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 if ( i == 1 ) then undo.SetUndo ( true ) end end undo.SetUndo ( true ) end function Wiggle ( step , threshold ) n_it = 0 new_score = GetScore () repeat prev_score = new_score structure.WiggleSelected ( step ) new_score = GetScore () n_it = n_it + 1 until ( ( math.abs ( new_score - prev_score ) < threshold ) or ( n_it > kMaxIterations ) ) end function AdjustBandStrength ( factor ) n_bands = band.GetCount () for i = 1 , n_bands do strength = band.GetStrength ( i ) band.SetStrength ( i , strength * factor ) strength = band.GetStrength ( i ) if ( i == 1 ) then undo.SetUndo ( false ) end end undo.SetUndo ( true ) end function WiggleBandedProtein () -- We are given a protein with a bunch of bands between segments: the intent is to wiggle until -- until the reduction from the start position is reasonably close to the desired value. -- Wiggle once to get an approximate idea of whether bands need to be strengthened, weakened, -- or whether they're just right. step = 2 init_score = GetScore () structure.WiggleSelected ( step ) score = GetScore () loss_after_banded_wiggle = init_score - score --print ( "WBP : init reduction " .. r3 ( loss_after_banded_wiggle ) ) if ( loss_after_banded_wiggle < 0 ) then return 0 -- Rare elseif ( loss_after_banded_wiggle > kSlackTolerance * target_wiggle_reduction ) then -- Reduce band strength return 1 elseif ( loss_after_banded_wiggle < target_wiggle_reduction / kSlackTolerance ) then -- Increase band strength return -1 else return 0 end end function Compress () -- After creating the band , attempt to do the wiggle series (twice if necessary) save.Quicksave ( kTemp ) s = WiggleBandedProtein () if ( s == 1 ) then -- Bands too strong save.Quickload ( kTemp ) multiplier = ( 1 - 0.4 * math.random () ) band_strength = band_strength * multiplier AdjustBandStrength ( multiplier ) s = WiggleBandedProtein () elseif ( s == -1 ) then -- Bands too weak save.Quickload ( kTemp ) multiplier = ( 1 + 0.4 * math.random () ) band_strength = band_strength * multiplier AdjustBandStrength ( multiplier ) s = WiggleBandedProtein () end end function Quake ( seg ) selection.SelectAll () Compress () band.DeleteAll () if ( mutate == true ) then structure.MutateSidechainsSelected ( 1 ) else structure.WiggleSelected ( 1 , false , true ) end gain_from_wiggle_or_mutate = GetScore () - score_after_banded_wiggle if ( ( mutate == true ) and ( math.abs ( gain_from_wiggle_or_mutate ) < 1e-5 ) ) then -- Mutate didn't work: try wiggling sidechains structure.WiggleSelected ( 1 , false , true ) end Wiggle ( 4 , 0.1 ) end function GetParameters () local dlog = dialog.CreateDialog ( "Local Quake 5.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.credit_check = dialog.AddCheckbox ( "Check conditions met " , test_for_credit ) 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 = dlog.mutate.value end test_for_credit = dlog.credit_check.value return true else return false end end function main () n_residues = structure.GetCount () band_strength = kInitBandStrength behavior.SetClashImportance ( 1 ) if ( GetParameters () == false ) then return -- graceful exit end print ( "Local Quake 5.0 : New Chapters" ) print ( "Nodes from " .. min_residue .. " to " .. max_residue ) print ( "Target reduction " .. target_wiggle_reduction ) print ( "Maximum of " .. max_bands_per_residue .. " bands with length between " .. min_band_length .. " and " .. max_band_length ) print ( n_nodes .. " nodes initially spaced " .. distance_between_nodes .. " segments apart" ) if ( mutate == true ) then print ( "Mutating" ) 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 local nq = 1 -- printing purposes only i = 1 while true do if ( ( n_quakes_without_improvement >= time_for_a_change ) and ( n_nodes > 1 ) ) 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 print ( "Node distance adjusted to " .. distance_between_nodes ) end recentbest.Restore () -- Need to record any improvements from the previous iteration score = GetScore () if ( ( score > best_score ) and ( test_for_credit == false or creditbest.AreConditionsMet () == true ) ) 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 () node_list = {} 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 ) table.insert ( node_list , node ) end end if ( n_candidate_bands > 0 ) then -- There has to be a better way of doing this if ( #node_list == 1 ) then print ( "i: " .. nq .. " (".. node_list [ 1 ] .. ")" ) elseif ( #node_list == 2 ) then print ( "i: " .. nq .. " (".. node_list [ 1 ] .. "+" .. node_list [ 2 ] .. ")" ) elseif ( #node_list == 3 ) then print ( "i: " .. nq .. " (".. node_list [ 1 ] .. "+" .. node_list [ 2 ] .. "+" .. node_list [ 3 ] .. ")" ) elseif ( #node_list == 4 ) then print ( "i: " .. nq .. " (".. node_list [ 1 ] .. "+" .. node_list [ 2 ] .. "+" .. node_list [ 3 ] .. "+" .. node_list [ 4 ] ..")" ) elseif ( #node_list > 4 ) then print ( "i: " .. nq .. " (".. node_list [ 1 ] .. "+" .. node_list [ 2 ] .. "+" .. node_list [ 3 ] .. "+" .. node_list [ 4 ] .. "+" ..")" ) end nq = nq + 1 CreateBands () Quake ( idx ) score_after_quake = GetScore () if ( monit ) then print ( " " .. r3 ( loss_after_banded_wiggle ) .. " , " .. r3 ( gain_from_wiggle_or_mutate ) .. " , " .. r3 ( score_after_quake - best_score ) ) 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 () undo.SetUndo ( true ) end --main () xpcall ( main , cleanup )

Comments


spvincent Lv 1

Changes since 4.0:

1) Optimized for NewChapters.
2) Check Conditions Met option added.
3) Cleaner output and minor algorithmic improvement

Documentation (largely duplicated from 4.0)

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.

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 there's more than one node they'll be separated along the protein by this distance. 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:

(   = 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.

Check conditions met:

If set, check that conditions are met before accepting any improvement. For puzzles with RMSD conditions, etc.