Code
--[[
Genetic Bands III by Crashguard303
Inspired by cartoon Villain's Script, I created this one, including freezing.
This script also works on not-very-best puzzle-states.
Random Creating:
It creates a set of clusters (herd). Each cluster has a random set of bands,freezes and a drift value.
There is a random list, which segments have already been banded or frozen, so it is guaranteed that all are tried.
If all have been tried, this list is shuffled, all segments are tried again.
The drift shows, in which directions banding or freezing segments can move.
(-1 will increase current segment number by, +1 will increase segment number by 1, 0 won't exist)
Then, for this puzzle state, all clusters are tried.
This means:
Pulling:
Bands and freezes are applied (plus constant bands, if you want), the puzzle is wiggled for one iteration.
Releasing:
Bands and freezes are removed.
4 different subroutines similar to blue fuse run.
They test different clashing-importance shake/wiggle combinations.
The best result of the fuse is stored to the cluster score.
Sorting:
If all clusters were tried, they are sorted by the scores they created, double score results are moved to the end of the list.
The best cluster is on top, the worst or and equal cluster is on bottom at the list.
Breeding:
All clusters from BreedFirst to BreedLast are breeded, first best with other random clusters, preferring good ones
The cluster with the most bands is mom, cluster with fewer bands is dad.
Bands of mom and drift value are copied at first,
then a random crossover point is generated within the length of dad's bands.
Bands from 1 to inclusive crossover point from the new cluster are replaced by bands of dad, rest of mom
The same happens to freezes.
Mutating:
For each band or freeze:
A random flag (Drift flag or multiplicator) (either 0 or 1) is created, depending on mutation probability.
If this flag is 1, drift value (-1 or +1) is added to the segment index, in other case, the segment index stays the same.
Band length and strength are randomly changed between an amount of -0.1 to 0.1
When mutating, all values are checked after they are changed, to guarantee that they don't leave a legal value range.
Inverting:
For each band or freeze:
A random flag (inversion flag) (either o or 1) is created, depending on inverting probability.
If this flag is 1, segment values are inverted, which means multiplying them by -1
Negative segment values results banding or freezing skip, they are deactivated.
If a segment has a negative value and is inverted, the value is positive and activated again
Filling:
The rest of the herd (BreedLast+1 to HerdSize) is replaced by new random clusters, as described in Random Creating.
Restart:
When new clusters have been breeded or replaced, the recent best puzzle state is loaded.
and pulling is performed again.
END OF MAIN DESCRIPTION
P.S.: If you find typos, you can keep them ;)
]]--
function math.frandom(m,n,e)
-- float random, e is number of remaining decimal digits
local e2=10^e
return math.random(m*e2,n*e2)/e2
end -- function
function math.sgn(x)
-- Signum function
if x==nil then
return nil
elseif x>0 then
return 1
elseif x<-0 then
return -1
else
return x
end -- if
end -- function
function CutOff(x,y)
-- Keep only y digits after decimal point from x
return math.floor(x*10^y)/10^y
end -- function
BoolString={}
BoolString[true]="In"
BoolString[false]="Ex"
function UseSegIRange(UseList,A,B,StepSize,bool)
-- In table "UseList", append or remove segment range A to B
local UseList=UseList
local A=A -- range start
local B=B -- range end
if A>B then A,B=B,A end -- Swap range start and end, if values are not okay
local StepSize=StepSize
local bool=bool -- True=append, false=remove
local k=A
repeat
UseList=UseList_AR(UseList,k,bool)
k=k+StepSize -- increase k by StepSize
until k>B -- until k exceeds B
return UseList
end -- function
function UseSegIValues(UseList,field,bool)
-- In table "UseList", append or remove segments listed in table "field"
local UseList=UseList
local field=field -- table to add
local bool=bool -- True=append, false=remove
local k
if #field>0 then -- If table to add is not empty
for k=1,#field do -- cycle through all elements from table
UseList=UseList_AR(UseList,field[k],bool)
end -- k loop
end -- if #field
return UseList
end -- function
function Use_ss(UseList,SSLetter,bool)
-- In table "UseList", append or remove segments with secondary structure "SSLetter"
local UseList=UseList
local SSLetter=SSLetter
local bool=bool -- True=append, false=remove
local k
for k=1,NumSegs do -- Cycle through all segment indices
if structure.GetSecondaryStructure(k)==SSLetter then -- If current segment index has same ss as given
UseList=UseList_AR(UseList,k,bool)
end -- if get_ss
end -- k loop
return UseList
end -- function
function Use_aa(UseList,AALetters,bool)
-- In table "UseList", append or remove segments with amino-acid in table "AALetters"
local UseList=UseList
local AALetters=AALetters
local bool=bool -- add or remove
if #AALetters>0 then -- Is there minimum one aa letter given?
local k
for k=1,NumSegs do -- Cycle through all segments
local aa=get_aa(k) -- get current aa of segment index k
local exit_condition=false
local l=0
repeat
l=l+1 -- Cycle through all given aa letters
if aa==AALetters[l] then -- If current segment k's aa is equal to AALetters[l]
UseList=UseList_AR(UseList,k,bool)
exit_condition=true -- l loop can end here
end -- if aa
if l==#AALetters then exit_condition=true end
until exit_condition==true
end -- k loop
end -- if AALetters
return UseList
end -- function
function Use_distance(UseList,MinDist,MaxDist,MinQuantity,MaxQuantity,bool)
-- In table "UseList", append or remove segments which have
-- MinQuantity to MaxQuantity neighbours within distance MinDist to MaxDist
local MinDist=MinDist
local MaxDist=MaxDist
local MinQuantity=MinQuantity
local MaxQuantity=MaxQuantity
local bool=bool -- True=append, false=remove
for k=1, NumSegs do -- Cycle through all segments
local QCount=0
for l=1,NumSegs do -- compare index k with all around
if l~=k then -- Don't count segment itself, only neighbours
local Distance=structure.GetDistance(k,l) -- Measure distance from segment k to segment l
if Distance>=MinDist then -- If equal or above min distance
if Distance<=MaxDist then -- If equal or below max distance
QCount=QCount+1 -- count this segment
end -- if Distance
end -- if Distance
end -- if l
end -- l loop
if QCount<=MaxQuantity then -- If count is equal or below max quantity
if QCount>=MinQuantity then -- If count is equal or above min quantity
UseList=UseList_AR(UseList,k,bool) -- add or remove from list
end -- if QCount
end -- if QCount
end -- k loop
return UseList
end -- function
function Use_close_ligand(UseList,MaxDist,bool)
-- In table "UseList", append or remove segments which have
-- a spatial distance of MaxDist or closer to ligand.
local UseList=UseList
local MaxDist=MaxDist
local bool=bool -- True=append, false=remove
local LigandIdx=NumSegs+1 -- Ligand Index
local k
for k=1,NumSegs do -- Cycle with k through all segments but not ligand
local Distance=structure.GetDistance(k,LigandIdx) -- Check spatial distance of segment k to ligand
if Distance<=MaxDist then -- If equal or below MaxDist
UseList=UseList_AR(UseList,k,bool) -- add or remove from list
end -- if
end -- k loop
return UseList
end -- function
function UseList_AR(UseList,value,bool)
-- Append value "value" to UseList
-- or remove all value's out of UseList, depending on bool
local UseList=UseList
local value=value
local bool=bool -- True=append, false=remove
-- print(BoolString[bool],"cluding segment index ",value)
if bool==true then
UseList=UseList_Append(UseList,value)
else
UseList=UseList_Remove(UseList,value)
end -- if bool
return UseList
end -- function
function UseList_Append(UseList,value)
-- Adds content of "value" at end of UseList
local UseList=UseList
local value=value
UseList[#UseList+1]=value
return UseList
end -- function
function UseList_Remove(UseList,value)
-- Creates new use list "UseList2" out of "UseList", but without all content of "value"
local UseList=UseList
local value=value
local UseList2={} -- Initialize new uselist
if #UseList>0 then -- if old
for k=1,#UseList do -- Scan UseList
if UseList[k]~=value then -- If "value" is not found
UseList2[#UseList2+1]=UseList[k] -- append this content to new use list
end -- if UseList
end -- k loop
end -- if UseList
return UseList2
end -- function
function CompactContent(OS,E,AS)
--[[
Adds string AS to temporary output string OS and counts number of added AS in E.
If OS contents 10 elements, line is printed out
I only had to write this sub because there Lua standard library is not included.
We neither can do some string manipulation nor use a print command without linebreaks at the moment :/
]]--
local OS=OS -- Temporary string for output
local AS=AS -- String to add
local E=E -- Element counter, how often AS was added
E=E+1
if AS<10 then -- Sorry for this, but there are no string operations possible at the moment
OS=OS.."00"..AS
elseif AS<100 then
OS=OS.."0"..AS
else
OS=OS..AS
end -- if AS
if E<10 then -- If there are not 10 elements per line
OS=OS.." " -- add space
else -- If there are 10 elements per line
print(OS) -- print line out
OS="" -- clear output string
E=0 -- reset element counter
end -- if E
return OS,E
end -- function
function CheckUselist(UseList)
-- If there are fewer than 3 specific segments to work on given, take all
local UseList=UseList
if #UseList<=3 then
print("No or too few specific segments to work on given.")
print("Banding and freezing will now manipulate all puzzle segments.")
UseList={}
local k
for k=1,NumSegs do -- Cycle through all segments
UseList[#UseList+1]=k -- extend UseList by 1 and add segment index
end -- k loop
end -- if UseList
print("Segment list is now:")
local OS="" -- Initialize output string
local E=0 -- Initialize element counter
local k
for k=1,#UseList do
OS,E=CompactContent(OS,E,UseList[k])
end -- k
if E>0 then
print(OS)
end
return UseList
end -- function
function shuffle2(ShuffleProb)
-- Scrambles segment use list depending on ShuffleProb
print("Shuffling segment list")
local kend=#UseList-1
local k
for k=1,kend do -- Cycle through all UseList entries
if math.random()<ShuffleProb then -- If random value<probability
-- same as: if random_flag(ShuffleProb) then
local l=math.random(k+1,#UseList) -- pick random list entry behind k
UseList[k],UseList[l]=UseList[l],UseList[k] -- swap values of UseList index k with UseList index l
end -- if random
end -- k loop
UsedSegments=0 -- After Shuffling, set list pointer to 0
end
function random_segment()
-- Gets a random segment index out of UseList which has not been used so far
UsedSegments=UsedSegments+1 -- Increase UseList pointer
local Seg_result=UseList[UsedSegments] -- fetch segment index
if UsedSegments==#UseList then shuffle2(ShuffleProb) end
-- If this was the last value of UseList, shuffle list and reset pointer
return Seg_result -- return random segment number
end -- function
function FillHerd(StartAt,EndAt,TimeStamp)
local StartAt=StartAt -- First cluster index to generate (not cluster slot)
if StartAt<=HerdSize then
local EndAt=EndAt -- Last cluster index to generate (not cluster slot)
local TimeStamp=TimeStamp -- "birth date"
print("Generating Herd from ",StartAt," to ",EndAt)
local k2
for k2=StartAt,EndAt do
-- print("Cluster: ",k2)
local k=ClusterPointer[k2] -- Fetch cluster slot by cluster index
ClusterScore[k]=0
ClusterDrift[k]=random_direction() -- Create value -1 or 1
if (Mimic and k2==1) then -- If this is a Mimic band
ClusterType[k]="mimic" -- set cluster information for Mimic
else -- If this is not a Mimic band
ClusterType[k]="random" -- set cluster information for random
end -- if k2
ClusterType[k]=ClusterType[k].."-"..TimeStamp -- Show "birth date"
if ClusterBands>0 then
ClusterSegA[k]={}
ClusterSegB[k]={}
ClusterLength[k]={}
ClusterStrength[k]={}
local l
for l=1,ClusterBands do
if puzzle_is_ligand==false then -- If this is no ligand puzzle
ClusterSegA[k][l]=random_segment() -- get random segment number
else -- If this is a ligand puzzle
ClusterSegA[k][l]=NumSegs+1 -- Segment A is always ligand
end -- if puzzle_is_ligand
if (Mimic==false or k2~=1) then -- If this is not a Mimic band
if random_flag(InvBProb) then -- and random flag for inverting is given
ClusterSegA[k][l]=-ClusterSegA[k][l] -- toggle cluster on/off by changing signum (+/-)
end -- if random_flag
end -- if k2
repeat
ClusterSegB[k][l]=random_segment()
local IDistance=index_distance(ClusterSegA[k][l],ClusterSegB[k][l]) -- fetch index distance
local SDistance=structure.GetDistance(math.abs(ClusterSegA[k][l]),ClusterSegB[k][l]) -- fetch spatial distance
until IDistance>=MID_Game and SDistance<=MaxBL_Game
-- Segments must have a minimum index distance
-- and maximum spatial distance
if (Mimic and k2==1) then -- If this is a Mimic band
ClusterLength[k][l]=FactorByLength(ClusterSegA[k][l],ClusterSegB[k][l])
ClusterStrength[k][l]=MaxBS
else -- if k2
ClusterLength[k][l]=math.frandom(0,1,6)
ClusterStrength[k][l]=math.frandom(MinBS,MaxBS,3)
end -- if k2
end -- l loop
end -- if ClusterBands
if ClusterFreezes>0 then
ClusterFreeze[k]={}
local l
for l=1,ClusterFreezes do
ClusterFreeze[k][l]=random_segment()
if (Mimic and k2==1) or random_flag(InvFProb) then -- If this is a Mimic band or random flag for inverting freeze information is given
ClusterFreeze[k][l]=-ClusterFreeze[k][l] -- toggle freeze on/off by changing signum (+/-)
end -- if random_flag
end -- l loop
end -- if
end -- k loop
end -- if StartAt
end -- function
function ShowHerd()
local k2
local l
print("Clusters are:")
for k2=1,HerdSize do
local k=ClusterPointer[k2]
print("Cluster:",k2," score:",ClusterScore[k]," drift:",ClusterDrift[k])
if ClusterBands>0 then
local l
for l=1,ClusterBands do
print("Band ",l,": ",ClusterSegA[k][l]," to ",ClusterSegB[k][l])
end
end -- if ClusterBands
if ClusterFreezes>0 then
local l
for l=1,ClusterFreezes do
print("Freeze ",l,": ",ClusterFreeze[k][l])
end -- l
end -- if
end -- k loop
end -- function
function ShowHerdShort()
print("Clusters are:")
local k2
for k2=1,HerdSize do
local k=ClusterPointer[k2]
print("Cluster:",k2,"(",k,") score:",ClusterScore[k])
print("Drift:",ClusterDrift[k])
end -- k loop
end -- function
function ShowScoreList()
local k2
for k2=1,HerdSize do
local k=ClusterPointer[k2]
print("Cluster:",k2,"(",k,") delta:",ClusterScore[k])
end -- k
end -- function
function SortHerd()
-- As we use pointers, we only have to swap the cluster "adresses" instead of copying all band/freeze values
local Finish=HerdSize-1
local k
for k=1,Finish do
local start=k+1
local l
for l=start,HerdSize do
if ClusterScore[ClusterPointer[l]]>ClusterScore[ClusterPointer[k]] then
-- print("Swapping cluster ",k,"(",ClusterPointer[k],"):",l,"(",ClusterPointer[l],")")
ClusterPointer[l],ClusterPointer[k]=ClusterPointer[k],ClusterPointer[l]
end -- if
end -- l
end -- k
end -- function
function CreateCrossoverPoint(x)
-- create random crossover point
if x==0 then
return 0
else -- if x ~=0
-- return math.random(0,x) -- create random value between 0 and x
return math.random(1,x-1) -- create random value between 1 and x-1
end -- if x
end -- function
function Breed(TimeStamp)
-- Copy all clusters to another bank, same name for all cluster tables but with 2 at end
-- Breed two clusters from bank 2 back to old bank to prevent breeding collision.
-- If this wouldn't be done, and (for example) cluster 1 and 2 would be breeded to cluster 3,
-- cluster 3 couldn't be used as breeding source again, as it would be overwritten already.
local TimeStamp=TimeStamp -- "birth date"
ClusterScore2={}
-- ClusterType2={}
ClusterDrift2={}
ClusterSegA2={}
ClusterSegB2={}
ClusterLength2={}
ClusterStrength2={}
ClusterFreeze2={}
local k
for k=1,HerdSize do
ClusterScore2[k]=ClusterScore[k]
ClusterDrift2[k]=ClusterDrift[k]
if ClusterBands>0 then
ClusterSegA2[k]={}
ClusterSegB2[k]={}
ClusterLength2[k]={}
ClusterStrength2[k]={}
local l
for l=1,ClusterBands do
ClusterSegA2[k][l]=ClusterSegA[k][l]
ClusterSegB2[k][l]=ClusterSegB[k][l]
ClusterLength2[k][l]=ClusterLength[k][l]
ClusterStrength2[k][l]=ClusterStrength[k][l]
end -- l loop
end -- if ClusterBands
if ClusterFreezes>0 then
ClusterFreeze2[k]={}
local l
for l=1,ClusterFreezes do
ClusterFreeze2[k][l]=ClusterFreeze[k][l]
end -- l loop
end -- if ClusterFreezes
end -- k loop
FitnessOffset=ClusterScore[ClusterPointer[HerdSize]]
-- Take worst cluster score as fitness offset reference
-- By subtracting this score offset from each cluster score and adding 1,
-- it is guaranteed that worst cluster score is always 1, all other scores are better.
-- This will shift all cluster scores up, if worst cluster score is below 1, so there will be no values<1 for fitness calculation.
-- It will pull all scores down, if worst cluster score is above 1, to guarantee maximum privilege by fitness.
-- If all clusters would have similar big score values, they would be choosen equally.
-- By shifting the scores making the last score be 1, other scores are always x-times higher than 1.
Fitness=0
local k
for k=1,HerdSize do
ClusterScore2[k]=ClusterScore2[k]-FitnessOffset+1 -- Shift cluster score copy by offset to met condition
-- We can overwrite this table, because it is only needed for breeding, and it won't affect the original table.
-- Original score will remain untouched.
Fitness=Fitness+ClusterScore2[k] -- Calculate fitness by adding all scores (offset included)
end -- k loop
local k
for k=BreedFirst,BreedLast do
ClusterBreed(k,TimeStamp)
end -- k loop
end -- function
function Roulette()
-- Returns a random cluster index (not slot), the better their score, the more often they will appear
local TValue=math.random()*(Fitness+1) -- Target fitness value, which will stop the wheel
local CValue=0 -- Current value, where single cluster points will be added
local Wheel=math.random(1,HerdSize) -- Random initial wheel position
repeat
Wheel=Wheel+1 -- Spin Wheel
if Wheel>HerdSize then Wheel=1 end -- If Wheel made a full turn, it starts at 1 again
CValue=CValue+ClusterScore2[ClusterPointer[Wheel]]
-- Increase current value by score of cluster at wheel position
until CValue>TValue
return Wheel
end -- function
function ClusterBreed(indexClusterB,TimeStamp)
local indexClusterB=indexClusterB -- Target cluster
local TimeStamp=TimeStamp -- "birth date"
local indexClusterA1 -- Source cluster 1
if Parent1isRoulette==false then
indexClusterA1=indexClusterB-BreedFirst+1 -- Choose best, starting with 1
else
indexClusterA1=Roulette() -- Choose one by fitness roulette, the better the score, the more often it can be chosen
end -- if Parent1isRoulette
local indexClusterA2 -- Source cluster 2
if Parent2isRoulette==true then
repeat
indexClusterA2=Roulette() -- Choose one by fitness roulette, the better the score, the more often it can be chosen
until indexClusterA2~=indexClusterA1 -- clusters must be different
else
indexClusterA2=indexClusterA1+1 -- Take next to Parent 1
if indexClusterA2>HerdSize then indexClusterA2=1 end
end -- if Parent2isRoulette
local ClusterB=ClusterPointer[indexClusterB] -- Fetch save slot for target cluster
local ClusterA1=ClusterPointer[indexClusterA1] -- Fetch load slot for source cluster 1 (dad)
local ClusterA2=ClusterPointer[indexClusterA2] -- Fetch load slot for source cluster 2 (mom)
local OS="Breeding "..indexClusterA1.."("..ClusterA1..") and "..indexClusterA2.."("..ClusterA2..") to "..indexClusterB.."("..ClusterB..")"
print(OS)
ClusterType[ClusterB]="breeded".."-"..TimeStamp
ClusterDrift[ClusterB]=ClusterDrift2[ClusterA1] -- Take drift information always from dad, preventing change in drift when breeding
if ClusterBands>0 then -- We only need to copy if there are any bands
local CrossoverEnd
if MultiCrossOver== false then
CrossoverEnd=CreateCrossoverPoint(ClusterBands) -- Create
print("Band crossover point: ",CrossoverEnd) -- and show crossover point
end -- if MultiCrossOver
ClusterSegA[ClusterB]={}
ClusterSegB[ClusterB]={}
ClusterLength[ClusterB]={}
ClusterStrength[ClusterB]={}
local BandsChild
local l
for l=1,ClusterBands do -- Is crossover point reached?
if CrossOverCheck(CrossoverEnd,l) then
BandsChild=ClusterA2 -- Take mom after crossover point
else
BandsChild=ClusterA1 -- Take dad until crossover point
end -- if CrossoverEnd
ClusterSegA[ClusterB][l]=ClusterSegA2[BandsChild][l]
ClusterSegB[ClusterB][l]=ClusterSegB2[BandsChild][l]
ClusterLength[ClusterB][l]=ClusterLength2[BandsChild][l]
ClusterStrength[ClusterB][l]=ClusterStrength2[BandsChild][l]
end -- l loop
end -- if ClusterBands
if ClusterFreezes>0 then -- We only need to copy if there are any freezes
local CrossoverEnd
if MultiCrossOver== false then
CrossoverEnd=CreateCrossoverPoint(ClusterFreezes) -- Create
print("Freeze crossover point: ",CrossoverEnd) -- and show crossover point
end -- if MultiCrossOver
ClusterFreeze[ClusterB]={}
local FreezesChild
local l
for l=1,ClusterFreezes do -- For all freezes
if CrossOverCheck(CrossoverEnd,l) then -- Is crossover point reached?
FreezesChild=ClusterA2 -- Take mom after crossover point
else
FreezesChild=ClusterA1 -- Take dad until crossover point
end -- if CrossoverEnd
ClusterFreeze[ClusterB][l]=ClusterFreeze2[FreezesChild][l]
end -- l loop
end -- if ClusterFreezes
-- Cluster mutating starts here
-- Secondary cluster set not needed anymore
-- Mutation is directly performed on target cluster
if ClusterBands>0 then
local l
for l=1,ClusterBands do
if puzzle_is_ligand==false then -- only on non-ligand-puzzles mutate segment A
if random_flag(DriftProb) then
ClusterSegA[ClusterB][l]=drift_segment(ClusterSegA[ClusterB][l],ClusterDrift[ClusterB])
end -- if random_flag
end -- if puzzle_is_ligand
if random_flag(InvBProb) then
ClusterSegA[ClusterB][l]=-ClusterSegA[ClusterB][l] -- invert
end -- if random_flag
repeat
if random_flag(DriftProb) then
ClusterSegB[ClusterB][l]=drift_segment(ClusterSegB[ClusterB][l],ClusterDrift[ClusterB])
end -- if random_flag
local Distance=index_distance(ClusterSegA[ClusterB][l],ClusterSegB[ClusterB][l])
until Distance>=MID_Game -- Segments must have a minimum index distance
if random_flag(DriftProb) then -- mutation allowed
ClusterLength[ClusterB][l]=drift_band_attribute(ClusterLength[ClusterB][l],1,3,0,1,6)
-- change bandlength by value + or - randomly 10e-[1 to 3 randomly]
-- keep bandlength between 0 and 1
-- if error occurs, generate new bandstrength value with 6 decimal digits
end -- if random_flag
if random_flag(DriftProb) then -- mutation allowed
ClusterStrength[ClusterB][l]=drift_band_attribute(ClusterStrength[ClusterB][l],1,3,MinBS,MaxBS,3)
-- change bandstrength by value + or - randomly 10e-[1 to 3 randomly]
-- keep bandstrength between MinBS and MaxBS
-- if error occurs, generate new bandlength value with 3 decimal digits
end -- if random_flag
end -- l loop
end -- if ClusterBands
if ClusterFreezes>0 then
local l
for l=1,ClusterFreezes do
if random_flag(DriftProb) then
ClusterFreeze[ClusterB][l]=drift_segment(ClusterFreeze[ClusterB][l],ClusterDrift[ClusterB])
end -- if random_flag
if random_flag(InvFProb) then
ClusterFreeze[ClusterB][l]=-ClusterFreeze[ClusterB][l] -- invert
end -- if random_flag
end -- l loop
end -- if
end -- function
function drift_segment(Segment,Drift)
local Drift=Drift
local Seg2=Segment
local Seg2sgn=math.sgn(Seg2)
Seg2=math.abs(Seg2)+Drift
if Seg2<1 then
Seg2=NumSegs
elseif Seg2>NumSegs then
Seg2=1
end -- if Seg2
Seg2=Seg2*Seg2sgn
return Seg2
end -- function
function drift_band_attribute(Target,RndE1,RndE2,Min,Max,RndE3)
-- apply random drift on variable Target resulting Target2 (Target2=Target+random value)
-- drift value can be positive or negative
-- and is 10^-[RndE1 to RndE2 randomly]
-- for example if RndE1 is 1 and RndE2 is 3, resulting value can be 10^-1,10-2 or 10^-3
-- If Min or Max is exceeded, value of Target2 is ring-wrapped (moebius transformation, no clamping):
-- If maximum is exceeded, the amount of exceedment (Target2-Value) is applied to minimum
-- If minimum is exceeded, the amount of exceedment (Target2-Value) is applied to maximum
local Target2=Target
local Target2=Target2+random_direction()*10^-math.random(RndE1,RndE2)
if Target2>Max then
Target2=Min+(Target2-Max)
elseif Target2<Min then
Target2=Max+(Target2-Min)
end -- if Target2
if Target2>Max or Target2<Min then
Target2=math.frandom(Min,Max,RndE3)
end -- if Target2
return Target2
end -- function
function CrossOverCheck(CrossoverEnd,Pos)
if MultiCrossOver then
if random_flag(0.5) then
return true
else
return false
end -- if random_flag
else -- if MultiCrossOver is false
if Pos>CrossoverEnd then
return true
else
return false
end -- if Pos
end -- if
end -- function
function random_flag(Prob)
-- Returns false or true randomly
-- how often true appears depends on probability Prob
local Prob=Prob
if math.random()<Prob then
return true
else
return false
end -- if random
end -- function
function random_direction()
-- returns either -1 or 1
return math.random(0,1)*2-1
end -- function
function structure.WiggleBackbone(iter)
structure.WiggleAll(iter,true,false)
end -- function
function structure.WiggleSidechains(iter)
structure.WiggleAll(iter,false,true)
end -- function
function xtool(method,iter,tL2)
-- unifies score-conditional shake and wiggle into one function
local OS=""
local iter=iter
local tL2=tL2
if iter>0 then
OS="max iter:"..iter
end -- if
-- print("Method:",method," ",OS," threshold:",tL2)
local curr_iter=0
local exit_condition=false
repeat
curr_iter=curr_iter+1
-- print(" Testing with iterations: ",curr_iter)
local tempScore=current.GetEnergyScore()
if method=="s" then
structure.ShakeSidechainsAll(curr_iter)
elseif method=="wb" then
structure.WiggleBackbone(curr_iter)
elseif method=="ws" then
structure.WiggleSidechains(curr_iter)
else
structure.WiggleAll(curr_iter)
end -- if method
local tempScore2=current.GetEnergyScore()
local rDelta=(tempScore2-tempScore)/curr_iter
-- print(" Rel. change: ",rDelta," pts/iteration")
if curr_iter==iter then exit_condition=true end
if math.abs(rDelta)<tL2 then exit_condition=true end
until exit_condition==true
end -- function
-- Inspired by vertex's blue fuse script
-- You may want to tweak this function
function PinkFuse()
save.Quicksave(3) -- store state before fuse
print("Release 1")
behavior.SetClashImportance(0.1)
xtool("s",Shakes,ScoreThreshold)
BestScoreCheck()
behavior.SetClashImportance(0.7)
xtool("wa",Wiggles,ScoreThreshold)
BestScoreCheck()
FuseEnd()
save.Quickload(3) -- load state before fuse
print("Release 2")
behavior.SetClashImportance(0.3)
xtool("s",Shakes,ScoreThreshold)
BestScoreCheck()
behavior.SetClashImportance(0.6)
xtool("wa",Wiggles,ScoreThreshold)
BestScoreCheck()
FuseEnd()
save.Quickload(3) -- load state before fuse
print("Release 3")
behavior.SetClashImportance(0.2)
xtool("s",Shakes,ScoreThreshold)
BestScoreCheck()
behavior.SetClashImportance(0.5)
xtool("wa",Wiggles,ScoreThreshold)
BestScoreCheck()
FuseEnd()
save.Quickload(3) -- load state before fuse
print("Release 4")
behavior.SetClashImportance(0.4)
xtool("s",Shakes,ScoreThreshold)
BestScoreCheck()
behavior.SetClashImportance(0.3)
xtool("wa",Wiggles,ScoreThreshold)
BestScoreCheck()
FuseEnd()
end
function FuseEnd()
-- Fuse try finishing with CI=1
behavior.SetClashImportance(1)
xtool("wa",Wiggles,ScoreThreshold)
xtool("s",Shakes,ScoreThreshold)
xtool("wa",Wiggles,ScoreThreshold)
BestScoreCheck()
end -- function
function BestScoreCheck()
local TempScore=current.GetEnergyScore()
if LastBest then
if TempScore>ACS5 then
save.Quicksave(5) -- Set best cluster result for this generation
ACS5=TempScore
-- print("ACS5 is ",ACS5)
end -- if TempScore
end -- if LastBest
if TempScore>ACS4 then
save.Quicksave(4) -- Set best cluster result for current cluster
ACS4=TempScore
end -- if TempScore
if TempScore>BestScore then
-- recentbest.Save()
BestScore=TempScore
print("New best total score: ",BestScore)
BSChange=true
end -- if
end -- function
function PullDownEqualResults()
print("Checking for double results")
-- Compare all cluster points with next in list
-- If next has the same score, move this cluster to end.
local kEnd=HerdSize-1
local k
for k=1,kEnd do
while ClusterScore[ClusterPointer[k]]==ClusterScore[ClusterPointer[k+1]] do
-- If next has the same score
ClusterScore[ClusterPointer[k+1]]=ClusterScore[ClusterPointer[HerdSize]]-1
-- make its score more worse then the baddest cluster
SortHerd() -- so it is placed at the end when sorting by score
end -- while ClusterScore
end -- k
end -- function
function InitializeClusterData()
ClusterScore={}
ClusterType={}
ClusterDrift={}
ClusterSegA={}
ClusterSegB={}
ClusterLength={}
ClusterStrength={}
ClusterFreeze={}
ClusterPointer={}
local k
for k=1,HerdSize do
ClusterPointer[k]=k
end -- k
end -- function
function PrintStartingTests()
print()
local OS=" "
if Runs==1 then
OS=OS.."1 run"
else
if Runs>1 then
OS=OS..Runs
else
OS=OS.."infinite"
end -- if
OS=OS.." runs"
end -- if
OS="Starting cluster tests for "..OS.."."
print(OS)
end -- function
function select_close_ligand()
local LigandSegment=NumSegs+1
local k
for k=1,NumSegs do
if structure.GetDistance(k,LigandSegment)<=MutateLigandDistance then
selection.Select(k)
-- print(k," is close enough to ligand for mutating.")
end -- if get_segment_distance
end -- k loop
end -- function
function SetRebuildWorst()
-- Acitvate rebuild worst if forced or condidtions are met
if RebuildForce==nil then
--[[
if BSChange then
RebuildWorst=false
else
RebuildWorst=true
end -- if BSChange
]]--
RebuildWorst=BSValueCheck()
else
RebuildWorst=RebuildForce
end -- if RebuildForce
end -- function
function BSValueCheck()
-- Returns true if current generation's best cluster didn't do much
local ChangeTooSmall=false
if BSValue>=0 then -- If last generation's best cluster score is better than at generation start
if BSValue<SCPT then -- but below limit
ChangeTooSmall=true
end -- if BSValue
else -- if BSValue<0 -- If last generation's best cluster score is not better than at generation start
if BSValue>SCNT then -- but above limit
ChangeTooSmall=true
end -- if BSValue
end -- if BSValue
return ChangeTooSmall
end -- function
function SetBreedFirstCurrent(BreedFirstCurrent)
-- Changes first cluster to test if forced or conditions are met
local BreedFirstCurrent=BreedFirstCurrent
if BreedFirstCurrentForce==nil then -- If we don't force BreedFirstCurrent do a special value
if (LastBest==false) and (RecentUse==false) then -- if we load initial state
BreedFirstCurrent=BreedFirst
else -- If we don't load initial state
if BSChange or BSValueCheck()==false then -- If there was a considerable change
BreedFirstCurrent=1 -- check all clusters again in next generation
else -- If there was no considerable change
BreedFirstCurrent=BreedFirstCurrent+1 -- increase start cluster for this generation (don't test best again)
if BreedFirstCurrent>BreedFirst then BreedFirstCurrent=BreedFirst end -- but not further than BreedFirst
end -- if BSChange
end -- if LastBest==false and RecentUse=false then
else -- if BreedFirstCurrentForce not nil
BreedFirstCurrent=BreedFirstCurrentForce
end -- if BreedFirstCurrentForce
return BreedFirstCurrent
end -- function
function GAB()
-- save.Quickload(1): Puzzle state at GAB Start
-- save.Quickload(2): Puzzle state at generation start
-- save.Quickload(3): Puzzle state at fuse start
-- save.Quickload(4): Best result for current cluster
-- save.Quickload(5): Best result for current generation
print("Starting GAB-III.")
print ("Minimum allowed bandlength: ",MinBL_Game)
print ("Maximum allowed bandlength: ",MaxBL_Game)
print ("Random seed is: ",RNDseed)
math.randomseed(RNDseed) -- initialize random seed
UseList=CheckUselist(UseList)
shuffle2(ShuffleProb) -- Shuffle UseList and reset pointer
InitializeClusterData()
FillHerd(1,HerdSize,1) -- From 1 to HerdSize
-- ShowHerd()
band.DeleteAll() -- clean bands
freeze.UnfreezeAll() -- and freezes
behavior.SetClashImportance(1)
selection.DeselectAll()
recentbest.Save()
save.Quicksave(1) -- Save initial puzzle state
BestScore=current.GetEnergyScore() -- initialize best score (ACS1)
PrintStartingTests(Runs)
local BreedFirstCurrent=1
CRun=0 -- set CRun to 0, counting up after each cluster generation
repeat
CRun=CRun+1 -- Increase current run value
if CRun>1 then -- If this is not the first run
if RecentHybrid>0 then -- If RecentHybrid>0, tweak RecentUse
if (CRun-1)%RecentHybrid==0 then -- Each RecentHybrid runs
RecentUse=true -- use recent best
else -- If not
RecentUse=false -- don't use recent best
end -- if CRun
end -- if RecentHybrid
if LastBest then
print("Loaded last generation's best.")
save.Quickload(5) -- Load best cluster result of last generation
else -- If Lastbest is false
if RecentUse then
print("Loaded recent best.")
recentbest.Restore() -- Load best result so far
else
print("Loaded initial state.")
save.Quickload(1) -- Load initial state
end -- if RecentUse
end -- if LastBest
SetRebuildWorst()
BreedFirstCurrent=SetBreedFirstCurrent(BreedFirstCurrent)
end -- if CRun
if RebuildWorstGen then -- If rebuild is requestet at geneartion start
rebuild_worst() -- do it
end
BSChange=false -- Reset improvement information
save.Quicksave(2) -- Set this state as start state for current generation
ACS2=current.GetEnergyScore() -- Get score at start for this generation
print()
print("Score now: ",ACS2)
local k2 -- Cluster counter (cluster number not in braces)
for k2=BreedFirstCurrent,HerdSize do
local k=ClusterPointer[k2] -- fetch slot number(adress) resulting from k2 (cluster number in braces)
print()
local OS="Gen.:"..CRun.." cluster:"..k2.."("..k..")/"..HerdSize.." drift:"..ClusterDrift[k].." type:"..ClusterType[k]
print(OS)
if k2>BreedFirstCurrent then
-- print("Loaded Quicksave 1.")
save.Quickload(2)
end -- if k2
band.DeleteAll() -- remove all bands, because recent best can contain some
freeze.UnfreezeAll() -- and freezing to apply others
if ClusterBands>0 then
local l -- Band counter
for l=1,ClusterBands do
if ClusterSegA[k][l]>0 then
band.AddBetweenSegments(ClusterSegA[k][l],ClusterSegB[k][l])
local TempBandCount=band.GetCount()
-- fetch current band number index to make setting its length and strength possible
local Length=LengthWithFactor(ClusterSegA[k][l],ClusterSegB[k][l],ClusterLength[k][l])
if (Mimic==false or l~=1) then -- If this is not a Mimic band
if Length>structure.GetDistance(ClusterSegA[k][l],ClusterSegB[k][l]) then -- and random generated target length is bigger than actual distance
Length=Length+BLchangeUpPush -- add push up value
-- print("Pushed up.")
if Length>MaxBL_Game then
Length=MaxBL_Game
end -- if Length>MaxBL_Game
else -- if random generated target length is smaller or equal than actual distance
Length=Length+BLchangeDownPush -- add push down value
-- print("Pushed down.")
if Length<MinBL_Game then
Length=MinBL_Game
end -- if Length<MinBL_Game
end -- if Length>structure.GetDistance
end -- if (Mimic==false or l~=1)
local Length1R=CutOff(ClusterLength[k][l],3) -- Cut after 3 decimal digits before displaying
local Length2R=CutOff(Length,3) -- Cut after 3 decimal digits before displaying
local OS="Band "..l..": "..ClusterSegA[k][l]..":"..ClusterSegB[k][l]
OS=OS.." L:"..Length1R.."="..Length2R
if ShowBF then
OS=OS.." S:"..ClusterStrength[k][l]
end -- if
print(OS)
band.SetGoalLength(TempBandCount,Length) -- Set current band length
band.SetStrength(TempBandCount,ClusterStrength[k][l]) -- Set current band strength
else -- if ClusterSegA[k][l] is <0, which means band is deactivated
if ShowBF then
local OS="Band "..l..": off"
print(OS)
end -- if
end -- if ClusterSegA
end -- l loop
end -- if ClusterBands
if RebuildWorstGen==false then rebuild_worst() end
selection.DeselectAll() -- Before freezing, so we can select segments to freeze
if ClusterFreezes>0 then -- if there are segments to freeze
local l -- Freeze counter
for l=1,ClusterFreezes do -- select all segments to freeze
if ClusterFreeze[k][l]>0 then
if ShowBF then
local OS="Freeze "..l..": "..ClusterFreeze[k][l]
print(OS)
end -- if
selection.Select(ClusterFreeze[k][l])
else
if ShowBF then
local OS="Freeze "..l..": off"
print(OS)
end -- if
end -- if ClusterFreez
end -- l loop
end -- if ClusterFreezes
if puzzle_is_ligand then -- if this is a ligand puzzle
selection.Select(NumSegs+1) -- select ligand for freezing
freeze.FreezeSelected(true,true) -- freeze backbone and sidechains
else -- if this is not a ligand puzzle
if ClusterFreezes>0 then -- but there are segments to freeze
freeze.FreezeSelected(true,false) -- freeze backbone only
end -- if ClusterFreezes
end -- if puzzle_is_ligand
if ConstantBands==true then
add_constant_bands() -- You can add some bands or freezes in this routine, which should appear each try
end -- if
print("Pulling...")
behavior.SetClashImportance(CI_pull)
selection.SelectAll () -- Select all segments to wiggle
structure.WiggleAll(PWiggles) -- Just a small pull
band.DeleteAll() -- clean up bands before saving
freeze.UnfreezeAll() -- clean up freezes before saving
if LastBest then -- If LastBest Mode
if k2==BreedFirstCurrent then -- and this is the first try of current generation
ACS5=current.GetEnergyScore()
-- initialize current generation best score
end -- if k2
end -- if LastBest
save.Quicksave(4) -- Initialize this state as reference for current cluster best score
ACS4=current.GetEnergyScore() -- and initialize current cluster best score
BestScoreCheck() -- raise BestScore ACS4, if result is better
xMutate(Mutating1) -- including BestScoreCheck
Release() -- including Fuse and BestScoreCheck
xMutate(Mutating2) -- including BestScoreCheck
ClusterScore[k]=ACS4-ACS2 -- Store best score minus start score for this try as cluster score
print("Difference to start: ",ClusterScore[k])
end -- k loop, take next cluster
print()
print("Sorting Cluster list by score")
SortHerd() -- Sort herd by score difference
ShowScoreList()
BSValue=ClusterScore[ClusterPointer[1]] -- Fetch best score difference
PullDownEqualResults() -- Move duplicate results to end of herd
Breed(CRun+1) -- Range: BreedFirst to BreedLast
if BSValueCheck() or BSValue<=0 then -- If generation's best cluster has low or negative score difference
print("No or few improvement for this generation.")
if HerdSize<IncHerdSize then -- and herd size increasing is allowed
print("Increasing herd size.")
local k
for k=1,2 do -- Increase herd size by 2 individuums
HerdSize=HerdSize+1 -- Increase herd size
ClusterPointer[HerdSize]=HerdSize -- and number of cluster save slots; set them to its own value
end -- k loop
BreedLast=BreedLast+1 -- Increase breed slots by 1
end -- if incHerdSize
end -- if BSChange
FillHerd(BreedLast+1,HerdSize,CRun+1)
-- generate random clustes behind breeded ones
-- ShowHerdShort()
until CRun==Runs
end -- function GAB
function FactorByLength(SegA,SegB)
-- As mimic doesn't use random band lengths (with random factor) but tries to imitate current puzzle state,
-- we have not to generate but to calculate the factor value
-- This is the opposite of function LengthWithFactor
local Min=SpatLimit(SegA,SegB,BLchangeDown)
local Max=SpatLimit(SegA,SegB,BLchangeUp)
return (structure.GetDistance(SegA,SegB)-Min)/(Max-Min)
end -- function
function LengthWithFactor(SegA,SegB,Factor)
-- Apply length factor to range between minimum allowed band length and maximum allowed band length
-- Factor=0 results minimum allowed band length
-- Factor=1 results maximum allowed band length
local Min=SpatLimit(SegA,SegB,BLchangeDown)
local Max=SpatLimit(SegA,SegB,BLchangeUp)
-- local OS="Length range without push: "..CutOff(Min,3)..":"..CutOff(Max,3)
-- print(OS)
return Factor*(Max-Min)+Min
end -- function
function SpatLimit(SegA,SegB,Distance)
local Distance2=structure.GetDistance(SegA,SegB)+Distance
if Distance2<MinBL_Game then
Distance2=MinBL_Game
elseif Distance2>MaxBL_Game then
Distance2=MaxBL_Game
end -- if Distance2
return Distance2
end -- funtction
function index_distance(SegA,SegB)
-- Fetch segment index distance
return math.abs(math.abs(SegA)-SegB)
end -- function
function Release()
if Releasing then
-- print("Score after pulling: ",ACS4)
print("Releasing...")
save.Quickload(4) -- load best cluster result so far
selection.SelectAll ()
if Fuse then
PinkFuse() -- save.Quicksave(4), raise BestScore and ACS4, if result is better
else
behavior.SetClashImportance(1)
structure.ShakeSidechainsAll(1)
structure.WiggleAll(12)
BestScoreCheck() -- save.Quicksave(4), raise BestScore and ACS4, if result is better
end -- if fuse
end -- if Releasing
end -- function
function rebuild_worst()
if RebuildWorst then
print("Rebuilding worst...")
selection.DeselectAll()
local WorstScore=current.GetSegmentEnergyScore(1)
local WorstIndex=1
local k
for k=2,NumSegs do
local SegmentScore=current.GetSegmentEnergyScore(k)
if SegmentScore<WorstScore then
WorstScore=SegmentScore
WorstIndex=k
end -- if SegmentScore
end -- k
if (WorstIndex-RebuildRange)<1 then
WorstIndex=1+RebuildRange
elseif (WorstIndex+RebuildRange)>NumSegs then
WorstIndex=NumSegs-RebuildRange
end -- if
local SegA=WorstIndex-RebuildRange
local SegB=WorstIndex+RebuildRange
for k=SegA,SegB do
selection.Select(k)
end -- k loop
structure.RebuildSelected(RebuildIter)
selection.DeselectAll()
end -- if RebuildWorst
end -- function
function xMutate(Mutating)
if Mutating then
save.Quickload(4) -- load best result for this cluster so far
print("Mutating...")
if puzzle_is_ligand then
selection.DeselectAll() -- clear selection
select_close_ligand() -- select all segments close as MutateLigandDistance or less to ligand
else
selection.SelectAll ()
end -- if puzzle_is_ligand
do_mutate(1)
BestScoreCheck() -- Check if it made an improvement
end -- if Mutating
end -- function
function detect_ligand(flag)
--[[
ligand puzzle detection
normally, segments have a secondary structure of "E", "H" or "L"
and they always have a spatial distance of about 3.75 to 3.85 to their next index neighbour.
a ligand is more far away.
this function should respond true if this is a ligand puzzle, and false if it is not.
if flag is nil, ligand auto-detection is enabled, distance of last two segments is checked
if flag is not nil, ligand auto-detection is disabled, result is flag
It also returns the last segment index which is no ligand
]]--
local flag=flag
local LastPos=structure.GetCount() -- fetch very last segment index number
if flag==nil then -- Only if flag is nil, detect if there is a ligand and change flag
print("Detecting if there is a ligand.")
local ss=structure.GetSecondaryStructure(LastPos)
flag=not(ss=="L" or ss=="H" or ss=="E" )
-- if last segment's ss is neither "l" nor "h" nor "e"
flag=flag or (structure.GetDistance(LastPos-1,LastPos)>=3.9)
-- or distance to second last segment is bigger or equal than 3.9
end -- if
local OS="This should be "
if flag then
OS=OS.."a"
LastPos=LastPos-1
else
OS=OS.."no"
end -- if flag
OS=OS.." ligand puzzle."
print(OS)
return flag,LastPos
end -- function
function create_UseList(flag)
-- Creates segment use list depending on which puzzle-type is there
local flag=flag
UseList={}
-- Initialize list of segments to use
-- UseList extensions (true) are applied to list, so multiple selected segments will appear more often
if flag==false then -- If this is no ligand puzzle
UseList=UseSegIRange(UseList,1,NumSegs,1,true)
-- Set every segment index from 1 to puzzle size as bandable
UseList=Use_distance(UseList,0,8,10,1200,false)
-- Consider all segments which have min 10 to max 1200 neighbours
-- over a distance of 0 to 8 as non-solitary and remove them from list
UseList=UseSegIRange(UseList,1,NumSegs,1,true)
-- Add a complete segment set again to make sure that distance check didn't erase all
UseList=Use_ss(UseList,"L",true)
-- Add segments with this secondary structure
-- "L"=loop
-- "H"=helix
-- "E"=sheet
-- UseList=UseSegIRange(UseList,1,NumSegs,2,false)
-- Set every 2nd segment index between 1 to puzzle size as not bandable, example
-- UseList=UseSegIValues(UseList,{1;3;9},true)
-- Include these single segments as bandable, example
-- UseList=UseSegIValues(UseList,{2;5;10},false)
-- Exclude these single segments as bandable, example
-- UseList=Use_aa(UseList,{"g";"a"},true)
-- Set this amino acid as bandable, example
else -- If this is a ligand puzzle
UseList=Use_close_ligand(UseList,20,true)
-- Set segments which have a maximum spatial distance of 20 to ligand as bandable
end -- if flag
end -- function
function LastBandLengthStrength(Length,Strength)
-- sets length and strength of the very last band to default values
local TempBandCount=band.GetCount()
band.SetGoalLength(TempBandCount,Length)
band.SetStrength(TempBandCount,Strength)
end -- function
function add_constant_bands()
-- Add some constant bands to fix parts of the puzzle here
-- and/or freeze some parts
band.AddBetweenSegments(40,15)
LastBandLengthStrength(5,0.1)
--[[
selection.DeselectAll()
for k=10,11 do
selection.Select(k)
end -- k
freeze.FreezeSelected(true,false)
]]--
end -- function
Runs=0
-- Number of runs (generations), integer value
-- Set to <1 to run infinitely
puzzle_is_ligand,NumSegs=detect_ligand()
-- puzzle_is_ligand: Ligand flag, boolean value
-- true for ligand puzzle
-- false for non-ligand-puzzle
-- NumSegs: Last segment index which is no ligand, integer value
-- detect_ligand(): Ligand auto detection.
-- If it fails, use detect_ligand(true) to declare that this is a ligand puzzle
-- and detect_ligand(false) to declare that this is a no ligand puzzle
create_UseList(puzzle_is_ligand)
-- Initialize segment working list
-- Depending on puzzle type
-- Advanced Users: see function create_UseList for description of uselist creating features
MID_Game=3
-- Minimum index distance of segment indices, integer value >=2
-- As the game doesn't allow banding the segments with themselves or the nearest neighbour,
-- This value is needed to prevent game errors,
-- but you can also use it to prevent sharp backbone turns.
MinBL_Game=3.8
-- Minimum band length limiter, float value >=0 and <=MaxBL_Game
-- Opposite of MaxBL_Game
-- Prevents bands getting too short
MaxBL_Game=10000
-- Maximum band length limiter, float value <=10000 and >=MinBL_Game
-- In rough, values about 20 or lower tend to compress the puzzle, values above 20 allow decompressing (stretching)
-- Maximum expedient value for a puzzle is about (number of segments-1)*3.8,
-- which would stretch the region between connected segments completely out.
BLchangeDown=-3.8*2
-- Maximum band change down, float value <=0
-- Generated bands have a minimum length of [current segment distance]+BLchangeDown+BLchangeDownPush
BLchangeDownPush=-3.8/2
-- Value added to BLchangeDown, float value <=0
-- This value is added to non-mimic bands if they are shorter or equal current segment distance to guarantee a certain change in length
BLchangeUp=3.8*1.5
-- Maximum band change up, float value >=0
-- Generated bands have a maximum length of [current segment distance]+BLchangeUp+BLchangeUpPush
BLchangeUpPush=3.8/2
-- Value added to BLchangeUp, float value >=0
-- This value is added to non-mimic bands if they are longer than current segment distance to guarantee a certain change in length
BreedFirst=3
-- First cluster to change by breeding, integer value
-- All clusters before this index will be kept as good solution and as potential parents for breeding new solutions
-- Setting this to 1 is not a good idea, because you will loose good clusters as breeding parents (some kind of incest), but should work, too
BreedLast=5
-- Last cluster to change by breeding, integer value >=BreedFirst and <=HerdSize
-- All clusters after this index to HerdSize will be generated randomly
-- This value will be increased if no better solution was found
HerdSize=8
-- Number of clusters, integer value >=BreedLast
-- This value will be increased if no better solution was found
IncHerdSize=16
-- Increase herdsize, integer value
-- Allows increasing the herdsize if no better solution was found (will generate more clusters)
-- if >HerdSize, allow increasing herdsize until this amount
-- if <=HerdSize, no increasing
ShowBF=true
-- Shows band and freeze details, boolean value
-- if true (default), segments which are banded or frozen are shown by text
ClusterBands=4
-- Maximum Bands to create per cluster, integer value >=0
ClusterFreezes=3
-- Maximum segments to freeze by random, integer value >=0
MinBS=0.8
-- Minimum strength per (random chosen) band, float value
-- Use decimal number between .1 and 10
MaxBS=1.2
-- Minimum strength per (random chosen) band, float value
-- Use decimal number between .1 and 10 and >=MinBS
Shakes=1
-- Number of iterations for fuse-shake, integer value
Wiggles=4
-- Number of iterations for fuse-wiggle, integer value
PWiggles=1
-- Number of iterations for pulling, integer value
CI_pull=1
-- Clashing importance for pulling, float value >=0 and <=1
-- Default is 1
-- Reduce this to make pulling more drastic
Releasing=true
-- Release flag, boolean value
-- true (default)= After pulling, releasing is performed.
-- false= releasing is skipped (and Fuse, too)
Fuse=true
-- Fuse flag, boolean value
-- true (default)= After pulling, Fuse (requires Releasing=true) is performed.
-- false= after pulling, just a shake(1) and wiggle_all(12) with CI=1 is performed
MutateLigandDistance=15
-- Radius length for selection sphere around ligand, where mutating is performed.
-- Selects only segments which are this or more close to the ligand for mutating.
ScoreThreshold=0.5
-- Threshold value for shake and wiggles, float value >=0
-- This won't repeat some w/s, if absolute score change per itertation is below this
MultiCrossOver=true
-- Multi crossover flag, boolean value
-- false (default)= One crossover point for each breeding is generated.
-- true= Each gene can be from mom or dad
Parent1isRoulette=false
-- Roulette flag for breeding parent 1, boolean value
-- if false (default), parent is cluster with best score and downwards
-- use this to force good clusters for breeding
-- if true, parent is chosen by fitness roulette
Parent2isRoulette=true
-- Roulette flag for breeding parent 2, boolean value
-- if true (default), parent is chosen by fitness roulette
-- if false, parent is next to parent 1
-- use this to force good clusters for breeding
DriftProb=0.3
-- band drift (cluster mutation) probability, float value >=0 and <1
-- Probability, if drift is added or not
InvBProb=0.2
-- Inverting (deactivating/reactivating) probability for bands, float value >=0 and <1
-- Makes inversion happen depending on this probability value
InvFProb=0.3
-- Inverting (deactivating/reactivating) probability for freezes, float value >=0 and <1
-- Makes inversion happen depending on this probability value
ShuffleProb=0.9
-- Shuffle rate, float value >=0 and <=1
-- Used to scramble random segment list, where bands are applied
-- 0 lets the list be as it is
-- 1 swaps all list entries with a random one behind them in list
RecentUse=true
-- recent best flag, boolean value
-- true= recent best is used for each generation
-- false= initial state is used for each generation
-- prevents getting stuck on local maximum (recommended for endgame)
-- If using RecentHybrid or LastBest, this value will be set automatically
RecentHybrid=0
-- Hybrid behaviour between RecentUse=false/true, integer value >=0
-- If ==0, regular behaviour as set in RecentUse
-- If >0, recent best is used only all RecentHybrid's generations
-- (counting from generation 2)
-- 1 would have the same effect as RecentUse=true
-- I recommend values>=2 when using it
LastBest=false
-- Flag for using best cluster of current generation, boolean value
-- Default is false
-- If true, neither recent best nor initial puzzle state are loaded, but best result of current generation,
-- allowing unimproving the puzzle to get out of local maximum
-- Try this, you are really stuck on a puzzle.
BreedFirstCurrentForce=nil
-- Loop start cluster force flag, integer value
-- If nil (default), best clusters will only be tested again
-- if there was an improvement in last generation
-- If >0, start at this cluster after first generation
-- If 1, test all clusters again (also good ones)
-- If BreedFirst, test only new generated clusters
SCPT=1
-- Score change positive tolerance, float value >=0
-- Activates rebuild (if allowed)
-- if current generation start score and best cluster score difference
-- is below this positive value (not good enough)
SCNT=-10
-- Score change negative tolerance, float value <=0
-- Activates rebuild (if allowed)
-- if current generation start score and best cluster score difference
-- is above this negative value (not bad enough)
RebuildWorstGen=true
-- Rebuild worst at generation start flag, boolean value
-- If true, rebuild worst is executed once for current generation
-- If false, rebuild worst is executed for each cluster (not recommended)
-- If nil, rebuild is never executed.
RebuildWorst=false
-- Rebuild flag at script start, boolean value
-- If true,
-- rebuild is executed in first generation
RebuildForce=nil
-- Force RebuildWorst, boolean value
-- Sets how RebuildWorst is changed.
-- If nil (default), worst segment is only rebuilt
-- if there was no improvement in last generation
-- If true, worst segment is always rebuilt before pulling
-- If false, worst segment is never rebuilt before pulling
RebuildRange=2
-- Rebuild range, integer value>=1
-- For example, if 1 and worst segment is 3, segments from 2 to 4 are rebuilt.
-- if 2 and worst segment is 3, segments from 1 to 5 are rebuilt.
RebuildIter=1
-- Iterations for rebuild worst, integer value>0
Mutating1=puzzle_is_ligand
-- if true, puzzle segment mutating after pulling is performed
Mutating2=false
-- if true, puzzle segment mutating after releasing is performed
ConstantBands=false
-- Add constant bands flag, boolean value
-- if true, adds user-defined bands and freezes of add_constant_bands function above
-- before regular test-cluster is applied
Mimic=false
-- Mimic flag, boolean value
-- if true, first cluster tries to imitate initial puzzle state
RNDseed=os.time()+recipe.GetRandomSeed()
-- Random Seed
-- Here, the initial value is set to system time plus the game's random seed, so we get different random numbers
-- for each PC and each time the script is started.
-- If you want the same numbers (for testing purposes/results), take an arbitrary constant value.
GAB()