Skip to content

Commit

Permalink
Release 2.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
mcfadd authored Jul 2, 2020
2 parents d2b61b4 + 71b3974 commit 94696af
Show file tree
Hide file tree
Showing 23 changed files with 509 additions and 347 deletions.
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ workflows:
only: /.*/
- publish-github-release:
name: "Publish Github Release"
context: GithubRelease
requires:
- "Windows Build and Test"
- "Linux Build and Test"
Expand Down
23 changes: 23 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,26 @@ We have a code of conduct for this project [here](https://github.com/mcfadd/Job_
3. Create a pull request from the fork by following [this guide][fork pull request]

[fork pull request]:https://help.github.com/en/articles/creating-a-pull-request-from-a-fork

### Versioning

JSSP is versioned using [Semantic Versioning 2.0.0](https://semver.org/).

Given a version number MAJOR.MINOR.PATCH, increment the:

- MAJOR version when you make incompatible API changes,
- MINOR version when you add functionality in a backwards compatible manner, and
- PATCH version when you make backwards compatible bug fixes.

Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.

### Releasing

When releasing a new version make sure to increment the versions in the following files:
- README.md
- setup.py
- docs/conf.py
- sonar-project.properties

Releases happen automatically in the Continuous Delivery (CD) stage of the CircleCI pipeline.
To trigger a release create an annotated git tag with the name of the version (i.e. MAJOR.MINOR.PATCH).
2 changes: 1 addition & 1 deletion JSSP/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .data import FJSData, CSVData
from .data import FJSData, SpreadsheetData
from .solution import SolutionFactory
from .solver import Solver
6 changes: 3 additions & 3 deletions JSSP/benchmark_plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def output_benchmark_results(output_dir, ts_agent_list=None, ga_agent=None, titl
"""
if (ts_agent_list is None or not all(ts_agent.benchmark for ts_agent in ts_agent_list)) \
and (ga_agent is None or not ga_agent.benchmark):
return
raise UserWarning("agent arguments were None or were not ran in benchmark mode.")

if title is None:
title = "Benchmark Run {}".format(datetime.datetime.now().strftime("%Y-%m-%d %H:%M"))
Expand All @@ -98,7 +98,7 @@ def compute_stats(lst):
}

# tabu search results
if ts_agent_list is not None:
if ts_agent_list is not None and all(ts_agent.benchmark for ts_agent in ts_agent_list):
_create_ts_plots(ts_agent_list, output_dir)
ts_result_makespans = []
ts_initial_makespans = []
Expand All @@ -118,7 +118,7 @@ def compute_stats(lst):
ts_iterations_stats = None

# genetic algorithm results
if ga_agent is not None:
if ga_agent is not None and ga_agent.benchmark:
_create_ga_plots(ga_agent, output_dir)
ga_initial_makespans = [sol.makespan for sol in ga_agent.initial_population]
ga_result_makespans = [sol.makespan for sol in ga_agent.result_population]
Expand Down
188 changes: 107 additions & 81 deletions JSSP/data.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import csv
import re
from abc import ABC
from pathlib import Path

import numpy as np
import pandas as pd


class Task:
Expand Down Expand Up @@ -298,49 +298,84 @@ def convert_fjs_to_csv(fjs_file, output_dir):
fout.write(line)


class CSVData(Data):
class SpreadsheetData(Data):
"""
JSSP instance data class for .csv data.
JSSP instance data class for spreadsheet data.
:type seq_dep_matrix_file: Path | str
:param seq_dep_matrix_file: path to the csv file containing the sequence dependency setup times
:type seq_dep_matrix: Path | str | Dataframe
:param seq_dep_matrix: path to the csv or xlsx file or a Dataframe containing the sequence dependency setup times
:type machine_speeds_file: Path | str
:param machine_speeds_file: path to the csv file containing all of the machine speeds
:type machine_speeds: Path | str | Dataframe
:param machine_speeds: path to the csv or xlsx file or a Dataframe containing all of the machine speeds
:type job_tasks_file: Path | str
:param job_tasks_file: path to the csv file containing all of the job-tasks
:type job_tasks: Path | str | Dataframe
:param job_tasks: path to the csv or xlsx file or a Dataframe containing all of the job-tasks
:returns: None
"""

def __init__(self, seq_dep_matrix_file, machine_speeds_file, job_tasks_file):
def __init__(self, seq_dep_matrix, machine_speeds, job_tasks):
"""
Initializes all of the static data from the csv files.
:type seq_dep_matrix_file: Path | str
:param seq_dep_matrix_file: path to the csv file containing the sequence dependency setup times
:type seq_dep_matrix: Path | str | Dataframe
:param seq_dep_matrix: path to the csv or xlsx file or a Dataframe containing the sequence dependency setup times.
If this param is None, then it is assumed that all setup times are 0 minutes.
:type machine_speeds_file: Path | str
:param machine_speeds_file: path to the csv file containing all of the machine speeds
:type machine_speeds: Path | str | Dataframe
:param machine_speeds: path to the csv or xlsx file or a Dataframe containing all of the machine speeds
:type job_tasks_file: Path | str
:param job_tasks_file: path to the csv file containing all of the job-tasks
:type job_tasks: Path | str | Dataframe
:param job_tasks: path to the csv or xlsx file or a Dataframe containing all of the job-tasks
:returns: None
"""
super().__init__()
self.job_tasks_file_path = Path(job_tasks_file)
self.seq_dep_matrix_file_path = Path(seq_dep_matrix_file)
self.machine_speeds_file_path = Path(machine_speeds_file)

self._read_job_tasks_file(self.job_tasks_file_path)
self._read_sequence_dependency_matrix_file(self.seq_dep_matrix_file_path)
self._read_machine_speeds_file(self.machine_speeds_file_path)
def _convert_to_df(path):
"""
Returns a data frame by reading a file.
:type path: str | Path
:param path: file to read as a data frame
:rtype: DataFrame
:return: data frame that was read
"""
path = Path(path)
if path.suffix == ".csv":
return pd.read_csv(path)
elif path.suffix == ".xlsx":
return pd.read_excel(path)
else:
raise UserWarning("File extension must either be .csv or .xlsx")

if isinstance(job_tasks, pd.DataFrame):
self.job_tasks_df = job_tasks
else:
self.job_tasks_df = _convert_to_df(job_tasks)

if seq_dep_matrix is None or isinstance(seq_dep_matrix, pd.DataFrame):
self.seq_dep_matrix_df = seq_dep_matrix
else:
self.seq_dep_matrix_df = _convert_to_df(seq_dep_matrix)

if isinstance(machine_speeds, pd.DataFrame):
self.machine_speeds_df = machine_speeds
else:
self.machine_speeds_df = _convert_to_df(machine_speeds)

self._read_job_tasks_df(self.job_tasks_df)
self._read_machine_speeds_df(self.machine_speeds_df)
if self.seq_dep_matrix_df is not None:
self._read_sequence_dependency_matrix_df(self.seq_dep_matrix_df)
else:
num_tasks = self.job_tasks_df.shape[0]
self.sequence_dependency_matrix = np.zeros((num_tasks, num_tasks), dtype=np.intc)

self.total_number_of_jobs = len(self.jobs)
self.total_number_of_tasks = self.sequence_dependency_matrix.shape[0]
self.max_tasks_for_a_job = max([x.get_number_of_tasks() for x in self.jobs])
self.total_number_of_tasks = sum(len(job.get_tasks()) for job in self.jobs)
self.max_tasks_for_a_job = max(job.get_number_of_tasks() for job in self.jobs)
self.total_number_of_machines = self.machine_speeds.shape[0]

self.job_task_index_matrix = np.full((self.total_number_of_jobs, self.max_tasks_for_a_job), -1, dtype=np.intc)
Expand All @@ -366,83 +401,74 @@ def __init__(self, seq_dep_matrix_file, machine_speeds_file, job_tasks_file):

task_index += 1

def _read_job_tasks_file(self, job_tasks_file):
def _read_job_tasks_df(self, job_tasks_df):
"""
Populates self.jobs by reading the job_tasks_file csv file.
Populates self.jobs by reading the job_tasks_df data frame.
:type job_tasks_file: Path | str
:param job_tasks_file: path to the csv file that contains the job-task data
:type job_tasks_df: DataFrame
:param job_tasks_df: data frame that contains the job-task data
:returns: None
.. Note:: this function assumes that all of the jobs in job_tasks_file are in ascending order
and are in the same order as in the sequence_dependency_matrix csv file.
"""
prev_job_id = -1 # record previously seen job_id
with open(job_tasks_file) as fin:
# skip headers (i.e. first row in csv file)
next(fin)
for row in csv.reader(fin):
# create task object
task = Task(
int(row[0]), # job_id
int(row[1]), # task_id
int(row[2]), # seq num
np.array([int(x) for x in row[3][1:-1].strip().split(' ')], dtype=np.intc), # usable machines
int(row[4]) # pieces
)
# create & append new job if we encounter job_id that has not been seen
if task.get_job_id() != prev_job_id:
self.jobs.append(Job(task.get_job_id()))
prev_job_id = task.get_job_id()

# update job's max sequence number
if task.get_sequence() > self.jobs[task.get_job_id()].get_max_sequence():
self.jobs[task.get_job_id()].set_max_sequence(task.get_sequence())

# append task to associated job.tasks list
self.jobs[task.get_job_id()].get_tasks().append(task)

def _read_sequence_dependency_matrix_file(self, seq_dep_matrix_file):
seen_jobs_ids = set()
for i, row in job_tasks_df.iterrows():
# create task object
task = Task(
int(row['Job']),
int(row['Task']),
int(row['Sequence']),
np.array([int(x) for x in row['Usable_Machines'][1:-1].strip().split(' ')], dtype=np.intc),
int(row['Pieces'])
)
job_id = task.get_job_id()

# create & append new job if we encounter job_id that has not been seen
if job_id not in seen_jobs_ids:
self.jobs.append(Job(job_id))
seen_jobs_ids.add(job_id)

# update job's max sequence number
if task.get_sequence() > self.get_job(job_id).get_max_sequence():
self.get_job(job_id).set_max_sequence(task.get_sequence())

# append task to associated job.tasks list
self.get_job(job_id).get_tasks().append(task)

def _read_sequence_dependency_matrix_df(self, seq_dep_matrix_df):
"""
Populates self.sequence_dependency_matrix by reading the seq_dep_matrix_file csv file.
Populates self.sequence_dependency_matrix by reading the seq_dep_matrix_df data frame.
:type seq_dep_matrix_file: Path | str
:param seq_dep_matrix_file: path to the csv file that contains the sequence dependency matrix
:type seq_dep_matrix_df: DataFrame
:param seq_dep_matrix_df: data frame that contains the sequence dependency matrix
:returns: None
"""
seq_dep_matrix_df = seq_dep_matrix_df.drop(seq_dep_matrix_df.columns[0], axis=1) # drop first column
tmp = []
for r, row in seq_dep_matrix_df.iterrows():
tmp2 = []
for c, value in row.iteritems():
tmp2.append(value)
tmp.append(tmp2)

.. Note:: this function assumes that all of the jobs in job_tasks_file are in ascending order
and are in the same order as in the sequence_dependency_matrix csv file.
self.sequence_dependency_matrix = np.array(tmp, dtype=np.intc)

def _read_machine_speeds_df(self, machine_speeds_df):
"""
with open(seq_dep_matrix_file) as fin:
# skip headers (i.e. first row in csv file)
next(fin)
self.sequence_dependency_matrix = np.array(
[[int(x) for x in row[1:]]
for row in csv.reader(fin)], dtype=np.intc)

def _read_machine_speeds_file(self, machine_speeds_file):
"""
Populates self.machine_speeds by reading the machine_speeds_file csv file.
Populates self.machine_speeds by reading the machine_speeds_df data frame.
:type machine_speeds_file: Path | str
:param machine_speeds_file: path to the csv file that contains the machine run speeds
:type machine_speeds_df: DataFrame
:param machine_speeds_df: data frame that contains the machine run speeds
:returns: None
.. Note:: this function assumes that the machines are listed in ascending order.
"""
with open(machine_speeds_file) as fin:
# skip headers (i.e. first row in csv file)
next(fin)
self.machine_speeds = np.array([int(row[1]) for row in csv.reader(fin)], dtype=np.float)
machine_speeds_df = machine_speeds_df.sort_values('Machine')
self.machine_speeds = np.array([row['RunSpeed'] for i, row in machine_speeds_df.iterrows()], dtype=np.float)


class FJSData(Data):
"""
JSSP instance data class for .fjs data.
JSSP instance data class for .fjs (Flexible Job Shop) data.
:type input_file: Path | str
:param input_file: path to the fjs file to read the data from
Expand Down
21 changes: 7 additions & 14 deletions JSSP/genetic_algorithm/ga.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import random
import statistics
import time
from enum import Enum

from ._ga_helpers import crossover
from ..exception import InfeasibleSolutionException
from ..solution import Solution, SolutionFactory
from ..util import get_stop_condition

"""
GA selection functions
Expand Down Expand Up @@ -128,6 +128,8 @@ def __init__(self, stopping_condition, population, time_condition=False,
assert selection_size is not None and 1 < selection_size, "selection_size must be an integer greater than 1"

# parameters
self.runtime = None
self.iterations = None
self.time_condition = time_condition
if time_condition:
self.runtime = stopping_condition
Expand Down Expand Up @@ -175,19 +177,10 @@ def start(self):
best_solution_iteration = 0

# create stopping condition function
if self.time_condition:
stop_time = time.time() + self.runtime

def stop_condition():
return time.time() >= stop_time
else:
stop_iter = self.iterations

def stop_condition():
return iterations >= stop_iter
stop_condition = get_stop_condition(self.time_condition, self.runtime, self.iterations)

not_done = True
while not stop_condition():
while not stop_condition(iterations):
if self.benchmark:
avg_population_makespan_v_iter.append(statistics.mean([sol.makespan for sol in population]))

Expand All @@ -210,7 +203,7 @@ def stop_condition():
if child1 != parent1 and child1 != parent2:
feasible_child = True
except InfeasibleSolutionException:
if stop_condition():
if stop_condition(iterations):
not_done = False
break

Expand All @@ -224,7 +217,7 @@ def stop_condition():
if child2 != parent1 and child2 != parent2:
feasible_child = True
except InfeasibleSolutionException:
if stop_condition():
if stop_condition(iterations):
not_done = False
break

Expand Down
Loading

0 comments on commit 94696af

Please sign in to comment.