Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,26 @@ jobs:

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

deploy-docs:
runs-on: ubuntu-latest
needs: build-and-publish
permissions:
contents: write

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements_docs.txt

- name: Deploy MkDocs to GitHub Pages
run: mkdocs gh-deploy --force
Binary file added docs/assets/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/logo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# RKO - Random-Key Optimizer (Python Framework)

Welcome to the documentation for the **RKO (Random-Key Optimizer)**.

The Random-Key Optimizer is a versatile and efficient metaheuristic framework designed for a wide range of combinatorial optimization problems. Its core paradigm is the encoding of solutions as vectors of random keys—real numbers uniformly distributed in the interval [0, 1). This representation maps the discrete, and often complex, search space of a combinatorial problem to a continuous n-dimensional unit hypercube.

## Installation

You can download the package using pip:
```bash
pip install rko
```

## Getting Started

To learn how to use RKO, please refer to the Github repository's [README](https://github.com/RKO-solver/RKO_Python) which details how to create your own `Environment` and run the `RKO` solver.

## API Reference

Explore the detailed API Reference using the navigation menu:

- **[RKO](reference/rko.md)**: The core solver class, encapsulating all search operators and metaheuristics.
- **[Environment](reference/environment.md)**: The abstract base class (`RKOEnvAbstract`) enabling the integration of problem-specific logic.
- **[LogStrategy](reference/logstrategy.md)**: Mechanisms for logging search progress.
- **[Plots](reference/plots.md)**: Visualization utilities for convergence and other metrics.

---

### Maintainers
- Felipe Silvestre Cardoso Roberto - [Linkedin](https://www.linkedin.com/in/felipesilvestrecr/)
- João Victor Assaoka Ribeiro - [Linkedin](https://www.linkedin.com/in/assaoka/)
3 changes: 3 additions & 0 deletions docs/reference/environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Environment

::: rko.Environment
3 changes: 3 additions & 0 deletions docs/reference/logstrategy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# LogStrategy

::: rko.LogStrategy
3 changes: 3 additions & 0 deletions docs/reference/plots.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Plots

::: rko.Plots
3 changes: 3 additions & 0 deletions docs/reference/rko.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# RKO

::: rko.RKO
29 changes: 29 additions & 0 deletions docs/stylesheets/extra.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
:root {
/* Cores principais baseadas no verde da UNIFESP (#225a37) */
--md-primary-fg-color: #225a37;
--md-primary-fg-color--light: #347d4e;
--md-primary-fg-color--dark: #13331f;
--md-primary-bg-color: #ffffff;
--md-primary-bg-color--light: #ffffffcc;

/* Cores de destaque (accent) usando o mesmo verde */
--md-accent-fg-color: #225a37;
--md-accent-fg-color--transparent: #225a37f2;
--md-accent-bg-color: #ffffff;
--md-accent-bg-color--light: #ffffffcc;
}

[data-md-color-scheme="slate"] {
/* Header verde no modo esculo com texto branco */
--md-primary-fg-color: #225a37;
--md-primary-fg-color--light: #347d4e;
--md-primary-fg-color--dark: #184529;
--md-primary-bg-color: #ffffff;
--md-primary-bg-color--light: #ffffffcc;

/* Cor de destaque mais clara no escuro para melhor contraste */
--md-accent-fg-color: #4ea86e;
--md-accent-fg-color--transparent: #4ea86e1a;
--md-accent-bg-color: #000000;
--md-accent-bg-color--light: #000000cc;
}
126 changes: 126 additions & 0 deletions docs/tutorials/knapsack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Knapsack Problem (KP) Tutorial

This tutorial demonstrates how to solve the classic 0/1 Knapsack Problem using the **RKO** framework. The goal of this problem is to pack a set of items, with given weights and profits, into a knapsack with limited capacity to maximize the total profit.

The environment requires reading a problem instance from a file, creating a binary solution based on threshold decoding, and computing the total profit with a penalty if the capacity is exceeded.

## 1. Defining the Environment

Below is the complete implementation of the `KnapsackProblem` class, which extends the `RKOEnvAbstract` base class.

```python
import numpy as np
import os
import sys

from rko import RKO, RKOEnvAbstract, FileLogger, HistoryPlotter

class KnapsackProblem(RKOEnvAbstract):
"""
An implementation of the Knapsack Problem environment for the RKO solver.
"""
def __init__(self, instance_path: str):
super().__init__() # Initialize the abstract base class
print(f"Loading Knapsack Problem instance from: {instance_path}")

self.instance_name = instance_path.split('/')[-1]
self.LS_type: str = 'Best' # Options: 'Best' or 'First'
self.dict_best: dict = {"Best": [149]}

# Internal loading method
self._load_data(instance_path)

# Solution size represents the number of randomly generated keys
self.tam_solution = self.n_items

self.save_q_learning_report = False

# Metaheuristics hyperparameters
self.BRKGA_parameters = {'p': [100, 50], 'pe': [0.20, 0.15], 'pm': [0.05], 'rhoe': [0.70]}
self.SA_parameters = {'SAmax': [10, 5], 'alphaSA': [0.5, 0.7], 'betaMin': [0.01, 0.03], 'betaMax': [0.05, 0.1], 'T0': [10]}
self.ILS_parameters = {'betaMin': [0.10, 0.5], 'betaMax': [0.20, 0.15]}
self.VNS_parameters = {'kMax': [5, 3], 'betaMin': [0.05, 0.1]}
self.PSO_parameters = {'PSize': [100, 50], 'c1': [2.05], 'c2': [2.05], 'w': [0.73]}
self.GA_parameters = {'sizePop': [100, 50], 'probCros': [0.98], 'probMut': [0.005, 0.01]}
self.LNS_parameters = {'betaMin': [0.10], 'betaMax': [0.30], 'TO': [100], 'alphaLNS': [0.95, 0.9]}

def _load_data(self, instance_path: str):
"""
Loads the knapsack problem data from a text file.
The file has format:
<number of items> <knapsack capacity>
<profit item 1> <weight item 1>
<profit item 2> <weight item 2>
...
"""
with open(instance_path, 'r') as f:
lines = f.readlines()
self.n_items, self.capacity = map(int, lines[0].strip().split())

self.profits = []
self.weights = []

for line in lines[1:]:
if line.strip():
p, w = map(int, line.strip().split())
self.profits.append(p)
self.weights.append(w)

def decoder(self, keys: np.ndarray) -> list[int]:
"""
Decodes a random-key vector into a knapsack solution.
An item is included if its corresponding key is > 0.5.
"""
# A solution is a binary list where 1 means the item is in the knapsack
solution = [1 if key > 0.5 else 0 for key in keys]
return solution

def cost(self, solution: list[int], final_solution: bool = False) -> float:
"""
Calculates the cost of the knapsack solution.
Since this is a maximization problem, the cost is the negative of the total profit.
A penalty is applied for exceeding the knapsack's capacity.
"""
total_profit = 0
total_weight = 0
for i, item_included in enumerate(solution):
if item_included:
total_profit += self.profits[i]
total_weight += self.weights[i]

# Apply a heavy penalty for infeasible solutions (exceeding capacity)
if total_weight > self.capacity:
penalty = 100000 * (total_weight - self.capacity)
total_profit -= penalty

# The RKO framework assumes a minimization problem by default,
# so we return the negative of the profit.
return -total_profit
```

### Problem Constraint Penalty
Often, the search process requires exploring the infeasible domain space to find the optimal global structure. Penalties like `100000 * (total_weight - self.capacity)` severely lower the score, deterring solvers while giving feedback on how close the solution was to feasibility. Notice that the score function returns `-total_profit`, since RKO always minimizes globally.

## 2. Setting Up and Optimizing

With the Environment mapped, simply set the logger up, start the search methods, and visualize the output graph when finished.

```python
if __name__ == "__main__":
current_directory = os.path.dirname(os.path.abspath(__file__))

# 1. Instantiate the Instance Environment
env = KnapsackProblem(os.path.join(current_directory, 'kp50.txt'))

# 2. Add Logger configuration
logger = FileLogger(os.path.join(current_directory, 'results.txt'), reset=True)

# 3. Solver Initiation
solver = RKO(env, logger=logger)

# 4. Solves with all heuristics combined over 30s limit overall
solver.solve(time_total=30, brkga=1, lns=1, vns=1, ils=1, sa=1, pso=1, ga=1, runs=2)

# 5. Output Graphics
HistoryPlotter.plot_convergence(os.path.join(current_directory, 'results.txt'), run_number=1).show()
```
131 changes: 131 additions & 0 deletions docs/tutorials/tsp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Travelling Salesperson Problem (TSP) Tutorial

This tutorial demonstrates how to solve the Travelling Salesperson Problem (TSP) using the **RKO** framework. TSP is a classic combinatorial optimization problem where the goal is to find the shortest possible route that visits every city exactly once and returns to the origin city.

In our implementation, the environment handles generating a random TSP instance, calculating distances, and decoding random keys into a valid tour.

## 1. Creating the Environment

Below is the complete implementation of the `TSPProblem` environment. It extends the `RKOEnvAbstract` class, defining the problem's specifics, such as city generation, random key decoding, and the cost function.

```python
import numpy as np
import os
import random
import matplotlib.pyplot as plt
from rko import RKO, RKOEnvAbstract, check_env, FileLogger, HistoryPlotter

class TSPProblem(RKOEnvAbstract):
"""
An implementation of the Traveling Salesperson Problem (TSP) environment for the RKO solver.
This class generates a random instance upon initialization.
"""
def __init__(self, num_cities: int = 20):
super().__init__()
print(f"Generating a random TSP instance with {num_cities} cities.")

self.num_cities = num_cities
self.instance_name = f"TSP_{num_cities}_cities"
self.LS_type: str = 'Best'
self.dict_best: dict = {}

self.save_q_learning_report = False

# Generate city coordinates and the distance matrix
self.cities = self._generate_cities(num_cities)
self.distance_matrix = self._calculate_distance_matrix()

# Solution size represents the number of randomly generated keys
self.tam_solution = self.num_cities

# Customizing parameters for metaheuristics (example)
self.BRKGA_parameters = {'p': [100, 50], 'pe': [0.20, 0.15], 'pm': [0.05], 'rhoe': [0.70]}
self.SA_parameters = {'SAmax': [10, 5], 'alphaSA': [0.5, 0.7], 'betaMin': [0.01, 0.03], 'betaMax': [0.05, 0.1], 'T0': [10]}
self.ILS_parameters = {'betaMin': [0.10, 0.5], 'betaMax': [0.20, 0.15]}
self.VNS_parameters = {'kMax': [5, 3], 'betaMin': [0.05, 0.1]}
self.PSO_parameters = {'PSize': [100, 50], 'c1': [2.05], 'c2': [2.05], 'w': [0.73]}
self.GA_parameters = {'sizePop': [100, 50], 'probCros': [0.98], 'probMut': [0.005, 0.01]}
self.LNS_parameters = {'betaMin': [0.10], 'betaMax': [0.30], 'TO': [100], 'alphaLNS': [0.95, 0.9]}

def _generate_cities(self, num_cities: int) -> np.ndarray:
"""Generates random (x, y) coordinates for each city in a 100x100 grid."""
return np.random.rand(num_cities, 2) * 100

def _calculate_distance_matrix(self) -> np.ndarray:
"""Computes the Euclidean distance between every pair of cities."""
num_cities = len(self.cities)
dist_matrix = np.zeros((num_cities, num_cities))
for i in range(num_cities):
for j in range(i, num_cities):
dist = np.linalg.norm(self.cities[i] - self.cities[j])
dist_matrix[i, j] = dist_matrix[j, i] = dist
return dist_matrix

def decoder(self, keys: np.ndarray) -> list[int]:
"""
Decodes a random-key vector into a TSP tour.
The tour is determined by the sorted order of the keys.
"""
tour = np.argsort(keys)
return tour.tolist()

def cost(self, solution: list[int], final_solution: bool = False) -> float:
"""
Calculates the total distance of a given TSP tour.
The RKO framework will minimize this value.
"""
total_distance = 0
num_cities_in_tour = len(solution)
for i in range(num_cities_in_tour):
from_city = solution[i]
to_city = solution[(i + 1) % num_cities_in_tour]
total_distance += self.distance_matrix[from_city, to_city]

return total_distance
```

### Decoding Strategy
In the `decoder` method, `np.argsort()` takes the randomly generated variables (in `[0, 1)`) and returns the indices representing their sorted order. This creates a valid permutation of cities automatically.

## 2. Running the RKO Solver

Once the environment is set up, you can instantiate the `RKO` solver and initiate the search. This setup uses multiple metaheuristics concurrently.

```python
if __name__ == "__main__":
current_directory = os.path.dirname(os.path.abspath(__file__))

# 1. Instantiate the problem environment (50 cities).
env = TSPProblem(num_cities=50)
check_env(env) # Verify the environment implementation is valid!

# 2. Setup the logger
logger = FileLogger(os.path.join(current_directory, 'results.txt'), reset=True)

# 3. Instantiate the RKO solver.
solver = RKO(env=env, logger=logger)

# 4. Run the solver for 10 seconds with selected metaheuristics
final_cost, final_solution, time_to_best = solver.solve(
time_total=10,
runs=1,
vns=1,
ils=1,
sa=1
)

solution = env.decoder(final_solution)

# 5. Output and logging
HistoryPlotter.plot_convergence(os.path.join(current_directory, 'results.txt'), run_number=1, title="TSP Convergence").show()

print("\n" + "="*30)
print(" FINAL RESULTS ")
print("="*30)
print(f"Instance Name: {env.instance_name}")
print(f"Best Tour Cost Found: {final_cost:.4f}")
print(f"Time to Find Best Solution: {time_to_best}s")
print(f"Best Tour (City Sequence): {solution}")
```

This will run VNS, ILS and SA in parallel, sharing solutions between all of them. The `HistoryPlotter` can be used to generate beautiful convergence graphs visualizing the search trajectory for the best value.
Loading