Code
--[[
0. general:
GAB+EO Remix V7.900 by Crashguard303
Copyright (C) 2013 Crashguard303 <http://fold.it/portal/user/119022>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
1. introduction:
1.1 inspired by:
Cartoon Villain (GA idea & first random band script of this kind)
Rav3n_pl (OO parameter/variable handling)
jeff101 (EO & input-boxes idea)
1.2 idea:
-- This is a further development of my last GAB+EO
which i wrote from scratch to improve table handling and modularity
(it was a work of more than one month!)
-- this script doesn't simply band to random segs,
it works with a list of "cardinal" origin segs,
which are detected and/or set by selection interface (blue) before.
-- origin segs always stay the same (they are just turned off or on),
-- target segs do vary
-> this will avoid double banding
-- new idea is to keep the number of random band parameters as small as they can be:
the script works with 2 fixed band strengths (as the length is already random)
-- primary bands: do seg movement in space or relative to any other seg (depending on band mode)
-- secondary bands: do seg movement between proximate segs.
they can be used to stabilize or move internal puzzle structure,
so their job is supporting the primary bands with their major task
-- as i didn't want to overload the starting sequence
with too many input boxes and selectable vals (more than 7 boxes at the moment!).
-> you can set all vals (even the other) in the declaration section.
1.3 features:
-- cardinal seg auto-detection: (see 3.3.2.3)
-- secondary-structure changes, amino-acids, etc will create a uselist
++ selecting segs via game interface (blue) is possible, too
-- 3 breeding modes: (see 3.2)
GA
EO1
EO2
-- 2 gene modes: (see 3.2.2.2)
"dual"
"poly"
-- 6 banding modes (see 3.3)
"space"
"center"
"user"
"furthest"
"random1"
"random2"
++ further settings for each
1.4 known bugs/problems:
1.4.1 ligand puzzles:
-- are supported by script, but i couldn't test if it works properly!
-> to band ligand:
use band to "user", then slide to last segment!
2. abbreviations/terms you might find:
2.1 structure:
gen: gen
clu: cluster
seg: segment
dst: distance
len: length
str: strength
itr: iterations
2.2 addressing:
list: table
idx: index (numerical)
key: key (alphanumerical)
val: value
2.3 vals:
prm: parameter (argument)
cur: current (measured)
max: maximum
min: minimum
cng: change (to add)
res: result (calculated)
rnd: random (genrated)
2.4 other:
gui: frontend (dialog input box)
3. features:
3.1 terms and herd generating:
This script (as GAB before) applies and tests combinations of random bands.
one band combination set is named cluster (clu) here
(GA-term: individuum).
a testing row of different clus is a generation (gen)
(by default, 1 generation consists of 8 clus = herd.size)
when one generation has tested, it is evaluated.
each clu is measured by it's score gain,
gen will be sorted by clu's score gain, best on top.
3.2 analyzing and breeding:
selection of of partners
and breeding them (mutating, too)
is the heart of a genetic algorithm !!!
3.2.1 analyzing:
after testing each gen,
clus are sorted by the score gain they made
-> first clu is this with best gain,
-> last clu is this with worst gain,
3.2.2 breeding:
3.2.2.1 selection via EO:
EO mode decides what happens with mediocre clus:
-- EO_mode.cur=0: mediocre clus are breeded (only this is GA)
-- EO_mode.cur=1: mediocre clus are considered as best (soft EO)
-- EO_mode.cur=2: mediocre clus are considered as worst (hard/real EO)
best clus (always):
-- are kept (default: cluster 1 and 2 = 1...herd.breed.first)
and "breeded" = mixed to new ones.
mediocre clus (EO_mode.cur=0):
-- are overwritten by breeded ones
(default: clu 3...5 = herd.breed.first to herd.breed.last)
worst clus (always):
-- are replaced by new random ones
(default: clu 6...8 = herd.breed.last...herd.size)
3.2.2.2 sex (breed mode):
-- imagine the clus as snails with both genders
-- "poly": each single gene can be from any clu by roulette!
-- "dual": 1 kid has only 2 parents (default):
-> dad is 1st breeding partner
-> mom is 2nd breeding partner
-- breeding partners can be chosen by:
-> their rank: 1st has best score gain
-> roulette: 1st appears most time, but all possible
-> if you breed 3 kids,
roulette combinations are:
-- by default:
herd.breed.roulette.dad==false
herd.breed.roulette.mom==true
-> kid#1: dad=clu#1 & mom#1=roulette
-> kid#2: dad=clu#2 & mom#2=roulette
-> kid#3: dad=clu#3 & mom#3=roulette
-- other setting:
herd.breed.roulette.dad==false
herd.breed.roulette.mom==false
-> kid#1: dad=clu#1 & mom#1=dad#1+1=clu#2
-> kid#2: dad=clu#2 & mom#2=dad#2+1=clu#3
-> kid#3: dad=clu#3 & mom#3=dad#3+1=clu#4
-- other setting:
herd.breed.roulette.dad==true
herd.breed.roulette.mom==false
-> kid#1: dad#1=roulette & mom#1=dad#1+1
-> kid#2: dad#2=roulette & mom#2=dad#2+1
-> kid#3: dad#3=roulette & mom#3=dad#3+1
-- other setting:
herd.breed.roulette.dad==true
herd.breed.roulette.mom==true
-> kid#1: dad#1=roulette & mom#1=roulette
-> kid#2: dad#2=roulette & mom#2=roulette
-> kid#3: dad#3=roulette & mom#3=roulette
3.3 applying bands to segments:
3.3.1 types of connections:
The clus which are tested will be not only from seg to seg anymore (like in old GAB)
but also from seg to free space (if using clu.band_to.val="space"),
allowing more freedom of direction.
3.3.2 origin segs and target segs:
3.3.2.1 origin segs:
-- are always the same
-- i did this to prevent double banding, simplifiy breeding
and keeping the clu's gene-length homogenous
-> you can't crossbreed a horse with a zebra because their DNAs have different lengths
-> in real life, dna also doesn't change length or genes get other tasks,
genes remain and are just active or inactive !!
-- segs which can be banded and their banding order
are stored in a uselist (selection)
-> but you can turn some off and change their activity by mutation
-> you can set them in the seg_use table declaration
3.3.2.2 target segs:
-- can be different, depending on mode clu.band_to.val:
-- origin segs can be banded to
-> "space": no seg, but to "air" (more freedom of movement,
but no reference to other segs anymore)
-> "center": calculated seg (real center seg)
-> "user": selected seg (pseudo "center" seg internally)
-> "furthest": calculated seg furthest to origin seg
-> "random1": another seg in seg_use list (given in table)
-> "random2": rnd seg in puzzle (all available)
3.3.2.3 if no list is given (in declaration section):
-- if you
-- neither change the seg_use list to your needs or
-- nor select any seg by game interface:
-> by default, only these segs are banded,
where changes in secondary structures do occur
and which do have special residues (default: Glycine)
-> key idea of this new script !!!
3.3.3 band strength:
-- is constant for all bands now (unlike to old GAB),
as the length is already set by random.
-> but you can set the global band strength
for primary and secondary bands (not always used)
--]]
function random_seed_set_val(random_seed)
local random_seed= random_seed
if (random_seed==nil) or (random_seed=="") then
random_seed=os.time()+recipe.GetRandomSeed()
else
random_seed= tonumber(random_seed)
end -- if random_seed
print('Random Seed: '..random_seed)
math.randomseed(random_seed) -- initialize random seed
return random_seed
end -- function randomseed
function above0(x)
if x>0 then
return 1
else -- if x<=0
return -1
end -- if x
end -- function above0
function crop_digits(x,n)
local y=x
y=y*10^n
y=math.floor(y)
y=y*10^(-n)
return y
end -- function crop_digits
function range_wrap(x,min,max,offset)
--[[
prevent x getting higher than max
or lower than min
if x gets out of range, it is set to the other boundary+it's exceeding amount
if x>max, val will be >min, if offset is 0 and float vals are used
or =min, if offset is 1 and integer vals are used
if x<min, val will be <max, if offset is 0 and float vals are used
or =min, if offset is 1 and integer vals are used
example:
range_wrap(1.1,0,1,0) will return 0.1 (x is by .1 >1, so it is set next to 0+.1)
range_wrap(-.1,0,1,0) will return 0.9 (x is by .1 <0, so it is set next to 1-.1)
range_wrap(11,0,10,0) will return 1 (x is by 1 >10, so it is set to 0+1)
range_wrap(-1,0,10,0) will return 9 (x is by 1 <10, so it is set to 10-1)
range_wrap(11,0,10,1) will return 0 (x is by 1 >10, so it is set to 0+1-1)
range_wrap(-1,0,10,1) will return 10 (x is by 1 < 0, so it is set to 10-1+1)
--]]
local y=x+0
if y>max then
y=min+(y-max)-offset
elseif y<min then
y=max+(y-min)+offset
end -- if y
return y
end -- function range_wrap
function range_clamp(x,min,max)
--[[
prevent x getting higher than max by setting it to max
or lower than min by setting it to min
--]]
local y=x+0
if y>max then
y=max
elseif y<min then
y=min
end -- if y
return y
end -- range_clamp()
function random_flag1(prob)
--[[
return true or false randomly, depending on prob(ability)
probability should be a float val between 0 and 1 as
vals <0 have the same effect as =0 and
vals >1 have the same effect as =1
returns true, if
prob=0: never
prob=0.1: sometimes
prob=0.5: quite as often as false
prob=0.9: most times
prob=1: always
calculation example:
-- if you want it to respond true
-- in 1 of 10 cases
-- use 1/10= 0.1
--]]
return (math.random()<prob)
end -- function random_flag1
function random_flag2(prob,invert,bool)
-- return val like random_flag1(), but
-- invert=true inverts val
-- bool=false converts boolean vals
-- true -> 1
-- false -> 0
local val=random_flag1(prob)
if invert then
val= not(val)
end -- if invert
if bool==false then
if val then
val= 1
else
val= 0
end -- if val
end -- if bool
return val
end -- function random_flag2
function list_show_deep(list,text)
-- full key and val as string - advanced
-- indexing: pairs
if text==nil then
text=tostring(list)
end -- if text
local k,v
for k,v in pairs(list) do
local ktext
if type(k)=="number" then
ktext= text..'['..tostring(k)..']'
else
ktext= text..'.'..tostring(k)
end -- if type(k)
if type(v)=="table" then
list_show_deep(v,ktext)
else
local vtext
if (type(v)=="number") or (type(v)=="boolean") then
vtext= tostring(v)
else
vtext= '\"'..tostring(v)..'\"'
end -- if type(v)
print(ktext..'='..vtext)
end -- if type(v)
end -- for k
end -- function list_show_deep
function puzzle_ismutable()
local ismutable= false
for i=1,puzzle.seg_count do
if structure.IsMutable(i) then
ismutable= true
break -- for i
end -- if structure.IsMutable
end -- for i
return ismutable
end -- function puzzle_ismutable
function puzzle_isligand()
local isligand
if structure.GetSecondaryStructure(puzzle.seg_count)==not(ss=="L" or ss=="H" or ss=="E" ) then
isligand= true
else
isligand= false
end -- if structure.GetSecondaryStructure
print('Ligand detected: '..tostring(isligand))
return isligand
end -- function puzzle_isligand
function seg_blocks(blocks)
-- create a list of mutlitple segment sections
-- example:
-- blocks={{1;3};{7;10}}
-- results list={1;2;3;7;8;9;10}
local list={}
local i
for i=1,#blocks do
local j
for j=blocks[i][1],blocks[i][2] do
print('Appending Idx:'..(#list+1)..'=Seg:'..j)
table.insert(list,j)
end -- for j
end -- for i
return list
end -- function seg_blocks
function seg_use_list_create(seg_use)
-- table seg_use contains
-- associative keys - their vals are information for creating the:
-- numerical keys - their vals are a list, which puzzle segment indices can be used
local seg_use=seg_use
print('Appending segs with idx given in table...')
seg_use=seg_use_list_append(seg_use,seg_use.include.idx)
if seg_use.include.selected.act then
print('Appending segs depending on selection: '..tostring(seg_use.include.selected.val)..'...')
seg_use=seg_use_list_append(seg_use,seg_list_selected_get(seg_use.include.selected.val))
end -- seg_use.include.selected.act
selection.DeselectAll()
if seg_use.include.frozen.act then
print('Appending segs depending on freeze: backbone: '..tostring(seg_use.include.frozen.backbone)..' sidechain:'..tostring(seg_use.include.frozen.sidechain))
seg_use=seg_use_list_append(seg_use,seg_list_frozen_get(seg_use.include.frozen.backbone,seg_use.include.frozen.sidechain))
end -- seg_use.include.frozen.act
if seg_use.include.ss_cngs then
print('Appending segs, where structure changes do occur...')
seg_use=seg_use_list_append(seg_use,ss_cngs_get_seg_list())
end -- if seg_use.include.ss_cngs
print('Appending segs with ss given in table...')
seg_use=seg_use_list_append(seg_use,seg_list_ss_get(seg_use.include.ss))
print('Appending segs with aa given in table...')
seg_use=seg_use_list_append(seg_use,seg_list_aa_get(seg_use.include.aa))
seg_use=seg_use_filter(seg_use)
-- sort segment list and
-- remove duplicate entries
print('Removing segs which are locked...')
seg_use=seg_use_list_remove(seg_use,seg_list_locked_get())
print('Removing segs with idx given in table...')
seg_use=seg_use_list_remove(seg_use,seg_use.exclude.idx)
if seg_use.exclude.selected.act then
print('Removing segs depending on selection: '..tostring(seg_use.exclude.selected.val)..'...')
seg_use=seg_use_list_remove(seg_use,seg_list_selected_get(seg_use.exclude.selected.val))
end -- seg_use.exclude.selected.act
if seg_use.exclude.frozen.act then
print('Remove segs depending on freeze: backbone: '..tostring(seg_use.exclude.frozen.backbone)..' sidechain:'..tostring(seg_use.exclude.frozen.sidechain))
seg_use=seg_use_list_remove(seg_use,seg_list_frozen_get(seg_use.exclude.frozen.backbone,seg_use.exclude.frozen.sidechain))
end -- seg_use.exclude.frozen
print('Removing segs with ss given in table...')
seg_use=seg_use_list_remove(seg_use,seg_list_ss_get(seg_use.exclude.ss))
print('Removing segs with aa given in table...')
seg_use=seg_use_list_remove(seg_use,seg_list_aa_get(seg_use.exclude.aa))
print('\nScript will use these segs:')
for i=1,#seg_use do
print(i..': '..seg_use[i])
end -- for i
return seg_use
end -- function seg_use_list_create
function seg_use_list_append(use_idx,append_idx)
local use_idx=use_idx
if seg_use.append_all~=false then
seg_use.append_all=true
end -- if seg_use.append_all
-- if seg_use.append_all is not false
-- force it to be true
-- as no other vals are allowed
if seg_use.append_all then
use_idx=seg_use_list_append_all(use_idx,append_idx)
else -- if seg_use.append_all~=true
use_idx=seg_use_list_append_missing(use_idx,append_idx)
end -- if seg_use.append_all
return use_idx
end -- function seg_use_list_append
function seg_use_list_append_all(use_idx,append_idx)
local use_idx=use_idx
local i
for i=1,#append_idx do
-- print('Appending Idx:'..(#use_idx+1)..'=Seg:'..append_idx[i])
table.insert(use_idx,append_idx[i])
end -- for i
return use_idx
end -- function seg_use_list_append_all
function seg_use_list_append_missing(use_idx,append_idx)
local use_idx=use_idx
local i
for i=1,#append_idx do
if val_isintable(use_idx,append_idx[i])==false then
print('Appending Idx:'..(#use_idx+1)..'=Seg:'..append_idx[i])
table.insert(use_idx,append_idx[i])
end -- if val_isintable
end -- for i
return use_idx
end -- function seg_use_list_append_missing
function val_isintable(list,valsearch)
-- return:
-- true: if list does contain valsearch
-- false: if list doesn't contain valsearch
if #list==0 then
return false
else
return key_getbyval(list,valsearch)~=nil
end -- if #list
end -- function val_isintable
function key_getbyval(list,valsearch)
-- check all keys in table list
-- return first found key which has valsearch
local keyres=nil
local key,val
for key,val in pairs(list) do
-- with key loop, cycle through all keys
-- for each key, get its val
if val==valsearch then
-- if val found
keyres= key
-- return it's key
break -- key,val
-- break key loop
end -- if val
end -- key loop
return keyres
end -- function key_getbyval
function seg_use_list_remove(use_idx,remove_idx)
local use_idx=use_idx
local i=1
for i=1,#remove_idx do
local j=1
while j<=#use_idx do
if use_idx[j]==remove_idx[i] then
print('Removing Idx:'..j..'=Seg:'..use_idx[j])
table.remove(use_idx,j)
j=j-1
end -- if use_idx
j=j+1
end -- while j
end -- for i
return use_idx
end -- function seg_use_list_remove
function seg_use_filter(seg_use)
-- sort list by segment idx numbers and
-- remove duplicate entries
local seg_use=seg_use
-- leave seg_use.sort_ascending as it is, as it is allowed to be nil
print('Sorting segs ascending: '..tostring(seg_use.sort_ascending))
if seg_use.remove_double~=false then
seg_use.remove_double=true
end -- if seg_use.remove_double
-- if seg_use.remove_double is not false
-- force it to be true
-- as no other vals are allowed
print('Checking and removing double segs:'..tostring(seg_use.remove_double))
local i=1
while i<=(#seg_use-1) do
local j=i+1
while j<=#seg_use do
if seg_use[i]==seg_use[j] then
-- if two elements in list are equal
if seg_use.remove_double then
-- print('Removing idx:'..j..' = Seg:'..seg_use[j])
table.remove(seg_use,j)
j=j-1
-- as element at position j was removed,
-- another one might have moved up to position j
end -- if seg_use.remove_double
elseif ((seg_use[i]>seg_use[j])==seg_use.sort_ascending) then
-- if they are not equal
-- and they don't have the order as given in seg_use.sort_ascending
-- print('Swapping idx:'..i..'=Seg:'..seg_use[i]..' with idx:'..j..'=Seg:'..seg_use[j])
seg_use[i],seg_use[j]=seg_use[j],seg_use[i]
end -- if seg_use
j=j+1
end -- for j
i=i+1
end -- while i<=(#seg_use-1)
return seg_use
end -- function seg_use_filter
function seg_list_locked_get()
local seg_found={}
local i
for i=1,puzzle.seg_count do
if structure.IsLocked(i) then
table.insert(seg_found,i)
end -- if structure.IsLocked
end -- for i
return seg_found
end -- function seg_list_locked_get
function ss_cngs_get_seg_list()
local seg_found={}
local seg_count2= puzzle.seg_count-1
local i=1
while i<seg_count2 do
local ss1=structure.GetSecondaryStructure(i)
local ss2=structure.GetSecondaryStructure(i+1)
-- check ss of seg with idx i and i+1
if ss1~=ss2 then
-- are ss different?
-- primary, we want to find segs with loop-ss at the end of helix or sheet sections
-- but sometimes helices are directly near sheets
if ss1=="L" then
-- loop found at i? (then other ss is behind i) then
table.insert(seg_found,i)
elseif ss2=="L" then
-- loop found near i? (then other ss is at i)
table.insert(seg_found,(i+1))
i=i+1
elseif ss1=="H" then
-- helix found at i? (then other ss (but no loop) is behind i)
table.insert(seg_found,i)
elseif ss2=="H" then
-- helix found near i? (then other ss (but no loop) is at i)
table.insert(seg_found,(i+1))
i=i+1
end -- if ssx==ss
end -- if ss1~=ss2
i=i+1
end -- while i<seg_count2
return seg_found
end -- function ss_cngs_get_seg_list
function seg_list_selected_get(selected_flag)
local seg_found={}
local i
for i=1,puzzle.seg_count do
if selection.IsSelected(i)==selected_flag then
table.insert(seg_found,i)
end -- if selection.IsSelected
end -- for i
return seg_found
end -- seg_list_selected_get
function seg_list_frozen_get(backbone_flag,sidechain_flag)
local seg_found={}
local i
for i=1,puzzle.seg_count do
local backbone,sidechain=freeze.IsFrozen(i)
if (backbone==backbone_flag) and (sidechain==sidechain_flag) then
table.insert(seg_found,i)
end -- if backbone,sidechain
end -- for i
return seg_found
end -- function seg_list_frozen_get
function ss_cngs_show(structure_cngs_get,freeze_flag1,freeze_flag2)
selection.DeselectAll()
freeze.UnfreezeAll()
print("Found changes:")
local i
for i=1,#seg_use do
-- by variable i, cycle through all segs in list which have been found
print(i..': '..seg_use[i])
-- show seg number
selection.Select(seg_use[i])
-- select seg number
end -- for i
freeze.FreezeSelected(freeze_flag1,freeze_flag2)
-- if flags are set, freeze selected segs, backbone and/or sidechains
selection.DeselectAll()
end -- function ss_cngs_show
function seg_list_ss_get(use_ss)
local seg_found={}
local i
for i=1,#use_ss do
local j
for j=1,puzzle.seg_count do
if structure.GetSecondaryStructure(j)==use_ss[i] then
-- print(j..': '..structure.GetSecondaryStructure(j))
table.insert(seg_found,j)
end -- if structure.GetSecondaryStructure
end -- for j
end -- for i
return seg_found
end -- function seg_list_ss_get
function seg_list_aa_get(use_aa)
local seg_found={}
local i
for i=1,#use_aa do
local j
for j=1,puzzle.seg_count do
if structure.GetAminoAcid(j)==use_aa[i] then
-- print(j..': '..structure.GetAminoAcid(j))
table.insert(seg_found,j)
end -- if structure.GetAminoAcid
end -- for j
end -- for i
return seg_found
end -- function seg_list_aa_get
function seg_center_get()
if (puzzle.seg_center.get_mode=="start") and (puzzle.seg_center.val~=nil) then
-- start center seg is to get and center seg is set
print('Stored center seg: '..puzzle.seg_center.val)
return puzzle.seg_center.val
-- use stored center seg
else
-- start center seg is not to get or center seg is not set
-- current center seg is to get
puzzle.seg_center.val= seg_center_get2()
-- get current center seg
return puzzle.seg_center.val
end -- if puzzle.seg_center.get_mode,puzzle.seg_center.val
end -- function seg_center_get
function seg_center_get2()
--[[
find center seg of puzzle
for each seg, measure it's dst to all other segs
and sum these dsts up
center seg has lowest sum of dsts
--]]
-- print ('Getting center segment.')
local dist_sum={min=math.huge;min_issegidx=0}
local i
for i=1,puzzle.seg_count do
dist_sum.cur=0
local j
for j=1,puzzle.seg_count do
dist_sum.cur=dist_sum.cur+structure.GetDistance(i,j)
end -- for j
-- print('Distance sum for #'..i..' = '..dist_sum.cur)
if dist_sum.cur<dist_sum.min then
dist_sum.min=dist_sum.cur
dist_sum.min_issegidx=i
-- print('Distance sum min hit!')
-- print('Distance sum min: #'..dist_sum.min_issegidx..' = '..dist_sum.min)
end -- if dist_sum.cur
end -- for i
-- print('Distance sum min: #'..dist_sum.min_issegidx..' = '..dist_sum.min)
-- similar to:
print('Current center seg: '..dist_sum.min_issegidx)
return dist_sum.min_issegidx
end -- function seg_center_get2
function seg_furthest_get(seg_target)
-- print ('Getting furthest segment to seg#'..seg_target)
local dist={max=0;max_issegidx=0}
local i
for i=1,puzzle.seg_count do
dist.cur= structure.GetDistance(seg_target,i)
if dist.cur>dist.max then
dist.max= dist.cur
dist.max_issegidx=i
-- print('Distance max hit!')
-- print('Distance max: #'..dist.max_issegidx..' = '..dist.max)
end -- if dist.cur
end -- i
-- print('Distance max: #'..dist.max_issegidx..' = '..dist.max)
return dist.max_issegidx
end -- seg_furthest_get
function herd_generate(clu,first,last)
print('Generating herd from '..first..' to '..last..'...')
local i
for i=first,last do
-- print('Generating clu: '..i)
clu[i]={}
local j
for j=1,#seg_use do
-- with j, cycle through all segment indices given in seg uselist
-- print('--seg:'..j..'use:'..seg_use[j])
clu[i][j]=band_generate(j)
-- create clu band
end -- for i
clu[i].name=clu_name_get(i,"r")
end -- for i
return clu
end -- function herd_generate
function band_generate(seg_cur)
local band={act=random_flag2(herd.mutate.prob.off,true,true)}
-- depending on random flag (influenced by herd.mutate.prob.off),
-- turn off band when indicated
if clu.band_to.val=="space" then
-- following vals are pure random float numbers between 0 and <1
band.rho= math.random()
band.theta= math.random()
-- band.theta= .5;
band.phi= math.random()
else
band.d_cng1= math.random()
if clu.band_to.val=="random" then
-- random segs?
if puzzle.seg_random.get_mode=="list" then
-- random seg in list ?
repeat
band.seg_target= seg_use[math.random(1,#seg_use)]
until band.seg_target~=seg_use[seg_cur]
-- prevent banding seg to itself
-- print('Random seg target of list: '..band.seg_target..' (not '..seg_use[seg_cur]..')')
else -- puzzle.seg_random.get_mode=="all"
-- random seg in puzzle ?
repeat
band.seg_target= math.random(1,puzzle.seg_count)
until band.seg_target~=seg_use[seg_cur]
-- prevent banding seg to itself
-- print('Random seg target of all: '..band.seg_target..' (not '..seg_use[seg_cur]..')')
end -- if puzzle.seg_random.get_mode
end -- if clu.band_to.val
end -- if clu.band_to.val
if clu.d_cng2.act then
-- secondary bands are used
band.d_cng2= math.random()
end -- if clu.d_cng2.act
return band
end -- function band_generate
function herd_mutate(clu,first,last)
local clu= clu
print('Mutating herd from '..first..' to '..last..'...')
local i
for i=first,last do
if herd.mutate.genes_show.act then
print('Mutating clu#'..i)
end -- if if herd.mutate.genes_show.act
local j
for j=1,#clu[i] do
if herd.mutate.genes_show.act then
print('Mutating band#'..j)
end -- if herd.mutate.genes_show.act
clu[i][j]=band_mutate(clu[i][j])
end -- for j
end -- for i
return clu
end -- function herd_mutate
function band_mutate(clu_band)
local band={act=mutate_bool_val(clu_band.act)}
if clu.band_to.val=="space" then
band.rho= mutate_float_val(clu_band.rho)
band.theta= mutate_float_val(clu_band.theta)
band.phi= mutate_float_val(clu_band.phi)
else
band.d_cng1= mutate_float_val(clu_band.d_cng1)
if clu.band_to.val=="random" then
band.seg_target= mutate_int_seg(clu_band.seg_target)
end -- if clu.band_to.val
end -- if clu.band_to.val
if clu.d_cng2.act then
-- secondary bands are used
band.d_cng2= mutate_float_val(clu_band.d_cng2)
end -- if clu.d_cng2.act
return band
end -- function band_mutate
function mutate_bool_val(val)
-- depending on herd.mutate.prob.vals
-- possibly toggle band activity:
-- band on -> band off
-- band off -> band on
local val= val or false
-- break reference
if random_flag1(herd.mutate.prob.vals) then
-- has random hit possibility?
mutate_val_show('Old',val)
val= not(val)
-- invert boolean
mutate_val_show('New',val)
return val
else
return val
end -- if random_flag1
end -- function mutate_bool_val
function mutate_float_val(val)
-- depending on herd.mutate.prob.vals
-- possibly change normalized val 0...1
-- by a random amount between +/-10^(min...max)
local val= val+0
-- break reference
if random_flag1(herd.mutate.prob.vals) then
-- has random hit possibility?
mutate_val_show('Old',val)
val= val+(math.random(0,1)*2-1)*(10^(math.random(herd.mutate.rng10e.min,herd.mutate.rng10e.max)))
val= range_wrap(val,0,1,0)
mutate_val_show('New',val)
return val
else
return val
end -- if random_flag1
end -- function mutate_float_val
function mutate_int_seg(val)
-- depending on herd.mutate.prob.vals
-- possibly change segment idx
-- by a random amount between +/-1
local val= val+0
-- break reference
if random_flag1(herd.mutate.prob.vals) then
-- has random hit possibility?
mutate_val_show('Old',val)
val= val+(random_flag2(0.6,false,false)*2-1)
-- 0.6 (0.5 would be 50:50) means appearing:
-- +1: more often
-- -1: less often
val= range_wrap(val,1,puzzle.seg_count,1)
mutate_val_show('New',val)
return val
else
return val
end -- if random_flag1
end -- function mutate_float_val
function mutate_val_show(text,val)
if herd.mutate.genes_show.act then
print(text..' val: '..tostring(val))
end -- if herd.mutate.genes_show.act
end -- function mutate_val_show
function clu_name_get(idx,kind)
-- create a table with following name elements:
return {
gen=gen.cur;
clu=idx;
knd=kind;
dat=os.date()
}
end -- function clu_name_get
function clu_name_get_string(clu_name,dat_flag)
local OS=""
OS= OS..'G'..clu_name.gen
OS= OS..'C'..clu_name.clu
OS= OS..'K'..clu_name.knd
if dat_flag then
OS= OS..'D'..clu_name.dat
end -- if dat_flag
return OS
end -- function clu_name_get
function clu_apply_bands(band_use)
-- apply all fixed bands
-- and all which are stored in current clu passed by table band_use
-- for each band pair, depending on mode clu.band_to.val
-- connect origin seg (given in uselist)
-- to another seg ("center", "furthest", "random") or
-- to "space"
-- apply fixed bands given in clu.fix:
local i
for i=1,#clu.fix do
-- print(clu.fix[i][1],clu.fix[i][2])
band.AddBetweenSegments(clu.fix[i][1],clu.fix[i][2])
local len= structure.GetDistance(clu.fix[i][1],clu.fix[i][2])+clu.fix.lencng
len= range_clamp(len,clu.band_sys_lim.min,clu.band_sys_lim.max)
band.SetGoalLength(band.GetCount(),len)
band.SetStrength(band.GetCount(),clu.fix.str)
end -- for i
local seg_center
if clu.band_to.val=="center" then
-- banding to "center" is used
-- alternative:
-- if (clu.band_to.val=="center") or (clu.band_to.val=="space") then
-- banding to "center" or "space" is used
seg_center= seg_center_get()
-- get current center segment
-- fetch it before cycling through all clu bands,
-- as it is the same each time
end -- if clu.band_to.val
-- appply random bands given in clu:
for i=1,#band_use do
-- with i, cycle through all clu bands and their data
if band_use[i].act then
-- only if current band i is act (gene is not off)
local seg_origin= seg_use[i]
-- get origin seg to band from seg uselist
if clu.band_to.val=="space" then
-- if banding to space, but not to other segs
-- get
-- axis reference seg indices (seg_axis_x,seg_axis_y) and
-- calculated here
-- spatial vals (rho, theta,phi) from normalzed vals
-- given by clu data
local seg_axis_x= seg_get_idx("usepuzz",seg_origin,-1)
-- band x-angle reference is previous seg in puzzle
local seg_axis_y= seg_get_idx("usepuzz",seg_origin,1)
-- band y-angle reference is next seg in puzzle
-- alternative:
-- local seg_axis_y= seg_center
-- band y-angle reference is center seg in puzzle
-- rho uses band str1
band.Add2(i,
seg_origin,
seg_axis_x,
seg_axis_y,
band_use[i].rho,
band_use[i].theta,
band_use[i].phi,
clu.pull.str1)
else -- (clu.band_to.val=="center") or (clu.band_to.val=="furthest") or (clu.band_to.val=="random") then
-- not banding to space, but to other segs
-- having a relative dst or are random
-- calculated here
local seg_target1
if clu.band_to.val=="center" then
seg_target1= seg_center
-- primary target seg is center seg in puzzle
elseif clu.band_to.val=="furthest" then
seg_target1= seg_furthest_get(seg_origin)
-- primary target seg is furthest to origin seg
elseif clu.band_to.val=="random" then
seg_target1= band_use[i].seg_target
-- primary target seg is random
else-- clu.band_to.val has other val
assert(false,'function clu_apply_bands: clu.band_to.val is invalid: '..tostring(clu.band_to.val))
end -- if clu.band_to.val
-- primary target seg uses band str1
band.AddBetweenSegments2(i,
seg_origin,
seg_target1,
band_use[i].d_cng1,
clu.d_cng1.min,
clu.d_cng1.max,
clu.pull.str1)
end -- if clu.band_to.val
if clu.d_cng2.act then
-- secondary bands are used
local seg_target2= seg_get_idx("uselist",i,1)
-- secondary target seg is next in seg uselist
-- secondary target seg uses band str2
band.AddBetweenSegments2(i,
seg_origin,
seg_target2,
band_use[i].d_cng2,
clu.d_cng2.min,
clu.d_cng2.max,
clu.pull.str2)
end -- if clu.d_cng2.act
else
-- print('B#'..i..' off')
end -- if band_use[i].act
end -- for i
end -- function clu_apply_bands
function seg_get_idx(seg_option,val,offset)
if seg_option=="uselist" then
return seg_use[range_wrap((val+offset),1,#seg_use,1)]
-- offset=+1 : angle reference is next seg in uselist
-- offset=-1 : angle reference is prev seg in uselist
elseif seg_option=="usepuzz" then
return range_wrap((val+offset),1,puzzle.seg_count,1)
-- offset=+1 : angle reference is next seg in puzzle
-- offset=-1 : angle reference is prev seg in puzzle
else
assert(false, 'function seg_get_idx: seg_option has invalid val: '..tostring(seg_get_idx))
end -- if seg_option
end -- function seg_get_idx
function band.Add2(i,
seg_origin,
seg_axis_x,
seg_axis_y,
rho,
theta,
phi,
str)
-- print(i,seg_origin,seg_axis_x,seg_axis_y,rho,theta,phi,str)
local seg_axis_y,cng_flag= seg_axis_y_check_idx(seg_origin,seg_axis_x,seg_axis_y)
-- avoid seg_axis_y having the same val as seg_axis_x
if (cng_flag and clu.band_skip_wrong)==false then
local R= rho^(1/3) + 0.001
-- bend val rho 0..1 towards 1 by cubic root giving val R
local rho= R*(clu.rho.max-clu.rho.min)+clu.rho.min
-- stretch R 0...1 to min...max giving rho again
rho= range_clamp(rho,0,clu.band_sys_lim.max)
-- rho within limits?
local theta= math.acos(2*theta-1)+0
-- x-angle (azimut) range: 180 degrees (here: pi)
local phi= 2*math.pi*phi+0
-- y-angle (elevation) range: 360 degrees (here: 2*pi)
local band_count1= band.GetCount()
-- check number of bands before applying another band
band.Add(seg_origin,
seg_axis_x,
seg_axis_y,
rho,
theta,
phi)
local band_count2= band.GetCount()
-- check number of bands after applying another band
if band_count1~=band_count2 then
-- only if band has been created
-- show its attributes
print(
'B#'..i..
' Seg#'..seg_origin..
':('..seg_axis_x..
'&'..seg_axis_y..
') Rho:'..crop_digits(rho,3)..
' Theta:'..crop_digits(math.deg(theta),3).."o"..
-- ..string.char(39)..
' Phi:'..crop_digits(math.deg(phi),3).."o"..
' Str:'..crop_digits(str,3)
)
band.SetStrength(band_count2,str)
-- change str
else
print('B#'..i..' skipped')
end -- if band_count
else
print('B#'..i..' skipped')
end -- if (cng_flag and clu.band_skip_wrong) == false
end -- function band.Add2
function seg_axis_y_check_idx(seg_origin,seg_axis_x,seg_axis_y)
-- avoid seg_axis_y having the same val as seg_axis_x or seg_origin
local seg_axis_y= seg_axis_y
-- print('seg_axis_y: '..seg_axis_y)
local cng_flag= false
if (seg_axis_y==seg_axis_x) or (seg_axis_y==seg_origin) then
seg_axis_y= range_wrap((seg_origin+1),1,puzzle.seg_count,1)
cng_flag= true
end -- if seg_axis_y
--[[
if cng_flag then
print('Value changed')
end -- if cng_flag
--]]
return seg_axis_y,cng_flag
end -- function seg_axis_y_check_idx
function band.AddBetweenSegments2(i,
seg_origin,
seg_target,
d_cng,
d_cng_min,
d_cng_max,
str)
-- print(i,seg_origin,seg_target,d_cng,d_cng_min,d_cng_max,str)
seg_target,cng_flag= seg_target_check_spat(seg_origin,seg_target)
-- avoid seg_target beeing too close to seg_origin
if (cng_flag and clu.band_skip_wrong)==false then
local dst= structure.GetDistance(seg_origin,seg_target)
local cng= (d_cng-0.5)*2
-- as d_cng is a normalized val 0...1 internally
-- (i don't want to mess with band lens before)
-- stretch it to -1...1
-- print('D_cng:'..crop_digits(d_cng,3)..' Cng:'..crop_digits(cng,3))
local sign= above0(cng)
-- -1: compression
-- +1: expansion
cng= sign*(math.abs(cng)*(d_cng_max-d_cng_min)+d_cng_min)
-- as sign is stored now, truncate it from cng,
-- resulting an absoulte val 0...1
-- do linear transformation like cng*factor+offset
-- (factor: delta(extremae): (max-min), offset: min)
-- this maps 0...1 to min...max
-- finally, multiply by sign again to get direction back
local len= dst+cng
-- print('Cng:'..crop_digits(cng,3)..' len:'..crop_digits(len,3))
len= range_clamp(len,clu.band_sys_lim.min,clu.band_sys_lim.max)
local band_count1= band.GetCount()
-- check number of bands before applying another band
band.AddBetweenSegments(seg_origin,seg_target)
local band_count2= band.GetCount()
-- check number of bands after applying another band
if band_count1~=band_count2 then
-- only if band has been created
-- show its attributes
print(
'B#'..i..
' Seg#'..seg_origin..
':'..seg_target..
' Len:'..crop_digits(len,3)..
' (Cng:'..crop_digits(cng,3)..')'..
' Str:'..crop_digits(str,3)
)
-- print('Distance:'..crop_digits(dst,3)..' Cng:'..crop_digits(cng,3))
band.SetGoalLength(band_count2,len)
-- change len
band.SetStrength(band_count2,str)
-- change str
else
print('B#'..i..' skipped')
end -- if band_count
else
print('B#'..i..' skipped')
end -- if (cng_flag and clu.band_skip_wrong)==false
end -- function band.AddBetweenSegments2
function seg_target_check_spat(seg_origin,seg_target)
-- avoid seg_target beeing too close to seg_origin
-- by measuring spatial dst
local seg_target= seg_target
local cng_flag= false
while structure.GetDistance(seg_origin,seg_target)<clu.seg_min_dist do
seg_target,cng_flag= seg_target_too_close_show(seg_origin,seg_target)
end -- structure.GetDistance
--[[
if cng_flag then
print('Value changed')
end -- if cng_flag
--]]
return seg_target,cng_flag
end -- seg_target_check_spat
function seg_target_too_close_show(seg_origin,seg_target)
-- print('seg_origin='..seg_origin..':'..'seg_target='..seg_target..' too close.')
-- print('increasing seg_target idx.')
local seg_target= range_wrap((seg_target+1),1,puzzle.seg_count,1)
return seg_target,true
end -- function seg_too_close_show
function pull_do()
if pull.act then
print('Pulling...')
CI_apply(pull.CI_default,'Default CI for pull:')
-- if given, apply default CI
local i
for i=1,#pull do
CI_method(pull[i])
end -- for i
end -- if pull.act
end -- function pull_do
function release_n_fuse()
if release.act then
print('Releasing...')
CI_apply(release.CI_default,'Default CI for release: ')
-- if given, apply default CI
CI_method_batch(release)
end -- if release.act
if fuse.act then
-- print('Fusing...')
local i
for i=1,#fuse do
print('Fuse#'..i..'...')
CI_apply(fuse.CI_default,'Default CI for fuse: ')
-- if given, apply default CI
CI_method_batch(fuse[i])
end -- for i
end -- if fuse.act
end -- function release_n_fuse
function CI_method_batch(CI_method_list)
quicksave_do("load",3)
local i
for i=1,#CI_method_list do
CI_method(CI_method_list[i])
quicksave_do("save",4)
if quicksave_score[4]>quicksave_score[5] then
quicksave_do("save",5)
end -- if quicksave_score
end -- for i
end -- function CI_method_batch
function CI_method(prm)
-- depending on passed table prm:
-- 1. if given, set CI
-- 2. for a given number of itrs and
-- 3. use a foldit tool (backbone/sidechain move or mutate)
-- i intentionally use only shake/wiggle all
-- and didn't implement local wiggle,
-- as it can make the puzzle too stiff (local maximum)
--[[
print('CI_method prms:')
local k,v
for k,v in pairs(prm) do
print(k,v)
end
--]]
CI_apply(prm.CI,'Current CI: ')
-- if given, apply CI
if type(prm.itr)=="number" then
-- number of itrs given?
if prm.itr>0 then
-- depending on prm.cmd, do one of these tools:
if prm.cmd=="m" then
if puzzle.ismutable then
if puzzle.mutate.act then
-- print('Mutating for '..prm.itr..' iterations...')
structure.MutateSidechainsAll(prm.itr)
end -- if puzzle.mutate.act
end -- if puzzle.ismutable
elseif prm.cmd=="s" then
-- print('Shaking for '..prm.itr..' iterations...')
structure.ShakeSidechainsAll(prm.itr)
elseif prm.cmd=="w" then
-- print('wiggling for '..prm.itr..' iterations...')
structure.WiggleAll(prm.itr,prm.backbone,prm.sidechains)
end -- if prm.cmd
end -- if prm.itr
end -- if prm.itr
end -- function CI_method
function CI_apply(CI,text)
if type(CI)=="number" then
-- CI given?
-- print(text..CI)
behavior.SetClashImportance(CI)
-- set it
end -- if type(CI)
end -- function CI_apply
function quicksave_do(mode,slot)
if mode=="save" then
save.Quicksave(slot)
quicksave_score[slot]=current.GetEnergyScore()
-- print('Quicksaved to slot:'..slot..' - Score:'..crop_digits(quicksave_score[slot],3))
elseif mode=="load" then
save.Quickload(slot)
-- print('Quickloaded from slot:'..slot..' - Score:'..crop_digits(quicksave_score[slot],3))
end -- if mode
end -- function quicksave_do
function quicksave_do_batch(mode,first,last)
local i
for i=first,last do
quicksave_do(mode,i)
end -- for i
end -- function quicksave_do
function herd_fitnessandsort(clu)
local clu= clu
-- print("Getting herd's scoregain minimum...")
clu.scoregain_min= clu[1].scoregain
-- to have a minimum gain val (gain_min) to compare
-- get first clu's scoregain val
local i
for i=2,#clu do
-- cycle through all other scoregain vals
if clu[i].scoregain<clu.scoregain_min then
-- new gain_min found
clu.scoregain_min= clu[i].scoregain
-- set it as new gain_min (worst)
end -- if clu[i].scoregain
end -- for i
-- print('Old minimum clu scoregain: '..clu.scoregain_min)
if clu.scoregain_min>0 then
-- gain_min >0?
clu.scoregain_min= 1
-- we don't need to push fitness vals
-- so resulting fitness vals will be equal to scoregain vals
end -- if clu.scoregain_min
-- if gain_min were <=0, we would have to push fitness vals
-- print('New minimum clu scoregain: '..clu.scoregain_min)
-- print("Putting clu's fitness vals...")
-- fitness= gain-gain_min+1
-- smallest fitness val must be >0 (prevent fitnesses <=0)
-- if all fitness vals were =0, roulette would run infinitely
-- (plus, we even want to give worst clus a small chance)
local i
for i=1,#clu do
clu[i].fitness= clu[i].scoregain-clu.scoregain_min+1
--[[
a gain_min of 1 doesn't do a change:
fitness= gain-gain_min+1
is fitness= gain-1 +1
is fitness= gain-0
-> so we've set a good gain_min (>0) to 1 before
-- now subtract gain_min
(can be negative, then it will be added to push value)
-> from clu's scoregain
-> giving fitness==0 for worst clu
-- then add 1, as 0 isn't allowed, too
-> giving fitness==1 for worst clu
--]]
end -- for i
clu= herd_normalize(clu)
-- sum of all fitness vals==1
if (herd.fitness_e~=nil) and (herd.fitness_e~=1) then
-- bending exponent given?
-- print("Bending clu's fitnesses...")
local i
for i=1,#clu do
clu[i].fitness= clu[i].fitness^(herd.fitness_e)
-- bend fitness val -> 1 (if 0<exponent<1)
end -- for i
clu= herd_normalize(clu)
-- sum of all fitness vals==1
end -- if herd.fitness_e
-- print("Sorting herd by clu's fitnesses...")
local i
for i=1,(#clu-1) do
local j
for j=(i+1),#clu do
if clu[i].fitness<clu[j].fitness then
clu[i],clu[j]=clu[j],clu[i]
end -- if clu[i].fitness
end -- for j
end -- for i
print('\nHerd after sorting is:')
herd_show(clu)
-- for debugging:
-- list_show_deep(clu,"clu")
print()
return clu
end -- function herd_fitnessandsort
function herd_normalize(clu)
local clu= clu
clu.fitness_sum= clu_fitness_sum_get(clu)
-- print("Normalizing clu's fitnesses...")
local i
for i=1,#clu do
clu[i].fitness= clu[i].fitness/clu.fitness_sum
end -- for i
-- print('Herd with normalized fitness is:')
-- herd_show(clu)
return clu
end -- function herd_normalize
function clu_fitness_sum_get(clu)
-- print("Getting herd's fitness sum...")
local fitness_sum= 0
local i
for i=1,#clu do
fitness_sum= fitness_sum+clu[i].fitness
end -- for i
-- print('Clus have fitness sum: '..fitness_sum)
return fitness_sum
end -- function
function herd_show(clu)
print('Gen#'..gen.cur..' had start score: '..quicksave_score[2])
local i
for i=1,#clu do
print(
'Clu#'..i..
' Name:'..clu_name_get_string(clu[i].name,false)..
' Score:'..crop_digits(clu[i].score,3)..
' Gain:'..crop_digits(clu[i].scoregain,3)..
' Fitness:'..crop_digits(clu[i].fitness,6)
)
end -- for i
clu.fitness_sum= clu_fitness_sum_get(clu)
-- print('Fitness checksum: '..clu.fitness_sum)
end -- function herd_show
function breed_n_generate(clu)
if herd.EO_mode.cur==2 then
clu= herd_generate(
clu,
herd.breed.first,
herd.size
)
elseif herd.EO_mode.cur==1 then
clu= herd_generate(
clu,
(herd.breed.last+1),
herd.size
)
else
clu= herd_breed(
clu,
herd.breed.first,
herd.breed.last
)
if herd.mutate.act then
clu= herd_mutate(
clu,
herd.breed.first,
herd.breed.last
)
end -- if herd.mutate.act then
clu= herd_generate(
clu,
(herd.breed.last+1),
herd.size
)
end -- if herd.EO_mode.cur
return clu
end -- function breed_n_generate
function herd_breed(clu,first,last)
local kid={}
-- intialize kid clus breeding section
if herd.breed.mode.val=="dual" then
kid= herd_breed_dual(first,last)
-- get kid by mom & dad breeding
elseif herd.breed.mode.val=="poly" then
kid= herd_breed_poly(first,last)
-- get kid by orgy breeding
else
assert(false,'function herd_breed: herd.breed.mode.val is invalid: '..tostring(herd.breed.mode.val))
end -- herd.breed.mode.val
local i
for i=first,last do
-- move kids to herd
-- overwrite old clus
clu[i]= kid[i]
clu[i].name= clu_name_get(i,"b")
end -- for i
return clu
-- return new clu table
end -- function herd_breed
function herd_breed_dual(first,last)
local kid={}
-- intialize kid clus breeding section
local i
for i=first,last do
-- fill breeding section
local dad
if herd.breed.roulette.dad then
dad=roulette()
else
dad= range_wrap((i-first+1),1,herd.size,1)
end -- if herd.breed.roulette.dad
local mom
if herd.breed.roulette.mom then
mom= roulette()
while mom==dad do
mom=roulette()
end -- while mom
else
mom= range_wrap((dad+1),1,herd.size,1)
end -- if herd.breed.roulette.dad
print('Breeding clu#'..i..'(kid) from: '..
'clu#'..dad..'(dad*'..crop_digits(herd.breed.dad_genes_prob,3)..')'..
' + '..
'clu#'..mom..'(mom*'..crop_digits((1-herd.breed.dad_genes_prob),3)..')')
kid[i]={}
for j=1,#clu[i] do
if random_flag1(herd.breed.dad_genes_prob) then
print_gene(j,dad,"dad")
kid[i][j]=clu[dad][j]
else
print_gene(j,mom,"mom")
kid[i][j]=clu[mom][j]
end -- if random_flag1
end -- for j
end -- for i
return kid
end -- function function herd_breed_dual
function herd_breed_poly(first,last)
local kid={}
-- intialize kid clus breeding section
local i
for i=first,last do
-- fill breeding section
print('Breeding clu#'..i..'(kid) from: whole herd')
kid[i]={}
for j=1,#clu[i] do
local parent= roulette()
print_gene(j,parent,"roulette")
kid[i][j]=clu[parent][j]
end -- for j
end -- for i
return kid
end -- function herd_breed_poly
function print_gene(j,parent_val,parent_text)
if herd.breed.genes_show.act then
-- detailed gene view activated?
print('Gene #'..j..': clu#'..tostring(parent_val)..'('..tostring(parent_text)..')')
end -- if herd.breed.genes_show act
end -- function print_gene
function roulette()
local fitness_target= math.random()
-- set random fitness sum goal
-- print('Fitness target:'..crop_digits(fitness_target,6))
local wheel= math.random(1,herd.size)
-- initialize random roulette wheel position
local fitness_sum= clu[wheel].fitness
-- get fitness val at wheel position
-- print('Wheel current:'..wheel..' Fitness sum:'..crop_digits(fitness_sum,6))
while fitness_sum<=fitness_target do
-- as long as fitness sum hasn't reached fitness sum goal
if herd.breed.roulette.israndom then
-- if roulette is random (not default)
-- set another random roulette wheel position
wheel= math.random(1,herd.size)
else
-- if roulette is not random (default)
wheel= range_wrap((wheel+1),1,herd.size,1)
-- spin wheel
end -- if herd.breed.roulette.israndom
fitness_sum= fitness_sum+clu[wheel].fitness
-- print('Wheel current:'..wheel..' Fitness sum:'..crop_digits(fitness_sum,6))
end -- while fitness_sum
-- print('Wheel end:'..wheel..' Fitness sum:'..crop_digits(fitness_sum,6))
return wheel
end -- function roulette
function main()
print('\nMain Programm start.')
puzzle.random_seed= random_seed_set_val(puzzle.random_seed)
print('Puzzle segs mutability: '..tostring(puzzle.ismutable))
if puzzle.ismutable==false then
puzzle.mutate.act= false
end -- if puzzle.ismutable
print('Puzzle segs mutation: '..tostring(puzzle.mutate.act))
print('Herd genes off prob: '..herd.mutate.prob.off)
print('Herd mutability prob: '..herd.mutate.prob.vals)
if herd.mutate.prob.vals<=0 then
-- herd mutate probability is not given
herd.mutate.act= false
-- deactivate hed genes mutating
-- (not needed, but prevents calling function)
end -- if herd.mutate.prob.vals
print('Herd mutation: '..tostring(herd.mutate.act))
if herd.breed.first>herd.breed.last then
herd.breed.first,herd.breed.last= herd.breed.last,herd.breed.first
end -- if herd.breed.first
print('EO mode: '..herd.EO_mode.cur)
print('Breed mode: '..herd.breed.mode.val)
print('Clu band mode old: '..clu.band_to.val)
if type(puzzle.seg_center.val)=="number" then
-- "center" seg already given by user?
clu.band_to.val= "user"
end -- if type(puzzle.seg_center.val)
if clu.band_to.val=="center" then
-- banding to center
if puzzle.seg_center.get_mode=="start" then
-- get center seg once?
puzzle.seg_center.val= seg_center_get()
end -- if puzzle.seg_center.get_mode
elseif clu.band_to.val=="user" then
-- banding to user-defined seg idx
assert((puzzle.seg_center.val>0) and (puzzle.seg_center.val<(puzzle.seg_count+1)),'function main: puzzle.seg_center.val is invalid: '..tostring(puzzle.seg_center.val))
puzzle.seg_center.get_mode="start"
-- get center seg once!
clu.band_to.val= "center"
-- "user" -> "center"
elseif string.sub(clu.band_to.val, 1, 6)=="random" then
-- using any random bands?
if clu.band_to.val=="random1" then
puzzle.seg_random.get_mode= "list"
else -- if clu.band_to.val=="random2"
puzzle.seg_random.get_mode= "all"
end -- if clu.band_to.val
clu.band_to.val= "random"
-- elseif (clu.band_to.val=="space") or (clu.band_to.val=="furthest")
end -- if clu.band_to.val
print('Clu band mode new: '..clu.band_to.val)
print('Seg center get_mode: '..puzzle.seg_center.get_mode)
print('Seg random get_mode: '..puzzle.seg_random.get_mode)
if (clu.band_to.val=="center") and (puzzle.seg_center.get_mode== "start") then
-- always the same "center" seg used?
table.insert(seg_use.exclude.idx,puzzle.seg_center.val)
-- add it's idx to exclude candidates
end -- if clu.band_to.val,puzzle.seg_center.get_mode
seg_use= seg_use_list_create(seg_use)
-- create seg list based on informations given at declaration
-- for debugging before generation testing, uncomment this:
-- prms_show()
gen.cur= 1
-- initialize gen counter
quicksave_score={}
-- saveslot 1: script start
-- saveslot 2: gen/clu start = before applying bands/pull
-- saveslot 3: after pull = before release/fuse start
-- saveslot 4: after single release/fuse step
-- saveslot 5: after all releases/fuses per clu = best result for current clu
-- saveslot 6: best result for current gen
-- saveslot 7: best result so far
-- save to all slots to initialize states & scores:
quicksave_do_batch("save",1,7)
recentbest.Save()
-- print('Set Recent Best')
clu=herd_generate(clu,1,herd.size)
-- list_show_deep(clu,"clu") --show all clu informations
herd.test={first=1,last=herd.size}
-- normally, test all generated clus
while gen.cur<=gen.max do
print('\n\nGen: '..gen.cur)
if herd.use.hybrid.state~=nil then
-- hybrid loading is used, change loading state
if gen.cur%herd.use.hybrid.gens==1 then
-- number of gens to trigger reloading start state is reached
herd.use.state.val= "start"
-- load start state
else
-- number of gens to trigger reloading start state is not reached
herd.use.state.val= herd.use.hybrid.state
-- load specified state
end -- if gen.cur
end -- if herd.use.hybrid.state
local loaded_text
-- load puzzle state before pulling:
if herd.use.state.val=="start" then
quicksave_do("load",1)
loaded_text= "Script Start State"
elseif herd.use.state.val=="gen" then
quicksave_do("load",6)
loaded_text= "Gen's Best State"
elseif herd.use.state.val=="script" then
quicksave_do("load",7)
loaded_text= "Script's Best State"
else
recentbest.Restore()
loaded_text= "Recent Best State"
end -- if herd.use.state.val
print('Loaded: '..loaded_text..' - Score: '..crop_digits(current.GetEnergyScore(),3))
-- save to these slots to initialize states & scores:
quicksave_do_batch("save",2,6)
if (herd.use.state.val=="start") and (herd.use.hybrid.state==nil) then
-- if script start state is always loaded
herd.test.first= 1
-- reset test start clu (first clu of gen to test)
while clu[herd.test.first].name.gen~=gen.cur do
-- test start clu was not generated in this gen (it is an old one)
herd.test.first= herd.test.first+1
-- use next clu
if herd.test.first>herd.size then
-- if next clu is too high (higher than herd size)
herd.test.first=1
-- set it to 1 again
break
-- exit start clu checking
end -- if
end -- while clu
end -- if if herd.use.state.val
local clu_cur
for clu_cur=herd.test.first,herd.test.last do
-- print('clu: '..clu_cur)
print('\nTesting: G'..gen.cur..
'C'..clu_cur..
'/'..herd.size..
' Name: '..clu_name_get_string(clu[clu_cur].name,true))
quicksave_do("load",2)
band.DeleteAll()
clu_apply_bands(clu[clu_cur])
pull_do()
band.DeleteAll()
-- save to these slots to initialize states & scores:
quicksave_do_batch("save",3,5)
print('Score after pulling: '..crop_digits(quicksave_score[3],3))
release_n_fuse()
clu[clu_cur].score=quicksave_score[5]
clu[clu_cur].scoregain=clu[clu_cur].score-quicksave_score[2]
print("This clu's best Score: "..crop_digits(quicksave_score[5],3).." - Gain: "..crop_digits(clu[clu_cur].scoregain,3))
if (clu_cur==herd.test.first) or (quicksave_score[5]>quicksave_score[6]) then
quicksave_do("load",5)
quicksave_do("save",6)
print('New best result for this gen: '..crop_digits(quicksave_score[6],3))
end -- if clu_cur
if quicksave_score[6]>quicksave_score[7] then
quicksave_do("load",6)
quicksave_do("save",7)
print('New best result so far! '..crop_digits(quicksave_score[7],3))
end -- if quicksave_score
end -- for clu_cur
clu=herd_fitnessandsort(clu)
-- for each clu calculate fitness given by it's score gain
-- sort clus by fitness, best gain on top
gen.cur=gen.cur+1
-- increase generation counter now,
-- so newly generated/breeded clus will
-- get an actual "birth date" = next gen
if (herd.flush_gain_thr.act==false) or (clu[1].scoregain>=herd.flush_gain_thr.thr) then
-- best gain checking is off
-- or gain is good enough
clu=breed_n_generate(clu)
-- keep good
-- breed good over mediocre
-- replace bad by new ones
herd.flush_gain_reset.cur= 0
-- reset flush counter
else
-- best gain checking is on
-- and gain is bad enough
print('\nTarget result for this gen: '..crop_digits(herd.flush_gain_thr.thr,3))
print('Best result for this gen only: '..crop_digits(clu[1].scoregain,3))
herd.flush_gain_reset.cur= herd.flush_gain_reset.cur+1
-- increase flush counter
print('Herd flush #'..herd.flush_gain_reset.cur)
clu=herd_generate(clu,1,herd.size)
-- flush entire herd
-- replace all clus by new ones
if herd.flush_gain_reset.act then
-- reset after flush activated?
if herd.flush_gain_reset.cur>herd.flush_gain_reset.thr then
print('Number of flushs exceeded.')
print('Restarting from script start state.')
quicksave_do("load",1)
-- get script start state again
-- save to (almost) all slots to flush states & scores:
quicksave_do_batch("save",2,7)
recentbest.Save()
-- print('Set Recent Best')
herd.flush_gain_reset.cur= 0
-- reset flush counter
end -- if herd.flush_gain_reset.cur
end -- if herd.flush_gain_reset.act
end -- if herd.flush_gain_thr.act,clu[1].scoregain
end -- while gen.cur~=gen.max
end -- function main
function prms_cng()
local dialog_val_cng= false
do -- dialog 0
local ask= dialog.CreateDialog('CG303 GAB+EO Remix - Parameters 0/7')
ask.Input10= dialog.AddLabel("Change default values here? There are many!")
ask.Input1a= dialog.AddCheckbox("edit",dialog_val_cng)
ask.Input12= dialog.AddLabel(" checked: OK to change values")
ask.Input13= dialog.AddLabel("not checked: OK to launch script")
ask= ask_window(ask)
dialog_val_cng= ask.Input1a.value
end -- do dialog 0
if dialog_val_cng then
if true then -- dialog 1
local ask= dialog.CreateDialog('CG303 GAB+EO Remix - Parameters 1/7')
if puzzle.random_seed==nil then
puzzle.random_seed= ""
end -- if puzzle.random_seed
ask.Input10= dialog.AddTextbox("Random seed ",tostring(puzzle.random_seed))
ask.Label11= dialog.AddLabel("Blank : use new seed"..
"\nNumber: use this seed")
ask.Label12= dialog.AddLabel(" ")
ask.Input20= dialog.AddSlider("Herd size",herd.size,4,16,0)
ask.Label21= dialog.AddLabel("Number of clu(ster)s for one gen(eration)")
ask= ask_window(ask)
puzzle.random_seed= ask.Input10.value
herd.size= ask.Input20.value
end -- if dialog 1
if true then -- dialog 2
local ask= dialog.CreateDialog('CG303 GAB+EO Remix - Parameters 2/7')
local keep=0
if (herd.size%2)==0 then
-- if number of clus is even
keep=2
-- keep 2 clus
else
-- ff number of clu is not even
keep=3
-- Keep 3 clus
end -- if (herd.size%2)
local breed=(herd.size-keep)/2
-- after subtracting kept clus,
-- divide the rest by 2
-- first half for breeding:
herd.breed.first=keep+1
herd.breed.last=keep+breed
-- other half for creating new
-- herd.breed.last+1...herd.size will be random
ask.Label00= dialog.AddLabel(" ")
ask.Label01= dialog.AddLabel("Breed settings:"..
"\nAfter one gen,"..
"\nreplace mediocre clu# by new breeded ones")
ask.Label02= dialog.AddLabel(" ")
ask.Input1x= dialog.AddSlider("First",herd.breed.first,1,herd.size,0)
ask.Label11= dialog.AddLabel("clu# to be replaced"..
"\nAll < are kept as parents")
ask.Label12= dialog.AddLabel(" ")
ask.Input2x= dialog.AddSlider("Last",herd.breed.last,1,herd.size,0)
ask.Label21= dialog.AddLabel("clu# to be replaced"..
"\nAll > are created new")
ask.Label22= dialog.AddLabel(" ")
ask.Label30= dialog.AddLabel("Breed settings are also used by EO as orientation")
ask= ask_window(ask)
herd.breed.first= ask.Input1x.value
herd.breed.last= ask.Input2x.value
end -- if dialog 2
if true then -- dialog 3
local ask= dialog.CreateDialog('CG303 GAB+EO Remix - Parameters 3/7')
ask.Label10= dialog.AddLabel("Extremal Optimization (discard mediocre clus)")
ask.Input1x= dialog.AddSlider("EO mode",herd.EO_mode.cur,herd.EO_mode.min,herd.EO_mode.max,0)
ask.Label12= dialog.AddLabel(" ")
ask.Label13= dialog.AddLabel("0: no EO (GA!), replace m.clus by breeded ones"..
"\n1: use EO, don't just leave m.clus as they are"..
"\n2: use EO, don't replace m.clus by random ones")
ask.Label14= dialog.AddLabel(" ")
ask.Label15= dialog.AddLabel(" ")
local state={list=herd.use.state.list}
ask.Label20= dialog.AddLabel("Puzzle state for each gen")
ask.Input2x= dialog.AddSlider("Load state",key_getbyval(state.list,herd.use.state.val),1,#state.list,0)
ask.Label22= dialog.AddLabel(" ")
ask.Label23= dialog.AddLabel(list_text(state.list))
ask.Label24= dialog.AddLabel(" ")
ask.Label25= dialog.AddLabel(" ")
ask.Label26= dialog.AddLabel(" ")
ask.Label27= dialog.AddLabel("start: as now"..
"\n (never reach local max)"..
"\ngen: best result of last gen"..
"\n (avoid local max)"..
"\nscript: script's best"..
"\n (similar to recent)"..
"\nrecent: recent best state"..
"\n (can produce local max)")
ask.Label28= dialog.AddLabel(" ")
ask.Label29= dialog.AddLabel(" ")
ask.Label2A= dialog.AddLabel(" ")
ask.Label2B= dialog.AddLabel(" ")
local band_to={list=clu.band_to.list}
ask.Label30= dialog.AddLabel("Band these segs to target seg:"..
"\n+ in list (detected by script)"..
"\n+ game interface selection (blue)")
ask.Label31= dialog.AddLabel(" ")
ask.Input3x= dialog.AddSlider("Band mode",key_getbyval(band_to.list,clu.band_to.val),1,#band_to.list,0)
ask.Label33= dialog.AddLabel(" ")
ask.Label34= dialog.AddLabel(" ")
ask.Label35= dialog.AddLabel(list_text(band_to.list))
ask.Label36= dialog.AddLabel(" ")
ask.Label37= dialog.AddLabel(" ")
ask.Label38= dialog.AddLabel(" ")
ask.Label39= dialog.AddLabel("center: 'center' seg is auto-detected"..
"\nuser: 'center' seg is set manually"..
"\nrandom1: uselist (detected & selection) to uselist"..
"\nrandom2: uselist to all of puzzle (classic)")
ask= ask_window(ask)
herd.EO_mode.cur= ask.Input1x.value
herd.use.state.val= state.list[ask.Input2x.value]
clu.band_to.val= band_to.list[ask.Input3x.value]
end -- if dialog 3
if clu.band_to.val=="user" then -- dialog 3.1
-- user wants to set own "center" seg
local ask= dialog.CreateDialog('CG303 GAB+EO Remix - Parameters 3.1/7')
ask.Label10= dialog.AddLabel("Set your own 'center' seg")
ask.Input1x= dialog.AddSlider("Center seg",seg_center_get2(),1,puzzle.seg_count,0)
ask= ask_window(ask)
puzzle.seg_center.val= ask.Input1x.value
end -- if clu.band_to.val ... then dialog 3.1
if true then -- dialog 4
local ask= dialog.CreateDialog('CG303 GAB+EO Remix - Parameters 4/7')
ask.Input1a= dialog.AddCheckbox("Mutate sidechains",puzzle.mutate.act)
ask.Label11= dialog.AddLabel("Works only if puzzle is mutable")
ask.Label12= dialog.AddLabel(" ")
ask.Input2a= dialog.AddCheckbox("Pull",pull.act)
ask.Input2x= dialog.AddSlider("CI with bands",pull.CI_default,0,1,2)
ask.Input3a= dialog.AddCheckbox("Release (1-pass)",release.act)
ask.Input3x= dialog.AddSlider("CI without bands",release.CI_default,0,1,2)
ask.Input4a= dialog.AddCheckbox("Fuse(s) (multiple-pass)",fuse.act)
ask.Label42= dialog.AddLabel("CI is varied")
ask= ask_window(ask)
puzzle.mutate.act= ask.Input1a.value
pull.act= ask.Input2a.value
pull.CI_default= ask.Input2x.value
release.act= ask.Input3a.value
release.CI_default= ask.Input3x.value
fuse.act= ask.Input4a.value
end -- if dialog 4
if true then -- dialog 5
local ask= dialog.CreateDialog('CG303 GAB+EO Remix - Parameters 5/7')
local prim_min,prim_max
if clu.band_to.val=="space" then
-- use rho vals for sliders:
prim_min= clu.rho.min
prim_max= clu.rho.max
else -- clu.band_to.val~="space"
-- use d_cng vals for sliders:
prim_min= clu.d_cng1.min
prim_max= clu.d_cng1.max
end -- if clu.band_to.val
if string.sub(clu.band_to.val, 1, 6)=="random" then
-- using any random bands?
clu.d_cng2.act= false
-- secondary bands off by default
end -- if "random"
ask.Input10= dialog.AddLabel("Primary bands push distance by +/-")
ask.Input1m= dialog.AddSlider("min",prim_min,0,3.8*5,2)
ask.Input1n= dialog.AddSlider("max",prim_max,0,3.8*5,2)
ask.Input1s= dialog.AddSlider("strength",clu.pull.str1,0,10,2)
ask.Input14= dialog.AddLabel(" ")
ask.Input20= dialog.AddLabel("Secondary bands push distance by +/-")
ask.Input2a= dialog.AddCheckbox("on",clu.d_cng2.act)
ask.Input2m= dialog.AddSlider("min",clu.d_cng2.min,0,3.8*5,2)
ask.Input2n= dialog.AddSlider("max",clu.d_cng2.max,0,3.8*5,2)
ask.Input2s= dialog.AddSlider("strength",clu.pull.str2,0,10,2)
ask.Input25= dialog.AddLabel(" ")
ask.Input30= dialog.AddLabel("Segs with this distance are 'too close'")
ask.Input3m= dialog.AddSlider("min",clu.seg_min_dist,0,3.8*5,2)
ask.Input32= dialog.AddLabel("if 'too close:")
ask.Input33= dialog.AddLabel("neither apply band"..
"\nnor change (correct) target seg idx")
ask.Input3a= dialog.AddCheckbox("skip band / don't change t.seg",clu.band_skip_wrong)
ask.Input35= dialog.AddLabel("if banding to many segs, possibly:")
ask.Input36= dialog.AddLabel("- activate this or"..
"\n- deactivate secondary bands")
ask= ask_window(ask)
if ask.Input1m.value>ask.Input1n.value then
ask.Input1m.value,ask.Input1n.value= ask.Input1n.value,ask.Input1m.value
end -- if ask.Input11,ask.Input12
if clu.band_to.val=="space" then
clu.rho.min= ask.Input1m.value
clu.rho.max= ask.Input1n.value
else
clu.d_cng1.min= ask.Input1m.value
clu.d_cng1.max= ask.Input1n.value
end -- if clu.band_to.val
clu.pull.str1= ask.Input1s.value
clu.d_cng2.act= ask.Input2a.value
if ask.Input2m.value>ask.Input2n.value then
ask.Input2m.value,ask.Input2n.value= ask.Input2n.value,ask.Input2m.value
end -- if ask.Input22,ask.Input23
clu.d_cng2.min= ask.Input2m.value
clu.d_cng2.max= ask.Input2n.value
clu.pull.str2= ask.Input2s.value
clu.seg_min_dist= ask.Input3m.value
clu.band_skip_wrong= ask.Input3a.value
end -- if dialog 5
if true then -- dialog 6
local ask= dialog.CreateDialog('CG303 GAB+EO Remix - Parameters 6/7')
ask.Input10= dialog.AddLabel("Band genes and mutating ratios")
ask.Input11= dialog.AddLabel(" ")
ask.Input12= dialog.AddLabel("0.00= 0% (none)"..
"\n0.50= 50% (half)"..
"\n1.00= 100% (all)")
ask.Input13= dialog.AddLabel(" ")
ask.Input20= dialog.AddLabel("Turn this many")
ask.Input2x= dialog.AddSlider("genes off",herd.mutate.prob.off,0,1,2)
ask.Input22= dialog.AddLabel("(EO+GA)")
ask.Input23= dialog.AddLabel(" ")
ask.Input30= dialog.AddLabel("Probability of")
ask.Input3x= dialog.AddSlider("gene mutating",herd.mutate.prob.vals,0,1,2)
ask.Input31= dialog.AddLabel("after breeding (GA)")
ask.Input32= dialog.AddLabel(" ")
ask.Input40= dialog.AddLabel("Probability of")
ask.Input4x= dialog.AddSlider("dad genes",herd.breed.dad_genes_prob,0,1,2)
ask.Input42= dialog.AddLabel("(first breeding (GA) partner)")
ask.Input44= dialog.AddLabel("Without mutating:")
ask.Input45= dialog.AddLabel("1.00: kid is like dad"..
"\n0.00: kid is like mom")
ask.Input46= dialog.AddLabel(" ")
ask.Input47= dialog.AddLabel(" ")
local breed={mode={list=herd.breed.mode.list}}
ask.Input50= dialog.AddLabel("Breeding behaviour (genetic roulette)")
ask.Input5x= dialog.AddSlider("Breed mode",key_getbyval(breed.mode.list,herd.breed.mode.val),1,#breed.mode.list,0)
-- ask.Input52= dialog.AddLabel(" ")
ask.Label53= dialog.AddLabel(list_text(breed.mode.list))
ask.Label54= dialog.AddLabel("Each kid from:")
ask.Label55= dialog.AddLabel("dual: 2 clus (mom & dad) (classic)"..
"\npoly: any clus (whole herd)")
ask.Label56= dialog.AddLabel(" ")
ask.Input60= dialog.AddLabel("Roulette is")
ask.Input6a= dialog.AddCheckbox("random",herd.breed.roulette.israndom)
ask.Label62= dialog.AddLabel("true: spreaded"..
"\nfalse: inertial (classic)")
ask.Label63= dialog.AddLabel(" ")
ask.Input70= dialog.AddLabel("Show breeding details")
ask.Input7a= dialog.AddCheckbox("gene origin clu#(parent)",herd.breed.genes_show.act)
ask.Input80= dialog.AddLabel("Show mutating details")
ask.Input8a= dialog.AddCheckbox("gene value (old & new)",herd.mutate.genes_show.act)
ask= ask_window(ask)
herd.mutate.prob.off= ask.Input2x.value
herd.mutate.prob.vals= ask.Input3x.value
herd.breed.dad_genes_prob= ask.Input4x.value
herd.breed.mode.val= breed.mode.list[ask.Input5x.value]
herd.breed.roulette.israndom= ask.Input6a.value
herd.breed.genes_show.act= ask.Input7a.value
herd.mutate.genes_show.act= ask.Input8a.value
end -- if dialog 6
if true then -- dialog 7
local ask= dialog.CreateDialog('CG303 GAB+EO Remix - Parameters 6/7')
ask.Input10= dialog.AddLabel("If best clu gain is less than")
ask.Input1m= dialog.AddSlider("min",
herd.flush_gain_thr.thr,
herd.flush_gain_thr.min,
herd.flush_gain_thr.max,
1)
ask.Input1a= dialog.AddCheckbox("flush herd completely",herd.flush_gain_thr.act)
ask.Input13= dialog.AddLabel(" ")
ask.Input20= dialog.AddLabel("When flushed more than this time")
ask.Input2n= dialog.AddSlider("max",
herd.flush_gain_reset.thr,
herd.flush_gain_reset.min,
herd.flush_gain_reset.max,
0)
ask.Input2a= dialog.AddCheckbox("reset puzzle to script start state",herd.flush_gain_reset.act)
ask= ask_window(ask)
herd.flush_gain_thr.thr= ask.Input1m.value
herd.flush_gain_thr.act= ask.Input1a.value
herd.flush_gain_reset.thr= ask.Input2n.value
herd.flush_gain_reset.act= ask.Input2a.value
end -- if dialog 7
end -- if dialog_val_cng
end -- function prms_cng
function ask_window(ask)
ask.Labelx1= dialog.AddLabel(" ")
ask.Labelx2= dialog.AddLabel(" ")
ask.OK= dialog.AddButton("OK", 1)
ask.Cancel= dialog.AddButton("Cancel", 0)
local button_val= dialog.Show(ask)
assert((button_val==1),'Script canceled!')
return ask
end -- function ask_window
function list_text(list)
-- create output text with multiple lines from list
local text= ""
local i
for i=1,#list do
if text~="" then
text= text.."\n"
end -- if text
text= text..tostring(i)..": "..list[i]
end -- for i
return text
end -- function list_text
-- declaration section:
-- all global variables (mostly organized as table) can be set here.
-- some of them here are used as default vals only
-- and can be altered by gui in change section
puzzle={seg_count= structure.GetCount();
random_seed=nil
-- set random seed to this val
-- if there is none,
-- os.time()+recipe.GetrandomSeed() will be used
}
print('Puzzle has '..puzzle.seg_count..' segments.')
puzzle.ismutable=puzzle_ismutable()
-- check if there are mutable segments
puzzle.mutate={act=(puzzle.ismutable or false)}
-- set mutating puzzle activity to the same val
-- (foldit-mutation, not equal to: mutate herd-cluster-bands)
puzzle.isligand=puzzle_isligand()
-- check if there is a ligand
puzzle.seg_center={
val=nil;
--[[
set which seg is considered as seg_center
integer number between first and last segment: use this segment index as center segment
puzzle.seg_count (same as last segment): use last segment index as center segment
-- nil: get "start" or "current" center segment
--]]
get_mode="current"
--[[
set when seg_center is to be fetched
"start": only at script start
use this when setting val to a constant number or banding to ligand
"current" (or any other): each time before applying bands
--]]
}
puzzle.seg_random={
-- get random seg from
get_mode="all"
-- "list": uselist segs
-- "all": puzzle segs
}
-- create list containing following segs:
seg_use={
include={
idx={1;puzzle.seg_count};
-- with idx number: 1st and last
--[[
you can also set:
-- ranges instead of single indices:
-- idx={seg_blocks({{1;5}})};
includes segs 1-5
-- idx={seg_blocks({{1;5};{10;20}})};
includes segs 1-5 and 10-20
-- idx={seg_blocks({{1;puzzle.seg_count}})};
includes all segs
-- mix single segs and ranges:
-- idx=seg_use_list_append_all({1;puzzle.seg_count},seg_blocks({{10;20}});
includes 1st seg, last seg and 10-20
--]]
selected={act=true;val=true};
-- if act==true, include:
-- val:
-- true: selected segs
-- false: unselected segs
frozen={act=false;backbone=true;sidechain=false};
-- if act==true:
-- add frozen segs
-- given in backbone and sidechain val
ss={};
-- with secondary structures
-- "L"=loop
-- "H"=helix
-- "E"=sheet
ss_cngs=true;
-- where secondary structure changes occur: yes
aa={"g"}
-- with amino acids
-- here:
-- include glycine
-- example:
-- aa={"f";"g"}
-- include phenylalanine; glycine
};
exclude={
idx={};
-- without idx number: none
selected={act=false;val=false};
-- if act==true, exclude:
-- val:
-- true: selected segs
-- false: unselected segs
frozen={act=false;backbone=true;sidechain=false};
-- if act==true:
-- remove frozen segs
-- given in backbone and sidechain val
-- note:
-- if you turn this on (act==true)
-- and set backbone to false and sidechain to false,
-- all unfrozen segs will be removed!
ss={};
-- without secondary structures
-- "L"=loop
-- "H"=helix
-- "E"=sheet
aa={}
-- without amino acids
-- example:
-- aa={"a";"v";"p";"w"}
-- exclude alanine; valine; proline; tryptophan
};
append_all=false;
-- append all segs to uselist
-- false: append only segs which are not already in list
-- (more to check when adding)
-- true: append all segs (double segs will be removed later when sorting)
-- (more to check when removing)
remove_double=true;
-- remove double segs from uselist after adding
-- true: remove
-- false: don't remove (may not work, depending on how bands are applied)
sort_ascending=true;
-- sort segs in uselist by idx
-- true: lowest idx first
-- false: highest idx first
-- nil (or any other): don't sort
}
gen={max=math.huge}
-- set number of maximum gens to test
herd={
size=8;
-- number of individuums
-- won't grow anymore in this script version
breed={first=3;
-- first mediocre clu to be replaced by breeded one (GA)
-- clus with lower idx will be kept as good ones
last=5;
-- last mediocre clu to be replaced by breeded one (GA)
-- clus with higher idx will be replaced by new random ones (flushed)
mode={
-- breed mode (if EO_mode==0)
-- general breeding behaviour
val="dual";
-- set breed mode
list={
"dual"; -- breed kid from 2 partners only (regular)
-- before breeding, select mom (& dad) once via roulette
-- while breeding, select gene from mom or dad
"poly" -- breed kid from whole herd (each gene can be from any clu)
-- while breeding, select gene-donor for each gene via roulette
-- ignore mom & dad options
}
};
genes_show={
-- show detailed gene information whenn breeding
act=false
-- show which gene is from which clu
-- true: show clu#(parent)
-- false: show possible parents only
};
roulette={
-- roulette wheel selection
dad=false;
-- use for dad (1st breeding partner)
-- true: use
-- false: don't use (1st dad is clu#1. 2ns dad is clu#2 ...)
mom=true;
-- use for mom (2nd breeding partner)
-- true: use
-- false: don't use (mom: next clu# after dad)
israndom=false
-- 'wheel' is complete random
-- true: yes, don't spin wheel - let it jump and ignore clu order
-- special, makes breeding partner selection behave different.
-- -> useful for "poly" breed mode, as genes will be more scattered.
-- fitness is considered, nevertheless
-- false: no, spin wheel - default, as it is usually done in GA.
-- fitness is considered, but more inertially
};
dad_genes_prob=0.55
-- set the (probability) ratio of dad genes (1st breeding partner):
-- float val between 0 and 1
-- <=0 : mom genes only
-- 0.25 : 1/4 dad
-- .5 : 1/2 dad and mom
-- 0.75 : 3/4 dad
-- >=1 : dad genes only
};
mutate={
-- mutate herd cluster bands after breeding
-- (GA-mutation, not equal to: mutate puzzle-segs)
act=true;
-- boolean val
-- true: mutate vals depending on prob.vals
-- false: mutate off (don't mutate breeded bands)
genes_show={
-- show detailed gene information whenn mutating
act=false
-- show which gene is from which clu
-- true: show clu#(parent)
-- false: show possible parents only
};
prob={off=0.25;vals=0.1};
-- mutate probability ratios
-- each: float val between 0 and 1
-- <=0 : never
-- 0.5 : 1/2 of cases
-- >=1 : always
-- off ratio: turn bands off when generated
-- this prevents too many act bands for 1 clu
-- (as a clu loads the complete uselist)
-- example:
-- if you have 10 segments in uselist,
-- and off==0.25,
-- 1/4 of clu band genes (about 2...3 will be off)
-- vals ratio: mutate band vals when breeded
-- toggles (switches) band activity (activated)
-- (off bands -> on and vice versa)
-- changes band vand vals (len, rho, theta, phi)
-- examples for vals:
-- <=0.0 : all band genes will be changed (then you can set herd.mutate.act= false)
-- ==0.1 : 1/10 of band genes
-- ==0.5 : 1/2 of band genes
-- >=1.0 : all band genes will be changed
rng10e={min=-3;max=-1}
-- mutate band float vals (len, rho, phi theta, not segs)
-- by +/-10^(min...max)
-- examples:
-- min=-1;max=-1: change by +/-0.1
-- min=-2;max=-1: change by +/-0.01
-- min=-3;max=-3: change by +/-0.001
-- min=-3;max=-1: change by +/-0.001, +/-0.01 +/-or 0.1
};
EO_mode={
-- Extremal Optimization mode
cur=0;
-- integer val between 0 and 2
-- 0 : no EO - breed clus (regular GAB) (default: keep 2, breed 3, generate 3)
-- 1 : soft EO - keep clus instead of breeding them (default: keep 5, generate 3)
-- 2 : hard EO - flush clus instead of breeding them (default: keep 2, generate 6)
min=0;
-- for gui
max=2
-- for gui
};
flush_gain_thr={
-- flush herd (and load start state)
-- if best gen's clu score gain is less than threshold
act=true;
-- boolean val
-- true: check best gain
-- false: don't check (and flush)
thr=0.5;
-- if score "gain" of best cluster is less than this thr
-- can be negative, too
min=-200;
-- for gui
max=200;
-- for gui
};
flush_gain_reset={
-- reset puzzle to script start state
act=false;
-- boolean val
-- true: reset
-- false: don't reset
cur=0;
-- flush counter
thr=2;
-- reset if flush counter exceeds this thr
min=0;
-- for gui
max=20
-- for gui
};
fitness_e=1;
-- exponent for herd fitness "bending"
-- allows worse clus to appear more often.
-- tweaks distribution of fitness vals
-- by passing them through this exponential function:
-- fitness^(1/fitness_e) is a root function
-- exponent vals <1 bend fitness vals towards 1
-- same is done with small random vals in R= rho^(1/3) calculating section
-- value examples: fitness_e -> fitness val
-- 1/1 (exponent 1): exponent won't have an effect (routine will will be skipped)
-- 1/2 (square root): fitness 0.01 -> 0.1, then normalized
-- 1/3 (cubic root): fitness 0.001 -> 0.1, then normalized
-- ... etc.
use={
state={
-- load state mode
val="recent";
-- set which state will be loaded before each cluster pulling
-- use one of these vals (also for gui):
list={
"start"; -- initial state at script start
-- use if you are really stuck or have a new puzzle
-- never leads to local maximum
"gen"; -- best result of last gen
-- use to allow score dropping
-- and to (hopefully) escape local maximum
"script"; -- script's best (script-internal "recent best")
-- what the script considers as best solution so far
-- may not catch all states (as scores are gotten after single relax/fuse steps)
-- may lead to local maximum
"recent" -- recent best
-- what the game considers as best solution so far
-- catches really best states (also inbetween relax/fuse steps)
-- these states may not be stable
-- may lead to local maximum
}
};
hybrid={
-- hybrid state loading
-- loads selected puzzle state given below,
-- but "start" state each gens to reset puzzle
-- useful if you are really stuck
-- or just beginning
state=nil;
-- nil: don't use hybrid state loading
-- "gen": best result of last gen
-- "script": script's best
-- "recent": recent best
gens=10;
-- load "start" state each this number of gens
-- for all other gens, load state given above
}
}
}
clu={
fix={
-- here you can set some seg-pairs, which dsts should be fixed by a band
-- example:
-- {2;21};
-- {31;46};
-- fix seg2 with seg21 and
-- seg31 with seg46
str=2;
-- set str of fixing bands
lencng=0;
-- lencng=-3.8/4;
-- add this val to measured dst between segs
-- giving fixing band len
-- use vals
-- =0 : just keep dst of segs
-- <0 : do some compression
-- >1 : do some expansion
};
pull={str1=1;str2=0.1};
-- set str of pulling bands
-- str1: primary bands
-- str2: secondary bands (used if d_cng2==act)
band_to={
-- banding mode
val="random2";
-- set where segs of uselist will be banded to
-- use one of these vals (also for gui):
list={
"space"; -- band to space (3D-Angles)
"center"; -- band to seg: center (auto-detection)
"user"; -- band to seg: center (user-defined in puzzle.seg_center.val)
"furthest"; -- band to seg: furthest
"random1"; -- band to seg: random in uselist
"random2" -- band to seg: random in puzzle (classical behaviour)
}
};
rho={min=3.8/8;max=3.8};
-- if band_to.val=="space" (seg to "air")
-- absolute random bandlen minimum and maximum
d_cng1={min=3.8/8;max=3.8};
-- if band_to.val~="space"
-- relative primary random bandlen minimum and maximum
-- resulting band len: current seg dist+min..max
d_cng2={act=true;min=0;max=3.8/8};
-- if act==true
-- relative secondary random bandlen (uselist-seg to uselist-seg)
-- resulting band len=current seg dist+min..max
band_sys_lim={
-- band len allowed (by "system")
min=3.8/2;
-- you can also put 0 here,
-- but then seg to seg bands can get 'too close'
max=10000
-- as said on fold.it homepage
};
seg_min_dist=3.8*1.25;
-- minimum spatial dst for seg-banding
-- (bandable segs detection thr based on dst)
-- segs with dst less than this val are considered as 'too close'
-- use this to prevent:
-- connecting one segment to its index-neighbour, but allow closing cuts
-- creating sharp edges in puzzle
band_skip_wrong=false;
-- set if "wrong bands" are corrected or rejected
-- wrong bands: segs to be banded are too close
-- true: reject actual band
-- (you will have fewer bands, in worst case 0!)
-- false: correct band target segment
-- (will take nearest possible segment)
}
-- set which methods are performed when pulling:
pull={act=true;
CI_default=0.5;
-- if no CI is given
{CI=nil;cmd="w";itr=2;backbone=true;sidechains=false}
-- CI=nil: don't change CI, use current (last) set value
}
-- set which methods are performed at each end of releasing and fusing:
release_n_fuse_end={
-- no
-- act
-- CI_default
-- because this list is appended only
{CI=nil;cmd="m";itr=1};
-- CI=nil: don't change CI, use current (last) set value
{CI=nil;cmd="w";itr=8;backbone=true; sidechains=true};
{CI=nil;cmd="m";itr=1};
{CI=nil;cmd="s";itr=1};
{CI=nil;cmd="w";itr=1;backbone=true; sidechains=true}
}
-- set which methods are performed when releasing:
release={act=false;
-- fast "fuse" (quickstab) activated?
CI_default=1;
-- if no CI is given
{CI=nil;cmd="m";itr=1};
-- CI=nil: don't change CI, use current (last) set value
{CI=nil;cmd="s";itr=1};
{CI=nil;cmd="w";itr=4;backbone=true; sidechains=false};
-- at the end, always the same:
release_n_fuse_end[1];
release_n_fuse_end[2];
release_n_fuse_end[3];
release_n_fuse_end[4];
release_n_fuse_end[5]
}
-- set which methods are performed when fusing:
-- currently there are 4 sequences of methods,
-- but you can change their number (and length for each sequence)
-- to do this,
-- copy and paste a complete sequence (each begins with --fuse#x:)
-- but take care of the braces!
fuse={act=true;
-- complex fuse activated?
CI_default=0.3;
-- if no CI is given
--sequence start:
--fuse#1:
{
{CI=0.1;cmd="m";itr=1};
{CI=nil;cmd="w";itr=4;backbone=true; sidechains=false};
-- CI=nil: don't change CI, use current (last) set value
{CI=0.7;cmd="m";itr=1};
{CI=nil;cmd="s";itr=1};
{CI=nil;cmd="w";itr=4;backbone=true; sidechains=false};
{CI=1.0;cmd="s";itr=0};
-- itr=0: just set CI back to 1
-- at the end, always the same:
release_n_fuse_end[1];
release_n_fuse_end[2];
release_n_fuse_end[3];
release_n_fuse_end[4];
release_n_fuse_end[5]
};
--fuse#2:
{
{CI=0.3;cmd="m";itr=1};
{CI=nil;cmd="w";itr=4;backbone=true; sidechains=false};
-- CI=nil: don't change CI, use current (last) set value
{CI=0.6;cmd="m";itr=1};
{CI=nil;cmd="s";itr=1};
{CI=nil;cmd="w";itr=4;backbone=true; sidechains=false};
{CI=1.0;cmd="s";itr=0};
-- itr=0: just set CI back to 1
release_n_fuse_end[1];
release_n_fuse_end[2];
release_n_fuse_end[3];
release_n_fuse_end[4];
release_n_fuse_end[5]
};
--fuse#3:
{
{CI=0.2;cmd="m";itr=1};
{CI=nil;cmd="w";itr=4;backbone=true; sidechains=false};
-- CI=nil: don't change CI, use current (last) set value
{CI=0.5;cmd="m";itr=1};
{CI=nil;cmd="s";itr=1};
{CI=nil;cmd="w";itr=4;backbone=true; sidechains=false};
{CI=1.0;cmd="s";itr=0};
-- itr=0: just set CI back to 1
release_n_fuse_end[1];
release_n_fuse_end[2];
release_n_fuse_end[3];
release_n_fuse_end[4];
release_n_fuse_end[5]
};
--fuse#4:
{
{CI=0.4;cmd="m";itr=1};
{CI=nil;cmd="w";itr=4;backbone=true; sidechains=false};
-- CI=nil: don't change CI, use current (last) set value
{CI=0.3;cmd="m";itr=1};
{CI=nil;cmd="s";itr=1};
{CI=nil;cmd="w";itr=4;backbone=true; sidechains=false};
{CI=1.0;cmd="s";itr=0};
-- itr=0: just set CI back to 1
release_n_fuse_end[1];
release_n_fuse_end[2];
release_n_fuse_end[3];
release_n_fuse_end[4];
release_n_fuse_end[5]
}
--sequence end:
}
-- debugging section:
function prms_show()
-- a call of this function will show complete content
-- of each table which is uncommented here
-- you can also use this function to print out values,
-- then copy & paste them back into script
list_show_deep(puzzle,"puzzle")
-- list_show_deep(seg_use,"seg_use")
-- list_show_deep(gen,"gen")
-- list_show_deep(herd,"herd")
list_show_deep(clu,"clu")
-- list_show_deep(release_n_fuse_end,"release_n_fuse_end")
-- list_show_deep(release,"release")
-- list_show_deep(fuse,"fuse")
end -- function prms_show()
-- for debugging before prm changes, uncomment this:
-- prms_show()
-- change section:
-- call function to change some variables/tables via gui
prms_cng()
-- show section:
-- for debugging after prm changes, uncomment this:
-- prms_show()
-- launch section:
-- call main program
main()