diff --git a/README.md b/README.md index 1b7a78d..d267633 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,27 @@ print(output) ### Firefly +Exploration into the Firefly algorithm (a generalized version of particle swarm optimization) in Pytorch. In particular interested in hybrid firefly + genetic algorithms, or ones that are gender-based. This code was adapted from lucidrains. + +```python +from swarms_torch.firefly import FireflyOptimizer +from torch import Tensor + + +def rosenbrock(x: Tensor) -> Tensor: + return ( + 100 * (x[..., 1:] - x[..., :-1] ** 2) ** 2 + (1 - x[..., :-1]) ** 2 + ).sum(dim=-1) + + +if __name__ == "__main__": + optimizer = FireflyOptimizer(cost_function=rosenbrock) + optimizer.optimize() + best_solution = optimizer.get_best_solution() + print(f"Best solution: {best_solution}") + +``` + @@ -239,4 +260,31 @@ Help us accelerate our backlog by supporting us financially! Note, we're an open # License -MIT \ No newline at end of file +MIT + + +## Citations + +```bibtex +@article{Yang2018WhyTF, + title = {Why the Firefly Algorithm Works?}, + author = {Xin-She Yang and Xingshi He}, + journal = {ArXiv}, + year = {2018}, + volume = {abs/1806.01632}, + url = {https://api.semanticscholar.org/CorpusID:46940737} +} +``` + +```bibtex +@article{article, + author = {El-Shorbagy, M. and Elrefaey, Adel}, + year = {2022}, + month = {04}, + pages = {706-730}, + title = {A hybrid genetic-firefly algorithm for engineering design problems}, + volume = {Journal of Computational Design and Engineering, Volume 9}, + journal = {Journal of Computational Design and Engineering}, + doi = {10.1093/jcde/qwac013} +} +``` \ No newline at end of file diff --git a/docs/swarms/firefly.md b/docs/swarms/firefly.md new file mode 100644 index 0000000..23762b3 --- /dev/null +++ b/docs/swarms/firefly.md @@ -0,0 +1,157 @@ +# FireflyOptimizer + +```python +class FireflyOptimizer(cost_function, steps=5000, species=4, population_size=1000, dimensions=10, lower_bound=-4.0, upper_bound=4.0, mix_species_every=25, beta0=2.0, gamma=1.0, alpha=0.1, alpha_decay=0.995, use_genetic_algorithm=False, breed_every=10, tournament_size=100, num_children=500, use_cuda=True, verbose=True) +``` + +The `FireflyOptimizer` class implements the Firefly Algorithm to minimize a given objective function. It simulates the flashing behavior of fireflies to explore the search space efficiently. + +## Parameters + +- **cost_function** (callable): + The objective function to minimize. Should accept a `torch.Tensor` and return a `torch.Tensor` of costs. + +- **steps** (int, optional): + Number of optimization steps. Default: `5000`. + +- **species** (int, optional): + Number of species in the population. Default: `4`. + +- **population_size** (int, optional): + Number of fireflies in each species. Default: `1000`. + +- **dimensions** (int, optional): + Dimensionality of the search space. Default: `10`. + +- **lower_bound** (float, optional): + Lower bound of the search space. Default: `-4.0`. + +- **upper_bound** (float, optional): + Upper bound of the search space. Default: `4.0`. + +- **mix_species_every** (int, optional): + Interval (in steps) to mix species. Default: `25`. + +- **beta0** (float, optional): + Base attractiveness coefficient. Default: `2.0`. + +- **gamma** (float, optional): + Light absorption coefficient controlling intensity decay. Default: `1.0`. + +- **alpha** (float, optional): + Randomness scaling factor. Default: `0.1`. + +- **alpha_decay** (float, optional): + Decay rate of `alpha` per step. Default: `0.995`. + +- **use_genetic_algorithm** (bool, optional): + Whether to include genetic algorithm operations. Default: `False`. + +- **breed_every** (int, optional): + Steps between breeding operations when using genetic algorithm. Default: `10`. + +- **tournament_size** (int, optional): + Number of participants in each tournament selection. Default: `100`. + +- **num_children** (int, optional): + Number of offspring produced during breeding. Default: `500`. + +- **use_cuda** (bool, optional): + Use CUDA for computations if available. Default: `True`. + +- **verbose** (bool, optional): + Print progress messages during optimization. Default: `True`. + +## Attributes + +| Attribute | Type | Description | +|--------------------|-----------------|--------------------------------------------------------| +| `fireflies` | `torch.Tensor` | Positions of the fireflies in the search space. | +| `device` | `torch.device` | Device used for computations (`cpu` or `cuda`). | +| `current_alpha` | `float` | Current value of `alpha` during optimization. | + +## Methods + +### `optimize()` + +Runs the optimization loop for the specified number of steps. + +**Example:** + +```python +optimizer.optimize() +``` + +### `get_best_solution()` + +Retrieves the best solution found by the optimizer. + +**Returns:** + +- **best_firefly** (`torch.Tensor`): + The best solution vector found. + +**Example:** + +```python +best_solution = optimizer.get_best_solution() +print(f"Best solution: {best_solution}") +``` + +### `generate()` + +Generates a new set of fireflies, reinitializing their positions. + +**Returns:** + +- **fireflies** (`torch.Tensor`): + The new set of fireflies. + +**Example:** + +```python +optimizer.generate() +``` + +### `reset()` + +Resets the optimizer to its initial state, including `alpha` and firefly positions. + +**Example:** + +```python +optimizer.reset() +``` + +--- + +**Note:** The Firefly Algorithm is inspired by the flashing behavior of fireflies and is suitable for continuous optimization problems. This implementation allows for customization and includes optional genetic algorithm operations for enhanced performance. + +**Example Usage:** + +```python +from swarms_torch.firefly import FireflyOptimizer +from torch import Tensor + + +def rosenbrock(x: Tensor) -> Tensor: + return ( + 100 * (x[..., 1:] - x[..., :-1] ** 2) ** 2 + (1 - x[..., :-1]) ** 2 + ).sum(dim=-1) + + +if __name__ == "__main__": + optimizer = FireflyOptimizer( + cost_function=rosenbrock, + steps=100, + species=10, + population_size=100, + dimensions=10, + lower_bound=-4, + upper_bound=4, + # Many more parameters can be set, see the documentation for more details + ) + optimizer.optimize() + best_solution = optimizer.get_best_solution() + print(f"Best solution: {best_solution}") +``` \ No newline at end of file diff --git a/examples/fire_fly_example.py b/examples/fire_fly_example.py new file mode 100644 index 0000000..4443d7c --- /dev/null +++ b/examples/fire_fly_example.py @@ -0,0 +1,24 @@ +from swarms_torch.firefly import FireflyOptimizer +from torch import Tensor + + +def rosenbrock(x: Tensor) -> Tensor: + return ( + 100 * (x[..., 1:] - x[..., :-1] ** 2) ** 2 + (1 - x[..., :-1]) ** 2 + ).sum(dim=-1) + + +if __name__ == "__main__": + optimizer = FireflyOptimizer( + cost_function=rosenbrock, + steps=100, + species=10, + population_size=100, + dimensions=10, + lower_bound=-4, + upper_bound=4, + # Many more parameters can be set, see the documentation for more details + ) + optimizer.optimize() + best_solution = optimizer.get_best_solution() + print(f"Best solution: {best_solution}") diff --git a/swarms_torch/__init__.py b/swarms_torch/__init__.py index 7ad5ec7..c2caab9 100644 --- a/swarms_torch/__init__.py +++ b/swarms_torch/__init__.py @@ -12,6 +12,7 @@ Particle, TransformerParticleSwarmOptimization, ) +from swarms_torch.firefly import FireflyOptimizer from swarms_torch.structs import * # noqa __all__ = [ @@ -28,4 +29,5 @@ "TransformerParticleSwarmOptimization", "HivemindSwarm", "MixtureOfMambas", + "FireflyOptimizer", ] diff --git a/swarms_torch/drone_swarm.py b/swarms_torch/drone_swarm.py index e832a79..85768d4 100644 --- a/swarms_torch/drone_swarm.py +++ b/swarms_torch/drone_swarm.py @@ -309,10 +309,12 @@ def forward( final_neighborhood_embedding = self.neighbor_mlp(obs_neighbors) return final_neighborhood_embedding + @dataclass class SwarmMultiHeadAttentionEncoder(nn.Module): dim: int + @dataclass class QuadSingleHeadAttentionEncoderSim2Real(SwarmMultiHeadAttentionEncoder): obs_space: int diff --git a/swarms_torch/firefly.py b/swarms_torch/firefly.py index f09dad0..b5cc063 100644 --- a/swarms_torch/firefly.py +++ b/swarms_torch/firefly.py @@ -90,13 +90,22 @@ def __init__( self.verbose = verbose # Additional initializations - assert self.tournament_size <= self.population_size, "Tournament size must be less than or equal to population size." - assert self.num_children <= self.population_size, "Number of children must be less than or equal to population size." - - self.device = torch.device('cuda' if torch.cuda.is_available() and self.use_cuda else 'cpu') + assert ( + self.tournament_size <= self.population_size + ), "Tournament size must be less than or equal to population size." + assert ( + self.num_children <= self.population_size + ), "Number of children must be less than or equal to population size." + + self.device = torch.device( + "cuda" if torch.cuda.is_available() and self.use_cuda else "cpu" + ) # Initialize fireflies - self.fireflies = torch.zeros((self.species, self.population_size, self.dimensions), device=self.device).uniform_(self.lower_bound, self.upper_bound) + self.fireflies = torch.zeros( + (self.species, self.population_size, self.dimensions), + device=self.device, + ).uniform_(self.lower_bound, self.upper_bound) # Initialize alpha (in case we need to reset) self.current_alpha = self.alpha @@ -109,24 +118,34 @@ def optimize(self) -> None: costs = self.cost_function(self.fireflies) if self.verbose: - logger.info(f'Step {step}: Minimum cost {costs.amin():.5f}') + logger.info(f"Step {step}: Minimum cost {costs.amin():.5f}") # Fireflies with lower light intensity (high cost) move towards higher intensity (lower cost) - move_mask = einx.greater('s i, s j -> s i j', costs, costs) + move_mask = einx.greater("s i, s j -> s i j", costs, costs) # Get vectors of fireflies to one another - delta_positions = einx.subtract('s j d, s i d -> s i j d', self.fireflies, self.fireflies) + delta_positions = einx.subtract( + "s j d, s i d -> s i j d", self.fireflies, self.fireflies + ) distance = delta_positions.norm(dim=-1) - betas = self.beta0 * torch.exp(-self.gamma * distance ** 2) + betas = self.beta0 * torch.exp(-self.gamma * distance**2) # Calculate movements - attraction = einx.multiply('s i j, s i j d -> s i j d', move_mask * betas, delta_positions) - random_walk = self.current_alpha * (torch.rand_like(self.fireflies) - 0.5) * (self.upper_bound - self.lower_bound) + attraction = einx.multiply( + "s i j, s i j d -> s i j d", move_mask * betas, delta_positions + ) + random_walk = ( + self.current_alpha + * (torch.rand_like(self.fireflies) - 0.5) + * (self.upper_bound - self.lower_bound) + ) # Move the fireflies - self.fireflies += einx.sum('s i j d -> s i d', attraction) + random_walk + self.fireflies += ( + einx.sum("s i j d -> s i d", attraction) + random_walk + ) self.fireflies.clamp_(min=self.lower_bound, max=self.upper_bound) @@ -138,8 +157,12 @@ def optimize(self) -> None: midpoint = self.population_size // 2 fireflies_a = self.fireflies[:, :midpoint] fireflies_b = self.fireflies[:, midpoint:] - rotated_fireflies_b = torch.roll(fireflies_b, shifts=1, dims=(0,)) - self.fireflies = torch.cat((fireflies_a, rotated_fireflies_b), dim=1) + rotated_fireflies_b = torch.roll( + fireflies_b, shifts=1, dims=(0,) + ) + self.fireflies = torch.cat( + (fireflies_a, rotated_fireflies_b), dim=1 + ) # Genetic algorithm operations if self.use_genetic_algorithm and (step % self.breed_every) == 0: @@ -156,14 +179,23 @@ def _genetic_operations(self, costs: Tensor) -> None: """ fitness = 1.0 / costs - batch_randperm = torch.randn((self.species, self.num_children, self.population_size), device=self.device).argsort(dim=-1) - tournament_indices = batch_randperm[..., :self.tournament_size] + batch_randperm = torch.randn( + (self.species, self.num_children, self.population_size), + device=self.device, + ).argsort(dim=-1) + tournament_indices = batch_randperm[..., : self.tournament_size] - tournament_participants = einx.get_at('s [p], s c t -> s c t', fitness, tournament_indices) + tournament_participants = einx.get_at( + "s [p], s c t -> s c t", fitness, tournament_indices + ) winners_per_tournament = tournament_participants.topk(2, dim=-1).indices # Breed the top two winners of each tournament - parent1, parent2 = einx.get_at('s [p] d, s c parents -> parents s c d', self.fireflies, winners_per_tournament) + parent1, parent2 = einx.get_at( + "s [p] d, s c parents -> parents s c d", + self.fireflies, + winners_per_tournament, + ) # Uniform crossover crossover_mask = torch.rand_like(parent1) < 0.5 @@ -171,9 +203,13 @@ def _genetic_operations(self, costs: Tensor) -> None: # Sort the fireflies by cost and replace the worst performing with the new children _, sorted_indices = costs.sort(dim=-1) - sorted_fireflies = einx.get_at('s [p] d, s sorted -> s sorted d', self.fireflies, sorted_indices) + sorted_fireflies = einx.get_at( + "s [p] d, s sorted -> s sorted d", self.fireflies, sorted_indices + ) - self.fireflies = torch.cat((sorted_fireflies[:, :-self.num_children], children), dim=1) + self.fireflies = torch.cat( + (sorted_fireflies[:, : -self.num_children], children), dim=1 + ) def get_best_solution(self) -> Tensor: """ @@ -184,12 +220,12 @@ def get_best_solution(self) -> Tensor: Tensor The best solution vector. """ - fireflies_flat = einx.rearrange('s p d -> (s p) d', self.fireflies) + fireflies_flat = einx.rearrange("s p d -> (s p) d", self.fireflies) costs = self.cost_function(fireflies_flat) sorted_costs, sorted_indices = costs.sort(dim=-1) best_firefly = fireflies_flat[sorted_indices[0]] best_cost = sorted_costs[0] - logger.info(f'Best solution found with cost {best_cost:.5f}') + logger.info(f"Best solution found with cost {best_cost:.5f}") return best_firefly def generate(self) -> Tensor: @@ -201,7 +237,10 @@ def generate(self) -> Tensor: Tensor The new set of fireflies. """ - self.fireflies = torch.zeros((self.species, self.population_size, self.dimensions), device=self.device).uniform_(self.lower_bound, self.upper_bound) + self.fireflies = torch.zeros( + (self.species, self.population_size, self.dimensions), + device=self.device, + ).uniform_(self.lower_bound, self.upper_bound) self.current_alpha = self.alpha return self.fireflies @@ -212,6 +251,7 @@ def reset(self) -> None: self.generate() self.current_alpha = self.alpha + # Example usage: # def rosenbrock(x: Tensor) -> Tensor: diff --git a/swarms_torch/swarmalators/swarmalator_base.py b/swarms_torch/swarmalators/swarmalator_base.py index ce43b21..a01a024 100644 --- a/swarms_torch/swarmalators/swarmalator_base.py +++ b/swarms_torch/swarmalators/swarmalator_base.py @@ -34,9 +34,7 @@ def function_for_sigma( # Define dynamics for sigma based on our assumptions d_sigma = ( - gamma * interaction_sum - + epsilon_a * sigma_i - - epsilon_r * (sigma_i**3) + gamma * interaction_sum + epsilon_a * sigma_i - epsilon_r * (sigma_i**3) ) return d_sigma diff --git a/swarms_torch/swarmalators/swarmalator_transformer.py b/swarms_torch/swarmalators/swarmalator_transformer.py index bce24c6..003f0e0 100644 --- a/swarms_torch/swarmalators/swarmalator_transformer.py +++ b/swarms_torch/swarmalators/swarmalator_transformer.py @@ -1,6 +1,7 @@ """ Swarmalators with transformer models, SUPER EXPERIMENTAL, NEEDS WORK """ + import torch from torch import nn