pso_rs/
lib.rs

1//! An easy-to-use, simple Particle Swarm Optimization (PSO) implementation in Rust.
2//!
3//! [![Crates.io](https://img.shields.io/crates/v/pso_rs?style=for-the-badge)](https://crates.io/crates/pso-rs)
4//! [![docs.rs](https://img.shields.io/docsrs/pso-rs?style=for-the-badge)](https://docs.rs/pso-rs/latest/pso_rs/)
5//! [![GitHub](https://img.shields.io/github/license/czonios/pso-rs?style=for-the-badge)](https://github.com/czonios/pso-rs/blob/master/LICENSE)
6//! [![Website](https://img.shields.io/website?style=for-the-badge&url=https%3A%2F%2Fczonios.github.io%2Fpso-rs%2F)](https://czonios.github.io/pso-rs/)
7//!
8//! It uses the [`rand`](https://crates.io/crates/rand) crate for random initialization, and the [`rayon`](https://crates.io/crates/rayon) crate for parallel objective function computation. It also has a nice progress bar curtesy of the [`indicatif`](https://crates.io/crates/indicatif) crate. Below is a screenshot of PSO running, attempting to minimize the Lennard-Jones potential energy in a cluster of 20 molecules:
9//!
10//! ![Screenshot](https://raw.githubusercontent.com/czonios/pso-rs/master/screenshots/pbar.gif)
11//!
12//! The [examples](#examples) below can get you started.
13//! In order to use it in your own optimization problem, you will need to define an objective function as it is defined in the [run](https://docs.rs/pso-rs/latest/pso_rs/fn.run.html) function, and a [`Config`](https://docs.rs/pso-rs/latest/pso_rs/model/struct.Config.html) object. See the [Notes](#notes) section for more tips.
14//!
15//! # Examples
16//!
17//! ## Run PSO
18//!
19//! ```rust
20//! use pso_rs::*;
21//!
22//! // define objective function (d-dimensional Rosenbrock)
23//! fn objective_function(
24//!     p: &Particle,
25//!     _flat_dim: usize,
26//!     dimensions: &Vec<usize>
27//! ) -> f64 {
28//!     (0..dimensions[0] - 1).map(|i| {
29//!         100.0 * ((p[i+1]-p[i]).powf(2.0)).powf(2.0)
30//!             + (1.0-p[i]).powf(2.0)
31//!     }).sum()
32//! }
33//!
34//! // define a termination condition (optional)
35//! fn terminate(f_best: f64) -> bool {
36//!     f_best - (0.0) < 1e-4
37//! }
38//!
39//! let config = Config {
40//!     // dimension shape of each particle
41//!     dimensions: vec![2],
42//!     // problem bounds in each dimension
43//!     bounds: vec![(-5.0, 10.0); 2],
44//!     // maximum no. of objective function computations
45//!     t_max: 10000,
46//!     // leave the rest of the params as default
47//!     ..Config::default()
48//! };
49//!
50//! let pso = pso_rs::run(
51//!     config,
52//!     objective_function,
53//!     Some(terminate)
54//! ).unwrap();
55//!     
56//! let model = pso.model;
57//! println!("Model: {:?} ", model.get_f_best());
58//! ```
59//!
60//! ## Initialize PSO for later execution
61//!
62//! ```rust
63//! use pso_rs::*;
64//!
65//! // define objective function (d-dimensional Rosenbrock)
66//! fn objective_function(
67//!     p: &Particle,
68//!     _flat_dim: usize,
69//!     dimensions: &Vec<usize>
70//! ) -> f64 {
71//!     (0..dimensions[0] - 1).map(|i| {
72//!         100.0 * ((p[i+1]-p[i]).powf(2.0)).powf(2.0)
73//!             + (1.0-p[i]).powf(2.0)
74//!     }).sum()
75//! }
76//!
77//!
78//! let config = Config {
79//!     dimensions: vec![2],
80//!     bounds: vec![(-5.0, 10.0); 2],
81//!     t_max: 10000,
82//!     ..Config::default()
83//! };
84//!
85//! let mut pso = pso_rs::init(
86//!     config,
87//!     objective_function
88//! ).unwrap();
89//!
90//! // run PSO with no termination condition
91//! pso.run(|_| false);
92//!     
93//! let model = pso.model;
94//! println!("Found minimum: {:#?} ", model.get_f_best());
95//! println!("Minimizer: {:#?}", model.get_x_best());
96//! ```
97//!
98//! # Notes
99//!
100//! ## Performance
101//!
102//! This implementation uses a flat vector (`Vec<f64>`) to represent any d-dimensional problem (see the [Optimization Problem Dimensionality](#optimization-problem-dimensionality) section). This means that the vector has an O(1) access time, and can be cached for fast access, similarly to a static array.
103//!
104//! The computation of the objective function for each particle is performed in parallel, as it is computationally expensive for any non-trivial problem. In the future, complete swarms will be able to be run in parallel and optionally communicate their best found positions by passing messages.
105//!
106//! ## Optimization problem dimensionality
107//!
108//! Even though you can have particles of any shape and size, as long as each item is `f64`, `pso_rs` represents each particle as a flat vector: `Vec<f64>`.
109//!
110//! This means that, for example, in order to find clusters of 20 molecules in 3D space that minimize the [Lennard-Jones potential energy](https://en.wikipedia.org/wiki/Lennard-Jones_potential), you can define `dimensions` as (20, 3).
111//! If you want, you can also create a custom `reshape` function, like this one for molecule clusters below:
112//!
113//! ```rust
114//! use pso_rs::*;
115//!
116//! fn reshape(
117//!     particle: &Particle,
118//!     particle_dims: &Vec<usize>
119//! ) -> Vec<Vec<f64>> {
120//!     let mut reshaped_cluster = vec![];
121//!     let mut i = 0;
122//!     for _ in 0..particle_dims[0] {
123//!         let mut reshaped_molecule = vec![];
124//!         for _ in 0..particle_dims[1] {
125//!             reshaped_molecule.push(particle[i]);
126//!             i += 1;
127//!         }
128//!         reshaped_cluster.push(reshaped_molecule);
129//!     }
130//!     reshaped_cluster
131//! }
132//!
133//! // used in the objective function
134//! fn objective_function(
135//!     p: &Particle,
136//!     _flat_dim: usize,
137//!     dimensions: &Vec<usize>
138//! ) -> f64 {
139//!     let _reshaped_particle = reshape(p, dimensions);
140//!     /* Do stuff */
141//!     0.0
142//! }
143//!
144//! let config = Config {
145//!     dimensions: vec![20, 3],
146//!     bounds: vec![(-2.5, 2.5); 3],
147//!     t_max: 1,
148//!     ..Config::default()
149//! };
150//!
151//! let pso = pso_rs::run(
152//!     config,
153//!     objective_function,
154//!     None
155//! ).unwrap();
156//!
157//! // somewhere in main(), after running PSO as in the example:
158//! println!(
159//!     "Best found minimizer: {:#?} ",
160//!     reshape(&pso.model.get_x_best(),
161//!         &pso.model.config.dimensions)
162//! );
163//! ```
164
165pub mod model;
166pub mod pso;
167
168pub use model::*;
169
170use model::Model;
171use pso::PSO;
172use std::error::Error;
173
174/// Creates a model and runs the PSO method
175///
176/// # Panics
177///
178/// Panics if any particle coefficient becomes NaN (usually because of bad parameterization, e.g. c1 + c2 < 4)
179pub fn run(
180    config: Config,
181    obj_f: fn(&Particle, usize, &Vec<usize>) -> f64,
182    terminate_f: Option<fn(f64) -> bool>,
183) -> Result<PSO, Box<dyn Error>> {
184    assert_config(&config)?;
185    let mut pso = init(config, obj_f).unwrap();
186    let term_condition = match terminate_f {
187        Some(terminate_f) => terminate_f,
188        None => |_| false,
189    };
190    pso.run(term_condition);
191    Ok(pso)
192}
193
194/// Initializes and returns a PSO instance without running the optimization process
195///
196/// Useful for initializing an instance for running at a later time
197pub fn init(
198    config: Config,
199    obj_f: fn(&Particle, usize, &Vec<usize>) -> f64,
200) -> Result<PSO, &'static str> {
201    assert_config(&config)?;
202    let model = Model::new(config, obj_f);
203    let pso = PSO::new(model);
204    Ok(pso)
205}
206
207fn assert_config(config: &Config) -> Result<(), &'static str> {
208    if config.c1 + config.c2 < 4.0 {
209        return Err("c1 + c2 must be greater than 4");
210    }
211    if config.dimensions.len() == 0 {
212        return Err("dimensions must be set");
213    }
214    if config.bounds.len() != config.dimensions[config.dimensions.len() - 1] {
215        return Err("bounds vector must have the same length as the last dimension of the model");
216    }
217    Ok(())
218}
219
220#[cfg(test)]
221mod tests {
222
223    #[test]
224    fn it_works() {
225        assert_eq!(2 + 2, 4);
226    }
227}