Skip to content

Add ci testing #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8f0b36a
Add first testing file attempt
Laurent-Van-Miegroet Apr 5, 2024
f6b75c9
Remove dependency on gym
Laurent-Van-Miegroet Apr 5, 2024
f900a64
Increase/define timeout and set fail-fast to false
Laurent-Van-Miegroet Apr 5, 2024
9e166b8
Fix False syntax
Laurent-Van-Miegroet Apr 5, 2024
035d7c4
Remove fail-fast
Laurent-Van-Miegroet Apr 5, 2024
0fdfb77
test only with python 3.10 to debug
Laurent-Van-Miegroet Apr 5, 2024
1524c1b
add flake8
Laurent-Van-Miegroet Apr 5, 2024
1e87d3e
Allow flake8 to fail
Laurent-Van-Miegroet Apr 5, 2024
5ca8486
Add python 3.11 testing back
Laurent-Van-Miegroet Apr 5, 2024
4cb80e1
add more linting checks
Laurent-Van-Miegroet Apr 5, 2024
191c180
seperate linting and testing
Laurent-Van-Miegroet Apr 5, 2024
b5c57bd
remove testing from linting part + create a sequence in CI
Laurent-Van-Miegroet Apr 5, 2024
affbf08
Merge branch 'main' into add-ci-testing
Laurent-Van-Miegroet Apr 5, 2024
dbe1c6e
Remove black from requirements
Laurent-Van-Miegroet Apr 5, 2024
c287f2e
add gitignore and update requirements.txt
Laurent-Van-Miegroet Apr 5, 2024
75ccd4d
test all methods
Laurent-Van-Miegroet Apr 5, 2024
24c0e18
allow test to fail to proceed for next one
Laurent-Van-Miegroet Apr 5, 2024
2b912a4
unactivate basic heuristic
Laurent-Van-Miegroet Apr 5, 2024
2fed803
Add CI badge
Laurent-Van-Miegroet Apr 5, 2024
cd15647
fix typo in yml file name
Laurent-Van-Miegroet Apr 5, 2024
76878b3
Add python badges
Laurent-Van-Miegroet Apr 5, 2024
ce12239
fix python badge
Laurent-Van-Miegroet Apr 5, 2024
415113d
add license badge
Laurent-Van-Miegroet Apr 5, 2024
b0072fb
fix license link
Laurent-Van-Miegroet Apr 5, 2024
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
85 changes: 85 additions & 0 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Testing

on: [push]

jobs:
linting:

runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
matrix:
python-version: ["3.10"]

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: 'pip' # caching pip dependencie
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
pip install -r requirements_dev.txt
- name: Lint with flake8
continue-on-error: true
run: |
flake8 . --count --max-complexity=15 --max-line-length=127 --statistics
- name: Lint with pylint
continue-on-error: true
run: |
find . -type f -name "*.py" | xargs pylint
- name: Lint with black
continue-on-error: true
run: |
black --check .
- name: Lint with isort
continue-on-error: true
run: |
isort . --check-only
- name: Check typing
continue-on-error: true
run: |
mypy .

testing:
# add needs to create sequence
needs: [linting]
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
matrix:
python-version: ["3.10","3.11"]

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: 'pip' # caching pip dependencie
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
pip install -r requirements.txt
- name: Run Basic Heuristic
run: |
#python run_basic_heuristics.py
- name: Run FJSP DRL
run: |
python run_FJSP_DRL.py
- name: Run GA
run: |
python run_genetic_algorithm.py
- name: Run MILP
run: |
python run_milp.py
- name: Run OR-tools
run: |
python run_or_tools.py
- name: Run Dispatching rules
run: |
python run_dispatching_rules.py


1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
![python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20-blue)
![Testing](https://github.com/ai-for-decision-making-tue/Job_Shop_Scheduling_Benchmark_Environments_and_Instances/actions/workflows/testing.yml/badge.svg)
[![License: GPLv3](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/ai-for-decision-making-tue/Job_Shop_Scheduling_Benchmark_Environments_and_Instances/blob/main/LICENSE)


# Job Shop Scheduling Benchmark: Environments and Instances for Learning and Non-learning Methods
Welcome to the **Job Shop Scheduling Benchmark**

Expand Down
Binary file modified requirements.txt
Binary file not shown.
4 changes: 4 additions & 0 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
flake8==7.0.0
black==24.3.0
mypy==1.9.0
pylint==3.1.0
219 changes: 219 additions & 0 deletions solutions/FJSP_DRL/train.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# GITHUB REPO: https://github.com/songwenas12/fjsp-drl

# Code based on the paper:
# "Flexible Job Shop Scheduling via Graph Neural Network and Deep Reinforcement Learning"
# by Wen Song, Xinyang Chen, Qiqiang Li and Zhiguang Cao
# Presented in IEEE Transactions on Industrial Informatics, 2023.
# Paper URL: https://ieeexplore.ieee.org/document/9826438

import copy
import toml
import sys
import random
import time
import logging
from collections import deque

import gym
import pandas as pd
from pathlib import Path
import torch
import numpy as np
from visdom import Visdom
import os
import argparse
import os
cwd = os.getcwd()
cwd = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(cwd)
grandparent_dir = os.path.dirname(parent_dir)
sys.path.extend([parent_dir, grandparent_dir])

import PPO_model
from env import CaseGenerator
from validate import validate, get_validate_env
from solutions.helper_functions import load_parameters

logging.basicConfig(level=logging.INFO)

base_path = Path(__file__).resolve().parents[2]
PARAM_FILE = base_path / "configs" / "FJSP_DRL.toml"

def setup_seed(seed):
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
np.random.seed(seed)
random.seed(seed)
torch.backends.cudnn.deterministic = True


def initialize_device(parameters: dict) -> torch.device:
device_str = "cpu"
if parameters['train_parameters']['device'] == "cuda":
device_str = "cuda:0" if torch.cuda.is_available() else "cpu"
return torch.device(device_str)


def main(param_file: str):
# PyTorch initialization
# gpu_tracker = MemTracker() # Used to monitor memory (of gpu)

try:
parameters = load_parameters(param_file)
except FileNotFoundError:
logging.error(f"Parameter file {param_file} not found.")
return

device = initialize_device(parameters)
logging.info(f"Using device {device}")
# Configure PyTorch's default device
torch.set_default_tensor_type('torch.cuda.FloatTensor' if device.type == 'cuda' else 'torch.FloatTensor')
if device.type == 'cuda':
torch.cuda.set_device(device)
print("PyTorch device: ", device.type)
torch.set_printoptions(precision=None, threshold=np.inf, edgeitems=None, linewidth=None, profile=None,
sci_mode=False)

# Load config and init objects
with open(param_file, 'r') as load_f:
load_dict = toml.load(load_f)
env_paras = load_dict["env_parameters"]
model_paras = load_dict["model_parameters"]
train_paras = load_dict["train_parameters"]
env_paras["device"] = device
model_paras["device"] = device
env_valid_paras = copy.deepcopy(env_paras)
env_valid_paras["batch_size"] = env_paras["valid_batch_size"]
model_paras["actor_in_dim"] = model_paras["out_size_ma"] * 2 + model_paras["out_size_ope"] * 2
model_paras["critic_in_dim"] = model_paras["out_size_ma"] + model_paras["out_size_ope"]

num_jobs = env_paras["num_jobs"]
num_mas = env_paras["num_mas"]
opes_per_job_min = int(num_mas * 0.8)
opes_per_job_max = int(num_mas * 1.2)

memories = PPO_model.Memory()
model = PPO_model.PPO(model_paras, train_paras, num_envs=env_paras["batch_size"])
env_valid = get_validate_env(env_valid_paras, train_paras) # Create an environment for validation
maxlen = 1 # Save the best model
best_models = deque()
makespan_best = float('inf')

# Use visdom to visualize the training process
is_viz = train_paras["viz"]
if is_viz:
viz = Visdom(env=train_paras["viz_name"])

# Generate data files and fill in the header
str_time = time.strftime("%Y%m%d_%H%M%S", time.localtime(time.time()))
save_path = './save/train_{0}'.format(str_time)
os.makedirs(save_path)
# Training curve storage path (average of validation set)
writer_ave = pd.ExcelWriter('{0}/training_ave_{1}.xlsx'.format(save_path, str_time))
# Training curve storage path (value of each validating instance)
writer_100 = pd.ExcelWriter('{0}/training_100_{1}.xlsx'.format(save_path, str_time))
valid_results = []
valid_results_100 = []
data_file = pd.DataFrame(np.arange(10, 1010, 10), columns=["iterations"])
data_file.to_excel(writer_ave, sheet_name='Sheet1', index=False)
writer_ave.close()
data_file = pd.DataFrame(np.arange(10, 1010, 10), columns=["iterations"])
data_file.to_excel(writer_100, sheet_name='Sheet1', index=False)
writer_100.close()

# Start training iteration
start_time = time.time()
env = None
for i in range(1, train_paras["max_iterations"] + 1):
# Replace training instances every x iteration (x = 20 in paper)
if (i - 1) % train_paras["parallel_iter"] == 0:
# \mathcal{B} instances use consistent operations to speed up training
nums_ope = [random.randint(opes_per_job_min, opes_per_job_max) for _ in range(num_jobs)]
case = CaseGenerator(num_jobs, num_mas, opes_per_job_min, opes_per_job_max, nums_ope=nums_ope)
env = gym.make('fjsp-v0', case=case, env_paras=env_paras)
print('num_job: ', num_jobs, '\tnum_mas: ', num_mas, '\tnum_opes: ', sum(nums_ope))

# Get state and completion signal
state = env.state
done = False
dones = env.done_batch
last_time = time.time()

# Schedule in parallel
while ~done:
with torch.no_grad():
actions = model.policy_old.act(state, memories, dones)
state, rewards, dones = env.step(actions)
done = dones.all()
memories.rewards.append(rewards)
memories.is_terminals.append(dones)
# gpu_tracker.track() # Used to monitor memory (of gpu)
print("spend_time: ", time.time() - last_time)

# Verify the solution
gantt_result = env.validate_gantt()[0]
if not gantt_result:
print("Scheduling Error!!!!!!")
# print("Scheduling Finish")
env.reset()

# if iter mod x = 0 then update the policy (x = 1 in paper)
if i % train_paras["update_timestep"] == 0:
loss, reward = model.update(memories, env_paras, train_paras)
print("reward: ", '%.3f' % reward, "; loss: ", '%.3f' % loss)
memories.clear_memory()
if is_viz:
viz.line(X=np.array([i]), Y=np.array([reward]),
win='window{}'.format(0), update='append', opts=dict(title='reward of envs'))
viz.line(X=np.array([i]), Y=np.array([loss]),
win='window{}'.format(1), update='append', opts=dict(title='loss of envs')) # deprecated

# if iter mod x = 0 then validate the policy (x = 10 in paper)
if i % train_paras["save_timestep"] == 0:
print('\nStart validating')
# Record the average results and the results on each instance
vali_result, vali_result_100 = validate(env_valid_paras, env_valid, model.policy_old)
valid_results.append(vali_result.item())
valid_results_100.append(vali_result_100)

# Save the best model
if vali_result < makespan_best:
makespan_best = vali_result
if len(best_models) == maxlen:
delete_file = best_models.popleft()
os.remove(delete_file)
save_file = '{0}/save_best_{1}_{2}_{3}.pt'.format(save_path, num_jobs, num_mas, i)
best_models.append(save_file)
torch.save(model.policy.state_dict(), save_file)

if is_viz:
viz.line(
X=np.array([i]), Y=np.array([vali_result.item()]),
win='window{}'.format(2), update='append', opts=dict(title='makespan of valid'))

# Save the data of training curve to files
data = pd.DataFrame(np.array(valid_results).transpose(), columns=["res"])
data.to_excel(writer_ave, sheet_name='Sheet1', index=False, startcol=1)
writer_ave.save()
writer_ave.close()
column = [i_col for i_col in range(100)]
data = pd.DataFrame(np.array(torch.stack(valid_results_100, dim=0).to('cpu')), columns=column)
data.to_excel(writer_100, sheet_name='Sheet1', index=False, startcol=1)
writer_100.save()
writer_100.close()

print("total_time: ", time.time() - start_time)


if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Train FJSP_DRL")
parser.add_argument(
"config_file",
metavar='-f',
type=str,
nargs="?",
default=PARAM_FILE,
help="path to config JSON",
)
args = parser.parse_args()
main(param_file=args.config_file)