Code
--[[
Bands! Bands in space!
w30 (NC): Default wiggle iterations upped to 30 for newchapter; wiggle with bands=2 iters
No more recursive wiggle, since this scores lower in newchapter.
Restructured dialogs to match "standard" EDRW logic.
GAB - Genetic Algorithm on Bands (evolving digimon to play Foldit)
by Rav3n_pl
based on CartoonVillan and Crashguard303 scripts
Lua V2
Definitions:
band: random band to space from random segment
critter: set of bands
herd: set of critters
1.
- randomize needed bands
- randomly assignig them to criters
2.
- score each critter
3.
- keep critters scoring above score
- breed best critters
- breed bastards (best one + random one)
- forget about rest of critter
- randomize new critters to fill herd
]]--
-- options:
energy=false --set true to seek energy in exploration puzzles; false works on all puzzles
pullCI=0.9 --Clash Impotrance during pull
shakeCI=0.21 --CI when shake on qstab or mutate once
maxCI=1 --maximum CI used by script
fastQstab=true -- only 1s1w after pull if true
fuzeThresh = 1 -- run fuze if we are close to best score (negative=new best score)
qstabThresh=1 -- run qstab if score drops more than... wiggle only in other case
onlyMutable=true --if true use ONLY all mutable aas (amino acids), no matter of always use
mutateOnce=true --if true use mutate(1) instead of shake in qstab
mutateAlways=false --if true use muatate(1) instead of all shakes
mutateNever=false --if true do not mutate (overrides other mutate options)
shouldReset=false -- reset puzzle at start
showBands=false --hide band values (like in shared screenshots) to follow foldit rules between people
herd= --herd options
{ --jon changed values
breedBest = 2, --5, --breed best 4 critters - all combinations => 6 kids form 4 critters, 3 form 3, 1 form 2, 9 form 5 ;]
keepBest = 2, --3, --save up to 3 best scoring critters, rest are forgotten
breedBastards = 1,--2, --8, --number of best will have one random bastard
--less random agents to focus on evolution --jon
--but setting bastards and newRandom to 0 seems to dwindle population at 10 gen
--really the population declines even with them
newRandom = 1,--1, --10, --adding new random ones each generation
maxGen= 1000, --maximum generations
--mine many bitcoin to stay warm during covid19
shuffle = true, --set true to run critters in random order --jon: i guess that's kinda fair
renew=20,--4, --create totally fresh herd after that many gens w/o improvement
fairness=true,
}
critter= --critter options
{
minBands=3, --minimum bands
maxBands=7, --maximum bands
keepScore = 0 , --survive to next gen only if score higher than
breedScore=-20, --will breed kids only if score higher. Bastards always breed
maxLoss=20 --30, --maximum loss by critter. set 0 to disable -- reduced to 20 for newchapter
}
bands= --bands options
{
minStr=0.3, --minimum band str
maxStr=1.1, --maximum band str
maxUp = 12.1, -- maximum band length
}
DoNotUse={--just comment lines below or add more areas to avoid
--{segCnt,segCnt}, --ligand cant be used
--{120,134},
--{1,10},
}
AlwaysUse={ --areas should be always used
--{segCnt,segCnt},--ligand need to be at one end
--{308,311}, --loopy
--{272,319}, --loopy
}
UseSegments= --use ONLY this segments
{
--2,3,4,5
}
-- bands by secondary structure
use=
{
Sheet=true, --set false to not band sheets
Helix=true, --set false to not band helices
Loop=true, --set false to not band loops
}
--end of options
segCnt=structure.GetCount()
p=print
function CI(c)
if c>maxCI then c=maxCI end
behavior.SetClashImportance(c)
end
function round(x)--cut all afer 3-rd place
return x-x%0.001
end
function down(x)
return x-x%1
end
function Score()--return score, exploration too
if energy==true then
return current.GetEnergyScore()
else
return current.GetScore()
end
end
--local seed=recipe.GetRandomSeed() --NOT WORKING!!!
--calculate REALLY good seed
seed=os.time()/math.abs(Score())
seed=seed%0.001
seed=1/seed
while seed<10000000 do seed=seed*1000 end
seed=seed-seed%1
p("Seed is: "..seed)
math.randomseed(seed)
--REALLY good seed made by rav3n_pl :P
function Wiggle(how, iters, minppi)
if how==nil then how="wa" end
if iters==nil then iters=1 end --increased to 15 to have 30 iters by default in newchapter --Jon decreased to 1 for fun
if minppi==nil then minppi=0.1 end
if iters>0 then
iters=iters-1
sp=Score()
if how == "s" then
if mutateAlways==true and mutateNever==false then --Jon copied modif. from Orange Guard recipe
-- mutate all for a certain num of iterations
structure.MutateSidechainsSelected(1)
end
structure.ShakeSidechainsSelected(1) --Jon made this line obstinate
elseif how == "wb" then structure.WiggleAll(2, true,false)
elseif how == "ws" then structure.WiggleAll(2, false,true)
elseif how == "wa" then structure.WiggleAll(2, true,true)
end
ep = Score()
ig=ep-sp
--if how~="s" then
-- if ig > minppi then return Wiggle(how, iters, minppi) end --tail call
--end -- recursive wiggle removed as it is not good under newchapter
end
end
function SaveBest()
local g=Score()-bestScore
if g>0 then
if g>0.01 then p("Gained another "..round(g).." pts.") end
bestScore=Score()
save.Quicksave(3)
end
end
function SaveRB() --RB recent best
if recentbest.GetScore()>bestScore or (recentbest.GetEnergyScore()>bestScore and energy==true)then
save.Quicksave(4)
--recentbest.Restore() --jon disabled because it seems to erase quicksave
SaveBest()
save.Quickload(4)
end
end
function Qstab()
selection.SelectAll()
CI(shakeCI)
if mutateOnce==true and mutateNever==false then --from orange guard recipe
structure.MutateSidechainsSelected(1)
else
Wiggle("s",1)
end
if fastQstab==false then
CI(0.4)
Wiggle("wa",1)
CI(1)
Wiggle("s",1)
end
CI(1)
Wiggle()
end
function FuzeEnd()
CI(1)
Wiggle("wa",1)
Wiggle("s",1)
Wiggle()
srb()
end
function Fuze1(ci1,ci2)
CI(ci1)
Wiggle("s",1)
CI(ci2)
Wiggle("wa",1)
end
function Fuze2(ci1,ci2)
CI(ci1)
Wiggle("wa",1)
CI(1)
Wiggle("wa",1)
CI(ci2)
Wiggle("wa",1)
end
function srb() --jon: save recent best?
recentbest.Restore()
SaveBest()
end
function Fuze()
p("Fuzing...")
local scr=Score()
selection.SelectAll()
recentbest.Save()
Fuze1(0.3,0.6) FuzeEnd()
Fuze2(0.3,1) srb()
Fuze1(0.05,1) srb()
Fuze2(0.7,0.5) FuzeEnd()
Fuze1(0.07,1)
srb()
end
function random(n1,n2) --random function returns int or float depends on input vars
if n1==nil then return math.random()
else
if n2==nil then
if n1%1==0 then
return math.random(n1) --integer
else
return math.random()*n1 --float
end
else
if n1%1==0 and n2%1==0 then
return math.random(n1,n2) --integer between
else
return math.random()*(n2-n1)+n1 --float between
end
end
end
end
function FillHerd() --fill up herd
local n=#critters
if n>0 then --fill up
n=herd.newRandom
else --fresh herd
n=herd.breedBest + herd.keepBest + herd.breedBastards
end
p("Randomizing "..n.." new critters...")
for i=1,n do
AddCritter()
end
end
function AddCritter() --vreate new random critter
local c={}
critterID=critterID+1
c.no=critterID
c.name=c.no..'-rnd'
c.bands={}
local r=random(critter.minBands, critter.maxBands)
for i=1,r do
c.bands[#c.bands+1]=AddBand()
end
critters[#critters+1]=c
p(c.name.." bands: "..#c.bands)
end
function AddBand() --create one random band
local cnt=0
local b={}
while true do --try till die
cnt=cnt+1
local s1=random(segCnt)
if onlyMutable==true or #UseSegments>0 then
s1=UseSegments[random(#UseSegments)]
end
if #UseSegments>0 or CanBeUsed(s1) then
local str=random(bands.minStr,bands.maxStr)
local len=random(bands.maxUp)
local theta = math.acos(math.random())
local phi = 2 * math.pi * math.random()
local segmentXAxis=0
local segmentYAxis=0
while true do --all 3 must be different
segmentXAxis = random(segCnt)
segmentYAxis = random(segCnt)
if segmentXAxis~=s1 and segmentYAxis~=s1 and segmentXAxis~=segmentYAxis then break end
end
--{segmentOrigin, segmentXAxis, segmentYAxis, rho, theta, phi}
b={s1, segmentXAxis, segmentYAxis, len, theta, phi, str}
break
end
if cnt>100 then
p("Sorry! Cant create band! Breaking script!")
BreakScript() --there is no such function, so it crashes script
end
end
return b
end
function CanBeUsed(sg1) --checking end of bands
function ssCheck(ss)
local good=false
if use.Sheet and ss=="E" then good=true end
if use.Helix and ss=="H" then good=true end
if use.Loop and ss=="L" then good=true end
return good
end
local ok=true
if #DoNotUse>0 then --none of 2 can be in that area
for i=1, #DoNotUse do
local r=DoNotUse[i]
for x=r[1],r[2] do
if x==sg1 then
ok=false
break
end
end
if ok==false then break end
end
end
if ok==false then
return false --if false can`t be used
else
ok=false
if #AlwaysUse>0 then --at least one have to be there
for i=1, #AlwaysUse do
local r=AlwaysUse[i]
for x=r[1],r[2] do
if x==sg1 then
ok=true
break
end
end
if ok==true then break end
end
else
ok=true
end
end
if ok==true then --check structure
ok=false
if ssCheck(structure.GetSecondaryStructure(sg1)) then ok=true end
end
return ok
end
function printArray(array)
for i=1,#array do
p(array[i])
end
end
function printArrayOneLine(array)
local arrayString=""
for i=1,#array do
arrayString=arrayString..array[i]..", "
end
print(arrayString.."end")
end
function ScoreHerd() --score all critters from herd
save.Quickload(3)
p("Scoring "..#critters.." critters...")
save.Quicksave(5)
save.Quicksave(6)
local herdScore=Score()
--jon
--if((os.time()-startTime)>159) then
p("(os.time()-startTime) is "..(os.time()-startTime))
--end
for i=1,#critters do
--if fairness then
if (herd.fairness) then --jon fixed own typo, I blame Lua for not warning me about undeclared variable
--p("fair komi")
--save.Quickload(6) --jon's idea to prevent plagiarism between agents, but isn't as fast as komi
-- for impartiality and also negating previous critter's possibly shoddy work
-- to avoid the first critter intentionally ruining the 2nd's situation
--puzzle.StartOver() --forfeit, doesn't last in Orange Guard
end
--print("critter "..i..": ")
band.DeleteAll() --jon: this is modified in OG (orange guard), perhaps lacking fairness
local crt=critters[i] --critter
local s=Score() --start score
local bnds=crt.bands
for b=1,#bnds do
local bnd=bnds[b]
print("gen"..gen..", critter "..i..", band "..b..": ")
if showBands then
printArrayOneLine(bnd)
--prints band Segment #, Stride size #, Index #, xyz, strength
end
local atom=5
local sn=bnd[1]
if sn==segCnt then atom=6 end --bug in last segment
if structure.GetAminoAcid(sn)=='g' then atom=0 end --glycyne
band.Add(sn,bnd[2],bnd[3],bnd[4],bnd[5],bnd[6],atom)
local bc=band.GetCount()
band.SetStrength(bc,bnd[7])
end
selection.SelectAll()
CI(pullCI)
recentbest.Save() --Description
--Save the current pose as the recent best pose.
Wiggle("wb",1)
band.DeleteAll()
CI(1)
if s-Score() > qstabThresh then
Qstab()
else
Wiggle()
end
SaveRB() --jon reorganized
if Score()-bestScore>fuzeThresh then
--Fuze() --jon disabled for fairly testing different scripts,
-- and it's also disabled in Susume's Orange Guard for some reason too
end
--jon
if(herd.fairness) then
crt.score=Score() -- -2*s -- -s/1.5 --Score()-s -- +komi*i --pemdas --komidashi --jon changed - to /
p("using fair komidashi because the endgame is hardest:")
p("crt.score=Score()")
--p("reverse komi Score()-2*s")
else
crt.score=Score()-s --jon: remember s is start score,
--but as long as the 2nd critter doesn't mess up the 1st's work
--it is pretty intelligent.
end
p("Critter "..crt.name.." : "..round(crt.score))
if critter.maxLoss>0 then
if Score()>herdScore-critter.maxLoss then
save.Quicksave(5)
herdScore=Score()
else
save.Quickload(5)
end
else
save.Quickload(3)
end
end
save.Quickload(3)
if band.GetCount()>0 then --clean bands from best solution (if any)
band.DeleteAll()
save.Quicksave(3)
end
end
function BreedCritters(mom,dad,t) --breed 2 critters. bands are taken randomly
local kid={}
critterID=critterID+1
kid.no=critterID
kid.name=kid.no.."-"..t..mom.no..'/'..dad.no
kid.bands={}
local mb=#mom.bands
local db=#dad.bands
if mb>db then mb,db=db,mb end --kid have bands count between mom and dad bands
local bn=random(mb,db)
for i=1,bn,2 do
kid.bands[#kid.bands+1]=mom.bands[random(#mom.bands)]
kid.bands[#kid.bands+1]=dad.bands[random(#dad.bands)]
end
p(kid.name.." bands: "..#kid.bands)
return kid
end
function KeepGood() --copy best scoring critters form last gen if score above
local newHerd={}
for i=1,herd.keepBest do
if critters[i].score>critter.keepScore then
newHerd[#newHerd+1]=critters[i]
end
end
return newHerd
end
function SortCritters() --bubble sort
for i=1,#critters do
for j=i+1,#critters do
if critters[i].score<critters[j].score then
critters[i],critters[j]=critters[j],critters[i] --love lua :)
end
end
end
end
function BreedHerd()
p("Breeding...")
SortCritters()
newHerd=KeepGood()
for i=1, herd.breedBest do
local mom=critters[i]
if mom.score>critter.breedScore or i<2 then --breed only good ones, 1st is always breed anyway
for j=i+1, herd.breedBest do
local dad=critters[j]
newHerd[#newHerd+1]=BreedCritters(mom,dad,"kid-")
newHerd[#newHerd+1]=BreedCritters(dad,mom,"kid-")
end
end
end
for i=1, herd.breedBastards do --they will always appear ;]
local mom=critters[i]
local j=random(herd.breedBastards+1,#critters)
local dad=critters[j]
newHerd[#newHerd+1]=BreedCritters(mom,dad,"bas-")
newHerd[#newHerd+1]=BreedCritters(dad,mom,"bas-")
end
critters=newHerd
FillHerd()
end
function ShuffleHerd()
if herd.shuffle==true then
for i=1,#critters do
local r=random(#critters)
if r~=i then
critters[i],critters[r]=critters[r],critters[i]
end
end
end
end
--jon copied from web
function script_path()
print (debug.getinfo(1).source)
--local str = debug.getinfo(2, "S").source:sub(2)
--return str:match("(.*/)")
--str:match("(.*[/\\])") --for Windows, but breaks recipe
end
function GAB()
--p("script name and version ".. print(script_path())) --arg[0]) --breaks recipe
--script_path()
p("Rav3n_pl Bands! Bands in space! GAB BiS")
if(shouldReset) then --jon
puzzle.StartOver() --reset_puzzle --should also work
CI(1) --clash
behavior.SetWigglePower("a") --auto
--overwrite prev.
save.Quicksave(3)
save.Quicksave(4)
save.Quicksave(5)
save.Quicksave(6) --jon's
p("puzzle reset")
end
if onlyMutable==true then
for i=1,segCnt do
if structure.IsMutable(i) then
UseSegments[#UseSegments+1]=i
end
end
end
bestScore=Score()
critterID=0
gen=0
ss=Score()
save.Quicksave(3)
recentbest.Save() --Description
--Save the current pose as the recent best pose.
--jon
startTime=os.time()
p("time now is "..os.time())
p("Start score: "..round(ss))
p("herd.maxGen "..herd.maxGen)
p("fairness "..tostring(herd.fairness))
critters={}
FillHerd()
badGen=0
while true do --this is (almost) endless script ;]
genScore=Score()
gen=gen+1
p()
p("Generation: "..gen..", score: "..round(Score())..", gain: "..round(Score()-ss))
ShuffleHerd()
ScoreHerd()
save.Quickload(3)
if gen>=herd.maxGen then --jon changed == to >= in case of 0 gens
--herd.maxGen was not being updated by user because of typo
print("gen limit "..herd.maxGen)
break --end of script
else
print("herd.maxGen " ..herd.maxGen)
end
if genScore<=Score() then badGen=badGen+1 else badGen=0 end
if badGen>=herd.renew then
p("Creating fresh random herd...")
critters={}
FillHerd()
badGen=0
else
BreedHerd()
end
--jon copied from AILearnsToPlayFoldit
local startSegmentNumber = math.random(1, structure.GetCount() - 3)
selection.DeselectAll()
selection.SelectRange(startSegmentNumber, startSegmentNumber + 3)
structure.IdealizeSelected()
selection.DeselectAll()
local startSegmentNumber = math.random(1,structure.GetCount() - 3)
selection.DeselectAll()
selection.SelectRange(startSegmentNumber, startSegmentNumber + 3)
structure.RebuildSelected(2)
selection.DeselectAll()
--structure.WiggleAll(1, true,true) --iter, backbone, sidechains
Wiggle("wa")
end
p("Final score: "..round(Score()).." Total gain: "..round(Score()-ss))
--jon
p("time now is "..os.time())
p("time used is "..(os.time()-startTime))
p("fairness "..tostring(herd.fairness))
p("Save7.lua")
end
function dialogOptions()
local mut=false
for i=1,segCnt do
if structure.IsMutable(i) then mut=true break end
end
if not mut then
onlyMutable=false
mutateOnce=false
end
local opt = dialog.CreateDialog("GAB BiS")
repeat
--jon: table dialog.AddSlider(string label, number value, number minimum, number maximum, number precision)
--0 precision: 1, 2, 3,
--1 precision: 0.1,0.2,0.3
opt.lbl1 = dialog.AddLabel("Main Options")
opt.gen = dialog.AddTextbox("Generations:", herd.maxGen)
opt.pull= dialog.AddSlider("Pulling CI", pullCI, 0.05, 1, 2)--desctip, default, min, max, precision
opt.shak= dialog.AddSlider("Shake CI", shakeCI, 0.01, 1, 2)
opt.maxci= dialog.AddSlider("Maximum CI", maxCI, 0.05, 1, 2)
opt.qstab=dialog.AddCheckbox("FastQstab", fastQstab)
opt.energy=dialog.AddCheckbox("Seek energy", energy)
opt.mutonly=dialog.AddCheckbox("Only mutable", onlyMutable)
opt.mutonce=dialog.AddCheckbox("Mutate once", mutateOnce)
opt.mutalways=dialog.AddCheckbox("Mutate always", mutateAlways)
opt.mutnever=dialog.AddCheckbox("Mutate never", mutateNever) --jon imported from susume's orange guard recipe
opt.fuzeth=dialog.AddTextbox("Fuze threshold: ",fuzeThresh)
opt.qsth=dialog.AddTextbox("Qstab threshold: ",qstabThresh)
opt.reset=dialog.AddCheckbox("reset puzzle", shouldReset) --at start
opt.showBands=dialog.AddCheckbox("showBands", showBands) --at start
opt.run = dialog.AddButton("OK", 1)
opt.more = dialog.AddButton("More", 2)
opt.cancel = dialog.AddButton("Cancel", 0)
res=dialog.Show(opt)
if res > 0 then
energy=opt.energy.value
pullCI=opt.pull.value
shakeCI=opt.shak.value
maxCI=opt.maxci.value
fastQstab=opt.qstab.value
fuzeThresh = tonumber(opt.fuzeth.value)
qstabThresh= tonumber(opt.qsth.value)
onlyMutable=opt.mutonly.value
mutateOnce=opt.mutonce.value
mutateAlways=opt.mutalways.value
mutateNever=opt.mutnever.value
herd.maxGen=tonumber(opt.gen.value) --jon fixed maxgen to maxGen
shouldReset=opt.reset.value
showBands=opt.showBands.value
if res==2 then MoreOptions() end
end
until res < 2
return ( res > 0 )
end
function MoreOptions ()
local opt = dialog.CreateDialog("GaB BiS More Options")
opt.lbl1 = dialog.AddLabel("Herd:")
opt.brbest=dialog.AddSlider("Breed best", herd.breedBest, 1, 20, 0)
opt.kbest=dialog.AddSlider("Keep best", herd.keepBest, 1, 20, 0)
opt.bas=dialog.AddSlider("Breed bastards", herd.breedBastards, 0, 20, 0) --jon min 1 to 0
opt.rnd=dialog.AddSlider("New random", herd.newRandom, 0, 30, 0) --jon min 1 to 0
opt.renew=dialog.AddSlider("Renew herd", herd.renew, 1, 20, 0) --jon 10 to 20
opt.shuff=dialog.AddCheckbox("Shuffle critters", herd.shuffle)
opt.fairness=dialog.AddCheckbox("All is fair in AI agents", herd.fairness) --jon
opt.lbl2 = dialog.AddLabel("Critter:")
opt.minb=dialog.AddTextbox("Minimum bands:", critter.minBands)
opt.maxb=dialog.AddTextbox("Maximum bands:", critter.maxBands)
opt.keeps=dialog.AddTextbox("Keep score:", critter.keepScore)
opt.breeds=dialog.AddTextbox("Breed score:", critter.breedScore)
opt.maxloss=dialog.AddTextbox("Max loss:", critter.maxLoss)
opt.lbl3 = dialog.AddLabel("Bands:")
opt.minst=dialog.AddSlider("Minmum str", bands.minStr, 0.1, 2, 1)
opt.maxst=dialog.AddSlider("Maximum str", bands.maxStr, 0.1, 2, 1)
opt.maxup=dialog.AddSlider("Max length", bands.maxUp, 3, 20, 0)
opt.lbl4 = dialog.AddLabel("Import pre-evolved algorithm:")
opt.lbl5 = dialog.AddLabel("(Not functional yet)")
opt.lbl6 = dialog.AddLabel("Segment #, stride #, index #, x,y,z, strength")
local moreDialog = opt --lazy copy pasting
moreDialog.Label = dialog.AddLabel("Include pre-defined algorithms (made up):")
moreDialog.AddAlgorithm1 = dialog.AddCheckbox("160, 70, 2, 34.555, 23.56, 30.9, 1.1", true)
moreDialog.AddAlgorithm2 = dialog.AddCheckbox("160, 70, 2, 34.555, 23.56, 30, 0.3", true)
moreDialog.LabelCustomAlgorithm = dialog.AddLabel("Include custom algorithm:")
moreDialog.CustomHash = dialog.AddTextbox("", "")
opt.run = dialog.AddButton("OK", 1)
dialog.Show(opt)
herd.breedBest = opt.brbest.value
herd.keepBest = opt.kbest.value
herd.breedBastards = opt.bas.value
herd.newRandom = opt.rnd.value
herd.shuffle = opt.shuff.value
herd.renew=opt.renew.value
herd.fairness=opt.fairness.value --jon
critter.minBands=tonumber(opt.minb.value)
critter.maxBands=tonumber(opt.maxb.value)
critter.keepScore = tonumber(opt.keeps.value)
critter.breedScore=tonumber(opt.breeds.value)
critter.maxLoss=tonumber(opt.maxloss.value)
bands.minStr=opt.minst.value
bands.maxStr=opt.maxst.value
bands.maxUp = opt.maxup.value
end
function Cleanup ( err )
print ( "Restoring clashing importance and best result" )
CI ( 1 )
save.Quickload ( 3 )
p("Final score: "..round(Score()).." Total gain: "..round(Score()-ss))
print ( err )
end
-- main call
if dialogOptions() then
xpcall ( GAB, Cleanup )
end
--end of script