-
Notifications
You must be signed in to change notification settings - Fork 3
/
naneat.go
5519 lines (5148 loc) · 190 KB
/
naneat.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
MIT License
Copyright (c) 2019 문동선 (NaniteFactory)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package naneat
import (
"encoding/json"
"errors"
"fmt"
"image"
"image/color"
"io/ioutil"
"log"
"math"
"math/rand"
"reflect"
"runtime/debug"
"sort"
"strconv"
"sync"
"time"
"github.com/campoy/unique"
"github.com/gofrs/uuid"
"gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/simple"
"gonum.org/v1/gonum/graph/topo"
)
// ----------------------------------------------------------------------------
// Package
// Initialization of this package.
func init() {
fmt.Println() // The standard out is flushed.
rand.Seed(time.Now().UTC().UnixNano()) // Seed for random mutations etc.
}
// ----------------------------------------------------------------------------
// Experimenter
// Experimenter exposes a set of user-friendly methods.
// Facade of this library.
type Experimenter interface {
Self() *Universe
Status() UniverseStatusGetter
RegisterMeasurer(agent Measurer)
UnregisterMeasurer(agent Measurer)
Run() error
Shutdown()
IsPumping() bool
}
// UniverseStatusGetter exposes a set of thread-safe universe state getters.
type UniverseStatusGetter interface {
Info() (
conf Configuration, topFitness float64,
population, numSpecies, generation, innovation, nicheCount int,
)
Measurers() (ret map[Measurer]*Subscribed)
Breeds() []*Species
Organisms() []*Organism
BiasGenes() []*NodeGene
NonBiasInputGenes() []*NodeGene
GetInputGenes() []*NodeGene
GetOutputGenes() []*NodeGene
}
// New is where everything starts.
//
// These two are the same:
// - func New(config *Configuration) Experimenter
// - func (config *Configuration) New() Experimenter
//
func New(config *Configuration) Experimenter {
return config.New()
}
// ----------------------------------------------------------------------------
// Measurer
// Measurer is a consumer interface for what measures an organism's fitness.
// It could be an emulator thread or a network server or any test case.
// Implement it however you'd like.
type Measurer interface {
// Notice the side effect of this method as an impure function that
// with fitness it returns it might also update the state of our NN.
MeasureAsync(organism []*NeuralNetwork) (postman Swallow, err error)
String() string
}
// Swallow is a bird that will deliver our fortune.
// Or it's a channel our organism's fitness is transferred over.
type Swallow <-chan float64
// Agent is a Measurer: Agent implements Measurer interface.
type Agent struct {
name string
chReceive chan []*NeuralNetwork
chSend chan float64
isWorking bool
}
// NewAgent is a constructor.
func NewAgent() *Agent {
return &Agent{
name: "Agent",
chReceive: make(chan []*NeuralNetwork, 1),
chSend: make(chan float64),
isWorking: false,
}
}
// String callback of Agent implements Measurer.
func (a *Agent) String() string {
return a.name
}
// Close all channels of this agent.
//
// Effects of closing channels:
// - Channels are closed.
// - The other goroutine is notified of the fact that the data flow was shut quite tough.
// - Possible memory leaks are prevented in case our GC is working so lazy.
//
func (a *Agent) Close() {
close(a.chReceive)
close(a.chSend)
}
// Send blocks the thread.
// The supplier uses this method.
func (a *Agent) Send(fitness float64) {
a.chSend <- fitness
a.isWorking = false
}
// Receive blocks the thread.
// The supplier uses this method.
func (a *Agent) Receive() (brains []*NeuralNetwork) {
a.isWorking = true
brains = <-a.chReceive
return brains
}
// IsWorking when this agent as a supplier thinks it is working,
// not when the consumer thinks it is.
// The consumer might use this method.
func (a *Agent) IsWorking() bool {
return a.isWorking
}
// Measure blocks the thread.
// The consumer might use this method.
func (a *Agent) Measure(brains []*NeuralNetwork) (fitness float64) {
a.chReceive <- brains
return <-a.chSend
}
// MeasureAsync does not block the thread.
// The consumer uses this method.
func (a *Agent) MeasureAsync(brains []*NeuralNetwork) (messenger Swallow, err error) {
select {
case a.chReceive <- brains:
return a.chSend, nil
default:
return nil, errors.New("failed to make an order: this agent has already been ordered")
}
}
// ----------------------------------------------------------------------------
// Configuration
// Configuration stores the universe's constants. A set of hyper parameters.
type Configuration struct {
// Label for the universe
ExperimentName string
// Max number of creatures
SizePopulation int
// The percentage bottom which percentage of organisms in a species is culled off every epoch.
// The range of this value is [0, 1].
PercentageCulling float64
// The Nth generation the stagnant species is going to face its extermination. xD
// The species not improving over this much of generations will get penalized.
// Negative value for this means infinity.
MaxCntArmageddon int // Age threshold to dropoff. Rotten species are not allowed to reproduce.
// If the top fitness of the entire universe does not improve for more than this much of generations,
// only the top two species are allowed to reproduce, refocusing the search into the most promising spaces.
// Negative value for this means infinity.
MaxCntApocalypse int // Threshold to the universe's stagnancy.
// If false, entire population of a species besides the champion is replaced by their offsprings at each reproduce procedure.
// If true, there are un-culled organisms in species: reproducible organisms are saved and lives on to next generation.
IsProtectiveElitism bool
IsFitnessRemeasurable bool // If true, the fitness of every organism that's already measured gets remeasured in the measuring stage of each generation.
// Ratio of different reproduction methods performed upon breeding species
ScaleCrossoverMultipointRnd int // Sexual reproduction 1
ScaleCrossoverMultipointAvg int // Sexual reproduction 2
ScaleCrossoverSinglepointRnd int // Sexual reproduction 3
ScaleCrossoverSinglepointAvg int // Sexual reproduction 4
ScaleFission int // Asexual breeding (Binary fission)
// Base of M-Rate (ChanceGene)
ChanceIsMutational bool // Genetic(dynamic) or constant.
ChanceAddNode float64 // Add-Node mutation chance.
ChanceAddLink float64 // Add-Link mutation chance only for non-bias nodes.
ChanceAddBias float64 // Add-Link mutation chance only for bias.
ChancePerturbWeight float64 // Synaptic weight mutation chance.
ChanceNullifyWeight float64 // Mutation chance of synaptic weight becoming zero.
ChanceTurnOn float64 // Enable mutation chance.
ChanceTurnOff float64 // Disable mutation chance.
ChanceBump float64 // Bump mutation chance.
// What defines the synaptic weight mutation.
// This is a size factor determining the deviation of a Gaussian distribution.
TendencyPerturbWeight float64 // The mean(offset). Set to 0.0 by default.
TendencyPerturbDice float64 // The mean(offset). Set by default to a very small positive value.
StrengthPerturbWeight float64 // Adjusted deviation. Synaptic weight is perturbed by this percent of it.
StrengthPerturbDice float64 // Adjusted deviation. Mutational rate is perturbed by this percent of it.
// Measuring the compatibility distance
CompatThreshold float64 // The line separating species.
CompatIsNormalizedForSize bool // The characterizing variables of disjointedness and excess get expressed in normalized percent, or, rather in the absolute which ignores the size of two genetic encoders.
CompatCoeffDisjoint float64 // The maximum disjointedness when expressed in normalized percent or simply a multiplier to the number of disjoint genes a chromosome has.
CompatCoeffExcess float64 // The maximum excessiveness when expressed in normalized percent or simply a multiplier to the number of excessive genes a chromosome has.
CompatCoeffWeight float64 // The maximum or simply a multiplier regarding the mutational difference of weights.
CompatCoeffChance float64 // The maximum or simply a multiplier regarding the mutational difference of chance gene's.
// Seeds of genetics (Genome/Chromosome)
NumChromosomes int // The number of Chromosome-s for a single Genome. The number of NNs a single Organism consists of.
NumBiases int // The number of biases.
NumNonBiasInputs int // The number of input nodes. This doesn't count for biases.
NumOutputs int // The number of output nodes.
}
// NewConfiguration is a constructor.
// This simply creates an empty object.
// Feel free to edit what's returned however you'd like.
func NewConfiguration() *Configuration {
return &Configuration{}
}
// NewConfigurationSimple is a constructor.
// It returns what's filled up with a default setting.
//
// A set of params I used to test with:
// - nNet = 2
// - nIn = 38 * 28
// - nOut = 7
//
func NewConfigurationSimple(nNet, nIn, nOut int) *Configuration {
return &Configuration{
ExperimentName: "UU",
SizePopulation: 400,
PercentageCulling: 0.7,
MaxCntArmageddon: 8,
MaxCntApocalypse: 20,
IsProtectiveElitism: true,
IsFitnessRemeasurable: false,
//
ScaleCrossoverMultipointRnd: 8, // 80%
ScaleCrossoverMultipointAvg: 1, // 10%
ScaleCrossoverSinglepointRnd: 0, // 0%
ScaleCrossoverSinglepointAvg: 0, // 0%
ScaleFission: 1, // 10%
//
ChanceIsMutational: true,
ChanceAddNode: 0.5, // 50%
ChanceAddLink: 4.0, // 400%
ChanceAddBias: 0.9, // 90%
ChancePerturbWeight: 1.0, // 100%
ChanceNullifyWeight: 0.0, // 0%
ChanceTurnOn: 0.1, // 10%
ChanceTurnOff: 0.4, // 40%
ChanceBump: 0.1, // 10%
TendencyPerturbWeight: 0.0, // 0%p (val offset +)
TendencyPerturbDice: 0.001, // 0.1%p (val offset +)
StrengthPerturbWeight: 0.15, // 15% of 50%
StrengthPerturbDice: 0.15, // 15%
//
CompatThreshold: 10.0,
CompatIsNormalizedForSize: true,
CompatCoeffDisjoint: 20.0,
CompatCoeffExcess: 20.0,
CompatCoeffWeight: 10.0,
CompatCoeffChance: 10.0,
//
NumChromosomes: nNet,
NumBiases: 1,
NumNonBiasInputs: nIn,
NumOutputs: nOut,
}
}
// BirthRatioSimplified returns the breeding methods ratio in smallest integers.
func (config *Configuration) BirthRatioSimplified() (
weightCrossoverMultipointRnd,
weightCrossoverMultipointAvg,
weightCrossoverSinglepointRnd,
weightCrossoverSinglepointAvg,
weightFission,
weightSterile float64,
) {
r1 := config.ScaleCrossoverMultipointRnd
r2 := config.ScaleCrossoverMultipointAvg
r3 := config.ScaleCrossoverSinglepointRnd
r4 := config.ScaleCrossoverSinglepointAvg
r5 := config.ScaleFission
gcd := GCD(r1, r2, r3, r4, r5)
weightCrossoverMultipointRnd = float64(r1 / gcd)
weightCrossoverMultipointAvg = float64(r2 / gcd)
weightCrossoverSinglepointRnd = float64(r3 / gcd)
weightCrossoverSinglepointAvg = float64(r4 / gcd)
weightFission = float64(r5 / gcd)
return
}
// NewUniverse is a constructor.
func (config *Configuration) NewUniverse() *Universe {
retUniv := &Universe{
Config: *config,
//
ChStopSign: make(chan chan struct{}),
IsRunning: false,
MutexRun: sync.Mutex{},
//
Agents: map[Measurer]*Subscribed{},
MutexAgents: sync.Mutex{},
//
Livings: map[*Organism]struct{}{},
Classes: []*Species{},
MutexLivings: sync.Mutex{},
MutexClasses: sync.Mutex{},
TopFitness: 0,
Generation: 0,
Innovation: 0,
NicheCount: 0,
//
InputGenes: make([]*NodeGene, config.NumBiases+config.NumNonBiasInputs),
OutputGenes: make([]*NodeGene, config.NumOutputs),
}
// sow
{ // inputs
nBias := config.NumBiases
for i := 0; i < nBias; i++ {
retUniv.InputGenes[i] = NewNodeGene("bias_"+strconv.Itoa(i), InputNodeBias, i)
}
for i := nBias; i < len(retUniv.InputGenes); i++ {
retUniv.InputGenes[i] = NewNodeGene("primal_in_"+strconv.Itoa(i-nBias), InputNodeNotBias, i)
}
}
for i := 0; i < len(retUniv.OutputGenes); i++ { // outputs
retUniv.OutputGenes[i] = NewNodeGene("primal_out_"+strconv.Itoa(i), OutputNode, i)
}
// creation + speciation
for len(retUniv.Livings) < retUniv.Config.SizePopulation {
newLife, err := retUniv.NewOrganismBasic()
if err != nil {
// log.Println("fatal:", err) // debug //
panic(err)
}
if err := retUniv.AddOrganism(newLife); err != nil {
// log.Println("fatal:", err) // debug //
panic(err)
}
if err := retUniv.Speciate(newLife); err != nil {
// log.Println("fatal:", err) // debug //
panic(err)
}
}
// The initial generation doesn't get more than a chance.
for _, s := range retUniv.Classes {
s.Stagnancy = retUniv.Config.MaxCntArmageddon
}
return retUniv
}
// New is where everything starts.
//
// These two are the same:
// - func New(config *Configuration) Experimenter
// - func (config *Configuration) New() Experimenter
//
func (config *Configuration) New() Experimenter {
return config.NewUniverse()
}
// ----------------------------------------------------------------------------
// Ark (Import)
// Ark JSON export of Universe. Backup state data.
type Ark struct {
ReferenceByUUID map[uuid.UUID]*NodeGene
Classes []*Species
TopFitness float64
Generation int
Innovation int
NicheCount int
Config Configuration
InputGenes []*NodeGene
OutputGenes []*NodeGene
}
// NewArkFromFile loads one universe state from a JSON file.
// Data in.
func NewArkFromFile(filepath string) (*Ark, error) {
jsonRaw, err := ioutil.ReadFile(filepath)
if err != nil {
return nil, err
}
var glove Ark
err = json.Unmarshal(jsonRaw, &glove)
if err != nil {
return nil, err
}
// log.Println(glove) // debug //
return &glove, nil
}
// New is the coolest constructor where everything starts.
func (pack *Ark) New() (ret Experimenter, err error) {
return pack.NewUniverse()
}
// NewUniverse is a constructor.
// Data out.
func (pack *Ark) NewUniverse() (retUniv *Universe, err error) {
retUniv = &Universe{
Config: pack.Config,
//
ChStopSign: make(chan chan struct{}),
IsRunning: false,
MutexRun: sync.Mutex{},
//
Agents: map[Measurer]*Subscribed{},
MutexAgents: sync.Mutex{},
//
Livings: map[*Organism]struct{}{},
Classes: pack.Classes,
TopFitness: pack.TopFitness,
Generation: pack.Generation,
Innovation: pack.Innovation,
NicheCount: pack.NicheCount,
//
InputGenes: pack.InputGenes,
OutputGenes: pack.OutputGenes,
}
// IO nodes
for i, nodeGene := range retUniv.InputGenes { // NodeGenesFromUUID
retUniv.InputGenes[i] = pack.ReferenceByUUID[nodeGene.UUID]
}
for i, nodeGene := range retUniv.OutputGenes { // NodeGenesFromUUID
retUniv.OutputGenes[i] = pack.ReferenceByUUID[nodeGene.UUID]
}
ioNodeGenes := func() []*NodeGene {
ret := append([]*NodeGene{}, retUniv.InputGenes...)
ret = append(ret, retUniv.OutputGenes...)
return ret
}()
// Links & Hidden nodes
for _, species := range retUniv.Classes {
for _, organism := range species.Livings {
// Genome (chrome)
for _, chrome := range organism.GenoType().Chromosomes() {
chrome.IONodeGenes = append([]*NodeGene{}, ioNodeGenes...)
for i, linkGene := range chrome.LinkGenes {
chrome.LinkGenes[i].Topo.From = pack.ReferenceByUUID[linkGene.Topo.From.UUID]
chrome.LinkGenes[i].Topo.To = pack.ReferenceByUUID[linkGene.Topo.To.UUID]
}
chrome.Sort()
}
// Phenome
phenotype := make([]*NeuralNetwork, organism.GenoType().Length())
for iChrome, chromosome := range organism.GenoType().Chromosomes() {
phenotype[iChrome], err = chromosome.NewNeuralNetwork()
if err != nil {
return nil, err
}
}
organism.Phenotype = phenotype
// After that...
organism.Breed = species // Speciate
retUniv.Livings[organism] = struct{}{} // Populate
}
}
return retUniv, nil
}
// ----------------------------------------------------------------------------
// Universe
// Universe is NEAT context in the highest level,
// which gets you an access to all available training data of a training session.
// It is strongly recommended not to access any of these members directly outside this package,
// unless you understand what those really are.
// Otherwise use this struct's methods and its constructor instead.
type Universe struct {
// Static info relates to all creations and destructions.
Config Configuration // Constants.
// Trivial runtime settings.
ChStopSign chan chan struct{} // Private used only for the Shutdown().
IsRunning bool // What tells whether this universe is actually running or not.
MutexRun sync.Mutex // Sync tool used only for Experimenter implementations. For IsRunning bool.
// Interface to talk to other possible modules.
Agents map[Measurer]*Subscribed // A set of agents measuring fitnesses of our creatures.
MutexAgents sync.Mutex // Sync tool used only for Experimenter implementations.
// Regarding space we have in this universe.
Livings map[*Organism]struct{} // A set of all creatures available in this universe.
Classes []*Species // Biological category of creatures.
TopFitness float64 // Of an organism in this universe.
MutexLivings sync.Mutex // Sync tool used only for Experimenter implementations.
MutexClasses sync.Mutex // Sync tool used only for Experimenter implementations.
// Regarding time how far we've been through.
// These can only go forward and there is no way for them to be winded back besides resetting the whole universe.
// So what's not necessary at all: Any method that would decrement any of these global (innovation/historical) number.
Generation int // The Nth generation. We could say the age of this universe is N generation old.
Innovation int // The innovation number, which should be/is global throughout this universe.
NicheCount int // This is a counter that tells exactly how many of niches have appeared since the creation of this universe. Niche is an identifier historical and unique for each of species in this universe.
// The ancestor of all creatures born and raised in this universe.
InputGenes []*NodeGene
OutputGenes []*NodeGene
}
// ----------------------------------------------------------------------------
// Universe - Subscribed (Component)
// Subscribed of a Measurer.
type Subscribed struct {
// Measurer receives an NN to be evaluated.
S Swallow // Channel assigned to a Measurer.
O *Organism // The creature being measured by a Measurer.
}
// NewSubscribed is a constructor.
func NewSubscribed(mailFrom Swallow, mailTo *Organism) *Subscribed {
return &Subscribed{
S: mailFrom,
O: mailTo,
}
}
// String callback of this.
func (subbed *Subscribed) String() string {
return fmt.Sprint(*subbed)
}
// ----------------------------------------------------------------------------
// Universe - Export
// NewArk of this universe.
func (univ *Universe) NewArk() *Ark {
return &Ark{
ReferenceByUUID: univ.NodeGenesByUUID(nil),
Classes: univ.Classes,
TopFitness: univ.TopFitness,
Generation: univ.Generation,
Innovation: univ.Innovation,
NicheCount: univ.NicheCount,
Config: univ.Config,
InputGenes: univ.InputGenes,
OutputGenes: univ.OutputGenes,
}
}
// NodeGenesByUUID returns a map of all node genes available in this Universe.
// The parameter can be nil.
func (univ *Universe) NodeGenesByUUID(fillMe map[uuid.UUID]*NodeGene) (filledOut map[uuid.UUID]*NodeGene) {
if fillMe == nil {
filledOut = map[uuid.UUID]*NodeGene{}
} else {
filledOut = fillMe
}
for _, species := range univ.Classes {
for _, organism := range species.Livings {
filledOut = organism.GenoType().NodeGenesByUUID(filledOut)
}
}
for _, nodeGene := range univ.InputGenes {
filledOut[nodeGene.UUID] = nodeGene
}
for _, nodeGene := range univ.OutputGenes {
filledOut[nodeGene.UUID] = nodeGene
}
return filledOut
}
// Save this universe to a JSON file.
func (univ *Universe) Save(filename string) (err error) {
// copy
jsonRawBackup, err := json.Marshal(univ.NewArk())
if err != nil {
return err
}
// dump
err = ioutil.WriteFile(filename, jsonRawBackup, 0644)
if err != nil {
return err
}
return nil
}
// ----------------------------------------------------------------------------
// Universe - Run (EP)
// Self implements the Experimenter interface.
func (univ *Universe) Self() *Universe {
return univ
}
// Status implements the Experimenter interface.
func (univ *Universe) Status() UniverseStatusGetter {
return univ
}
// RegisterMeasurer to this universe.
func (univ *Universe) RegisterMeasurer(agent Measurer) {
univ.MutexAgents.Lock()
defer univ.MutexAgents.Unlock()
univ.Agents[agent] = nil
}
// UnregisterMeasurer to this universe.
func (univ *Universe) UnregisterMeasurer(agent Measurer) {
univ.MutexAgents.Lock()
defer univ.MutexAgents.Unlock()
delete(univ.Agents, agent)
}
// Run this universe. Entry point to our GA.
// *This* procedure of *this* universe can be run only one at a time.
// Otherwise it meets the only condition this can error.
// Give this blocking synchronous function a thread or a goroutine.
func (univ *Universe) Run() error {
// init
univ.MutexRun.Lock()
if univ.IsRunning {
univ.MutexRun.Unlock()
return errors.New("already running")
}
univ.IsRunning = true
univ.MutexRun.Unlock()
// clean up
defer func() {
univ.MutexRun.Lock()
defer univ.MutexRun.Unlock()
univ.IsRunning = false
}()
// handled error
type shutdownError struct {
error
chReply chan struct{}
}
newShutdownError := func(chReply chan struct{}) *shutdownError {
return &shutdownError{errors.New("stop signed"), chReply}
}
// Def.
measure := func(univ *Universe) error {
// Note: univ.Agents map[Measurer]Subscribed // 1 // We know by this, the number of Measurers and their work info.
mapSetMeasuringBirds := map[Swallow]Measurer{} // 2 // Messengers in a set. So we get an idea about the number of students being evaluated(mailed).
setOrgFroshesYetMeasured := map[*Organism]struct{}{} // 3 // A set temporarily storing newbs to be evaluated. (So we can count them.)
// Set newbs.
univ.MutexLivings.Lock()
for organism := range univ.Livings {
if univ.Config.IsFitnessRemeasurable || !organism.IsFitnessMeasured() {
setOrgFroshesYetMeasured[organism] = struct{}{}
}
}
univ.MutexLivings.Unlock()
for len(mapSetMeasuringBirds) > 0 || len(setOrgFroshesYetMeasured) > 0 {
select {
case chReply := <-univ.ChStopSign: // don't reply and throw that to other routine
return newShutdownError(chReply) // Stop running this measure().
default:
// Noop. Do nothing.
}
{ // 1. Send enough
univ.MutexAgents.Lock()
for measurer, postman := range univ.Agents {
if postman != nil {
continue
}
for newbie := range setOrgFroshesYetMeasured {
bird, err := measurer.MeasureAsync(newbie.Phenotype[:])
if err != nil { // 'This agent is already working' error.
// log.Println("fatal:", err) // debug //
panic(err)
}
log.Println("started measuring:", newbie) //
// 123
univ.Agents[measurer] = NewSubscribed(bird, newbie) // 1
mapSetMeasuringBirds[bird] = measurer // 2
delete(setOrgFroshesYetMeasured, newbie) // 3
//
break // Only a single item is retrieved from this map.
}
}
univ.MutexAgents.Unlock()
}
{ // 2. Recv one
if !(len(mapSetMeasuringBirds) > 0) {
continue
}
birdCases := make([]reflect.SelectCase, len(mapSetMeasuringBirds)+1)
{ // dynamic N cases
birdCases[0] = reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(univ.ChStopSign),
}
i := 1
for messenger := range mapSetMeasuringBirds {
birdCases[i] = reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(messenger),
}
i++
}
}
// select case of dynamic N cases
if iMessenger, itfMessageFitness, notClosed := reflect.Select(birdCases); notClosed { // This blocks the thread.
if iMessenger == 0 { // if univ.ChStopSign
chReply := itfMessageFitness.Interface().(chan struct{}) // don't reply and throw that to other routine
return newShutdownError(chReply) // Stop running this measure().
}
univ.MutexAgents.Lock()
// set vars
messenger := birdCases[iMessenger].Chan.Interface().(Swallow)
messageFitness := itfMessageFitness.Float()
measurer := mapSetMeasuringBirds[messenger]
organism := univ.Agents[measurer].O
// update
organism.UpdateFitness(messageFitness)
univ.TopFitness = math.Max(univ.TopFitness, messageFitness)
log.Println("measured:", organism) //
// 123
univ.Agents[measurer] = nil // 1
delete(mapSetMeasuringBirds, messenger) // 2
// 3 // 3 is already done.
univ.MutexAgents.Unlock()
} else { // This must be errornous case - a channel close.
if iMessenger == 0 { // if univ.ChStopSign
panic(fmt.Sprint(birdCases[iMessenger], " must not be closed"))
}
univ.MutexAgents.Lock()
log.Println("warning: a closed channel is selected") //
messenger := birdCases[iMessenger].Chan.Interface().(Swallow)
measurer := mapSetMeasuringBirds[messenger]
delete(univ.Agents, measurer) // 1 // unregister measurer
delete(mapSetMeasuringBirds, messenger) // 2
// 3 // 3 is already done.
// End of abnormal event handler.
univ.MutexAgents.Unlock()
}
}
// spin loop
// for messenger, organism := range mapSetMeasuringBirds {
// select {
// case fitness := <-messenger:
// organism.UpdateFitness(fitness)
// delete(mapSetMeasuringBirds, messenger)
// default:
// // Noop. Do nothing.
// }
// }
} // for
univ.Save("akashic." + univ.Config.ExperimentName + "." + strconv.Itoa(univ.Generation) + ".measured.json")
return nil
}
extinctify := func(univ *Universe) {
// Update species.
univ.MutexClasses.Lock()
for _, group := range univ.Classes {
// Check stagnancy.
_ /*stagnancy*/, _ /*topFitnessOfSpecies*/, err := group.EvaluateGeneration() // update stagnancy and topFitness
if err != nil {
// log.Println("fatal:", err) // debug //
panic(err)
}
}
univ.MutexClasses.Unlock()
// Presidential election.
boss, inPlaceOfBoss := func(univ *Universe) (immuneToExtinction *Species, vicePresident *Species) { // get one leading species
univ.MutexClasses.Lock()
sortByProminence, err := univ.GetSpeciesOrderedByProminence()
univ.MutexClasses.Unlock()
if err != nil {
// log.Println("fatal:", err) // debug //
panic(err)
}
if len(sortByProminence) >= 2 {
return sortByProminence[0], sortByProminence[1]
}
return sortByProminence[0], nil // assuming at least one species is there in univ.
}(univ)
log.Println("top leading species:", boss) //
if inPlaceOfBoss != nil {
log.Println("second leading species:", inPlaceOfBoss) //
}
// The black spot distribution.
const (
_ = iota
extinctionReasonOverStagnantGlobal
extinctionReasonOverStagnantLocal
extinctionReasonTooWeakAvgFitness
extinctionReasonTooWeakTopFitness
extinctionReasonTooWeakHomeAlone
)
sumFitAvgAdj, fitAvgsAdj := univ.AdjAvgFitnessesOfSpecies() // reads
sumFitTopAdj, fitTopsAdj := univ.AdjTopFitnessesOfSpecies() // reads
indicesSpeciesExtinct := []int{}
reasonsSpeciesExtinct := map[*Species]int{}
univ.MutexClasses.Lock()
for iGroup, group := range univ.Classes { // univ.Classes because of univ.AverageFitnessesOfSpecies()
// Blackspot because RemoveSpecies() modifies `univ.Classes` thus can't be called while iterating over it.
if group == boss { // the boss is immune to any of these death conditions
continue
}
if univ.Config.MaxCntApocalypse >= 0 && boss.Stagnancy > univ.Config.MaxCntApocalypse { // Apocalypse not Armageddon
if group != inPlaceOfBoss {
indicesSpeciesExtinct = append(indicesSpeciesExtinct, iGroup)
reasonsSpeciesExtinct[group] = extinctionReasonOverStagnantGlobal
}
continue
}
if univ.Config.MaxCntArmageddon >= 0 && group.Stagnancy > univ.Config.MaxCntArmageddon { // general over-stagnant
indicesSpeciesExtinct = append(indicesSpeciesExtinct, iGroup)
reasonsSpeciesExtinct[group] = extinctionReasonOverStagnantLocal
continue
}
if 1 > int(math.Floor((fitAvgsAdj[iGroup]/sumFitAvgAdj)*float64(univ.Config.SizePopulation))) { // too weak in avg-fitness to even become a species with at least 1 population
indicesSpeciesExtinct = append(indicesSpeciesExtinct, iGroup)
reasonsSpeciesExtinct[group] = extinctionReasonTooWeakAvgFitness
continue
}
if 1 > int(math.Floor((fitTopsAdj[iGroup]/sumFitTopAdj)*float64(univ.Config.SizePopulation))) { // too weak in top-fitness to even become a species with at least 1 population
indicesSpeciesExtinct = append(indicesSpeciesExtinct, iGroup)
reasonsSpeciesExtinct[group] = extinctionReasonTooWeakTopFitness
continue
}
if group.Size() < 2 && group.Stagnancy > 0 { // species with only a single population doesn't need to get more than one chance
indicesSpeciesExtinct = append(indicesSpeciesExtinct, iGroup)
reasonsSpeciesExtinct[group] = extinctionReasonTooWeakHomeAlone
continue
}
}
univ.MutexClasses.Unlock()
// Extinction.
classesRemovedAndEmpty, expelledOrgsByClasses, err := univ.RemoveClasses(indicesSpeciesExtinct...) // should be called outside the univ.Classes iteration.
if err != nil {
// log.Println("fatal:", err) // debug //
panic(err)
}
for iClass, expelledOrgs := range expelledOrgsByClasses {
classEmptied := classesRemovedAndEmpty[iClass]
for _, organismExpelled := range expelledOrgs {
if err := univ.RemoveOrganism(organismExpelled); err != nil {
// log.Println("fatal:", err) // debug //
panic(err)
}
}
switch reasonsSpeciesExtinct[classEmptied] {
case extinctionReasonOverStagnantGlobal:
log.Println(len(expelledOrgs), "creature(s) of", classEmptied.Niche(), "got annihilated for this entire universe being stagnant over", univ.Config.MaxCntApocalypse, "generation(s) (extinction)") //
case extinctionReasonOverStagnantLocal:
log.Println(len(expelledOrgs), "creature(s) of", classEmptied.Niche(), "got annihilated for being locally stagnant over", univ.Config.MaxCntArmageddon, "generation(s) (extinction)") //
case extinctionReasonTooWeakAvgFitness:
log.Println(len(expelledOrgs), "creature(s) of", classEmptied.Niche(), "got annihilated for being too weak in avg-fitness (extinction)") //
case extinctionReasonTooWeakTopFitness:
log.Println(len(expelledOrgs), "creature(s) of", classEmptied.Niche(), "got annihilated for being too weak in top-fitness (extinction)") //
case extinctionReasonTooWeakHomeAlone:
log.Println(len(expelledOrgs), "creature(s) of", classEmptied.Niche(), "got annihilated for being too weak with only a single population (extinction)") //
default:
panic("extinction reason unjustified")
}
}
log.Println(len(classesRemovedAndEmpty), "classification(s) got annihilated for being stagnant or being too weak (extinction)") //
}
cull := func(univ *Universe) {
// Wiki: In biology, culling is the process of segregating organisms from
// a group according to desired or undesired characteristics.
univ.MutexClasses.Lock()
for _, zoo := range univ.Classes {
eliminated := zoo.Cull(univ.Config.PercentageCulling)
log.Println(len(eliminated), "creature(s) got culled off of", zoo.Niche()) //
for _, organism := range eliminated {
univ.RemoveOrganism(organism)
}
}
univ.MutexClasses.Unlock()
}
reproduce := func(univ *Universe) (orphans []*Organism) {
// fitnesses
sumFitAvgAdj, fitAvgsAdj := univ.AdjAvgFitnessesOfSpecies()
// orphans
orphans = []*Organism{}
nSizeOrphanageForNewbs := func() int {
// assuming this is after the culling and the extinction.
if univ.Config.IsProtectiveElitism {
return univ.Config.SizePopulation - len(univ.Livings)
}
return univ.Config.SizePopulation - len(univ.Classes)
}()
// orphanage 1
univ.MutexClasses.Lock()
for i, s := range univ.Classes {
if nOffsprings := int(math.Floor((fitAvgsAdj[i] / sumFitAvgAdj) * float64(nSizeOrphanageForNewbs))); nOffsprings > 0 {
babies, err := univ.NewOrganismBrood(s, nOffsprings)
if err != nil {
// log.Println("fatal:", err) // debug //
panic(err)
}
orphans = append(orphans, babies...)
}
}
univ.MutexClasses.Unlock()
log.Println("reproduced", len(orphans), "new offspring(s)", "and", nSizeOrphanageForNewbs-len(orphans), "filler(s) will be") //
// orphanage 2
for len(orphans) < nSizeOrphanageForNewbs {
newFiller, err := univ.SpeciesRandom().Champion().GenoType().Copy()
if err != nil {
// log.Println("fatal:", err) // debug //
panic(err)
}
univ.Mutate(newFiller)
o, err := newFiller.NewOrganismSimple()
if err != nil {
// log.Println("fatal:", err) // debug //
panic(err)
}
orphans = append(orphans, o)
}
log.Println("total of", len(orphans), "creature(s) reproduced") //
if !univ.Config.IsProtectiveElitism {
univ.MutexClasses.Lock()
for _, zoo := range univ.Classes { // re-culling process in reproduce method
eliminated := zoo.CullToSinglePopulation()
log.Println(len(eliminated), "elite creature(s) of", zoo.Niche(), "are retired") //
for _, organism := range eliminated {
univ.RemoveOrganism(organism)
}
}
univ.MutexClasses.Unlock()
}
// test
if nSizeOrphanageForNewbs != len(orphans) {
panic(fmt.Sprint(
fmt.Sprintln("fatal miscalculation: your math is way off"),
fmt.Sprintln(
"nSizeOrphanageForNewbs ->", nSizeOrphanageForNewbs,
"len(orphans) ->", len(orphans),
),
fmt.Sprintln(
"sumFitAvgAdj ->", sumFitAvgAdj,
"fitAvgsAdj ->", fitAvgsAdj,
),
))
}
// return
return orphans
}
speciate := func(univ *Universe, orphans ...*Organism) {
for _, orphan := range orphans {
if orphan == nil {
continue
}
if err := univ.AddOrganism(orphan); err != nil {
// log.Println("fatal:", err) // debug //
panic(err)
}
if err := univ.Speciate(orphan); err != nil {
// log.Println("fatal:", err) // debug //
panic(err)
}
// log.Println("speciated:", orphan) //
}
}
forwardGeneration := func(univ *Universe) {
univ.Generation++
univ.Save("akashic." + univ.Config.ExperimentName + "." + strconv.Itoa(univ.Generation) + ".json")