Icon representing a recipe

Recipe: BandedWormPairs

created by KarenCH

Profile


Name
BandedWormPairs
ID
102067
Shared with
Public
Parent
None
Children
Created on
May 30, 2016 at 18:27 PM UTC
Updated on
May 30, 2016 at 18:27 PM UTC
Description

Banded worm, with pairs of bands and more random action

Best for


Code


------------------------------------------------------------------------------ -- Banded Worm ------------------------------------------------------------------------------ -- Modifies Worm LWS v2 by rav3n_pl -- -- by KarenCH -- (original idea based on a suggestion from porkythepundit) ------------------------------------------------------------------------------ ------------------------------------------------------------------------------ -- interesting global variables that users might want to modify ------------------------------------------------------------------------------ PROB_CHOOSE_CONTACTMAP = 0.50 PROB_PUTBAND = 1.0 -- how often do we put bands in our wiggle steps? PROB_BIS_NOT_BETWEEN = 0.25 -- do we prefer BIS or bands between segs PROB_2ND_IS_BIS = 0.50 PROB_BETWEEN_USES_ATOM = 0.50 -- between: should wiggled seg have band from non-default atom BAND_STRENGTH_DEFAULT = 1.0 BAND_TO_SIDECHAINS = true -- less interesting global variables that users might consider modifying BIS_LENGTH = 5.0 -- max length of a BIS SCALE_MAXCI = 1.0 CONTACTMAP_THRESHOLD = 0.25 -- how strong a contact should be to be "noticed" USENORMALSCORE = true -- exploration puzzles would set false DEBUGRUN = true -- mostly goes with DebugPrint, but could get more use later ------------------------------------------------------------------------------ -- variables users probably don't want to play with ------------------------------------------------------------------------------ QS_Start = 1 QS_Best = 3 -- state-tracking variables. dangerous to fool with PuzzleHasContactMap = false PuzzleHasLockedSegs = false EndCalled = false InitialScore = 0.0 StartTime = 0 CurrentBestScore = 0.0 InitialClashImportance = behavior.GetClashImportance() NonLockedSegList = {} segCt = structure.GetCount() MinUnlockedSeg = 1 MaxUnlockedSeg = segCt InitialBandCount = 0 -- important atoms in an amino acid BETA_CARBON = 5 TERMINAL_BETA = 6 CENTER_CARBON = 2 --this is the default atom for bands to attach to ------------------------------------------------------------------------------ -- FUNCTIONS ------------------------------------------------------------------------------ function BandedWormPairs( pattern ) recentbest.Save( ) SetCI( 1.0 ) SaveBest( ) local ss = getScore() for w = 1, #pattern do len = pattern[ w ] local sw = getScore() local swCurr = sw print( "Starting BandedWormPairs of len " .. len .. ", score: ".. TrimNum( getScore() ) ) for s=MinUnlockedSeg, MaxUnlockedSeg - len + 1 do selection.DeselectAll() selection.SelectRange( s, s + len - 1 ) if random( ) < PROB_PUTBAND then local idx = random( s, s + len - 1 ) PutSingleRandomBandToSeg( idx, PROB_BIS_NOT_BETWEEN ) if random( ) < 0.25 then local idx2 = random( s, s + len - 1 ) PutSingleRandomBandToSeg( idx2, PROB_2ND_IS_BIS ) end structure.LocalWiggleSelected( random(2,4) ) DelBands( ) structure.WiggleAll( 2 ) end structure.LocalWiggleSelected( 5 ) local swNew = getScore( ) local gain = swNew - swCurr if gain > 0 then structure.LocalWiggleSelected( 20 ) recentbest.Restore( ) DelBands( ) swNew = getScore( ) gain = swNew - swCurr if TrimNum( gain ) > 0 then print( ">>>> At " .. s .. ": Gained ".. TrimNum( gain ) ) end SaveBest( ) swCurr = swNew else recentbest.Restore( ) structure.LocalWiggleSelected( 4 ) end recentbest.Restore( ) DelBands( ) end print( "Pattern gain: ".. getScore( ) - sw ) SaveBest( ) end selection.DeselectAll() print( "Total BandedWormPairs gain: " .. TrimNum( getScore( ) - ss ) ) end function PutSingleRandomBandToSeg( idx, probBis ) changeSucceeded = false local strength = random( 0.5 * BAND_STRENGTH_DEFAULT, 1.5 * BAND_STRENGTH_DEFAULT, true ) local doBIS = random( ) < probBis if doBIS then changeSucceeded = PutBandInSpace( idx, BIS_LENGTH, strength ) else if SegHasContactData( idx, CONTACTMAP_THRESHOLD ) and random( ) > PROB_CHOOSE_CONTACTMAP then local doSidechain = random( ) < PROB_BETWEEN_USES_ATOM changeSucceeded = PutSomeContactMapBands( CONTACTMAP_THRESHOLD, strength, 1, doSidechain ) else local atom = PickAtomNumberForBand( idx ) changeSucceeded = PutBandToRandomSeg( idx, 5, strength, atom ) end end return changeSucceeded end ---------------------------------------------------------------- -- ---------------- BOILERPLATE --------------------- -- ---------------------------------------------------------------- ---------------------------------------------------------------- -- BASIC FUNCTIONALITY ---------------------------------------------------------------- function DebugPrint( str ) if DEBUGRUN then print( str ) end end function TrimNum( val ) return val - val % 0.001 end -- Not for "external" use - call getScore. This could change if customers want -- something besides current or recentbest. function internalGetScore( wantRB ) if wantRB == nil then wantRB = false end if KILLING_FILTERS then behavior.SetSlowFiltersDisabled( false ) end local s=0.0 if not USENORMALSCORE then if wantRB then s = recentbest.GetEnergyScore( ) else s=current.GetEnergyScore( ) end else if wantRB then s = recentbest.GetScore( ) else s=current.GetScore( ) end end if KILLING_FILTERS then behavior.SetSlowFiltersDisabled( true ) end return s end function getScore( ) return internalGetScore( false ) end function getRBScore( ) return internalGetScore( true ) end function SaveBest( ) local score = getScore( ) if score > CurrentBestScore then save.Quicksave( QS_Best ) CurrentBestScore = score end end function SetCI( ci ) behavior.SetClashImportance( SCALE_MAXCI * ci ) end ------------- CONTACTMAP FUNCTIONS ------------ function CheckForContactMap( ) PuzzleHasContactMap = false local saveval = 0.0 for i=1, segCt-1 do for j=i+1, segCt do val = contactmap.GetHeat( i, j ) if saveval ~= 0.0 and val ~= saveval then PuzzleHasContactMap = true ContactMapScore = GetContactScore( ) return -- all we wanted to know was whether scores exist end if saveval == 0.0 then saveval = val end end end return end function SegHasContactData( segIn, heatThreshold ) for i = 1, segCt do if i < segIn - 1 or i > segIn + 1 then if contactmap.GetHeat( segIn, i ) >= heatThreshold then return true end end end return false end function InitializeContactMapSegList( heatThreshold ) HasContactMapSegList = {} for i = 1, segCt do if SegHasContactData( i, heatThreshold ) then HasContactMapSegList[ #HasContactMapSegList + 1 ] = i end end end function GetContactScore( ) if not PuzzleHasContactMap then return 0 end local sum = 0.0 local segCt = structure.GetCount( ) for i = 1,segCt-1 do for j = i + 1, segCt do if contactmap.IsContact( i, j ) then sum = sum + contactmap.GetHeat( i, j ) end end end return sum end ----------------------- MATHY STUFF ----------------------- function seedRandom() seed=os.time( )/math.abs( getScore( ) ) seed=seed%0.001 seed=1/seed while seed<10000000 do seed=seed*1000 end seed=seed-seed%1 DebugPrint( "Seed is: "..seed ) math.randomseed( seed ) -- throw away a couple of randoms math.random( ) math.random( ) end function random( n1,n2, forceFloat ) --random function returns int or float depends on input vars if forceFloat == nil then forceFloat = false end if n1==nil then return math.random() else if n2==nil then if n1 == 0 then return 0 end -- a random number between 0 and 0 is 0 if n1%1==0 then -- can't test for "forceFloat", so caller must beware return math.random( n1) --integer else return math.random( ) * n1 --float end else if n1%1==0 and n2%1==0 and not forceFloat then return math.random( n1, n2 ) --integer between else return math.random( ) * (n2 - n1) + n1 --float between end end end end function randomSeg( ) return random( segCt ) end function randomThetaPhi() return math.acos( random( -1.0, 1.0, true ) ), random( 2 * math.pi ) end function randomizeIndexList( idxList ) for i=1, #idxList do j = random( #idxList ) if j ~= i then idxList[i], idxList[j] = idxList[j], idxList[i] end end end -- for branched aas, simply picks one (longer, one with donor/acceptor tip, or if no difference then either) function GetAtomOfTip( aa ) if aa == "a" then return 10 elseif aa == "c" then return 10 elseif aa == "d" then return 8 elseif aa == "e" then return 9 elseif aa == "f" then return 20 elseif aa == "g" then return 0 -- glycine has no tip. just use a backbone atom elseif aa == "h" then return 17 elseif aa == "i" then return 18 elseif aa == "k" then return 9 elseif aa == "l" then return 16 elseif aa == "m" then return 17 elseif aa == "n" then return 14 elseif aa == "p" then return 13 elseif aa == "q" then return 9 elseif aa == "r" then return 22 elseif aa == "s" then return 11 elseif aa == "t" then return 6 elseif aa == "v" then return 13 elseif aa == "w" then return 24 elseif aa == "y" then return 12 else return 0 end end function GetTipAtomOfSeg( idx ) return GetAtomOfTip( structure.GetAminoAcid( idx )) end function PickAtomNumberForBand( idx ) if not BAND_TO_SIDECHAINS then return CENTER_CARBON end local r = random( 1, 7 ) -- consider adjusting probability? if r <= 3 then -- do this for r = 1,2,3 return BETA_CARBON elseif r == 4 then -- do this rarely, because it's only rarely useful return GetTipAtomOfSeg( idx ) else -- do this for r = 5,6,7 return CENTER_CARBON end end function IsUnlockedSeg( seg1 ) return not structure.IsLocked( seg1 ) end function FindAllMovableSegs( ) for i=1, segCt do if structure.IsLocked( i ) then PuzzleHasLockedSegs = true else NonLockedSegList[ #NonLockedSegList + 1] = i end end if PuzzleHasLockedSegs then for i=1, segCt do if IsUnlockedSeg( i ) then MinUnlockedSeg = i break end end for i=segCt, 1, -1 do if IsUnlockedSeg( i ) then MaxUnlockedSeg = i break end end end return false end ----------------------- BANDY STUFF ----------------------- function DelBands( ) band.DeleteAll( ) end function BandBetweenSegsWithParameters( seg1, seg2, strength, goalLength, atom1, atom2 ) if not ( IsUnlockedSeg( seg1 ) or IsUnlockedSeg( seg2 ) ) then return false end if atom1 == nil then atom1 = CENTER_CARBON end if atom2 == nil then atom2 = CENTER_CARBON end local bIdx = band.AddBetweenSegments( seg1, seg2, atom1, atom2 ) if bIdx ~= band.GetCount( ) then DebugPrint( "failed to add band from "..seg1.." to "..seg2) return false end if bIdx <= InitialBandCount then return true end -- don't change user-supplied bands if goalLength ~= nil then band.SetGoalLength( bIdx, goalLength ) end if strength ~= nil and strength > 0.0 then band.SetStrength( bIdx, strength ) end return true end function BandInSpaceWithParameters( seg, segFini, segInit, rho, theta, phi, strength, goalLength ) if not ( IsUnlockedSeg( seg) ) then return false end local bIdx = band.Add( seg, segFini, segInit, rho, theta, phi ) if bIdx ~= band.GetCount( ) then return false end if bIdx <= InitialBandCount then return true end -- don't change user-supplied bands if goalLength ~= nil then band.SetGoalLength( bIdx, goalLength ) end if strength ~= nil and strength ~= 0.0 then band.SetStrength( bIdx, strength ) end return true end function PutBandInSpace( idx, maxRho, strength ) -- trying to do tips is too weird, so we omit the capability local idx2, idx3 if idx < segCt then idx2 = idx + 1 else idx2 = idx - 2 end if idx > 1 then idx3 = idx - 1 else idx3 = idx + 2 end -- random point in sphere of radius maxRho local theta, phi = randomThetaPhi( ) local rho = (maxRho * random()^(1/3)) + 0.001 return BandInSpaceWithParameters( idx, idx2, idx3, rho, theta, phi, strength ) end function PutBandToRandomSeg( idx, minGap, strength, atom ) needNew = true local failedTries = 0 while ( needNew and failedTries < 30 ) do idx2 = randomSeg( ) -- ok if this one isn't movable (we assume idx is movable) if idx2 > idx + minGap or idx2 < idx - minGap then needNew = false break else failedTries = failedTries + 1 end end if not needNew then return BandBetweenSegsWithParameters( idx, idx2, strength, nil, atom, nil ) end return false end function PutSomeContactMapBands( heatThreshold, strength, ctBands, doSidechains ) changeSucceeded = false local hotList = {} for i = 1, segCt-2 do for j = i+2, segCt do local heat = contactmap.GetHeat( i, j ) if heat >= heatThreshold and not contactmap.IsContact( i , j ) then hotList[ #hotList + 1 ] = { i, j, heat } end end end randomizeIndexList( hotList ) for i=1, math.min( ctBands, #hotList ) do local atom1 = nil local atom2 = nil if BAND_TO_SIDECHAINS and doSidechains then atom1 = PickAtomNumberForBand( hotList[i][1] ) atom2 = PickAtomNumberForBand( hotList[i][2] ) end local ch = BandBetweenSegsWithParameters( hotList[i][1], hotList[i][2], strength, nil, atom1, atom2 ) changeSucceeded = ch or changeSucceeded end return changeSucceeded end -------------------------------------------------------------------------- -- SETUP, CLEANUP, and MAIN -------------------------------------------------------------------------- function CleanPuzzleState( ) SetCI( 1.0 ) selection.DeselectAll() DelBands() end function PrintState( ) local gain = getScore() - InitialScore if gain < 0.001 then print( " No change" ) else print( " Startscore: "..TrimNum( InitialScore )) print( " Score: "..TrimNum( getScore() ) ) print( " Total gain: "..TrimNum( gain )) end print( " Run time: "..os.time() - StartTime) end function End( errstr ) if EndCalled then return end -- no infinite recursion please EndCalled = true print( "" ) if errstr ~= nil then if string.find( errstr, "Cancelled" ) then print( "User cancel" ) else print( errstr ) end end save.Quickload( QS_Best ) PrintState( ) CleanPuzzleState( ) end function InitializePuzzleState( ) seedRandom( ) InitialScore = getScore( ) CurrentBestScore = InitialScore StartTime = os.time() save.Quicksave( QS_Start ) save.Quicksave( QS_Best ) InitialClashImportance = behavior.GetClashImportance() SCALE_MAXCI = InitialClashImportance FindAllMovableSegs( ) CheckForContactMap() if PuzzleHasContactMap then InitializeContactMapSegList( CONTACTMAP_THRESHOLD ) end end function main( ) InitializePuzzleState( ) if KILLING_FILTERS then behavior.SetSlowFiltersDisabled( true ) end patterns = { -- nothing terribly special about these, -- just try to get small sizes and larger ones mixed together { 14,8,6,7,13,12,2,10,11 }, { 2,5,11,3,13,4,7,1,6 }, { 5,7,1,3,9,6,2,4,8 }, { 3,8,4,5,12,6,10,2,7 } } local gain = 0.0 repeat local score = getScore( ) for i=1, 4 do pattern = patterns[ i ] BandedWormPairs( pattern ) end gain = getScore( ) - score until gain < 0.01 if KILLING_FILTERS then behavior.SetSlowFiltersDisabled( false ) end End( ) end xpcall( main, End )

Comments


KarenCH Lv 1

some bug-fixes recommended by Loci. also increased number of patterns it uses, so it runs much longer (still not infinite)