Skip to content

Commit

Permalink
Merge pull request #236 from hyeok9855/main
Browse files Browse the repository at this point in the history
[Feat] add GFACS and replace PyVRP with HGS for CVRP local search
  • Loading branch information
hyeok9855 authored Feb 18, 2025
2 parents 643ef99 + 9261d20 commit 0e69192
Show file tree
Hide file tree
Showing 25 changed files with 1,380 additions and 434 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,7 @@ local_nbrun*

# osx meta files
.DS_Store
outputs/
outputs/

# HGS-CVRP for cvrp local search
HGS-CVRP/
61 changes: 43 additions & 18 deletions configs/experiment/routing/deepaco.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,62 @@ logger:
name: deepaco-${env.name}${env.generator_params.num_loc}

model:
batch_size: 512
val_batch_size: 1000
test_batch_size: 1000
train_data_size: 128_000
val_data_size: 10_000
test_data_size: 10_000
batch_size: 20
val_batch_size: 20
test_batch_size: 20
train_data_size: 400
val_data_size: 20
test_data_size: 100
optimizer: "AdamW"
optimizer_kwargs:
lr: 1e-4
lr: 5e-4
weight_decay: 0
lr_scheduler:
"MultiStepLR"
"CosineAnnealingLR"
lr_scheduler_kwargs:
milestones: [25, 35]
gamma: 0.1
T_max: 50
eta_min: 1e-5
metrics:
test:
- reward_000
- reward_002
- reward_009 # since n_iterations["text"] = 10
train_with_local_search: True
ls_reward_aug_W: 0.99

policy_kwargs:
n_ants:
train: 50
val: 20
test: 20
train: 30
val: 30
test: 100
n_iterations:
train: 1 # unused value
val: 20
test: 20
val: 5
test: 10
temperature: 1.0
top_p: 0.0
top_k: 0
start_node: null
multistart: False
k_sparse: 5 # this should be adjusted based on the `num_loc` value

aco_kwargs:
alpha: 1.0
beta: 1.0
decay: 0.95

use_local_search: True
use_nls: True
n_perturbations: 5
local_search_params:
max_iterations: 1000
perturbation_params:
max_iterations: 20

trainer:
max_epochs: 40
max_epochs: 50
gradient_clip_val: 3.0
precision: "bf16-mixed"
device:
- 0

seed: 1234
seed: 1234
84 changes: 84 additions & 0 deletions configs/experiment/routing/gfacs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# @package _global_

defaults:
- override /model: gfacs.yaml
- override /env: tsp.yaml
- override /callbacks: default.yaml
- override /trainer: default.yaml
- override /logger: wandb.yaml

env:
generator_params:
num_loc: 50

logger:
wandb:
project: "rl4co"
tags: ["gfacs", "${env.name}"]
group: ${env.name}${env.generator_params.num_loc}
name: gfacs-${env.name}${env.generator_params.num_loc}

model:
batch_size: 20
val_batch_size: 20
test_batch_size: 20
train_data_size: 400
val_data_size: 20
test_data_size: 100
optimizer: "AdamW"
optimizer_kwargs:
lr: 5e-4
weight_decay: 0
lr_scheduler:
"CosineAnnealingLR"
lr_scheduler_kwargs:
T_max: 50
eta_min: 1e-5
metrics:
test:
- reward_000
- reward_002
- reward_009 # since n_iterations["text"] = 10
train_with_local_search: True
alpha_min: 0.5
alpha_max: 1.0
alpha_flat_epochs: 5
beta_min: 100
beta_max: 500
beta_flat_epochs: 5

policy_kwargs:
n_ants:
train: 30
val: 30
test: 100
n_iterations:
train: 1 # unused value
val: 5
test: 10
temperature: 1.0
top_p: 0.0
top_k: 0
multistart: False
k_sparse: 5 # this should be adjusted based on the `num_loc` value

aco_kwargs:
alpha: 1.0 # This alpha is different from the alpha in the model
beta: 1.0 # This beta is different from the beta in the model
decay: 0.95
use_local_search: True
use_nls: True
n_perturbations: 5
local_search_params:
max_iterations: 1000
perturbation_params:
max_iterations: 20

trainer:
max_epochs: 50
gradient_clip_val: 3.0
precision: "bf16-mixed"
devices:
- 0

seed: 1234
2 changes: 1 addition & 1 deletion configs/model/deepaco.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
_target_: rl4co.models.DeepACO

baseline: "exponential"
baseline: "no"
3 changes: 3 additions & 0 deletions configs/model/gfacs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
_target_: rl4co.models.GFACS

baseline: "no"
20 changes: 20 additions & 0 deletions rl4co/envs/routing/cvrp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
As local search in CVRP, we adopted the SWAP* algorithm by Vidal et al. [1, 2] ([repo](https://github.com/vidalt/HGS-CVRP)). Specifically, we use the modified version of code provided by [DeepACO](https://github.com/henry-yeh/DeepACO/tree/main/cvrp_nls/HGS-CVRP-main), which we uploaded to [https://github.com/ai4co/HGS-CVRP](https://github.com/ai4co/HGS-CVRP) for convenience.


### Installation

```bash
cd rl4co/envs/routing/cvrp
git clone [email protected]:ai4co/HGS-CVRP.git
cd HGS-CVRP
bash build.sh
```

### References

[1] Vidal, T., Crainic, T. G., Gendreau, M., Lahrichi, N., Rei, W. (2012). A hybrid genetic algorithm for multidepot and periodic vehicle routing problems. Operations Research, 60(3), 611-624.

[2] Vidal, T. (2022). Hybrid genetic search for the CVRP: Open-source implementation and SWAP* neighborhood. Computers & Operations Research, 140, 105643.

[3] Ye, H., Wang J., Cao, Z., Liang, H., Li, Y. (2023).
DeepACO: Neural-enhanced ant systems for combinatorial optimization. Advances in neural information processing systems (NeurIPS) 36 (2023): 43706-43728.
9 changes: 4 additions & 5 deletions rl4co/envs/routing/cvrp/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@

try:
from .local_search import local_search
except Exception:
# In case some dependencies are not installed (e.g., pyvrp)
except: # In case when we fail to build HGS
local_search = None
from .render import render

Expand Down Expand Up @@ -133,7 +132,7 @@ def _reset(
@staticmethod
def get_action_mask(td: TensorDict) -> torch.Tensor:
# For demand steps_dim is inserted by indexing with id, for used_capacity insert node dim for broadcasting
exceeds_cap = td["demand"] + td["used_capacity"] > td["vehicle_capacity"]
exceeds_cap = td["demand"] + td["used_capacity"] > td["vehicle_capacity"] + 1e-5

# Nodes that cannot be visited are already visited or too much demand to be served now
mask_loc = td["visited"][..., 1:].to(exceeds_cap.dtype) | exceeds_cap
Expand Down Expand Up @@ -182,7 +181,7 @@ def check_solution_validity(td: TensorDict, actions: torch.Tensor):
# Cannot use less than 0
used_cap[used_cap < 0] = 0
assert (
used_cap <= td["vehicle_capacity"] + 1e-5
used_cap <= td["vehicle_capacity"][:, 0] + 1e-5
).all(), "Used more than capacity"

@staticmethod
Expand Down Expand Up @@ -257,7 +256,7 @@ def replace_selected_actions(
def local_search(td: TensorDict, actions: torch.Tensor, **kwargs) -> torch.Tensor:
assert (
local_search is not None
), "Cannot import local_search module. Check if `pyvrp` is installed."
), "Cannot import local_search module. Check `rl4co/envs/routing/cvrp/README.md` for instructions to build HGS."
return local_search(td, actions, **kwargs)

@staticmethod
Expand Down
Loading

0 comments on commit 0e69192

Please sign in to comment.