forked from CorticalComputer/DXNN2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
genotype.erl
759 lines (709 loc) · 37 KB
/
genotype.erl
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
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% This source code and work is provided and developed by Gene I. Sher & DXNN Research Group WWW.DXNNResearch.COM
%
%The original release of this source code and the DXNN MK2 system was introduced and explained in my book: Handbook of Neuroevolution Through Erlang. Springer 2012, print ISBN: 978-1-4614-4462-6 ebook ISBN: 978-1-4614-4463-6.
%
%Copyright (C) 2009 by Gene Sher, DXNN Research Group CorticalComputer@gmail.com
%
% Licensed under the Apache License, Version 2.0 (the "License");
% you may not use this file except in compliance with the License.
% You may obtain a copy of the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS,
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
% See the License for the specific language governing permissions and
% limitations under the License.
%%%%%%%%%%%%%%%%%%%% Deus Ex Neural Network :: DXNN %%%%%%%%%%%%%%%%%%%%
-module(genotype).
-compile(export_all).
-include("records.hrl").
sync()->make:all([load]).
construct_Agent(Specie_Id,Agent_Id,SpecCon)->
random:seed(now()),
Generation = 0,
Encoding_Type = random_element(SpecCon#constraint.agent_encoding_types),
SPlasticity=random_element(SpecCon#constraint.substrate_plasticities),
SLinkform =random_element(SpecCon#constraint.substrate_linkforms),
{Cx_Id,Pattern,Substrate_Id} = construct_Cortex(Agent_Id,Generation,SpecCon,Encoding_Type,SPlasticity,SLinkform),
Agent = #agent{
id = Agent_Id,
encoding_type = Encoding_Type,
cx_id = Cx_Id,
specie_id = Specie_Id,
constraint = SpecCon,
generation = Generation,
pattern = Pattern,
tuning_selection_f = random_element(SpecCon#constraint.tuning_selection_fs),
annealing_parameter = random_element(SpecCon#constraint.annealing_parameters),
tuning_duration_f = SpecCon#constraint.tuning_duration_f,
perturbation_range = random_element(SpecCon#constraint.perturbation_ranges),
mutation_operators = SpecCon#constraint.mutation_operators,
tot_topological_mutations_f = random_element(SpecCon#constraint.tot_topological_mutations_fs),
heredity_type = random_element(SpecCon#constraint.heredity_types),
evo_hist = [],
substrate_id = Substrate_Id
},
write(Agent),
update_fingerprint(Agent_Id).
%The population monitor should have all the information with regards to the morphologies and specie constraint under which the agent's genotype should be created. Thus the construct_Agent/3 is run with the Specie_Id to which this NN based system will belong, the Agent_Id that this NN based intelligent agent will have, and the SpecCon (specie constraint) that will define the list of activation functions and other parameters from which the seed agent can choose its parameters. First the generation is set to 0, since the agent is just created, then the construct_Cortex/3 is ran, which creates the NN and returns its Cx_Id. Once the NN is created and the the cortex's id is returned, we can fill out the information needed by the agent record, and write it to the mnesia database.
construct_Cortex(Agent_Id,Generation,SpecCon,Encoding_Type,SPlasticity,SLinkform)->
Cx_Id = {{origin,generate_UniqueId()},cortex},
Morphology = SpecCon#constraint.morphology,
case Encoding_Type of
neural ->
Sensors = [S#sensor{id={{-1,generate_UniqueId()},sensor},cx_id=Cx_Id,generation=Generation}|| S<- morphology:get_InitSensors(Morphology)],
Actuators = [A#actuator{id={{1,generate_UniqueId()},actuator},cx_id=Cx_Id,generation=Generation}||A<-morphology:get_InitActuators(Morphology)],
%N_Ids=construct_InitialNeuroLayer(Cx_Id,Generation,SpecCon,Sensors,Actuators,[],[]),
[write(S) || S <- Sensors],
[write(A) || A <- Actuators],
{N_Ids,Pattern} = construct_SeedNN(Cx_Id,Generation,SpecCon,Sensors,Actuators,[]),
S_Ids = [S#sensor.id || S<-Sensors],
A_Ids = [A#actuator.id || A<-Actuators],
Cortex = #cortex{
id = Cx_Id,
agent_id = Agent_Id,
neuron_ids = N_Ids,
sensor_ids = S_Ids,
actuator_ids = A_Ids
},
Substrate_Id = undefined;
substrate ->
Substrate_Id={{void,generate_UniqueId()},substrate},
Sensors = [S#sensor{id={{-1,generate_UniqueId()},sensor},cx_id=Cx_Id,generation=Generation,fanout_ids=[Substrate_Id]}|| S<- morphology:get_InitSensors(Morphology)],
Actuators = [A#actuator{id={{1,generate_UniqueId()},actuator},cx_id=Cx_Id,generation=Generation,fanin_ids=[Substrate_Id]}||A<-morphology:get_InitActuators(Morphology)],
[write(S) || S <- Sensors],
[write(A) || A <- Actuators],
Dimensions=calculate_OptimalSubstrateDimension(Sensors,Actuators),
Density = 5,
Depth = 1,
Densities = [Depth,1|lists:duplicate(Dimensions-2,Density)], %[X,Y,Z,T...]
Substrate_CPPs = [CPP#sensor{id={{-1,generate_UniqueId()},sensor},cx_id=Cx_Id,generation=Generation}|| CPP<- morphology:get_InitSubstrateCPPs(Dimensions,SPlasticity)],
Substrate_CEPs = [CEP#actuator{id={{1,generate_UniqueId()},actuator},cx_id=Cx_Id,generation=Generation}||CEP<-morphology:get_InitSubstrateCEPs(Dimensions,SPlasticity)],
%N_Ids=construct_InitialNeuroLayer(Cx_Id,Generation,SpecCon,Substrate_CPPs,Substrate_CEPs,[],[]),
[write(Substrate_CPP) || Substrate_CPP <- Substrate_CPPs],
[write(Substrate_CEP) || Substrate_CEP <- Substrate_CEPs],
{N_Ids,Pattern} = construct_SeedNN(Cx_Id,Generation,SpecCon,Substrate_CPPs,Substrate_CEPs,[]),
%io:format("Sensors:~p~n Actuators:~p~n Substate_CPPs:~p~n Substrate_CEPs:~p~n",[Sensors,Actuators,Substrate_CPPs,Substrate_CEPs]),
S_Ids = [S#sensor.id || S<-Sensors],
A_Ids = [A#actuator.id || A<-Actuators],
CPP_Ids = [CPP#sensor.id || CPP<-Substrate_CPPs],
CEP_Ids = [CEP#actuator.id || CEP<-Substrate_CEPs],
Substrate = #substrate{
id = Substrate_Id,
agent_id = Agent_Id,
cpp_ids = CPP_Ids,
cep_ids = CEP_Ids,
densities = Densities,
plasticity=SPlasticity,
linkform = SLinkform
},
write(Substrate),
Cortex = #cortex{
id = Cx_Id,
agent_id = Agent_Id,
neuron_ids = N_Ids,
sensor_ids = S_Ids,
actuator_ids = A_Ids
}
end,
write(Cortex),
{Cx_Id,Pattern,Substrate_Id}.
%construct_Cortex/3 generates a new Cx_Id, extracts the morphology from the Constraint record passed to it in SpecCon, and then extracts the initial sensors and actuators for that morphology. After the sensors and actuators are extracted, the function calls construct_InitialNeuroLayer/7, which creates a single layer of neurons connected to the specified sensors and actuators, and returns the ids of the created neurons. Finally, the sensors and actuator ids are extracted from the sensors and actuators, and the cortex record is composed and stored to the database.
construct_SeedNN(Cx_Id,Generation,SpecCon,Sensors,[A|Actuators],Acc)->
%Standard neurons results in a single layer connected directly from all sensors and to the set of actuators.
%If only circuits are present (not simply micro of ovl=1), then a single circuit of Vl = actuator.vl is created for each actuator. And then another set of circuits connected to these.
%Outsplice can be applied to anything, except for circuits outputing to actuators, unless they are replaced with circuits of the same VL.
%io:format("SpecCon#constraint.neural_afs:~p~n",[SpecCon#constraint.neural_afs]),
case (length([1|| {circuit,_} <- SpecCon#constraint.neural_afs]) == 0) of
false ->
N2_Id = {{0.99,genotype:generate_UniqueId()},neuron},
InitSpec = #layer{neurode_type=tanh,tot_neurodes=A#actuator.vl,dynamics=dynamic,type=standard},
AF = {circuit,InitSpec},% = generate_NeuronAF([{circuit,IS}||{circuit,IS}<-SpecCon#constraint.neural_afs]),
PF = {PFName,NLParameters} = generate_NeuronPF(SpecCon#constraint.neural_pfns),
Input_IdPs = circuit:create_Circuit([],InitSpec),
Neuron2=#neuron{
id=N2_Id,
cx_id = Cx_Id,
generation=Generation,
af=AF,
pf = PF,
aggr_f=generate_NeuronAggrF(SpecCon#constraint.neural_aggr_fs),
input_idps=Input_IdPs,
output_ids=[],
ro_ids = []
},
write(Neuron2),
N1_Id = {{0,genotype:generate_UniqueId()},neuron},
construct_Neuron(Cx_Id,Generation,SpecCon,N1_Id,[],[]),
link_Neuron(Generation,[S#sensor.id||S<-Sensors],N1_Id,[N2_Id]),
genome_mutator:link_FromElementToElement(Generation,N2_Id,A#actuator.id),
N_Ids = [N1_Id,N2_Id];
true ->
N_Ids = [{{0,genotype:generate_UniqueId()},neuron}||_<-lists:seq(1,A#actuator.vl)],
Result=[construct_Neuron(Cx_Id,Generation,SpecCon,N_Id,[],[])||N_Id<-N_Ids],
[link_Neuron(Generation,[S#sensor.id||S<-Sensors],N_Id,[A#actuator.id])||N_Id<-N_Ids]
end,
construct_SeedNN(Cx_Id,Generation,SpecCon,Sensors,Actuators,lists:append(N_Ids,Acc));
construct_SeedNN(_Cx_Id,_Generation,_SpecCon,_Sensors,[],Acc)->
{lists:reverse(Acc),create_InitPattern(Acc)}.
create_InitPattern([Id|Ids])->
{{LI,_},_} = Id,
create_InitPattern(Ids,LI,[Id],[]).
create_InitPattern([Id|Ids],CurIndex,CurIndexAcc,PatternAcc)->
{{LI,_},_} = Id,
case LI == CurIndex of
true ->
create_InitPattern(Ids,CurIndex,[Id|CurIndexAcc],PatternAcc);
false ->
create_InitPattern(Ids,LI,[Id],[{CurIndex,CurIndexAcc}|PatternAcc])
end;
create_InitPattern([],CurIndex,CurIndexAcc,PatternAcc)->
lists:sort([{CurIndex,CurIndexAcc}|PatternAcc]).
link_Neuron(Generation,From_Ids,N_Id,To_Ids)->
%io:format("Link_Neuron:~p~n",[{From_Ids,N_Id,To_Ids}]),
[genome_mutator:link_FromElementToElement(Generation,From_Id,N_Id) || From_Id <-From_Ids],
[genome_mutator:link_FromElementToElement(Generation,N_Id,To_Id) || To_Id <- To_Ids].
construct_Neuron(Cx_Id,Generation,SpecCon,N_Id,Input_Specs,Output_Ids)->
PF = {PFName,NLParameters} = generate_NeuronPF(SpecCon#constraint.neural_pfns),
AF = generate_NeuronAF(SpecCon#constraint.neural_afs),
%io:format("AF:~p~n",[AF]),
Input_IdPs = case AF of
{circuit,InitSpec} ->
%io:format("InitSpec:~p~n",[InitSpec]),
circuit:create_Circuit(Input_Specs,InitSpec);
_ ->
create_InputIdPs(PFName,Input_Specs,[])
end,
%io:format("Input_IdPs:~p~n",[Input_IdPs]),
Neuron=#neuron{
id=N_Id,
cx_id = Cx_Id,
generation=Generation,
af=AF,
pf = PF,
aggr_f=generate_NeuronAggrF(SpecCon#constraint.neural_aggr_fs),
input_idps=Input_IdPs,
output_ids=Output_Ids,
ro_ids = calculate_ROIds(N_Id,Output_Ids,[])
},
write(Neuron).
create_InputIdPs(PF,[{Input_Id,Input_VL}|Input_IdPs],Acc) ->
WeightsP = create_NeuralWeightsP(PF,Input_VL,[]),
create_InputIdPs(PF,Input_IdPs,[{Input_Id,WeightsP}|Acc]);
create_InputIdPs(_PF,[],Acc)->
Acc.
create_NeuralWeightsP(_PFName,0,Acc) ->
Acc;
create_NeuralWeightsP(PFName,Index,Acc) ->
WP = {random:uniform()-0.5,0,0,plasticity:PFName(weight_parameters)},
create_NeuralWeightsP(PFName,Index-1,[WP|Acc]).
create_weight(none)->
{random:uniform()-0.5,0,0};
create_weight(PFName)->
random:uniform()-0.5.
%Each neuron record is composed by the construct_Neuron/6 function. The construct_Neuron/6 creates the Input list from the tuples [{Id,Weights}...] using the vector lengths specified in the Input_Specs list. The create_InputIdPs/3 function uses create_NeuralWeightsP/2 to generate a tuple list with random weights in the range of -0.5 to 0.5, and plasticity parameters dependent on the PF function. The activation function that the neuron uses is chosen randomly from the neural_afs list within the constraint record passed to the construct_Neuron/6 function. construct_Neuron uses calculate_ROIds/3 to extract the list of recursive connection ids from the Output_Ids passed to it. Once the neuron record is filled in, it is saved to the database.
generate_NeuronAF(Activation_Functions)->
case Activation_Functions of
[] ->
tanh;
Other ->
lists:nth(random:uniform(length(Other)),Other)
end.
%The generate_NeuronAF/1 accepts a list of activation function tags, and returns a randomly chosen one. If an empty list was passed as the parameter, the function returns the default tanh tag.
generate_NeuronPF(PFNames)->
case PFNames of
[] ->
{none,[]};
Other ->
PFName = lists:nth(random:uniform(length(Other)),Other),
NLParameters = plasticity:PFName(neural_parameters),
{PFName,NLParameters}
end.
%The generate_NeuronPF/1 accepts a list of plasticity function tags, and returns a randomly chosen one. If an empty list was passed as the parameter, the function returns the default none tag.
generate_NeuronAggrF(Aggregation_Functions)->
case Aggregation_Functions of
[] ->
none;
Other ->
lists:nth(random:uniform(length(Other)),Other)
end.
%The generate_NeuronAggrF/1 accepts a list of aggregation function tags, and returns a randomly chosen one. If an empty list was passed as the parameter, the function returns the default dot_product tag.
calculate_ROIds(Self_Id,[Output_Id|Ids],Acc)->
case Output_Id of
{_,actuator} ->
calculate_ROIds(Self_Id,Ids,Acc);
Output_Id ->
{{TLI,_},_NodeType} = Self_Id,
{{LI,_},_} = Output_Id,
case LI =< TLI of
true ->
calculate_ROIds(Self_Id,Ids,[Output_Id|Acc]);
false ->
calculate_ROIds(Self_Id,Ids,Acc)
end
end;
calculate_ROIds(_Self_Id,[],Acc)->
lists:reverse(Acc).
%The function calculate_ROIds/3 accepts as input the Self_Id of the neuron, and the Output_Ids of the elements the neuron connects to. Since each element specifies its type and, in the case of neurons, specifies the layer index it belongs to, the function checks if the Output_Id's layer index is lower than the Self_Id's layer index, if it is, the output connection is recursive and the Output_Id is added to the recursive output list. Once the recursive connection ids have been extracted from the Output_Ids, the extracted id list is returned to the caller.
generate_ids(0,Acc) ->
Acc;
generate_ids(Index,Acc)->
Id = generate_UniqueId(),
generate_ids(Index-1,[Id|Acc]).
generate_UniqueId()->
{MegaSeconds,Seconds,MicroSeconds} = now(),
1/(MegaSeconds*1000000 + Seconds + MicroSeconds/1000000).
%The generate_UniqueId/0 creates a unique Id using current time, the Id is a floating point value. The generate_ids/2 function creates a list of unique Ids.
random_element(List)->
lists:nth(random:uniform(length(List)),List).
%The random_element/1 function accepts a list as input, and returns a single, randomly chosen element as output.
calculate_OptimalSubstrateDimension(Sensors,Actuators)->
S_Formats = [S#sensor.format || S<-Sensors],
A_Formats = [A#actuator.format || A<-Actuators],
extract_maxdim(S_Formats++A_Formats,[]) + 2.
%
extract_maxdim([F|Formats],Acc)->
DS=case F of
{symmetric,Dims}->
length(Dims);
no_geo ->
1;
undefined ->
1
end,
extract_maxdim(Formats,[DS|Acc]);
extract_maxdim([],Acc)->
lists:max(Acc).
%
update_fingerprint(Agent_Id)->
A = read({agent,Agent_Id}),
%io:format("A:~p~n",[A]),
Cx = read({cortex,A#agent.cx_id}),
GeneralizedSensors = [(read({sensor,S_Id}))#sensor{id=undefined,cx_id=undefined,fanout_ids=[],generation=undefined} || S_Id<-Cx#cortex.sensor_ids],
GeneralizedActuators = [(read({actuator,A_Id}))#actuator{id=undefined,cx_id=undefined,fanin_ids=[],generation=undefined} || A_Id<-Cx#cortex.actuator_ids],
GeneralizedPattern = [{LayerIndex,length(LNIds)}||{LayerIndex,LNIds}<-A#agent.pattern],
GeneralizedEvoHist = generalize_EvoHist(A#agent.evo_hist,[]),
N_Ids = Cx#cortex.neuron_ids,
{Tot_Neuron_ILs,Tot_Neuron_OLs,Tot_Neuron_ROs,AF_Distribution} = get_NodeSummary(N_Ids),
Type = A#agent.encoding_type,
TopologySummary = #topology_summary{
type = Type,
tot_neurons = length(N_Ids),
tot_n_ils = Tot_Neuron_ILs,
tot_n_ols = Tot_Neuron_OLs,
tot_n_ros = Tot_Neuron_ROs,
af_distribution = AF_Distribution},
Fingerprint = {GeneralizedPattern,GeneralizedEvoHist,GeneralizedSensors,GeneralizedActuators,TopologySummary},
write(A#agent{fingerprint=Fingerprint}).
%update_fingerprint/1 calculates the fingerprint of the agent, where the fingerprint is just a tuple of the various general features of the NN based system, a list of features that play some role in distinguishing its genotype's general properties from those of other NN systems. The fingerprint here is composed of the generalized pattern (pattern minus the unique ids), generalized evolutionary history (evolutionary history minus the unique ids of the elements), a generalized sensor set, and a generalized actuator set.-record(topology_summary,{type,tot_neurons,tot_n_ils,tot_n_ols,tot_n_ros,af_distribution}).
generalize_EvoHist([{MO,{{ALI,_AUId},AType},{{BLI,_BUId},BType},{{CLI,_CUId},CType}}|EvoHist],Acc)->
generalize_EvoHist(EvoHist,[{MO,{ALI,AType},{BLI,BType},{CLI,CType}}|Acc]);
generalize_EvoHist([{MO,{{ALI,_AUId},AType},{{BLI,_BUId},BType}}|EvoHist],Acc)->
generalize_EvoHist(EvoHist,[{MO,{ALI,AType},{BLI,BType}}|Acc]);
generalize_EvoHist([{MO,{{ALI,_AUId},AType}}|EvoHist],Acc)->
generalize_EvoHist(EvoHist,[{MO,{ALI,AType}}|Acc]);
generalize_EvoHist([{MO,_EId}|EvoHist],Acc)->
generalize_EvoHist(EvoHist,[{MO}|Acc]);
generalize_EvoHist([],Acc)->
lists:reverse(Acc).
%generalize_EvoHist/2 generalizes the evolutionary history tuples by removing the unique element ids. Two neurons which are using exactly the same activation function, located exactly in the same layer, and using exactly the same weights will still have different unique ids, thus these ids must be removed to produce a more general set of tuples. There are 3 types of tuples in evo_hist list, with 3, 2 and 1 element ids. Once the evolutionary history list is generalized, it is returned to the caller.
update_NNTopologySummary(Agent_Id)->%TODO: If the node is a circuit, then tot_neurons has to be calculated differently
A = genotype:read({agent,Agent_Id}),
Cx_Id = A#agent.cx_id,
Cx = genotype:read({cortex,Cx_Id}),
N_Ids = Cx#cortex.neuron_ids,
{Tot_Neuron_ILs,Tot_Neuron_OLs,Tot_Neuron_ROs,AF_Distribution} = get_NodeSummary(N_Ids),
Type = A#agent.encoding_type,
Topology_Summary = #topology_summary{
type = Type,
tot_neurons = length(N_Ids),
tot_n_ils = Tot_Neuron_ILs,
tot_n_ols = Tot_Neuron_OLs,
tot_n_ros = Tot_Neuron_ROs,
af_distribution = AF_Distribution},
Topology_Summary.
get_NodeSummary(N_Ids)->
get_NodeSummary(N_Ids,0,0,0,[]).
get_NodeSummary([N_Id|N_Ids],ILAcc,OLAcc,ROAcc,FunctionDistribution)->
N = genotype:read({neuron,N_Id}),
%AF = N#neuron.af,
%io:format("AF:~p~n Inpt_IdPs:~p~n",[AF,N#neuron.input_idps]),
IL_Count = case N#neuron.af of
{circuit,{micro,_}}->
AF = micro,
length((N#neuron.input_idps)#circuit.i);
{circuit,Layer} ->
AF=Layer#layer.type,
length((N#neuron.input_idps)#circuit.i);
AF ->
length(N#neuron.input_idps)
end,
OL_Count = length(N#neuron.output_ids),
RO_Count = length(N#neuron.ro_ids),
U_FunctionDistribution = case lists:keyfind(AF,1,FunctionDistribution) of
{AF,Count} ->
lists:keyreplace(AF,1,FunctionDistribution,{AF,Count+1});
false ->
[{AF,1}|FunctionDistribution]
end,
get_NodeSummary(N_Ids,IL_Count+ILAcc,OL_Count+OLAcc,RO_Count+ROAcc,U_FunctionDistribution);
get_NodeSummary([],ILAcc,OLAcc,ROAcc,FunctionDistribution)->
%io:format("FunctoinDistribution:~p~n",[FunctionDistribution]),
{ILAcc,OLAcc,ROAcc,FunctionDistribution}.
read(TnK)->
case mnesia:read(TnK) of
[] ->
undefined;
[R] ->
R
end.
%read/1 accepts the tuple composed of a table name and a key: {TableName,Key}, which it then uses to read from the mnesia database and return the record to the caller. write/1 accepts a record and writes it to the database. delete/1 accepts a the tuple {TableName,Key}, and deletes the associated record from the table.
dirty_read(TnK)->
case mnesia:dirty_read(TnK) of
[] ->
undefined;
[R] ->
R
end.
write(R)->
F = fun()->
mnesia:write(R)
end,
mnesia:transaction(F).
dirty_write(R)->
mnesia:dirty_write(R).
delete(TnK)->
F = fun()->
mnesia:delete(TnK)
end,
mnesia:transaction(F).
dirty_delete(TnK)->
mnesia:dirty_delete(TnK).
print(Agent_Id)->
F = fun()->
A = read({agent,Agent_Id}),
Cx = read({cortex,A#agent.cx_id}),
io:format("~p~n",[A]),
io:format("~p~n",[Cx]),
[io:format("~p~n",[read({sensor,Id})]) || Id <- Cx#cortex.sensor_ids],
[io:format("~p~n",[read({neuron,Id})]) || Id <- Cx#cortex.neuron_ids],
[io:format("~p~n",[read({actuator,Id})]) || Id <- Cx#cortex.actuator_ids],
case A#agent.substrate_id of
undefined ->
ok;
Substrate_Id->
Substrate = read({substrate,Substrate_Id}),
io:format("~p~n",[Substrate]),
[io:format("~p~n",[read({sensor,Id})]) || Id <- Substrate#substrate.cpp_ids],
[io:format("~p~n",[read({actuator,Id})]) || Id <- Substrate#substrate.cep_ids]
end
end,
mnesia:transaction(F).
%print/1 accepts an agent's id, and prints out the complete genotype of that agent.
print_ListForm(Agent_Id)->
%{ok, File_Output} = file:open(FileName, write),
{ok, File_Output} = file:open(atom_to_list(Agent_Id)++".agent", write),
A = dirty_read({agent,Agent_Id}),
Cx = dirty_read({cortex,A#agent.cx_id}),
Sensors = [dirty_read({sensor,Id}) || Id <- Cx#cortex.sensor_ids],
Neurons = [dirty_read({neuron,Id}) || Id <- Cx#cortex.neuron_ids],
Actuators = [dirty_read({actuator,Id}) || Id <- Cx#cortex.actuator_ids],
print_Sensors(Sensors,File_Output),
print_Neurons(Neurons,File_Output),
print_Actuators(Actuators,File_Output).
print_Sensors([S|Sensors],File_Output)->
io:format(File_Output,"~p:",[S#sensor.id]),
%[io:format(File_Output," ~p",[N_Id])||N_Id<-S#sensor.fanout_ids],
io:format(File_Output,"~n",[]),
print_Sensors(Sensors,File_Output);
print_Sensors([],_File_Output)->
ok.
print_Neurons([N|Neurons],File_Output)->
io:format(File_Output,"~p:",[N#neuron.id]),
[{io:format(File_Output," ~p#",[From_Id]),[io:format(File_Output," ~p",[W]) || {W,_,_,_}<-WeightsP]} ||{From_Id,WeightsP}<-N#neuron.input_idps],
io:format(File_Output,"~n",[]),
%io:format(File_Output,"~p:",[N#neuron.id]),
%[io:format(File_Output," ~p",[O_Id])||O_Id<-N#neuron.output_ids],
%io:format(File_Output,"~n",[]),
print_Neurons(Neurons,File_Output);
print_Neurons([],_File_Output)->
ok.
print_Actuators([A|Actuators],File_Output)->
io:format(File_Output,"~p:",[A#actuator.id]),
[io:format(File_Output," ~p",[I_Id])||I_Id<-A#actuator.fanin_ids],
io:format(File_Output,"~n",[]),
print_Actuators(Actuators,File_Output);
print_Actuators([],_File_Output)->
ok.
delete_Agent(Agent_Id)->
A = genotype:read({agent,Agent_Id}),
Cx = genotype:read({cortex,A#agent.cx_id}),
[genotype:dirty_delete({neuron,Id}) || Id <- Cx#cortex.neuron_ids],
[genotype:dirty_delete({sensor,Id}) || Id <- Cx#cortex.sensor_ids],
[genotype:dirty_delete({actuator,Id}) || Id <- Cx#cortex.actuator_ids],
genotype:dirty_delete({cortex,A#agent.cx_id}),
genotype:dirty_delete({agent,Agent_Id}),
case A#agent.substrate_id of
undefined ->
ok;
Substrate_Id ->
Substrate = genotype:dirty_read({substrate,Substrate_Id}),
[genotype:dirty_delete({sensor,Id}) || Id <- Substrate#substrate.cpp_ids],
[genotype:dirty_delete({actuator,Id})|| Id <- Substrate#substrate.cep_ids],
genotype:dirty_delete({substrate,Substrate_Id})
end.
%delete_Agent/1 accepts the id of an agent, and then delets that agent's genotype. This function assumes that the id of the agent will be removed from the specie's agent_ids list, and any other clean up procedures, by the calling function.
delete_Agent(Agent_Id,safe)->
F = fun()->
A = genotype:read({agent,Agent_Id}),
S = genotype:read({specie,A#agent.specie_id}),
Agent_Ids = S#specie.agent_ids,
%DeadPool = S#specie.dead_pool,
genotype:write(S#specie{agent_ids = lists:delete(Agent_Id,Agent_Ids)}),
delete_Agent(Agent_Id)
end,
Result=mnesia:transaction(F),
%io:format("delete_agent(Agent_Id,safe):~p Result:~p~n",[Agent_Id,Result]),
ok.
%delete_Agent/2 accepts the id of an agent, and then delets that agent's genotype, but ensures that the specie to which the agent belongs, has its agent_ids element updated. Unlinke delete_Agent/1, this function updates the specie record.
clone_Agent(Agent_Id)->
CloneAgent_Id = {generate_UniqueId(),agent},
%io:format("Inside clone_agent: Agent_Id:~p CloneAgent_Id~p~n",[Agent_Id,CloneAgent_Id]),
clone_Agent(Agent_Id,CloneAgent_Id).
clone_Agent(Agent_Id,CloneAgent_Id)->
A = dirty_read({agent,Agent_Id}),
%io:format("Agent:~p~n",[A]),
Cx = dirty_read({cortex,A#agent.cx_id}),
IdsNCloneIds = ets:new(idsNcloneids,[set,private]),
ets:insert(IdsNCloneIds,{bias,bias}),
ets:insert(IdsNCloneIds,{Agent_Id,CloneAgent_Id}),
[CloneCx_Id] = map_ids(IdsNCloneIds,[A#agent.cx_id],[]),
CloneN_Ids = map_ids(IdsNCloneIds,Cx#cortex.neuron_ids,[]),
CloneS_Ids = map_ids(IdsNCloneIds,Cx#cortex.sensor_ids,[]),
CloneA_Ids = map_ids(IdsNCloneIds,Cx#cortex.actuator_ids,[]),
case A#agent.substrate_id of
undefined ->
clone_neurons(IdsNCloneIds,Cx#cortex.neuron_ids),
clone_sensors(IdsNCloneIds,Cx#cortex.sensor_ids),
clone_actuators(IdsNCloneIds,Cx#cortex.actuator_ids),
U_EvoHist=map_EvoHist(IdsNCloneIds,A#agent.evo_hist),
dirty_write(Cx#cortex{
id = CloneCx_Id,
agent_id = CloneAgent_Id,
sensor_ids = CloneS_Ids,
actuator_ids = CloneA_Ids,
neuron_ids = CloneN_Ids
}),
dirty_write(A#agent{
id = CloneAgent_Id,
cx_id = CloneCx_Id,
offspring_ids = [],
evo_hist = U_EvoHist
});
Substrate_Id ->
Substrate = dirty_read({substrate,A#agent.substrate_id}),
[CloneSubstrate_Id] = map_ids(IdsNCloneIds,[A#agent.substrate_id],[]),
CloneCPP_Ids = map_ids(IdsNCloneIds,Substrate#substrate.cpp_ids,[]),
CloneCEP_Ids = map_ids(IdsNCloneIds,Substrate#substrate.cep_ids,[]),
clone_neurons(IdsNCloneIds,Cx#cortex.neuron_ids),
clone_sensors(IdsNCloneIds,Cx#cortex.sensor_ids),
clone_actuators(IdsNCloneIds,Cx#cortex.actuator_ids),
Substrate = dirty_read({substrate,A#agent.substrate_id}),
clone_sensors(IdsNCloneIds,Substrate#substrate.cpp_ids),
clone_actuators(IdsNCloneIds,Substrate#substrate.cep_ids),
U_EvoHist=map_EvoHist(IdsNCloneIds,A#agent.evo_hist),
dirty_write(Substrate#substrate{
id = CloneSubstrate_Id,
agent_id = CloneAgent_Id,
cpp_ids = CloneCPP_Ids,
cep_ids = CloneCEP_Ids
}),
dirty_write(Cx#cortex{
id = CloneCx_Id,
agent_id = CloneAgent_Id,
sensor_ids = CloneS_Ids,
actuator_ids = CloneA_Ids,
neuron_ids = CloneN_Ids
}),
dirty_write(A#agent{
id = CloneAgent_Id,
cx_id = CloneCx_Id,
substrate_id = CloneSubstrate_Id,
evo_hist = U_EvoHist,
offspring_ids=[]
})
end,
ets:delete(IdsNCloneIds),
CloneAgent_Id.
%clone_Agent/2 accepts Agent_Id, and CloneAgent_Id, and then clones the agent, giving the clone CloneAgent_Id. The function first creates an ETS table to which it writes the ids of all the elements of the genotype, and their corresponding clone ids. Once all ids and clone ids have been generated, the function then begins to clone the actual elements. clone_Agent/2 first clones the neurons using clone_neurons/2, then the sensors using clone_sensonrs/2, and finally the actuators using clone_actuators. Once these elements are cloned, the function writes to database the clone versions of the cortex and the agent records, by writing to databse the original records with updated ids.
map_ids(TableName,[Id|Ids],Acc)->
CloneId=case Id of
{{LayerIndex,_NumId},Type}->%maps neuron and cortex ids.
{{LayerIndex,generate_UniqueId()},Type};
{_NumId,Type}->%mapes sensor and actuator ids.
{generate_UniqueId(),Type}
end,
ets:insert(TableName,{Id,CloneId}),
map_ids(TableName,Ids,[CloneId|Acc]);
map_ids(_TableName,[],Acc)->
Acc.
%map_ids/3 accepts the name of the ets table, and a list of ids. It then goes through every id and creates a clone version of the id by generating a new unique id. The function is able to generate new id structures for neuron, cortex, sensor, and actuator id types.
clone_sensors(TableName,[S_Id|S_Ids])->
S = read({sensor,S_Id}),
CloneS_Id = ets:lookup_element(TableName,S_Id,2),
CloneCx_Id = ets:lookup_element(TableName,S#sensor.cx_id,2),
CloneFanout_Ids =[ets:lookup_element(TableName,Fanout_Id,2)|| Fanout_Id <- S#sensor.fanout_ids],
write(S#sensor{
id = CloneS_Id,
cx_id = CloneCx_Id,
fanout_ids = CloneFanout_Ids
}),
clone_sensors(TableName,S_Ids);
clone_sensors(_TableName,[])->
done.
%clone_sensors/2 accepts as input the name of the ets table and the list of sensor ids. It then goes through every sensor id, reads the sensor from the database, and updates all the ids (id, cx_id, and fanout_ids) from their original values, to their clone values stored in the ets table. Then the new version of the sensor is written to the database.
clone_actuators(TableName,[A_Id|A_Ids])->
A = read({actuator,A_Id}),
CloneA_Id = ets:lookup_element(TableName,A_Id,2),
CloneCx_Id = ets:lookup_element(TableName,A#actuator.cx_id,2),
CloneFanin_Ids =[ets:lookup_element(TableName,Fanin_Id,2)|| Fanin_Id <- A#actuator.fanin_ids],
write(A#actuator{
id = CloneA_Id,
cx_id = CloneCx_Id,
fanin_ids = CloneFanin_Ids
}),
clone_actuators(TableName,A_Ids);
clone_actuators(_TableName,[])->
done.
%clone_actuators/2 accepts as input the name of the ets table and the list of actuator ids. It then goes through every actuator id, reads the actuator from the database, and updates all the ids (id, cx_id, and fanin_ids) from their original values, to their clone values stored in the ets table. Then the new version of the actuator is written to the database.
clone_neurons(TableName,[N_Id|N_Ids])->
N = read({neuron,N_Id}),
CloneN_Id = ets:lookup_element(TableName,N_Id,2),
CloneCx_Id = ets:lookup_element(TableName,N#neuron.cx_id,2),
CloneInput_IdPs = case N#neuron.af of
{circuit,_} ->
C = N#neuron.input_idps,
U_I=[{ets:lookup_element(TableName,I_Id,2),IVL}|| {I_Id,IVL} <- C#circuit.i],
C#circuit{i=U_I};
_ ->
[{ets:lookup_element(TableName,I_Id,2),WeightsP}|| {I_Id,WeightsP} <- N#neuron.input_idps]
end,
CloneInput_IdPs_Modulation = [{ets:lookup_element(TableName,I_Id,2),WeightsP}|| {I_Id,WeightsP} <- N#neuron.input_idps_modulation],
CloneOutput_Ids = [ets:lookup_element(TableName,O_Id,2)|| O_Id <- N#neuron.output_ids],
CloneRO_Ids =[ets:lookup_element(TableName,RO_Id,2)|| RO_Id <- N#neuron.ro_ids],
write(N#neuron{
id = CloneN_Id,
cx_id = CloneCx_Id,
input_idps = CloneInput_IdPs,
input_idps_modulation = CloneInput_IdPs_Modulation,
output_ids = CloneOutput_Ids,
ro_ids = CloneRO_Ids
}),
clone_neurons(TableName,N_Ids);
clone_neurons(_TableName,[])->
done.
%clone_neuron/2 accepts as input the name of the ets table and the list of neuron ids. It then goes through every neuron id, reads the neuron from the database, and updates all the ids (id, cx_id, output_ids, ro_ids) and input_idps from their original values, to their clone values stored in the ets table. Once the everything is updated, the new (clone) version of the neuron is written to the database.
map_EvoHist(TableName,EvoHist)->
%io:format("EvoHist:~p~n",[EvoHist]),
map_EvoHist(TableName,EvoHist,[]).
map_EvoHist(TableName,[{MO,E1Id,E2Id,E3Id}|EvoHist],Acc)->
%io:format("1:~p~n",[{MO,E1Id,E2Id,E3Id}]),
Clone_E1Id = ets:lookup_element(TableName,E1Id,2),
Clone_E2Id = ets:lookup_element(TableName,E2Id,2),
Clone_E3Id = ets:lookup_element(TableName,E3Id,2),
map_EvoHist(TableName,EvoHist,[{MO,Clone_E1Id,Clone_E2Id,Clone_E3Id}|Acc]);
map_EvoHist(TableName,[{MO,E1Id,E2Id}|EvoHist],Acc)->
%io:format("2:~p~n",[{MO,E1Id,E2Id}]),
Clone_E1Id = ets:lookup_element(TableName,E1Id,2),
Clone_E2Id = ets:lookup_element(TableName,E2Id,2),
map_EvoHist(TableName,EvoHist,[{MO,Clone_E1Id,Clone_E2Id}|Acc]);
map_EvoHist(TableName,[{MO,E1Ids}|EvoHist],Acc) when is_list(E1Ids) ->
%io:format("E1Ids:~p~n",[{MO,E1Ids}]),
Clone_E1Ids = [ets:lookup_element(TableName,E1Id,2) || E1Id <- E1Ids],
%io:format("Clone_E1Ids:~p~n",[Clone_E1Ids]),
map_EvoHist(TableName,EvoHist,[{MO,Clone_E1Ids}|Acc]);
map_EvoHist(TableName,[{MO,E1Id}|EvoHist],Acc)->
%io:format("3:~p~n",[{MO,E1Id}]),
Clone_E1Id = ets:lookup_element(TableName,E1Id,2),
map_EvoHist(TableName,EvoHist,[{MO,Clone_E1Id}|Acc]);
map_EvoHist(_TableName,[],Acc)->
%io:format("4~n"),
lists:reverse(Acc);
map_EvoHist(TableName,Uknown,Acc)->
io:format("Severe crash in map_EvoHist, can't find the proper pattern match:~p~n",[{TableName,Uknown,Acc}]),
exit("AHHHHHHHHH").
%map_EvoHist/2 is a wrapper for map_EvoHist/3, which in turn accepts the evo_hist list containing the mutation operator tuples that have been appplied to the NN system. The function is used when a clone of a NN system is created. The function updates the original Ids of the elements the mutation oeprators have been applied to, to the clone's Ids, so that the updated evo_hist can reflect the clone's topology, as if the mutation operators have been applied to it, and that it is not a clone. Once all the tuples in the evo_hist have been updated with the clone element ids, the list is reverted to its proper order, and the updated list is returned to the caller.
speciate(Agent_Id)->
update_fingerprint(Agent_Id),
A = read({agent,Agent_Id}),
case A#agent.id of
test ->%Test agent belongs to no specie and no population.
write(A#agent{fitness = undefined});
_ ->
Parent_S = read({specie,A#agent.specie_id}),
P = read({population,Parent_S#specie.population_id}),
case [Id || Id <- P#population.specie_ids, (read({specie,Id}))#specie.fingerprint == A#agent.fingerprint] of
[] ->
Specie_Id = population_monitor:create_specie(P#population.id,A#agent.constraint,A#agent.fingerprint),
S = read({specie,Specie_Id}),
U_A = A#agent{specie_id=Specie_Id,fitness = undefined},
U_S = S#specie{agent_ids = [Agent_Id]},
write(U_A),
write(U_S);
[Specie_Id] ->
S = read({specie,Specie_Id}),
U_A = A#agent{specie_id=Specie_Id,fitness = undefined},
U_S = S#specie{agent_ids = [Agent_Id|S#specie.agent_ids]},
write(U_A),
write(U_S)
end
end.
%The function speciate/1 reads a newly created agent record, calculates that agent's fingerprint, and then based on that fingerprint either inserts it into an already existing specie, or creates a new specie of which the agent is the first of a kind. The function first creates the fingerprint of the agent using the genotype:create_fingerprint/1 function. Then the function checks whether this is a test agent, in which case it is only used for testing, and does not belong to any specie or population. If the agent is not a test agent, then the specie and population to which its parent belonged is retreived from the database (the specie and population ids are conserved in the offspring during mutation, so the agent already holds his parent's specie and population ids). Afterwards, a specie is found which has the same fingerprint as the agent. If there is no such specie, then a new specie is created, a specie that belongs to the same population as the agent, and has the same constriants and fingerprint as the agent fathering the specie. Then the agent's id is entered into the specie, and the updated specie and agent are written to database. If on the other hand a specie already exists with the same fingerprint as the agent, then the agent's id is added to the existing specie, and the updated specie and agent are written to database.
test()->
Specie_Id = test,
Agent_Id = test,
CloneAgent_Id = test_clone,
SpecCon = #constraint{morphology=multiobjective_PoleBalancing,connection_architecture=recurrent, population_evo_alg_f=generational,neural_afs=[tanh],agent_encoding_types=[neural],substrate_plasticities=[none]},
F = fun()->
construct_Agent(Specie_Id,Agent_Id,SpecCon),
clone_Agent(Specie_Id,CloneAgent_Id),
print(Agent_Id),
print(CloneAgent_Id),
delete_Agent(Agent_Id),
delete_Agent(CloneAgent_Id)
end,
mnesia:transaction(F).
%test/0 performs a test of the standard functions of the genotype module, by first creating a new agent, then cloning that agent, then printing the genotype of the original agent and its clone, and then finally deleting both of the agents.
create_test()->
Specie_Id = test,
Agent_Id = test,
%SpecCon = #constraint{},
SpecCon = #constraint{
morphology=targetNavigation,
connection_architecture=recurrent,
population_evo_alg_f=generational,
neural_afs=[tanh],
%neural_afs=[{circuit,{micro,[#layer{neurode_type=tanh,tot_neurodes=2,dynamics=dynamic},#layer{neurode_type=tanh,tot_neurodes=1,dynamics=static}]}}],
%neural_afs = [{circuit,#layer{neurode_type=tanh,tot_neurodes=1,type=standard}}],
%neural_afs = [{circuit,#layer{neurode_type=tanh,tot_neurodes=10,dynamics=dynamic,type=dae}}],
agent_encoding_types=[neural],
substrate_plasticities=[none],
neural_pfns=[none],
heredity_types = [darwinian] %[darwinian,lamarckian]
},
F = fun()->
case genotype:read({agent,test}) of
undefined ->
construct_Agent(Specie_Id,Agent_Id,SpecCon),
print(Agent_Id);
_ ->
delete_Agent(Agent_Id),
construct_Agent(Specie_Id,Agent_Id,SpecCon),
print(Agent_Id)
end
end,
mnesia:transaction(F).
%create_test/0 creates a simple NN based agent using the default constraint record. The function first checks if an agent with the id 'test' already exists, if it does, the function deletes that agent and creates a new one. Otherwise, the function just creates a brand new agent with the 'test' id.