Project: Step 3.2
Mutable Parameters
Disclaimer: the automatically generated English translation is provided only for convenience and it may contain wording flaws. The original French document must be taken as reference!
Objectives:
Implement utility classes that allow for the modeling of variable parameters.
A number of bacterial characteristics may change over time. In some cases, this will affect their ability to survive.
For example, for single-flagellated bacteria, we saw in the project description that they will move at a constant speed. However, we will introduce the fact that this speed can change over time (making the bacteria slower or faster after a certain number of simulation cycles).
Single-flagellated bacteria will have other mutable parameters such as:
- their probability of randomly changing direction (tilting) to increase their chance of finding nutrients,
- or their color.
Changes in movement speed or in the probability of switching direction can clearly influence survival ability. You can also experiment with your simulation tool to try to "manually" identify the values that offer optimal survival ability.
The idea is to see if, through mutations, natural selection of optimal characteristics can occur.
The inclusion of color as a mutable trait will allow us to simulate the fact that certain attributes can mutate while being completely neutral with respect to survival fitness (the emergence of traits in a population without them being linked to a mechanism of positive selection).
This will potentially allow the simulation tool to demonstrate that not only traits favoring survival can become established in a population, but also neutral traits. The latter may be favored by external conditions such as the introduction of an obstacle into the culture box (project extension modules).
Let us now turn to the modeling of mutable traits.
These:
- can be of very different types: a double for the speed norm, a probability for traits related to flipping, or a "color" type value (we’ll discuss this further below) for color. Some mutable parameters will be double values that must remain positive (the length of tentacles in certain bacteria introduced later).
- all have in common the ability to change their value via a mutation method (mutate).
In the following, you are asked to program two classes to represent mutable values.
Program these utility classes in the src/Utility directory
The target for compiling and running this section is mutableTest. You must uncomment it when the time comes in the CmakeLists.txt. Note that Ctrl-F allows you to search within the QtCreator editor (to find where mutableTest is located, for example).
Class MutableNumber
A MutableNumber is simply a double whose value can change randomly. The random values will be drawn from a Gaussian (normal) distribution with a mean of zero and a given standard deviation.
A MutableNumber will have the following attributes:
- its double value.
- its probability of mutation, also a double.
- two booleans indicating, respectively, whether the number has a lower bound and an upper bound.
- its lower and upper bounds (whose values are meaningful only if the preceding booleans are true).
- the standard deviation allowing for random modification of the number’s value (a double).
You will provide this class with the following methods:
- a getter get() returning the value of the number.
- a set(double value) allowing you to assign a value to the number.
- a method mutate() that allows you to randomly modify the value of the number.
Here is one possible way to code it:
- call the function bernoulli (provided in Random/Random.[hpp/cpp]) by passing it the probability of mutation as a parameter. The function bernoulli(p) actually returns the value 1 with probability p and 0 with probability 1-p.
- If the call to the function bernoulli returns 1, generate a random number from a normal distribution using the call normal(0, sigma*sigma) where sigma is the value of the standard deviation attribute (as a reminder, the function normal is also defined in Random/Random.[hpp/cpp]).
- Finally, add the value of the random number thus generated to the value of the number.
The MutableNumber class will also be equipped with :
- a constructor that allows all of its attributes to be initialized with values passed as parameters. Boolean attributes will have false as their default value. The lower and upper bounds will have zero as their default value.
- a constructor that allows all of its attributes to be initialized from an entry in a configuration file (of type j::Value const&). Suppose the constructor parameter is j::Value const& config, then
- the value to be assigned to the number will be config["initial"].toDouble().
- the value to be assigned to the mutation probability will be config["rate"].toDouble().
- the value to be assigned to the standard deviation will be config["sigma"].toDouble().
- etc. (proceed in a similar manner using the "labels" "clamp min" , "clamp max", "min" and "max" from the configuration file app.json).
The values assigned to the mutable number must take into account that it may have lower and upper bounds. In this case, care must be taken to cap the assigned value using these bounds. Thus, attempting to assign a value lower than the lower bound will result in assigning the lower bound, and the same applies to the upper bound.
[Question Q3.7] Which of the methods suggested for a MutableNumber should limit the value between the lower and upper bounds ? How can code duplication be avoided if this processing needs to be repeated in multiple places ? Answer these questions in your REPONSES file and implement the suggested code.
To simplify the generation of mutable numbers of different types (probabilities, always-positive numbers), finally implement the following methods:
- probability(double initialValue, double mutationProbability, double sigma) creating and returning a MutableNumber of type probability (values capped between 0 and 1).
- probability(j::Value const& config) does the same as the previous one but retrieves the initial value, the mutation probability, and the standard deviation from a config entry in the configuration file.
- positive(double initialValue, double mutationProbability, double sigma, bool hasMax=false, double max=0.) creates and returns a MutableNumber of type positive (minimum values capped at zero).
- positive(j::Value const& config, bool hasMax=false, double max=0.) does the same as the previous one but retrieves the initial value, the mutation probability, and the standard deviation from an entry config in the configuration file.
[Question Q3.8] How should you proceed so that these methods can be invoked without creating an instance of MutableNumber ? Answer this question in your REPONSES file and implement the suggested code.
Class MutableColor
As mentioned earlier, the color of the bacteria will be a mutable parameter.
You are now asked to implement the class MutableColor to model the concept of mutable color.
A color will be represented by its four components, according to the RGBA model. Each component will be encoded as a MutableNumber.
You will therefore provide the MutableColor class with:
- an attribute representing the RGBA components of the color (an array of 4 MutableNumber).
- a constructor MutableColor(j::Value const& config) :
- assigning to the R component of the color a MutableNumber created from the entry config["r"] in the configuration file (to fully understand what you're doing, take a look at how the color of a simple bacterium is encoded in the app.json file; see this under the "monotrichous" tag).
- assigning a MutableNumber created from the config["g"] entry in the configuration file to the G component of the color.
- and doing the same for the two remaining components.
- a mutate() method that mutates each of the color components (remember that these are MutableNumbers with a mutate method!);
- a getter sf::Color get() returning the color in a format recognized by SFML :
The code for converting your 4
MutableNumber into a
sf::Color is relatively technical and is provided here as-is :
return { static_cast<sf::uint8>(comp[0].get() * 255),
static_cast<sf::uint8>(comp[1].get() * 255),
static_cast<sf::uint8>(comp[2].get() * 255),
static_cast<sf::uint8>(comp[3].get() * 255) };
where
comp represents the attribute of
MutableColor (the array of 4
MutableNumber).
For technical reasons, be sure to re-enable the default constructor of MutableNumber (what happens if we don't?).
Test 7: Mutable Parameters
The test program for this section is provided in the file src/Tests/UnitTests/MutablePropertyTest.cpp. This is a non-graphical test, similar to the one you used for the first part of the project.
Run this test using the target mutableTest. You will have taken care to uncomment it in the file CMakelists.txt and to have integrated this new material using Build > Run CMake.
Once your classes have been correctly coded, you should see the following displayed:
===============================================================================
All tests passed (6000 assertions in 1 test case)
Back to the project description (Part 3)
Next module (part 3.3)