├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── build.yml │ └── test.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── data │ └── tsp.txt ├── eqn.rs ├── gen_eqn.rs ├── huim.rs ├── knp.rs └── tsp.rs ├── src ├── crossover │ ├── blend.rs │ ├── cycle.rs │ ├── linear.rs │ ├── mod.rs │ ├── multi_point.rs │ ├── order.rs │ ├── partially_mapped.rs │ ├── shuffle.rs │ ├── simulated_binary.rs │ ├── single_point.rs │ ├── uniform.rs │ └── uniform_partially_mapped.rs ├── lib.rs ├── mutation │ ├── flipping.rs │ ├── inversion.rs │ ├── mod.rs │ ├── polynomial.rs │ ├── random.rs │ ├── scramble.rs │ └── swap.rs ├── scaling │ ├── linear.rs │ ├── mod.rs │ └── sigma.rs └── selection │ ├── mod.rs │ ├── random.rs │ ├── rank.rs │ ├── roulette_wheel.rs │ ├── steady_state.rs │ ├── stochastic_universal.rs │ └── tournament.rs ├── tea.yaml └── tests ├── test_crossover.rs ├── test_mutation.rs └── test_selection.rs /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Desktop (please complete the following information):** 23 | - OS: [e.g. iOS] 24 | - Browser [e.g. chrome, safari] 25 | - Version [e.g. 22] 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 👷 20 | run: cargo build --verbose 21 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | test: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Run tests 🕵🏻‍♂️ 20 | run: cargo test --verbose 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | lakshay.singh1108@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "genx" 3 | version = "0.4.0" 4 | authors = ["Lakshya Singh "] 5 | edition = "2018" 6 | include = [ 7 | "src/**/*", 8 | "examples/*.rs", 9 | "Cargo.toml", 10 | "README.md", 11 | "LICENSE" 12 | ] 13 | homepage = "https://github.com/king-11/genx" 14 | description = "An easy to use and modular genetic algorithms library." 15 | repository = "https://github.com/king-11/genx" 16 | keywords = ["genetic", "evolutionary", "algorithm", "optimization", "ml"] 17 | categories = ["algorithms", "mathematics", "science"] 18 | readme = "README.md" 19 | license = "MIT" 20 | 21 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 22 | 23 | [dependencies] 24 | rand = "0.8.4" 25 | itertools = "0.10.1" 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Lakshya Singh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # genx 2 | 3 | ![crb](https://img.shields.io/crates/v/genx.svg) 4 | ![dcb](https://docs.rs/genx/badge.svg) 5 | ![build](https://img.shields.io/github/workflow/status/king-11/genx/Rust) 6 | ![size](https://img.shields.io/github/languages/code-size/king-11/genx) 7 | ![downloads](https://img.shields.io/crates/d/genx?color=rgb%2849%20190%20224%29) 8 | ![license](https://img.shields.io/github/license/king-11/genx) 9 | 10 | **genx** provides modular building blocks to run simulations of optimization and search problems using [Genetic Algorithms](https://en.wikipedia.org/wiki/Genetic_algorithm) (GA). 11 | 12 | The vision for genx is to be a __flexible__ and greatly __extensible__ library for implementing genetic algorithm applications. genx is written in Rust. The library's API utilizes functional programming paradigm and exposes it's API in that manner only. 13 | 14 | The implementation is split into __building blocks__ which are all represented by traits. This crate provides most common and probably all possible implementation for all building blocks. So it can be used for many problems out of the box. 15 | 16 | [Documentation](https://docs.rs/genx/) 17 | 18 | ## Basic Example 19 | 20 | ### Selection 21 | 22 | Here's a trivial example that returns back individual selected using [stochastic universal sampling](https://en.wikipedia.org/wiki/Stochastic_universal_sampling) 23 | 24 | ```rust 25 | use genx::selection::stochastic_universal::stochastic_universal_selection; 26 | 27 | let num_parents:usize = 10; 28 | let fitness_values = vec![2.4,8.4,3.2,9.4,9.0,11.0,4.5,0.6,4.4,2.3,5.6,10.0,0.2,9.0,4.8,7.7]; 29 | 30 | let result = stochastic_universal_selection(&fitness_values, num_parents, None) 31 | .iter() 32 | .map(|&a| fitness_values[a]) 33 | .collect::>(); 34 | ``` 35 | 36 | `stochastic_universal_selection` takes in the `fitness_value` vector, number of parents it needs to select and a seed value which is `Option`. It returns back the indices of selected individuals which we map to actual fitness values. 37 | 38 | ### Mutation 39 | 40 | Mutation function takes in a single individual, distribution index and returns in the mutated individual using [polynomial mutation](https://www.iitk.ac.in/kangal/papers/k2012016.pdf) for real valued individual. 41 | 42 | ```rust 43 | use genx::mutation::polynomial::polynomial_mutation; 44 | let individual = 29.11; 45 | let result = polynomial_mutation(individual, 4.2, 4.0, None); 46 | ``` 47 | 48 | The returned value may or may not be equal as is mutated based on a randomly generated value, which for deterministic results can be seeded. 49 | 50 | ## Building Blocks 51 | 52 | The genetic algorithm needs a population that it evolves with each iteration. A population contains a number of individuals. Each individual represents a possible candidate solution for an optimization problem for which the best solution is searched for. 53 | 54 | A Genetic Algorithm proceeds through following operations: 55 | 56 | - __Encoding__: Binary, Real Values, Order, Tree, etc. 57 | - __Selection__: Selecting individuals after fitness evaluation. 58 | - __Crossover__: Creating new individuals from selected pool of individuals. 59 | - __Mutation__: Making stark changes in generated individual for diversification. 60 | - __Convergence__: Test for goal accomplishment or convergence. 61 | 62 | The building blocks currently available (defined as traits) are: 63 | 64 | - __Selection__ 65 | - __Mutation__ 66 | 67 | This crate provides multiple implementations for each one of those operators. So one can experiment with combining the different implementations to compose the best algorithm for a specific search or optimization problem. 68 | 69 | ## Usage 70 | 71 | Add this to your `Cargo.toml`: 72 | 73 | ```toml 74 | [dependencies] 75 | genx = "0.4.0" 76 | ``` 77 | 78 | If you are not using Rust 2018 edition add this to your crate root: 79 | 80 | ```rust 81 | extern crate genx; 82 | ``` 83 | 84 | 85 | ## Why Genetic Algorithms 86 | 87 | Genetic Algorithms are at the core of [soft computing](https://en.wikipedia.org/wiki/Soft_computing) which is a branch of computing that comes to rescue when problem at hand is not feasible to be solved using [hard computing](http://www2.cs.uh.edu/~ceick/6367/Soft-Computing.pdf). There are several advantages of genetic algorithms: 88 | 89 | - Algorithms are adaptive and can adjust to the change of dynamic environment​ 90 | - The approach makes use of approximate solutions to solve problems that may be either unsolvable or too time-consuming to solve with current hardware.​ 91 | - Imprecise but usable solutions to complex computational problems allowing researchers to approach some problems that traditional computing cannot process.​ 92 | 93 | ## Inspiration 94 | 95 | I started this project mainly to learn about genetic algorithms (GAs) as part of my college curriculum where am studying severals methods for soft computing. I found myself looking for simple easy to use modular functions that I can use to learn more about each step of GA. 96 | -------------------------------------------------------------------------------- /examples/data/tsp.txt: -------------------------------------------------------------------------------- 1 | 0 83 93 129 133 139 151 169 135 114 110 98 99 95 81 152 159 181 172 185 147 157 185 220 127 181 2 | 83 0 40 53 62 64 91 116 93 84 95 98 89 68 67 127 156 175 152 165 160 180 223 268 179 197 3 | 93 40 0 42 42 49 59 81 54 44 58 64 54 31 36 86 117 135 112 125 124 147 193 241 157 161 4 | 129 53 42 0 11 11 46 72 65 70 88 100 89 66 76 102 142 156 127 139 155 180 228 278 197 190 5 | 133 62 42 11 0 9 35 61 55 62 82 95 84 62 74 93 133 146 117 128 148 173 222 272 194 182 6 | 139 64 49 11 9 0 39 65 63 71 90 103 92 71 82 100 141 153 124 135 156 181 230 280 202 190 7 | 151 91 59 46 35 39 0 26 34 52 71 88 77 63 78 66 110 119 88 98 130 156 206 257 188 160 8 | 169 116 81 72 61 65 26 0 37 59 75 92 83 76 91 54 98 103 70 78 122 148 198 250 188 148 9 | 135 93 54 65 55 63 34 37 0 22 39 56 47 40 55 37 78 91 62 74 96 122 172 223 155 128 10 | 114 84 44 70 62 71 52 59 22 0 20 36 26 20 34 43 74 91 68 82 86 111 160 210 136 121 11 | 110 95 58 88 82 90 71 75 39 20 0 18 11 27 32 42 61 80 64 77 68 92 140 190 116 103 12 | 98 98 64 100 95 103 88 92 56 36 18 0 11 34 31 56 63 85 75 87 62 83 129 178 100 99 13 | 99 89 54 89 84 92 77 83 47 26 11 11 0 23 24 53 68 89 74 87 71 93 140 189 111 107 14 | 95 68 31 66 62 71 63 76 40 20 27 34 23 0 15 62 87 106 87 100 93 116 163 212 132 130 15 | 81 67 36 76 74 82 78 91 55 34 32 31 24 15 0 73 92 112 96 109 93 113 158 205 122 130 16 | 152 127 86 102 93 100 66 54 37 43 42 56 53 62 73 0 44 54 26 39 68 94 144 196 139 95 17 | 159 156 117 142 133 141 110 98 78 74 61 63 68 87 92 44 0 22 34 38 30 53 102 154 109 51 18 | 181 175 135 156 146 153 119 103 91 91 80 85 89 106 112 54 22 0 33 29 46 64 107 157 125 51 19 | 172 152 112 127 117 124 88 70 62 68 64 75 74 87 96 26 34 33 0 13 63 87 135 186 141 81 20 | 185 165 125 139 128 135 98 78 74 82 77 87 87 100 109 39 38 29 13 0 68 90 136 186 148 79 21 | 147 160 124 155 148 156 130 122 96 86 68 62 71 93 93 68 30 46 63 68 0 26 77 128 80 37 22 | 157 180 147 180 173 181 156 148 122 111 92 83 93 116 113 94 53 64 87 90 26 0 50 102 65 27 23 | 185 223 193 228 222 230 206 198 172 160 140 129 140 163 158 144 102 107 135 136 77 50 0 51 64 58 24 | 220 268 241 278 272 280 257 250 223 210 190 178 189 212 205 196 154 157 186 186 128 102 51 0 93 107 25 | 127 179 157 197 194 202 188 188 155 136 116 100 111 132 122 139 109 125 141 148 80 65 64 93 0 90 26 | 181 197 161 190 182 190 160 148 128 121 103 99 107 130 130 95 51 51 81 79 37 27 58 107 90 0 27 | -------------------------------------------------------------------------------- /examples/eqn.rs: -------------------------------------------------------------------------------- 1 | use genx::{ 2 | crossover::simulated_binary_crossover, 3 | mutation::polynomial_mutation, 4 | selection::{random_selection, stochastic_universal_selection}, 5 | }; 6 | use rand::{distributions::Uniform, prelude::Distribution}; 7 | 8 | fn main() { 9 | let equation = |x: f32| 3.0 * x * x + 8.0 * (-x) + 5.0; 10 | let population_size = 15; 11 | let iterations = 1000; 12 | let mutation_probability = 0.5; 13 | let between = Uniform::from(-10.0..10.0); 14 | let mut prng = rand::thread_rng(); 15 | 16 | let fitness_function = |x: f32| 1.0 / ((equation(x) - x).abs() + 1.0); 17 | let mut population = (0..population_size) 18 | .map(|_| between.sample(&mut prng)) 19 | .collect::>(); 20 | 21 | let get_fitness_values = |population: &Vec| { 22 | population 23 | .iter() 24 | .map(|x| fitness_function(*x)) 25 | .collect::>() 26 | }; 27 | 28 | let best_fitness_value = |population: &Vec| { 29 | population 30 | .iter() 31 | .max_by(|&a, &b| { 32 | fitness_function(*a) 33 | .partial_cmp(&fitness_function(*b)) 34 | .unwrap_or(std::cmp::Ordering::Equal) 35 | }) 36 | .unwrap() 37 | .clone() 38 | }; 39 | 40 | let mut best_now = best_fitness_value(&population); 41 | let between = Uniform::from(0.0..1.0); 42 | for _ in 0..iterations { 43 | let idxs = random_selection(population_size, 2, None); 44 | let (mut child1, mut child2) = 45 | simulated_binary_crossover(population[idxs[0]], population[idxs[1]], 5.0, None); 46 | 47 | if between.sample(&mut prng) < mutation_probability { 48 | child1 = polynomial_mutation(child1, 100.0, 10.0, None) 49 | } 50 | if between.sample(&mut prng) < mutation_probability { 51 | child2 = polynomial_mutation(child2, 100.0, 10.0, None) 52 | } 53 | 54 | population.push(child1); 55 | population.push(child2); 56 | 57 | let fitness_values = get_fitness_values(&population); 58 | let selected_idx = stochastic_universal_selection(&fitness_values, population_size, None); 59 | population = selected_idx 60 | .iter() 61 | .map(|&a| population[a]) 62 | .collect::>(); 63 | let best = best_fitness_value(&population); 64 | if fitness_function(best) > fitness_function(best_now) { 65 | best_now = best; 66 | } 67 | } 68 | println!("{} {}", best_now, equation(best_now)); 69 | } 70 | -------------------------------------------------------------------------------- /examples/gen_eqn.rs: -------------------------------------------------------------------------------- 1 | use genx::{ 2 | crossover::simulated_binary_crossover, 3 | mutation::polynomial_mutation, 4 | selection::{roulette_wheel_selection, stochastic_universal_selection}, 5 | }; 6 | use rand::{distributions::Uniform, prelude::Distribution}; 7 | 8 | fn main() { 9 | let poly = vec![1.0, -2.0, 1.0]; 10 | let equation = |x: f32| { 11 | let mut val = 0.0; 12 | let mut pow = 1.0; 13 | for i in poly.iter() { 14 | val = val + pow * i; 15 | pow = pow * x; 16 | } 17 | val 18 | }; 19 | let population_size = 100; 20 | let iterations = 1000; 21 | let mutation_probability = 0.7; 22 | let between = Uniform::from(-10.0..10.0); 23 | let mut prng = rand::thread_rng(); 24 | let fitness_function = |x: f32| 1.0 / ((equation(x) - x).abs() + 1.0); 25 | 26 | let mut population = (0..population_size) 27 | .map(|_| between.sample(&mut prng)) 28 | .collect::>(); 29 | 30 | let get_fitness_values = |population: &Vec| { 31 | population 32 | .iter() 33 | .map(|x| fitness_function(*x)) 34 | .collect::>() 35 | }; 36 | 37 | let best_fitness_value = |population: &Vec| { 38 | population 39 | .iter() 40 | .max_by(|&a, &b| { 41 | fitness_function(*a) 42 | .partial_cmp(&fitness_function(*b)) 43 | .unwrap_or(std::cmp::Ordering::Equal) 44 | }) 45 | .unwrap() 46 | .clone() 47 | }; 48 | 49 | let mut best_now = best_fitness_value(&population); 50 | let between = Uniform::from(0.0..1.0); 51 | for _ in 0..iterations { 52 | let idxs = roulette_wheel_selection(&get_fitness_values(&population), 2, None); 53 | let (mut child1, mut child2) = 54 | simulated_binary_crossover(population[idxs[0]], population[idxs[1]], 5.0, None); 55 | if between.sample(&mut prng) < mutation_probability { 56 | child1 = polynomial_mutation(child1, 90.0, 5.0, None); 57 | } 58 | if between.sample(&mut prng) < mutation_probability { 59 | child2 = polynomial_mutation(child2, 90.0, 5.0, None); 60 | } 61 | population.push(child1); 62 | population.push(child2); 63 | let fitness_values = get_fitness_values(&population); 64 | let selected_index = stochastic_universal_selection(&fitness_values, population_size, None); 65 | 66 | population = selected_index 67 | .iter() 68 | .map(|&a| population[a]) 69 | .collect::>(); 70 | let best = best_fitness_value(&population); 71 | if fitness_function(best) > fitness_function(best_now) { 72 | best_now = best; 73 | } 74 | } 75 | 76 | println!("{} {}", best_now, equation(best_now)); 77 | } 78 | -------------------------------------------------------------------------------- /examples/huim.rs: -------------------------------------------------------------------------------- 1 | use genx::{ 2 | crossover::uniform_crossover, mutation::inversion_mutation, selection::random_selection, 3 | selection::stochastic_universal_selection, 4 | }; 5 | use rand::{distributions::Uniform, prelude::Distribution}; 6 | 7 | fn prune_database( 8 | database: Vec>, 9 | cost: &Vec, 10 | min_util: f32, 11 | num_items: i32, 12 | ) -> Vec> { 13 | let mut utils = vec![0.0; num_items as usize]; 14 | for i in database.iter() { 15 | let mut val = 0.0; 16 | for j in i.iter() { 17 | val += cost[j.0 as usize] * j.1 as f32; 18 | } 19 | for j in i.iter() { 20 | utils[j.0 as usize] += val; 21 | } 22 | } 23 | let mut new_database: Vec> = Vec::new(); 24 | for i in database.iter() { 25 | let mut new_transaction: Vec<(i32, i32)> = Vec::new(); 26 | for j in i.iter() { 27 | if utils[j.0 as usize] >= min_util { 28 | new_transaction.push(j.clone()); 29 | } 30 | } 31 | if new_transaction.len() > 0 { 32 | new_database.push(new_transaction.clone()); 33 | } 34 | } 35 | new_database 36 | } 37 | 38 | fn main() { 39 | let num_items = 5; 40 | let cost = vec![2.0, 3.0, 5.0, 6.0, 1.0]; 41 | let mut database = vec![ 42 | vec![(0, 2), (1, 6)], 43 | vec![(2, 1), (4, 1)], 44 | vec![(3, 2), (0, 3)], 45 | ]; 46 | let min_util: f32 = 10.0; 47 | database = prune_database(database.clone(), &cost, min_util, num_items); 48 | let mut bit_database: Vec> = Vec::new(); 49 | for i in database.iter() { 50 | let mut bit: Vec = vec![false; num_items as usize]; 51 | for j in i.iter() { 52 | bit[j.0 as usize] = true; 53 | println!("{},{}", j.0, j.1); 54 | } 55 | bit_database.push(bit.clone()); 56 | } 57 | let iterations = 300; 58 | let population_size = 20; 59 | let mutation_probability: f32 = 0.4; 60 | let mut population = (0..population_size) 61 | .into_iter() 62 | .map(|_| (0..num_items).map(|_| rand::random::()).collect()) 63 | .collect::>>(); 64 | 65 | let fitness_function = |x: &Vec, cost: &Vec| { 66 | let mut res = 0.0; 67 | for (index, val) in database.iter().enumerate() { 68 | let mut flag = 1; 69 | for j in 0..num_items { 70 | if x[j as usize] == true && bit_database[index][j as usize] == false { 71 | flag = 0; 72 | } 73 | } 74 | let mut ut = 0.0; 75 | if flag == 1 { 76 | for j in val.iter() { 77 | if x[j.0 as usize] { 78 | ut += j.1 as f32 * cost[j.0 as usize]; 79 | } 80 | } 81 | } 82 | res += ut; 83 | } 84 | res 85 | }; 86 | let best_fitness_value = |population: &Vec>, cost: &Vec| { 87 | population 88 | .iter() 89 | .max_by(|&a, &b| { 90 | fitness_function(a, &cost) 91 | .partial_cmp(&fitness_function(b, &cost)) 92 | .unwrap_or(std::cmp::Ordering::Equal) 93 | }) 94 | .unwrap() 95 | .clone() 96 | }; 97 | let get_fitness_values = |population: &Vec>, cost: &Vec| { 98 | population 99 | .iter() 100 | .map(|x| fitness_function(x, &cost)) 101 | .collect::>() 102 | }; 103 | 104 | let mut best_now = best_fitness_value(&population, &cost); 105 | let between = Uniform::from(0.0..1.0); 106 | let mut prng = rand::thread_rng(); 107 | for _ in 0..iterations { 108 | let idxs = random_selection(population_size, 2, None); 109 | let (mut child1, mut child2) = 110 | uniform_crossover(&population[idxs[0]], &population[idxs[1]], 0.5, None); 111 | 112 | if between.sample(&mut prng) < mutation_probability { 113 | inversion_mutation(&mut child1, None); 114 | } 115 | if between.sample(&mut prng) < mutation_probability { 116 | inversion_mutation(&mut child2, None); 117 | } 118 | population.push(child1); 119 | population.push(child2); 120 | 121 | let fitness_values = get_fitness_values(&population, &cost); 122 | let selected_idx = stochastic_universal_selection(&fitness_values, population_size, None); 123 | population = selected_idx 124 | .iter() 125 | .map(|&a| population[a].clone()) 126 | .collect::>>(); 127 | let best = best_fitness_value(&population, &cost); 128 | if fitness_function(&best, &cost) > fitness_function(&best_now, &cost) { 129 | best_now = best; 130 | } 131 | println!("Fitness {}", fitness_function(&best_now,&cost)); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /examples/knp.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Display}; 2 | 3 | use genx::{ 4 | crossover::uniform_crossover, 5 | mutation::inversion_mutation, 6 | selection::{random_selection, stochastic_universal_selection}, 7 | }; 8 | 9 | use rand::{distributions::Uniform, prelude::Distribution}; 10 | 11 | struct Item { 12 | weight: u32, 13 | value: u32, 14 | } 15 | 16 | impl Debug for Item { 17 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 18 | write!( 19 | f, 20 | "Item {{ weight: {}, value: {} }}", 21 | self.weight, self.value 22 | ) 23 | } 24 | } 25 | 26 | impl Display for Item { 27 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 28 | write!( 29 | f, 30 | "Item {{ weight: {}, value: {} }}", 31 | self.weight, self.value 32 | ) 33 | } 34 | } 35 | 36 | fn main() { 37 | let knapsack_threshold = 80; 38 | let knapsack_items = vec![ 39 | Item { 40 | weight: 9, 41 | value: 150, 42 | }, 43 | Item { 44 | weight: 13, 45 | value: 35, 46 | }, 47 | Item { 48 | weight: 15, 49 | value: 10, 50 | }, 51 | Item { 52 | weight: 50, 53 | value: 60, 54 | }, 55 | Item { 56 | weight: 15, 57 | value: 60, 58 | }, 59 | Item { 60 | weight: 68, 61 | value: 45, 62 | }, 63 | Item { 64 | weight: 27, 65 | value: 60, 66 | }, 67 | Item { 68 | weight: 39, 69 | value: 40, 70 | }, 71 | Item { 72 | weight: 23, 73 | value: 30, 74 | }, 75 | Item { 76 | weight: 52, 77 | value: 10, 78 | }, 79 | Item { 80 | weight: 11, 81 | value: 70, 82 | }, 83 | Item { 84 | weight: 32, 85 | value: 30, 86 | }, 87 | Item { 88 | weight: 24, 89 | value: 15, 90 | }, 91 | ]; 92 | let knapsack_size = knapsack_items.len(); 93 | 94 | let iterations = 300; 95 | let population_size = 20; 96 | let mutation_probability = 0.4; 97 | let mut population = (0..population_size) 98 | .into_iter() 99 | .map(|_| (0..knapsack_size).map(|_| rand::random::()).collect()) 100 | .collect::>>(); 101 | 102 | let fitness_function = |individual: &Vec| -> f32 { 103 | let mut total_weight = 0; 104 | let mut total_value = 0; 105 | for (item, &is_included) in knapsack_items.iter().zip(individual.iter()) { 106 | if is_included { 107 | total_weight += item.weight; 108 | total_value += item.value; 109 | } 110 | } 111 | if total_weight > knapsack_threshold { 112 | total_value = 0; 113 | } 114 | 1.0 + total_value as f32 115 | }; 116 | 117 | let get_fitness_values = |population: &Vec>| { 118 | population 119 | .iter() 120 | .map(|x| fitness_function(x)) 121 | .collect::>() 122 | }; 123 | 124 | let best_fitness_value = |population: &Vec>| { 125 | population 126 | .iter() 127 | .max_by(|&a, &b| { 128 | fitness_function(a) 129 | .partial_cmp(&fitness_function(b)) 130 | .unwrap_or(std::cmp::Ordering::Equal) 131 | }) 132 | .unwrap() 133 | .clone() 134 | }; 135 | 136 | let mut best_now = best_fitness_value(&population); 137 | let between = Uniform::from(0.0..1.0); 138 | let mut prng = rand::thread_rng(); 139 | for _ in 0..iterations { 140 | let idxs = random_selection(population_size, 2, None); 141 | let (mut child1, mut child2) = 142 | uniform_crossover(&population[idxs[0]], &population[idxs[1]], 0.5, None); 143 | 144 | if between.sample(&mut prng) < mutation_probability { 145 | inversion_mutation(&mut child1, None); 146 | } 147 | if between.sample(&mut prng) < mutation_probability { 148 | inversion_mutation(&mut child2, None); 149 | } 150 | population.push(child1); 151 | population.push(child2); 152 | 153 | let fitness_values = get_fitness_values(&population); 154 | let selected_idx = stochastic_universal_selection(&fitness_values, population_size, None); 155 | population = selected_idx 156 | .iter() 157 | .map(|&a| population[a].clone()) 158 | .collect::>>(); 159 | let best = best_fitness_value(&population); 160 | if fitness_function(&best) > fitness_function(&best_now) { 161 | best_now = best; 162 | } 163 | } 164 | 165 | println!("{}", fitness_function(&best_now)); 166 | println!("{:?}", best_now.iter().zip(knapsack_items).filter(|predicate| *predicate.0).map(|x| x.1).collect::>()); 167 | } 168 | -------------------------------------------------------------------------------- /examples/tsp.rs: -------------------------------------------------------------------------------- 1 | use std::{cmp::Ordering, io::Read}; 2 | 3 | use genx::{ 4 | crossover::partially_mapped_crossover, 5 | mutation::scramble_mutation, 6 | scaling::linear_scaling, 7 | selection::{random_selection, stochastic_universal_selection}, 8 | }; 9 | 10 | use rand::{distributions::Uniform, prelude::Distribution, seq::SliceRandom, thread_rng}; 11 | 12 | fn read_data() -> Vec> { 13 | let mut data = vec![]; 14 | // FRI26 is a set of 26 cities, from TSPLIB. The minimal tour has length 937. 15 | // 1 25 24 23 26 22 21 17 18 20 19 16 11 12 13 15 14 10 9 8 7 5 6 4 3 2 1 16 | // best tour 17 | let mut file = std::fs::File::open("examples/data/tsp.txt").unwrap(); 18 | let mut buffer = String::new(); 19 | file.read_to_string(&mut buffer).unwrap(); 20 | for line in buffer.lines() { 21 | let temp_line = line.split_whitespace(); 22 | let mut current_row = vec![]; 23 | for x in temp_line { 24 | current_row.push(x.parse::().unwrap()); 25 | } 26 | data.push(current_row); 27 | } 28 | data 29 | } 30 | 31 | fn main() { 32 | let graph = read_data(); 33 | let n = graph.len(); 34 | let population_size = 200; 35 | let iterations = 5000; 36 | let mutation_probability = 0.5; 37 | let between = Uniform::from(0.0..1.0); 38 | let mut prng = thread_rng(); 39 | 40 | let distance = |a: &Vec| { 41 | let mut sum = 0.0; 42 | let mut prev_idx = a[0]; 43 | for &idx in a.iter() { 44 | sum += graph[prev_idx][idx]; 45 | prev_idx = idx; 46 | } 47 | 48 | sum 49 | }; 50 | 51 | let fitness_function = |a: &Vec| { 52 | let sum = distance(a); 53 | 54 | 1.0 / (sum.sqrt().sqrt() + 1.0) 55 | }; 56 | 57 | let mut population = (0..population_size) 58 | .map(|_| { 59 | let mut a = (0..n).collect::>(); 60 | a.shuffle(&mut prng); 61 | a 62 | }) 63 | .collect::>>(); 64 | 65 | let get_fitness_values = |population: &Vec>| { 66 | population 67 | .iter() 68 | .map(|a| fitness_function(a)) 69 | .collect::>() 70 | }; 71 | 72 | let mut best_now = population[0].clone(); 73 | 74 | for _ in 0..iterations { 75 | let idxs = random_selection(population_size, 2, None); 76 | let (mut child1, mut child2) = 77 | partially_mapped_crossover(&population[idxs[0]], &population[idxs[1]], None); 78 | 79 | if between.sample(&mut prng) < mutation_probability { 80 | scramble_mutation(&mut child1, None); 81 | } 82 | if between.sample(&mut prng) < mutation_probability { 83 | scramble_mutation(&mut child2, None); 84 | } 85 | 86 | population.push(child1); 87 | population.push(child2); 88 | let mut fitness_values = get_fitness_values(&population); 89 | linear_scaling(&mut fitness_values, 1.2); 90 | let selected_idx = stochastic_universal_selection(&fitness_values, population_size, None); 91 | population = selected_idx 92 | .iter() 93 | .map(|&a| population[a].clone()) 94 | .collect::>>(); 95 | 96 | let best = population 97 | .iter() 98 | .max_by(|a, b| { 99 | fitness_function(a) 100 | .partial_cmp(&fitness_function(b)) 101 | .unwrap_or(Ordering::Equal) 102 | }) 103 | .unwrap(); 104 | 105 | if fitness_function(best) > fitness_function(&best_now) { 106 | best_now = best.clone(); 107 | } 108 | } 109 | // 110 | print!("{:?} {}", best_now, distance(&best_now)); 111 | } 112 | -------------------------------------------------------------------------------- /src/crossover/blend.rs: -------------------------------------------------------------------------------- 1 | use rand::{prelude::StdRng, Rng, SeedableRng}; 2 | 3 | /** 4 | ## Description: 5 | It uses the following formula to generate offsprings from parents: 6 | C1 = (1-γ)P1 + γP2 7 | C2 = (1-γ)P2 + γP1 8 | Here, γ = (1+2ɑ)r - ɑ [ɑ is a fixed value, say 0.5 and r is a random value between 0 and 1] 9 | 10 | ### Note: 11 | - The function can also take in an optional `seed` value of type `Option` for deterministic results. 12 | 13 | ## Return: 14 | The return value is a tuple containing two offsprings of type `f32` 15 | 16 | ## Example: 17 | ```rust 18 | use genx::crossover::blend_crossover; 19 | 20 | let (parent1, parent2) = (13.37, 9.43); 21 | let (child1, child2) = blend_crossover(parent1, parent2, 0.5, None); 22 | ``` 23 | */ 24 | pub fn blend_crossover(parent1: f32, parent2: f32, alpha: f32, seed: Option) -> (f32, f32) { 25 | let mut prng = match seed { 26 | Some(val) => StdRng::seed_from_u64(val), 27 | None => StdRng::from_entropy(), 28 | }; 29 | 30 | let gamma: f32 = (1.0 + 2.0 * alpha) * prng.gen::() - alpha; 31 | ( 32 | (1.0 - gamma) * parent1 + gamma * parent2, 33 | (1.0 - gamma) * parent2 + gamma * parent1, 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/crossover/cycle.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | use itertools::multizip; 4 | 5 | use super::{check_continuous, check_length}; 6 | 7 | /** 8 | ## Description: 9 | It identifies a number of so-called cycles between two parent chromosomes. Then, to form Child 1, 10 | cycle one is copied from parent 1, cycle 2 from parent 2, cycle 3 from parent 1, and so on. It 11 | attempts to create an offspring from the parents where every position is occupied by a corresponding 12 | element from one of the parents. 13 | 14 | ## Return: 15 | The return value is a tuple containing two offsprings of type `Vec` 16 | 17 | ## Example: 18 | ```rust 19 | use genx::crossover::cycle_crossover; 20 | 21 | let parent1 = vec![1, 3, 4, 7, 0, 2, 6, 5]; 22 | let parent2 = vec![2, 3, 4, 0, 7, 6, 1, 5]; 23 | let (child1, child2) = cycle_crossover(&parent1, &parent2); 24 | ``` 25 | */ 26 | pub fn cycle_crossover(parent1: &Vec, parent2: &Vec) -> (Vec, Vec) { 27 | check_length(parent1, parent2); 28 | 29 | if !check_continuous(parent1) || !check_continuous(parent2) { 30 | panic!("Vectors must contain continuous unique values"); 31 | }; 32 | 33 | let (mut map1, mut map2) = (HashMap::new(), HashMap::new()); 34 | 35 | for (idx, (&val1, &val2)) in multizip((parent1, parent2)).enumerate() { 36 | map1.insert(val1, idx); 37 | map2.insert(val2, idx); 38 | } 39 | 40 | let (mut child1, mut child2) = (parent2.clone(), parent1.clone()); 41 | let mut set:HashSet = HashSet::new(); 42 | 43 | let mut current_val:(usize, usize) = (parent1[0], 0); 44 | while !set.get(¤t_val.0).is_some() { 45 | set.insert(current_val.0); 46 | child1[current_val.1] = parent1[current_val.1]; 47 | current_val = (parent2[current_val.1], map1[&parent2[current_val.1]]) 48 | } 49 | 50 | set.clear(); 51 | current_val = (parent2[0], 0); 52 | while !set.get(¤t_val.0).is_some() { 53 | set.insert(current_val.0); 54 | child2[current_val.1] = parent2[current_val.1]; 55 | current_val = (parent1[current_val.1], map2[&parent1[current_val.1]]) 56 | } 57 | set.clear(); 58 | 59 | (child1, child2) 60 | } 61 | -------------------------------------------------------------------------------- /src/crossover/linear.rs: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | ## Description: 4 | It uses the linear combination of the present chromosomes to produce new children. 5 | SSuppose P1 and P2 are the parent values. Corresponding offspring chromosome values can be 6 | obtained as Ci = ɑiP1 + βiP2, where i = 1,2,3...n 7 | 8 | ### Note: 9 | - The function takes a vector of (ɑ, β) pairs as a parameter. 10 | 11 | ## Return: 12 | The return value is a vector `Vec` containing children for each parameter. 13 | 14 | ## Example: 15 | ```rust 16 | use genx::crossover::linear_crossover; 17 | 18 | let (parent1, parent2) = (11.13, 12.19); 19 | let params = vec![(0.5, 0.5), (0.6, -0.4)]; 20 | let children = linear_crossover(parent1, parent2, ¶ms); 21 | ``` 22 | */ 23 | pub fn linear_crossover(parent1: f32, parent2: f32, parameters: &Vec<(f32, f32)>) -> Vec { 24 | let mut offsprings = Vec::with_capacity(parameters.len()); 25 | for &(alpha, beta) in parameters.iter() { 26 | offsprings.push(alpha * parent1 + beta * parent2); 27 | } 28 | offsprings 29 | } 30 | -------------------------------------------------------------------------------- /src/crossover/mod.rs: -------------------------------------------------------------------------------- 1 | //! Crossover is one of the prominent operators used in genetic algorithms. 2 | //! Crossover process is vital in generating new chromosomes by combing two or more parent 3 | //! chromosomes with the hope that they create new and efficient chromosomes. 4 | //! Crossover occurs after selection of pairs of parent chromosomes and helps in 5 | //! exchanging information between parents to create children. 6 | //! 7 | //! During crossover the 8 | //! parent chromosomes are taken in pair and their genes are exchanged in certain order 9 | //! to obtain off spring. These offspring become next generation parent chromosomes. 10 | //! It is performed by exchanging alleles between two selected parent 11 | //! chromosomes in order to explore new solution space. 12 | //! 13 | //! All the crossover functions take in atleast two arguments 14 | //! which are the two parent from which two offsprings are generated. 15 | //! 16 | //! Available crossover functions for binary encoded 17 | //! individuals are: 18 | //! * `single_point` 19 | //! * `multi_point` 20 | //! * `uniform` 21 | //! * `shuffle` 22 | //! 23 | //! Available crossover functions for real encoded 24 | //! individuals are: 25 | //! * `linear` 26 | //! * `blend` 27 | //! * `simulated_binary` 28 | //! 29 | //! Above all real encoded crossover techniques take in one additional parameter 30 | //! that determines the shift in values between the parents and offsprings. 31 | //! 32 | //! Available crossover functions for order encoded 33 | //! individuals are: 34 | //! * `cycle` 35 | //! * `order` 36 | //! * `partially_mapped` 37 | //! * `uniform_partially_mapped` 38 | //! 39 | //! For order encoded individuals the crossover function will panic in case 40 | //! of invalid order or missing elements. So values should be in range of 41 | //! 0..n-1 where n is length of the order encoded individual. 42 | //! 43 | //! You can read more about selection schemas and their working from the [research paper](http://ictactjournals.in/paper/IJSC_V6_I1_paper_4_pp_1083_1092.pdf) 44 | use std::collections::HashSet; 45 | 46 | pub mod single_point; 47 | 48 | pub mod multi_point; 49 | 50 | pub mod shuffle; 51 | 52 | pub mod uniform; 53 | 54 | pub mod partially_mapped; 55 | 56 | pub mod order; 57 | 58 | pub mod cycle; 59 | 60 | pub mod linear; 61 | 62 | pub mod blend; 63 | 64 | pub mod simulated_binary; 65 | 66 | pub mod uniform_partially_mapped; 67 | 68 | pub use self::single_point::single_point_crossover; 69 | 70 | pub use self::multi_point::multi_point_crossover; 71 | 72 | pub use self::shuffle::shuffle_crossover; 73 | 74 | pub use self::uniform::uniform_crossover; 75 | 76 | pub use self::partially_mapped::partially_mapped_crossover; 77 | 78 | pub use self::order::order_crossover; 79 | 80 | pub use self::cycle::cycle_crossover; 81 | 82 | pub use self::linear::linear_crossover; 83 | 84 | pub use self::blend::blend_crossover; 85 | 86 | pub use self::simulated_binary::simulated_binary_crossover; 87 | 88 | pub use self::uniform_partially_mapped::uniform_partially_mapped_crossover; 89 | 90 | fn check_continuous(vec: &Vec) -> bool { 91 | let n = vec.len(); 92 | let mut set:HashSet = HashSet::new(); 93 | for x in vec.iter() { 94 | set.insert(*x); 95 | if *x >= n { 96 | return false; 97 | } 98 | } 99 | 100 | set.len() == vec.len() 101 | } 102 | 103 | fn check_length(parent1 : &Vec, parent2 : &Vec) { 104 | if parent1.len() != parent2.len() { 105 | panic!("Vectors must be the same length"); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/crossover/multi_point.rs: -------------------------------------------------------------------------------- 1 | use rand::{rngs::StdRng, seq::SliceRandom, SeedableRng}; 2 | 3 | use super::check_length; 4 | 5 | /** 6 | ## Description: 7 | It uses the random crossover points to combine the parents same as per 1-Point crossover. 8 | To provide the great combination of parents it selects more than one crossover points to create the 9 | offspring or child. 10 | 11 | ### Note: 12 | - The function takes an integer `k` denoting the number of crossover points. 13 | - The function can also take in an optional `seed` value of type `Option` for deterministic results. 14 | 15 | ## Return: 16 | The return value is a tuple containing two offsprings of type `Vec` 17 | 18 | ## Example: 19 | ```rust 20 | use genx::crossover::multi_point_crossover; 21 | 22 | let parent1 = vec![true, false, false, true, true, false, false, true]; 23 | let parent2 = vec![true, true, true, false, true, false, true, true]; 24 | let (child1, child2) = multi_point_crossover(&parent1, &parent2, 3, None); 25 | ``` 26 | */ 27 | pub fn multi_point_crossover( 28 | parent1: &Vec, 29 | parent2: &Vec, 30 | k: usize, 31 | seed: Option, 32 | ) -> (Vec, Vec) { 33 | check_length(parent1, parent2); 34 | 35 | let mut prng = match seed { 36 | Some(seed) => StdRng::seed_from_u64(seed), 37 | None => StdRng::from_entropy(), 38 | }; 39 | 40 | 41 | let n = parent1.len(); 42 | let mut indices = (0..n) 43 | .collect::>() 44 | .choose_multiple(&mut prng, k) 45 | .map(|&x| x) 46 | .collect::>(); 47 | 48 | indices.sort(); 49 | let (mut child1, mut child2) = (Vec::with_capacity(n), Vec::with_capacity(n)); 50 | let mut child_idx: usize = 0; 51 | for (i, &idx) in indices.iter().enumerate() { 52 | if i & 1 == 0 { 53 | while child_idx < idx { 54 | child1.push(parent1[child_idx]); 55 | child2.push(parent2[child_idx]); 56 | child_idx += 1; 57 | } 58 | } else { 59 | while child_idx < idx { 60 | child1.push(parent2[child_idx]); 61 | child2.push(parent1[child_idx]); 62 | child_idx += 1; 63 | } 64 | } 65 | } 66 | 67 | if child_idx < n { 68 | while child_idx < n { 69 | if k & 1 == 0 { 70 | child1.push(parent1[child_idx]); 71 | child2.push(parent2[child_idx]); 72 | } else { 73 | child1.push(parent2[child_idx]); 74 | child2.push(parent1[child_idx]); 75 | } 76 | child_idx += 1; 77 | } 78 | } 79 | 80 | return (child1, child2); 81 | } 82 | -------------------------------------------------------------------------------- /src/crossover/order.rs: -------------------------------------------------------------------------------- 1 | use super::{check_continuous, check_length}; 2 | use rand::{prelude::IteratorRandom, rngs::StdRng, SeedableRng}; 3 | 4 | /** 5 | ## Description: 6 | It constructs an offspring by choosing a substring of one parent and preserving the relative order 7 | of the elements of the other parent. 8 | 9 | ### Note: 10 | - The function can also take in an optional `seed` value of type `Option` for deterministic results. 11 | 12 | ## Return: 13 | The return value is a tuple containing two offsprings of type `Vec` 14 | 15 | ## Example: 16 | ```rust 17 | use genx::crossover::order_crossover; 18 | 19 | let parent1 = vec![1, 3, 4, 7, 0, 2, 6, 5]; 20 | let parent2 = vec![2, 3, 4, 0, 7, 6, 1, 5]; 21 | let (child1, child2) = order_crossover(&parent1, &parent2, None); 22 | ``` 23 | */ 24 | pub fn order_crossover( 25 | parent1: &Vec, 26 | parent2: &Vec, 27 | seed: Option, 28 | ) -> (Vec, Vec) { 29 | check_length(parent1, parent2); 30 | if !check_continuous(parent1) || !check_continuous(parent2) { 31 | panic!("Vectors must contain continuous unique values"); 32 | } 33 | 34 | let n = parent1.len(); 35 | let mut prng = match seed { 36 | Some(seed) => StdRng::seed_from_u64(seed), 37 | None => StdRng::from_entropy(), 38 | }; 39 | 40 | let mut selected = (0..n).map(|x| x).choose_multiple(&mut prng, 2); 41 | selected.sort(); 42 | 43 | let (mut child1, mut child2) = (parent1.clone(), parent2.clone()); 44 | let (mut holes1, mut holes2) = ( 45 | (0..n).map(|_| false).collect::>(), 46 | (0..n).map(|_| false).collect::>(), 47 | ); 48 | 49 | for i in selected[0]..=selected[1] { 50 | holes1[parent2[i]] = true; 51 | holes2[parent1[i]] = true; 52 | 53 | child1[i] = parent2[i]; 54 | child2[i] = parent1[i]; 55 | } 56 | 57 | let (mut pos1, mut pos2) = (selected[1] + 1, selected[1] + 1); 58 | for i in 0..n { 59 | if !holes1.get(parent1[(i + selected[1] + 1) % n]).unwrap() { 60 | child1[pos1 % n] = parent1[(i + selected[1] + 1) % n]; 61 | pos1 += 1; 62 | } 63 | if !holes2.get(parent2[(i + selected[1] + 1) % n]).unwrap() { 64 | child2[pos2 % n] = parent2[(i + selected[1] + 1) % n]; 65 | pos2 += 1; 66 | } 67 | } 68 | 69 | (child1, child2) 70 | } 71 | -------------------------------------------------------------------------------- /src/crossover/partially_mapped.rs: -------------------------------------------------------------------------------- 1 | use super::{check_continuous, check_length}; 2 | 3 | use rand::{SeedableRng, prelude::IteratorRandom, rngs::StdRng}; 4 | 5 | /** 6 | ## Description: 7 | It transmits ordering and values information from the parent strings to the offspring. A portion of 8 | one parent string is mapped onto a portion of the other parent string and the remaining information 9 | is exchanged. 10 | 11 | ### Note: 12 | - The function can also take in an optional `seed` value of type `Option` for deterministic results. 13 | 14 | ## Return: 15 | The return value is a tuple containing two offsprings of type `Vec` 16 | 17 | ## Example: 18 | ```rust 19 | use genx::crossover::partially_mapped_crossover; 20 | 21 | let parent1 = vec![1, 3, 4, 7, 0, 2, 6, 5]; 22 | let parent2 = vec![2, 3, 4, 0, 7, 6, 1, 5]; 23 | let (child1, child2) = partially_mapped_crossover(&parent1, &parent2, None); 24 | ``` 25 | */ 26 | pub fn partially_mapped_crossover(parent1: &Vec, parent2: &Vec, seed: Option) -> (Vec, Vec) { 27 | check_length(parent1, parent2); 28 | 29 | let n = parent1.len(); 30 | if !check_continuous(parent1) || !check_continuous(parent2) { 31 | panic!("Vectors must contain continuous unique values"); 32 | } 33 | 34 | let mut prng = match seed { 35 | Some(seed) => StdRng::seed_from_u64(seed), 36 | None => StdRng::from_entropy(), 37 | }; 38 | 39 | let mut selected = (0..n).map(|x| x).choose_multiple(&mut prng, 2); 40 | selected.sort(); 41 | let (mut idx1, mut idx2) = (parent1.clone(), parent2.clone()); 42 | for i in 0..n { 43 | idx1[parent1[i]] = i; 44 | idx2[parent2[i]] = i; 45 | } 46 | let (mut child1, mut child2) = (parent1.clone(), parent2.clone()); 47 | 48 | for i in selected[0]..=selected[1] { 49 | let (val1, val2) = (child1[i], child2[i]); 50 | 51 | child1[i] = val2; 52 | child1[idx1[val2]] = val1; 53 | child2[i] = val1; 54 | child2[idx2[val1]] = val2; 55 | 56 | let temp = (idx1[val1], idx1[val2]); 57 | idx1[val1] = temp.1; 58 | idx1[val2] = temp.0; 59 | let temp = (idx2[val1], idx2[val2]); 60 | idx2[val1] = temp.1; 61 | idx2[val2] = temp.0; 62 | } 63 | 64 | (child1, child2) 65 | } 66 | -------------------------------------------------------------------------------- /src/crossover/shuffle.rs: -------------------------------------------------------------------------------- 1 | use rand::{SeedableRng, prelude::SliceRandom, rngs::StdRng}; 2 | 3 | use super::{check_length, single_point_crossover}; 4 | 5 | /** 6 | ## Description: 7 | Shuffle Crossover selects the two parents for crossover. It firstly randomly shuffles the genes in 8 | the both parents but in the same way. Then it applies the 1-Point crossover technique by randomly 9 | selecting a point as crossover point and then combines both parents to create two offspring. After 10 | performing 1-point crossover the genes in offspring are then unshuffled in same way as they have 11 | been shuffled. 12 | 13 | ### Note: 14 | - The function can also take in an optional `seed` value of type `Option` for deterministic results. 15 | 16 | ## Return: 17 | The return value is a tuple containing two offsprings of type `Vec` 18 | 19 | ## Example: 20 | ```rust 21 | use genx::crossover::shuffle_crossover; 22 | 23 | let parent1 = vec![true, false, false, true, true, false, false, true]; 24 | let parent2 = vec![true, true, true, false, true, false, true, true]; 25 | let (child1, child2) = shuffle_crossover(&parent1, &parent2, None); 26 | ``` 27 | */ 28 | pub fn shuffle_crossover(parent1: &Vec, parent2: &Vec, seed: Option) -> (Vec, Vec) { 29 | check_length(parent1, parent2); 30 | 31 | let mut prng = match seed { 32 | Some(seed) => StdRng::seed_from_u64(seed), 33 | None => StdRng::from_entropy(), 34 | }; 35 | 36 | 37 | let mut indices = (0..parent1.len()).collect::>(); 38 | indices.shuffle(&mut prng); 39 | let shuffled_parent1 = indices.iter().map(|&i| parent1[i]).collect::>(); 40 | let shuffled_parent2 = indices.iter().map(|&i| parent2[i]).collect::>(); 41 | 42 | let (shuffled_child1, shuffled_child2) = single_point_crossover(&shuffled_parent1, &shuffled_parent2, seed); 43 | 44 | let mut child1 = (0..parent1.len()).map(|_| false).collect::>(); 45 | let mut child2 = (0..parent2.len()).map(|_| false).collect::>(); 46 | 47 | for (idx, &index) in indices.iter().enumerate() { 48 | child1[index] = shuffled_child1[idx]; 49 | child2[index] = shuffled_child2[idx]; 50 | }; 51 | 52 | return (child1, child2); 53 | } 54 | -------------------------------------------------------------------------------- /src/crossover/simulated_binary.rs: -------------------------------------------------------------------------------- 1 | use rand::{Rng, SeedableRng, prelude::StdRng}; 2 | 3 | /** 4 | ## Description: 5 | Simulated binary crossover uses probability density function that simulates the single-point 6 | crossover in binary-coded GAs. 7 | 8 | ### Note: 9 | - The function takes a `f32` parameter `operator` denoting SBX crossover distribution factor. 10 | - The function can also take in an optional `seed` value of type `Option` for deterministic results. 11 | 12 | ## Return: 13 | The return value is a tuple containing two offsprings of type `f32` 14 | 15 | ## Example: 16 | ```rust 17 | use genx::crossover::simulated_binary_crossover; 18 | 19 | let (parent1, parent2) = (11.19, 20.97); 20 | let (child1, child2) = simulated_binary_crossover(parent1, parent2, 0.5, None); 21 | ``` 22 | */ 23 | pub fn simulated_binary_crossover(parent1: f32, parent2: f32, operator: f32, seed: Option) -> (f32, f32) { 24 | let mut prng = match seed { 25 | Some(val) => StdRng::seed_from_u64(val), 26 | None => StdRng::from_entropy(), 27 | }; 28 | 29 | let beta = |ui: f32| { 30 | if ui <= 0.5 { 31 | return (2.0*ui).powf(1.0/(operator + 1.0)); 32 | } 33 | else { 34 | return (1.0/(2.0*(1.0 - ui))).powf(1.0/(operator + 1.0)); 35 | } 36 | }; 37 | 38 | let ui = prng.gen::(); 39 | let beta_ui = beta(ui); 40 | (0.5*((parent1+parent2) - beta_ui*(parent1-parent2)), 0.5*((parent1+parent2) + beta_ui*(parent1-parent2))) 41 | } 42 | -------------------------------------------------------------------------------- /src/crossover/single_point.rs: -------------------------------------------------------------------------------- 1 | use rand::{rngs::StdRng, Rng, SeedableRng}; 2 | 3 | use super::check_length; 4 | 5 | /** 6 | ## Description: 7 | It is one of the simple crossover technique used for random GA applications. This crossover 8 | uses the single point fragmentation of the parents and then combines the parents at the 9 | crossover point to create the offspring or child. 10 | 11 | ### Note: 12 | - The function can also take in an optional `seed` value of type `Option` for deterministic results. 13 | 14 | ## Return: 15 | The return value is a tuple containing two offsprings of type `Vec` 16 | 17 | ## Example: 18 | ```rust 19 | use genx::crossover::single_point_crossover; 20 | 21 | let parent1 = vec![true, false, false, true, true, false, false, true]; 22 | let parent2 = vec![true, true, true, false, true, false, true, true]; 23 | let (child1, child2) = single_point_crossover(&parent1, &parent2, None); 24 | ``` 25 | */ 26 | pub fn single_point_crossover( 27 | parent1: &Vec, 28 | parent2: &Vec, 29 | seed: Option, 30 | ) -> (Vec, Vec) { 31 | check_length(parent1, parent2); 32 | 33 | let mut prng = match seed { 34 | Some(seed) => StdRng::seed_from_u64(seed), 35 | None => StdRng::from_entropy(), 36 | }; 37 | 38 | 39 | let n = parent1.len(); 40 | let (mut child1, mut child2) = (Vec::with_capacity(n), Vec::with_capacity(n)); 41 | let idx = prng.gen_range(0..parent1.len()); 42 | for i in 0..parent1.len() { 43 | if i < idx { 44 | child1.push(parent1[i]); 45 | child2.push(parent2[i]); 46 | } else { 47 | child1.push(parent2[i]); 48 | child2.push(parent1[i]); 49 | } 50 | } 51 | 52 | return (child1, child2); 53 | } 54 | -------------------------------------------------------------------------------- /src/crossover/uniform.rs: -------------------------------------------------------------------------------- 1 | use itertools::multizip; 2 | use rand::{SeedableRng, distributions::Uniform, prelude::Distribution, rngs::StdRng}; 3 | use std::mem::swap; 4 | 5 | use super::check_length; 6 | 7 | /** 8 | ## Description: 9 | Uniform crossover selects the two parents for crossover. It creates two child offspring of n genes 10 | selected from both of the parents uniformly. The random real number decides whether the first child 11 | select the ith genes from first or second parent. 12 | 13 | ### Note: 14 | - The function takes a float value `probability` in the range [0.0 - 1.0] representing the bias. 15 | - The function can also take in an optional `seed` value of type `Option` for deterministic results. 16 | 17 | ## Return: 18 | The return value is a tuple containing two offsprings of type `Vec` 19 | 20 | ## Example: 21 | ```rust 22 | use genx::crossover::uniform_crossover; 23 | 24 | let parent1 = vec![true, false, false, true, true, false, false, true]; 25 | let parent2 = vec![true, true, true, false, true, false, true, true]; 26 | let (child1, child2) = uniform_crossover(&parent1, &parent2, 0.6, None); 27 | ``` 28 | */ 29 | pub fn uniform_crossover( 30 | parent1: &Vec, 31 | parent2: &Vec, 32 | probability: f64, 33 | seed: Option, 34 | ) -> (Vec, Vec) { 35 | check_length(parent1, parent2); 36 | 37 | let mut prng = match seed { 38 | Some(seed) => StdRng::seed_from_u64(seed), 39 | None => StdRng::from_entropy(), 40 | }; 41 | 42 | 43 | let mask = Uniform::from(0.0..1.0); 44 | let (mut child1, mut child2) = (parent1.clone(), parent2.clone()); 45 | for (val1, val2) in multizip((&mut child1, &mut child2)) { 46 | if mask.sample(&mut prng) < probability { 47 | swap(val1, val2); 48 | } 49 | } 50 | 51 | return (child1, child2); 52 | } 53 | -------------------------------------------------------------------------------- /src/crossover/uniform_partially_mapped.rs: -------------------------------------------------------------------------------- 1 | use super::{check_continuous, check_length}; 2 | 3 | use rand::{ 4 | distributions::Uniform, 5 | prelude::{Distribution, IteratorRandom}, 6 | rngs::StdRng, 7 | SeedableRng, 8 | }; 9 | 10 | pub fn uniform_partially_mapped_crossover( 11 | parent1: &Vec, 12 | parent2: &Vec, 13 | probability: f64, 14 | seed: Option, 15 | ) -> (Vec, Vec) { 16 | check_length(parent1, parent2); 17 | 18 | let n = parent1.len(); 19 | if !check_continuous(parent1) || !check_continuous(parent2) { 20 | panic!("Vectors must contain continuous unique values"); 21 | } 22 | 23 | let mut prng = match seed { 24 | Some(seed) => StdRng::seed_from_u64(seed), 25 | None => StdRng::from_entropy(), 26 | }; 27 | 28 | let mut selected = (0..n).map(|x| x).choose_multiple(&mut prng, 2); 29 | selected.sort(); 30 | let (mut idx1, mut idx2) = (parent1.clone(), parent2.clone()); 31 | for i in 0..n { 32 | idx1[parent1[i]] = i; 33 | idx2[parent2[i]] = i; 34 | } 35 | let (mut child1, mut child2) = (parent1.clone(), parent2.clone()); 36 | let mask = Uniform::from(0.0..1.0); 37 | for i in selected[0]..=selected[1] { 38 | if mask.sample(&mut prng) > probability { 39 | continue; 40 | } 41 | 42 | let (val1, val2) = (child1[i], child2[i]); 43 | child1[i] = val2; 44 | child1[idx1[val2]] = val1; 45 | child2[i] = val1; 46 | child2[idx2[val1]] = val2; 47 | 48 | let temp = (idx1[val1], idx1[val2]); 49 | idx1[val1] = temp.1; 50 | idx1[val2] = temp.0; 51 | let temp = (idx2[val1], idx2[val2]); 52 | idx2[val1] = temp.1; 53 | idx2[val2] = temp.0; 54 | } 55 | 56 | (child1, child2) 57 | } 58 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! **Genx** provides modular building blocks to run simulations of optimization 2 | //! and search problems using [Genetic Algorithms](https://en.wikipedia.org/wiki/Genetic_algorithm) (GA). 3 | //! 4 | //! The vision for genx is to be a **flexible** and greatly **extensible** library for implementing genetic 5 | //! algorithm applications. genx is written in Rust. The library's API utilizes _functional 6 | //! programming_ paradigm and exposes it's API in that manner only. 7 | //! 8 | //! The implementation is split into __building blocks__ which are all represented 9 | //! by traits. This crate provides most common and probably all possible implementation 10 | //! for all building blocks. So it can be used for many problems out of the box. 11 | //! 12 | //! ## Basic Example 13 | //! 14 | //! ### Selection 15 | //! 16 | //! Here's a trivial example that returns back individual selected using [stochastic universal sampling](https://en.wikipedia.org/wiki/Stochastic_universal_sampling) 17 | //! 18 | //! ```rust 19 | //! use genx::selection::stochastic_universal::stochastic_universal_selection; 20 | //! 21 | //! let num_parents:usize = 10; 22 | //! let fitness_values = vec![2.4,8.4,3.2,9.4,9.0,11.0,4.5,0.6,4.4,2.3,5.6,10.0,0.2,9.0,4.8,7.7]; 23 | //! 24 | //! let result = stochastic_universal_selection(&fitness_values, num_parents, None) 25 | //! .iter() 26 | //! .map(|&a| fitness_values[a]) 27 | //! .collect::>(); 28 | //! ``` 29 | //! 30 | //! `stochastic_universal_selection` takes in the `fitness_value` vector, number of parents it needs to select and a seed value 31 | //! which is `Option`. It returns back the indices of selected individuals which we map to actual fitness values. 32 | //! 33 | //! ### Mutation 34 | //! 35 | //! Mutation function takes in a single individual, distribution index, max_perturbation and returns in the mutated individual using [polynomial mutation](https://www.iitk.ac.in/kangal/papers/k2012016.pdf) for real valued individual. 36 | //! 37 | //! ```rust 38 | //! use genx::mutation::polynomial::polynomial_mutation; 39 | //! let individual = 29.11; 40 | //! let result = polynomial_mutation(individual, 4.2, 4.0, None); 41 | //! ``` 42 | //! 43 | //! The returned value may or may not be equal as is mutated based on a randomly generated value which 44 | //! for deterministic results can be seeded. 45 | 46 | //! ## Building Blocks 47 | //! 48 | //! The genetic algorithm needs a population that it evolves with each iteration. 49 | //! A population contains a number of individuals. Each individual represents a possible 50 | //! candidate solution for an optimization problem for which the best solution is searched for. 51 | //! 52 | //! ### Steps in Genetic Algorithm 53 | //! 54 | //! A Genetic Algorithm proceeds through following operations: 55 | //! - **Encoding**: Binary, Real Values, Order, Tree, etc. 56 | //! - **Selection**: Selecting individuals after fitness evaluation. 57 | //! - **Crossover**: Creating new individuals from selected pool of individuals. 58 | //! - **Mutation**: Making stark changes in generated individual for diversification. 59 | //! - **Convergence**: Test for goal accomplishment or convergence. 60 | //! 61 | //! ### Available Building Blocks 62 | //! 63 | //! The building blocks available in the crate (defined as traits) are: 64 | //! - **[`selection`]** 65 | //! - **[`mutation`]** 66 | //! - **[`crossover`]** 67 | //! - **[`scaling`]** 68 | //! 69 | //! This crate provides multiple implementations for each one of those operators. 70 | //! So one can experiment with combining the different implementations to compose 71 | //! the best algorithm for a specific search or optimization problem. 72 | 73 | //! ## Usage 74 | //! Add this to your `Cargo.toml`: 75 | //! ```toml 76 | //! [dependencies] 77 | //! genx = "0.4.0" 78 | //! ``` 79 | //! If you are not using Rust 2018 edition add this to your crate root: 80 | //! ```rust 81 | //! extern crate genx; 82 | //! ``` 83 | 84 | //! ## Why Genetic Algorithms 85 | //! 86 | //! Genetic Algorithms are at the core of [soft computing](https://en.wikipedia.org/wiki/Soft_computing) which is a branch of 87 | //! computing that comes to rescue when problem at hand is not feasible to be solved using [hard computing](http://www2.cs.uh.edu/~ceick/6367/Soft-Computing.pdf). 88 | //! There are several advantages of genetic algorithms: 89 | //! 90 | //! - Algorithms are adaptive and can adjust to the change of dynamic environment​ 91 | //! - The approach makes use of approximate solutions to solve problems that may be either unsolvable or too time-consuming to solve with current hardware.​ 92 | //! - Imprecise but usable solutions to complex computational problems allowing researchers to approach some problems that traditional computing cannot process.​ 93 | 94 | pub mod mutation; 95 | 96 | pub mod selection; 97 | 98 | pub mod crossover; 99 | 100 | pub mod scaling; 101 | -------------------------------------------------------------------------------- /src/mutation/flipping.rs: -------------------------------------------------------------------------------- 1 | use rand::{rngs::StdRng, SeedableRng, Rng}; 2 | 3 | /** 4 | ## Description 5 | Flipping mutation is a mutation only for binary encoded individuals. 6 | Given the `mutation_probability` and `individual` it iterates through the boolean vector of `individual` 7 | and generates a random number between `0.0` and `1.0`. If the number is less than `mutation_probability` 8 | then we _flip_ the value at that index else it remains the same. 9 | 10 | _Note: The function can also take in an optional `seed` value of type `Option` for deterministic results._ 11 | 12 | ## Return 13 | The return value is a `Result<(), &'static str>` which will return error only if the `mutation_probability` is greater than `1` 14 | ## Example 15 | ```rust 16 | use genx::mutation::flipping_mutation; 17 | let mut individual = vec![false, true, false, false, 18 | true, true, true, false, false, true, false, 19 | false, true, false, false, true]; 20 | let original_individual = individual.clone(); 21 | match flipping_mutation(&mut individual, 0.5, Some(42)) { 22 | Ok(_) => (), 23 | Err(error) => panic!("{:?}", error) 24 | }; 25 | assert_ne!(original_individual, individual); 26 | ``` 27 | */ 28 | pub fn flipping_mutation(individual: &mut Vec, mutation_probability: f32, seed: Option) -> Result<(), &'static str> { 29 | if mutation_probability < 0.0 || mutation_probability > 1.0 { 30 | return Err("mutation_probability should lie between 0.0 and 1.0 inclusive"); 31 | } 32 | 33 | let mut prng = match seed { 34 | Some(val) => StdRng::seed_from_u64(val), 35 | None => StdRng::from_entropy() 36 | }; 37 | for val in individual.iter_mut() { 38 | let random_probability = prng.gen_range(0.0..1.0); 39 | if random_probability < mutation_probability { 40 | *val = !(*val); 41 | } 42 | }; 43 | 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /src/mutation/inversion.rs: -------------------------------------------------------------------------------- 1 | use std::{mem::swap}; 2 | 3 | use rand::{Rng, SeedableRng, rngs::StdRng}; 4 | 5 | /** 6 | ## Description 7 | Inversion mutation is a mutation only for binary encoded individuals. 8 | Given the `individual` it randomly generates two indices and then inverts 9 | the value between those indices of the individual. 10 | 11 | _Note: The function can also take in an optional `seed` value of type `Option` for deterministic results._ 12 | 13 | ## Example 14 | ```rust 15 | use genx::mutation::inversion_mutation; 16 | let mut individual = vec![false, true, false, false, 17 | true, true, true, false, false, true, false, 18 | false, true, false, false, true]; 19 | let original_individual = individual.clone(); 20 | inversion_mutation(&mut individual, Some(42)); 21 | assert_ne!(original_individual, individual); 22 | ``` 23 | */ 24 | pub fn inversion_mutation(individual: &mut Vec, seed: Option) { 25 | let mut prng = match seed { 26 | Some(val) => StdRng::seed_from_u64(val), 27 | None => StdRng::from_entropy() 28 | }; 29 | 30 | let length_of_individual = individual.len(); 31 | 32 | let mut idx1 = prng.gen_range(0..length_of_individual); 33 | let mut idx2 = prng.gen_range(0..length_of_individual); 34 | 35 | if idx2 < idx1 { 36 | swap(&mut idx1, &mut idx2); 37 | } 38 | 39 | for idx in idx1..=idx2 { 40 | individual[idx] = !individual[idx]; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /src/mutation/mod.rs: -------------------------------------------------------------------------------- 1 | //! The `mutation` module provides implementation of the 2 | //! functions for various mutation schemes for binary encoded 3 | //! and real value encoded individuals. 4 | //! 5 | //! The provided functions are organized in sub-modules 6 | //! named after the utilized mutation schema. 7 | //! 8 | //! Available mutation schema for binary encoded 9 | //! individuals are: 10 | //! * `flipping` 11 | //! * `inversion` 12 | //! * `scramble` 13 | //! * `swap` 14 | //! 15 | //! All the mutation functions for binary encoded schema 16 | //! take in atleast an argument of mutable reference to 17 | //! the boolean vector of individual to mutate. Hence they 18 | //! change the actual boolean Vector. 19 | //! 20 | //! Only those functions where, there is a need to constrain 21 | //! range of values that can be provided as argument will return 22 | //! a `Result<(), &'static str>`. The returned value should be checked 23 | //! in case `Err` was returned because of invalid argument values. 24 | //! 25 | //! Available mutation schema for real value encoded 26 | //! individuals are: 27 | //! * `random` 28 | //! * `polynomial` 29 | //! 30 | //! All the mutation functions for real value encoded schema 31 | //! take in the floating point value of individual and return 32 | //! the mutated value. 33 | 34 | pub mod flipping; 35 | 36 | pub mod inversion; 37 | 38 | pub mod polynomial; 39 | 40 | pub mod random; 41 | 42 | pub mod scramble; 43 | 44 | pub mod swap; 45 | 46 | // Re-exports 47 | pub use self::flipping::flipping_mutation; 48 | pub use self::inversion::inversion_mutation; 49 | pub use self::polynomial::polynomial_mutation; 50 | pub use self::random::random_mutation; 51 | pub use self::scramble::scramble_mutation; 52 | pub use self::swap::swap_mutation; 53 | -------------------------------------------------------------------------------- /src/mutation/polynomial.rs: -------------------------------------------------------------------------------- 1 | use rand::{Rng, rngs::StdRng, SeedableRng}; 2 | 3 | /** 4 | ## Description 5 | Polynomial mutation is a mutation only for real encoded individuals. 6 | Given the `individual`, `distribution_index` and `max_perturbation` it generates a closure 7 | which is an exponential polynomial function using the `distribution_index`. Given a randomly generated value 8 | in range `0.0..1.0` to the closure it returns the `perturbation_factor`. 9 | 10 | ### Note 11 | - A large value for `distribution_index` will make stark mutation for only a few values whereas a small `distribution_index` will cause uniform mutation for all randomly generated values (undesirable). 12 | - The function can also take in an optional `seed` value of type `Option` for deterministic results. 13 | 14 | ## Return 15 | Finally returned value is `individual + calculate_perturbation_factor(random_value)*max_perturbation` 16 | 17 | ## Example 18 | ```rust 19 | use genx::mutation::polynomial_mutation; 20 | 21 | let individual = 29.11; 22 | let result = polynomial_mutation(individual, 4.2, 4.0, Some(42)); 23 | assert_ne!(individual, result); 24 | ``` 25 | */ 26 | pub fn polynomial_mutation(individual: f32, distribution_index: f32, max_perturbation: f32, seed: Option) -> f32 { 27 | let random_value = (match seed { 28 | Some(val) => StdRng::seed_from_u64(val), 29 | None => StdRng::from_entropy() 30 | }).gen_range(0.0..1.0); 31 | 32 | let calculate_perturbation_factor = |random_value: f32| -> f32 { 33 | if random_value < 0.5 { 34 | return (2.0*random_value).powf(1.0/(distribution_index + 1.0)) - 1.0; 35 | } 36 | return 1.0 - (2.0*(1.0 - random_value).powf(1.0/(distribution_index + 1.0))); 37 | }; 38 | 39 | individual + calculate_perturbation_factor(random_value)*max_perturbation 40 | } 41 | -------------------------------------------------------------------------------- /src/mutation/random.rs: -------------------------------------------------------------------------------- 1 | use rand::{Rng, rngs::StdRng, SeedableRng}; 2 | 3 | /** 4 | ## Description 5 | Random mutation is a mutation only for real encoded individuals. 6 | Given the `individual` and `perturbation_factor` it generates a random value 7 | in range `0.0..1.0` which is then used to linearly mutate the individual. 8 | 9 | #### Note 10 | - A large value for `perturbation_factor` will make stark mutation for all values. 11 | - The function can also take in an optional `seed` value of type `Option` for deterministic results. 12 | 13 | ## Return 14 | Finally returned value is `individual + (2.0*random_value - 1.0)*perturbation_factor` 15 | 16 | ## Example 17 | ```rust 18 | use genx::mutation::random_mutation; 19 | 20 | let individual = 29.11; 21 | let result = random_mutation(individual, 4.2, Some(42)); 22 | assert_ne!(individual, result); 23 | ``` 24 | */ 25 | pub fn random_mutation(individual: f32, perturbation_factor: f32, seed: Option) -> f32 { 26 | let random_value = (match seed { 27 | Some(val) => StdRng::seed_from_u64(val), 28 | None => StdRng::from_entropy() 29 | }).gen_range(0.0..1.0); 30 | 31 | individual + (2.0*random_value - 1.0)*perturbation_factor 32 | } 33 | -------------------------------------------------------------------------------- /src/mutation/scramble.rs: -------------------------------------------------------------------------------- 1 | use std::mem::swap; 2 | 3 | use rand::{Rng, SeedableRng, rngs::StdRng, seq::SliceRandom}; 4 | 5 | /** 6 | ## Description 7 | Scramble mutation is a mutation only for vector encoded individuals. 8 | Given the `individual` it randomly generates two indices and then 9 | shuffles the values between those two indices. 10 | 11 | _Note: The function can also take in an optional `seed` value of type `Option` for deterministic results._ 12 | 13 | ## Example 14 | ```rust 15 | use genx::mutation::scramble_mutation; 16 | let mut individual = vec![false, true, false, false, 17 | true, true, true, false, false, true, false, 18 | false, true, false, false, true]; 19 | let original_individual = individual.clone(); 20 | scramble_mutation(&mut individual, Some(42)); 21 | assert_ne!(original_individual, individual); 22 | ``` 23 | */ 24 | pub fn scramble_mutation(individual: &mut Vec, seed: Option) { 25 | let mut prng = match seed { 26 | Some(val) => StdRng::seed_from_u64(val), 27 | None => StdRng::from_entropy() 28 | }; 29 | 30 | let length_of_individual = individual.len(); 31 | 32 | let mut idx1 = prng.gen_range(0..length_of_individual); 33 | let mut idx2 = prng.gen_range(0..length_of_individual); 34 | 35 | if idx2 < idx1 { 36 | swap(&mut idx1, &mut idx2); 37 | } 38 | 39 | let slice = &mut individual[idx1..=idx2]; 40 | slice.shuffle(&mut prng); 41 | } 42 | -------------------------------------------------------------------------------- /src/mutation/swap.rs: -------------------------------------------------------------------------------- 1 | use rand::{rngs::StdRng, seq::SliceRandom, SeedableRng}; 2 | 3 | /** 4 | ## Description 5 | Swap mutation is a mutation only for vector encoded individuals. 6 | Given the `individual` it randomly generates two indices and then swaps the value at those two indices. 7 | 8 | _Note: The function can also take in an optional `seed` value of type `Option` for deterministic results._ 9 | 10 | ## Example 11 | ```rust 12 | use genx::mutation::swap_mutation; 13 | let mut individual = vec![false, true, false, false, 14 | true, true, true, false, false, true, false, 15 | false, true, false, false, true]; 16 | let original_individual = individual.clone(); 17 | swap_mutation(&mut individual, Some(11)); 18 | assert_ne!(original_individual, individual); 19 | ``` 20 | */ 21 | pub fn swap_mutation(individual: &mut Vec, seed: Option) -> Result<(), &'static str> 22 | where 23 | T: Copy, 24 | { 25 | let mut prng = match seed { 26 | Some(val) => StdRng::seed_from_u64(val), 27 | None => StdRng::from_entropy(), 28 | }; 29 | 30 | if individual.len() < 2 { 31 | return Err("Need atleast two bits for swapping"); 32 | } 33 | 34 | let mut fitness_values_with_index: Vec<(T, usize)> = Vec::new(); 35 | for (i, &value) in individual.iter().enumerate() { 36 | fitness_values_with_index.push((value, i)); 37 | } 38 | 39 | let selected_indices = fitness_values_with_index 40 | .choose_multiple(&mut prng, 2) 41 | .map(|&x| x) 42 | .collect::>(); 43 | individual[selected_indices[0].1] = selected_indices[1].0; 44 | individual[selected_indices[1].1] = selected_indices[0].0; 45 | 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /src/scaling/linear.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | 3 | pub fn linear_scaling(fitness_values: &mut Vec, scaling_factor: f32) { 4 | let minimum_fitness = fitness_values 5 | .iter() 6 | .min_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal)) 7 | .unwrap(); 8 | let maximum_fitness = fitness_values 9 | .iter() 10 | .max_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal)) 11 | .unwrap(); 12 | let average_fitness = fitness_values.iter().sum::() / (fitness_values.len() as f32); 13 | if average_fitness == 0.0 { 14 | for x in fitness_values { 15 | *x = 1.0; 16 | } 17 | return; 18 | } 19 | let mut a = (average_fitness * (scaling_factor - 1.0)) / (maximum_fitness - average_fitness); 20 | let mut b = (average_fitness * (maximum_fitness - scaling_factor * average_fitness)) 21 | / (maximum_fitness - average_fitness); 22 | 23 | if *minimum_fitness <= -1.0 * b / a { 24 | a = average_fitness / (average_fitness - minimum_fitness); 25 | b = -1.0 * minimum_fitness * average_fitness / (average_fitness - minimum_fitness); 26 | } 27 | 28 | let linear_function = |x:f32| a*x + b; 29 | for x in fitness_values { 30 | *x = linear_function(*x); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/scaling/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod linear; 2 | 3 | pub mod sigma; 4 | 5 | pub use self::linear::linear_scaling; 6 | 7 | pub use self::sigma::sigma_scaling; 8 | -------------------------------------------------------------------------------- /src/scaling/sigma.rs: -------------------------------------------------------------------------------- 1 | pub fn sigma_scaling(fitness_values: &mut Vec, scaling_factor: f32) { 2 | let average_fitness = fitness_values.iter().sum::() / (fitness_values.len() as f32); 3 | let standard_deviation = fitness_values 4 | .iter() 5 | .map(|x| (x - average_fitness).powi(2)) 6 | .sum::(); 7 | let standard_deviation = (standard_deviation / (fitness_values.len() as f32)).sqrt(); 8 | let worst_fitness = average_fitness - standard_deviation * scaling_factor; 9 | for x in fitness_values { 10 | if *x <= worst_fitness { 11 | *x = 0.0; 12 | } 13 | else { 14 | *x = *x - worst_fitness; 15 | } 16 | 17 | *x = *x + 1.0; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/selection/mod.rs: -------------------------------------------------------------------------------- 1 | //! The `selection` module provides implementation of the 2 | //! functions to select a set of individuals out of the total 3 | //! population to do crossover for generating new individuals. 4 | //! 5 | //! The provided functions are organized in sub-modules 6 | //! named after the utilized selection method: 7 | //! * `random` 8 | //! * `rank` 9 | //! * `roulette_wheel` 10 | //! * `steady_state` 11 | //! * `stochastic_universal` 12 | //! * `tournament` 13 | //! 14 | //! All the functions take in atleast two arguments a Vector of floating 15 | //! point values that contains fitness values of individuals, and 16 | //! number of individuals to select. Finally functions return indices of 17 | //! selected individuals. 18 | //! 19 | //! You can read more about selection schemas and their working from the [wikipedia page](https://en.wikipedia.org/wiki/Selection_(genetic_algorithm)) 20 | 21 | pub mod random; 22 | 23 | pub mod rank; 24 | 25 | pub mod roulette_wheel; 26 | 27 | pub mod steady_state; 28 | 29 | pub mod stochastic_universal; 30 | 31 | pub mod tournament; 32 | 33 | // Re-exports 34 | pub use self::random::random_selection; 35 | pub use self::rank::rank_selection; 36 | pub use self::roulette_wheel::roulette_wheel_selection; 37 | pub use self::steady_state::steady_state_selection; 38 | pub use self::stochastic_universal::stochastic_universal_selection; 39 | pub use self::tournament::tournament_selection; 40 | -------------------------------------------------------------------------------- /src/selection/random.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::min; 2 | 3 | use rand::{SeedableRng, prelude::IteratorRandom, rngs::StdRng}; 4 | 5 | /** 6 | ## Description 7 | Random Selection is the simplest form of selection which randomly 8 | generates an index from the provided `fitness_values` vector and that 9 | is the selection. Same procedure is done until we have `num_parents` selected individuals. 10 | 11 | _Note: The function can also take in an optional `seed` value of type `Option` for deterministic results._ 12 | 13 | ## Return 14 | 15 | The return value is a `Vec` pointing to the selected indices. 16 | 17 | ## Example 18 | ```rust 19 | use genx::selection::random_selection; 20 | let num_parents:usize = 10; 21 | let fitness_values = vec![10.0,0.2,9.0,4.8,7.7,8.4,3.2,9.4,9.0,11.0,4.5]; 22 | 23 | let result = random_selection(fitness_values.len(), num_parents, None); 24 | ``` 25 | */ 26 | pub fn random_selection(population_size: usize, num_parents: usize, seed: Option) -> Vec { 27 | let mut prng = match seed { 28 | Some(val) => StdRng::seed_from_u64(val), 29 | None => StdRng::from_entropy() 30 | }; 31 | (0..population_size).map(|x| x).choose_multiple(&mut prng, min(num_parents, population_size)) 32 | } 33 | -------------------------------------------------------------------------------- /src/selection/rank.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | use rand::{rngs::StdRng, SeedableRng, Rng}; 3 | 4 | /** 5 | ## Description 6 | Rank Selection is a form of proportionate selection. It generates a roulette wheel where area occupied by a particular individual is proportional to its rank in fitness vector when sorted in non decreasing order. 7 | 8 | For each of the `num_parents` iterations a random number between `0.0..1.0` is generated which then decides the location at which roulette wheel will stop and that particular individual's index is added to vector of selected individuals. 9 | 10 | ### Note 11 | 12 | - Individuals with same fitness value are given the same ranks. 13 | - The function can also take in an optional `seed` value of type `Option` for deterministic results. 14 | 15 | ## Return 16 | 17 | The return value is a `Vec` pointing to the selected indices. 18 | 19 | ## Example 20 | ```rust 21 | use genx::selection::rank_selection; 22 | let num_parents:usize = 10; 23 | let fitness_values = vec![10.0,0.2,9.0,4.8,7.7,8.4,3.2,9.4,9.0,11.0,4.5]; 24 | 25 | let result = rank_selection(&fitness_values, num_parents, None); 26 | ``` 27 | */ 28 | pub fn rank_selection(fitness_values: &Vec, num_parents: usize, seed: Option) -> Vec { 29 | let mut fitness_values_with_index : Vec<(f32, usize)> = Vec::new(); 30 | for (i, &value) in fitness_values.iter().enumerate() { 31 | fitness_values_with_index.push((value, i)); 32 | }; 33 | fitness_values_with_index.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal)); 34 | let mut ranks:Vec = vec![1]; 35 | let mut sum_of_fitness = 1; 36 | for (i, _) in fitness_values_with_index.iter().enumerate() { 37 | if i == 0 { 38 | continue; 39 | } 40 | if fitness_values_with_index[i].0 == fitness_values_with_index[i-1].0 { 41 | ranks.push(ranks[i-1]); 42 | } 43 | else { 44 | ranks.push(ranks[i-1] + 1); 45 | } 46 | sum_of_fitness += ranks[i]; 47 | }; 48 | let normalized_probabilities = ranks.iter().map(|&a| (a as f32)/(sum_of_fitness as f32)).collect::>(); 49 | 50 | let mut prng = match seed { 51 | Some(val) => StdRng::seed_from_u64(val), 52 | None => StdRng::from_entropy() 53 | }; 54 | let mut selected_indices:Vec = Vec::new(); 55 | for _ in 0..num_parents { 56 | let val = prng.gen(); 57 | let mut cummulative_probability = 0f32; 58 | for (i, &(_, idx)) in fitness_values_with_index.iter().enumerate() { 59 | cummulative_probability += normalized_probabilities[i]; 60 | if cummulative_probability >= val { 61 | selected_indices.push(idx); 62 | break; 63 | } 64 | }; 65 | }; 66 | selected_indices 67 | } 68 | -------------------------------------------------------------------------------- /src/selection/roulette_wheel.rs: -------------------------------------------------------------------------------- 1 | use rand::{rngs::StdRng, SeedableRng, Rng}; 2 | 3 | /** 4 | ## Description 5 | Roulette Wheel Selection is a form of proportionate selection. It generates a roulette wheel where area occupied by a particular individual is proportional to its fitness values. 6 | 7 | For each of the `num_parents` iterations a random number between `0.0..1.0` is generated which then decides the location at which roulette wheel will stop and that particular individual's index is added to vector of selected individuals. 8 | 9 | ### Note 10 | 11 | - Individuals with same fitness value occupy same area on roulette wheel. 12 | - The function can also take in an optional `seed` value of type `Option` for deterministic results. 13 | 14 | ## Return 15 | 16 | The return value is a `Vec` pointing to the selected indices. 17 | 18 | ## Example 19 | ```rust 20 | use genx::selection::roulette_wheel_selection; 21 | let num_parents:usize = 10; 22 | let fitness_values = vec![10.0,0.2,9.0,4.8,7.7,8.4,3.2,9.4,9.0,11.0,4.5]; 23 | 24 | let result = roulette_wheel_selection(&fitness_values, num_parents, None); 25 | ``` 26 | */ 27 | pub fn roulette_wheel_selection(fitness_values: &Vec, num_parents: usize, seed: Option) -> Vec { 28 | let sum_of_fitness = fitness_values.iter().sum::(); 29 | let normalized_probabilities = fitness_values.iter().map(|&a| a/sum_of_fitness).collect::>(); 30 | 31 | let mut prng = match seed { 32 | Some(val) => StdRng::seed_from_u64(val), 33 | None => StdRng::from_entropy() 34 | }; 35 | let mut selected_indices:Vec = Vec::new(); 36 | for _ in 0..num_parents { 37 | let val = prng.gen(); 38 | let mut cummulative_probability = 0f32; 39 | for (index, _) in fitness_values.iter().enumerate() { 40 | cummulative_probability += normalized_probabilities[index]; 41 | if cummulative_probability >= val { 42 | selected_indices.push(index); 43 | break; 44 | } 45 | }; 46 | }; 47 | selected_indices 48 | } 49 | -------------------------------------------------------------------------------- /src/selection/steady_state.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::{Ordering, min}; 2 | 3 | /** 4 | ## Description 5 | Steady State Selection is a sorting based selection method. It will always select the top `num_parents` in terms of fitness value from the pool of individuals. 6 | 7 | _Note: If `num_parents` is greater than length of `fitness_values` vector (n), then only indices from `0..n-1` will be returned._ 8 | 9 | ## Return 10 | 11 | The return value is a `Vec` pointing to the selected indices. 12 | 13 | ## Example 14 | ```rust 15 | use genx::selection::steady_state_selection; 16 | let num_parents:usize = 10; 17 | let fitness_values = vec![10.0,0.2,9.0,4.8,7.7,8.4,3.2,9.4,9.0,11.0,4.5]; 18 | 19 | let result = steady_state_selection(&fitness_values, num_parents); 20 | ``` 21 | */ 22 | pub fn steady_state_selection(fitness_values: &Vec, num_parents: usize) -> Vec { 23 | let mut fitness_values_with_index : Vec<(f32, usize)> = Vec::new(); 24 | let population_size = fitness_values.len(); 25 | for (i, &value) in fitness_values.iter().enumerate() { 26 | fitness_values_with_index.push((value, i)); 27 | }; 28 | fitness_values_with_index.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal)); 29 | let selected_indices = fitness_values_with_index.iter().map(|&a| a.1).collect::>(); 30 | selected_indices[0..min(num_parents, population_size)].to_vec() 31 | } 32 | -------------------------------------------------------------------------------- /src/selection/stochastic_universal.rs: -------------------------------------------------------------------------------- 1 | use rand::{rngs::StdRng, Rng, SeedableRng}; 2 | 3 | /** 4 | ## Description 5 | Stochastic Universal Selection/Sampling is also a proportionate selection method very much like roulette wheel selection. The major differences is that it selects the required number of individuals (`num_parents`) in a single spin of the wheel which allows for population diversity. 6 | 7 | To know more about stochastic universal sampling refer the [wikipedia page](https://en.wikipedia.org/wiki/Stochastic_universal_sampling) 8 | 9 | ### Note 10 | 11 | - Individuals with same fitness value occupy same area on roulette wheel. 12 | - The function can also take in an optional `seed` value of type `Option` for deterministic results. 13 | 14 | ## Return 15 | 16 | The return value is a `Vec` pointing to the selected indices. 17 | 18 | ## Example 19 | ```rust 20 | use genx::selection::stochastic_universal_selection; 21 | let num_parents:usize = 10; 22 | let fitness_values = vec![10.0,0.2,9.0,4.8,7.7,8.4,3.2,9.4,9.0,11.0,4.5]; 23 | let result = stochastic_universal_selection(&fitness_values, num_parents, None); 24 | ``` 25 | */ 26 | pub fn stochastic_universal_selection( 27 | fitness_values: &Vec, 28 | num_parents: usize, 29 | seed: Option, 30 | ) -> Vec { 31 | let sum_of_fitness = fitness_values.iter().sum::(); 32 | let mut fitness_scale: Vec = Vec::new(); 33 | let mut back: f32 = 0.0; 34 | for (idx, &val) in fitness_values.iter().enumerate() { 35 | if idx == 0 { 36 | back = val; 37 | fitness_scale.push(back); 38 | } else { 39 | back = val + back; 40 | fitness_scale.push(back); 41 | } 42 | } 43 | 44 | let fitness_step = sum_of_fitness / num_parents as f32; 45 | let mut prng = match seed { 46 | Some(val) => StdRng::seed_from_u64(val), 47 | None => StdRng::from_entropy(), 48 | }; 49 | let mut random_offset = prng.gen_range(0.0..fitness_step); 50 | let random_inital = random_offset; 51 | let mut current_offset = 0usize; 52 | let mut selected_indices: Vec = Vec::new(); 53 | for i in 0..num_parents { 54 | while fitness_scale[current_offset] < i as f32 * fitness_step + random_inital { 55 | current_offset += 1; 56 | } 57 | selected_indices.push(current_offset); 58 | random_offset += fitness_step; 59 | } 60 | 61 | selected_indices 62 | } 63 | -------------------------------------------------------------------------------- /src/selection/tournament.rs: -------------------------------------------------------------------------------- 1 | use rand::{seq::SliceRandom, rngs::StdRng, SeedableRng}; 2 | use std::cmp::{Ordering, min}; 3 | 4 | /** 5 | ## Description 6 | Tournament Selection is a randomized sorting based selection method. We conduct `num_parents` tournaments where we randomly select `tournament_size` individuals. Out of those selected for a particular tournament, the fittest one is added to the vector of selected indices. 7 | 8 | ### Note: 9 | 10 | - If `tournament_size` is greater than length of `fitness_values` vector (n), then overall fittest individual gets selected from all the tournaments. 11 | - The function can also take in an optional `seed` value of type `Option` for deterministic results. 12 | 13 | ## Return 14 | 15 | The return value is a `Vec` pointing to the selected indices. 16 | 17 | ## Example 18 | ```rust 19 | use genx::selection::tournament_selection; 20 | let num_parents:usize = 10; 21 | let fitness_values = vec![10.0,0.2,9.0,4.8,7.7,8.4,3.2,9.4,9.0,11.0,4.5]; 22 | 23 | let result = tournament_selection(&fitness_values, num_parents, 4, None); 24 | ``` 25 | */ 26 | pub fn tournament_selection(fitness_values: &Vec, num_parents: usize, tournament_size: usize, seed: Option) -> Vec { 27 | let mut fitness_values_with_index : Vec<(f32, usize)> = Vec::new(); 28 | for (i, &value) in fitness_values.iter().enumerate() { 29 | fitness_values_with_index.push((value, i)); 30 | }; 31 | 32 | let number_individuals = fitness_values.len(); 33 | let mut selected_indices:Vec = Vec::new(); 34 | for idx in 0..num_parents { 35 | let mut prng = match seed { 36 | Some(val) => StdRng::seed_from_u64(val*(idx as u64)), 37 | None => StdRng::from_entropy() 38 | }; 39 | let mut current_tournament = fitness_values_with_index.choose_multiple(&mut prng, min(tournament_size, number_individuals)).map(|&x| x).collect::>(); 40 | current_tournament.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal)); 41 | selected_indices.push(current_tournament[0].1); 42 | }; 43 | 44 | selected_indices 45 | } 46 | -------------------------------------------------------------------------------- /tea.yaml: -------------------------------------------------------------------------------- 1 | # https://tea.xyz/what-is-this-file 2 | --- 3 | version: 1.0.0 4 | codeOwners: 5 | - '0x07b82B6e1146AEF56e6d7335cf5F7E04e86Ae1EC' 6 | quorum: 1 7 | -------------------------------------------------------------------------------- /tests/test_crossover.rs: -------------------------------------------------------------------------------- 1 | extern crate genx; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | use genx::crossover::{single_point_crossover, multi_point_crossover}; 6 | 7 | #[test] 8 | fn test_single_point_crossover() { 9 | let parent1 = vec![false, true, false, false, true, true, true, false, true, false, false]; 10 | let parent2 = vec![true, true, false, true, false, true, false, true, true, false, true]; 11 | let childs = single_point_crossover(&parent1, &parent2, Some(42)); 12 | assert_eq!(childs.0.len(), childs.1.len()); 13 | let mut idx:usize = 0; 14 | for (i, x) in childs.0.iter().enumerate() { 15 | if *x != parent1[i] { 16 | idx = i; 17 | break; 18 | } 19 | } 20 | let mut expected_child = Vec::new(); 21 | for i in 0..childs.0.len() { 22 | if i < idx { 23 | expected_child.push(parent1[i]); 24 | } 25 | else { 26 | expected_child.push(parent2[i]); 27 | } 28 | } 29 | assert_eq!(childs.0, expected_child); 30 | for (i, x) in expected_child.iter_mut().enumerate() { 31 | if i < idx { 32 | *x = parent2[i]; 33 | } 34 | else { 35 | *x = parent1[i]; 36 | } 37 | } 38 | assert_eq!(childs.1, expected_child); 39 | } 40 | 41 | #[test] 42 | fn test_multi_point_crossover(){ 43 | let parent1 = vec![false, true, false, false, true, true, true, false, true, false, false]; 44 | let parent2 = vec![true, true, false, true, false, true, false, true, true, false, true]; 45 | let (child1, child2) = multi_point_crossover(&parent1, &parent2, 3, Some(42)); 46 | assert_eq!(child1.len(), child2.len()); 47 | assert_eq!(child1.len(), parent1.len()); 48 | assert_eq!(child1, [false, true, false, true, false, true, true, false, true, false, true]); 49 | assert_eq!(child2, [true, true, false, false, true, true, false, true, true, false, false]); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/test_mutation.rs: -------------------------------------------------------------------------------- 1 | extern crate genx; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | use genx::mutation::{flipping::flipping_mutation, inversion::inversion_mutation, polynomial::polynomial_mutation, random::random_mutation, scramble::scramble_mutation, swap::swap_mutation}; 6 | 7 | #[test] 8 | fn test_inversion_mutation() { 9 | let mut individual = vec![false, true, false, false, true, true, true, false, true, true]; 10 | let original_individual = individual.clone(); 11 | inversion_mutation(&mut individual, Some(43)); 12 | assert_eq!(individual, [false, true, false, false, false, false, false, true, true, true]); 13 | assert_ne!(original_individual, individual); 14 | } 15 | 16 | #[test] 17 | fn test_scramble_mutation() { 18 | let mut individual = vec![false, true, false, false, true, true, true, false, false, true, false]; 19 | let original_individual = individual.clone(); 20 | scramble_mutation(&mut individual, Some(43)); 21 | assert_eq!(individual, [false, true, false, false, true, false, false, true, true, true, false]); 22 | assert_ne!(original_individual, individual); 23 | } 24 | 25 | #[test] 26 | fn test_swap_mutation_success() { 27 | let mut individual = vec![false, true, false, false, true, true, true, false, false, true, false]; 28 | let original_individual = individual.clone(); 29 | match swap_mutation(&mut individual, Some(43)) { 30 | Ok(_) => (), 31 | Err(error) => panic!("{:?}", error) 32 | }; 33 | assert_eq!(individual, [false, true, false, false, true, true, true, false, true, false, false]); 34 | assert_ne!(original_individual, individual); 35 | } 36 | 37 | #[test] 38 | #[should_panic] 39 | fn test_swap_mutation_fail() { 40 | let mut individual = vec![false]; 41 | match swap_mutation(&mut individual, Some(43)) { 42 | Ok(_) => (), 43 | Err(error) => panic!("{}", error) 44 | }; 45 | } 46 | 47 | #[test] 48 | fn test_flipping_mutation_success() { 49 | let mut individual = vec![false, true, false, false, true, true, true, false, false, true, false, false, true, false, false, true, true, true, false, false, true, false, true, true, false, false, true, false, true, false, false, true, true, true, false]; 50 | let original_individual = individual.clone(); 51 | match flipping_mutation(&mut individual, 0.05, Some(43)) { 52 | Ok(_) => (), 53 | Err(error) => panic!("{:?}", error) 54 | }; 55 | assert_eq!(individual, [false, true, false, false, false, true, true, false, false, true, false, false, true, false, false, false, true, true, false, false, true, true, true, true, false, false, true, false, true, false, false, true, true, true, false]); 56 | assert_ne!(original_individual, individual); 57 | } 58 | 59 | #[test] 60 | #[should_panic] 61 | fn test_flipping_mutation() { 62 | let mut individual = vec![false, true, false, false, true, true, true, false, false, true, false, false, true, false, false, true, true, true, false, false, true, false, true, true, false, false, true, false, true, false, false, true, true, true, false]; 63 | let original_individual = individual.clone(); 64 | match flipping_mutation(&mut individual, 1.05, Some(43)) { 65 | Ok(_) => (), 66 | Err(error) => panic!("{:?}", error) 67 | }; 68 | assert_eq!(individual, [false, true, false, false, false, true, true, false, false, true, false, false, true, false, false, false, true, true, false, false, true, true, true, true, false, false, true, false, true, false, false, true, true, true, false]); 69 | assert_ne!(original_individual, individual); 70 | } 71 | 72 | #[test] 73 | fn test_random_mutation() { 74 | let individual = 29.11; 75 | let result = random_mutation(individual, 4.2, Some(43)); 76 | assert_ne!(result, individual); 77 | } 78 | 79 | #[test] 80 | fn test_polynomial_mutation() { 81 | let individual = 29.11; 82 | let result = polynomial_mutation(individual, 4.2, 4.0, Some(43)); 83 | assert_ne!(result, individual); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/test_selection.rs: -------------------------------------------------------------------------------- 1 | extern crate genx; 2 | 3 | #[cfg(test)] 4 | mod tests { 5 | use genx::selection::{random::random_selection, rank::rank_selection, roulette_wheel::roulette_wheel_selection, steady_state::steady_state_selection, stochastic_universal::stochastic_universal_selection, tournament::tournament_selection}; 6 | 7 | #[test] 8 | fn test_random_selection() { 9 | let num_parents:usize = 10; 10 | let fitness_values = vec![2.4,5.6,2.3,1.2,0.6,4.4,2.3,5.6,10.0,0.2,9.0,4.8,7.7,8.4,3.2,9.4,9.0,11.0,4.5]; 11 | 12 | let result = random_selection(fitness_values.len(), num_parents, Some(42)).iter().map(|&a| fitness_values[a]).collect::>(); 13 | assert_eq!(result,[2.4, 9.0, 2.3, 7.7, 0.6, 4.4, 4.8, 8.4, 10.0, 0.2]); 14 | } 15 | 16 | #[test] 17 | fn test_rank_selection() { 18 | let num_parents:usize = 10; 19 | let fitness_values = vec![2.4,5.6,2.3,1.2,0.6,4.4,2.3,5.6,10.0,0.2,9.0,4.8,7.7,8.4,3.2,9.4,9.0,11.0,4.5]; 20 | 21 | let result = rank_selection(&fitness_values, num_parents, Some(42)).iter().map(|&a| fitness_values[a]).collect::>(); 22 | assert_eq!(result,[3.2, 8.4, 4.8, 8.4, 10.0, 9.0, 11.0, 5.6, 11.0, 1.2]); 23 | } 24 | 25 | #[test] 26 | fn test_roulette_wheel_selection() { 27 | let num_parents:usize = 10; 28 | let fitness_values = vec![2.4,5.6,2.3,1.2,0.6,4.4,2.3,5.6,10.0,0.2,9.0,4.8,7.7,8.4,3.2,9.4,9.0,11.0,4.5]; 29 | 30 | let result = roulette_wheel_selection(&fitness_values, num_parents, Some(42)).iter().map(|&a| fitness_values[a]).collect::>(); 31 | assert_eq!(result,[4.4, 7.7, 10.0, 7.7, 11.0, 3.2, 4.5, 9.0, 4.5, 5.6]); 32 | } 33 | 34 | #[test] 35 | fn test_steady_state_selection() { 36 | let num_parents:usize = 10; 37 | let fitness_values = vec![2.4,5.6,2.3,1.2,0.6,4.4,2.3,5.6,10.0,0.2,9.0,4.8,7.7,8.4,3.2,9.4,9.0,11.0,4.5]; 38 | 39 | let result = steady_state_selection(&fitness_values, num_parents).iter().map(|&a| fitness_values[a]).collect::>(); 40 | assert_eq!(result,[11.0, 10.0, 9.4, 9.0, 9.0, 8.4, 7.7, 5.6, 5.6, 4.8]); 41 | } 42 | 43 | #[test] 44 | fn test_stochastic_universal_selection() { 45 | let num_parents:usize = 10; 46 | let fitness_values = vec![2.4,5.6,2.3,1.2,0.6,4.4,2.3,5.6,10.0,0.2,9.0,4.8,7.7,8.4,3.2,9.4,9.0,11.0,4.5]; 47 | 48 | let result = stochastic_universal_selection(&fitness_values, num_parents, Some(42)).iter().map(|&a| fitness_values[a]).collect::>(); 49 | assert_eq!(result,[2.4, 0.6, 5.6, 10.0, 9.0, 7.7, 8.4, 9.4, 9.0, 11.0]); 50 | } 51 | 52 | #[test] 53 | fn test_tournament_selection() { 54 | let num_parents:usize = 10; 55 | let fitness_values = vec![2.4,5.6,2.3,1.2,0.6,4.4,2.3,5.6,10.0,0.2,9.0,4.8,7.7,8.4,3.2,9.4,9.0,11.0,4.5]; 56 | 57 | let result = tournament_selection(&fitness_values, num_parents, 5, Some(42)).iter().map(|&a| fitness_values[a]).collect::>(); 58 | assert_eq!(result,[11.0, 10.0, 5.6, 9.0, 8.4, 10.0, 9.4, 11.0, 9.4, 10.0]); 59 | } 60 | } 61 | --------------------------------------------------------------------------------