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 DISABLEFILTERS 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 1.4.5 fixed segMeanScore=(scoreboard.GetGroupScore()-8000)/segCnt
-- v 1.4.6 LociOiling - 20200409
-- use new functions to positively detect filters and symmetry
-- obsessive-compulsive reformatting, tweaking, and fiddling
-- put all patterns in a table, steamline the mainline
-- eliminate repeated calls to InitializePuzzleState
-- move initial dialogs
-- eliminate getScore and unused getRBScore, use current.GetEnergyScore
-- upgrade cleanup function
-- upgrade seedRandom function
-- v 1.4.7 LociOiling - 20200427
-- eliminate crash on TrimNum at end of pattern in v 1.4.6
-- combine filter and ligand dialogs into single dialog
-- add new settings to main dialog
-- rework filter and ligand logic slightly
-- don't keep checking for contact info in PutSingleRandomBandToSeg
-- v 1.4.8 LociOiling - 20200429
-- detect and avoid multiple ranges of locked segments
-- (added "SLT", Timo van der Laan's segment set and list module, for unlockeds)
-- avoid repeated calls to GetAminoAcid
-- v 1.4.9 LociOiling - 20210629
-- fix a ligand-related problem due to typo
--
ReVersion = "Banded Worm Pairs Inf Filter 1.4.9"
undo.SetUndo(false)
-- interesting global variables that users might want to modify
LILWIGGLE = 5 -- cycles for short Local Wiggle Selected
BIGWIGGLE = 20 -- cycles for longer Local Wiggle Selected
WF = 1 -- wiggle factor, for WiggleSelected
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_2NDBAND = 0.25
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
-- 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 ()
InitialClashImportance = behavior.GetClashImportance()
AcidList = {}
NonLockedSegSet = {}
NonLockedBool = {}
segCt = structure.GetCount()
--
-- new symmetry - 20200409
--
symChains = 0 -- set later
symTypes = { -- indexed by symChains + 1
"monomer", -- just for completeness
"dimer",
"trimer",
"tetramer",
"pentamer",
"hexamer",
"heptamer",
"octamer",
}
symTyp = "" -- actual symmetry type
symP = 1.0 -- probablity of banding to symmetric chain
segCnt2 = segCt
while structure.GetSecondaryStructure(segCnt2)=="M" do segCnt2=segCnt2-1 end
HASLIGAND = false
FirstLigand = segCnt2
LastLigand = segCt
if segCnt2 ~= segCt then
HASLIGAND = true
end
InitialBandCount = 0
ubcount = 0 -- user bands
ubandlist={} -- {band number, band strength, goal_length}
USERBANDS=false
HASFILTERS = false
FILTERNAMES = {}
DISABLEFILTERS = false
--
-- variables for BitSpawn overrides
--
classes_copied = 0
myclcp = {}
INCLUDELIGAND = false
SLT = { -- SLT--SLT--SLT--SLT--SLT--SLT--SLT--SLT--SLT--SLT--
--[[
SLT - Segment set, list, and type module v0.5
Includes the segment set and list module and the segment type module
developed by Timo van der Laan.
The following Foldit recipes contain the original code for these modules:
* Tvdl enhanced DRW 3.1.1 - https://fold.it/portal/recipe/102840
* TvdL DRemixW 3.1.2 - https://fold.it/portal/recipe/102398
The "set and list" module performs logical operations and
transformations on tables containing ranges of segment.
The segment type module find lists and sets of segments with various
properties, such as selected or frozen.
A "list" is one-dimensional table containing segment numbers.
A "set" is a two-dimensional table containing segment number ranges.
For example, given a list of segments:
list = { 1, 2, 3, 7, 8, 11, 13, 14, 15 }
the corresponding set is:
set = { { 1, 3 }, { 7, 8 }, { 11, 11 }, {13, 15 } }
Most functions assume that the sets are well-formed,
meaning they are ordered and have no overlaps.
As an example, the method FindUnlocked returns a set of
all the unlocked segments in a puzzle. The method can be
called as follows:
funlocked = SLT:FindUnlocked ()
The return value funlocked is a two-dimensional table
containing ranges of unlocked segments.
In source format, the table might look like this:
funlocked = {
{ 27, 35, },
{ 47, 62, },
{ 78, 89, },
}
The code to use this table would look like:
--
-- for each range of segments
--
for ii = 1, #funlocked do
--
-- for each segment in the range, so something
--
for jj = funlocked [ ii ] [ 1 ], funlocked [ ii ] [ 2 ] do
... something ...
end
end
This psuedo-module is a table containing a mix of
data fields and methods. This wiki article explains
the packaging technique:
https://foldit.fandom.com/wiki/Lua_packaging_for_Foldit
Authorship
----------
Original by Timo van der Laan:
02-05-2012 TvdL Free to use for non commercial purposes
French comments by Bruno Kestemont and perhaps others.
v0.1 - LociOiling
+ extract and reformat code
v0.2 - LociOiling - 2017/11/03
+ add primary FindUnlocked function
v0.3 - LociOiling
+ add FindRotamers function
v0.4 - LociOiling - 2019/10/29
+ package as table
+ remove dependencies on segCnt and segCnt2
v0.5 - LociOiling - 2019/12/17
+ convert functions to methods, update internal references
]]--
--
-- variables
--
segCnt = nil, -- segment count, not adjusted for ligands
segCnt2 = nil, -- segment count, not including terminal ligands
--
-- initializer - can be called externally, but invoked inline if segCnt or segCnt2 are nil
--
Init = function ( self )
self.segCnt = structure.GetCount ()
self.segCnt2 = self.segCnt
while structure.GetSecondaryStructure ( self.segCnt2 ) == "M" do
self.segCnt2 = self.segCnt2 - 1
end
end,
--
-- segment set and list functions
--
SegmentListToSet = function ( self, list ) -- retirer doublons
local result = {}
local ff = 0
local ll = -1
table.sort ( list )
for ii = 1, #list do
if list [ ii ] ~= ll + 1 and list [ ii ] ~= ll then
-- note: duplicates are removed
if ll > 0 then
result [ #result + 1 ] = { ff, ll }
end
ff = list [ ii ]
end
ll = list [ ii ]
end
if ll > 0 then
result [ #result + 1 ] = { ff, ll }
end
return result
end,
SegmentSetToList = function ( self, set ) -- faire une liste a partir d'une zone
local result = {}
for ii = 1, #set do
for kk = set [ ii ] [ 1 ], set [ ii ] [ 2 ] do
result [ #result + 1 ] = kk
end
end
return result
end,
SegmentCleanSet = function ( self, set )
-- Makes it well formed
return self:SegmentListToSet ( self:SegmentSetToList ( set ) )
end,
SegmentInvertSet = function ( self, set, maxseg )
--
-- Gives back all segments not in the set
-- maxseg is added for ligand
--
local result={}
if maxseg == nil then
maxseg = structure.GetCount ()
end
if #set == 0 then
return { { 1, maxseg } }
end
if set [ 1 ] [ 1 ] ~= 1 then
result [ 1 ] = { 1, set [ 1 ] [ 1 ] - 1 }
end
for ii = 2, #set do
result [ #result + 1 ] = { set [ ii - 1 ] [ 2 ] + 1, set [ ii ] [ 1 ] - 1, }
end
if set [ #set ] [ 2 ] ~= maxseg then
result [ #result + 1 ] = { set [ #set ] [ 2 ] + 1, maxseg }
end
return result
end,
SegmentInvertList = function ( self, list )
if self.segCnt2 == nil then
self:Init ()
end
table.sort ( list )
local result = {}
for ii = 1, #list - 1 do
for jj = list [ ii ] + 1, list [ ii + 1 ] - 1 do
result [ #result + 1 ] = jj
end
end
for jj = list [ #list ] + 1, self.segCnt2 do
result [ #result + 1 ] = jj
end
return result
end,
SegmentInList = function ( self, seg, list ) -- verifier si segment est dans la liste
table.sort ( list )
for ii = 1, #list do
if list [ ii ] == seg then
return true
elseif list [ ii ] > seg then
return false
end
end
return false
end,
SegmentInSet = function ( self, set, seg ) --verifie si segment est dans la zone
for ii = 1, #set do
if seg >= set [ ii ] [ 1 ] and seg <= set [ ii ] [ 2 ] then
return true
elseif seg < set [ ii ] [ 1 ] then
return false
end
end
return false
end,
SegmentJoinList = function ( self, list1, list2 ) -- fusionner 2 listes de segments
local result = list1
if result == nil then
return list2
end
for ii = 1, #list2 do
result [ #result + 1 ] = list2 [ ii ]
end
table.sort ( result )
return result
end,
SegmentJoinSet = function ( self, set1, set2 ) --fusionner (ajouter) 2 zones
return self:SegmentListToSet ( self:SegmentJoinList ( self:SegmentSetToList ( set1 ), self:SegmentSetToList ( set2 ) ) )
end,
SegmentCommList = function ( self, list1, list2 ) -- chercher intersection de 2 listes
local result = {}
table.sort ( list1 )
table.sort ( list2 )
if #list2 == 0 then
return result
end
local jj = 1
for ii = 1, #list1 do
while list2 [ jj ] < list1 [ ii ] do
jj = jj + 1
if jj > #list2 then
return result
end
end
if list1 [ ii ] == list2 [ jj ] then
result [ #result + 1 ] = list1 [ ii ]
end
end
return result
end,
SegmentCommSet = function ( self, set1, set2 ) -- intersection de 2 zones
return self:SegmentListToSet ( self:SegmentCommList ( self:SegmentSetToList ( set1 ), self:SegmentSetToList ( set2 ) ) )
end,
SegmentSetMinus = function ( self, set1, set2 )
return self:SegmentCommSet ( set1, self:SegmentInvertSet ( set2 ) )
end,
SegmentPrintSet = function ( self, set )
print ( self:SegmentSetToString ( set ) )
end,
SegmentSetToString = function ( self, set ) -- pour pouvoir imprimer
local line = ""
for ii = 1, #set do
if ii ~= 1 then
line = line .. ", "
end
line = line .. set [ ii ] [ 1 ] .. "-" .. set [ ii ] [ 2 ]
end
return line
end,
SegmentSetInSet = function ( self, set, sub )
if sub == nil then
return true
end
--
-- Checks if sub is a proper subset of set
--
for ii = 1, #sub do
if not self:SegmentRangeInSet ( set, sub [ ii ] ) then
return false
end
end
return true
end,
SegmentRangeInSet = function ( self, set, range ) -- verifier si zone est dans suite
if range == nil or #range == 0 then
return true
end
local bb = range [ 1 ]
local ee = range [ 2 ]
for ii = 1, #set do
if bb >= set [ ii ] [ 1 ] and bb <= set [ ii ] [ 2 ] then
return ( ee <= set [ ii ] [ 2 ] )
elseif ee <= set [ ii ] [ 1 ] then
return false
end
end
return false
end,
SegmentSetToBool = function ( self, set ) --vrai ou faux pour chaque segment utilisable ou non
local result = {}
for ii = 1, structure.GetCount () do
result [ ii ] = self:SegmentInSet ( set, ii )
end
return result
end,
--
-- End of Segment Set module
--
--
-- Module Find Segment Types
--
FindMutablesList = function ( self )
if self.segCnt2 == nil then
self:Init ()
end
local result = {}
for ii = 1, self.segCnt2 do
if structure.IsMutable ( ii ) then
result [ #result + 1 ] = ii
end
end
return result
end,
FindMutables = function ( self )
return self:SegmentListToSet ( self:FindMutablesList () )
end,
FindFrozenList = function ( self )
if self.segCnt2 == nil then
self:Init ()
end
local result = {}
for ii = 1, self.segCnt2 do
if freeze.IsFrozen ( ii ) then
result [ #result + 1 ] = ii
end
end
return result
end,
FindFrozen = function ( self )
return self:SegmentListToSet ( self:FindFrozenList () )
end,
FindLockedList = function ( self )
if self.segCnt2 == nil then
self:Init ()
end
local result = {}
for ii = 1, self.segCnt2 do
if structure.IsLocked ( ii ) then
result [ #result + 1 ] = ii
end
end
return result
end,
FindLocked = function ( self )
return self:SegmentListToSet ( self:FindLockedList () )
end,
FindUnlockedList = function ( self )
if self.segCnt2 == nil then
self:Init ()
end
local result = {}
for ii = 1, self.segCnt2 do
if not structure.IsLocked ( ii ) then
result [ #result + 1 ] = ii
end
end
return result
end,
FindUnlocked = function ( self )
return self:SegmentListToSet ( self:FindUnlockedList () )
end,
FindZeroScoreList = function ( self )
if self.segCnt == nil then
self:Init ()
end
local result = {}
for ii = 1, self.segCnt do
local sub = 0
for jj = 1, #SubScores do
sub = sub + current.GetSegmentEnergySubscore ( ii, SubScores [ jj ] [ 1 ] )
end
if sub == 0 then
result [ #result + 1 ] = ii
end
end
return result
end,
FindZeroScore = function ( self )
return self:SegmentListToSet ( self:FindZeroScoreList () )
end,
FindRotamersList = function ( self )
if self.segCnt == nil then
self:Init ()
end
local result = {}
for ii = 1, self.segCnt do
local rots = rotamer.GetCount ( ii )
if rots > 1 then
result [ #result + 1 ] = ii
end
end
return result
end,
FindRotamers = function ( self )
return self:SegmentListToSet ( self:FindRotamersList () )
end,
FindSelectedList = function ( self )
if self.segCnt == nil then
self:Init ()
end
local result = {}
for ii = 1, self.segCnt do
if selection.IsSelected ( ii ) then
result [ #result + 1 ] = ii
end
end
return result
end,
FindSelected = function ( self )
return self:SegmentListToSet ( self:FindSelectedList () )
end,
FindAAtypeList = function ( self, aa )
if self.segCnt2 == nil then
self:Init ()
end
local result = {}
for ii = 1, self.segCnt2 do
if structure.GetSecondaryStructure ( ii ) == aa then
result [ #result + 1 ] = ii
end
end
return result
end,
FindAAtype = function ( self, aa )
return self:SegmentListToSet ( self:FindAAtypeList ( aa ) )
end,
FindAminotype = function ( self, at ) --NOTE: only this one gives a list not a set
if self.segCnt2 == nil then
self:Init ()
end
local result={}
for ii = 1, self.segCnt2 do
if structure.GetAminoAcid ( ii ) == at then
result [ #result + 1 ] = ii
end
end
return result
end,
}-- SLT--SLT--SLT--SLT--SLT--SLT--SLT--SLT--SLT--SLT--
--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; LociOiling 20200405
--
-- check for symmetry puzzle
--
if structure.GetSymCount ~= nil then -- temporary workaround until functions released to main
symChains = structure.GetSymCount () -- set global symChains
if symChains > 0 then
local sc = symChains + 1
local sw = ""
sw = sc .. "-way"
if sc <= #symTypes then
symTyp = symTypes [ sc ] .. " (" .. sw .. ")"
else
symTyp = sw
end
print ( symTyp .. " symmetry puzzle" )
--
-- adjust default odds of banding to a symmetric chain
--
symP = symP - ( 1 / sc ) -- all we are saying is give Ps a chance
end
end
--
-- determine if filters apply
--
FILTERNAMES = filter.GetNames ()
if #FILTERNAMES > 0 then
HASFILTERS = true
DISABLEFILTERS = true
end
return
end
--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: DISABLEFILTERS must be defined elsewhere (otherwise, it will not do anything)
-- DISABLEFILTERS=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
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
--STOP Generic Filter Management
function BandedWorm ( pattern )
--recentbest.Save( )
FakeRecentBestSave ()
SetCI ( 1.0 )
SaveBest ()
local ss = current.GetEnergyScore ()
local idx = 1
for ww = 1, #pattern do
save.Quickload( QS_Best ) -- new for DEBUG
len = pattern [ ww ]
local sw = current.GetEnergyScore ()
local swCurr = sw
print( "Starting BandedWormPairs of len " .. len .. ", score: ".. TrimNum( sw ) )
for uu = 1, #NonLockedSegSet do
for ss = NonLockedSegSet [ uu ] [ 1 ], NonLockedSegSet [ uu ] [ 2 ] - len + 1 do
selection.DeselectAll ()
selection.SelectRange ( ss, ss + len - 1 )
if random () < PROB_PUTBAND then
if HASLIGAND and random () < PROB_BAND_TO_LIGAND then
idx = random ( FirstLigand, LastLigand )
else
idx = random ( ss, ss + len - 1 )
end
PutSingleRandomBandToSeg( idx, PROB_BIS_NOT_BETWEEN )
if random () < PROB_2NDBAND then
local idx2 = random ( ss, ss + len - 1 )
PutSingleRandomBandToSeg ( idx2, PROB_2ND_IS_BIS )
end
uBandEnable () -- new v1.2 random adding one of the user bands
structure.LocalWiggleSelected ( random ( 2, 4 ) )
ManageBands ()
structure.WiggleAll ( 2 * WF )
end
structure.LocalWiggleSelected ( LILWIGGLE )
local swNew = current.GetEnergyScore ()
local gain = swNew - swCurr
if gain > 0 then
structure.LocalWiggleSelected ( BIGWIGGLE )
--recentbest.Restore ()
FakeRecentBestRestore ()
ManageBands ()
swNew = current.GetEnergyScore ()
gain = swNew - swCurr
if TrimNum ( gain ) > 0 then
print( ">>>> At " .. ss .. ", gain: ".. TrimNum ( gain )..", score: ".. TrimNum ( swNew ) )
end
SaveBest ()
swCurr = swNew
else
--recentbest.Restore ()
FakeRecentBestRestore ()
structure.LocalWiggleSelected ( 4 )
end
--recentbest.Restore ()
FakeRecentBestRestore ()
ManageBands ()
end
end
print( "Pattern gain: ".. current.GetEnergyScore () - sw )
SaveBest ()
end
selection.DeselectAll()
print ( "Total BandedWormPairs gain: " .. TrimNum ( current.GetEnergyScore () ) - ss )
end
function PutSingleRandomBandToSeg ( idx, probBis )
changeSucceeded = false
local strength = random( 0.5 * BAND_STRENGTH_DEFAULT,
1.5 * BAND_STRENGTH_DEFAULT,
true )
local doBIS = random( ) < probBis
if doBIS then
changeSucceeded = PutBandInSpace( idx, BIS_LENGTH, strength )
else
if PuzzleHasContactMap
and 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
----------------------------------------------------------------
-- BASIC FUNCTIONALITY
----------------------------------------------------------------
function DebugPrint( str )
if DEBUGRUN then print( str ) end
end
function TrimNum ( val )
return val - val % 0.001
end
function SaveBest( )
local score = current.GetEnergyScore ( )
if score > CurrentBestScore then
save.Quicksave( QS_Best )
CurrentBestScore = score
end
-- CheckFullScore() -- for DEBUG only
end
--START Debugging Recentbest Foldit Bug Temporary solution of Foldit bug (BK 29/8/2017)
function Score() -- for BWPIF only
return current.GetScore()
end
function FakeRecentBestSave()
if HASFILTERS then -- trying to solve the Foldit bug
save.Quicksave(Qs_Recent)
else
recentbest.Save()
end
end
function FakeRecentBestRestore()
if HASFILTERS then -- trying to solve the Foldit bug
local ss=Score()
recentbest.Restore() -- filter disabled (bug)
local se=Score() -- 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 HASFILTERS then -- trying to solve the Foldit bug
save.Quicksave(Qs_Current)
recentbest.Restore() -- filter disabled (bug)
local se=Score() -- now with the filter
save.Quickload(Qs_Current)
return se
else
recentbest.GetScore( )
end
end
function FakeRecentBestGetEnergyScore()
if HASFILTERS 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 CheckFullScore() -- check without filter (only for DEBUG)
if filter.AreAllEnabled()==false then
DebugPrint ("DEBUG: filter is off, turning on")
FiltersOn()
end
end
]]--
function SetCI( ci )
behavior.SetClashImportance( SCALE_MAXCI * ci )
end
------------- CONTACTMAP FUNCTIONS ------------
function CheckForContactMap( )
PuzzleHasContactMap = false
local saveval = 0.0
for i=1, segCt-1 do
for j=i+1, segCt do
val = contactmap.GetHeat( i, j )
if saveval ~= 0.0 and val ~= saveval then
PuzzleHasContactMap = true
ContactMapScore = GetContactScore( )
return -- all we wanted to know was whether scores exist
end
if saveval == 0.0 then saveval = val end
end
end
return
end
function SegHasContactData( segIn, heatThreshold )
for i = 1, segCt do
if i < segIn - 1 or i > segIn + 1 then
if contactmap.GetHeat( segIn, i ) >= heatThreshold then return true end
end
end
return false
end
function InitializeContactMapSegList( heatThreshold )
HasContactMapSegList = {}
for i = 1, segCt do
if SegHasContactData( i, heatThreshold ) then
HasContactMapSegList[ #HasContactMapSegList + 1 ] = i
end
end
end
function GetContactScore( )
if not PuzzleHasContactMap then return 0 end
local sum = 0.0
local segCt = structure.GetCount( )
for i = 1,segCt-1 do
for j = i + 1, segCt do
if contactmap.IsContact( i, j ) then sum = sum + contactmap.GetHeat( i, j ) end
end
end
return sum
end
----------------------- MATHY STUFF -----------------------
--
-- 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 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( AcidList [ 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
----------------------- 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 uBandEnable() -- 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 )
if not ( NonLockedBool [ seg1 ] or NonLockedBool [ seg2 ] ) then return false end
if atom1 == nil then atom1 = CENTER_CARBON end
if atom2 == nil then atom2 = CENTER_CARBON end
local bIdx = band.AddBetweenSegments( seg1, seg2, atom1, atom2 )
if bIdx ~= band.GetCount( ) then
DebugPrint( "failed to add band from "..seg1.." to "..seg2)
return false
end
if bIdx <= InitialBandCount then return true end -- don't change user-supplied bands
if goalLength ~= nil then
band.SetGoalLength( bIdx, goalLength )
end
if strength ~= nil and strength > 0.0 then
band.SetStrength( bIdx, strength )
end
return true
end
function BandInSpaceWithParameters( seg, segFini, segInit, rho, theta, phi, strength, goalLength, atomIndexOrigin, atomIndexXAxis, atomIndexYAxis)
if not ( NonLockedBool [ 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 )
needNew = true
local failedTries = 0
while ( needNew and failedTries < 30 ) do
idx2 = randomSeg( ) -- ok if this one isn't movable (we assume idx is movable)
if idx2 > idx + minGap or idx2 < idx - minGap then
needNew = false
break
else
failedTries = failedTries + 1
end
end
if not needNew then
return BandBetweenSegsWithParameters( idx, idx2, strength, nil, atom, nil )
end
return false
end
function PutSomeContactMapBands( heatThreshold, strength, ctBands, doSidechains )
changeSucceeded = false
local hotList = {}
for i = 1, segCt-2 do
for j = i+2, segCt do
local heat = contactmap.GetHeat(i, j)
if heat >= heatThreshold and not contactmap.IsContact( i , j ) then
hotList[ #hotList + 1] = { i, j, heat }
end
end
end
randomizeIndexList( hotList )
for i=1, math.min( ctBands, #hotList ) do
local atom1 = nil
local atom2 = nil
if BAND_TO_SIDECHAINS and doSidechains then
atom1 = PickAtomNumberForBand( hotList[i][1] )
atom2 = PickAtomNumberForBand( hotList[i][2] )
end
local ch = BandBetweenSegsWithParameters( hotList[i][1], hotList[i][2], strength, nil, doTip1, doTip2 )
changeSucceeded = ch or changeSucceeded
end
return changeSucceeded
end
--------------------------------------------------------------------------
-- SETUP and MAIN
--------------------------------------------------------------------------
function PrintState( )
local gain = current.GetEnergyScore () - InitialScore
if gain < 0.001 then
print( "No change" )
else
print( "Start score: " .. TrimNum ( InitialScore ))
print( "Final score: " .. TrimNum ( current.GetEnergyScore () ) )
print( "Total gain: " .. TrimNum ( gain ) )
end
local rtime = os.time() - StartTime
print( "Run time: ".. rtime .. " seconds, " .. TrimNum ( rtime / 3600 ) .. " hours" )
end
function InitializePuzzleState ()
seedRandom ()
for ii = 1, segCnt2 do
AcidList [ #AcidList + 1 ] = structure.GetAminoAcid ( ii )
end
detectfilterandmut()
if HASLIGAND then
PROB_BAND_TO_LIGAND = 1
end
InitialScore = current.GetEnergyScore ()
CurrentBestScore = InitialScore
StartTime = os.time()
save.Quicksave ( QS_Start )
save.Quicksave ( QS_Best )
InitialClashImportance = behavior.GetClashImportance ()
SCALE_MAXCI = InitialClashImportance
uMakeBands () -- new v1.2
NonLockedSegSet = SLT:FindUnlocked ()
NonLockedBool = SLT:SegmentSetToBool ( NonLockedSegSet )
CheckForContactMap ()
if PuzzleHasContactMap then
InitializeContactMapSegList ( CONTACTMAP_THRESHOLD )
end
end
function GetParam ()
local askresult = 0
local dlg = dialog.CreateDialog ( ReVersion )
dlg.LILWIGGLE = dialog.AddSlider ( "Short local wiggle", LILWIGGLE, 1, 10, 0 )
dlg.BIGWIGGLE = dialog.AddSlider ( "Long local wiggle", BIGWIGGLE, 15, 50, 0 )
dlg.WF = dialog.AddSlider ( "Wiggle factor", WF, 1, 5, 0 )
dlg.PROBL1 = dialog.AddLabel ( "----probability sliders----" )
dlg.PROBL2 = dialog.AddLabel ( "0 = no chance, 1 = sure thing" )
dlg.PROBL3 = dialog.AddLabel ( "\"BiS\" = band in space, one end not on a segment" )
dlg.PROB_PUTBAND = dialog.AddSlider ( "1st band", PROB_PUTBAND, 0, 1, 2 )
dlg.PROB_BIS_NOT_BETWEEN = dialog.AddSlider ( "1st BiS", PROB_BIS_NOT_BETWEEN, 0, 1, 2 )
dlg.PROB_2NDBAND = dialog.AddSlider ( "2nd band", PROB_2NDBAND, 0, 1, 2 )
dlg.PROB_2ND_IS_BIS = dialog.AddSlider ( "2nd BiS", PROB_2ND_IS_BIS, 0, 1, 2 )
dlg.PROB_BETWEEN_USES_ATOM = dialog.AddSlider ( "random atom", PROB_BETWEEN_USES_ATOM, 0, 1, 2 )
if PuzzleHasContactMap then
dlg.PROB_CHOOSE_CONTACTMAP = dialog.AddSlider ( "use contact", PROB_CHOOSE_CONTACTMAP , 0, 1, 2 )
end
dlg.PROBL9 = dialog.AddLabel ( "--end of probability sliders--" )
if HASFILTERS then
dlg.l0 = dialog.AddLabel ( "" )
dlg.l2 = dialog.AddLabel ( #FILTERNAMES .. " Filters for this puzzle" )
dlg.DISABLEFILTERS = dialog.AddCheckbox ( "Disable filters?", DISABLEFILTERS )
end
if HASLIGAND then
dlg.INCLUDELIGAND = dialog.AddCheckbox ( "Include ligand(s)?", INCLUDELIGAND )
end
dlg.ok = dialog.AddButton ( "OK", 1 )
dlg.cancel = dialog.AddButton ( "Cancel", 0 )
repeat
askresult = dialog.Show ( dlg )
if askresult > 0 then
LILWIGGLE = dlg.LILWIGGLE.value
BIGWIGGLE = dlg.BIGWIGGLE.value
WF = dlg.WF.value
PROB_PUTBAND = dlg.PROB_PUTBAND.value
PROB_BIS_NOT_BETWEEN = dlg.PROB_BIS_NOT_BETWEEN.value
PROB_2NDBAND = dlg.PROB_2NDBAND.value
PROB_2ND_IS_BIS = dlg.PROB_2ND_IS_BIS.value
PROB_BETWEEN_USES_ATOM = dlg.PROB_BETWEEN_USES_ATOM.value
if PuzzleHasContactMap then
PROB_CHOOSE_CONTACTMAP = dlg.PROB_CHOOSE_CONTACTMAP.value
end
if HASFILTERS then
DISABLEFILTERS = dlg.DISABLEFILTERS.value
end
if HASLIGAND then
INCLUDELIGAND = dlg.INCLUDELIGAND.value
end
end
until askresult < 2
return askresult > 0
end
function main ()
local patternz = {
{ 2, 5, 11, 3, 13, 4, 7, 1, 6, }, -- 1
{ 14, 8, 6, 7, 13, 12, 2, 10, 11, }, -- 2
{ 5, 7, 1, 3, 9, 6, 2, 4, 8, }, -- 3
{ 3, 6, 12, 4, 14, 5, 8, 2, 7, }, -- 4
{ 3, 8, 4, 5, 12, 6, 10, 2, 7, }, -- 5
}
print ( ReVersion )
print ( "Puzzle: " .. puzzle.GetName () )
print ( "Track: " .. ui.GetTrackName () )
InitializePuzzleState ()
if GetParam () then
-- how to use:
-- setting default options if filters BK 4/2/2015
-- MutClass(structure, false)
-- MutClass(band, false)
-- MutClass(current, true)
if DISABLEFILTERS then -- WARNING: TO VERIFY !! (maybe it's irreversible for several functions below)
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
print ( "options:" )
print ( "wiggle factor = " .. WF )
print ( "\"big\" local wiggle = " .. BIGWIGGLE )
print ( "probability of 1st band = " .. PROB_PUTBAND )
print ( "probability 1st band is band in space (BiS) = " .. PROB_BIS_NOT_BETWEEN )
print ( "probability of 2nd band = " .. PROB_2NDBAND )
print ( "probability 2nd band is band in space (BiS) = " .. PROB_2ND_IS_BIS )
print ( "probability non-BiS band uses random non-default atom = " .. PROB_BETWEEN_USES_ATOM )
if PuzzleHasContactMap then
print ( "probability of using contact map for non-BiS band = " .. PROB_CHOOSE_CONTACTMAP )
end
if HASFILTERS then
print ( "disable filters = " .. tostring ( DISABLEFILTERS ) )
end
if HASLIGAND then
print ( "include ligand(s) = " .. tostring ( INCLUDELIGAND ) )
end
print ( "--" )
for ii = 1, 1000 do
for jj = 1, #patternz do
print ( "start pattern " .. jj .. "/" .. #patternz )
local gain = 0.0
repeat
local score = current.GetEnergyScore ()
BandedWorm ( patternz [ jj ] )
gain = current.GetEnergyScore () - score
until gain < 0.01
print ( "end pattern " .. jj .. "/" .. #patternz )
end
end
end
cleanup ()
end
function cleanup ( errmsg )
if CLEANUPENTRY ~= nil then
return
end
CLEANUPENTRY = true
print ( "---" )
local reason
local start, stop, line, msg
if errmsg == nil then
reason = "complete"
else
--
-- civilized error reporting,
-- thanks to Bruno K. and Jean-Bob
--
start, stop, line, msg = errmsg:find ( ":(%d+):%s()" )
if msg ~= nil then
errmsg = errmsg:sub ( msg, #errmsg )
end
if errmsg:find ( "Cancelled" ) ~= nil then
reason = "cancelled"
else
reason = "error"
end
end
print ( ReVersion .. " " .. reason )
print ( "Puzzle: " .. puzzle.GetName () )
print ( "Track: " .. ui.GetTrackName () )
if reason == "error" then
print ( "Unexpected error detected" )
print ( "Error line: " .. line )
print ( "Error: \"" .. errmsg .. "\"" )
end
save.Quickload ( QS_Best )
PrintState ()
SetCI ( 1.0 )
selection.DeselectAll ()
ManageBands ()
end
xpcall( main, cleanup )