Code
--Banded Worm Pairs Infinite (& Filter)
------------------------------------------------------------------------------
-- Banded Worm
------------------------------------------------------------------------------
-- Modifies Worm LWS v2 by rav3n_pl
--
-- by KarenCH
------------------------------------------------------------------------------
-- Made infinite and filters optimized by Bruno Kestemont 15/2/2015
-- v 1.1 corrected random contact map 20/9/2015
-- v 1.2 added random use of user bands (and random multiplier of their strength)
-- v 1.2.1 undo.SetUndo
-- v 1.3.0 BAND_TO_SIDECHAINS allowed
-- v 1.3.1 Fixed unideal loop bug (thanks to gitwut) (band= true)
-- v 1.3.2 Second attempt to fix it (added save.Quickload)
-- v 1.4 Dialog for filters
-- v 1.4.1 and tried again to fix GENERICFILTER on recentbest (see feedback discussion, Foldit Bug)
-- replaced by FakeRecentBestSave() and FakeRecentBestRestore() 29/8/2017
-- v 1.4.2 added Ligand dialog
-- v 1.4.3 systematic display of current score (Greg's suggestion), again fixing filter bug
-- v 1.4.4 fixed detect bonusses
-- v 2.0 saves and loads intermediate solutions to/from PC (usefull for parallel recipes tracking)
-- v 2.1 testing for sharing on each Savebest
-- v 3.0 replaced Hardsave by Isaksson share
-- a dialog is now always included
-- v 3.1 added band to symmetrics, 31/10/2019
-- (v 3.2 is on another track)
-- v 3.3 weighted scores and conditions (directly from 3.1)
-- v 3.3.1 fixed conditions weighted score
-- v 3.3.2 printing more info (jeff101's suggestions)
-- v 3.4 sketchbook adapted: minGain
-- v 3.4.1 trying to fix filter score bug
-- v 3.4.2 Isaksson share 1.4
-- v 3.4.3 Isaksson share 1.5.1
-- v 3.4.4 Redesign of MAIN by TurtleByte
-- v 3.4.5 Avoiding bug for old Foldit versions if structure.GetSymCount is nil
-- better seedrandom by LociOiling
-- added SET NOTES BY PD
-- v 3.4.5 fixed sym
-- v 3.4.7 fixed patternnb=table.concat(pattern, ",") for printState
-- v 3.4.8b (number used by LociOiling) fixed Firstligand typo bug
-- v 3.4.9 fixing quicksave bug by initializing quicksaves
-- v 3.4.10 restored line recentbest.restore in function BandedWorm
--TO DO = probability otions in advanced dialog
recipename= "Banded Worm Pairs Inf Filter 3.4.10"
undo.SetUndo(false)
-- interesting global variables that users might want to modify
PROB_CHOOSE_USERBANDS = 0.10 -- don't put it too high
PROB_CHOOSE_CONTACTMAP = 0.60
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
PROB_BAND_TO_LIGAND= 0 -- prob band to ligand if ligand
-- new symmetry - 20200409 by LociOiling
--
OLDFOLDIT=false
if filter.GetNames == nil or structure.GetSymCount == nil then OLDFOLDIT=true print("OLD FOLDIT VERSION DETECTED") end
symChains = 0 -- set later
symTypes = { -- indexed by symChains + 1
"monomer", -- just for completeness
"dimer",
"trimer",
"tetramer",
"pentamer",
"hexamer",
"heptamer",
"octamer",
}
symTyp = "" -- actual symmetry type
PROB_BAND_TO_SYMMETRIC=0 -- probablity of banding to symmetric chain
-- init gives equal chance to band to any branche including first one, 0 chance if monomer
-- 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 -- min heat of contacts
USENORMALSCORE = true -- exploration puzzles would set false
DEBUGRUN = false -- mostly goes with DebugPrint, but could get more use later
-- atoms in an amino acid
BETA_CARBON = 5
TERMINAL_BETA = 6 -- not used
CENTER_CARBON = 2 --this is the default atom for bands to attach to
BAND_TO_SIDECHAINS = true -- New BK, some bands to sidechains as well
-- variables users probably don't want to play with (SLOTS)
QS_Start = 1
QS_Best = 3
Qs_Recent = 5 -- for debugging recentbest bug
Qs_Current = 6 -- for debugging recentbest score
-- variables users really shouldn't play with
PuzzleHasContactMap = false
PuzzleHasLockedSegs = false
EndCalled = false
InitialScore = 0.0 -- not important: will be reinitialized in InitializePuzzleState( )
StartTime = 0 -- not important: will be reinitialized in InitializePuzzleState( )
CurrentBestScore = 0 -- not important: will be reinitialized in InitializePuzzleState( )
SStartTime=os.time()
InitialClashImportance = behavior.GetClashImportance()
NonLockedSegList = {}
segCt = structure.GetCount()
MinUnlockedSeg = 1
MaxUnlockedSeg = segCt
minGain=0 -- for sketchbook (in SaveBest)
segCnt2=segCt
while structure.GetSecondaryStructure(segCnt2)=="M" do segCnt2=segCnt2-1 end
FirstLigand= segCnt2 -- 0 if no ligand
LastLigand= segCt
InitialBandCount = 0
ubcount = 0 -- user bands
ubandlist={} -- {band number, band strength, goal_length}
USERBANDS=false
PROBABLEFILTER=false
--FILTERMANAGE=false -- default yes during wiggle (will always be activate when scoring)
GENERICFILTER=false
PROBABLELIGAND=false
GENERICLIGAND=false
SKETCHBOOK=false
gen=0 -- this is only to have something to write for Poldunn set notes
--initializing the slots in order not to use quicksaves from before running the recipe
save.Quicksave( QS_Start ) -- DEBUG, trying to debug the return to other recipe's solution
save.Quicksave( QS_Best ) -- DEBUG, trying to debug the return to other recipe's solution
save.Quicksave( Qs_Recent ) -- DEBUG, trying to debug the return to other recipe's solution
save.Quicksave( Qs_Current ) -- DEBUG, trying to debug the return to other recipe's solution
--identifying explicitly (heavy) filtered puzzles (Contact with remain filter enabled, what is good)
--START extraction of information from puzzle metadata --Extrait des infos
function detectfilterandmut() -- Bruno Kestemont 10/10/2013; 13/2/2015; 5/1/2019, 8/11/2019, 18/5/2020
local descrTxt=puzzle.GetDescription()
local puzzletitle=puzzle.GetName()
local sym=1
local function SymetryFinder() -- by Bruno Kestemont 7/2/2013, 25/8/2013, 18/5/2020 to find number of symmetrics
local segMeanScore=(scoreboard.GetGroupScore()-8000)/segCt -- top score pour eviter les debuts de puzzle
--p("Score moyen par segment= "..segMeanScore)
if PROBABLESYM then
if segMeanScore<33.39 then sym=1
PROBABLESYM=false
elseif segMeanScore<85 then sym=2
elseif segMeanScore<132 then sym=3
elseif segMeanScore<197 then sym=4
else sym=5
end
else sym=1
end
return
end
local function FindSymmetryFromDescription() -- for old Foldit versions
if #puzzletitle>0 then
if (puzzletitle:find("Sym") or puzzletitle:find("Symmetry") or puzzletitle:find("Symmetric")
or puzzletitle:find("Dimer") or puzzletitle:find("Trimer") or puzzletitle:find("Tetramer")
or puzzletitle:find("Pentamer")) then
PROBABLESYM=true
if puzzletitle:find("Dimer") and not puzzletitle:find("Dimer of Dimers") then sym=2
elseif puzzletitle:find("Trimer") or puzzletitle:find("trimer") then sym=3
elseif puzzletitle:find("Dimer of Dimers") or puzzletitle:find("Tetramer") then sym=4
elseif puzzletitle:find("Pentamer") then sym=5
else SymetryFinder() -- only if PROBABLESYM yet
end
end
end
if #descrTxt>0 and (descrTxt:find("Sym") or descrTxt:find("Symmetry") or descrTxt:find("Symmetric")
or descrTxt:find("sym") or descrTxt:find("symmetry") or descrTxt:find("symmetric")) then
PROBABLESYM=true
if (descrTxt:find("Dimer") or descrTxt:find("dimer") or descrTxt:find("C2 symmetry") or descrTxt:find("twoo symmetric"))
and not (descrTxt:find("Dimer of Dimers") or descrTxt:find("dimer of dimers")) then sym=2
elseif descrTxt:find("Trimer") or descrTxt:find("trimer") or descrTxt:find("C3 symmetry") or descrTxt:find("three symmetric") then sym=3
elseif (descrTxt:find("Dimer of Dimers") or descrTxt:find("Tetramer") or descrTxt:find("C4 symmetry") or descrTxt:find("four symmetric"))
and not (descrTxt:find("dimer of dimers") or descrTxt:find("tetramer"))then sym=4
elseif descrTxt:find("Pentamer") or descrTxt:find("pentamer") or descrTxt:find("C5 symmetry") or descrTxt:find("five symmetric") then sym=5
else SymetryFinder()
end
end
end
if #descrTxt>0 and (descrTxt:find("Move Limit") or descrTxt:find("SKETCHBOOK") or descrTxt:find("sketchbook")) then
SKETCHBOOK=true
FILTERMANAGE=false -- better no: one cut is one move
GENERICFILTER=false -- better no: one cut is one move
print("SKETCHBOOK")
end
if #descrTxt>0 and (descrTxt:find("filter") or descrTxt:find("filters") or descrTxt:find("bonus") or descrTxt:find("bonuses") and not descrTxt:find("disabled"))then
PROBABLEFILTER=true
FILTERMANAGE=true -- default yes during wiggle (will always be activate when scoring)
GENERICFILTER=false -- to be evaluated
end
if #descrTxt>0 and (descrTxt:find("H-bond networks") or descrTxt:find("Hydrogen Bond Networks") or descrTxt:find("H-bond Networks")
or descrTxt:find("H-bond Network") and not descrTxt:find("disabled"))then
PROBABLEFILTER=true
FILTERMANAGE=false -- default no
GENERICFILTER=false -- to be evaluated
end
if filter.GetBonusTotal() ~= 0 then PROBABLEFILTER=true end
if #descrTxt>0 and (descrTxt:find("design") or descrTxt:find("designs")) then
HASMUTABLE=true
IDEALCHECK=true
HANDFOLD=true
end
if #descrTxt>0 and (descrTxt:find("De-novo") or descrTxt:find("de-novo") or descrTxt:find("freestyle")
or descrTxt:find("prediction") or descrTxt:find("predictions")) then
IDEALCHECK=true
HANDFOLD=true
end
-- new way to find symmetrics using new command
if structure.GetSymCount ~= nil then -- temporary workaround until functions released to main, thanks to LociOiling
symChains = structure.GetSymCount () -- set global symChains (0 f monomer, 1 if dimer etc)
if symChains > 0 then
PROBABLESYM=true
sim = symChains + 1 -- used for printing
local sw = ""
sw = sim .. "-way"
if sim <= #symTypes then
symTyp = symTypes [ sim ] .. " (" .. sw .. ")"
else
symTyp = sw
end
print ( symTyp .. " symmetry puzzle" )
--
-- adjust default odds of banding to a symmetric chain
PROB_BAND_TO_SYMMETRIC = symChains / (1 + symChains) -- if band between segs, probability to band to a symmetric. If, 1, never band to itself
end
else
FindSymmetryFromDescription()
end
--print resulting sym info
if PROBABLESYM then
print("Symmetric")
if sym==2 then
print("Dimer")
elseif sym==3 then
print("Trimer")
elseif sym==4 then
print("Tetramer")
elseif sym==5 then
print("Pentamer")
elseif sym>5 then
print("Terrible polymer")
end
else print("Monomer")
end
if #puzzletitle>0 and puzzletitle:find("Sepsis") then -- new BK 17/6/2013
SEPSIS=true
HANDFOLD=true
--p(true,"-Sepsis")
print("Sepsis")
end
if #puzzletitle>0 and puzzletitle:find("Electron Density") then -- for Electron Density
--p(true,"-Electron Density")
ELECTRON=true
HANDFOLD=true
print("Electron density")
end
if #puzzletitle>0 and puzzletitle:find("Centroid") then -- New BK 20/10/2013
--p(true,"-Centroid")
CENTROID=true
print("Centroid")
end
if #puzzletitle>0 and puzzletitle:find("Hotspot") then -- New BK 21/01/2014
HOTSPOT=true
print("Hotspot")
end
return
end
detectfilterandmut()
--END extraction of information from puzzle metadata --Extrait des infos
--START Generic Filter Management by BitSpawn 21/12/2014, updated by Bruno Kestemont 4/1/2019
--Source: http://fold.it/portal/node/1998917
--Note: GENERICFILTER must be defined elsewhere (otherwise, it will not do anything)
-- GENERICFILTER=true
-- function to copy class/table
function CopyTable(orig)
local copy = {}
for orig_key, orig_value in pairs(orig) do
copy[orig_key] = orig_value
end
return copy
end
-- functions for filters
function FiltersOn()
if filter.AreAllEnabled()==false then
filter.EnableAll()
end
end
function FiltersOff()
if filter.AreAllEnabled() then
filter.DisableAll()
end
end
-- function to overload a function
function mutFunction(func)
local currentfunc = func
local function mutate(func, newfunc)
local lastfunc = currentfunc
currentfunc = function(...) return newfunc(lastfunc, ...) end
end
local wrapper = function(...) return currentfunc(...) end
return wrapper, mutate
end
-- function to overload a class
-- to do: set the name of function
classes_copied = 0
myclcp = {}
function MutClass(cl, filters)
classes_copied = classes_copied+1
myclcp[classes_copied] = CopyTable(cl)
local mycl =myclcp[classes_copied]
for orig_key, orig_value in pairs(cl) do
myfunc, mutate = mutFunction(mycl[orig_key])
if filters==true then
mutate(myfunc, function(...)
FiltersOn()
if table.getn(arg)>1 then
-- first arg is self (function pointer), we pack from second argument
local arguments = {}
for i=2,table.getn(arg) do
arguments[i-1]=arg[i]
end
return mycl[orig_key](unpack(arguments))
else
--print("No arguments")
return mycl[orig_key]()
end
end)
cl[orig_key] = myfunc
else
mutate(myfunc, function(...)
FiltersOff()
if table.getn(arg)>1 then
local arguments = {}
for i=2, table.getn(arg) do
arguments[i-1]=arg[i]
end
return mycl[orig_key](unpack(arguments))
else
return mycl[orig_key]()
end
end)
cl[orig_key] = myfunc
end
end
end
-- how to use:
--setting default options if filters BK 4/2/2015
--MutClass(structure, false)
--MutClass(band, false)
--MutClass(current, true)
--STOP Generic Filter Management
------------------------------------------------------------------------------
-- FUNCTIONS
------------------------------------------------------------------------------
function BandedWorm( pattern )
recentbest.Save( ) -- restored 1/12/2021
FakeRecentBestSave()
SetCI( 1.0 )
SaveBest( )
local ss = getScore()
local idx=1
for w = 1, #pattern do
local ps=getScore()
save.Quickload( QS_Best ) -- new for DEBUG, returning to Savebest (weighted) solution
before_Solution(rnd,str_name) -- on start of each rnd or loop
len = pattern[ w ]
local sw = getScore()
local cg=sw-ps -- correction gain when returning to savebest (weighted) solution
if cg<0 then print("Returned to best weighted score, gain correction: ", TrimNum(cg)) end
local swCurr = sw
print( "Starting BandedWormPairs of len " .. len .. ", score: ".. TrimNum( swCurr ) )
for s=MinUnlockedSeg, MaxUnlockedSeg - len + 1 do
selection.DeselectAll()
selection.SelectRange( s, s + len - 1 )
if random( ) < PROB_PUTBAND then
if random( ) < PROB_BAND_TO_LIGAND then
idx= random( FirstLigand, LastLigand)
else
idx = random( s, s + len - 1 )
end
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
uBandEnabel() -- new v1.2 random adding one of the user bands
structure.LocalWiggleSelected( random(2,4) )
ManageBands( )
structure.WiggleAll( 2 )
end
structure.LocalWiggleSelected( 5 )
local swNew = getScore( )
local gain = swNew - swCurr
if gain > 0 then
structure.LocalWiggleSelected( 20 )
--recentbest.Restore( )
FakeRecentBestRestore()
ManageBands( )
swNew = getScore( )
gain = swNew - swCurr
if TrimNum( gain ) > 0 then
print( ">>>> At " .. s .. ": Gained ".. TrimNum( gain ).." Score: "..TrimNum( swNew ))
end
SaveBest( )
swCurr = swNew
else
--recentbest.Restore( )
FakeRecentBestRestore()
structure.LocalWiggleSelected( 4 )
end
--recentbest.Restore( )
FakeRecentBestRestore()
ManageBands( )
end
local pg= getScore( ) - sw
print( "Pattern gain: ".. pg )
SaveBest( )
gen = gen+1 -- added for the following Poldunn set notes
--3a) Uncommend the 2 following lines and COPY from here ---------------
loop_count=gen -- adapt "gen" to the name of loops used in the recipe
SetNote(note_number, starting_score, recipename, loop_count) -- WARNING: and mach "recipename" and "loop_count" with the names of the recipe
--3b) COPY to here after main generations or loops of the recipe --------------
end
selection.DeselectAll()
print( "Total BandedWormPairs gain: " .. TrimNum( getScore( ) - ss ) )
end
function PutSingleRandomBandToSeg( idx, probBis ) -- to symmetric as well
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 -- changed > to < BK
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
-- START Full Score stuff by Bruno Kestemont 8/11/2019
-- Copy this at the beginning of the recipe
-- Light version with total bonus only
-- Use: Score= FullScore() (always full score)
-- Use (weighted): WScore= FullScore(10) with weighted bonus x 10.
-- if there are no bonusses, the weight has no effect, normal score is calculated.
FilterWeight = 1 -- default
energy= false
function FullScore(weight)--return score, exploration too, weithed filter
local weight = weight or 1
local result=0
local FiltersWereDisabled = not filter.AreAllEnabled() -- if true, all filters are default enabled
if FiltersWereDisabled then -- I think it's necessary to be able to calculate the bonus
filter.EnableAll() -- Enables all filters
end
local bonus = filter.GetBonusTotal() -- 0 if no filter
weight=weight-1 -- reset default to 0 because all calculations bellow are with filter enables
if energy==true then
result= current.GetEnergyScore()
else
result= current.GetScore()
end
result= result + bonus * weight
if FiltersWereDisabled then -- return to the previous state
filter.DisableAll()
end
return result
end
-- END Full Score stuff
-- Not for "external" use - call getScore. This could change if customers want
-- something besides current or recentbest.
energy = not USENORMALSCORE
function internalGetScore( wantRB )
if wantRB == nil then wantRB = false end
local s=0.0
if not USENORMALSCORE then
if wantRB then
--s = recentbest.GetEnergyScore( )
s= FakeRecentBestGetEnergyScore()
else s=current.GetEnergyScore( )
end
else
if wantRB then
--s = recentbest.GetScore( )
s = FakeRecentBestGetScore()
else s=current.GetScore( )
end
end
return s
end
function getScore( )
return internalGetScore( false )
end
function getRBScore( )
return internalGetScore( true )
end
function SaveBest( weighted) -- NEW version for filter weighting (BY DEFAULT !!)
local weighted = weighted or true
local score = getScore( )
if weighted and FilterWeight > 1 then score = FullScore(FilterWeight) end
if score > CurrentBestScore + minGain then -- minGain for sketchbook
save.Quicksave( QS_Best )
CurrentBestScore = score
after_Solution(rnd,str_name) -- after any gain
else
save.Quickload( QS_Best ) --
if score < CurrentBestScore + minGain then
print("Mingain not met, return to saved solution")
end
end
end
--START Debugging Recentbest Foldit Bug Temporary solution of Foldit bug (BK 29/8/2017)
function Score(weight) -- for BWPIF only
--return current.GetScore()
local weight=weight or 1
return FullScore(weight)
end
function FakeRecentBestSave()
if PROBABLEFILTER then -- trying to solve the Foldit bug
save.Quicksave(Qs_Recent)
else
recentbest.Save()
end
end
function FakeRecentBestRestore()
if PROBABLEFILTER then -- trying to solve the Foldit bug
local ss=Score(weight)
recentbest.Restore() -- filter disabled (bug)
local se=Score(weight) -- now with the filter
if se > ss then
save.Quicksave(Qs_Recent)
end
save.Quickload(Qs_Recent)
else
recentbest.Restore()
end
end
function FakeRecentBestGetScore()
if PROBABLEFILTER then -- trying to solve the Foldit bug
save.Quicksave(Qs_Current)
recentbest.Restore() -- filter disabled (bug)
local se=Score(weight) -- now with the filter
save.Quickload(Qs_Current)
return se
else
recentbest.GetScore( )
end
end
function FakeRecentBestGetEnergyScore()
if PROBABLEFILTER then -- trying to solve the Foldit bug
save.Quicksave(Qs_Current)
recentbest.Restore() -- filter disabled (bug)
local se=current.GetEnergyScore( ) -- now with the filter
save.Quickload(Qs_Current)
return se
else
recentbest.GetEnergyScore( )
end
end
--END Debugging Recentbest Foldit Bug
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 -----------------------
-- seedRandom -- original by KarenCH
--
-- looks for a seed > 10,000,000 and < 2 ^ 32
--
-- v2 - LociOiling - 20191103
-- * added 2 ^ 32 overflow check
--
function seedRandom()
local seed = os.time () / math.abs ( current.GetEnergyScore () )
seed = seed % 0.001
seed = 1 / seed
while seed < 10000000 do
seed = seed * 1000
end
while seed > 2 ^ 32 do
seed = seed / 10
end
seed = seed - seed % 1
print ( "Random number seed = " .. 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 randomSym( ) -- random symmetric chain to band, 0 if no symmetry
if structure.GetSymCount ~= nil then
return random( structure.GetSymCount ()) -- 1 or 2 if trimer
else return 0
end
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, 4 ) -- consider adjusting probability?
if r == 1 then
return BETA_CARBON
elseif r == 2 then
return CENTER_CARBON
else
return GetTipAtomOfSeg( idx ) -- 50% of the cases
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 uMakeBands() -- list of existing user bands, strength, goal length
ubcount=band.GetCount()
for i=1, ubcount do
ubandlist[i]={i, band.GetStrength(i), band.GetGoalLength(i)} -- {band number, band strength, goal_length}
end
if #ubandlist==0 then
USERBANDS=false
else
USERBANDS=true
end
return ubandlist
end
function uBandEnabel() -- random enable a user band
if USERBANDS and PROB_CHOOSE_USERBANDS >= random(0.0,1.0) then
local maxstrength= 5
local minstrength=0.1
local bandIndex= random(1,ubcount)
local multiplier = random( 0.5,2)
local bandstrength=band.GetStrength(bandIndex)
bandstrength=ubandlist[bandIndex][2]*multiplier
if bandstrength>maxstrength then
bandstrength=maxstrength
elseif bandstrength<minstrength then
bandstrength=minstrength
end
band.SetStrength(bandIndex, bandstrength) -- to do: random length?
band.Enable(bandIndex)
end
end
function ManageBands() --delete recipe bands, disable user bands
if ubcount==0 or ubcount==nil then band.DeleteAll()
else
local bands=band.GetCount()
if bands>ubcount then
for i=bands, ubcount+1, -1 do band.Delete(i) end -- TO DO: il reste peut etre une bande ici
end
end
band.DisableAll()
end
function BandBetweenSegsWithParameters( seg1, seg2, strength, goalLength, atom1, atom2 , symchain)
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=0 -- it's a band
if OLDFOLDIT then bIdx = band.AddBetweenSegments( seg1, seg2, atom1, atom2 ) -- old Foldit Version
else bIdx = band.AddBetweenSegments( seg1, seg2, atom1, atom2, symchain )
end
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, atomIndexOrigin, atomIndexXAxis, atomIndexYAxis)
if not ( IsUnlockedSeg( seg) ) then return false end
local atomIndexOrigin, atomIndexXAxis, atomIndexYAxis = atomIndexOrigin or CENTER_CARBON, atomIndexXAxis or 0, atomIndexYAxis or 0
local bIdx = band.Add( seg, segFini, segInit, rho, theta, phi, atomIndexOrigin, atomIndexXAxis, atomIndexYAxis )
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 )
local atomIndexOrigin, atomIndexXAxis, atomIndexYAxis = 0,0,0 -- x and y not used actually
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
-- random atom for the banded (BIS) segment
local MaxatomIndexOrigin = PickAtomNumberForBand( idx ) -- it's the end of the sidechain
atomIndexOrigin = random( 0 , MaxatomIndexOrigin )
return BandInSpaceWithParameters( idx, idx2, idx3, rho, theta, phi, strength, atomIndexOrigin, atomIndexXAxis, atomIndexYAxis )
end
function PutBandToRandomSeg( idx, minGap, strength, atom ) -- adapted to sym 31/10/2019 B. Kestemont
needNew = true
local failedTries = 0
local doSYM = random( ) < PROB_BAND_TO_SYMMETRIC
while ( needNew and failedTries < 30 ) do
idx2 = randomSeg( ) -- ok if this one isn't movable (we assume idx is movable)
symchain= randomSym( ) -- 0 if no symmetry, 1 if dimer, 1 or 2 if trimer ...
if not doSYM then symchain=0 end -- there are (1- PROB_BAND_TO_SYMMETRIC) chances to band to itself
if symchain > 0 or idx2 > idx + minGap or idx2 < idx - minGap then
needNew = false
break
else
failedTries = failedTries + 1
end
end
if not needNew then
--print("DEBUG idx1 - idx2 - sym ",idx, idx2, symchain)
return BandBetweenSegsWithParameters( idx, idx2, strength, nil, atom, nil , symchain)
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, doTip1, doTip2 )
changeSucceeded = ch or changeSucceeded
end
return changeSucceeded
end
--------------------------------------------------------------------------
-- SETUP, CLEANUP, and MAIN
--------------------------------------------------------------------------
function CleanPuzzleState( )
SetCI( 1.0 )
selection.DeselectAll()
ManageBands()
end
function PrintState( )
print("#######")
print("Status of main loop "..mainloop.." pattern "..patternnb)
local gain = getScore() - UnweightedInitialScore
local TotalGain=getScore() - UUnweightedInitialScore
if gain < 0.001 then
print( " No change" )
else
print( " Pattern startscore: "..TrimNum( UnweightedInitialScore ))
print( " Score: "..TrimNum( getScore() ) )
print( " Pattern gain: "..TrimNum( gain ))
end
print( " Pattern run time: "..os.time() - StartTime)
print( " Total run time: "..os.time() - SStartTime)
print( " Total bwpi gain: "..TrimNum( TotalGain ) .. " from "..TrimNum( UUnweightedInitialScore ).." to "..TrimNum( getScore() ))
end
function End( errstr )
undo.SetUndo(true)
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
--START Isaksson Share v1.5.1
--Copy this into your recipe and call before and after every period of time.
--Warning: all saves onto you hard disk will have to be removed manually afterwards !
--reference: supplementary material for Koepnick et al (2019) De novo protein design by citizen scientists, Nature, volume 570, pages 390394
--https://static-content.springer.com/esm/art%3A10.1038%2Fs41586-019-1274-4/MediaObjects/41586_2019_1274_MOESM1_ESM.pdf
--v1: script by Isaksson
--v1.2 added the dialog (BK 1/10/2019)
--v1.3 added time intervals when no gain
--v1.4 PrintVerbose, check every 60 seconds
--v1.5 option Temp name
--v1.5.1 fixed timeleft
timeLeft=os.difftime(puzzle.GetExpirationTime(),os.time())/3600 -- in hours
print("Remaining time: "..timeLeft)
startTime=os.time() -- in seconds
userTimeInterval= 60 -- in seconds
lastchecktime = startTime -- in seconds
verbose = false -- print more or less info
save_resolution=1000
function timeLeft()
local result=os.difftime(puzzle.GetExpirationTime(),os.time())/3600 -- in hours
return result
end
function PrintVerbose(text)
if verbose then
print(text)
end
end
function ItsCheckTime()
local currentTime= os.time() -- in seconds
if currentTime-lastchecktime > userTimeInterval then
PrintVerbose(timeLeft().." hours remaining")
return true
end
return false
end
function rounding(x)--cut all afer 3-rd place
return x-x%0.001
end
function before_Solution(rnd,str_name)
if save_resolution > 999 then return end -- New BK 1/10/2019
PrintVerbose("-Before-")
hi_score = 0
hi_idx = 1
sol = save.GetSolutions()
if (sol[0] == nil) then
print("0 solutions found")
--save.SaveSolution("auto_"..rnd)
else
PrintVerbose((#sol+1) .. " solutions found")
for idx = 0, #sol do
soli = sol[idx]
if (string.find(soli.name,rnd) ~= nil) then
PrintVerbose(soli.name .. " : " .. soli.score)
if (soli.score > hi_score) then
hi_score = soli.score
hi_idx = idx
end
end
end
--print("Load the solution ...")
if(current.GetEnergyScore()+save_resolution < hi_score) then
save.LoadSolution(sol[hi_idx])
soli = sol[hi_idx]
print("Load solution: "..soli.name.." : "..rounding(soli.score,value_score_resolution))
end
end
end
function after_Solution(rnd,str_name)
if save_resolution > 999 then return end -- New BK 1/10/2019
PrintVerbose("-After-")
hi_score = 0
hi_idx = 1
sol = save.GetSolutions()
if (sol[0] == nil) then
print("0 solutions found")
str = "auto".."-"..rnd.."-"..rounding(current.GetEnergyScore(),value_score_resolution)
print("Save solution "..str)
save.SaveSolution(str)
else
PrintVerbose((#sol+1) .. " solutions found")
for idx = 0, #sol do
soli = sol[idx]
if (string.find(soli.name,rnd) ~= nil) then
PrintVerbose(soli.name .. " : " .. soli.score)
if (soli.score > hi_score) then
hi_score = soli.score
hi_idx = idx
end
end
end
PrintVerbose("Save the solution ...")
band.DeleteAll()
if(current.GetEnergyScore()-save_resolution > hi_score) then
str = str_name.."-"..rnd.."-"..rounding(current.GetEnergyScore(),value_score_resolution)
print("Save solution: "..str)
save.SaveSolution(str)
end
end
end
str_name= ui.GetTrackName() -- copy-paste anywhere on top of the recipe
rnd= "Temp" -- Warning: edit before in order to cach the right loop name
--How to use:
--Uncomment and insert the following 2 lines on the right places, before or after each loop:
--if ItsCheckTime() then before_Solution(rnd,str_name) end -- as often as possible after score evaluations, e.g. in savebest(), or before any action
--after_Solution(rnd,str_name) -- after any gain (will not save any loaded solution)
--Uncommend and copy-paste the following 2x2 lines within the dialog on the right places:
--ask.LabelRowBeforeSave = dialog.AddLabel("Keep save resolution to 1000 to avoid local saves") --(optional) copy-paste and adapt in the dialog
--ask.save_resolution = dialog.AddSlider("Save resolution:", 1000, 0.3, 1000, 1) --copy-paste and adapt in the dialog (parameters)-- 1000 means no sharing
--ask.save_Name = dialog.AddTextbox("Name2share", rnd)
--rnd=ask.save_Name.value
--save_resolution=ask.save_resolution.value -- copy-paste and adapt after the dialog.show
--if save_resolution < 1000 then print("Checking for solutioun every "..userTimeInterval.." seconds") end
--End of Isaksson Share
--START SET NOTES BY PD
--[[ Set note with author, recipe and big steps -- By Pauldunn
This subroutine is used in much group recipe versions in various groups
originally in Go Science
It traces the various recipes and players that improved the current solution
Module to quick copy-paste it in existing recipes, by Bruno Kestemont
]]--
--HOW TO include in a recipe?
--1a) COPY from here ================
function InitNotes(recipename)
local recipename=recipename or ""
note_number=structure.GetCount()
for seg=structure.GetCount(),1,-1 do
if structure.GetNote(seg)~="" then break end
note_number=seg
end
print(string.format("Recording "..recipename.." results in Note for segment %i",note_number))
starting_score=current.GetScore()
--structure.SetNote(note_number,string.format("(%s) %.3f + %s(%i) %.3f",user.GetPlayerName(),starting_score,recipename,loop_count,current.GetScore()))
end
function SetNote(note_number, starting_score, recipename, loop_count)
local recipename=recipename or ""
local starting_score=starting_score or 0
local loop_count=loop_count or 1
--print(string.format("Recording "..recipename.." results in Note for segment %i",note_number))
structure.SetNote(note_number,string.format("(%s) %.3f + %s(%i) %.3f",user.GetPlayerName(),starting_score,recipename,loop_count,current.GetScore()))
end
InitNotes(recipename) -- WARNING: and match "recipename" with the recipename of the recipe
--1b) COPY to here. somewhere after the options and inits of the recipe ================
--3a) Uncommend the 2 following lines and COPY from here ---------------
--loop_count=gen -- adapt "gen" to the name of loops used in the recipe
--SetNote(note_number, starting_score, recipename, loop_count) -- WARNING: and mach "recipename" and "loop_count" with the names of the recipe
--3b) COPY to here after main generations or loops of the recipe --------------
--END SET NOTES BY PD
function GetParams()
dlg = dialog.CreateDialog(recipename)
if PROBABLEFILTER then
dlg.GENERICFILTER = dialog.AddCheckbox ( "Filters off unless for score" , GENERICFILTER )
dlg.FilterWeight= dialog.AddSlider("Filter weight= ", FilterWeight, 1, 1000, 0)
end
if PROBABLELIGAND then
dlg.GENERICLIGAND = dialog.AddCheckbox ( "Move ligand (or all)" , GENERICLIGAND )
end
if SKETCHBOOK then
dlg.minGain= dialog.AddSlider("Min gain/it.", minGain, 0, 50, 1)
end
--dlg.LabelRowBeforeSave = dialog.AddLabel("Keep save resolution to 1000 to avoid local saves") --(optional) copy-paste and adapt in the dialog
dlg.save_resolution = dialog.AddSlider("Save after gain >", 1000, 0.3, 1000, 1) --copy-paste and adapt in the dialog (parameters)-- 1000 means no sharing
dlg.save_Name = dialog.AddTextbox("Name2share", rnd)
dlg.ok = dialog.AddButton("OK", 1)
dlg.cancel = dialog.AddButton("Cancel", 0)
if dialog.Show(dlg) > 0 then
save_resolution=dlg.save_resolution.value -- copy-paste and adapt after the dialog.show
rnd=dlg.save_Name.value
if PROBABLEFILTER then
GENERICFILTER=dlg.GENERICFILTER.value
FilterWeight=dlg.FilterWeight.value
end
if PROBABLELIGAND then
GENERICLIGAND=dlg.GENERICLIGAND.value
end
if SKETCHBOOK then
minGain= dlg.minGain.value
end
if GENERICLIGAND and not FirstLigand == 0 then --there should be a ligand but who knows
PROB_BAND_TO_LIGAND=1
end
if GENERICFILTER then -- WARNING: TO VERIFY !! (may be it's irreversible for several funtions bellow)
MutClass(structure, false)
MutClass(band, true) -- if false, you have to enable filter just afterwards (otherwise unideal filter bug)
MutClass(current, true)
MutClass(recentbest, true) -- otherwise, it remembers cut solutions
MutClass(save, true) -- better to save with full score
end
return true
else
print("Cancelled")
return false
end
end
function InitializePuzzleState( )
seedRandom( )
UnweightedInitialScore = getScore( )
--CurrentBestScore = InitialScore
InitialScore = FullScore(FilterWeight) -- new (used in saveBest)
CurrentBestScore = InitialScore -- used in saveBest
StartTime = os.time()
save.Quicksave( QS_Start )
save.Quicksave( QS_Best )
InitialClashImportance = behavior.GetClashImportance()
SCALE_MAXCI = InitialClashImportance
uMakeBands() -- new v1.2
FindAllMovableSegs( )
CheckForContactMap()
if PuzzleHasContactMap then InitializeContactMapSegList( CONTACTMAP_THRESHOLD ) end
end
function main() -- by TrutleByte 04-01-2020
local function repeatPattern(pattern, last)
if last == nil then last = false end
local gain = 0.0
local round = 0
local score
repeat
score = getScore()
round = round + 1
patternnb=table.concat(pattern, ",") -- fixed by BK for <printState
print("Starting round " .. round .. " of pattern " .. patternnb)
BandedWorm(pattern)
gain = getScore() - score
until gain < 0.01
PrintState()
if not last then InitializePuzzleState() end
end
if GetParams() then
UUnweightedInitialScore = getScore()
for i = 1, 1000 do
mainloop = i
InitializePuzzleState()
print("#####################")
print("Starting main loop " .. mainloop)
repeatPattern({2, 5, 11, 3, 13, 4, 7, 1, 6})
repeatPattern({14, 8, 6, 7, 13, 12, 2, 10, 11})
repeatPattern({5, 7, 1, 3, 9, 6, 2, 4, 8})
repeatPattern({3, 6, 12, 4, 14, 5, 8, 2, 7})
repeatPattern({3, 8, 4, 5, 12, 6, 10, 2, 7}, true)
end
End()
end
end
function Oldmain( ) -- to be deleted
if GetParams() then
UUnweightedInitialScore = getScore( )
for i= 1, 1000 do
mainloop=i
InitializePuzzleState( )
print("#####################")
print("Starting main loop "..mainloop)
local gain = 0.0
local round = 0
repeat
local score = getScore( )
round=round+1
pattern={ 2,5,11,3,13,4,7,1,6 }
patternnb="2,5,11,3,13,4,7,1,6"
print("Starting round "..round.." of pattern "..patternnb)
BandedWorm( pattern )
gain = getScore( ) - score
until gain < 0.01
PrintState( )
InitializePuzzleState( )
local gain = 0.0
local round = 0
repeat
local score = getScore( )
round=round+1
pattern={ 14,8,6,7,13,12,2,10,11 }
patternnb="14,8,6,7,13,12,2,10,11"
print("Starting round "..round.." of pattern "..patternnb)
BandedWorm( pattern )
gain = getScore( ) - score
until gain < 0.01
PrintState( )
InitializePuzzleState( )
local gain = 0.0
local round = 0
repeat
local score = getScore( )
round=round+1
pattern={ 5,7,1,3,9,6,2,4,8 }
patternnb="5,7,1,3,9,6,2,4,8"
print("Starting round "..round.." of pattern "..patternnb)
BandedWorm( pattern )
gain = getScore( ) - score
until gain < 0.01
PrintState( )
InitializePuzzleState( )
local gain = 0.0
local round = 0
repeat
local score = getScore( )
round=round+1
pattern={ 3,6,12,4,14,5,8,2,7 }
patternnb="3,6,12,4,14,5,8,2,7"
print("Starting round "..round.." of pattern "..patternnb)
BandedWorm( pattern )
gain = getScore( ) - score
until gain < 0.01
PrintState( )
InitializePuzzleState( )
local gain = 0.0
local round = 0
repeat
local score = getScore( )
round=round+1
pattern={ 3,8,4,5,12,6,10,2,7}
patternnb="3,8,4,5,12,6,10,2,7"
print("Starting round "..round.." of pattern "..patternnb)
BandedWorm( pattern )
gain = getScore( ) - score
until gain < 0.01
PrintState( )
end
End( )
end
end
xpcall( main, End )