-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathSGPWorld.h
More file actions
1052 lines (896 loc) · 39.1 KB
/
Copy pathSGPWorld.h
File metadata and controls
1052 lines (896 loc) · 39.1 KB
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
#ifndef SGPWORLD_H
#define SGPWORLD_H
#include "../default_mode/SymWorld.h"
#include "Scheduler.h"
#include "SGPConfigSetup.h"
#include "SGPHost.h"
#include "SGPSymbiont.h"
#include "org_type_info.h"
#include "ReproductionQueue.h"
#include "ProgramBuilder.h"
#include "SGPMutator.h"
#include "tasks/LogicTaskEnvironment.h"
#include "hardware/SGPHardwareSpec.h"
#include "hardware/GenomeLibrary.h"
#include "hardware/SGPHardware.h"
#include "events/EventManager.h"
#include "emp/Evolve/World_structure.hpp"
#include "emp/data/DataNode.hpp"
#include "emp/math/Random.hpp"
#include <functional>
#include <filesystem>
// TODO - Document how each base configuration works for SGPWorld
// TODO - Comments for every member variable / function / type alias
namespace sgpmode {
// TODO - do we want this to be configurable?
const size_t PROGRAM_LENGTH = 100;
// NOTE - SymWorld::GetPop returns pop by copy instead of by reference?
// GetPop is expensive operation, avoid use
// TODO - add tests for stack limits on organisms
class SGPWorld : public SymWorld {
public:
using sgp_cpu_peripheral_t = CPUState<SGPWorld>;
using hw_spec_t = SGPHardwareSpec<Library, sgp_cpu_peripheral_t, SGPWorld>;
using sgp_host_t = SGPHost<hw_spec_t>;
using sgp_sym_t = SGPSymbiont<hw_spec_t>;
using tag_t = typename hw_spec_t::tag_t;
using sgp_hw_t = SGPHardware<hw_spec_t>;
using sgp_prog_t = typename sgp_hw_t::program_t;
using task_env_t = tasks::LogicTaskEnvironment;
using task_reqs_t = typename task_env_t::TaskReqInfo;
using task_io_bank_t = typename task_env_t::io_bank_t;
using task_io_t = typename task_io_bank_t::TaskIO;
using mutator_t = SGPMutator<sgp_prog_t, Library>;
using sgp_prog_rectifier_t = sgpl::OpCodeRectifier<Library>;
using event_manager_t = EventManager<SGPWorld>;
using fun_sym_do_birth_t = std::function<emp::WorldPosition(
emp::Ptr<sgp_sym_t>, /* symbiont baby ptr */
const emp::WorldPosition& /* parent_position */
)>;
using fun_vert_trans_compatible_t = std::function<bool(
sgp_sym_t&, /* symbiont_ptr */
sgp_host_t&, /* host_offspring_ptr (trans to) */
sgp_host_t& /* host_parent_ptr (trans from) */
)>;
// Are host and endosymbiont compatible for horizontal transmission?
// At the moment, task match based on parent vs current
// NOTE: arguments can't be const because necessary Host.h/Organism.h functions aren't const
using fun_horizontal_transmission_compatibility_check_t = std::function<bool(
sgp_host_t&,
sgp_sym_t&
)>;
// Determines whether two task profiles are "compatible" with one another.
using fun_task_profile_compatibility_t = std::function<bool(
const emp::BitVector&,
const emp::BitVector&
)>;
using fun_get_host_task_profile_t = std::function<const emp::BitVector&(const sgp_host_t&)>;
using fun_get_sym_task_profile_t = std::function<const emp::BitVector&(const sgp_sym_t&)>;
using fun_do_resource_inflow_t = std::function<void(void)>;
using fun_calc_host_nutrient_interaction_t = std::function<double(
sgp_host_t&,
sgp_sym_t&, /* symbiont */
double, /* task value before nutrient interaction */
size_t, /* task id */
size_t /* symbiont count */
)>;
using fun_calc_sym_nutrient_interaction_t = std::function<double(
sgp_host_t&,
sgp_sym_t&, /* symbiont */
double, /* task value before nutrient interaction */
size_t, /* task id */
size_t /* symbiont count */
)>;
using func_apply_host_points_t = std::function<void(
sgp_host_t&,
double, /* task value before nutrient interaction */
size_t /* task id */
)>;
// using fun_process_endosym_t = std::function<void(
// sgp_sym_t&, /* endosymbiont */
// const emp::WorldPosition&, /* sym pos */
// sgp_host_t& /* host */
// )>;
using org_mode_t = typename org_info::SGPOrganismType;
using stress_sym_mode_t = typename org_info::StressSymbiontType;
using health_sym_mode_t = typename org_info::HealthSymbiontType;
using nutrient_sym_mode_t = typename org_info::NutrientSymbiontType;
// Used for any snapshot info that should be added to the config snapshot file
// in addition to values in sgp_config object.
// TODO - move this to own file
struct ConfigSnapshotEntry {
std::string param; ///< Parameter name
std::string value; ///< Parameter value
ConfigSnapshotEntry(
const std::string& p,
const std::string& v
) :
param(p),
value(v)
{ }
};
// Collection of current update statistics
// Calculated only when CurrentUpdateInfo data file is updated.
struct CurrentUpdateData {
emp::vector<size_t> host_task_in_profile_counts;
emp::vector<size_t> host_task_in_parent_org_counts;
emp::vector<size_t> host_task_in_current_org_counts;
emp::vector<size_t> host_generations;
emp::vector<size_t> sym_task_in_profile_counts;
emp::vector<size_t> sym_task_in_parent_org_counts;
emp::vector<size_t> sym_task_in_current_org_counts;
emp::vector<size_t> sym_generations;
emp::vector<size_t> host_sym_profile_matches_by_task;
emp::vector<size_t> host_sym_profile_mismatches_by_task;
size_t host_sym_perfect_matches_total;
size_t host_sym_any_matches_total;
size_t num_tasks;
std::unordered_map<emp::BitVector, size_t> host_parent_tasks_performed;
std::unordered_map<emp::BitVector, size_t> host_current_tasks_performed;
std::unordered_map<emp::BitVector, size_t> sym_parent_tasks_performed;
std::unordered_map<emp::BitVector, size_t> sym_current_tasks_performed;
// Reset Current update data, adjust task count
void Reset(size_t task_count) {
num_tasks = task_count;
utils::ResizeFill(host_task_in_profile_counts, num_tasks, 0);
utils::ResizeFill(host_task_in_parent_org_counts, num_tasks, 0);
utils::ResizeFill(host_task_in_current_org_counts, num_tasks, 0);
utils::ResizeFill(sym_task_in_profile_counts, num_tasks, 0);
utils::ResizeFill(sym_task_in_parent_org_counts, num_tasks, 0);
utils::ResizeFill(sym_task_in_current_org_counts, num_tasks, 0);
utils::ResizeFill(host_sym_profile_matches_by_task, num_tasks, 0);
utils::ResizeFill(host_sym_profile_mismatches_by_task, num_tasks, 0);
host_generations.clear();
sym_generations.clear();
host_parent_tasks_performed.clear();
host_current_tasks_performed.clear();
sym_parent_tasks_performed.clear();
sym_current_tasks_performed.clear();
host_sym_perfect_matches_total = 0;
host_sym_any_matches_total = 0;
}
// Reset current task data, use same number of tasks as before
void Reset() {
Reset(num_tasks);
}
} current_update_data;
struct StressEscapee {
emp::Ptr<sgp_sym_t> sym_offspring;
// emp::WorldPosition escape_location;
emp::BitVector parent_task_profile;
size_t escape_location;
StressEscapee() = default;
StressEscapee(
emp::Ptr<sgp_sym_t> sym,
const emp::BitVector& tasks,
size_t loc
) :
sym_offspring(sym),
parent_task_profile(tasks),
escape_location(loc)
{ }
};
// Tag used to trigger start module in signalgp programs during run
tag_t START_TAG;
// -- Signals to allow custom behavior to be triggered by other classes -- /
// AEV question: getter methods for these?
// begin_update_sig - Triggers at the beginning of an Update call.
// Triggers before schedule update, before processing any organisms.
// E.g., used for resetting any per-update data tracking.
emp::Signal<void(void)> begin_update_sig;
// ---- Symbiont birth signals / functors ----
// before_sym_do_birth_sig - Triggers during SymDoBirth function.
// Triggers after sym offspring is created but before fun_sym_do_birth() is called.
emp::Signal<void(
emp::Ptr<sgp_sym_t>, /* sym_baby_ptr */
const emp::WorldPosition& /* parent_pos */
)> before_sym_do_birth_sig;
// after_sym_do_birth_sig - Triggers during SymDoBirth function.
// Triggers after fun_sym_do_birth() is called.
emp::Signal<void(
const emp::WorldPosition&, /* sym_baby_pos */
emp::Ptr<sgp_sym_t> /* parent_sym */
)> after_sym_do_birth_sig;
// fun_sym_do_birth - Configurable functor that handles calling appropriate
// "DoBirth" function depending on whether free-living symbionts are turned on.
fun_sym_do_birth_t fun_sym_do_birth;
// ---- Host birth signals / functors ----
// before_host_do_birth_sig - Triggers during HostDoBirth().
// When this triggers, host offspring has been created (when reproduction queue)
// is processed. Triggers before endosymbionts attempt vertical transmission and
// before DoBirth is called.
emp::Signal<void(
sgp_host_t&, /* host_offspring_ptr */
sgp_host_t&, /* host_parent_ptr */
const emp::WorldPosition& /* parent_pos */
)> before_host_do_birth_sig;
// after_host_do_birth_sig - Triggers during HostDoBirth().
// Triggers after endosymbionts attempt vertical transmission and after DoBirth
// is called on the host offspring.
// NOTE - add more parameters to this? We know the parent / offsspring where this is called.
emp::Signal<void(
const emp::WorldPosition& /* host_offspring_pos */
)> after_host_do_birth_sig;
// ---- Host process signals / functors ----
// before_host_cpu_exec_sig - Triggers in ProcessHostAt()
// Triggers before running the host's CPU / after updating host location.
// Host is not guaranteed to still be alive if prior actions attached to this signal
// kill the host.
emp::Signal<void(
sgp_host_t&
)> before_host_cpu_exec_sig;
// after_host_process_sig - Triggers in ProcessHostAt()
// Triggers at end of ProcessHostAt. There is one final check for death after
// after this triggers in case an attached action kills the host.
emp::Signal<void(
sgp_host_t&
)> after_host_process_sig;
// after_host_cpu_step_sig - Triggers in ProcessHostAt()
// Triggers after each CPU cycle (potentially multiple times per update) and after
// handling a repro attempt by the host for that CPU cycle.
emp::Signal<void(
sgp_host_t&
)> after_host_cpu_step_sig;
// after_host_cpu_exec_sig - Triggers in ProcessHostAt()
// Triggers after executing all CPU cycles allotted to host being processed and
// before processing the host's endosymbionts.
emp::Signal<void(
sgp_host_t&
)> after_host_cpu_exec_sig;
// fun_process_endosym_t fun_process_endosym; // NOTE - not used at the moment
// ---- Free-living symbiont signals / functors ----
// before_freeliving_sym_process_sig - Triggers in ProcessFreeLivingSymAt()
// Triggers if sym is alive before executing sym's CPU.
emp::Signal<void(
sgp_sym_t& /* sym */
)> before_freeliving_sym_process_sig;
// after_freeliving_sym_process_sig - Triggers in ProcessFreeLivingSymAt()
// Triggers at end of ProcessFreeLivingSymAt, but before a final check/potential
// DoSymDeath call
emp::Signal<void(
sgp_sym_t& /* sym */
)> after_freeliving_sym_process_sig;
// after_freeliving_sym_cpu_step_sig - Triggers in ProcessFreeLivingSymAt()
// Triggers after each CPU cycle after handling an instruction-triggered repro attempt.
emp::Signal<void(
sgp_sym_t& /* sym */
)> after_freeliving_sym_cpu_step_sig;
// after_freeliving_sym_cpu_exec_sig - Triggers in ProcessFreeLivingSymAt()
// Triggers after executing all CPU cycles allotted to sym being processed and
// before SGPSymbiont::Process is called.
emp::Signal<void(
sgp_sym_t& /* sym */
)> after_freeliving_sym_cpu_exec_sig;
// ---- Endosymbiont process signals / functors ----
// Happens before this endosymbiont's host is processed
emp::Signal<void(
const emp::WorldPosition&, /* sym_pos */
sgp_sym_t&, /* sym */
sgp_host_t& /* host */
)> before_endosym_host_process_sig;
// before_endosym_process_sig - Triggers during ProcessEndoSymbiont()
emp::Signal<void(
const emp::WorldPosition&, /* sym_pos */
sgp_sym_t&, /* sym */
sgp_host_t& /* host */
)> before_endosym_process_sig;
// after_endosym_process_sig - Triggers during ProcessEndoSymbiont()
emp::Signal<void(
const emp::WorldPosition&, /* sym_pos */
sgp_sym_t&, /* sym */
sgp_host_t& /* host */
)> after_endosym_process_sig;
// after_endosym_cpu_step_sig - Triggers during ProcessEndoSymbiont()
emp::Signal<void(
const emp::WorldPosition&, /* sym_pos */
sgp_sym_t&, /* sym */
sgp_host_t& /* host */
)> after_endosym_cpu_step_sig;
// after_endosym_cpu_exec_sig - Triggers during ProcessEndoSymbiont()
emp::Signal<void(
const emp::WorldPosition&, /* sym_pos */
sgp_sym_t&, /* sym */
sgp_host_t& /* host */
)> after_endosym_cpu_exec_sig;
// ---- Environment signals/functors ----
// fun_do_resource_inflow_t fun_do_resource_inflow;
// Called in FindHostForHorizontalTrans(), configured in SetupPopStructure().
// Returns a target position for symbiont to horizontally transmit into.
// Returns std::nullopt if failed to find suitable target position.
std::function<std::optional<emp::WorldPosition>(
size_t, /* Parent's host location id in world (pops[0][id])*/
emp::Ptr<sgp_sym_t> /* Pointer to symbiont parent (producing the sym offspring) */
)> fun_find_host_for_horizontal_trans;
// External facing helpers for orgnanisms to call
//void HostAttemptRepro(const emp::WorldPosition& pos, sgp_host_t& host);
void EndosymAttemptRepro(
const emp::WorldPosition& pos,
sgp_sym_t& sym,
emp::Ptr<Organism> host
);
void FreeLivingSymAttemptRepro(
const emp::WorldPosition& pos,
sgp_sym_t& sym
);
protected:
Scheduler scheduler; // Manages order that world locations (organisms) are processed each update
size_t max_world_size; // Maximum number of locations in the world
ReproductionQueue repro_queue; // Stores which organisms are queued for reproduction
tasks::LogicTaskEnvironment task_env; // Manages task set, task requirements, and task rewards
event_manager_t event_manager;
// TODO - Consider having symbiont rectifier and host rectifier
// -> Symbiont-specific instructions wouldn't be in host's instruction set
sgp_prog_rectifier_t opcode_rectifier; // Used to "disable" instructions at runtime based on run configuration
ProgramBuilder<hw_spec_t> prog_builder = ProgramBuilder<hw_spec_t>(opcode_rectifier); // Utility for building signalgp programs
mutator_t mutator = mutator_t(opcode_rectifier); // Handles mutating sgp programs
emp::vector<StressEscapee> symbiont_stress_escapees;
emp::vector<size_t> escapee_ids; // Used to randomize order of processing escapees (to avoid biasing)
// Flag for whether setup has been run.
bool setup = false;
emp::Ptr<emp::DataMonitor<double>> data_node_sym_donated;
emp::Ptr<emp::DataMonitor<double>> data_node_sym_stolen;
emp::Ptr<emp::DataMonitor<double>> data_node_sym_earned;
// Tracks host/symbiont task success counts.
// NOTE - Managed by world instead of task set because world
// determines whether a task is successful
emp::vector<emp::DataMonitor<size_t>> data_node_host_tasks;
emp::vector<emp::DataMonitor<size_t>> data_node_sym_tasks;
// Tracks host task successes each update. Counts reset to 0 @ begin_update_sig.
emp::vector<size_t> host_task_successes;
// Tracks symbiont task successes each update. Counts reset to 0 @ begin_update_sig.
emp::vector<size_t> sym_task_successes;
/**
*
* Purpose: Holds all configuration settings and references the same configuration
* object as my_config from superclass, but with the correct subtype.
*
*/
SymConfigSGP& sgp_config;
// What kind of SGP organism type to use?
org_mode_t sgp_org_type = org_mode_t::DEFAULT;
// If using stress organisms, what kind of stress?
stress_sym_mode_t stress_sym_type = stress_sym_mode_t::MUTUALIST;
bool stress_extinction_update = false;
health_sym_mode_t health_sym_type = health_sym_mode_t::MUTUALIST;
nutrient_sym_mode_t nutrient_sym_type = nutrient_sym_mode_t::MUTUALIST;
fun_calc_host_nutrient_interaction_t fun_calc_host_nutrient_interaction;
fun_calc_sym_nutrient_interaction_t fun_calc_sym_nutrient_interaction;
func_apply_host_points_t fun_apply_host_points;
// NOTE - Don't love this being owned by the world.
// Not sure of better alterative. Need to know this in InitializeState
// (don't want to re-lookup every call using strings). Couldn't have it float
// inside of the GenomeLibrary file because the static map used by these
// GetOpCode functions isn't initialized at that point.
std::unordered_set<uint8_t> sgp_jump_opcodes = {
Library::GetOpCode("JumpIfNEq"),
Library::GetOpCode("JumpIfEq"),
Library::GetOpCode("JumpIfLess")
};
// Directory to dump output files into.
std::filesystem::path output_dir;
// Contains config information (from outside sgp_config) to include in configuration snapshot
emp::vector<ConfigSnapshotEntry> config_snapshot_entries;
// Function to check compatibility between host and symbiont
// - Used to check eligibility for vertical / horizontal transmission, etc.
fun_horizontal_transmission_compatibility_check_t fun_host_sym_horizontal_trans_compatibility_check;
// Function used to check compatibility between host and symbiont that reproduced
// via a stress event.
// - Can't use same function as when checking horizontal transmission compatibility because
// we no longer have access to the symbiont parent for a stress transmission event.
std::function<bool(sgp_host_t&, const emp::BitVector&)> fun_host_sym_stress_trans_compatibility_check;
fun_task_profile_compatibility_t fun_task_profile_compatibility_check;
// Configurable function that accesses task profile to be used for hosts.
// - E.g., do we want to use parent tasks, current tasks, etc.
fun_get_host_task_profile_t fun_get_host_task_profile;
// Configurable function that accesses task profile to be used for symbionts.
// By keeping host/sym functions separately, we could configure them independently.
// E.g., if we want hosts to use their full profile vs syms using first task only
fun_get_sym_task_profile_t fun_get_sym_task_profile;
// ---- Symbiont vertical transmission signals / functors ----
// before_sym_vert_transmission_sig - Triggers in SGPSymbiont::Vertical Transmission function.
// Triggers before VerticalTransmission is called on endosymbiont attempting
// vertical transmission. I.e., before attempt is made.
emp::Signal<void(
emp::Ptr<sgp_sym_t>, /* sym_ptr - symbiont producing offspring */
emp::Ptr<sgp_host_t>, /* host_offspring_ptr - transmission to */
emp::Ptr<sgp_host_t> /* host_parent_ptr - transmission from */
)> before_sym_vert_transmission_sig;
// after_sym_vert_transmission_sig - Triggers in SGPSymbiont::Vertical Transmission function.
// Triggers ater vertical transmission attempt has been made.
// If attempt was successful, sym_offspring_ptr will point to the new symbiont
// offspring (that was vertically transmitted). If unsuccessful, sym_offspring_ptr
// will be a nullptr.
emp::Signal<void(
emp::Ptr<sgp_sym_t>, /* sym_offspring_ptr */
emp::Ptr<sgp_sym_t>, /* sym_parent_ptr */
emp::Ptr<sgp_host_t>, /* host_offspring_ptr */
emp::Ptr<sgp_host_t>, /* host_parent_ptr */
bool /* vertical transmission success */
)> after_sym_vert_transmission_sig;
// fun_vert_trans_compatible - Called during HostDoBirth to determine if
// a given symbiont can vertically transmit into host offspring.
fun_vert_trans_compatible_t fun_vert_trans_compatible;
// ----- Internal helper functions -----
// Called by Update()
void DoReproduction();
// Internal helper function to handle host births.
// Handles both host do birth and triggering vertical transmission on any
// symbionts within the host.
// Need to pass in parent pointer because parent may no longer exist at the
// given world position when this function is called.
emp::WorldPosition HostDoBirth(
emp::Ptr<Organism> host_offspring_ptr,
emp::Ptr<Organism> host_parent_ptr,
const emp::WorldPosition& parent_pos
);
emp::WorldPosition FreeLivingSymDoBirth(
emp::Ptr<sgp_sym_t> sym_baby_ptr,
const emp::WorldPosition& parent_pos
);
emp::WorldPosition SymAttemptHorizontalInfection(
emp::Ptr<sgp_sym_t> sym_baby_ptr,
const emp::WorldPosition& parent_pos
);
// Refactor AEV: Functionality ported to SGPSymbiont::VerticalTransmission
// bool EndosymAttemptVertTransmission(
// Internal helper function to delete dead organisms in graveyard.
void ProcessGraveyard();
void ProcessStressEscapees();
// --- Internal setup helper functions ---.
// Called internally on world setup.
// NOTE - Can we get rid of passing these values in as pointers?
void SetupHosts(long unsigned int* POP_SIZE) override;
void SetupSymbionts(long unsigned int* total_syms) override;
void SetupOrgTypeVariables();
void DisableConfigurableInstructions();
void SetupScheduler();
void SetupChangingEnvironment();
void SetupReproduction();
void SetupSymReproduction();
void SetupHostReproduction();
void SetupHostTaskRewards();
void SetupTaskProfileMode();
void SetupTaskProfileCompatibilityMode();
void SetupHorizontalTransmissionCompatibilityMode();
void SetupFindHostForHorizontalTransmission();
void SetupHostSymInteractions();
void SetupTaskEnvironment();
void SetupEvents();
void SetupMutator();
void SetupStressInteractions();
void SetupHealthInteractions();
void SetupNutrientInteractions();
// Clear all world signals
void ClearWorldSignals() {
begin_update_sig.Clear();
before_sym_do_birth_sig.Clear();
after_sym_do_birth_sig.Clear();
before_sym_vert_transmission_sig.Clear();
after_sym_vert_transmission_sig.Clear();
before_host_do_birth_sig.Clear();
after_host_do_birth_sig.Clear();
before_host_cpu_exec_sig.Clear();
after_host_process_sig.Clear();
after_host_cpu_step_sig.Clear();
before_freeliving_sym_process_sig.Clear();
after_freeliving_sym_process_sig.Clear();
after_freeliving_sym_cpu_step_sig.Clear();
before_endosym_host_process_sig.Clear();
before_endosym_process_sig.Clear();
after_endosym_process_sig.Clear();
after_endosym_cpu_step_sig.Clear();
}
// Utility function to get cpu state from an org pointer
sgp_cpu_peripheral_t& GetCPUState(emp::Ptr<Organism> org_ptr) {
return (org_ptr->IsHost()) ?
(static_cast<sgp_host_t*>(org_ptr.Raw()))->GetHardware().GetCPUState() :
(static_cast<sgp_sym_t*>(org_ptr.Raw()))->GetHardware().GetCPUState();
}
public:
SGPWorld(
emp::Random& rnd,
emp::Ptr<SymConfigSGP> _config
) :
SymWorld(rnd, _config),
scheduler(rnd),
task_env(rnd),
sgp_config(*_config)
{
// Configure default (no) nutrient interaction (IMPORTANT!)
// - Nutrient interaction setup will override this behavior if enabled.
fun_calc_host_nutrient_interaction = [](
sgp_host_t& host,
sgp_sym_t& sym,
double task_points,
size_t task_id,
size_t task_matching_sym_count
) {
return 0.0;
};
fun_calc_sym_nutrient_interaction = [](
sgp_host_t& host,
sgp_sym_t& sym,
double task_points,
size_t task_id,
size_t task_matching_sym_count
) {
return 0.0;
};
}
~SGPWorld() {
if(data_node_sym_donated) data_node_sym_donated.Delete();
if(data_node_sym_stolen) data_node_sym_stolen.Delete();
if(data_node_sym_earned) data_node_sym_earned.Delete();
}
/**
* Input: None
*
* Output: The sgp configuration used for this world.
*
* Purpose: Allows accessing the world's sgp config.
*/
// NOTE - Thoughts on holding a reference vs a pointer. Reference is a little
// cleaner in my opinion, but will always need to know at construction
// (which is how things were already setup with the pointer).
const SymConfigSGP& GetConfig() const { return sgp_config; }
emp::Ptr<SymConfigSGP> GetConfigPtr() { return &sgp_config; }
// NOTE - could make this a functor to allow runtime configuration or differences
// between different kinds of organisms
// NOTE - Other conditions that we want to check?
bool CanPerformTask(
sgp_cpu_peripheral_t& cpu_state,
const task_reqs_t& task_reqs
) {
const size_t task_id = task_reqs.task_id;
const size_t max_repeats = task_reqs.max_repeats;
return cpu_state.GetTaskPerformanceCount(task_id) < max_repeats;
}
/* Accessor for host_task_successes */
emp::vector<size_t>& GetHostTaskSuccesses() { return host_task_successes; }
/* Accessor for sym_task_successes */
emp::vector<size_t>& GetSymTaskSuccesses() { return sym_task_successes; }
task_env_t& GetTaskEnv() { return task_env; }
const task_env_t& GetTaskEnv() const { return task_env; }
size_t GetTaskCount() const { return task_env.GetTaskCount(); }
/* Accessor for host task profiles */
const emp::BitVector& GetHostTaskProfile(const sgp_host_t& host) { return fun_get_host_task_profile(host); }
/* Accessor for symbiont task profiles */
const emp::BitVector& GetSymbiontTaskProfile(const sgp_sym_t& symbiont) { return fun_get_sym_task_profile(symbiont); }
/**
* Purpose: Accessor for event manager (const)
*/
const event_manager_t& GetEventManager() const { return event_manager; }
/**
* Purpose: Accessor for event manager
*/
event_manager_t& GetEventManager() { return event_manager; }
/**
* Input: A host, a symbiont, the value of a task before applying nutrient interaction, and the task id.
* Output: The change in the points the host will gain from the task after the nutrient interaction.
* Purpose: To calculate the configured nutrient interaction for the given symbiont and task
*/
double CalcHostNutrientInteraction(
sgp_host_t& host,
sgp_sym_t& sym,
double task_value_before,
size_t task_id,
size_t task_matching_sym_count
) {
return fun_calc_host_nutrient_interaction(host,sym, task_value_before, task_id,task_matching_sym_count);
}
/**
* Input: A host, a symbiont, the value of a task before applying nutrient interaction, and the task id.
* Output: The amount of points that the symbiont will gain/lose after the nutrient interaction.
* Purpose: To calculate the configured nutrient interaction for the given symbiont and task
*/
double CalcSymNutrientInteraction(
sgp_host_t& host,
sgp_sym_t& sym,
double task_value_before,
size_t task_id,
size_t task_matching_sym_count
) {
return fun_calc_sym_nutrient_interaction(host,sym, task_value_before, task_id,task_matching_sym_count);
}
/**
* Input: A host, the value of a task before applying any interaction, and the task id.
* Output: None.
* Purpose: To give the points the amount of points it should receieve after all symbiont interactions have been applied
*/
void ApplyHostPoints(
sgp_host_t& host,
double task_value_before,
size_t task_id
) {
fun_apply_host_points(host,task_value_before, task_id);
}
const emp::BitVector& GetSymTaskProfile(
sgp_sym_t& sym
) {
return fun_get_sym_task_profile(sym);
}
const emp::BitVector& GetHostTaskProfile(
sgp_host_t& host
) {
return fun_get_host_task_profile(host);
}
bool TaskProfileCompatibilityCheck(
const emp::BitVector& host_task_profile,
const emp::BitVector& sym_task_profile
) {
return fun_task_profile_compatibility_check(host_task_profile, sym_task_profile);
}
bool CheckVertTransCompatibility(
sgp_sym_t& sym,
emp::Ptr<Organism> host_offspring,
emp::Ptr<Organism> host
) {
emp_assert(host.DynamicCast<sgp_host_t>(), "SGPSymbiont must have an SGPHost host");
emp_assert(host_offspring.DynamicCast<sgp_host_t>(), "SGPHost host must have SGPHost offspring");
return fun_vert_trans_compatible(sym, static_cast<sgp_host_t&>(*host_offspring), static_cast<sgp_host_t&>(*host));
}
void TriggerBeforeSymVertTransmissionSig(
emp::Ptr<sgp_sym_t> sym,
emp::Ptr<Organism> host_offspring,
emp::Ptr<Organism> host_parent
) {
emp_assert(host_parent.DynamicCast<sgp_host_t>(), "SGPSymbiont must have an SGPHost host");
emp_assert(host_offspring.DynamicCast<sgp_host_t>(), "SGPHost host must have SGPHost offspring");
before_sym_vert_transmission_sig.Trigger(sym, host_offspring.DynamicCast<sgp_host_t>(), host_parent.DynamicCast<sgp_host_t>());
}
void TriggerAfterSymVertTransmissionSig(
std::optional<emp::Ptr<Organism>> sym_offspring,
emp::Ptr<sgp_sym_t> sym,
emp::Ptr<Organism> host_offspring,
emp::Ptr<Organism> host_parent,
bool success
) {
emp_assert(host_parent.DynamicCast<sgp_host_t>(), "SGPSymbiont must have an SGPHost host");
emp_assert(host_offspring.DynamicCast<sgp_host_t>(), "SGPHost host must have SGPHost offspring");
emp::Ptr<sgp_sym_t> sym_offspring_ptr = (success) ?
static_cast<sgp_sym_t*>(sym_offspring.value().Raw()) :
nullptr;
after_sym_vert_transmission_sig.Trigger(sym_offspring_ptr,
sym, host_offspring.DynamicCast<sgp_host_t>(), host_parent.DynamicCast<sgp_host_t>(), success);
}
/* Called before the host of the endosym is processed, different from before endosym processed!*/
void TriggerBeforeEndoSymHostProcessSig(
const emp::WorldPosition& sym_pos, /* sym_pos */
sgp_sym_t& sym, /* sym */
emp::Ptr<Organism> host /* host */
) {
emp_assert(host.DynamicCast<sgp_host_t>(), "SGPSymbiont must have an SGPHost host");
before_endosym_host_process_sig.Trigger(sym_pos, sym, static_cast<sgp_host_t&>(*host));
}
/* Called before a specific endosym is processed.*/
void TriggerBeforeEndoSymProcessSig(
const emp::WorldPosition& sym_pos,
sgp_sym_t& sym,
emp::Ptr<Organism> host
) {
emp_assert(host.DynamicCast<sgp_host_t>(), "SGPSymbiont must have an SGPHost host");
before_endosym_process_sig.Trigger(sym_pos, sym, static_cast<sgp_host_t&>(*host));
}
void TriggerAfterEndosymCPUStepSig(
const emp::WorldPosition& sym_pos,
sgp_sym_t& sym,
emp::Ptr<Organism> host
) {
emp_assert(host.DynamicCast<sgp_host_t>(), "SGPSymbiont must have an SGPHost host");
after_endosym_cpu_step_sig.Trigger(sym_pos, sym, static_cast<sgp_host_t&>(*host));
}
void TriggerAfterEndosymCPUExecSig(
const emp::WorldPosition& sym_pos,
sgp_sym_t& sym,
emp::Ptr<Organism> host
) {
emp_assert(host.DynamicCast<sgp_host_t>(), "SGPSymbiont must have an SGPHost host");
after_endosym_cpu_exec_sig.Trigger(sym_pos, sym, static_cast<sgp_host_t&>(*host));
}
void TriggerAfterEndosymProcessSig(
const emp::WorldPosition& sym_pos,
sgp_sym_t& sym,
emp::Ptr<Organism> host
) {
emp_assert(host.DynamicCast<sgp_host_t>(), "SGPSymbiont must have an SGPHost host");
after_endosym_process_sig.Trigger(sym_pos, sym, static_cast<sgp_host_t&>(*host));
}
const std::unordered_set<uint8_t>& GetJumpInstOpcodes() const { return sgp_jump_opcodes; }
/**
* Input: None
*
* Output: None
*
* Purpose: To simulate a timestep in the world, which includes calling the
* process functions for hosts and symbionts and updating the data nodes.
*/
void Update() override {
emp_assert(setup);
// NOTE - When do we want events to occur? Typically, I think we want them
// as the *very* first thing that happens on an update. E.g., changing
// a task value, etc.
event_manager.ProcessEvents(*this);
begin_update_sig.Trigger();
// Handle resource inflow
// TODO - implement inflow configuration
// fun_do_resource_inflow();
// Update scheduler's evaluation order
scheduler.UpdateSchedule();
// Run scheduler to process organisms
scheduler.Run(*this);
// Process reproduction queue
repro_queue.Process();
ProcessStressEscapees();
// Process graveyard, deletes all dead organisms.
ProcessGraveyard();
// NOTE - these were previously called at the beginning of the update
// any specific reason to do that instead of at end?
// If we move this to the end of the update, file updates happen after
// world update logic.
//
// These must be done here because we don't call SymWorld::Update()
// That may change in the future
emp::World<Organism>::Update();
if (sgp_config.PHYLOGENY()) {
sym_sys->Update();
}
}
// TODO: AEV: Why is this separate from RunExperiment in SymWorld? Needs to be combined to support all the other functionality from RunExperiment
void Run(bool verbose = false) {
emp_assert(setup);
emp_assert(sgp_config.UPDATES() >= 0);
emp_assert(setup_spatial_structure);
const size_t updates = sgp_config.UPDATES();
for (size_t u = 0; u <= updates; ++u) {
Update();
if (verbose && (u % sgp_config.PRINT_INTERVAL()) == 0) {
std::cout << "Update: " << u << std::endl;
}
}
}
// Process hosts at given position in world pop vector and free-living symbionts in world syms vector.
void ProcessOrgsAt(size_t pop_id);
// Process host at given position in world
// NOTE - what functionality should be centralized vs in the host class vs functor/signal?
void ProcessHostAt(const emp::WorldPosition& pos, sgp_host_t& host);
// Process symbiont at given position in the world
void ProcessFreeLivingSymAt(const emp::WorldPosition& pos, sgp_sym_t& sym);
//void ProcessHostOutputBuffer(sgp_host_t& host);
void ProcessSymOutputBuffer(sgp_sym_t& sym);
// NOTE - moved to be public for testing
void AssignNewEnvIO(sgp_cpu_peripheral_t& cpu_state) {
const size_t env_id = GetRandom().GetUInt(task_env.GetIOBank().GetSize());
const auto& task_io = task_env.GetIOBank().GetIO(env_id);
cpu_state.SetTaskEnvID(env_id);
cpu_state.SetInputs(task_io.input_buffer);
cpu_state.ResetCreditedOutputs();
}
// Prototypes for setup methods
// TODO - distinguish between world configuration and population initialization
// Why? Tests, different mains configure initial population differently
/**
* Input: None.
*
* Output: None.
*
* Purpose: Prepare the SGPWorld for an experiment by applying the configuration settings
* and populating the world with hosts and symbionts.
*/
void Setup() override;
void SetMutationZero();
// Prototypes for reproduction handling methods
// SymDoBirth is for horizontal transmission and birthing free-living symbionts.
emp::WorldPosition SymDoBirth(
emp::Ptr<Organism> sym_baby,
emp::WorldPosition parent_pos
) override;
void HostDoMutation(sgp_host_t& host);
void SymDoMutation(sgp_sym_t& sym);
void SymDonateToHost(Organism& from_sym, Organism& to_host);
void SymStealFromHost(Organism& to_sym, Organism& from_host);
void FreeLivingSymDoInfect(Organism& sym);
// Returns neighboring host from given symbiont
// NOTE - Opinions on name change? (originally GetNeighborHost)
std::optional<emp::WorldPosition> FindHostForHorizontalTrans(
size_t host_world_id, /* Parent's host location id in world (pops[0][id])*/
emp::Ptr<sgp_sym_t> sym_parent_ptr /* Pointer to symbiont parent (producing the sym offspring) */
);
/**
* Input: An organism pointer to add to the graveyard
*
* Output: None
*
* Purpose: To add organisms to the graveyard
*/
void SendToGraveyard(emp::Ptr<Organism> org) override;
org_mode_t GetOrgType() const { return sgp_org_type; }
stress_sym_mode_t GetStressSymType() const { return stress_sym_type; }
health_sym_mode_t GetHealthSymType() const { return health_sym_type; }
nutrient_sym_mode_t GetNutrientSymType() const { return nutrient_sym_type; }
ReproductionQueue& GetReproQueue() { return repro_queue; }
// Data node methods
emp::DataMonitor<double>& GetSymDonatedDataNode() {
emp_assert(data_node_sym_donated != nullptr);
return *data_node_sym_donated;
}
emp::DataMonitor<double>& GetSymStolenDataNode() {
emp_assert(data_node_sym_stolen != nullptr);
return *data_node_sym_stolen;
}
emp::DataMonitor<double>& GetSymEarnedDataNode() {
emp_assert(data_node_sym_earned != nullptr);
return *data_node_sym_earned;
}
void SetupTasksNodes();
ProgramBuilder<hw_spec_t>& GetProgramBuilder() { return prog_builder; }
const ProgramBuilder<hw_spec_t>& GetProgramBuilder() const { return prog_builder; }
emp::DataFile& SetupOrgCountFile(const std::string& filepath);
emp::DataFile& SetupSymDonatedFile(const std::string& filepath);
emp::DataFile& SetupTasksFile(const std::string& filepath);
void WriteTaskCombinationsFile(const std::string& filepath);
void WriteOrgReproHistFile(const std::string& filepath);
emp::DataFile& SetupCurrentUpdateInfoFile(const std::string& filepath);
void CollectCurrentUpdateData();
emp::DataFile& SetupSymbiontInteractionValuesFile(const std::string& filepath);
void OutputDominantDataFile();
void CreateDataFiles() override;
void SnapshotConfig(const std::string& filename="run_config.csv");
bool NoBetterMatchingSymbionts(sgp_host_t& host, const emp::BitVector& profile) {
if (host.HasSym()) {
const emp::BitVector& host_task_profile = fun_get_host_task_profile(host);
const size_t match_strength = utils::MatchingOnesCount(
profile,
host_task_profile
);
// NOTE - might as well remove const from arguments because we'd be allowed to modify
// endosymbionts through the pointer...
bool strongest_match = true;
for (emp::Ptr<Organism> org_ptr : host.GetSymbionts()) {