Added benchmarks. You can run benchmarks using cargo bench. The benches are all inside benches/.
Added logger feature to get information about the execution.
Added logger::VerbosityLevel to filter the output.
Added logger::VerbosityType to choose between only LOG, SAVE and LOG_AND_SAVE.
Logger
The logger is a very usefull tool to measure and retrieve some data from the execution. By default the logger is disabled, you can enable it this way:
use easy_ga::VerbosityLevel; // Verbosity level {DISABLED, LOW, MID, HIGH}
use easy_ga::VerbosityType; // Verbosity type {LOG, SAVE, LOG_AND_SAVE}
use easy_ga::LOG_verbosity; // Sets the verbosity level.
use easy_ga::LOG_verbosity_type; // Sets the verbosity type.
LOG_verbosity(VerbosityLevel::LOW); // VerbosityLevel::DISABLED by default
LOG_verbosity_type(VerbosityType::LOG_AND_SAVE); // VerbosityType::LOG by default
VerbosityLevel:
DISABLED: The logs are disabled.
LOW: Only very usefull information.
MID: Maybe not to desired information but also usefull.
HIGH: All logs are avaliable including tracing logs.
Benchmarking was added in the version 1.1.0 and you can run them donwloading the repository and running cargo bench from the command-line. The benchmarks are placed inside the benches/ folder.
Easy_GA is a genetic algorithm library made for Rust projects. It provides full customization for your own genotypes definitions and a genetic algorithm implementation to wrap all the common logic within a genetic algorithm.
Features
trait Gene: Definition to implement for your custom genotypes.
trait Selection: Definition for your custom selection algorithms.
Tournament: Selection algorithm implementation with n members on it.
GeneticAlgorithm: The main class to wrap the business logic in the genetic algorithm execution.
Usage
In your Cargo.tml you have to add the Easy_GA dependency
[dependencies]
easy_ga = "*"
Now I will show you a basic example of Easy_GA that you can find on main.rs
Files to include in order to use features:
use easy_ga::Gene; // For defining our own gene.
use easy_ga::GeneticAlgorithm; // To create a GeneticAlgorithm.
use easy_ga::SelectionAlgorithms; // To specity a concrete SelectionAlgorithm.
Definition of a custom Gene implementing easy_ga::Gene trait:
We have other ways to initializate our GeneticAlgorithm such as GeneticAlgorithm::new_with_values if we don’t want the chain calling method.
Now that we have defined our genotype and have initializate our GeneticAlgorhtm we have 2 ways of running it:
GeneticAlgorithm::run: This method runs the algorithm until the end and returns a tuple with (Gene, StopCriteria) that represents the best Gene in the execution and the reason to stop the execution.
let (gene, stop_criteria) = genetic_algorithm.run();
Iteration by iteration: We have the posibilty of running the algorithm generation by generation and make modification while the execution is running.
while genetic_algorithm.is_running() {
let new_generation: &Vec<MyGene> = genetic_algorithm.next_iteration();
}
Logger
The logger is a very usefull tool to measure and retrieve some data from the execution. By default the logger is disabled, you can enable it this way:
use easy_ga::VerbosityLevel; // Verbosity level {DISABLED, LOW, MID, HIGH}
use easy_ga::VerbosityType; // Verbosity type {LOG, SAVE, LOG_AND_SAVE}
use easy_ga::LOG_verbosity; // Sets the verbosity level.
use easy_ga::LOG_verbosity_type; // Sets the verbosity type.
LOG_verbosity(VerbosityLevel::LOW); // VerbosityLevel::DISABLED by default
LOG_verbosity_type(VerbosityType::LOG_AND_SAVE); // VerbosityType::LOG by default
VerbosityLevel:
DISABLED: The logs are disabled.
LOW: Only very usefull information.
MID: Maybe not to desired information but also usefull.
HIGH: All logs are avaliable including tracing logs.
Benchmarking was added in the version 1.1.0 and you can run them donwloading the repository and running cargo bench from the command-line. The benchmarks are placed inside the benches/ folder.
Next steps
This is a personal side project mainly for me so any further implementations will be done in my spare time as a good way to teach me more about Rust.
Multithreading
Add verbosity for debugging ✔
More unit testing and system testing
New default Selection algorithms
CSV and JSON result export
Fix some quality of life problems with references and chain calling
Add benchmarks ✔
License
Easy_GA is licensed under Mozilla Public License 2.0.
In some languages it is common to have conversions between different types such as converting an integer to a floating point, for example. There are two types of conversions in programming; implicit conversions and explicit conversions.
Explicit conversions
An explicit conversions, as the name suggest, it is when the programmer have to explicitly specify what conversion has to be done. This way the code may look more messy but the programmer has the control of the code in all time.
C++
voidfloatToInt(intx) { std::cout << x; // prints 42}intmain() {float x =42.51231;floatToInt(static_cast<int>(x));}
Implicit conversions
Implicit conversions are a powerfull tool that some languages provide usually to make the code easier to read and more agile when coding. Implicit conversions as the name suggest are done behind the scenes, wich means that the language itself knows that one type has to be converted to another type. In the code snippet below we can see an example:
C++
voidfloatToInt(intx) { std::cout << x; // prints 42}intmain() {float x =42.51231;floatToInt(x);}
In this case, we can see how easily is to lose information in some data types if you rely in implicit conversion too much. This is te reason some modern languages like Rust doesn’t allow implicit conversions.
“explicit” specifier
explicit specifier is a keyword that C++ provides and can be used in constructors and conversion functions to avoid undisered implicit conversions.
C++
structX {X(int) {}operator std::string() const { return std::string();}};structY {explicitY(int) {}explicitoperator std::string() const {return std::string();}};intmain() { X x1 =10; X x2(10); X x3 = {10}; std::string s1 = x1; std::string s2 =static_cast<std::string>(x1); Y y1 =10; // Error: cannot convert int to Y Y y2(10); Y y3 = {10}; // Error: cannot convert initializer-list to Y std::string s3 = y2; // Error: Implicit conversion not implemented. std::string s4 =static_cast<std::string>(y2);}
In an effort for C++ to prevent issues with implicit conversion we have the ‘uniform initialization’ or ‘braced initialization’ in C++11 with the operator{}. This operator forze us to expecify the exact type our constructor is expecting.
C++
structZ {Z(int, bool) {}};intmain() {intx1(10.5); // Implicit conversion from double to int -> 10int x2{10.5}; // Error: narrowing conversion from double to int. Z z1(10.5, -1); Z z2{10, -1}; // Error: narrowing conversion int to bool. Z z3{10, false};}
Explicit functions
But since ‘braced initialization’ only applies when constructing a type or an object, if we want a specific function to only accept the type we are indicating, the solution is a little bit more tricky.
1. Deleting function overloads
C++
structZ {Z() =default;voidFoo(int) {}voidFoo(float) =delete;voidFoo(bool) =delete;};intmain() { Z z1; z1.Foo(1); z1.Foo(1.5); // Error: use of deleted function z1.Foo(true); // Error: use of deleted function}
We can use generic parametrization to ensure that only functions we declare are going to be called.
C++
structZ {Z() =default;voidFoo(int) {}template<typenameT>voidFoo(T) =delete;};intmain() { Z z1; z1.Foo(1); z1.Foo(true); // Error: use of deleted function z1.Foo(1.5); // Error: use of deleted function z1.Foo('a'); // Error: use of deleted function}
2. Concepts
In C++ 20 concepts were added and it is the proper way to address this problem. Let’s see an example:
C++ is one of those languages that give us tools to play with almos every aspect of an implementation. Usually, programming languages with type conversions have some conversiones already defined by the standard. In this case, C++ allow us to define our owns conversions. In the next code snippet we can see an explicit and implicit conversion between two custom types. Both structs recieve 2 std::strings& and 1 int and implement custom casting to std::string.
In my humild opinion, I think we should avoid implicit conversion as much as possible. Particularly, when working with other people involved in the code they may not be aware of the implicit conversions you are using and this can tend to get lost easily in a large project.
ADL work is to set a couple of rules to lookup for functions based on their namespaces parameters provided, e.g:
C++
intmain() { std::vector<int> v;auto it = std::begin(v); // Common std::begin function.auto itAdl =begin(v); // Also calling std::begin because 'v' is in the std namespace.}
This is a common piece of code in C++ that everyone has written without knowing why exactly works. In this case both ways are equivalent because they are calling std::begin. The first one is called directly and the second one is found thanks to ADL set of rules.
This is very powerfull specially when working with operators like the example below:
C++
intmain() {const std::string& out = std::string("Hello") +"world"; std::cout << out;}
There is neither a + or << global operator but thanks to ADL it is smart enought to look for them in their relative namespaces. Let’s take a look what are the real code generated by the compiler using https://cppinsights.io/
In this case if our own accumulate implementation have not been provided, std::accumulate would be called instead.
Obviously we can take advantage of this an use it in our own code. Even thougt I personally think it is a better practice to always specify the namespace on our functions and objects, it could be a very powerfull solution if at some time we want to change a large piece of code just by swapping namespaces.
C++
namespace ns1 {structX{};voidg(constXx) {}voidf(constXx) {}}namespace ns2 {structX{};voidg(constXx) {}voidf(constXx) {}}intmain() { ns1::X x;g(x); // ns1::g(ns1::X) is called.f(x); // ns1::f(ns1::X) is called.}
In this particular example if we change the namespace in the object declaration, all the function calls would also change.
You might think that this is a stupid debate, and maybe you are right but let me take a moment to explain to you if we should care whether to return true or false.
True and false are the basis in computer programming and traditional logic. The binary system is used based on this only two possibilities, the value is either one or zero.
To understand if returning true or false has an impact in our code performance we have to talk about how computers work in a very low level, in specific I’m going to talk a bit about x86-64 assembly and different C++ compilers.
Benchmarking
Once we compile our code in C++ the compilers convert the code into assembly code. In assembly we usually talk about number of instructions instead of number of lines. And even different instructions can take different number of cycles to be processed in our CPU. Okey, let’s see how return true and return false is compiled into assembly code using GCC 11.2 and Clang 12.0.1. Both without any optimizations on.
Clang 12.0.1 -O0
C++
/* push rbp mov rbp, rsp xor eax, eax and al, 1 movzx eax, al pop rbp ret*/boolReturnFalse() {returnfalse;}/* push rbp mov rbp, rsp mov al, 1 and al, 1 movzx eax, al pop rbp ret*/boolReturnTrue(){returntrue;}
As we can see Clang takes 4 more instructions than GCC to do the same piece of code. Now let’s take a look what happens when we turn optimizations on (-O3) in both compilers.
Now both compilers are able to perform both tasks in only 2 instructions. But as I mention before all instructions are not the same, let’s take a moment to analize this in machine code.
The instruction mov definitions is: “Copies the second operand (source operand) to the first operand (destination operand)”. Wich means that we are copying the right value to the left register. And its translation to machine code is:
ShellScript
moveax,1# b8 01 00 00 00
ShellScript
moval,1# b0 01
Why are the machine code different if both instructions perform the same operation? This is because copying a value into a register depends on the register. In this case eax is a 32 bits register meanwhile al is an 8 bit subset of eax register.
On the other hand, the xor definition says: “Performs a bitwise exclusive OR (XOR) operation on the destination (first) and source (second) operands and stores the result in the destination operand location“. A classic XOR logic operation which has the advantage that if done with the register itself it will always become 0. The CPU is extremely optimized to perform this kind of bitwise operations.
Returning 0 (false) seems to be better than returning 1 (true) but the difference it is almost indistinguishable. Even thought it is always a good practice to analize your problem and try to optimize it as much as you can, e.g: You can not always return false instead of true, what you can is to study the percentage of times your result will be true or false. This is a common practice when developing in assembly code specially when you have to deal with some if-else and you really want to just ride of the evaluations as fast as possible.
Shinobu is a videogame made for Amstrad CPC using Z80 assembly and with the CPCtelera library. This project was highly related to optimize every piece of memory due to the Amstrad CPC limitation of 16KB of memory RAM.
The source code can be found in the github repository and the game can be played not only in a physical Amstrad CPC but in a web browser.
If you know a bit of modern C++ you will have use the std::vector<T> container a lot of times without even wondering what type you specify to instantiate the container. This is fine for every case except for on specific one, std::vector<bool>.
If you go to the std::vector specification you will find that there is only one specification which is std::vector<bool>. That’s because the bool type specification is implemented outside the standard std::vector<T> container. The reason for this could be pretty obvious and logic for you but it could also be a headache for others who try to use it.
As you may know one bool has the size of 1 byte in the C++ standard. This may make us think why we should use 8 bits when we can know if a bool is true or false just with 1 bit of information, if it’s either 1 = true or 0 = false. Well, that’s exactly what the C++ commitee thought back in the days and decided to use only 1 bit to represent the value in the container, in this way the can store up to 8 values just in 1 byte of memory which means 8 more values than using a standard boolean.
And with that decision is when the problems come. As you may know, a reference is just a memory adress to the beginning of a specific data, which means that you can point to the start of something and increase this position byte by byte.
As you can see the offset between the first and the second integer is 4 bytes because that is the size of an int in C++ standard. Now lets try the same with std::vector<bool>.
“Exposes class std::vector<bool>::reference as a method of accessing individual bits. In particular, objects of this class are returned by operator[] by value.“
And for the same reason you can’t take values by reference using base-ranged loops which are implemented based on begin() and end().
C++
#include<vector>#include<iostream>intmain() { std::vector<bool> vector_bool{false, true}; std::vector<int> vector_int{1, 2, 3};for (int& i : vector_int) { // OK. }for (bool& b : vector_bool) { // error: cannot bind non-const lvalue reference of type 'bool&' to an rvalue of type 'bool' }}
Finally, to show the final evidence about the difference between std::vector<T> and std::vector<bool> we can see their sizes in bytes.
Twitch Loot Auto-Clicker is a browser extension to improve the experience while using twitch.tv. This extensions find where do you have points avaliable to collect in a twitch channel and auto click them for you to maximize your rewards.
Its second utility is to bring to you an extense and detailed summary about the points earned while using the extension.
The extension has over 700 active daily users and growing every day with users all arround the globe.
Spark Engine is a graphic engine made from scratch using C++ 17 and OpenGL. Spark Engine has the ability to render 3D and 2D objects and its main object is to be used for videogames and graphics shows.
Among all its features we can mention the ability to render dynamic lighting, shadows, and a full integrated particle system. It is also capable to generate and render custom vegetation on an optimized way. Its frustum feature bring the user the posibility to only render what the user is seeing to improve the performance.
The engine can be used in Windows or Linux as a dynamic library for C++. The source code can be found in the open github repository.
Beast Brawl is a PC videogame made as my final year project with other five teammates. The objective is to capture the main item and keep it for the longest time possible while other players try to steal it.
It is in essence a racing game with power ups that will help you to either mantein or steal the object. It has an online mode to play with other players and compete to be the best beast.
The game has been made completely from scratch using C++ 17. That includes also the physics engine, graphic engine ,sound engine and network engine.