pso_rs/lib.rs
1//! An easy-to-use, simple Particle Swarm Optimization (PSO) implementation in Rust.
2//!
3//! [](https://crates.io/crates/pso-rs)
4//! [](https://docs.rs/pso-rs/latest/pso_rs/)
5//! [](https://github.com/czonios/pso-rs/blob/master/LICENSE)
6//! [](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//! 
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}