Icon representing a recipe

Recipe: Banded Worm Pairs Inf Filt 1.4.8

created by LociOiling

Profile


Name
Banded Worm Pairs Inf Filt 1.4.8
ID
103387
Shared with
Public
Parent
Banded Worm Pairs Inf Filt 1.4.7
Children
Created on
April 29, 2020 at 21:52 PM UTC
Updated on
April 29, 2020 at 21:52 PM UTC
Description

Banded worm, with pairs of bands and more random action, including user bands - Filter optimization and infinite run. V1.4.6. improves filter detection. V1.4.7 fixes a bug and adds new controls. V1.4.8 has some performance changes.

Best for


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 -- ReVersion = "Banded Worm Pairs Inf Filter 1.4.8" 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 )

Comments


LociOiling Lv 1

Banded Worm Pairs Inf Filt 1.4.6 features better detection of filters, using the relatively new filter.GetNames function.

There are also a few updated messages, for example the run time is now expressed in both seconds and hours at the end.

There are several internal changes, such as detecting symmetry using the relatively new structure.GetSymCount function. (Actually banding to symmetric chains is still on the to-do list.)

The main routine has been restructured to take the length patterns from a table, and repeated calls to InitializePuzzleState have been eliminated. Most of the initialization has been moved inside of InitializePuzzleState to make sure it's protected by the xpcall.

Formula350 Lv 1

Ran for over 4hrs before it did error and cease to run.
Happened right after the 9th Pattern Gain report.

Here's a trimmed output log:

(I had to edit the XML stuff so it would display here properly)

[?xml version="1.0" encoding="UTF-8"?] [Foldit:Script xmlns:Foldit="http://fold.it/scriptlog"] [Foldit:Head] [Foldit:ScriptName]Banded Worm Pairs Inf Filt 1.4.6[/Foldit:ScriptName] [Foldit:ScriptDesc]Banded worm, with pairs of bands and more random action, including user bands - Filter optimization and infinite run. V1.4.6. improves filter detection.[/Foldit:ScriptDesc] [Foldit:MacroID]103387[/Foldit:MacroID] [Foldit:MacroRevisionID]204819[/Foldit:MacroRevisionID] [Foldit:ParentID]0[/Foldit:ParentID] [Foldit:ParentRevisionID]0[/Foldit:ParentRevisionID] [/Foldit:Head] [Foldit:ScriptOutput] Banded Worm Pairs Inf Filter 1.4.6 Random number seed = 2520305603 Starting BandedWormPairs of len 2, score: 16044.518 >>>> At 132, gain: 0.089, score: 16044.608 >>>> At 133, gain: 0.074, score: 16044.682 >>>> At 134, gain: 0.079, score: 16044.761 >>>> At 137, gain: 0.161, score: 16044.923 >>>> At 139, gain: 0.139, score: 16045.063 ---[SNIP]--- >>>> At 124, gain: 0.031, score: 16078.201 >>>> At 137, gain: 0.164, score: 16078.367 >>>> At 167, gain: 0.121, score: 16078.488 >>>> At 185, gain: 0.119, score: 16078.607 Pattern gain: 0.49322989442044 --- Banded Worm Pairs Inf Filter 1.4.6 error Unexpected error detected Error line: 420 Error: "attempt to perform arithmetic on local 'val' (a function value)" Start score: 16044.518 Final score: 16078.632 Total gain: 34.113 Run time: 17565 seconds, 4.879 hours

Formula350 Lv 1

That section of code (line 420) is identical to that section from BWP v1.4.5 which I've not experienced this issue with that one (though I've not ran v1.4.5 on puzzle 1825 specifically, but pretty sure I had on previous Corona-Binder puzzles).

This is line 420, for the record:
seed=os.time( )/math.abs( getScore( ) )

Further details of… maybe use….
I started the recipe at April 21st 2020 @ 3:35AM (local time) and its 'failure' occurred at 8:28AM (4.879hrs later, as the log states)

Potentially relevant I'm-Not-A-Coder observation(s)??

  • Comparing to v1.4.5, this v1.4.6 has a lot less code; 1051 lines vs 800.
  • Change log at start also seems to indicate this is instead based on v1.2? (granted, code comments can easily be deleted; wasn't able to track down v1.2 in order to properly compare.)
  • Initial startup option is set to "DEBUGRUN = TRUE", whereas v1.4.5 has it set to FALSE.

LociOiling Lv 1

As Formula350 noted, V 1.4.6 had a bug which killed the recipe after one pattern. This has been fixed.

V 1.4.7 also has some new features. A dialog will always appear in this version. The dialog has several new sliders to adjust settings in the recipe. There are sliders for "long" and "short" local wiggle, and a wiggle factor slider for regular wiggle. The initial values for the sliders should produce the same behavior as the previous versions of the recipe. Increasing these settings near the end of a puzzle may be helpful.

There are also several sliders the control the odds of making certain types of bands. Again, these sliders start with the longstanding default values, but make it easy to experiment with other settings.

There's another slight improvement in the banding logic. The previous version checked for contact map info whenever it added a band. The recipe actually determines whether there are contacts at the start, allowing the repeated checks to be skipped, potentially speeding things up slightly.

LociOiling Lv 1

Previous versions of the recipe had code which scanned the puzzle comments to determine puzzle properties.

The code looked for things like "dimer" and "trimer" to detect a symmetry puzzle. This used to work in some cases, but the puzzle designers can be inconsistent about descriptions. There are new functions which produce more consistent results. The current version uses the near functions.

I seem to recall there were similar checks related to filters and perhaps some other properties, but again they've been updated to use some more recent features.

I'll compare the versions again, but my main goal has been to preserve the basic behavior of the recipe. The existing filter and symmetry logic didn't work consistently, and this has been fixed. There was also some unneeded work being done, and this has been eliminated.

For the record, line 420 in V 1.4.6 was in the TrimNum function. The actual error was caused by line 386, which called TrimNum, but forgot to include the parentheses on current.GetEnergyScore, so TrimNum ended up with a function instead of a number as its argument.

LociOiling Lv 1

Banded Worm Pairs Inf Filt V 1.4.8 has some changes intended to improve performance.

The recipe now detects and works on multiple ranges of unlocked segments. Puzzle 1829 is an example of why this may be helpful. There are two separate ranges of unlocked segments in 1829. The previous versions of BWP IF started working on the first unlocked segment, and continued to the last unlocked segment. This means the recipe was spending time on the locked segments in between, which is unlikely to produce points.

The recipe also avoids repeated calls to determine whether a segment is locked (via structure.IsLocked). Instead, it checks the locked status of all segments at the start, and keeps the results in a table. This approach is usually faster.

Similarly, the recipe previously checked the amino acid type for each segment repeatedly (using structure.GetAminoAcid). All checking is again done at the start, then the saved results are used.

V 1.4.8 now contains Timo van der Laan's segment set, list, and type logic, used to determine the locked status. So the recipe is longer than it was before, although many of the new functions aren't used at this point.

Formula350 Lv 1

Had JUST started running v1.4.8 on 1831 Evo when… poof My client vanished (no error in log entry as a result)

CI was 0.50 on MWP

Recipe Settings (in order, top to bottom):
5
20
4

1.00
0.25
0.35
0.50
0.65


Here's the Recipe's output portion of the XML:
Banded Worm Pairs Inf Filter 1.4.8 Puzzle: 1831: Coronavirus Binder Design: Round 8 Track: Evo Random number seed = 1303781327 options: wiggle factor = 4 "big" local wiggle = 20 probability of 1st band = 1 probability 1st band is band in space (BiS) = 0.25 probability of 2nd band = 0.35 probability 2nd band is band in space (BiS) = 0.5 probability non-BiS band uses random non-default atom = 0.65 disable filters = true -- start pattern 1/5 Starting BandedWormPairs of len 2, score: 14860.505 --- Banded Worm Pairs Inf Filter 1.4.8 error Puzzle: 1831: Coronavirus Binder Design: Round 8 Track: Evo Unexpected error detected Error line: 730 Error: "bad argument #1 to '?' (band index out of bounds)" No change Run time: 130 seconds, 0.036 hours


EDIT: I forgot to mention, just in case this is relevant…
When I started the recipe I had forgotten to remove some of my bands. One of which, the recipe decided to use, which was set to Strength 1.5!

I immediately deleted that band AND my 4 other bands (that was the only one in use). It was after that Wiggle finished that it crashed. I imagine probably because it was trying to remove the band that it was using, that I already removed.

If so, I admit to it being a bit of a fringe case :P

Formula350 Lv 1

On version 1.4.7 that I'm now running, but was still noticed right before the previous crash…
It seems to want to use a QuickSave slot INSTEAD of the structure as it is when starting the recipe.

I started it at a score of 14860 with nothing frozen.
I had ran one of Susume's OG recipes overnight and in QS #3 had the top score but with all the frozen Core sidechains. Somehow Foldit made THAT the top score (14863), even though it shouldn't have been.

Either way, the bug being: It doesn't continue to use the solution that the recipe was started on.

LociOiling Lv 1

The band.Add function has trig-based parameters, and lots of them. It can cause errors like this one.

Diagnosis is tricky in this case, since the whole band class gets overridden in this recipe for filter control. So you get the question mark for the function name.

I'll look at a better solution for the next version.

(Edit: this one goes with bug the first, order of replies got messed up somehow.)