<h1 align="center">Particle Swarm Optimization (PSO) package</h1>

📋 Table of contents
- [📖 About](#-about)
- [🚀 How to run the project using a virtual environment](#-how-to-run-the-project-using-a-virtual-environment)
  - [Using `poetry`](#using-poetry)
  - [Using the prefered installer for Python (`pip`)](#using-the-prefered-installer-for-python-pip)
- [📚 Class Diagrams](#-class-diagrams)
  - [Sumarized Diagrams](#sumarized-diagrams)
    - [Overview](#overview)
    - [`Optimization` class](#optimization-class)
    - [`GUI` class](#gui-class)
  - [Vector and swarm diagrams](#vector-and-swarm-diagrams)
  - [Complete Class Diagram](#complete-class-diagram)


## 📖 About

This project was developed by [@Unbo10](https://github.com/Unbo10) and [@jsruedase](https://github.com/jsruedase) as the final project of the Object-Oriented Programming course, taught at Universidad Nacional de Colombia by [Prof. @fegonzalez7](https://github.com/fegonzalez7). It is an implementation of the Particle Swarm Optimization (PSO from now onwards) algorithm using Python and some of its libraries like `tkinter`, `openpyxl` and `numpy`.

To know and understand better the PSO algorithm, we encourage you to check the Wiki of this project:
- ![Wiki](https://github.com/Unbo10/ParticleSwarmOptimization/wiki/PSO's-heuristic)

Once you are familiarized with it, we can proceed. We chose to work on this problem because it was a great way to apply all the concepts that we learned in class, to learn how to use external libraries to make a graphic interface or work with a database and finally, because it was a challenge. 

The PSO algorithm consists of a set of Particles that "explore" the function, with the purpose of finding it's minimum. That's why the implementation that we made consists of a ```Vector``` class, that is the class from wich ```Position```, ```Heuristic``` and ```Velocity``` inherit. Then, we define the ```Particle``` class, that consists of the three previous classes and has methods such as ```initialize_randomly``` or ```_update_velocity```, that allows us to control the particles over the domain of the function. Finally, we have the ```ParticleSwarm``` class, which is the set of all the particles that we want to create. This is the abstraction of the project that enables us to solve the problem, with the algorithm. The steps to follow are detailed in the Wiki.

## 🚀 How to run the project using a virtual environment

First of all, what is a virtual envoronment? A virtual envirnment is a "separate folder" that creates an independent set of installed packages. This means that we can have different versions of some packages in that folder than those that we have globally installed. This is useful because some versions of this project might need specifically some requirements that change over time.

For this project, `poetry` was chosen to be the dependency manager and the packaging tool. It allows you to create virtual environments (just like `pip`), and has some additional features that make it more complete and easier to use. For this reason, we recommend following the instructions with `poetry`, but you may try with `pip` as well (it hasn't been tested thoroughly, but at least the dependencies seem to be installed).

### Using `poetry`

1. Open a terminal and clone the repository by executing `git clone` followed by the repository's URL:
```bash
git clone https://github.com/Unbo10/ParticleSwarmOptimization.git
```

1. Change the directory to the project's folder:
```bash
cd ParticleSwarmOptimization/
```

1. Install `poetry`:
```bash
pip install poetry
```

1. Run the following command to make sure the virtual environment is created in the project's folder:
```bash
poetry config virtualenvs.in-project true
```

To chek if it was configured correctly, you can run `poetry config --list`, and you should see `virtualenvs.in-project = true`.

5. Spawn a new shell and the virtual environment:
```bash
poetry shell
```

6. Install the project's dependencies:
```bash
poetry install
```

After completing these steps, you should see to the left of the terminal `(pso-py3.10)`, indicating that you're in the virtual environment. Now, you may change the directory to `pso/` by running `cd pso/` and then run the command
```bash
poetry run run
```
to execute the `run()` function from the module `main`. This will start the GUI.

Whenever you want to exit the virtual environment, you can run `exit`, and whenever you wat to enter it again, you can navigate to the project's folder (if you were in the `pso/` folder you can run `cd ..`) and:
- run `source .venv/bin/activate` if you are in Linux.
- run `venv\Scripts\activate.bat` if you are in Windows.

### Using the prefered installer for Python (`pip`)

1. Clone the repository:
```bash
git clone https://github.com/Unbo10/ParticleSwarmOptimization.git
```

2. In the folder that you cloned the repository, create the virtual environment:

```bash
pip install virtualenv
```

3. Create environment folder inside the current project directory:
```bash
python -m venv env
```

4. Activate the virtual environment:
- In Linux: `source env/bin/activate`
- In Windows: `.\env\Scripts\activate`
  
Once you activate it you should see ```(env)``` on the terminal.

5. Install the project package:
```bash
pip install -e.
```

6. Go to the pso folder:
```bash
cd pso
```
7. Run main.py:
- In Linux: `python3 main.py`
- In Windows: `py main.py`

## 📚 Class Diagrams

### Sumarized Diagrams

#### Overview

```mermaid
    classDiagram
    direction LR
    class Main{

    }
    Main "1" --o "*" Optimization
    Main "1" --* "1" Data
    Main "1" --* "1" GUI
    class GUI{

    }
    GUI "1" --o "1" Data
    GUI "1" --* "*" Optimization
    class Data{

    }
    Data "1" --o "*" Optimization
    class Optimization{

    }
    Optimization "1" --o "1" Data
```

#### `Optimization` class

``` mermaid 
    classDiagram
    direction LR
        class Optimization{
    }
    Optimization "1" --o "1" ParticleSwarm
    Optimization "*" --o "1" Data

    class Data {
    }

    class ParticleSwarm{
    }
    ParticleSwarm "1" o--"*" Particle

    class Particle{
    }
    Particle "1" o-- "1" Heuristic
    Particle "1" o-- "2" Position
    Particle "1" o-- "1" Velocity


    class Vector {
    }

    class Heuristic{
    }
    Heuristic "1" --|> "1" Vector

    class Position{
    }
    Position "1" --|> "1" Vector

    class Velocity {
    }
    Velocity "1" --|> "1" Vector

```

#### `GUI` class

```mermaid
    classDiagram
    direction LR
        class GUI {
    }

    GUI "1" --o "*" Optimization
    GUI "1" --* "1" MainMenu
    GUI "1" --* "1" CreateMenu
    GUI "1" --* "1" SelectMenu
    GUI "1" --* "1" ExitMenu

    class ExitMenu {
    }


    class MainMenu {
    }

    MainMenu "1" --* "1" BottomFrame
    MainMenu "1" --* "1" OptionsFrame

    class BottomFrame {
    }
    
    BottomFrame "1" --|> "1" tkFrame
    BottomFrame "1" --* "2" BottomButton

    class BottomButton {
    }

    BottomButton "1" --* "1" PopUpFrame
    BottomButton "1" --|> "1" tkButton

    class PopUpFrame {
    }

    PopUpFrame "1" --|> "1" tkFrame

    class OptionsFrame {
    }

    OptionsFrame "1" --* "3" OptionsButton
    OptionsFrame "1" --* "3" OptionsButton

    class OptionsButton{
    }

    OptionsButton "1" --|> "1" tkButton

    class CreateButton {
    }
    CreateButton "1" --|> "1" OptionsButton


    class CreateInput {
    }


    class CreateMenu {
    }
    CreateMenu "1" --* "1" FunctionChoiceMenu
    CreateMenu "1" --* "6" CreateInput
    CreateMenu "1" --* "2" CreateButton


    class FunctionChoiceMenu {
    }


    class SelectMenu {
    }

    SelectMenu "1" --* "1" OptionsButton
    SelectMenu "1" *-- "*" OptimizationFrame
    SelectMenu "1" --* "1" BackButton

    class BackButton {
    }

    class OptimizationFrame {   
    }
    OptimizationFrame "1" --* "1" ViewButton
    
    class ViewButton {
    }

    ViewButton "1" --* "1" ViewFrame

    class ViewFrame {
    }

    ViewFrame "1" --* "1" BackButton

```

### Vector and swarm diagrams
``` mermaid
    classDiagram
    class Optimization{
        - I Data data
        - float cognitive_coefficient
        - float inertia_coefficient
        - float social_coefficient
        - int dimensions
        - int iterations
        - int particle_amount
        - ParticleSwarm swarm

        - heuristic(Position position, int selection)
        - optimize()

        + get_dimensions(): int
        + get_index(): int
        + get_iterations(): int
        + get_swarm(): ParticleSwarm
    }
    Optimization "1" --o "1" ParticleSwarm


    class ParticleSwarm{
        - float cognitive_coefficient
        - float inertia_coefficient
        - float social_coefficient
        - int particle_amount
        - list[Particle] particles
        - Position gbest
        + callable heuristic_f

        - __repr__() : str
        # initialize_particles_randomly(int bound)
        + update_gbest(): None

        + get_cognitive_coefficient(): float
        + get_inertia(): float
        + get_social_coefficient(): float
        + get_particles_amount(): int
        + get_gbest(): Vector
        + get_particles(): ~Particle~
        + get_heuristic(): callable
    }
    ParticleSwarm "1" o--"*" Particle

    class Particle{
        <!-- ? Are r_1 and r_2 chosen for every iteration or at each iteration? -->
        + dict color
        - float cognitive_coefficient
        - float inertia_coefficient
        - float social_coefficient
        - Heuristic heuristic
        - Position pbest
        - Position position
        - Velocity velocity
        - int index
        - bool has_gbest

        # update_pbest(position)
        # update_velocity(Position gbest)
        + initialize_randomly(int bound)
        
        + get_pbest()
        + get_position()
        + get_velocity()
        + get_heuristic()
        + get_index()

        + set_heuristic(Heuristic heuristic)
        + set_index(int index)
        + set_pbest(Position pbest)
        + set_position(Position position)
        + set_velocity(Velocity velocity)
    }
    Particle "1" o-- "1" Heuristic
    Particle "1" o-- "2" Position
    Particle "1" o-- "1" Velocity


    class Vector {
        # np.ndarray coordinates
        # int dimensions

        - __repr__() : str
        + initialize_randomly(float)
        # update()
        
        + get_coordinates() : np.ndarray
        + get_dimensions() : int
        + set_coordinates(coordinates: np.ndarray)
        + set_dimensions(dimensions: int)  
    }

    class Heuristic{
        <!-- ? Should the inherited attributes be kept in the child class?-->
        <!-- ? Should ndarrays be simply called arrays since the fact they are np has to do more with the implementation? -->
        # callable heuristic
        # update()
        + get_heuristic_f()
    }
    Heuristic "1" --|> "1" Vector

    class Position{
        # update(Velocity: velocity)
    }
    Position "1" --|> "1" Vector

    class Velocity {
        
        - __init__(int dimensions)
    }
    Velocity "1" --|> "1" Vector

```

### Complete Class Diagram

Includes `Data`, the class asociated to the database, and `GUI`, the class in charge of displaying and managing the graphic user interface.

``` mermaid
    classDiagram
    direction TB

    class Main {
        + GUI gui
        + Data database
        + ~Optimization~ optimization_history
        
        + get_last_xslx_file() str
        + get_version() str
    }

    %% ? Should inherited composition (or any) relations be stated

    Main "1" --* "1" Data
    Main "1" --o "*" Optimization
    Main "1" --* "1" GUI

    class GUI {
        - C tk.Tk() root
        - __init__(~Optimization~ optimization_history, str program_version)
        # tk.Frame root_frame
        - ~Optimization~ optimization_history
        # int window_height
        # window_width
        - <ExitMenu> exit_menu
        - <MainMenu> main_menu
        - <SelectMenu> select_menu
        - dict menus

        # change_menu(menu_name)
        # initialize_root(int width, int height, str title)
        + run()
    }

    GUI "1" --> "1" Color : uses (?)
    GUI "1" --o "*" Optimization
    GUI "1" --* "1" MainMenu
    GUI "1" --* "1" SelectMenu
    GUI "1" --* "1" ExitMenu

    class Color {
        + C str test1_bg
        + C str test2_bg
        + C str test3_bg
    }

    class FontName {
        + str button
        + str label
        + str title
    }

    class ExitMenu {
        + tk.Frame root
        - callable initialize_root
        - tk.PhotoImage image
        - tk.Label label
        - tk.Text text

        + display()
    }

    ExitMenu --> Color : "uses(?)"
    ExitMenu --> FontName : "uses(?)"

    class MainMenu {
        %% - __init__(tk.Frame parent_frame, callable initialize_window, callable change_menu, str program_version, int window_width, int window_height)
        # tk.Frame parent_frame
        + tk.Frame root
        - callable initialize_window
        # tk.Label title
        # int window_width
        # int window_height
        # int title_height
        # int bottom_frame_height
        - BottomFrame bottom_frame
        - OptionsFrame options_frame

        + display()
    }

    MainMenu "1" --* "1" BottomFrame
    MainMenu "1" --* "1" OptionsFrame
    MainMenu --> Color : "uses(?)"
    MainMenu --> FontName : "uses(?)"

    class BottomFrame {
        %%- __init__(tk.Frame parent_frame, int window_width, int window_height, int title_height, int bottom_frame_height, str program_version)
        - int window_width
        - int window_height
        - int title_height
        - int height
        - int pop_up_frame_height
        - BottomButton info_button
        - BottomButton help_button
        - tk.Label version_label
        # <PopUpFrame> pop_up_frames

        + display()
    }
    
    BottomFrame "1" --|> "1" tkFrame
    BottomFrame --> Color : "uses(?)"
    BottomFrame --> FontName : "uses(?)"
    BottomFrame "1" --* "2" BottomButton

    class BottomButton {
        - tk.PhotoImage image
        - tk.PhotoImage active_image
        - str bg
        - str abg
        - str cbg
        + PopUpFrame pop_up_frame

        - enter(tk.Event e)
        - leave(tk.Event e)
        - click(tk.Event e)
        - release(tk.Event event, <PopUpFrame> pop_up_frames)
        - bind_to_events(dict pop_up_frames)
        + display(<PopUpFrame> pop_up_frames, int row, int column, str sticky)
    }

    BottomButton "1" --|> "1" tkButton
    BottomButton --> Color : "uses(?)"
    BottomButton "1" --* "1" PopUpFrame

    class PopUpFrame {
        - str name
        - str text_to_insert
        - tk.Text text
        - tk.Button hide_button
        - tk.Scrollbar scrollbar
        - int width
        - int height
        - int y
        + bool visible

        - enter_hide_button(tk.Event e)
        - leave_hide_button(tk.Event e)
        - click_hide_button(tk.Event e)
        - release_hide_button(tk.Event e, PopUpFrame other_frame)
        - bind_hide_button(PopUpFrame other_frame)
        + display(PopUpFrame other_frame)
        + get_name() str
    }

    PopUpFrame "1" --|> "1" tkFrame
    PopUpFrame --> Color : "uses(?)"
    PopUpFrame --> FontName : "uses(?)"

    class OptionsFrame {
        - tk.Frame parent_frame
        + tk.Frame root
        - callable change_menu
        - tk.Label title
        - int window_width
        - int window_height
        - int title_height
        - int bottom_frame_height
        - dict button_parameters
        - OptionsButton create_button
        - OptionsButton select_button
        - OptionsButton exit_button
        - dict buttons

        +display()
    }

    OptionsFrame "1" --* "3" OptionsButton
    OptionsFrame --> Color : "uses(?)"
    OptionsFrame --> FontName : "uses(?)"
    OptionsFrame "1" --* "3" OptionsButton

    class OptionsButton{
        - tk.Frame parent_frame
        - callable callable
        - dict callable_args
        - tuple padx
        - tuple pady
        - str optim_button_fg
        - str optim_button_hbg
        - str optim_button_hcolor
        - str optim_button_abg
        - str optim_button_bg
        - str back_button_afg

        - enter(tk.Event e)
        - leave(tk.Event e)
        - click(tk.Event e)
        - release(tk.Event event, list<PopUpF> pop_up_frames)
        - bind_to_events(dict pop_up_frames)
        + grid_display(intPopUpFrame  pop_up_frames, row, int column, str sticky)
        + pack_display(str fill, str anchor)
    }

    OptionsButton "1" --|> "1" tkButton
    OptionsButton --> Color : "uses(?)"

    class CreateButton {
        - str text1
        - str text2
        - str __active_text
        - callable __callable1
        - callable __callable2

        + __init__(tk.Frame parent_frame, str text1, str text2, callable callable1, callable callable2, int padx, int pady)
        - _release(tk.Event event)
    }

    CreateButton "1" --|> "1" OptionsButton
    CreateButton --> Color : "uses"


    class CreateInput {
        - tk.Label __label
        - tk.StringVar input_value
        - tk.Entry entry
        - str __default_value

        + __init__(tk.Frame parent_frame, str default_value, str text, int width)
        - __select_text(tk.Event e) : None
        + grid(int label_row, int column, str sticky)
        + get_input() : str
    }

    CreateInput --> Color : "uses"
    CreateInput --> FontName : "uses"

    class CreateMenu {
        - tk.Button __run_view_button
        - tk.Button __reset_button
        - tk.Frame __buttons_frame
        - tk.Button __back_button
        - tk.Tk root
        - int __width
        - int __height
        - tk.Label __title
        - tk.Frame __inputs_frame

        + display_graph(str graph_type)
        + forget() : None
        + run_or_view_optimization(tk.Event e, bool create_optimization) : None
        - __create_contour_levels(list[float] levels_boundaries) : np.linspace
        - __create_x_y_values(int bound) : tuple[np.ndarray]
        - __create_fig(str option) : Figure
    }

    CreateMenu --> Color : "uses"
    CreateMenu --> FontName : "uses"

    class FunctionChoiceMenu {
        - list~str~ __options
        - callable __display_graph
        - tk.StringVar __choice
        - tk.OptionMenu __dropdown_menu
        - tk.Label __label

        + __init__(tk.Frame parent_frame, str text, list~str~ options, callable display_graph)
        - __trigger_graph_change(*args) : None
        + grid(int label_row, int column, str sticky) : None
    }

    FunctionChoiceMenu --> Color : "uses"
    FunctionChoiceMenu --> FontName : "uses"

    class SelectMenu {
        %% - __init__(tk.Frame parent_frame, callable initialize_window, callable change_menu, ~Optimization~ optimization_history, int window_width, int window_height)
        - ~Optimization~ optimization_history
        - int window_width
        - int window_height
        - int title_height
        - callable initialize_window
        + tk.Frame root
        - tk.Label title
        - tk.Canvas canvas
        - int container_frame_width
        - int container_frame_height
        - tk.Frame container_frame
        - int scrollbar_width
        - tk.Scrollbar scrollbar
        - BackButton back_button
        - tk.Label no_optimizations_label
        - OptionsButton create_optimization_button
        - ~OptimizationFrame~ optimization_frames

        - scroll_mouse_wheel(tk.Event event)
        + display()
        + forget()
    }

    SelectMenu --> Color : "uses(?)"
    SelectMenu --> FontName : "uses(?)
    SelectMenu "1" --* "1" OptionsButton
    SelectMenu "1" *-- "*" OptimizationFrame
    SelectMenu "1" --* "1" BackButton

    class BackButton {
        - tk.PhotoImage image
        - tk.PhotoImage active_image
        - int width
        - int height
        - callable change_menu
        - dict change_menu_args

        - enter(tk.Event e)
        - leave(tk.Event e)
        - click(tk.Event e)
        - release(tk.Event e)
        - bind_to_events()
        + display()
    }

    class ViewButton {
        - tk.Image __image
        - tk.Image __active_image
        - ViewFrame view_frame
        - callable __forget_select_menu

        + __init__(tk.Frame master, tk.Image image, tk.Image active_image, callable forget_select_menu, callable initialize_window, callable change_menu, Optimization optimization, str bg=Color.select_label_optim_bg, str fg=Color.select_label_optim_fg)
        - __enter(tk.Event e) : None
        - __leave(tk.Event e) : None
        - __click(tk.Event e) : None
        - __bind_to_events() : None
    }

    ViewButton --> Color : "uses"
    ViewButton "1" --* "1" ViewFrame
    ViewButton --> Optimization : "uses"

    class ViewFrame {
        - callable __initialize_window
        - Optimization __optimization
        - Figure __function_fig
        - tk.Label __title
        - BackButton __back_button

        + __init__(callable initialize_window, callable change_menu, Optimization optimization, Figure function_fig, str bg=Color.test3_bg) : ViewFrame
    }

    ViewFrame --> Color : "uses"
    ViewFrame --> FontName : "uses"
    ViewFrame --> BackButton : "contains"
    ViewFrame --> Optimization : "uses"
    ViewFrame --> Figure : "uses"

    class OptimizationFrame {
        %% - __init__(tk.Frame root, Optimization optimization, int width, int height, int separation, int scrollbar_width, int frame_index)
        - tk.Frame frame
        - int width
        - int height
        - int scrollbar_width
        - int separation
        - int index
        - dict widget_parameters
        - tk.Label Fontname_label
        - tk.Label function_label
        - tk.Label dimensions_label
        - tk.Label minima_indicator_label
        - tk.Label minima_value_label
        - tk.Label cognitive_coefficient_label
        - tk.Label num_particles_label
        - tk.Label social_coefficient_label
        - tk.PhotoImage preview_image
        - tk.PhotoImage preview_active_image
        - tk.Button preview_button
        - tk.Label inertia_coefficient_label
        - tk.Label iterations_label

        +display(int parent_width)
        + enter_preview_button(e)
        + click_preview_button(e)
        + leave_preview_button(e)
        + release_preview_button(e)    
    }

    class Optimization{
        - I Data data
        - float cognitive_coefficient
        - float inertia_coefficient
        - float social_coefficient
        - int dimensions
        - int iterations
        - int particle_amount
        - ParticleSwarm swarm

        - heuristic(Position position, int selection)
        - optimize()

        + get_dimensions(): int
        + get_index(): int
        + get_iterations(): int
        + get_swarm(): ParticleSwarm
    }
    Optimization "1" --o "1" ParticleSwarm
    Optimization "*" --o "1" Data

    class Data {
        - __init__(str excel_file_name)
        - <pd.DataFrame> particle_history
        - ~~int~~ gbest_history
        - int number_of_optimizations
        - str xlsx_name
        - str xlsx_path

        + append_gbest_indexes(list<int> optimization_gbest_indexes)
        + append_optimization(pd.DataFrame optimization_df)
        + create_spreadsheet()
        + print_optimization(int optimization_index)
        + get_particle_history() ~pd.DatscrollbaraFrame~
    }

    class ParticleSwarm{
        - float cognitive_coefficient
        - float inertia_coefficient
        - float social_coefficient
        - int particle_amount
        - list[Particle] particles
        - Position gbest
        + callable heuristic_f

        - __repr__() : str
        # initialize_particles_randomly(int bound)
        + update_gbest(): None

        + get_cognitive_coefficient(): float
        + get_inertia(): float
        + get_social_coefficient(): float
        + get_particles_amount(): int
        + get_gbest(): Vector
        + get_particles(): ~Particle~
        + get_heuristic(): callable
    }
    ParticleSwarm "1" o--"*" Particle

    class Particle{
        <!-- ? Are r_1 and r_2 chosen for every iteration or at each iteration? -->
        + dict color
        - float cognitive_coefficient
        - float inertia_coefficient
        - float social_coefficient
        - Heuristic heuristic
        - Position pbest
        - Position position
        - Velocity velocity
        - int index
        - bool has_gbest

        # update_pbest(position)
        # update_velocity(Position gbest)
        + initialize_randomly(int bound)
        
        + get_pbest()
        + get_position()
        + get_velocity()
        + get_heuristic()
        + get_index()

        + set_heuristic(Heuristic heuristic)
        + set_index(int index)
        + set_pbest(Position pbest)
        + set_position(Position position)
        + set_velocity(Velocity velocity)
    }
    Particle "1" o-- "1" Heuristic
    Particle "1" o-- "2" Position
    Particle "1" o-- "1" Velocity


    class Vector {
        # np.ndarray coordinates
        # int dimensions

        - __repr__() : str
        + initialize_randomly(float)
        # update()
        
        + get_coordinates() : np.ndarray
        + get_dimensions() : int
        + set_coordinates(coordinates: np.ndarray)
        + set_dimensions(dimensions: int)  
    }

    class Heuristic{
        <!-- ? Should the inherited attributes be kept in the child class?-->
        <!-- ? Should ndarrays be simply called arrays since the fact they are np has to do more with the implementation? -->
        # callable heuristic
        # update()
        + get_heuristic_f()
    }
    Heuristic "1" --|> "1" Vector

    class Position{
        # update(Velocity: velocity)
    }
    Position "1" --|> "1" Vector

    class Velocity {
        
        - __init__(int dimensions)
    }
    Velocity "1" --|> "1" Vector

    ```
