Empirical Introductory Lab
Goals
To use Empirical to create a simple artificial ecology simulation.
Setup
Open the Empirical World Lab assignment on Moodle to get the Git repository template. You’ll need to do the usual setup of the submodules and emscripten.
Also open this reference document and the evolutionary algorithm lab to reference the methods that will be useful.
Exercise 1
a. The code is currently functional, try it out using the following commands:
./compile-run.sh
You’ll notice some warnings, but you can ignore those for now.
b. Nothing actually prints out currently. Open the file native.cpp
. This is the file that is run by the above commands. You can see that currently it just includes some files, makes a random number generator and a world object, but nothing else.
c. The first thing we need to do is create an organism that can be added to the world. Take a look at the Organism
constructor in Org.h
to see what arguments it currently takes and create an organism in native.cpp
and Inject()
it into the world:
Organism* new_org = new Organism(&random);
world.Inject(*new_org);
You can double check that your organism has made it into the world by printing out the world’s size:
std::cout << world.size();
d. If you didn’t add any more organisms or do anything else, your world would just have space for one organism. To force your world to have room for your population to grow, use the Resize()
method:
world.Resize(10, 10);
e. Verify that you have a population of one living organism in your world by printing out the result of world.GetNumOrgs()
. Compile and run your code using the same commands from (a).
Exercise 2
Now it’s time to actually make time proceed for your world.
The starter code has a simple Update
method in your world that doesn’t do much other than call the superclass’ method.
-
Add to this method so that it goes through every organism in the population and calls their
Process
method. You can get the size of the world withGetSize()
and the population of organisms is stored in the variablepop
. -
Go back to
native.cpp
and call your world’sUpdate
method, then compile and run to see what happens. -
You should notice that way more than one organism seems to be processing. This is because you need to check if a location is occupied before processing it (there are ghost organisms in all the ‘empty’ spots). Go back to
World.h
and add a check to yourUpdate
loop that if a position isn’t occupied, it skips that position inpop
:if(!IsOccupied(i)) {continue;}
-
Compile and run again to make sure that the correct number of organisms are processing (i.e. just one!).
-
Now you are ready to run for more updates. Write a for loop in
native.cpp
that callsUpdate
10 times.
Exercise 3
Because your Process()
method in Organism
doesn’t do any reproduction, your starting organism can’t actually reproduce. We could have the world take care of that process, but with the goal of keeping our organism class highly modular, we’ll have it do it instead.
a. In your Organism
class, add a method CheckReproduction()
that returns an emp::Ptr<Organism>
. It needs to be a pointer because sometimes we won’t return anything and we can’t return an empty reference, but we can return a null pointer. The Empirical pointer is nearly identical to the standard pointer, but has some additional debugging functionality.
b. In asynchronous reproduction models, instead of having a fitness function that determines which organisms reproduce every generation, we have resources or points that organisms accumulate and once they have enough, they reproduce. Include a check for if your organism has 1,000 points and if they do, create a new Organism
like this:
emp::Ptr<Organism> offspring = new Organism(*this);
This is using a copy constructor, which is provided by default in C++. It takes all the instance variables set for the current Organism and sets them the same for the new Organism.
c. The copy constructor is very useful for keeping everything about the parent the same as the offspring, however it also copies the value for points
which means that the offspring gets free resources! Change the offspring’s points back to 0 as it should be.
d. Finally, we also need to make sure that the parent actually pays the cost of reproduction, so subtract 1000 points from the parent’s points.
e. Since you need to return something even if you don’t make a new offspring, make sure to return a nullptr
in the situation where reproduction doesn’t occur.
Exercise 4
We have a reproduction method, but don’t actually call it yet. For that, we need to go into the World.h
file and add some things to its Update()
method.
a. We don’t want to give unfair advantage to organisms at the beginning of the list, since if they always get to reproduce first, genotypes could persist in the population even if they aren’t actually better, but just because they happen to be first in the list and so get checked for reproduction before everything else. Empirical has a useful function for getting a permutation of a list for this purpose:
emp::vector<size_t> schedule = emp::GetPermutation(random, GetSize());
size_t
is a special type in C++ that stands for “size type” and is appropriate for integers that will always be positive, like sizes and positions in a list.
b. Now you can use a for-loop to loop over the schedule:
emp::vector<size_t> schedule = emp::GetPermutation(random, GetSize());
for (int i : schedule) {
//do stuff
}
c. Organisms don’t have anyway of gaining points yet though. Change the Process
method so that it takes an argument points
and adds those points to what the organism has already. Give them 100 points per update for now. We could call the CheckReproduction
method right away as well, but this could lead to similar problems mentioned before where some organisms are lucky and get resources and the chance to reproduce right away.
d. Instead, create another schedule and loop after your first one to check reproduction after everyone has gotten resources.
e. Remember that if there is an offspring returned, you’ll need to add it to the population with the DoBirth
method.
emp::Ptr<Organism> offspring = pop[i]->CheckReproduction();
//this is implemented in Organism
if(offspring) {
DoBirth(*offspring, i); //i is the parent's position in the world
}
This is a good time to recompile and run to see your population size increase.
Exercise 5
Because Empirical supports cross-compiling from C++ to Javascript, you can visualize your simulation without a lot of extra code. The web.cpp
file contains the typical starter code for a browser visualization that you’ve seen before. You just need to add a few things from native.cpp
and draw your rectangles.
-
Create two private instance variables underneath the ones provided for your random number generator and world:
emp::Random random{5}; OrgWorld world{random};
-
In the constructor for your animator, create your new organism, inject it into the world, and resize the world, just like you did in
native.cpp
(you can literally copy and paste the code!). -
All the updating logic will go in
DoFrame
but without the for loop. Call your world’sUpdate
method there. -
Finally, you’ll want to draw your squares for your organisms and loop over the spaces to color them if there is an organism there. You’ll need to keep track of where you are in the world’s position in addition to your 2D grid:
int org_num = 0; for (int x = 0; x < num_w_boxes; x++){ for (int y = 0; y < num_h_boxes; y++) { if (world.IsOccupied(org_num)) { canvas.Rect(x * RECT_SIDE, y * RECT_SIDE, RECT_SIDE, RECT_SIDE, "black", "black"); } else { canvas.Rect(x * RECT_SIDE, y * RECT_SIDE, RECT_SIDE, RECT_SIDE, "white", "black"); } org_num++; } }
-
I’ve provided you with another file for compiling and running the web version of your code:
compile-run-web.sh
. Run this and make sure that you are getting a growing population of organisms. -
You probably noticed that your organisms are just popping up all over the place in your grid. This is because by default you have a well-mixed spatial structure, kind of like they are all floating in water. To enforce neighbors, change the population structure to a Grid using
SetPopStruct_Grid
in the constructor and see what that looks like:world.SetPopStruct_Grid(num_w_boxes, num_h_boxes);
-
Remember to
git add *
,git commit -m "message"
andgit push
so your code is saved since you’ll probably want it for the assignment!
Exercise 6
Organisms just sitting around isn’t the most exciting, so how do you get them to move? It’s unfortunately a little bit awkward because Empirical doesn’t have the exact built-in functions that we need.
-
First, you’ll need to make a new method in your
World
subclass that removes an organism from the population and returns it. I recommend calling itExtractOrganism
. You already know how to get an organism at a particular position in the world, and ‘removing’ it from the population just involves setting its spot to null:pop[i] = nullptr;
Then you just need to return the organism that you grabbed.
-
Once you’ve extracted an organism that you want to move, you need to decide where you want to put it. Assuming that you want to place it in a neighboring location to it according to your grid world structure, you can use
World
’sGetRandomNeighborPos(i)
which takes the organism’s current index location and returns anemp::WorldPosition
that can be treated as an index location (it’s not quite just a number, but you can pass it around like it is). -
Now you need to put your organism back into the world at the location that you’ve chosen. You can use
World
’sAddOrgAt
method to place it at a particular location:AddOrgAt(Organism, position);
You’ll need to decide if you let an organism move into a space that is already occupied!
-
To test your movement, I recommend preventing reproduction and having just one organism so you can watch them scoot around the grid!
Exercise 7
For your assignment, you’re going to need more than one species. Empirical’s World
can only hold one type, so you have to get a little bit creative to have multiple species and there are two main ways of going about it: an instance variable in Organism
and subclasses.
-
The instance variable approach is simpler but less elegant, so let’s do that first. Create an instance variable
int species
inOrganism
along with aGetSpecies
method and set the species in the constructor. Make sure your species is set correctly during reproduction too! -
Update your
.cpp
files so that they make one of each species to start. -
Update your
web.cpp
so that it draws a different color rectangle based on the species of the organism and make sure that you are seeing your two species. You’ll find theGetOrg(position)
method ofWorld
helpful for this. -
Species aren’t interesting if they don’t do something different, so make a conditional statement in
Process
and/orCheckReproduction
so that something is different based on the species. Maybe one of your species doesn’t get all the points each update or takes more points to reproduce. -
Make sure that you are seeing the difference between your species’ behavior in the web GUI.
Exercise 8
Having to use conditionals to check on the species all over the place is very inelegant and this is a perfect situation for inheritance! Create two subclasses (probably in their own files) that inherit from Organism
and move your different behaviors into those files. You’ll need to think about what can be shared in the superclass and what needs to be defined in the subclasses. You’ll also need to update your .cpp
files so that they create the correct types of organisms.
Extensions
If you have extra time, try adding mutation to your organism’s reproduction or adding to your organism’s Process
method so that it actually does something based on your instance variable genome. Ideas include:
- Donate resources to another organism
- Spend resources to steal from another organism
- Spend resources to build defense from the environment or other organisms
You could also try out Empirical’s Canvas image support so your organisms can be more than just colored boxes!
Credit
This lab uses the cookie-cutter material from this tutorial by Matthew Andres Moreno and Santiago Rodriguez Papa