Code
--------------------------------------------------------------------
-- Code for Contact Mapper
--------------------------------------------------------------------
-- by KarenCH
-- TODO:
-- Consider alternative underlying algorithms, such as SA or multiple starts or GAB
-- Consider mods to wigglers, such as having AllCMBands drop CI significantly during
-- squeeze.
-- What about a rebuild hooked up to one of the banders (disable - rebuild - enable - wiggle)
-- Try more actions such as "put one band" + rebuild at low ci; use ZLB-wiggler. "Til contact is
-- made" banded wiggler. Put Comp-band modifications in.
--
-- If contact map info becomes "rich" again, with varying "heat" values, uncomment
-- the threshold slider in the dialog-handling function
--
--------------------------------------------------------------------
-- some oddballs that users might want to set
USENORMALSCORE = true -- exploration puzzles would set false
DEBUGRUN = true -- mostly goes with DebugPrint, but could get more use later
-- simple bander options
COREPULLING_CI = 0.70
BAND_STRENGTH_DEFAULT = 1.0 -- not really meaningful; wigglers strength-modify
BAND_STRENGTH_REGION = 1.0
BAND_MINCOMPRESS = 0.75
BAND_MAXEXPAND = 1.25
BANDEDWIGGLE_TRYCT = 5
BANDEDWIGGLE_TARGET = 500.0
BANDEDWIGGLE_SMALL_TARGET = 50.0
BIS_LENGTH = 5.0 -- max length of a BIS
-- values for controlling in-step wiggle timing and gain
QUICKWIGGLE_ITERS = 2
QUICKWIGGLE_GAIN = 0.50 -- for iterative wiggles in quickwiggle time
PROB_QSTAB_EXTRA = 0.50
-- values for controlling behavior of 'fuse' (after an acceptable neighbor is found)
USE_SLOWWIGGLE = false -- if true, then add touch-up fuse
SLOWWIGGLE_ITERS = 2
SLOWWIGGLE_GAIN = 0.10
PROB_SLOWWIGGLE_CUTFUSE = 0.70
PROB_SLOWWIGGLE_LOCAL = 0.20
PROB_SLOWWIGGLE_BLUEFUSE = 0.10
SLOWWIGGLE_ONLYGLOBAL_THRESHOLD = 100.0
-- values for dealing with mutation
FORBIDMUTATION = true -- I'm not playing with mutation at this time
MUTATE_NOT_SHAKE = false -- this is for choosing between shake and mutate
NOMUTATELIST = {0}
-- values associated with idealize
IDEALIZE_USE_CUTS = true
IDEALIZE_FORBID_UNHEALED_CUTS = true
IDEALIZE_MAXTRIES = 5 -- a "try" fails if cuts not healed. increase ZLBstrength, try again.
IDEALIZE_ZLB_STRENGTH = 2.0
IDEALIZE_TUBE_RADIUS = 4.0
IDEALIZE_MAX_SEGLEN = 5
-- values associated with rebuilding
REBUILD_FAILSBEFOREQUIT = 75
REBUILD_TRYCOUNT = 5
REBUILD_ADDIDEALIZE = true
REBUILD_USECUTS = true
REBUILD_MAX_SEGLEN = 9
REBUILD_MAX_EARLY_SCOREDROP = 3000.0
REBUILD_MAX_MEDIUM_SCOREDROP = 500.0
REBUILD_MAX_LATE_SCOREDROP = 100.0
-- Clash Importance
SCALE_MAXCI = 1.0
-- values for contact map reading
CONTACTMAP_THRESHOLD = 0.96
CONTACTMAP_ALLOW_TIPBANDS = true
CONTACTMAP_ADD_RANDOMBANDS = true
RANDOMBAND_COUNT = 3
CONTACTMAP_RESTRICTIONS = true -- only improvements that don't worsen contact map
-------------------------------------------------------------------------------------------------------------
------------ VALUES USERS SHOULD NOT BE TOUCHING ---------------------------------
-------------------------------------------------------------------------------------------------------------
-------------- CONSTANTS THAT PROBABLY SHOULD NEVER CHANGE --------------
REBUILD_INNER_TUBE_RADIUS = 12.0 -- 9.0 has been seen in some scripts
REBUILD_OUTER_TUBE_RADIUS = 18.0
HELIX_DISTANCE_SKIP4 = 6.3 -- for skips of 4
HELIX_DISTANCE_SKIP5 = 8.6 -- for skips of 5
-- QUICKSAVE slots used herein (QS_Run equivalent is taken by QS_TopLevelTemp)
QS_Start = 1
QS_Best = 3
QS_ShortUseTemp1 = 94 -- used by low-level subroutines that stash state for short time
QS_ShortUseTemp2 = 95 -- used by low-level subroutines that stash state for short time
QS_NeighborTemp1 = 96 -- used by "Neighbor Generation" subroutines (mid-level managers)
QS_NeighborTemp2 = 97 -- used by "Neighbor Generation" subroutines (mid-level managers)
QS_NeighborTemp3 = 98 -- used by "Neighbor Generation" subroutines (mid-level managers)
QS_TopLevelTemp = 99 -- used by top-level manager subroutines that call low-level managers
--------------- THINGS THE PROGRAM LEARNS AND REMEMBERS --------------
PuzzleAllowsMutate = false
PuzzleHasLockedSegs = false
PuzzleHasContactMap = false
IsDesignerPuzzle = false
EndCalled = false
InitialScore = 0.0
CurrentBestScore = 0.0
StartTime = 0
HasContactMapSegList = {}
ContactMapScore = 0.0
helixStarts = {}
helixEnds = {}
sheetStarts = {}
sheetEnds = {}
loopStarts = {}
loopEnds = {}
NonLockedSegList = {}
InitialClashImportance = behavior.GetClashImportance()
segCt = structure.GetCount()
---------------- STATISTICS-TRACKING ---------------------
algName = "none"
algNameList = {}
algStats = {
name = "none",
posScoreDelta = 0.0,
scoreDelta = 0.0,
runTime = 0,
callCount = 0,
failCount = 0,
}
TotalShakeTime = 0
------------------------------------------------------------------------------
--
---- CONTACT-MAPPER-SPECIFIC STUFF
--
------------------------------------------------------------------------------
OperationChosen = "none"
COUNT_OPERATIONS = 5000
----------------------------------------------------------------
-- HELPER FUNCTIONS
----------------------------------------------------------------
function getCMSegsWithinSphere( idxList, centerIdx, radius, includeCenter, heatThreshold )
if includeCenter then
idxList[ #idxList + 1 ] = centerIdx
end
for i=1, segCt do
if i ~= centerIdx and structure.GetDistance( centerIdx, i ) <= radius and
contactmap.GetHeat( centerIdx, i ) >= heatThreshold
then
idxList[ #idxList + 1 ] = i
end
end
end
function getCMSegsOutsideSphere( idxList, centerIdx, radius, heatThreshold )
for i=1, segCt do
if i ~= centerIdx and structure.GetDistance( centerIdx, i ) > radius and
contactmap.GetHeat( centerIdx, i ) >= heatThreshold
then
idxList[ #idxList + 1 ] = i
end
end
end
----------------------------------------------------------------
-- BANDERS
----------------------------------------------------------------
function PutAllContactMapBands( heatThreshold, strength, doComp, doTip1, doTip2 )
print( "AllCMBands" )
changeSucceeded = false
local baseStrength = strength
for i = 1, segCt-2 do
for j = i+2, segCt do
if contactmap.GetHeat(i, j) >= heatThreshold then
local goalLength = nil
if doComp then
goalLength = structure.GetDistance( i, j )
if contactmap.IsContact( i, j ) then
goalLength = 0.9 * goalLength
strength = 0.5 * baseStrength
else
goalLength = 0.7 * goalLength
strength = 1.5 * baseStrength
end
else
if contactmap.IsContact( i, j ) then goalLength = structure.GetDistance( i, j ) end
end
changeSucceeded = BandBetweenSegsWithParameters( i, j, strength, goalLength, doTip1, doTip2 )
or changeSucceeded
end
end
end
return changeSucceeded
end
function PutSomeContactMapBands( heatThreshold, strength, ctBands, doTip1, doTip2 )
if ctBands > 1 then
print( "SomeCMBands with ct="..ctBands )
else
print( "SingleCMBand" )
end
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 which = random( #hotList )
local ch = BandBetweenSegsWithParameters( hotList[i][1], hotList[i][2], strength, nil, doTip1, doTip2 )
changeSucceeded = ch or changeSucceeded
end
return changeSucceeded
end
function PutAllContactMapBandsToSeg( idx, heatThreshold, strength, doComp, doTip1, doTip2 )
print( "AllCMBandsToSeg ".. idx )
changeSucceeded = false
local baseStrength = strength
if doComp then
-- COMP-style bands
for i=1, segCt do
if i < idx - 1 or i > idx + 1 then
local heat = contactmap.GetHeat( i, idx )
if heat >= heatThreshold then
local goalLength = structure.GetDistance( i, idx )
if contactmap.IsContact( i, idx ) then
strength = 0.5 * baseStrength
goalLength = 0.9 * goalLength
else
strength = 1.5 * baseStrength
goalLength = 0.7 * goalLength
end
local changeSuc = BandBetweenSegsWithParameters(
idx, i, strength, goalLength,
doTip1, doTip2 )
changeSucceeded = changeSuc or changeSucceeded
end
end
end
else
-- only bands to non-contacting segs
local hotList = {}
for i = 1, segCt do
if i < idx - 1 or i > idx + 1 then
local heat = contactmap.GetHeat( i, idx )
if heat >= heatThreshold and not contactmap.IsContact( i , idx ) then
hotList[ #hotList + 1] = i
end
end
end
for i=1, #hotList do
if hotList[ i ] ~= idx then -- not supposed to happen!
local changeSuc = BandBetweenSegsWithParameters(
idx, hotList[i], strength * contactmap.GetHeat( i, idx ),
nil, doTip1, doTip2 )
changeSucceeded = changeSuc or changeSucceeded
end
end
end
return changeSucceeded
end
function PutContactMapBandsBetweenRegions(
heatThreshold,
startIdx1, endIdx1, startIdx2, endIdx2,
strength, doComp, doTip1, doTip2 )
print( "CMTwoRegions: ("..startIdx1..", "..endIdx1..") to ("..startIdx2..", "..endIdx2..")" )
changeSucceeded = false
local baseStrength = strength
for i = startIdx1, endIdx1 do
for j = startIdx2, endIdx2 do
if contactmap.GetHeat( i, j ) >= heatThreshold then
local goalLength = nil
if doComp then
goalLength = structure.GetDistance( i, j )
if contactmap.IsContact( i, j ) then
strength = 0.5 * baseStrength
goalLength = 0.9 * goalLength
else
strength = 1.5 * baseStrength
goalLength = 0.7 * goalLength
end
end
changeSucceeded = BandBetweenSegsWithParameters( i, j, strength, goalLength, doTip1, doTip2 )
or changeSucceeded
end
end
end
return changeSucceeded
end
function PutPushPullContactMapBands( badSeg, bandsWanted, center, strength, heatThreshold, wantPushNotPull )
print( "PushPullCM: "..badSeg.." center="..center )
changeSucceeded = false
local okSegList = {}
local SC = structure.GetDistance( badSeg, center )
local SR
for i=1, segCt do
local RC=structure.GetDistance( i, center )
SR=structure.GetDistance( i, badSeg )
if SR<15
and RC<SC-3
and math.abs( i - badSeg ) > 5
and math.abs( i - center ) > 3
then
okSegList[#okSegList + 1] = i
end
end
randomizeIndexList( okSegList )
local bandsAdded = 0
for i=1, #okSegList do
SR=structure.GetDistance( i, badSeg )
if wantPushNotPull then
SR=SR + 4
else
SR=SR - 4
end
if SR>20 then SR=20 end
if SR<3 then SR=3 end
if BandBetweenSegsWithParameters( badSeg, okSegList[i], strength, SR ) then
changeSucceeded = true
bandsAdded = bandsAdded + 1
end
if bandsAdded >= bandsWanted then break end
end -- FOR(i)
return changeSucceeded
end
function PutVoidKillerContactMapBands( idx, strength, heatThreshold )
print( "VoidKillerCM at "..idx )
changeSucceeded = false
local t={} -- store possible segments
for b=1,segCt do --test all segments
if math.abs(idx-b) >= 15 and contactmap.GetHeat( idx, b ) >= heatThreshold then
local ab = structure.GetDistance(idx, b)
if ab > 10.0 then --no void if less
local void=true
for c=1,segCt do --searching for any segment between them
if c ~= idx and c ~= b then -- don't want c = idx or b
local ac = structure.GetDistance(idx,c)
local bc = structure.GetDistance(b,c)
if ac<ab and bc<ab and ac>4 and bc>4 then
if ac+bc<ab+1.5
then void=false break --no void there for sure
end
end
end
end -- END for c
if void==true then
t[#t+1]={ idx, b }
end
end -- END if idx and b are spatially at least 10 apart
end -- END if idx and b are count-wise at least 15 apart
end -- LOOP over candidate b's
if #t>0 then
for i=1,#t do
local s=t[i][1]
local e=t[i][2]
local d=structure.GetDistance( s, e )
d = d - 3.0 -- try to compress this much...
if d<3 then d=3 end
if d>20 then d=20 end
if BandBetweenSegsWithParameters( s, e, strength, d ) then changeSucceeded = true end
end
end
return changeSucceeded
end
function PutCoreCompressBands(coreList, skipCt, startOffset, strength, compression, pullPhilics, doTips)
if doTips == nil then doTips = false end
if pullPhilics == nil then pullPhilics = false end
print("CoreCompress: skipCt="..skipCt..", "..#coreList.." cores, and ".. TrimNum(compression) .. " c/e")
local str = " connecting to "
if doTips then str=str .. "tips" else str = str .. "backbone" end
if pullPhilics then str=str .. ", pulling philics away" else str = str .. ", philics and phobics treated same" end
print( str )
local maxToBand = #coreList
local bandList = {}
local added = 0
-- first put bands between all of our central segs
-- notice: there's no protection from some of them being adjacent, but it's ok
-- if one or more of these bands fail to be created.
for i=1, maxToBand-1 do
for j=i+1, maxToBand do
if coreList[i] < coreList[j] - 1 or coreList[i] > coreList[j] + 1 then
local goalLength= compression * structure.GetDistance( coreList[ i ], coreList[ j ] )
if BandBetweenSegsWithParameters( coreList[ i ], coreList[ j ], strength, goalLength ) then
added = added + 1
end
end
end
end
-- now put bands between the "rest" of the protein and our chosen ones
for i=startOffset, segCt, skipCt do
local skipThisI = false
for j=1, maxToBand do
if coreList [ j ] == i then
skipThisI = true
break
end
end
if not skipThisI then
for j=1, maxToBand do
if i < coreList[ j ] - 1 or i > coreList[ j ] + 1 then
goalLength= compression * structure.GetDistance( i, coreList[ j ] )
if pullPhilics and not structure.IsHydrophobic( i ) then
goalLength= goalLength + 1.0
end
if BandBetweenSegsWithParameters( i, coreList[ j ], strength, goalLength, false, doTips ) then
added = added + 1
end
end
end
end
end
return added > 0
end
function PutGentleCompBands(bandsWanted, minLength, maxLength, minSeparation, strength, compression )
bandsAdded = 0
failCount = 0
repeat
s = randomMovableSeg()
e = randomMovableFarSeg( s, minSeparation, minLength, maxLength )
if e == 0 then
failCount = failCount + 1
else
-- yes, this would try to add the same segment twice, but that will fail simply...
changeSucceeded = BandBetweenSegsWithParameters( s,e, strength, dist * compression )
if changeSucceeded then
bandsAdded = bandsAdded + 1
else
failCount = failCount + 1
end
end
until bandsAdded == bandsWanted or failCount >= 100
return true
end
----------------------------------------------------------------
-- ACTIONS
----------------------------------------------------------------
function PickSingleContactMapBand( )
local iters = QUICKWIGGLE_ITERS
changeSucceeded = false
local heatThreshold = CONTACTMAP_THRESHOLD
local strength = BAND_STRENGTH_DEFAULT
local doTip1 = false
local doTip2 = false
if CONTACTMAP_ALLOW_TIPBANDS then
doTip1 = random() < 0.50
doTip2 = random() < 0.50
end
local ctRandBands = 0
if CONTACTMAP_ADD_RANDOMBANDS then
if random( ) < 0.50 then ctRandBands = RANDOMBAND_COUNT end
end
changeSucceeded = PutSomeContactMapBands( heatThreshold, strength, 1, doTip1, doTip2 )
if changeSucceeded then
if ctRandBands > 0 then
PutGentleCompBands( ctRandBands, 14.0, 30.0, 5, strength , 0.7 )
end
SimpleBandedWiggleWrapper( iters, BANDEDWIGGLE_TARGET, COREPULLING_CI )
end
return changeSucceeded
end
function PickSomeContactMapBands( )
local iters = QUICKWIGGLE_ITERS
changeSucceeded = false
local heatThreshold = CONTACTMAP_THRESHOLD
local strength = BAND_STRENGTH_DEFAULT
local doTip1 = false
local doTip2 = false
if CONTACTMAP_ALLOW_TIPBANDS then
doTip1 = random() < 0.50
doTip2 = random() < 0.50
end
local ctRandBands = 0
if CONTACTMAP_ADD_RANDOMBANDS then
if random( ) < 0.50 then ctRandBands = RANDOMBAND_COUNT end
end
local ctWanted = random( 5, 10 )
changeSucceeded = PutSomeContactMapBands( heatThreshold, strength, ctWanted, doTip1, doTip2 )
if changeSucceeded then
if ctRandBands > 0 then
PutGentleCompBands( ctRandBands, 14.0, 30.0, 5, strength , 0.7 )
end
SimpleBandedWiggleWrapper( iters, BANDEDWIGGLE_TARGET, COREPULLING_CI )
end
return changeSucceeded
end
function PickContactMapBandsForSeg( idx )
if idx == nil then idx = randomContactableSeg( ) end
changeSucceeded = false
local iters = QUICKWIGGLE_ITERS
local heatThreshold = CONTACTMAP_THRESHOLD
local strength = BAND_STRENGTH_DEFAULT
local doTip1 = false
local doTip2 = false
local doComp = random( ) < 0.50
if CONTACTMAP_ALLOW_TIPBANDS then
doTip1 = random() < 0.50
doTip2 = random() < 0.50
end
local ctRandBands = 0
if CONTACTMAP_ADD_RANDOMBANDS then
if random( ) < 0.50 then ctRandBands = RANDOMBAND_COUNT end
end
changeSucceeded = PutAllContactMapBandsToSeg( idx, heatThreshold, strength, doComp, doTip1, doTip2 )
if changeSucceeded then
if ctRandBands > 0 then
PutGentleCompBands( ctRandBands, 14.0, 30.0, 5, strength , 0.7 )
end
if doComp then
SimpleWiggleBeforeAndAfter( iters, 1, segCt, 1.0, false )
else
SimpleBandedWiggleWrapper( iters, BANDEDWIGGLE_TARGET, COREPULLING_CI )
end
end
return changeSucceeded
end
function PickCMTwoRegionBander( idx1 )
if idx1 == nil then idx1 = randomContactableSeg( ) end
changeSucceeded = false
local iters = QUICKWIGGLE_ITERS
local strength = BAND_STRENGTH_DEFAULT
local heatThreshold = CONTACTMAP_THRESHOLD
local doTip1 = false
local doTip2 = false
local doComp = random( ) < 0.50
if CONTACTMAP_ALLOW_TIPBANDS then
doTip1 = random() < 0.50
doTip2 = random() < 0.50
end
local ctRandBands = 0
if CONTACTMAP_ADD_RANDOMBANDS then
if random( ) < 0.50 then ctRandBands = RANDOMBAND_COUNT end
end
local startIdx1, endIdx1 = GetRegionStartAndEnd( idx1 )
if startIdx1 == 1 and endIdx1 == segCt then return false end -- we just can't deal with this
local idx2 = startIdx1
local tryCount = 0
while idx2 >= startIdx1 and idx2 <= endIdx1 and tryCount < 50 do
for i=startIdx1, endIdx1 do
idx2 = randomContactSeg( i, CONTACTMAP_THRESHOLD )
if idx2 ~= 0 and (idx2 < startIdx1 or idx2 > endIdx1) then break end -- got one
end
tryCount = tryCount + 1
end
if idx2 == 0 then return false end -- we have failed
if tryCount == 50 then return false end
local startIdx2, endIdx2 = GetRegionStartAndEnd( idx2 )
changeSucceeded = PutContactMapBandsBetweenRegions( heatThreshold,
startIdx1, endIdx1, startIdx2, endIdx2,
strength, doComp,
doTip1, doTip2 )
if changeSucceeded then
if ctRandBands > 0 then
PutGentleCompBands( ctRandBands, 14.0, 30.0, 5, strength , 0.7 )
end
if doComp then
SimpleWiggleBeforeAndAfter( iters, 1, segCt, 1.0, false )
else
SimpleBandedWiggleWrapper( iters, BANDEDWIGGLE_TARGET, COREPULLING_CI )
end
end
return changeSucceeded
end
function PickContactMapper( )
local iters = QUICKWIGGLE_ITERS
changeSucceeded = false
local threshold = CONTACTMAP_THRESHOLD
local strength = BAND_STRENGTH_DEFAULT
local doTip1 = false
local doTip2 = false
local doComp = random( ) < 0.50
if CONTACTMAP_ALLOW_TIPBANDS then
doTip1 = random() < 0.50
doTip2 = random() < 0.50
end
local ctRandBands = 0
if CONTACTMAP_ADD_RANDOMBANDS then
if random( ) < 0.50 then ctRandBands = RANDOMBAND_COUNT end
end
changeSucceeded = PutAllContactMapBands( threshold, strength, doComp, doTip1, doTip2 )
if changeSucceeded then
if ctRandBands > 0 then
PutGentleCompBands( ctRandBands, 14.0, 30.0, 5, strength , 0.7 )
end
if doComp then
SimpleWiggleBeforeAndAfter( iters, 1, segCt, 1.0, false )
else
SimpleBandedWiggleWrapper( iters, BANDEDWIGGLE_TARGET, COREPULLING_CI )
end
end
return changeSucceeded
end
function PickPushPullContactMapAction( badSeg )
if badSeg == nil then badSeg = randomContactableSeg( ) end
local segList = {}
local iters = QUICKWIGGLE_ITERS
local wantPushNotPull = random() < 0.50
local ctBands = 5
changeSucceeded = false
local strength = BAND_STRENGTH_DEFAULT
DelBands()
getCMSegsOutsideSphere( segList, badSeg, 8, CONTACTMAP_THRESHOLD )
if #segList == 0 then
-- nothing we can do!
print(" PushPull failed due to lack of distant 'center' segs")
return false
end
centerSeg = segList[ random( #segList) ]
changeSucceeded = PutPushPullContactMapBands( badSeg, ctBands, centerSeg, strength,
CONTACTMAP_THRESHOLD, wantPushNotPull )
if changeSucceeded then
SimpleBandedWiggleWrapper( iters, BANDEDWIGGLE_TARGET )
end
DelBands()
return changeSucceeded
end
function PickVoidKillerContactMapAction( idx )
if idx == nil then idx = randomContactableSeg( ) end
local iters = QUICKWIGGLE_ITERS
local strength = BAND_STRENGTH_DEFAULT
DelBands()
changeSucceeded = PutVoidKillerContactMapBands( idx, strength, CONTACTMAP_THRESHOLD )
if changeSucceeded then
SimpleBandedWiggleWrapper( iters, BANDEDWIGGLE_TARGET )
end
DelBands()
return changeSucceeded
end
function PickCoreCompression( compression )
local thisCore = {}
local maxCenters = randomDice( 3, 1, 3 ) -- range 3 to 9, prefers 5-7
local strength = BAND_STRENGTH_DEFAULT
local skipCt = random( 3, 13 )
local startOffset = random( 1, skipCt )
local pullPhilics = random( ) < 0.5
local doTips = false
if CONTACTMAP_ALLOW_TIPBANDS then
doTips = random() < 0.50
end
getCentralHydrophobics( thisCore, maxCenters)
return PutCoreCompressBands( thisCore, skipCt, startOffset, strength, compression, pullPhilics, doTips )
end
function PickCoreSpreader( )
local iters = QUICKWIGGLE_ITERS
local maxBWigTries = 5
changeSucceeded = false
compression = random( 1.05, BAND_MAXEXPAND )
local coreSegs = { }
getCentralHydrophobics( coreSegs, math.floor(segCt / 8) ) -- for a 100-seg protein, this is about 13
changeSucceeded = PickCoreCompression( compression )
if not changeSucceeded then return false end
BandedWiggleUntilEnoughChange( iters, BANDEDWIGGLE_TARGET, COREPULLING_CI )
DelBands()
return true
end
function PickStirThenContactMap( )
PickCoreSpreader( )
return PickContactMapper( )
end
----------------------------------------------------------------
-- THE CLEANUP WIGGLER
----------------------------------------------------------------
function DoCleanupWiggle( iters, minGainForIteration, prevScore )
SetCI( 1.0 )
local currScore = getScore( )
recentbest.Save( )
ShakeOrMutate( iters/2 )
recentbest.Restore( ) -- just in case lowCI dropped the score instead of increasing it
IterativeWiggleAll( iters, minGainForIteration, true, true)
recentbest.Restore( ) -- just in case lowCI dropped the score instead of increasing it
if USE_SLOWWIGGLE and prevScore - currScore < SLOWWIGGLE_ONLYGLOBAL_THRESHOLD
then
local r = random( )
if r < PROB_SLOWWIGGLE_LOCAL then
print( "IterativeWiggleLocalByChunk" )
IterativeWiggleLocalByChunk( iters, 1, segCt, minGainForIteration, random(3,6), random()<0.50 )
elseif r < PROB_SLOWWIGGLE_LOCAL + PROB_SLOWWIGGLE_BLUEFUSE then
print( "BlueFuse" )
BlueFuse( )
else -- PROB_SLOWWIGGLE_CUTFUSE
print( "IterativeCutAndWiggle" )
IterativeCutAndWiggle( iters, minGainForIteration, random(3,11) )
end
end
recentbest.Restore( ) -- just in case a fusish wiggle dropped the score instead of increasing it
end
----------------------------------------------------------------
-- CONTROLLERS and DIALOG BOXES
----------------------------------------------------------------
function PerformOperation( )
local minGainForIteration = SLOWWIGGLE_GAIN
local iters = SLOWWIGGLE_ITERS
local CtOperations = COUNT_OPERATIONS
print( "Initial Score = "..TrimNum(getScore( )).." ("..GetContactScore( )..") Performing " .. CtOperations .. " steps" )
local OperationList = {
"SimpleContactMapper",
"StirThenContactMap",
"SingleContactMapBand",
"FewContactMapBands",
"ContactMapBandsToSeg",
"ContactMapBandTwoRegions",
"PushPullContactMap",
"VoidKillerContactMap",
}
algName = OperationChosen
save.Quicksave( QS_Best )
for i=1, CtOperations do
save.Quickload( QS_Best )
local startScore = getScore( )
local cmScore = GetContactScore( )
local startTime = os.time( )
recentbest.Save( )
if OperationChosen == "MixAndMatch" then
algName = ChooseAlgorithm( OperationList )
end
print( "###Step "..i.." Score="..TrimNum(startScore).." CmScore="..cmScore )
local changeSucceeded = false
if algName == "SimpleContactMapper" then
changeSucceeded = PickContactMapper( )
elseif algName == "StirThenContactMap" then
changeSucceeded = PickStirThenContactMap( )
elseif algName == "SingleContactMapBand" then
changeSucceeded = PickSingleContactMapBand( )
elseif algName == "FewContactMapBands" then
changeSucceeded = PickSomeContactMapBands( )
elseif algName == "ContactMapBandsToSeg" then
changeSucceeded = PickContactMapBandsForSeg( )
elseif algName == "ContactMapBandTwoRegions" then
changeSucceeded = PickCMTwoRegionBander( )
elseif algName == "PushPullContactMap" then
changeSucceeded = PickPushPullContactMapAction( )
elseif algName == "VoidKillerContactMap" then
changeSucceeded = PickVoidKillerContactMapAction( )
end
-- Note: The operations can change algName by adding suffixes
if not changeSucceeded then
print( " Action Failed" )
AddFailToStats()
else
local newScore = getScore( )
local newCmScore = GetContactScore( )
if USE_SLOWWIGGLE and newCmScore >= cmScore and newScore >= 0.995 * startScore then
-- we're close... let's try again to get a gain... was BlueFuse( )
print( "CutAndWiggle because we're close" )
IterativeCutAndWiggle( iters, minGainForIteration, random( 3,11 ) )
didFuse = true
end
DelBands( )
newScore = getScore( )
newCmScore = GetContactScore( )
local endTime = os.time( )
print( " Neighbor score: "..TrimNum( newScore ).." ("..newCmScore..") time="..endTime - startTime )
AddActionToStats( newScore - startScore, endTime - startTime )
if newScore > startScore and (newCmScore >= cmScore or not CONTACTMAP_RESTRICTIONS) then
print( ">>>> Action improved score to "..TrimNum(newScore).. " CmScore="..newCmScore )
-- yes, doing this can lower CM score, but we take it anyway (the broken points won't go far apart, at least)
DoCleanupWiggle( iters, minGainForIteration, startScore )
save.Quicksave( QS_Best )
end
end
DelBands()
end
end
function LetUserChooseParams( )
dlg = dialog.CreateDialog("Contact Mapping Tricks")
dlg.cmLabel1 = dialog.AddLabel( "----- Actions: Topmost checked box will be chosen-----" )
dlg.cmMixAndMatch = dialog.AddCheckbox( "Mix and Match actions (all)", true )
dlg.cmStirrerMap = dialog.AddCheckbox( "Stirred ContactMapper", false )
dlg.cmSimpleMap = dialog.AddCheckbox( "Full ContactMapper", false )
dlg.cmOneMapBand = dialog.AddCheckbox( "Single CM band", false )
dlg.cmFewMapBands = dialog.AddCheckbox( "Few scattered CM bands", false )
dlg.cmMapBandsToSeg = dialog.AddCheckbox( "All CM bands to a seg", false )
dlg.cmMapBandsTwoRegions = dialog.AddCheckbox( "CM bands between two regions", false )
dlg.cmPushPull = dialog.AddCheckbox( "PushPull with ContactMap", false )
dlg.cmVoidKiller = dialog.AddCheckbox( "VoidKiller with ContactMap", false )
dlg.cmLabel2 = dialog.AddLabel( "--------------------- Options -------------------" )
dlg.cmOpCount = dialog.AddSlider( "Total Tries", COUNT_OPERATIONS, 100, 100000, 0 )
dlg.cmMaxCI = dialog.AddSlider( "Max CI", SCALE_MAXCI, 0.00, 1.00, 2 )
-- dlg.cmHeatThreshold = dialog.AddSlider( "HeatMinThreshold", CONTACTMAP_THRESHOLD, 0.0, 1.0, 2 )
dlg.cmAllowTipBands = dialog.AddCheckbox( "Allow bands to tips", CONTACTMAP_ALLOW_TIPBANDS )
dlg.cmAddRandBands = dialog.AddCheckbox( "Add random bands", CONTACTMAP_ADD_RANDOMBANDS )
dlg.cmForceContactBettering = dialog.AddCheckbox( "Contact map tries to improve", CONTACTMAP_RESTRICTIONS )
dlg.cmFuseSuccesses = dialog.AddCheckbox( "Follow-on Fuse", USE_SLOWWIGGLE )
dlg.ok = dialog.AddButton("OK", 1)
dlg.cancel = dialog.AddButton("Cancel", 0)
if dialog.Show(dlg) == 0 then return false end
if dlg.cmMixAndMatch.value then OperationChosen = "MixAndMatch"
elseif dlg.cmStirrerMap.value then OperationChosen = "StirThenContactMap"
elseif dlg.cmSimpleMap.value then OperationChosen = "SimpleContactMapper"
elseif dlg.cmOneMapBand.value then OperationChosen = "SingleContactMapBand"
elseif dlg.cmFewMapBands.value then OperationChosen = "FewContactMapBands"
elseif dlg.cmMapBandsToSeg.value then OperationChosen = "ContactMapBandsToSeg"
elseif dlg.cmMapBandsTwoRegions.value then OperationChosen = "ContactMapBandTwoRegions"
elseif dlg.cmPushPull.value then OperationChosen = "PushPullContactMap"
elseif dlg.cmVoidKiller.value then OperationChosen = "VoidKillerContactMap"
else return false -- something went wrong...
end
COUNT_OPERATIONS = dlg.cmOpCount.value
--CONTACTMAP_THRESHOLD = dlg.cmHeatThreshold.value
CONTACTMAP_RESTRICTIONS = dlg.cmForceContactBettering.value
USE_SLOWWIGGLE = dlg.cmFuseSuccesses.value
CONTACTMAP_ALLOW_TIPBANDS = dlg.cmAllowTipBands.value
CONTACTMAP_ADD_RANDOMBANDS = dlg.cmAddRandBands.value
SCALE_MAXCI = dlg.cmMaxCI.value
if CONTACTMAP_RESTRICTIONS then
print( "Contact map scores may not drop" )
else
print( "Contact scores can drop" )
end
if USE_SLOWWIGGLE then
print( "Touch-up fusing will happen" )
else
print( "No fuses" )
end
if CONTACTMAP_ALLOW_TIPBANDS then
print( "Bands can connect to sidechain tips" )
else
print( "Bands only to backbone" )
end
if CONTACTMAP_ADD_RANDOMBANDS then
print( "Can add ".. RANDOMBAND_COUNT.. " random extra bands" )
else
print( "No random extra bands" )
end
return true
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
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
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 LoadBest( )
save.Quickload( QS_Best )
end
function SetCI( ci )
behavior.SetClashImportance( SCALE_MAXCI * ci )
end
function ExactSetCI( ci )
behavior.SetClashImportance( ci )
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 randomDice( ctDice, minValPerDie, maxValPerDie ) -- distro that prefers "middle" values
total = 0
for i=1, ctDice do
total = total + random( minValPerDie, maxValPerDie )
end
return total
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
function randomSeg( )
return random( segCt )
end
function randomMovableSeg( )
if not PuzzleHasLockedSegs then
return randomSeg()
end
return NonLockedSegList[ random( #NonLockedSegList ) ]
end
function randomMovableFarSeg( segOrigin, minSep, minDist, maxDist )
local segList = {}
for e=1, segCt do
if IsMovableSeg( e ) and (e <= segOrigin - minSep or e >= segOrigin + minSep ) then
dist = structure.GetDistance( segOrigin, e )
if dist >= minDist and dist <= maxDist then segList[ #segList + 1 ] = e end
end
end
if #segList >= 1 then return segList[ random( #segList ) ]
else return 0
end
end
function randomLowScoringSeg( ctIndicesToChooseAmong, subscore )
local idxList = {}
getWorstScoringAAs( idxList, ctIndicesToChooseAmong, subscore )
return idxList[ random( #idxList ) ]
end
function randomContactableSeg( )
return HasContactMapSegList[ random( #HasContactMapSegList ) ]
end
function randomContactSeg( segIn, heatThreshold )
local segOpts = {}
for i = 1, segCt do
if i < segIn - 1 or i > segIn + 1 then
if contactmap.GetHeat( segIn, i ) >= heatThreshold then
segOpts[ #segOpts + 1] = i
end
end
end
if #segOpts == 0 then return 0 end
return segOpts[ random( #segOpts ) ]
end
function RandomPrimeNotMoreThan( max )
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}
lastIdx = 1
for i=1, #primes do
if primes[ i ] > max then break end
lastIdx = i
end
return primes[ math.random( lastIdx ) ]
end
function Coprime( n )
-- find the highest prime < 70% of "n" that is coprime with "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}
for i = #primes, 1, -1 do
if primes[ i ] < 0.70*n and n % primes[ i ] ~= 0 then
return primes[ i ]
end
end
return 1
end
function GetEuclideanDistanceVector( scmat1, scmat2, vecOut )
score = 0.0
for j=1, #scmat1[1] do
vecOut[j] = 0.0
end
for i=1, segCt do
for j=1, #scmat1[i] do
vecOut[j] = vecOut[j] + (scmat2[i][j] - scmat1[i][j]) * (scmat2[i][j] - scmat1[i][j])
end
end
for j=1, #vecOut do
vecOut[j] = math.sqrt( vecOut[j] )
end
end
function ConstructScoreMatrix( scmat )
-- TODO: Get the list of "active" subscores from puzzle info; use that info here.
for i=1, segCt do
scmat[i] = {}
scmat[i][1] = current.GetSegmentEnergySubscore( i, "backbone" )
scmat[i][2] = current.GetSegmentEnergySubscore( i, "sidechain" )
scmat[i][3] = current.GetSegmentEnergySubscore( i, "clashing" )
scmat[i][4] = current.GetSegmentEnergySubscore( i, "packing" )
scmat[i][5] = current.GetSegmentEnergySubscore( i, "density" )
scmat[i][6] = current.GetSegmentEnergySubscore( i, "hiding" )
scmat[i][7] = current.GetSegmentEnergySubscore( i, "bonding" )
scmat[i][8] = current.GetSegmentEnergySubscore( i, "disulfides" )
scmat[i][9] = current.GetSegmentEnergySubscore( i, "ideality" )
scmat[i][10] = current.GetSegmentEnergySubscore( i, "pairwise" )
scmat[i][11] = current.GetSegmentEnergySubscore( i, "other" )
end
end
function IsNeighborDifferentFromOld( oldScoreMat, threshold )
local newScoreMat = {}
local scoreVector = {}
ConstructScoreMatrix( newScoreMat )
GetEuclideanDistanceVector( oldScoreMat, newScoreMat, scoreVector )
for i=1, #scoreVector do
if scoreVector[i] > threshold then return true end
end
return false
end
function IsProteinChanged( oldScore, oldScoreMat, threshold )
if not current.AreConditionsMet( ) then return false end
local currScore = getScore( )
if currScore > oldScore then return true end
return IsNeighborDifferentFromOld( oldScoreMat, threshold )
end
---------- SORTING FOR DISTANCES AND SCORES ------------
function sortAllByScore(segs, scores)
table.sort(segs, function(n1,n2) return scores[n1] < scores[n2] end)
end
------------------- SCORING FUNCTIONS ------------------
-- returns all aas in order of score (if subscore is nil then does GetSegmentEnergyScore)
function getWorstScoringAAs( idxList, maxWanted, subscore )
local scoreList = {}
local idxes = {}
for i=1, segCt do
idxes[i] = i
end
for i=1, segCt do
if subscore == nil then
scoreList[i] = current.GetSegmentEnergyScore(i)
else
scoreList[i] = current.GetSegmentEnergySubscore( i, subscore )
end
end
sortAllByScore( idxes, scoreList )
for i = 1, math.min( maxWanted, segCt ) do
idxList[i] = idxes[i]
end
end
function getBestScoringAAs( idxList, maxWanted, subscore )
local scoreList = {}
local idxes = {}
for i=1, segCt do
idxes[i] = i
end
for i=1, segCt do
if subscore == nil then
scoreList[i] = current.GetSegmentEnergyScore(i)
else
scoreList[i] = current.GetSegmentEnergySubscore( i, subscore )
end
end
sortAllByScore( idxes, scoreList )
for i = 1, math.min( maxWanted, segCt ) do
idxList[i] = idxes[segCt - i + 1]
end
end
function isBadScorer(idx, mustBeAtLeastThisBad)
local idxList = {}
getWorstScoringAAs( idxList, mustBeAtLeastThisBad)
for i=1, #idxList do
if idxList[i] == idx then return true end
end
return false
end
function SortIdxListBySubscore( idxList, subscoreName )
local subscoreList = {}
for i=1, #idxList do
subscoreList[ i ] = current.GetSegmentEnergySubscore( idxList[ i ], subscoreName )
end
sortAllByScore( idxList, subscoreList )
end
------------------- DISTANCE FUNCTIONS ------------------
function computeDistanceSums(distList)
for i=1, segCt do
distList[i] = 0.0
end
for i=1, segCt-1 do
for j=i+1, segCt do
distList[i] = distList[i] + structure.GetDistance( i, j ) / segCt
distList[j] = distList[j] + structure.GetDistance( i, j ) / segCt
end
end
end
function getCentralAAs( idxList )
local distList = {}
for i=1, segCt do
idxList[i] = i
end
computeDistanceSums( distList )
sortAllByScore( idxList, distList )
end
-- returns all hydrophobics in order of "centralness"
function getCentralHydrophobics(idxList, ctWanted)
local idxListInternal = {}
getCentralAAs(idxListInternal)
for i=1, #idxListInternal do
if structure.IsHydrophobic( idxListInternal[ i ] ) then
idxList[ #idxList + 1 ] = idxListInternal[ i ]
if #idxList == ctWanted then return end
end
end
end
function isCentralHydrophobic(idx, mustBeAtLeastThisGood)
local idxList = {}
getCentralHydrophobics( idxList, mustBeAtLeastThisGood)
for i=1, #idxList do
if idxList[i] == idx then return true end
end
return false
end
function getSegsWithinSphere( idxList, centerIdx, radius, includeCenter )
if includeCenter then
idxList[ #idxList + 1 ] = centerIdx
end
for i=1, segCt do
if i ~= centerIdx and structure.GetDistance( centerIdx, i ) <= radius then
idxList[ #idxList + 1 ] = i
end
end
end
function getSegsOutsideSphere( idxList, centerIdx, radius )
for i=1, segCt do
if i ~= centerIdx and structure.GetDistance( centerIdx, i ) > radius then
idxList[ #idxList + 1 ] = i
end
end
end
function getSegsWithinRangeOfTube( idxList, startSeg, endSeg, radius )
-- if a seg is "near" to one of the ones in our [startSeg, endSeg] range, then we want it
for i=1, segCt do
for j=startSeg, endSeg do
if structure.GetDistance( i, j ) < radius then
idxList[ #idxList + 1] = i
break
end
end -- for j
end -- for i
end
function IsLocalMinimum( segCenter, segCheck)
if segCheck == segCenter then return false end -- exclude selfies
ckDist = structure.GetDistance(segCenter, segCheck)
if segCheck > 1 then
ckDist1 = structure.GetDistance( segCenter, segCheck - 1 )
if ckDist1 < ckDist then return false end
if segCheck > 2 then
ckDist2 = structure.GetDistance( segCenter, segCheck - 2 )
if ckDist2 < ckDist then return false end
end
end
if segCheck < segCt then
ckDist1 = structure.GetDistance( segCenter, segCheck + 1 )
if ckDist1 < ckDist then return false end
if segCheck < segCt - 1 then
ckDist2 = structure.GetDistance( segCenter, segCheck + 2 )
if ckDist2 < ckDist then return false end
end
end
return true
end
function IsLocalMaximum( segCenter, segCheck)
if segCheck == segCenter then return false end -- exclude selfies
ckDist = structure.GetDistance(segCenter, segCheck)
if segCheck > 1 then
ckDist1 = structure.GetDistance( segCenter, segCheck - 1 )
if ckDist1 > ckDist then return false end
if segCheck > 2 then
ckDist2 = structure.GetDistance( segCenter, segCheck - 2 )
if ckDist2 > ckDist then return false end
end
end
if segCheck < segCt then
ckDist1 = structure.GetDistance( segCenter, segCheck + 1 )
if ckDist1 > ckDist then return false end
if segCheck < segCt - 1 then
ckDist2 = structure.GetDistance( segCenter, segCheck + 2 )
if ckDist2 > ckDist then return false end
end
end
return true
end
----------------------------------------------------------------
-- MULTI-TRY ACTION CONTROLLER
----------------------------------------------------------------
function MultitryActOnRegion( NumberOfStarts, NumberOfActionsPerTry, startIdx, endIdx, ActionFunction )
print( "MultitryActOnRegion for "..NumberOfStarts.." starts with "..NumberOfActionsPerTry.." steps" )
BestScore=current.GetScore( )
LoadBest( )
save.Quicksave( QS_NeighborTemp1 )
save.Quicksave( QS_NeighborTemp2 )
save.Quicksave( QS_NeighborTemp3 )
for k=1, NumberOfStarts do
save.Quickload( QS_NeighborTemp1 ) -- always start over from initial state
save.Quicksave( QS_NeighborTemp2 )
print( "Starting try " .. k.. ": Score="..getScore( ) )
local initscore = getScore( )
for j=1, NumberOfActionsPerTry do -- use "best so far"
save.Quickload( QS_NeighborTemp2 )
local currScore = getScore( )
ActionFunction( startIdx, endIdx )
local endScore = getScore( )
if endScore > currScore then
print( ">>>> At ".. j ..": score="..endScore )
save.Quicksave( QS_NeighborTemp2 )
currScore = endScore
end
if endScore > BestScore then
save.Quicksave( QS_NeighborTemp3 )
BestScore = endScore
end
SaveBest( )
end -- FOR j<actions
save.Quickload( QS_NeighborTemp2 )
local endScore = getScore( )
print( "Finishing try " .. k.. " Score: "..endScore )
end -- FOR k<starts
LoadBest( )
end
----------------------------------------------------------------
-- ACTION STATISTICS
----------------------------------------------------------------
function WhereIsStringInArray( array, str )
for i=1, #array do if str == array[ i ] then return i end end
return 0
end
function AddActionToStats( scoreDelta, timeDelta )
if WhereIsStringInArray( algNameList, algName ) == 0 then
algNameList[ #algNameList + 1] = algName
end
if algStats[ algName ] == nil then
algStats[ algName ] = {
posScoreDelta = 0.0,
scoreDelta = 0.0,
runTime = 0,
callCount = 0,
gainCount = 0,
failCount = 0,
}
end
if scoreDelta > 0.0 then
algStats[ algName ].posScoreDelta = algStats[ algName ].posScoreDelta + scoreDelta
algStats[ algName ].gainCount = algStats[ algName ].gainCount + 1
end
algStats[ algName ].scoreDelta = algStats[ algName ].scoreDelta + scoreDelta
algStats[ algName ].runTime = algStats[ algName ].runTime + timeDelta
algStats[ algName ].callCount = algStats[ algName ].callCount + 1
end
function AddFailToStats( )
if WhereIsStringInArray( algNameList, algName ) == 0 then
algNameList[ #algNameList + 1] = algName
end
if algStats[ algName ] == nil then
algStats[ algName ] = {
posScoreDelta = 0.0,
scoreDelta = 0.0,
runTime = 0,
callCount = 0,
gainCount = 0,
failCount = 1,
}
return
end
algStats[ algName ].failCount = algStats[ algName ].failCount + 1
end
function ChooseAlgorithm( algList )
-- we dither runs and gains by 1 partly to avoid divide-by-zero, and
-- partly to encourage bringing in not-yet-used algorithms.
local scaleValue = 0.0
local funcValues = {}
for i = 1, #algList do
local runs = 0
local gains = 0
if algStats[ algList[ i ] ] == nil then
runs = 1
gains = 1
else
-- failCount/2 is a bit artificial, but failed actions should count against an algorithm too.
runs = 1 + algStats[ algList[ i ] ].callCount + math.floor( algStats[ algList[ i ] ].failCount / 2 )
gains = 1 + algStats[ algList[ i ] ].gainCount
end
funcValues[ i ] = gains / runs -- finding value of this item
scaleValue = scaleValue + funcValues[ i ] -- finding total size of bucket
end
local which = random( )
local sum = 0.0
for i = 1, #funcValues do
sum = sum + ( funcValues[ i ] / scaleValue )
if which < sum then
return algList[ i ]
end
end
algName = algList[ #algList ]
return algName
end
function ReportStats( )
print("Action stats:")
for i = 1, #algNameList do
local t = algNameList[ i ]
if algStats[ t ] ~= nil and algStats[ t ].callCount > 0 then
print( " " .. t .. ": scoreDel=" .. TrimNum( algStats[ t ].scoreDelta ) ..
" posScore=" .. TrimNum( algStats[ t ].posScoreDelta ) ..
" runTime=" .. algStats[ t ].runTime ..
" runs=" .. algStats[ t ].callCount ..
" gains=" .. algStats[ t ].gainCount ..
" fails=" .. algStats[ t ].failCount )
end
end
end
function ResetStats( )
for i = 1, #algNameList do
local t = algNameList[ i ]
if algStats[ t ] ~= nil then
algStats[ t ].scoreDelta = 0
algStats[ t ].posScoreDelta = 0
algStats[ t ].runTime = 0
algStats[ t ].callCount = 0
algStats[ t ].gainCount = 0
algStats[ t ].failCount = 0
end
end
end
----------------------------------------------------------------
-- INFORMATION GATHERING
----------------------------------------------------------------
---------------- SIDECHAIN FUNCTIONS -------------------
aaCount = 20
aaPhilCount = 9
aaPhobCount = 11
function AA(idx)
-- hydrophilics
if idx == 1 then return "r"
elseif idx == 2 then return "s"
elseif idx == 3 then return "t"
elseif idx == 4 then return "n"
elseif idx == 5 then return "d"
elseif idx == 6 then return "q"
elseif idx == 7 then return "e"
elseif idx == 8 then return "h"
elseif idx == 9 then return "k"
-- hydrophobics
elseif idx == 10 then return "g"
elseif idx == 11 then return "a"
elseif idx == 12 then return "c"
elseif idx == 13 then return "v"
elseif idx == 14 then return "l"
elseif idx == 15 then return "i"
elseif idx == 16 then return "m"
elseif idx == 17 then return "p"
elseif idx == 18 then return "f"
elseif idx == 19 then return "y"
else return "w" end -- idx == 20
end
function AAphilic(idx)
-- hydrophilics
if idx == 1 then return "r"
elseif idx == 2 then return "s"
elseif idx == 3 then return "t"
elseif idx == 4 then return "n"
elseif idx == 5 then return "d"
elseif idx == 6 then return "q"
elseif idx == 7 then return "e"
elseif idx == 8 then return "h"
else return "k" -- idx == 9
end
end
function AAphobic(idx)
-- hydrophobics
if idx == 1 then return "g"
elseif idx == 2 then return "a"
elseif idx == 3 then return "c"
elseif idx == 4 then return "v"
elseif idx == 5 then return "l"
elseif idx == 6 then return "i"
elseif idx == 7 then return "m"
elseif idx == 8 then return "p"
elseif idx == 9 then return "f"
elseif idx == 10 then return "y"
else return "w" -- idx == 11
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 11
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
-------------- DESIGNER PUZZLE PROPERTIES --------------
function checkAla()
local ala=0
for n = 1, segCt do
segType = structure.GetAminoAcid(n)
if segType == 'a' or segType == 'g' then
ala=ala+1
end
end
return ala == segCt
end
function IsPuzzleMutable()
for i=1,segCt do
if structure.IsMutable(i) then return true end
end
return false
end
function IsNoMutate( idx )
if FORBIDMUTATION then return true end
if not PuzzleAllowsMutate then return true end
if not structure.IsMutable(idx) then return true end
for i=1,#NOMUTATELIST do
if idx == NOMUTATELIST[i] then return true end
end
return false
end
------------- LOCKED-SEGMENT-RELATED FUNCTIONS ------------
function FindAllMovableSegs()
for i=1,segCt do
if structure.IsLocked(i) then
PuzzleHasLockedSegs = true
else
NonLockedSegList[ #NonLockedSegList + 1] = i
end
end
return false
end
function IsMovableSeg( seg1 )
if seg1 == nil or seg1 < 1 or seg1 > segCt then return false end
local BB, SC = freeze.IsFrozen( seg1 )
local sl = structure.IsLocked( seg1 )
return not (BB or SC or sl)
end
function IsAllMovableRange(seg1, seg2)
if seg1 == nil or seg2 == nil then return false end
if seg1 > seg2 then seg1, seg2 = seg2, seg1 end
if seg1 < 1 or seg2 > segCt then return false end
for i=seg1, seg2 do
if not IsMovableSeg( i ) then return false end
end
return true
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
function ActionDamagedContactMap( )
if PuzzleHasContactMap then
local sc = GetContactScore( )
if sc < ContactMapScore then return true end
ContactMapScore = sc -- now we have a "better" score, so keep that one
end
return false
end
------------- REGION-IDENTIFICATION FUNCTIONS ------------
function getHelices()
inside = false
for i=1, segCt do
if ( IsMovableSeg( i ) and structure.GetSecondaryStructure(i) == "H" ) then
if ( inside == false ) then
inside = true
helixStarts[ #helixStarts + 1] = i
end
elseif ( IsMovableSeg( i ) and inside == true ) then
inside = false
helixEnds[ #helixEnds + 1] = i - 1
end -- if ( "H" ) elseif ( within )
end -- for (segCt)
-- deal with "last seg is helix"
if ( inside == true ) then
helixEnds[ #helixEnds + 1] = segCt
end
end
function getSheets()
inside = false
for i=1, segCt do
if ( IsMovableSeg( i ) and structure.GetSecondaryStructure(i) == "E" ) then
if ( inside == false ) then
inside = true
sheetStarts[ #sheetStarts + 1 ] = i
end
elseif (IsMovableSeg( i ) and inside == true ) then
inside = false
sheetEnds[ #sheetEnds + 1 ] = i - 1
end -- if/else 'E'
end -- for (segCt)
-- deal with "last seg is sheet"
if ( inside == true ) then
sheetEnds[ #sheetEnds + 1 ] = segCt
end
end
function getLoops()
inside = false
for i=1, segCt do
if ( IsMovableSeg( i ) and structure.GetSecondaryStructure(i) == "L" ) then
if ( inside == false ) then
inside = true
loopStarts[ #loopStarts + 1 ] = i
end
elseif ( IsMovableSeg( i ) and inside == true ) then
inside = false
loopEnds[ #loopEnds + 1 ] = i - 1
end -- if/else 'L'
end -- for (segCt)
-- deal with "last seg is loop"
if ( inside == true ) then
loopEnds[ #loopEnds + 1 ] = segCt
end
end
function GetRegionStartAndEnd( idx )
s = idx
e = idx
if not IsMovableSeg( idx ) then return idx,idx end
local typeIdx = structure.GetSecondaryStructure( idx )
if idx > 1 then
s = idx - 1
while s >= 1 do
if structure.GetSecondaryStructure( s ) ~= typeIdx or not IsMovableSeg( s ) then break end
s = s - 1
end
s = s + 1
end
if idx < segCt then
e = idx + 1
while e <= segCt do
if structure.GetSecondaryStructure( e ) ~= typeIdx or not IsMovableSeg( e ) then break end
e = e + 1
end
e = e - 1
end
return s, e
end
-- uses GetRegionStartAndEnd, but tries to tune "loop" regions a bit
function GetRegionForOperation( idx )
-- try to find an acceptable region (really bizarre choices of SS can give trouble)
if not IsMovableSeg( idx ) then
return idx, idx
end
sIdx, eIdx = GetRegionStartAndEnd( idx )
-- Check: if our entire region is only 1 or 2 segs long, then see about expanding it
local regionType = structure.GetSecondaryStructure( idx )
if eIdx - sIdx <= 1 then
-- small range. Try to expand it...
local sIdx2, eIdx2
if sIdx > 2 then
if structure.GetSecondaryStructure(sIdx - 1) == regionType then
sIdx2, eIdx2 = GetRegionStartAndEnd( sIdx - 1)
sIdx = sIdx2
end
end
if eIdx < segCt - 1 then
if structure.GetSecondaryStructure( eIdx + 1 ) == regionType then
sIdx2, eIdx2 = GetRegionStartAndEnd( eIdx + 1 )
eIdx = eIdx2
end
end -- END try to fix small range
if eIdx - sIdx <= 1 then return idx,idx end -- failed
end
-- Check: if loop and more than 30-long, then only take part of it
if regionType == 'L' and eIdx - sIdx > 30 then
-- trim it; it is too long
if idx - sIdx < 15 then
eIdx = idx + 15 -- trim the other, longer side
elseif eIdx - idx < 15 then
sIdx = idx - 15 -- trim the other, longer side
else
-- more than 15 segs slop on both sides!
-- try to be random and "medium-long": 3d5 on each side should do
sIdx = idx - randomDice(3, 1, 5)
eIdx = idx + randomDice(3, 1, 5)
end
end
return sIdx, eIdx
end
----------------------------------------------------------------------
-- CUTTING FUNCTIONS
----------------------------------------------------------------------
function DeleteCutAtIndex( idx )
if idx == nil or idx < 1 or idx > segCt then return true end
local sc = getScore()
structure.DeleteCut( idx )
return getScore() ~= sc -- if no score change at all, then cut deletion didn't happen!
end
function AddRandomCuts( cutList, maxCuts )
for i=1, maxCuts do
local seg = randomMovableSeg( )
local gotMatch = false
for j=1, #cutList do
if cutList[ j ] == seg then gotMatch = true end
end
if not gotMatch then
structure.InsertCut( seg )
cutList[ #cutList + 1 ] = seg
end
end
end
function CleanCutsInList( cutList )
changeSucceeded = true
for i=1, #cutList do
changeSucceeded = DeleteCutAtIndex( cutList[ i ] ) and changeSucceeded
end
return changeSucceeded
end
----------------------------------------------------------------------
-- BARE-BONE REBUILD FUNCTION
----------------------------------------------------------------------
function RebuildSelected( iters )
band.DisableAll( )
structure.RebuildSelected( iters )
band.EnableAll( )
end
------------------------------------------------------------------------------
--
---- WIGGLER FUNCTIONS
--
------------------------------------------------------------------------------
------------------------------------------------------------------------------
-- GENERAL-PURPOSE SHAKERS AND WIGGLERS
------------------------------------------------------------------------------
function ShakeOrMutate( iters, idxList )
local startTime = os.time( )
if iters == nil then iters = 1 end
if idxList == nil then
if MUTATE_NOT_SHAKE and not FORBIDMUTATION then
structure.MutateSidechainsAll( iters )
else
if not IsDesignerPuzzle then structure.ShakeSidechainsAll( iters ) end
end
else
selection.DeselectAll( )
for i=1, #idxList do selection.Select( i ) end
if MUTATE_NOT_SHAKE and not FORBIDMUTATION then
structure.MutateSidechainsSelected ( iters )
else
if not IsDesignerPuzzle then structure.ShakeSidechainsSelected ( iters ) end
end
selection.DeselectAll( )
end
TotalShakeTime = TotalShakeTime + ( os.time( ) - startTime )
end
function WiggleRange( iters, startSeg, endSeg, addFreeze, pureLocal )
local maxSeg = structure.GetCount()
local lastSeg = math.min( endSeg, maxSeg )
local firstSeg = math.max( 1, startSeg )
if lastSeg < firstSeg then return end -- nothing to do!
-- set up for the local wiggle
selection.DeselectAll()
if addFreeze then
if firstSeg > 1 then freeze.Freeze( firstSeg - 1, true, true ) end
if lastSeg < maxSeg then freeze.Freeze( lastSeg + 1, true, true ) end
end
-- actually perform the local wiggle
selection.SelectRange( firstSeg, lastSeg )
if pureLocal then
structure.LocalWiggleSelected( iters, true, true )
else
structure.WiggleSelected( iters, true, true )
end
-- clean up what we did
if addFreeze then
if firstSeg > 1 then freeze.Unfreeze( firstSeg - 1, true, true ) end
if lastSeg < maxSeg then freeze.Unfreeze( lastSeg + 1, true, true ) end
end
end
function IterativeWiggleAll( iters, minGain, doBackbone, doSidechain )
local lastScore = getScore( )
local gain = minGain
while gain >= minGain do
structure.WiggleAll( iters, doBackbone, doSidechain ) -- do's ok for nil
gain = getScore( ) - lastScore
lastScore = getScore( )
end
end
---------------------------------------------
-- SIMPLE STABILIZER WIGGLES
---------------------------------------------
function IterativeWiggleList( idxList, iters, minGain, doBackbone, doSidechain )
selection.DeselectAll( )
for i=1, #idxList do selection.Select( i ) end
local lastScore = getScore( )
local gain = minGain
while gain >= minGain do
structure.WiggleSelected( iters, doBackbone, doSidechain ) -- do's ok for nil
gain = getScore( ) - lastScore
lastScore = getScore( )
end
selection.DeselectAll( )
end
function IterativeWiggleShakeWiggle( idxList, iters )
IterativeWiggleList( idxList, iters, QUICKWIGGLE_GAIN )
sc = getScore( )
ShakeOrMutate( 1, idxList )
if math.abs( getScore( ) - sc) > 1 then -- why bother if s/m didn't do anything?
IterativeWiggleAll(iters, QUICKWIGGLE_GAIN, true, true )
end
end
function qStabWiggle( iters, doExtraWork )
if doExtraWork == nil then doExtraWork = false end
SetCI( 1.0 )
ShakeOrMutate( 1 )
if doExtraWork then
SetCI( 0.4 )
IterativeWiggleAll( iters, QUICKWIGGLE_GAIN, true, true )
SetCI( 1.0)
ShakeOrMutate( 1 )
end
SetCI( 1.0)
IterativeWiggleAll( iters, QUICKWIGGLE_GAIN, true, true ) -- was 10*
end
-- the only user for now is the idealizer
function WiggleZlbNicely( iters, strength, skipLower, skipUpper )
if skipLower == nil then skipLower = segCt+1 end
if skipUpper == nil then skipUpper = 0 end
ZLB(strength, skipLower, skipUpper)
--structure.WiggleAll( iters, true, true)
IterativeWiggleAll( iters, QUICKWIGGLE_GAIN, true, true )
end
function SimpleRegionWiggle( iters, idxLo, idxHi, pickGlobalWiggle, wiggleExpansion )
if wiggleExpansion == nil then
wiggleExpansion = 1 + random( math.floor( segCt / 6 ))
end
local startIdx = math.max( 1, idxLo - wiggleExpansion )
local endIdx = math.min( segCt, idxHi + wiggleExpansion )
WiggleRange( iters, startIdx, endIdx, false, pickGlobalWiggle )
end
---------------------------------------------------------
--BAND-CHANGING WRAPUP WIGGLERS
---------------------------------------------------------
-- returns the actual amount of change that occurred (absolute value)
function BandedWiggleUntilEnoughChange( iters, scoreThreshold, pullingCI )
if pullingCI == nil then pullingCI = COREPULLING_CI end
SetCI( pullingCI )
deltaPoints = 0.0
ss=getScore()
for str=0.30, 1.50, 0.11 do --search enough band strength to move
for i = 1, band.GetCount() do
band.SetStrength(i, str)
end
structure.WiggleAll( iters, true, false)
if math.abs( ss-getScore() ) > scoreThreshold then
-- we've changed the structure "enough", send it off
break
end
end
SetCI( 1.0 )
return math.abs( ss - getScore() )
end
function SimpleBandedWiggleWrapper( iters, minChangeWanted, pullingCI )
print( "SimpleBandedWiggleWrapper" )
local scoreThreshold = math.min( minChangeWanted, getScore() / 500.0)
BandedWiggleUntilEnoughChange( iters, scoreThreshold, pullingCI )
DelBands( )
freeze.UnfreezeAll( )
qStabWiggle( iters, random() < PROB_QSTAB_EXTRA ) -- a bit of wiggle afterwards to help settle down
end
-- returns the actual amount of change that occurred (absolute value)
function BandedWiggleUntilTargetMet( iters, scoreDeltaTarget, maxTries, pullingCI )
print( "BandedWiggleUntilTargetMet" )
save.Quicksave( QS_ShortUseTemp )
save.Quicksave( QS_ShortUseTemp2 )
if pullingCI == nil then pullingCI = COREPULLING_CI end
SetCI( pullingCI )
local s1=getScore()
local str=0.50
local tries = 0
local s2best = 99999
recentbest.Save( )
while tries < maxTries do
save.Quickload( QS_ShortUseTemp)
for i = 1, band.GetCount() do band.SetStrength(i, str) end
structure.WiggleAll( iters )
local s2 = math.abs( getScore() - s1 )
if s2 > scoreDeltaTarget * 1.2 then
str = str * ( 0.85 + random( 0.10 ) )
elseif s2 < scoreDeltaTarget * 0.8 then
str = str + ( 1.0 - str ) * ( 0.05 + random( 0.1 ) )
else
save.Quicksave( QS_ShortUseTemp2 )
break -- we're good enough
end
if math.abs( s2 - scoreDeltaTarget ) < s2best then
save.Quicksave( QS_ShortUseTemp2 )
s2best = math.abs( s2 - scoreDeltaTarget )
end
tries = tries + 1
end
save.Quickload( QS_ShortUseTemp2 ) -- the best we could come up with...
if getRBScore( ) > getScore( ) then
recentbest.Restore( ) -- sometimes score went up. farbeit from us to refuse an improvement
end
SetCI( 1.0 )
DelBands( )
qStabWiggle( iters, random() < PROB_QSTAB_EXTRA ) -- a bit of wiggle afterwards to help settle down
end
function SimpleWiggleBeforeAndAfter( iters, startIdx, endIdx, probQstab, forbidGlobalWiggle )
print( "SimpleWiggleBeforeAndAfter" )
if forbidGlobalWiggle == nil then forbidGlobalWiggle = false end
local wantGlobalWiggle = false
if not forbidGlobalWiggle then wantGlobalWiggle = random() < 0.50 end
local pullingCI = COREPULLING_CI
if random() < probQstab then
qStabWiggle( iters, random() < PROB_QSTAB_EXTRA ) -- 2nd param is a "work harder" one
else
SetCI( pullingCI )
SimpleRegionWiggle( iters, startIdx, endIdx, wantGlobalWiggle, random(4) )
SetCI( 1.0 )
IterativeWiggleAll( iters, QUICKWIGGLE_GAIN, true, true )
end
DelBands() -- if we put in bands, they should go now
freeze.UnfreezeAll() -- if we froze anything, we should clean that up now
if random() < probQstab then
qStabWiggle( iters, random() < PROB_QSTAB_EXTRA ) -- 2nd param is a "work harder" one
else
ShakeOrMutate( iters / 2 )
wantGlobalWiggle = not wantGlobalWiggle -- invert decision about global wiggles.
if forbidGlobalWiggle then wantGlobalWiggle = false end
SimpleRegionWiggle( iters, startIdx, endIdx, wantGlobalWiggle, random(4,8) )
IterativeWiggleAll( iters, QUICKWIGGLE_GAIN, true, true )
end
end
function nudge( score )
SetCI( 0.3 )
structure.WiggleSelected( 1 )
SetCI( 1 )
structure.WiggleSelected( 8 )
end
function WiggleAndShakeForRebuilder( initScore, maxFall )
print( "WiggleAndShakeForRebuilder" )
-- initScore is the score before our rebuild happened
recentbest.Save( )
scoreA = getScore( )
if initScore - scoreA > maxFall then
ShakeOrMutate( 2 )
elseif initScore - scoreA > maxFall/2 then
ShakeOrMutate( 1 )
end
scoreA = getScore( )
structure.WiggleAll( 15 )
recentbest.Restore( )
scoreB = getScore( )
if scoreB - scoreA < 10 and initScore - scoreB > 30 then
-- The wiggle got stuck and didn't achieve anything. try a nudge to see if that helps
nudge( )
recentbest.Restore( )
scoreB = getScore( )
end
if initScore - scoreB <= 100.0 then
ShakeOrMutate( 1 )
structure.WiggleSelected( 10 )
recentbest.Restore( )
nudge( )
recentbest.Restore( )
end -- else not worth the effort
end
--------------------------------------------------
-- SLOW CLEANUP WIGGLERS
--------------------------------------------------
function BlueFuse( )
recentbest.Save( )
SetCI(.05)
structure.ShakeSidechainsAll(1)
SetCI(1)
structure.WiggleAll(5)
recentbest.Restore( )
SetCI(.07)
structure.ShakeSidechainsAll(1)
SetCI(1)
structure.WiggleAll(5)
recentbest.Restore( )
SetCI(.3)
structure.WiggleAll(8)
SetCI(1)
structure.WiggleAll(15)
recentbest.Restore( )
end
function IterativeWiggleLocalByChunk(iters, startSeg, endSeg, minGain, chunkSize, addFreeze)
currOffset = 0
local lastScore = getScore( )
gain = minGain
while gain >= minGain do
if chunkSize == 1 then currOffset = 0
else currOffset = random(chunkSize - 1)
end
ctBlocks = (endSeg - startSeg) / chunkSize
for idx = 0,ctBlocks do
recentbest.Save( )
startIdx = startSeg + currOffset + chunkSize*idx
WiggleRange(iters, startIdx, startIdx + chunkSize, addFreeze, true)
recentbest.Restore( )
if addFreeze then freeze.UnfreezeAll() end
end
local currScore = getScore( )
gain = currScore - lastScore
lastScore = currScore
end
end
function BasicCutAndWiggleFuse( baseIters, shift, offset )
if shift == nil then shift = 3 end
if offset == nil then offset = 1 else offset = 1 + (offset % shift) end
for i = offset, segCt-1, shift do
structure.InsertCut(i)
end
recentbest.Save( ) -- this save/restore is entirely within cut region, so no cut problems, probably
WiggleWiggleSimpleFuse( 0.30, 1.0, baseIters, 2*baseIters )
recentbest.Restore( )
for i = 1, segCt do -- take the opportunity to kill any "lying around" cuts
DeleteCutAtIndex( i )
end
recentbest.Save( )
WiggleWiggleSimpleFuse( 0.30, 1.0, baseIters, 4*baseIters )
recentbest.Restore( )
SetCI( 1.0 )
end
function IterativeCutAndWiggle( baseIters, minGain, shift )
save.Quicksave( QS_ShortUseTemp1 )
local score = getScore( )
local gain = minGain
repeat
save.Quickload( QS_ShortUseTemp1 )
BasicCutAndWiggleFuse( baseIters, shift, random( shift ) )
local newscore = getScore( )
if newscore >= score then
gain = newscore - score
score = newscore
save.Quicksave( QS_ShortUseTemp1 )
else
gain = 0.0
end
until gain < minGain
save.Quickload( QS_ShortUseTemp1 )
end
--------------------------------------------------
-- BASIC FUSE WIGGLERS
--------------------------------------------------
function WiggleShakeSimpleFuse( ci, iters )
SetCI( ci )
structure.WiggleAll( iters ) -- instead of IterativeWiggleAll...
ShakeOrMutate( iters/2 )
end
function WiggleWiggleSimpleFuse( ci1, ci2, shortiter, longiter )
SetCI( ci1 )
structure.WiggleAll( shortiter )
SetCI( ci2 )
structure.WiggleAll( longiter ) -- "long" wiggle
end
function ShakeWiggleFuse( ci1, ci2, iters ) -- aka Fuze1
SetCI( ci1 )
ShakeOrMutate( iters/2 )
SetCI( ci2 )
IterativeWiggleAll( iters, QUICKWIGGLE_GAIN, true, true )
end
function WiggleWiggleWiggleFuse( ci1, ci2, iters ) -- aka Fuze2
SetCI( ci1 )
IterativeWiggleAll( iters, QUICKWIGGLE_GAIN, true, true )
SetCI( 1.00 )
IterativeWiggleAll( iters, QUICKWIGGLE_GAIN, true, true )
SetCI( ci2 )
IterativeWiggleAll( iters, QUICKWIGGLE_GAIN, true, true )
end
function WiggleShakeWiggleFuse( iters ) -- aka FuzeEnd
SetCI( 1.00 )
IterativeWiggleAll( iters, QUICKWIGGLE_GAIN, true, true )
ShakeOrMutate( 1 )
IterativeWiggleAll( iters, QUICKWIGGLE_GAIN, true, true )
end
----------------------------------------------------------------------
--
---- BANDER FUNCTIONS
--
----------------------------------------------------------------------
function DelBands()
band.DeleteAll()
end
function BandBetweenSegsWithParameters( seg1, seg2, strength, goalLength, fromTip1, fromTip2 )
-- caller can set goalLength without setting strength by providing strength = 0.0
if not ( IsMovableSeg( seg1) or IsMovableSeg( seg2) ) then return false end
if fromTip1 == nil then fromTip1 = false end
if fromTip2 == nil then fromTip2 = false end
if fromTip1 then tip1 = GetTipAtomOfSeg(seg1) else tip1 = 0 end
if fromTip2 then tip2 = GetTipAtomOfSeg(seg2) else tip2 = 0 end
local bIdx = band.AddBetweenSegments( seg1, seg2, tip1, tip2 )
if bIdx ~= band.GetCount( ) then
DebugPrint( "failed to add band from "..seg1.." to "..seg2)
return false
end
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 )
-- caller can set goalLength without setting strength by providing strength = 0.0
if not ( IsMovableSeg( seg) ) then return false end
local bIdx = band.Add( seg, segFini, segInit, rho, theta, phi )
if bIdx ~= band.GetCount( ) then return false end
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 ZLB( strength, skipLower, skipUpper )
if skipLower == nil then skipLower = segCt + 1 end
if skipUpper == nil then skipUpper = 0 end
for i=2, segCt-1 do
if i < skipLower or i > skipUpper then
BandInSpaceWithParameters( i, i+1, i-1, 0.01, 0.0, 0.0, strength, 0.01)
end
end
if skipLower > 1 then
BandInSpaceWithParameters( 1, 2, 3, 0.01, 0.0, 0.0, strength, 0.01)
end
if skipUpper < segCt then
BandInSpaceWithParameters( segCt, segCt-1, segCt-2, 0.01, 0.0, 0.0, strength, 0.01)
end
end
----------------------------------------------------------------------
--
---- IDEALIZERS AND REBUILDERS
--
----------------------------------------------------------------------
----------------------------------------------------------------------
-- IDEALIZERS
----------------------------------------------------------------------
function IdealizeInRange( startSeg, endSeg, iters, useCuts )
print( "IdealizeInRange for "..startSeg.." to "..endSeg )
if useCuts == nil then useCuts = true end
local idxList = {}
changeFailed = false
if startSeg > endSeg then startSeg, endSeg = endSeg, startSeg end
if startSeg < 1 then startSeg = 1 end
if endSeg > segCt then endSeg = segCt end
local tries = 0
save.Quicksave( QS_ShortUseTemp1 )
local delta = 0.0
repeat
save.Quickload( QS_ShortUseTemp1 )
changeFailed = false
if IDEALIZE_MAXTRIES > 1 and tries == IDEALIZE_MAXTRIES - 1 then
delta = 0.0
useCuts = false -- overrule the user's request for use of cuts (should always succeed)
end
if useCuts then
if startSeg - 2 > 1 then
DebugPrint( " putting cut at ".. startSeg - 2 )
structure.InsertCut( startSeg - 2 )
end
if endSeg + 1 < segCt then
DebugPrint( " putting cut at ".. endSeg + 1 )
structure.InsertCut( endSeg + 1 )
end
if startSeg - 3 >= 1 then
BandBetweenSegsWithParameters( startSeg-3, startSeg, 1.0 + delta,
structure.GetDistance( startSeg-3, startSeg ) )
end
if endSeg + 3 <= segCt then
BandBetweenSegsWithParameters( endSeg, endSeg + 3, 1.0 + delta,
structure.GetDistance( endSeg, endSeg+3 ) )
end
end
selection.DeselectAll()
getSegsWithinRangeOfTube( idxList, startSeg, endSeg, IDEALIZE_TUBE_RADIUS )
for i=1, #idxList do selection.Select( idxList[i] ) end
structure.IdealizeSelected()
selection.DeselectAll()
if useCuts then
-- get some wiggling happening
print( "WiggleZlbNicely" )
WiggleZlbNicely( iters, IDEALIZE_ZLB_STRENGTH + delta,
math.max( 1, startSeg-2 ), math.min( segCt, endSeg+2 ) )
-- now put the protein back in its proper shape
if startSeg - 2 > 1 then
if not DeleteCutAtIndex(startSeg - 2 ) and IDEALIZE_FORBID_UNHEALED_CUTS then
changeFailed = true
end
end
if changeFailed == false and endSeg + 1 < segCt then
if not DeleteCutAtIndex(endSeg + 1 ) and IDEALIZE_FORBID_UNHEALED_CUTS then
changeFailed = true
end
end
else -- since we didn't put in any cuts, we can try simple wiggling to see how we did
print( "WiggleRange" )
WiggleRange( iters, startSeg - 2, endSeg + 1, false, random() < 0.50 )
ShakeOrMutate( iters/2 )
end
if not changeFailed then
DelBands()
return true
end
tries = tries + 1
delta = delta + 0.25
until tries >= IDEALIZE_MAXTRIES
DelBands()
return false
end
----------------------------------------------------------------------
-- REBUILDERS
-- TRY: freeze before and after start-end or cut before and after start-end
-- ALSO: try a rebuild that changes Secondary structure to loop before
-- run, restores it after run
----------------------------------------------------------------------
function RebuildInRange( startSeg, endSeg, iters, maxTries, maxFall )
if maxFall == nil then maxFall = REBUILD_MAX_MEDIUM_SCOREDROP end
print( "RebuildInRange from "..startSeg.." to "..endSeg )
changeSucceeded = false
local oldScoreMat = {}
local locss = {}
local killedSS = false
local startScore = getScore( )
ConstructScoreMatrix( oldScoreMat )
-- probably PickRebuild should decide whether to do secondary structure stuff
for i=startSeg, endSeg do locss[i] = structure.GetSecondaryStructure( i ) end
for i=startSeg, endSeg do
if structure.GetSecondaryStructure( i ) == 'E' then
killedSS = true
structure.SetSecondaryStructure( i, 'L' )
end
end
selection.DeselectAll()
save.Quicksave( QS_ShortUseTemp1 ) -- holds our "running" pose
save.Quicksave( QS_ShortUseTemp2 ) -- holds our "best so far" result
bestRescore = -10000.0
for i=1, maxTries do
local idxListInner = { }
save.Quickload( QS_ShortUseTemp1 ) -- bring up our "running" pose
selection.SelectRange( startSeg, endSeg )
RebuildSelected( i ) -- not only up to maxTries times, but up to maxTries iterations per time
selection.DeselectAll( )
if math.abs( getScore( ) - startScore ) >= 0.01 then
-- give the whole thing a little bit of shake-and-wiggle-time (deliberately not complete)
structure.WiggleSelected( iters )
getSegsWithinRangeOfTube( idxListInner, startSeg, endSeg, REBUILD_INNER_TUBE_RADIUS )
ShakeOrMutate( iters/2, idxListInner ) -- only shake the "nearby" segments
structure.WiggleAll( iters, true, true ) -- just a short bit of time on this wiggle to settle things down a bit
rescore = getScore()
if rescore > startScore then -- we have a real improvement!
save.Quicksave( QS_ShortUseTemp1 ) -- replace our "running" pose with this better one
save.Quicksave( QS_ShortUseTemp2 ) -- our best-so-far is better
startScore = rescore
bestRescore = rescore
changeSucceeded = true
else -- not improved, but maybe it's worth keeping anyway?
if rescore > bestRescore and IsNeighborDifferentFromOld( oldScoreMat, 0.1 ) then
save.Quicksave( QS_ShortUseTemp2 ) -- our best-so-far is better
bestRescore = rescore
if startScore - rescore < maxFall then changeSucceeded = true end
end
end
end
end
save.Quickload( QS_ShortUseTemp2 )
if killedSS then
for i=startSeg, endSeg do structure.SetSecondaryStructure( i, locss[ i ] ) end
end
selection.DeselectAll( )
return changeSucceeded
end
function RebuildSection_Quaker( startSeg, endSeg, maxTries, maxFall )
print( "RebuildSection_Quaker from "..startSeg.." to "..endSeg )
local initScore = getScore( )
local tries = 0
repeat
selection.DeselectAll( )
selection.SelectRange( startSeg, endSeg )
tries = tries + 1
RebuildSelected( tries )
newScore = getScore( )
until newScore ~= initScore or tries > maxTries
if newScore == initScore then
return false -- we failed
end
recentbest.Save( ) -- N.B. This is exactly the sort of situation recentbest was made for!
RebuildSelected( maxTries - 1 ) -- maybe use REBUILD_MAXITERS - 1 ?
recentbest.Restore( )
WiggleAndShakeForRebuilder( initScore, maxFall )
-- quaking rebuild put a perform-quake-if-score-not-better-but-not-too-bad here
return true
end
function RebuildSimple( idx, len, addCleanupWiggle ) -- randomizeR calls with idx=randomSeg(), len=random( 4 ), true
save.Quicksave( QS_ShortUseTemp1 )
local initscore = getScore( )
selection.DeselectAll( )
selection.SelectRange( idx, idx + len - 1 )
RebuildSelected( 1 )
structure.ShakeSidechainsSelected( 1 )
structure.IdealizeSelected( )
structure.ShakeSidechainsSelected( 1 )
structure.WiggleAll( 25 )
structure.ShakeSidechainsAll( 1 )
structure.WiggleAll( 25 )
local currscore = getScore( )
if currscore > initscore then
save.Quicksave( QS_ShortUseTemp1 )
initscore = currscore
end
if addCleanupWiggle then
BlueFuse( )
if currscore > initscore then
save.Quicksave( QS_ShortUseTemp1 )
end
end
save.Quickload( QS_ShortUseTemp1 )
return true
end
----------------------------------------------------------------------
-- OPERATION-SPECIFIC CHOOSING FUNCTIONS
----------------------------------------------------------------------
function PickRebuildOperation( startSeg, endSeg, maxFall )
local iters = QUICKWIGGLE_ITERS
changeSucceeded = false
if random( ) < 0.50 then
algName = "RebuildInRange"
local initScore = getScore( )
changeSucceeded = RebuildInRange( startSeg, endSeg, iters, REBUILD_TRYCOUNT, maxFall )
if changeSucceeded then
if REBUILD_ADDIDEALIZE then
DebugPrint("Putting Idealize after rebuild")
save.Quicksave( QS_ShortUseTemp2 ) -- so we can throw out the idealize if we don't like result
local score = getScore( )
local check = IdealizeInRange( startSeg, endSeg, iters, REBUILD_USECUTS )
if check and score < getScore( ) then
save.Quicksave( QS_ShortUseTemp2 )
score = getScore( )
end
save.Quickload( QS_ShortUseTemp2 )
end
local idxListOuter = {}
getSegsWithinRangeOfTube( idxListOuter, startSeg, endSeg, REBUILD_OUTER_TUBE_RADIUS )
IterativeWiggleShakeWiggle( idxListOuter, iters )
end
else
-- this one comes with its own wiggler and skaker built-in
algName = "RebuildSectionQ" -- we'll just overrule alg's name choice for the time being
changeSucceeded = RebuildSection_Quaker( startSeg, endSeg, 8, maxFall ) -- 8 => REBUILD_TRYCOUNT ?
end
return changeSucceeded
end
function PickPointRebuildOperation( idx, maxRange, maxFall )
if maxFall == nil then maxFall = REBUILD_MAX_MEDIUM_SCOREDROP end
if maxRange == nil then maxRange = math.floor( REBUILD_MAX_SEGLEN / 2 ) end
local startIdx = math.max( 1, idx - random( maxRange ) )
local endIdx = math.min( segCt, idx + random( maxRange ) )
for i=idx, endIdx do
if not IsMovableSeg(i) then
endIdx = i - 1
break
end
end
for i=idx, startIdx, -1 do
if not IsMovableSeg(i) then
startIdx = i + 1
break
end
end
return PickRebuildOperation( startIdx, endIdx, maxFall )
end
function PickIdealizeRangeOperation( idx )
local iters = QUICKWIGGLE_ITERS
local startIdx, endIdx = GetRegionForOperation( idx )
if endIdx - startIdx + 1 > IDEALIZE_MAX_SEGLEN then
if idx - startIdx + 1 > IDEALIZE_MAX_SEGLEN then
startIdx = math.max( 1, idx - IDEALIZE_MAX_SEGLEN)
end
end
if endIdx - startIdx + 1 > IDEALIZE_MAX_SEGLEN then
endIdx = startIdx + IDEALIZE_MAX_SEGLEN - 1
end
changeSucceeded = IdealizeInRange( startIdx, endIdx, iters, IDEALIZE_USE_CUTS )
if changeSucceeded then qStabWiggle( iters, random() < PROB_QSTAB_EXTRA ) end
return changeSucceeded
end
function PickIdealizePointOperation( idx )
local iters = QUICKWIGGLE_ITERS
changeSucceeded = IdealizeInRange( idx, idx, iters, IDEALIZE_USE_CUTS )
if changeSucceeded then qStabWiggle( iters, random() < PROB_QSTAB_EXTRA ) end
return changeSucceeded
end
--------------------------------------------------------------------------
-- MAIN AND SETUP AND CLEANING
--------------------------------------------------------------------------
function CleanPuzzleState( )
SetCI( 1.0 )
selection.DeselectAll()
freeze.UnfreezeAll()
DelBands()
for i=1,segCt do
if IsMovableSeg(i) then DeleteCutAtIndex( i ) end -- we can try, at least!
end
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( )
print("")
ReportStats( )
print( "TotalShakeTime="..TotalShakeTime ) -- because I want to know.
CleanPuzzleState( )
end
function InitializePuzzleState()
seedRandom( )
InitialScore = getScore( )
StartTime = os.time( )
-- learn a few things
FindAllMovableSegs()
getHelices( )
getSheets( )
getLoops( )
PuzzleAllowsMutate = IsPuzzleMutable( )
if PuzzleAllowsMutate then IsDesignerPuzzle = checkAla() end
CheckForContactMap()
if PuzzleHasContactMap then InitializeContactMapSegList( CONTACTMAP_THRESHOLD ) end
InitialClashImportance = behavior.GetClashImportance( )
SCALE_MAXCI = InitialClashImportance
selection.DeselectAll()
freeze.UnfreezeAll()
DelBands( )
recentbest.Save( )
end
function main()
InitializePuzzleState()
didSomething = false
------------------------DEBUG STUFF
-- if any actions could stand some debugging, put them here
-- then multi-line comment from below to end of function
------------------------
while LetUserChooseParams( ) do
PerformOperation( )
didSomething = true
PrintState( )
end
print( " Done!" )
if didSomething then End( ) end
end
xpcall( main, End )