-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathSGPHost.h
More file actions
624 lines (551 loc) · 20.6 KB
/
Copy pathSGPHost.h
File metadata and controls
624 lines (551 loc) · 20.6 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
#ifndef SGPHOST_H
#define SGPHOST_H
#include "../default_mode/Host.h"
#include "hardware/SGPHardware.h"
#include "SGPConfigSetup.h"
#include "emp/base/Ptr.hpp"
#include "emp/bits/Bits.hpp"
#include "sgpl/utility/ThreadLocalRandom.hpp"
#include <functional>
namespace sgpmode {
template <typename HW_SPEC_T>
class SGPHost : public Host {
public:
using this_t = SGPHost<HW_SPEC_T>;
using world_t = typename HW_SPEC_T::world_t;
using sgp_sym_t = typename world_t::sgp_sym_t;
using hw_spec_t = HW_SPEC_T;
using hw_t = SGPHardware<hw_spec_t>;
using program_t = typename hw_t::program_t;
size_t matching_syms_to_interact_with = 0;
protected:
// CPU cpu;
hw_t hardware;
/**
*
* Purpose: Tracks the number of reproductive events in this host's lineage.
*
*/
size_t reproductions = 0;
/**
*
* Purpose: Represents the SGPWorld that the hosts are living in.
*
*/
const emp::Ptr<world_t> my_world;
/**
*
* Purpose: Holds all configuration settings and points to same configuration
* object as my_config from superclass, but with the correct subtype.
*
*/
emp::Ptr<SymConfigSGP> sgp_config;
// // Function to configure functionality.
// void ConfigureDefaults() {
// // Clear signals.
// process_sig_before_cpu_step.Clear();
// process_sig_before_syms.Clear();
// process_sig_after_syms.Clear();
// process_sig_end.Clear();
// // Set process syms to default behavior.
// process_syms =
// }
public:
/**
* Constructs a new SGPHost as an ancestor organism, with either a random
* genome or a blank genome that knows how to do a simple task depending on
* the config setting RANDOM_ANCESTOR.
*/
SGPHost(
emp::Ptr<emp::Random> _random,
emp::Ptr<world_t> _world,
emp::Ptr<SymConfigSGP> _config,
double _intval = 0.0, /* Interaction value */
const emp::vector<emp::Ptr<Organism>>& _syms = {},
const emp::vector<emp::Ptr<Organism>>& _repro_syms = {}, // NOTE - what are repro_syms?
double _points = 0.0
) :
Host(_random, _world, _config, _intval, _syms, _repro_syms, _points),
hardware(_world, this),
my_world(_world),
sgp_config(_config)
{ }
/**
* Constructs an SGPHost with a copy of the provided genome.
*/
SGPHost(
emp::Ptr<emp::Random> _random,
emp::Ptr<world_t> _world,
emp::Ptr<SymConfigSGP> _config,
const program_t& genome,
double _intval = 0.0, /* Interaction value */
const emp::vector<emp::Ptr<Organism>>& _syms = {},
const emp::vector<emp::Ptr<Organism>>& _repro_syms = {},
double _points = 0.0
) :
Host(_random, _world, _config, _intval, _syms, _repro_syms, _points),
hardware(_world, this, genome),
my_world(_world),
sgp_config(_config)
{ }
SGPHost(const SGPHost& host) :
Host(host),
hardware(host.my_world, this, host.hardware.GetProgram()),
my_world(host.my_world)
{ }
/**
* Input: None
*
* Output: None
*
* Purpose: Perform necessary cleanup when a host dies, freeing heap-allocated
* state and canceling any in-progress reproduction.
*/
~SGPHost() {
// cpu.state.used_resources.Delete();
// cpu.state.shared_available_dependencies.Delete();
// cpu.state.internal_environment.Delete();
// Invalidate any in-progress reproduction
// TODO - move this out of this class?
// - Or, move functionality into world (add world function for invalidating queued repro)
auto& cpu_state = hardware.GetCPUState();
if (cpu_state.ReproInProgress()) {
my_world->GetReproQueue().Invalidate(cpu_state.GetReproQueuePos());
// my_world->to_reproduce[cpu_state.GetReproQueuePos()].second =
// emp::WorldPosition::invalid_id;
}
// if (hardware.state.in_progress_repro != -1) {
// my_world->to_reproduce[cpu.state.in_progress_repro].second =
// emp::WorldPosition::invalid_id;
// }
}
bool operator<(const Organism& other) const {
if (const SGPHost* sgp = dynamic_cast<const SGPHost*>(&other)) {
return GetProgram() < sgp->GetProgram();
} else {
return false;
}
}
bool operator<(const SGPHost& other) const {
return GetProgram() < other.GetProgram();
}
// NOTE / TODO - What about host interaction values?
bool operator==(const Organism& other) const {
if (const SGPHost* sgp = dynamic_cast<const SGPHost*>(&other)) {
return GetProgram() == sgp->GetProgram();
} else {
return false;
}
}
bool operator==(const SGPHost& other) const {
return hardware.GetProgram() == other.hardware.GetProgram();
}
/**
* Input: Set the reproduction counter
*
* Output: None
*
* Purpose: To set the count of reproductions in this lineage.
*/
void SetReproCount(size_t _in) { reproductions = _in; }
void DecPoints(double amt) {
points -= amt;
if (points < 0){
points = 0;
}
}
void AddPoints(double amt) {
points += amt;
if (points < 0){
points = 0;
}
}
size_t GetCountofMatchingSymsToInteractWith(){
return matching_syms_to_interact_with;
}
void SetCountofMatchingSymsToInteractWith(size_t new_matching_count){
matching_syms_to_interact_with = new_matching_count;
}
/**
* Input: None.
*
* Output: The reproduction count
*
* Purpose: To get the count of reproductions in this lineage.
*/
size_t GetReproCount() const { return reproductions; }
/**
* Input: None
*
* Output: The CPU associated with this host.
*
* Purpose: Allows accessing the host's CPU.
*/
hw_t& GetHardware() { return hardware; }
const hw_t& GetHardware() const { return hardware; }
const program_t& GetProgram() const { return hardware.GetProgram(); }
program_t& GetProgram() { return hardware.GetProgram(); }
/**
* Input: None
*
* Output: A pointer to the world this host belongs to.
*
* Purpose: Allows accessing the host's world.
*/
emp::Ptr<world_t> GetWorld() { return my_world; }
/**
* Input: A pointer to the symbiont to add.
*
* Output: The position of the added symbiont.
*
* Purpose: To add a symbiont to the host. Overrides Host's AddSymbiont to also assign the symbiont's environment IO.
*/
int AddSymbiont(emp::Ptr<Organism> sym) {
my_world->AssignNewEnvIO(static_cast<sgp_sym_t&>(*sym).GetHardware().GetCPUState());
auto result = Host::AddSymbiont(sym);
return result;
}
/**
* Input: The location of the host.
*
* Output: None
*
* Purpose: To process the host, meaning: running its program code, which can
* include reproduction and acquisition of resources; removing dead syms; and
* processing alive syms.
*/
// TODO - why pass a copy of the position?
// - Need to override parent implementation
void Process(emp::WorldPosition pos) {
// If host is dead, don't process.
if (GetDead()) {
return;
}
// NOTE - Discuss timing of endosym pre-process signal and host preprocess signal
// Currently endosyms go first and then hosts. This is to model endosyms
// having opportunity to steal / donate cpu cycles and then host responding
// to endosym behavior (but could argue it should be the other way around).
// Give endosymbionts their baseline CPU cycles
// Trigger signal to all endosymbionts that host is about to process
// Gives endosymbionts chance to interact with host before it processes.
// E.g., symbiont could steal / donate cpu cycles, resources, etc.
UpdateSymMatchCount();
emp::vector<emp::Ptr<Organism>>& syms = GetSymbionts();
for (size_t endosym_i = 0; endosym_i < syms.size(); ++endosym_i) {
//This ensures that which symbiont is the starting symbiont rotates each update
size_t sym_index = (endosym_i + my_world->GetUpdate()) % syms.size();
emp_assert(!(syms[sym_index]->IsHost()));
emp::Ptr<sgp_sym_t> cur_symbiont = static_cast<sgp_sym_t*>(syms[sym_index].Raw());
const bool dead = cur_symbiont->GetDead();
// Skip if dead
if (dead) {
continue;
}
// Endosymbiont gains baseline number of CPU cycles
cur_symbiont->GetHardware().GetCPUState().GainCPUCycles(
sgp_config->CYCLES_PER_UPDATE()
);
my_world->TriggerBeforeEndoSymHostProcessSig(
{endosym_i + 1, GetLocation().GetIndex()},
*cur_symbiont,
this
);
}
my_world->TriggerBeforeHostCPUExec(*this);
// Host may have died as a result of this signal.
if (GetDead()) {
return;
}
// NOTE - Do we want to drain cpu cycles here (i.e., get cashed in for execution?)
const size_t cycles_to_exec = GetHardware().GetCPUState().ExtractCPUCycles();
// host.GetHardware().GetCPUState().LoseCPUCycles(cycles_to_execute);
// Execute organism hardware according to cycles_to_exec
// NOTE - Discuss possibility of host dying because of instruction executions.
// As-is, still run hardware forward full amount regardless
for (size_t i = 0; i < cycles_to_exec; ++i) {
if (GetDead()) {
return;
}
// TODO - do we need to update org location every update? (this was being done in RunCPUStep every cpu step)
// Execute 1 CPU cycle
GetHardware().RunCPUStep(1);
// Did host attempt to reproduce?
// NOTE - could move into a signal response
// NOTE - want to handle this after every clock cycle?
if (GetHardware().GetCPUState().ReproAttempt()) {
// upside to handling this here: we have direct access to organism
AttemptReproduction(pos);
}
my_world->TriggerAfterHostCPUStep(*this);
// NOTE - Check death here?
}
my_world->TriggerAfterHostCPUExec(*this);
// Handle any endosymbionts (configurable at setup-time)
// NOTE - is there any reason that this might need to be a functor?
ProcessEndosymbionts();
// Endosymbionts might kill host.
if (GetDead()) {
return;
}
GrowOlder();
my_world->TriggerAfterHostProcess(*this);
}
void ProcessEndosymbionts() {
// If host doesn't have a symbiont, return.
if (!HasSym()) {
return;
}
emp::vector<emp::Ptr<Organism>>& syms = GetSymbionts();
size_t sym_count = syms.size();
for (size_t sym_i = 0; sym_i < sym_count; /*sym_i handled internally*/) {
emp_assert(!(syms[sym_i]->IsHost()));
// If host is dead (e.g., because of previous symbiont), stop processing.
if (GetDead()) {
return;
}
emp::Ptr<sgp_sym_t> cur_symbiont = static_cast<sgp_sym_t*>(syms[sym_i].Raw());
const bool dead = cur_symbiont->GetDead();
if (!dead) {
// Symbiont not dead, process it
// TODO - change to functor?
cur_symbiont->Process({sym_i + 1, GetLocation().GetIndex()});
++sym_i;
} else {
// TODO: this should probably be it's own function to abstract this logic and share it other places
emp_assert(sym_count > 0);
// TODO - Check that it is okay to re-order symbionts to avoid erase calls
// Symbiont is dead, need to delete it.
cur_symbiont.Delete();
// Swap this symbiont with last in list, decrementing sym_count
std::swap(syms[sym_i], syms[--sym_count]);
// We will need to process what we just swapped into place, so
// re-process sym_i (don't increment it)
}
}
// Resize syms to remove deleted dead symbionts swapped to end
emp_assert(sym_count <= syms.size());
syms.resize(sym_count);
// TODO - signal?
}
/**
* Input: None.
*
* Output: None.
*
* Purpose: To check if host can reproduce and mark repro in progress in CPU state if so.
* TODO: Perhaps Default mode should have something similar
*/
void AttemptReproduction(const emp::WorldPosition& pos) {
const double repro_cost = sgp_config->HOST_REPRO_RES();
if (GetPoints() >= repro_cost) {
// Host pays cost
DecPoints(repro_cost);
// Add host to repro queue
// TODO - protect with mutex?
const size_t queue_id = my_world->GetReproQueue().Enqueue(
GetHardware().GetCPUState().GetOrgPtr(),
pos
);
// Mark host hardware as repro in progress, no longer in repro "attempt" state.
GetHardware().GetCPUState().MarkReproInProgress(queue_id);
} else {
// Attempt failed, so reset repro state.
GetHardware().GetCPUState().ResetReproState();
}
}
/**
* Input: None.
*
* Output: None.
*
* Purpose: Reward for any solved tasks in the output buffer and update data tracking appropriately.
*/
void ProcessOutputBuffer() {
// Refactor note: Ported from SGPWorld.cc ProcessHostOutputBuffer
//AEV TODO: Check which of these we have access to more easily than currently done
auto& cpu_state = GetHardware().GetCPUState();
const size_t env_task_id = cpu_state.GetTaskEnvID();
auto& task_env = my_world->GetTaskEnv();
const auto& task_io = task_env.GetIOBank().GetIO(env_task_id);
// Process output buffer
auto& output_buffer = cpu_state.GetOutputBuffer();
for (uint32_t val : output_buffer) {
// Is this the correct output for any tasks?
if (task_io.IsValidOutput(val)) {
// Yes, this output is correct.
// Get all task ids associated with this output value
const emp::vector<size_t>& task_ids = task_io.GetTaskIDs(val);
// Give credit for completed tasks
for (size_t task_id : task_ids) {
// Is this a host task?
if (!task_env.IsHostTask(task_id)) continue;
// Not first task
const bool not_first_task = sgp_config->HOST_ONLY_FIRST_TASK_CREDIT() && cpu_state.GetFirstTaskPerformed().Any() && !cpu_state.GetFirstTaskPerformed().Get(task_id);
if (not_first_task) {
continue;
}
// Has this organism already gotten credit with this output on this task?
if (cpu_state.OutputCredited(task_id, val)) continue;
// Check task requirements
auto& task_req_info = task_env.GetHostTaskReq(task_id);
if (!my_world->CanPerformTask(cpu_state, task_req_info)) {
continue;
}
// Manage CPU state after completing a task:
// (1) Mark task as being performed
cpu_state.MarkTaskPerformed(task_id);
// (2) Credit output
cpu_state.CreditOutputValue(task_id, val);
// (3) Clear output credits if outputs credited >= number of pre-computed outputs
// for this task in the task io bank.
if (cpu_state.GetOutputsCredited(task_id).size() >= task_io.GetNumTaskOutputs(task_id)) {
cpu_state.ResetCreditedOutputs(task_id);
}
// Calc value, add to organism points
double new_points = task_req_info.fun_calc_task_val(
task_env,
task_req_info,
GetPoints()
);
double task_points = new_points - GetPoints();
//World handles giving host points and adjusting that amount based on if any points are removed or by symbionts
my_world->ApplyHostPoints(*this, task_points,task_id);
my_world->GetHostTaskSuccesses()[task_id] += 1;
}
}
}
// Clear output buffer
output_buffer.clear();
}
/**
* Input: None.
*
* Output: A new host baby of the current host, mutated.
*
* Purpose: To create a new baby host and reset this host's points to 0.
*/
emp::Ptr<Organism> Reproduce() {
emp::Ptr<this_t> host_offspring = static_cast<this_t*>(Host::Reproduce().Raw());
auto& offspring_hw = host_offspring->GetHardware();
auto& offspring_cpu_state = offspring_hw.GetCPUState();
auto& cpu_state = hardware.GetCPUState();
host_offspring->SetReproCount(reproductions + 1);
cpu_state.SetCPUCyclesSinceRepro(0);
offspring_cpu_state.SetCPUCyclesSinceRepro(0);
// Offspring needs to be given parent's (this) task profile
offspring_cpu_state.SetParentTasksPerformed(
cpu_state.GetTasksPerformed()
);
offspring_cpu_state.SetParentFirstTaskPerformed(
cpu_state.GetFirstTaskPerformed()
);
// NOTE - Discuss how we use this information + how it's updated
// NOTE - This was previously behind a config setting; do we want to re-add
// ability to turn off this tracking?
// NOTE - Could have the systematics manager track this?
// TODO - move the lineage tracking logic into its own struct / functions
// Update "lineage" information:
// - lineage task loss / gain
// - lineage divergence / convergence toward parent's partner
const size_t num_tasks = offspring_cpu_state.GetNumTasks();
emp_assert(num_tasks == cpu_state.GetNumTasks());
for (size_t task_id = 0; task_id < num_tasks; ++task_id) {
const bool performed_task = cpu_state.GetTaskPerformed(task_id);
const bool parent_performed_task = cpu_state.GetParentTaskPerformed(task_id);
// Offspring gains susceptibility to be infected by sym with this task
const bool task_gain = performed_task && !parent_performed_task;
const bool task_loss = !performed_task && parent_performed_task;
offspring_cpu_state.SetLineageTaskGainCount(
task_id,
cpu_state.GetLineageTaskGainCount(task_id) + (size_t)task_gain
);
offspring_cpu_state.SetLineageTaskLossCount(
task_id,
cpu_state.GetLineageTaskLossCount(task_id) + (size_t)task_loss
);
// Divergence / convergence toward parent's partner
const size_t cur_task_diverge_partner = cpu_state.GetLineageTaskDivergeFromPartner(task_id);
const size_t cur_task_converge_partner = cpu_state.GetLineageTaskConvergeToPartner(task_id);
// NOTE - is this info on the offspring's convergence/divergence or info on *this* host's convergence/divergence?
bool converges = false;
bool diverges = false;
if (HasSym()) {
sgp_sym_t& sym = *static_cast<sgp_sym_t*>(syms[0].Raw());
// NOTE - Looking at sym's parent here (do we want to do this or look at sym?)
const emp::BitVector& sym_tasks = sym.GetHardware().GetCPUState().GetParentTasksPerformed();
const bool sym_performed_task = sym_tasks[task_id];
// converge: host_parent != sym_partner and host == sym_partner
converges = (parent_performed_task != sym_performed_task) && (performed_task == sym_performed_task);
// diverge: host_parent == sym_partner and host != sym_partner
diverges = (parent_performed_task == sym_performed_task) && (performed_task != sym_performed_task);
}
offspring_cpu_state.SetLineageTaskConvergeToPartner(
task_id,
cur_task_converge_partner + (size_t)converges
);
offspring_cpu_state.SetLineageTaskDivergeFromPartner(
task_id,
cur_task_diverge_partner + (size_t)diverges
);
}
// This organism reproduced, reset repro state.
hardware.GetCPUState().ResetReproState();
return host_offspring;
}
/**
* Input: None.
*
* Output: A new host with same properties as this host.
*
* Purpose: To avoid creating an organism via constructor in other methods.
*/
emp::Ptr<Organism> MakeNew() {
return emp::NewPtr<this_t>(
random,
my_world,
my_world->GetConfigPtr(),
GetProgram(),
GetIntVal()
);
}
/**
* Input: None
*
* Output: None
*
* Purpose: To mutate the code in the genome of this host.
*/
// Called by Host::Reproduce
void Mutate() {
// Mutate the interaction value
// NOTE - could also move this into the SGPMutator, which would allow us
// to deviate from what happens in the base class mutate functions
Host::Mutate();
// Apply SGP-specific mutations (managed by world)
my_world->HostDoMutation(*this);
// TODO - Switch from HostDoMutation() to:
// -> my_world->GetHostMutator().DoMutation(*this);
// TODO - move Hardware Reset to makenew, keep initializeState (need to reset jumptable)
// Reset host's hardware
hardware.Reset(); // NOTE - this function was previously just Initializing state,
// which didn't reset the cpu. I think we want to reset the CPU here also?
}
/*
*Input: None
*Output: None
*Purpose: To update the host's counter for the number of their symbionts that they task match with
*/
void UpdateSymMatchCount(){
matching_syms_to_interact_with = 0;
emp::vector<emp::Ptr<Organism>>& syms = GetSymbionts();
for (size_t endosym_i = 0; endosym_i < syms.size(); ++endosym_i) {
emp::Ptr<sgp_sym_t> cur_symbiont = static_cast<sgp_sym_t*>(syms[endosym_i].Raw());
const emp::BitVector& endosym_task_profile = my_world->GetSymTaskProfile(*cur_symbiont);
const emp::BitVector& host_task_profile = my_world->GetHostTaskProfile(*this);
bool is_matching = my_world->TaskProfileCompatibilityCheck(host_task_profile,endosym_task_profile);
matching_syms_to_interact_with += is_matching;
}
}
};
}
#endif