Project: Step 5.2
Statistics (Trend Curves)

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: Graphically display the trend curves for certain parameters.

Complex mechanisms such as mutations are difficult to visualize over time by simply observing the contents of a culture dish. Similarly, to compare the evolution of several competing colonies, it is easier to use graphs.

The goal of this project phase is to enable the display of trend curves for a number of parameters, according to the following model (red box):

Modele
Due to the design choices made, the statistics displayed are those of the current dish.

Understanding and extending existing code is an important programming skill. In this step, you will practice this aspect a bit more extensively than before.

To complete this step, you will need to use a variant of associative arrays (see map.pdf), namely unordered associative arrays. An unordered_map is used in a similar way to a map (the subtle differences between the two will be discussed in class shortly).

Graph class

A Graph class is provided in the archive for this step (directory src/Stats).

This class allows you to draw a set of superimposed curves. Each curve is a series of interconnected points and is associated with a title (a string of characters).

An instance of Graph is therefore a set of overlapping curves. It is characterized by a label (also a string of characters).

For example, above you have a graph that overlays 4 series of points:

In the simulation, this graph will have a label (invisible on screen) used to identify it (for example, "General" for a general statistics graph, or "nutrient quantity" for a specific graph related to nutrient quantities, etc.).
Do not confuse the label of a graph used to identify it internally with the titles associated with each of its data series ("monotrichous", in red, in the window shown in the example above):
Modele

In summary, a graph has a label and a set of point series. Each point series has a title.

The idea is to be able to display several of these Graphs and switch between them using new controls (similar to the ones you used to control the temperature and gradient exponent).

For example, when the current control (the one in red) is the statistics control, it should be possible to switch using PgUp/X to display the graph showing the total amount of available nutrients in the current dish (associated with the label nutrient quantity), this graph would, for example, only have a series of points to display (no superimposed curves):

Modele
Various constants containing strings are provided in the file Utility/Constants.hpp. In particular, you will find the constant s::GENERAL for the label "General".

At this stage, you are asked to complete the Stats class (see below) to manage all the graphs you wish to display as part of this simulation and to add the functionality to switch from one graph to another.

The Stats class will therefore allow you to display multiple graphs, each graph being associated with a label that describes it.

The Application class now contains an attribute of type Stats* and offers a number of features related to this attribute. It also performs operations on this attribute (such as calling its drawOn method).

It is the graphical programs, such as the class FinalApplication, which, by calling the method addGraph inherited from Application, will define:

It is the FinalApplication class that, through calls to the addGraph method inherited from Application, will define the label associated with a graph, the set of point series associated with it (designated by their titles), as well as the lower and upper bounds of the value ranges associated with these point series.

Take a look at lines 25 and 26 of the onRun method in FinalApplication.cpp (currently commented out). These lines are specifically responsible for adding graphs to this program.

The Stats class

An object of the Stats class is essentially characterized by a set of graphs (a set of std::unique_ptr<Graph>) and a set of labels associated with these graphs (strings). This is a drawable object that evolves over time.

Each graph (and therefore each label as well) will be associated with an integer identifier. For example, identifier zero will be associated with the graph labeled "general," identifier 1 will be associated with the graph labeled "nutrient quantity," and so on.

An object of type Stats will also be characterized by the active identifier (the one corresponding to the option selected in the controls): the drawing method of Stats will draw only the graph with the active identifier.

In the Stats class you will complete:

Hints:

[Question Q5.3]: Which data structure(s) do you choose for the set of graphs and the set of titles in the Stats class? Answer this question in your REPONSES file and make the corresponding additions to your code.

You will also complete, in the Stats class:

The role of the Stats::addGraph method is to:
To comply with the decision to display only statistics for the current view, the statistics display area must be reset when switching from one dish to another. This will obviously be done using the reset method that you coded for the statistics and that you can invoke from any class in the code using the following syntax:
    getApp().resetStats(); // call to the Stats::reset method
  

Test 18: Preparing to display the curves

To test this section, you can continue using the final application provided in src/Tests/GraphicalTests/FinalApplication.cpp, which can be launched using the application target. Be sure to first uncomment the addition of the graphs (lines 25 and 26 of FinalApplication) and change the parameter on line 23 from false to true (to enable the display of statistics).

Note that the size of the graph area used to display statistics can be configured in the file app.json:

    "window":{
        "antialiasing level":4,
        "title":"INFOSV Simulation",
        "simulation":{
            "width":700,
            "height":700
        },
        "stats":{
            "width":200 // For example, replace 200 with 300 here
        },
        "control":{
            "width":300
        }
  

You should then simply see the labels associated with the upcoming curves displayed (without the actual curves being shown at this point). For example, for the graph labeled "general" :

Modele
The PgUp/X key (or equivalent) should allow you to switch to the "nutrient quantity" chart:
Model
PgUp/X (and PgDown/Y to switch back to the "general" chart).
Switching between charts using the PageDown/Y and PageUp/X is done by calling the previous and next methods of your Stats class.

Graph Data

To actually display curves, you must populate the data series associated with each graph. This is what you are asked to do in the following.

The Stats::update method

The data is not updated with every update but every sf::seconds(getAppConfig()["stats"]["refresh rate"].toDouble()); seconds.

Draw inspiration from your work in NutrientGenerator to implement this constraint.

The algorithm for updating the data is then as follows.

For each graph:

  1. calculate the new data to be inserted into each series of the graph;
  2. insert each new data point into its series;

Let’s start with an example. Suppose we are updating the Graph "general" (as described above). It consists of 5 series of points: one for the number of single-tentacled bacteria, another for the number of multi-tentacled bacteria, and so on. These series can be schematized as follows:

{ "monotrichous", {p11, p12, ...  p1j}}
{"pilus mediated", {p21, p22, ...  p2j}}
{"group motility", {p31, p32, ...  p3j}}
{"nutrient sources", {p41, p42, ...  p4j}}
{"temperature", {p51, p52, ...  p5j}}
where pXj is the number of entities in the series as calculated during the jth update (number of simple bacteria for X equal to 1, tentacled bacteria for X equal to 2, etc.)

Step 1 of the algorithm above must therefore calculate the set

{new_data = { "simple bacteria", p1j+1}, {"twitching bacteria", p2j+1}, ... {"nutrient sources", p4j+1}...}
where each of the pXj+1 is the number of elements of each type (simple bacteria, nutrients, etc.) as it can be calculated at the current time step from the Lab.

Step 2 of the algorithm “injects” this data into the graph to update the series:

{ "monotrichous", {p11, p12, ...  p1j+1}}
{"pilus mediated", {p21, p22, ...  p2j+1}}
{"group motility", {p31, p32, ...  p3j+1}}
{"nutrient sources", {p41, p42, ...  p4j+1}}
{"temperature", {p51, p52, ...  p5j+1}}

Step 2 of the algorithm can be implemented very simply by calling Graph::updateData(deltaT, new_data) on the graph to be updated, where deltaT is the time elapsed since the last data update and new_data is the set of new data for each series (as described in the previous example).

You are therefore asked to implement a method std::unordered_map<std::string, double> Lab::fetchData(const std::string &) that takes the graph title (a string) as a parameter and returns the corresponding new_data set (also consider whether this method should be marked as const or not).

For example:

[Question Q5.4] Which methods do you plan to add or modify, and in which classes, to perform the desired counts and construct the new_data sets? In other words, how can you count the number of instances of a certain class? Answer this question in your REPONSES file and implement the necessary code.

Test 19: Displaying Population Growth Curves

Run your simulation as before and create different types of bacteria. You should see the growth curves for the populations and the amount of nutrients displayed. Suddenly create a large number of bacteria of a certain type, and you should be able to see that the growth curve "reacts" accordingly. Verify that you can switch between statistics using the provided control keys:

Verify that the statistical curves reset properly when switching between culture dishes.

Test 20: Adding new curves (bonus)

Finally, complete the curve display by enabling the visualization of trend curves for variable numerical parameters. For example, for bacteria with tentacles, the average lengths and speeds of the tentacles.

Run your simulation as before. You should be able to visualize the evolution of the numeric parameters of your bacteria; here is an example for bacteria with tentacles:

Modele
You are then free to display any statistics you deem relevant. Note that lines 28, 29, and 30 of FinalApplication lay the groundwork for this.

You now have a complete simulation tool. Try configuring it to find conditions that, for example, allow for fluctuations in the numbers of the various populations without causing the extinction of any of them.

You can indicate in your README file which initial settings allow you to observe specific long-term population trends.
Back to the project description (part 5)