-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathSGPWorld.cc
More file actions
473 lines (424 loc) · 18.1 KB
/
Copy pathSGPWorld.cc
File metadata and controls
473 lines (424 loc) · 18.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
#ifndef SGP_WORLD_C
#define SGP_WORLD_C
#include "SGPWorld.h"
#include "SGPHost.h"
#include "SGPSymbiont.h"
#include "../utils.h"
namespace sgpmode {
// TODO - Make clear that this will process host and free-living symbiont
// ProcessOrgsAt?
void SGPWorld::ProcessOrgsAt(size_t pop_id) {
// Process host at this location (if any)
if (IsOccupied(pop_id)) {
auto& org = GetOrg(pop_id);;
emp_assert(org.IsHost());
ProcessHostAt(
{pop_id},
static_cast<sgp_host_t&>(org)
);
}
// TODO - double check that my interpretation is correct here
// TODO - can we condiitonally tack this onto processing only
// when free-living syms are turned on
// Process free-living symbiont at this location (if any)
if (IsSymPopOccupied(pop_id)) {
emp_assert(!GetSymAt(pop_id)->IsHost());
ProcessFreeLivingSymAt(
emp::WorldPosition(0, pop_id),
static_cast<sgp_sym_t&>(*(GetSymAt(pop_id)))
);
}
}
// TODO - discuss timing
// NOTE - DoDeath repeated several times here. Maybe move that check out to ProcessOrgsAt?
void SGPWorld::ProcessHostAt(const emp::WorldPosition& pos, sgp_host_t& host) {
// Update host location
host.GetHardware().GetCPUState().SetLocation(pos); // TODO - is this necessary here?
// NOTE - Will symbionts be able to modify host's cycles during *their* executation?
// How do we want to handle that? (modify host's execution on next update?)
// NOTE - Will need to update/revist this if we have instruction-mediated interactions
// Hosts gain baseline number of CPU cycles
host.GetHardware().GetCPUState().GainCPUCycles(
sgp_config.CYCLES_PER_UPDATE()
);
host.Process(pos);
//check if host is dead at return
if (host.GetDead()){
DoDeath(pos);
}
}
// TODO - Handle Reproduction?
// TODO - Go over support for free-living symbionts. Not sure it was
// fully supported to begin with, so should discuss what needs to be added.
void SGPWorld::ProcessFreeLivingSymAt(const emp::WorldPosition& pos, sgp_sym_t& sym) {
// TODO - ask about the code below (should it ever be run on endosymbionts?)
// if (my_host == nullptr && my_world->GetUpdate() % sgp_config->LIMITED_TASK_RESET_INTERVAL() == 0)
// cpu.state.used_resources->reset();
emp_assert(!sym.IsHost()); // NOTE - IsSym function?
// have to check for death first, because it might have moved
if (sym.GetDead()) {
DoSymDeath(pos.GetPopID());
} else {
// Sym gains cpu cycles
sym.GetHardware().GetCPUState().GainCPUCycles(sgp_config.CYCLES_PER_UPDATE());
// Not dead, process.
before_freeliving_sym_process_sig.Trigger(sym);
// NOTE - Do we want to drain cpu cycles here (i.e., get cashed in for execution?)
const size_t cycles_to_exec = sym.GetHardware().GetCPUState().ExtractCPUCycles();
for (size_t i = 0; i < cycles_to_exec; ++i) {
sym.GetHardware().RunCPUStep(1);
// Did this sym attempt to reproduce?
if (sym.GetHardware().GetCPUState().ReproAttempt()) {
FreeLivingSymAttemptRepro(pos, sym);
}
after_freeliving_sym_cpu_step_sig.Trigger(sym);
}
after_freeliving_sym_cpu_exec_sig.Trigger(sym);
// Call symbiont's process function
sym.Process(pos);
after_freeliving_sym_process_sig.Trigger(sym);
}
// TODO - double check that this belongs just here and not also in endosymbiont code
if (IsSymPopOccupied(pos.GetPopID()) && sym.GetDead()) {
DoSymDeath(pos.GetPopID());
}
}
void SGPWorld::FreeLivingSymAttemptRepro(
const emp::WorldPosition& pos,
sgp_sym_t& sym
) {
// NOTE - this is largely redundant with other attempt functions. Need to think
// think about whether attempt logic should be different
// NOTE - could make this a configurable functor if we want different success/failure
// conditions on attempt
const double repro_cost = sgp_config.FREE_SYM_REPRO_RES();
if (sym.GetPoints() >= repro_cost) {
// Sym pays cost
sym.DecPoints(repro_cost);
// Add sym to repro queue
// TODO - protect with mutex for threading
const size_t queue_id = repro_queue.Enqueue(
sym.GetHardware().GetCPUState().GetOrgPtr(),
pos
);
// Mark symbiont's hardware as repro in progress, no longer in "attempt" state
sym.GetHardware().GetCPUState().MarkReproInProgress(queue_id);
} else {
// Attempt failed, so reset repro state.
sym.GetHardware().GetCPUState().ResetReproState();
}
}
void SGPWorld::SetMutationZero() {
// Call base world's set mutation to zero function
SymWorld::SetMutationZero();
// Set sgp mutation rate to 0
mutator.SetPerBitMutationRate(0);
}
void SGPWorld::DoReproduction() {
// Process reproduction queue
// NOTE - If do repro remains simplified to just calling the repro_queue's
// process function, can get rid of this function.
repro_queue.Process();
}
// Called for symbionts in the reproduction queue
// fun_sym_do_birth is set to either free living or horizontal infection based on config
emp::WorldPosition SGPWorld::SymDoBirth(
emp::Ptr<Organism> sym_baby,
emp::WorldPosition parent_pos
) {
emp_assert(!sym_baby->IsHost());
emp::Ptr<sgp_sym_t> sym_baby_ptr = static_cast<sgp_sym_t*>(sym_baby.Raw());
// Trigger any before birth actions
before_sym_do_birth_sig.Trigger(sym_baby_ptr, parent_pos);
emp::WorldPosition sym_baby_pos(fun_sym_do_birth(sym_baby_ptr, parent_pos));
return sym_baby_pos;
}
emp::WorldPosition SGPWorld::HostDoBirth(
emp::Ptr<Organism> host_offspring_ptr,
emp::Ptr<Organism> host_parent_ptr,
const emp::WorldPosition& parent_pos
) {
emp_assert(host_offspring_ptr->IsHost());
emp_assert(host_parent_ptr->IsHost());
// Static cast host offspring and host parent pointers
emp::Ptr<sgp_host_t> offspring_ptr = static_cast<sgp_host_t*>(host_offspring_ptr.Raw());
emp::Ptr<sgp_host_t> parent_ptr = static_cast<sgp_host_t*>(host_parent_ptr.Raw());
before_host_do_birth_sig.Trigger(
*offspring_ptr,
*parent_ptr,
parent_pos
);
// NOTE - Can make contents of this function into a functor if needs to be
// configurable for different types of hosts.
// Host::Reproduce() doesn't take care of vertical transmission, that
// happens here. Loop over parent's symbiont, check if each can transmit
// vertically to host offspring.
for (emp::Ptr<Organism> sym_org_ptr : parent_ptr->GetSymbionts()) {
emp_assert(!sym_org_ptr->IsHost());
// Cast generic org pointer to more specific sym pointer type
emp::Ptr<sgp_sym_t> sym_ptr = static_cast<sgp_sym_t*>(sym_org_ptr.Raw());
// This symbiont attempts vertical transmission (returns success if necessary), relevant checks performed in SGPSymbiont
sym_ptr->VerticalTransmission(offspring_ptr);
}
// Call emp::World's DoBirth for host offspring that we're currently "birthing".
const emp::WorldPosition offspring_pos(DoBirth(host_offspring_ptr, parent_pos));
after_host_do_birth_sig.Trigger(offspring_pos);
return offspring_pos;
}
emp::WorldPosition SGPWorld::FreeLivingSymDoBirth(
emp::Ptr<sgp_sym_t> sym_baby_ptr,
const emp::WorldPosition& parent_pos
) {
// TODO - add any signals?
return MoveIntoNewFreeWorldPos(sym_baby_ptr, parent_pos);
}
emp::WorldPosition SGPWorld::SymAttemptHorizontalInfection(
emp::Ptr<sgp_sym_t> sym_baby_ptr,
const emp::WorldPosition& parent_pos
) {
// TODO - add any signals?
const size_t parent_pop_idx = parent_pos.GetPopID();
emp::Ptr<Organism> parent = this->GetOrgPtr(parent_pop_idx)->GetSymbionts()[parent_pos.GetIndex() - 1];
emp_assert(!parent->IsHost());
emp::Ptr<sgp_sym_t> sym_parent = static_cast<sgp_sym_t*>(parent.Raw());
// hew_host_pos is an optional<emp::WorldPosition>
const auto new_host_pos = FindHostForHorizontalTrans(parent_pop_idx, sym_parent);
if (new_host_pos) {
const size_t host_id = new_host_pos.value().GetIndex();
int new_index = pop[host_id]->AddSymbiont(sym_baby_ptr);
if (new_index > 0) {
//sym successfully infected
return emp::WorldPosition(new_index, host_id);
} else {
//sym got killed trying to infect
return emp::WorldPosition();
}
} else {
sym_baby_ptr.Delete();
return emp::WorldPosition();
}
}
// Process any symbiont offspring that "escaped" the stress event
void SGPWorld::ProcessStressEscapees() {
emp_assert(repro_queue.GetSize() == 0);
// Process escapees in random order (to avoid strongly favoring all offspring from "late" escapee)
escapee_ids.resize(symbiont_stress_escapees.size(), 0);
std::iota(
escapee_ids.begin(),
escapee_ids.end(),
0
);
emp::Shuffle(*random_ptr, escapee_ids);
// for (size_t esc_i = 0; esc_i < symbiont_stress_escapees.size(); ++esc_i) {
for (size_t esc_i : escapee_ids) {
// (1) Find place to AddSymbiont
auto& escapee_info = symbiont_stress_escapees[esc_i];
bool success = false;
for (size_t attempt_i = 0; attempt_i < sgp_config.FIND_NEIGHBOR_HOST_ATTEMPTS(); ++attempt_i) {
emp::WorldPosition candidate_pos(GetRandomNeighborPos(escapee_info.escape_location));
if (candidate_pos.IsValid() && IsOccupied(candidate_pos)) {
emp::Ptr<Organism> neighbor_org_ptr = GetOrgPtr(candidate_pos.GetIndex());
emp_assert(neighbor_org_ptr->IsHost());
// Cast neighbor as sgp_host_t ptr.
emp::Ptr<sgp_host_t> neighbor_host_ptr = static_cast<sgp_host_t*>(neighbor_org_ptr.Raw());
// Check whether escapee can infect?
const bool can_infect = fun_host_sym_stress_trans_compatibility_check(
*neighbor_host_ptr,
escapee_info.parent_task_profile
);
// TODO - add stress infect success tracking
if (!can_infect) continue;
// escapee_info.sym_offspring->GetHardware().GetCPUState().ResetReproState();
//AssignNewEnvIO(escapee_info.sym_offspring->GetHardware().GetCPUState()); // AEV No longer needed, added to AddSymbiont
// int new_index =
neighbor_host_ptr->AddSymbiont(escapee_info.sym_offspring);
// AddSymbiont might fail (but when it does, it deletes the offspring)
// so not possible to keep attempting until actual success
success = true;
break;
}
}
// If sym didn't successfully infect, delete it.
if (!success) {
escapee_info.sym_offspring.Delete();
}
}
symbiont_stress_escapees.clear();
// TODO - add data collection for successful escapes
}
void SGPWorld::ProcessGraveyard() {
// clean up the graveyard
for (size_t i = 0; i < graveyard.size(); ++i) {
// NOTE - Does this need to call DoDeath?
// the original implementation (in old Update function) does not
GetCPUState(graveyard[i]).ResetReproState();
graveyard[i].Delete();
}
graveyard.clear();
}
// TODO - add test to make sure this works for hosts as well
void SGPWorld::SendToGraveyard(emp::Ptr<Organism> org) {
// NOTE - Previous version of this function assumed symbiont
// Just in case we end up needing it for host's, might as well make it
// work for them as well?
auto& cpu_state = GetCPUState(org);
if (cpu_state.ReproInProgress()) {
repro_queue.Invalidate(
cpu_state.GetReproQueuePos()
);
}
SymWorld::SendToGraveyard(org);
}
std::optional<emp::WorldPosition> SGPWorld::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) */
) {
// Outsource to configurable functor
return fun_find_host_for_horizontal_trans(host_world_id, sym_parent_ptr);
}
void SGPWorld::ProcessSymOutputBuffer(sgp_sym_t& sym) {
auto& cpu_state = sym.GetHardware().GetCPUState();
const size_t env_task_id = cpu_state.GetTaskEnvID();
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 valid sym task?
if (!task_env.IsSymTask(task_id)) continue;
// Not first task
const bool not_first_task = sgp_config.SYM_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.GetSymTaskReq(task_id);
if (!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);
}
// Track success
++sym_task_successes[task_id];
// Calc base task value based on task environment, task requirements, and
// symbiont's current point value.
// NOTE - A little funky because task value might be a multiplier on
// current sym points.
// So, to get the value *added* by the task, we subtract original point value.
double new_points = task_req_info.fun_calc_task_val(
task_env,
task_req_info,
sym.GetPoints()
);
double task_points = new_points - sym.GetPoints();
//Parasitic Nutrient symbionts receieve less rewards from completing tasks to incentivize matching tasks with hosts
if(sgp_config.ENABLE_NUTRIENT() && GetNutrientSymType() == nutrient_sym_mode_t::PARASITE){
task_points *= sgp_config.PARASITE_BASE_TASK_VALUE_PROP();
}
// Add earned task points to symbiont's point total
sym.AddPoints(task_points);
// // Enforce limits on points
// const double max_points = sgp_config.SYM_HORIZ_TRANS_RES();
// if (sym.GetPoints() > (1.5 * sgp_config.SYM_HORIZ_TRANS_RES())) {
// sym.SetPoints(1.5 * sgp_config.SYM_HORIZ_TRANS_RES());
// }
}
}
}
// Clear output buffer
output_buffer.clear();
}
SGPWorld::mutator_t SGPWorld::getMutator(){
return mutator;
}
void SGPWorld::SymDonateToHost(Organism& from_sym, Organism& to_host) {
emp_assert(!from_sym.IsHost());
emp_assert(to_host.IsHost());
// NOTE - could make this a configurable functor if we think
// that different config settings will need different donate logic.
// NOTE - could static cast sym to sgp_sym, host to sgp_host if necessary
sgp_sym_t& sym = static_cast<sgp_sym_t&>(from_sym);
sgp_host_t& host = static_cast<sgp_host_t&>(to_host);
// Donate X% of the total points of the symbiont-host system
// This way, a sym can donate e.g. 40 or 60 percent of their points in a
// couple of instructions
const double sym_points = sym.GetPoints();
const double to_donate = emp::Min(
sym_points,
(sym_points + host.GetPoints()) * sgp_config.SYM_DONATE_PROP()
);
// TODO - Protect for threaded implementation
// TODO - setup data tracking
// state.world->GetSymDonatedDataNode().WithMonitor(
// [=](auto &m) { m.AddDatum(to_donate); });
// Adjust host/sym points accordingly
host.AddPoints(to_donate);
sym.DecPoints(to_donate);
}
void SGPWorld::SymStealFromHost(Organism& to_sym, Organism& from_host) {
emp_assert(!to_sym.IsHost());
emp_assert(from_host.IsHost());
// NOTE - could make this a configurable functor if we think
// that different config settings will need different steal logic.
// NOTE - could static cast sym to sgp_sym, host to sgp_host if necessary
sgp_sym_t& sym = static_cast<sgp_sym_t&>(to_sym);
sgp_host_t& host = static_cast<sgp_host_t&>(from_host);
const double to_steal = emp::Min(
host.GetPoints(),
(sym.GetPoints() + host.GetPoints()) * sgp_config.SYM_STEAL_PROP()
);
// TODO - make safe for threading mode + setup data tracking
// state.world->GetSymStolenDataNode().WithMonitor(
// [=](auto &m) { m.AddDatum(to_steal); });
host.DecPoints(to_steal);
sym.AddPoints(to_steal);
}
void SGPWorld::FreeLivingSymDoInfect(Organism& sym) {
emp_assert(!sym.IsHost());
emp_assert(sgp_config.SYM_LIMIT() >= 0);
// NOTE - Could add some runtime customizability here if we want. E.g., functors, etc.
sgp_sym_t& sgp_sym = static_cast<sgp_sym_t&>(sym);
// Get sym's location in emp::World pop
const size_t pop_index = sgp_sym.GetHardware().GetCPUState().GetLocation().GetPopID();
// Check that there's an available host
// If this location isn't occupied, infect fails (at no cost?).
if (!IsOccupied(pop_index)) {
return;
}
// Check that there's enough space for infection
const size_t num_syms = pop[pop_index]->GetSymbionts().size();
// NOTE - Should sym_limit be allowed to be negative in config?
if (num_syms < (size_t)sgp_config.SYM_LIMIT()) {
// Extract the symbiont from the fls vector and decrement the free-living org
// count. Then add the sym to the host's sym list.
// TODO - consider whether we want signals here + if there are some
// bookkeeping things we need to do. E.g., add signals, etc.
// TODO - Do we need to assign a new environment here? I don't think so?
// Symbiont should have been assigned an environment on birth.
// NOTE - Previously, the infect instruction did not check whether AddSymbiont
// was successful. Discuss whether we want to check that here.
pop[pop_index]->AddSymbiont(ExtractSym(pop_index));
sgp_sym.GetHardware().GetCPUState().SetLocation(
emp::WorldPosition(pop_index, num_syms)
);
} else {
// Injection failed, set it dead and do deletion next update
sgp_sym.SetDead();
}
}
}
#endif