Skip to content

Commit

Permalink
1.0.0-alpha.4: Merge pull request #12 from development
Browse files Browse the repository at this point in the history
1.0.0-alpha.4:  Improve `plot_disjunctive_graph`, add `build_solved_disjunctive_graph` , documentation fixes
  • Loading branch information
Pabloo22 authored Oct 21, 2024
2 parents 1bbf361 + 67f41b1 commit b5fed75
Show file tree
Hide file tree
Showing 35 changed files with 1,087 additions and 615 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ JobShopLib is a Python package for creating, solving, and visualizing Job Shop S

It follows a modular design, allowing users to easily extend the library with new solvers, dispatching rules, visualization functions, etc.

See the [documentation](https://job-shop-lib.readthedocs.io/en/latest/) for more details about the latest version (1.0.0a2).
See the [documentation](https://job-shop-lib.readthedocs.io/en/latest/) for more details about the latest version.

## Installation :package:

Expand All @@ -36,7 +36,7 @@ See [this](https://colab.research.google.com/drive/1XV_Rvq1F2ns6DFG8uNj66q_rcoww
Version 1.0.0 is currently in alpha stage and can be installed with:

```bash
pip install job-shop-lib==1.0.0a3
pip install job-shop-lib==1.0.0a4
```

Although this version is not stable and may contain breaking changes in subsequent releases, it is recommended to install it to access the new reinforcement learning environments and familiarize yourself with new changes (see the [latest pull requests](https://github.com/Pabloo22/job_shop_lib/pulls?q=is%3Apr+is%3Aclosed)). This version is the first one with a [documentation page](https://job-shop-lib.readthedocs.io/en/latest/).
Expand Down
2 changes: 1 addition & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Welcome to JobShopLib's documentation!
======================================

**Version:** 1.0.0-a.3
**Version:** 1.0.0-a.4


JobShopLib is a Python package for creating, solving, and visualizing
Expand Down
142 changes: 104 additions & 38 deletions job_shop_lib/_job_shop_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,47 @@
class JobShopInstance:
"""Data structure to store a Job Shop Scheduling Problem instance.
Additional attributes such as `num_jobs` or `num_machines` can be computed
from the instance and are cached for performance if they require expensive
computations.
Additional attributes such as ``num_machines`` or ``durations_matrix`` can
be computed from the instance and are cached for performance if they
require expensive computations.
Methods:
.. autosummary::
:nosignatures:
from_taillard_file
to_dict
from_matrices
set_operation_attributes
Properties:
.. autosummary::
:nosignatures:
num_jobs
num_machines
num_operations
is_flexible
durations_matrix
machines_matrix
durations_matrix_array
machines_matrix_array
operations_by_machine
max_duration
max_duration_per_job
max_duration_per_machine
job_durations
machine_loads
total_duration
Attributes:
jobs (list[list[Operation]]):
A list of lists of operations. Each list of operations represents
a job, and the operations are ordered by their position in the job.
The `job_id`, `position_in_job`, and `operation_id` attributes of
the operations are set when the instance is created.
The ``job_id``, ``position_in_job``, and `operation_id` attributes
of the operations are set when the instance is created.
name (str):
A string with the name of the instance.
metadata (dict[str, Any]):
Expand All @@ -34,11 +65,16 @@ class JobShopInstance:
jobs:
A list of lists of operations. Each list of operations
represents a job, and the operations are ordered by their
position in the job. The `job_id`, `position_in_job`, and
`operation_id` attributes of the operations are set when the
position in the job. The ``job_id``, ``position_in_job``, and
``operation_id`` attributes of the operations are set when the
instance is created.
name:
A string with the name of the instance.
set_operation_attributes:
If True, the ``job_id``, ``position_in_job``, and ``operation_id``
attributes of the operations are set when the instance is created.
See :meth:`set_operation_attributes` for more information. Defaults
to True.
**metadata:
Additional information about the instance.
"""
Expand All @@ -47,15 +83,37 @@ def __init__(
self,
jobs: list[list[Operation]],
name: str = "JobShopInstance",
set_operation_attributes: bool = True,
**metadata: Any,
):
self.jobs: list[list[Operation]] = jobs
self.set_operation_attributes()
if set_operation_attributes:
self.set_operation_attributes()
self.name: str = name
self.metadata: dict[str, Any] = metadata

def set_operation_attributes(self):
"""Sets the job_id and position of each operation."""
"""Sets the ``job_id``, ``position_in_job``, and ``operation_id``
attributes for each operation in the instance.
The ``job_id`` attribute is set to the id of the job to which the
operation belongs.
The ``position_in_job`` attribute is set to the
position of the operation in the job (starts from 0).
The ``operation_id`` attribute is set to a unique identifier for the
operation (starting from 0).
The formula to compute the ``operation_id`` in a job shop instance with
a fixed number of operations per job is:
.. code-block:: python
operation_id = job_id * num_operations_per_job + position_in_job
"""

operation_id = 0
for job_id, job in enumerate(self.jobs):
for position, operation in enumerate(job):
Expand Down Expand Up @@ -90,8 +148,8 @@ def from_taillard_file(
Additional information about the instance.
Returns:
A JobShopInstance object with the operations read from the file,
and the name and metadata provided.
A :class:`JobShopInstance` object with the operations read from the
file, and the name and metadata provided.
"""
with open(file_path, "r", encoding=encoding) as file:
lines = file.readlines()
Expand Down Expand Up @@ -128,13 +186,17 @@ def to_dict(self) -> dict[str, Any]:
like Taillard's.
Returns:
The returned dictionary has the following structure:
{
"name": self.name,
"duration_matrix": self.durations_matrix,
"machines_matrix": self.machines_matrix,
"metadata": self.metadata,
}
dict[str, Any]: The returned dictionary has the following
structure:
.. code-block:: python
{
"name": self.name,
"duration_matrix": self.durations_matrix,
"machines_matrix": self.machines_matrix,
"metadata": self.metadata,
}
"""
return {
"name": self.name,
Expand All @@ -151,7 +213,8 @@ def from_matrices(
name: str = "JobShopInstance",
metadata: dict[str, Any] | None = None,
) -> JobShopInstance:
"""Creates a JobShopInstance from duration and machines matrices.
"""Creates a :class:`JobShopInstance` from duration and machines
matrices.
Args:
duration_matrix:
Expand All @@ -168,7 +231,7 @@ def from_matrices(
A dictionary with additional information about the instance.
Returns:
A JobShopInstance object.
A :class:`JobShopInstance` object.
"""
jobs: list[list[Operation]] = [[] for _ in range(len(duration_matrix))]

Expand Down Expand Up @@ -220,7 +283,7 @@ def num_operations(self) -> int:

@functools.cached_property
def is_flexible(self) -> bool:
"""Returns True if any operation has more than one machine."""
"""Returns ``True`` if any operation has more than one machine."""
return any(
any(len(operation.machines) > 1 for operation in job)
for job in self.jobs
Expand All @@ -230,12 +293,14 @@ def is_flexible(self) -> bool:
def durations_matrix(self) -> list[list[int]]:
"""Returns the duration matrix of the instance.
The duration of the operation with `job_id` i and `position_in_job` j
is stored in the i-th position of the j-th list of the returned matrix:
The duration of the operation with ``job_id`` i and ``position_in_job``
j is stored in the i-th position of the j-th list of the returned
matrix:
.. code-block:: python
duration = instance.durations_matrix[i][j]
```python
duration = instance.durations_matrix[i][j]
```
"""
return [[operation.duration for operation in job] for job in self.jobs]

Expand All @@ -252,9 +317,9 @@ def machines_matrix(self) -> list[list[list[int]]] | list[list[int]]:
To access the machines of the operation with position i in the job
with id j, the following code must be used:
```python
machines = instance.machines_matrix[j][i]
```
.. code-block:: python
machines = instance.machines_matrix[j][i]
"""
if self.is_flexible:
Expand All @@ -269,8 +334,9 @@ def machines_matrix(self) -> list[list[list[int]]] | list[list[int]]:
def durations_matrix_array(self) -> NDArray[np.float32]:
"""Returns the duration matrix of the instance as a numpy array.
The returned array has shape (num_jobs, max_num_operations_per_job).
Non-existing operations are filled with np.nan.
The returned array has shape (``num_jobs``,
``max_num_operations_per_job``).
Non-existing operations are filled with ``np.nan``.
Example:
>>> jobs = [[Operation(0, 2), Operation(1, 3)], [Operation(0, 4)]]
Expand All @@ -286,9 +352,9 @@ def durations_matrix_array(self) -> NDArray[np.float32]:
def machines_matrix_array(self) -> NDArray[np.float32]:
"""Returns the machines matrix of the instance as a numpy array.
The returned array has shape (num_jobs, max_num_operations_per_job,
max_num_machines_per_operation). Non-existing machines are filled with
np.nan.
The returned array has shape (``num_jobs``,
``max_num_operations_per_job``, ``max_num_machines_per_operation``).
Non-existing machines are filled with ``np.nan``.
Example:
>>> jobs = [
Expand Down Expand Up @@ -411,15 +477,15 @@ def total_duration(self) -> int:
def _fill_matrix_with_nans_2d(
matrix: list[list[int]],
) -> NDArray[np.float32]:
"""Fills a matrix with np.nan values.
"""Fills a matrix with ``np.nan`` values.
Args:
matrix:
A list of lists of integers.
Returns:
A numpy array with the same shape as the input matrix, filled with
np.nan values.
``np.nan`` values.
"""
max_length = max(len(row) for row in matrix)
squared_matrix = np.full(
Expand All @@ -433,15 +499,15 @@ def _fill_matrix_with_nans_2d(
def _fill_matrix_with_nans_3d(
matrix: list[list[list[int]]],
) -> NDArray[np.float32]:
"""Fills a 3D matrix with np.nan values.
"""Fills a 3D matrix with ``np.nan`` values.
Args:
matrix:
A list of lists of lists of integers.
Returns:
A numpy array with the same shape as the input matrix, filled with
np.nan values.
``np.nan`` values.
"""
max_length = max(len(row) for row in matrix)
max_inner_length = len(matrix[0][0])
Expand Down
15 changes: 12 additions & 3 deletions job_shop_lib/_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,20 @@ class Operation:
"The time it takes to perform the operation. Often referred"
" to as the processing time."
),
"job_id": "The id of the job the operation belongs to.",
"position_in_job": "The index of the operation in the job.",
"job_id": (
"The id of the job the operation belongs to. Defaults to -1. "
"It is usually set by the :class:`JobShopInstance` class after "
"initialization."
),
"position_in_job": (
"The index of the operation in the job. Defaults to -1. "
"It is usually set by the :class:`JobShopInstance` class after "
"initialization."
),
"operation_id": (
"The id of the operation. This is unique within a "
":class:`JobShopInstance`."
":class:`JobShopInstance`. Defaults to -1. It is usually set by "
"the :class:`JobShopInstance` class after initialization."
),
}

Expand Down
22 changes: 10 additions & 12 deletions job_shop_lib/_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ class Schedule:
is_complete
add
reset
Args:
instance:
The :class:`JobShopInstance` object that the schedule is for.
schedule:
A list of lists of :class:`ScheduledOperation` objects. Each
list represents the order of operations on a machine. If
not provided, the schedule is initialized as an empty schedule.
**metadata:
Additional information about the schedule.
"""

__slots__ = {
Expand All @@ -48,18 +58,6 @@ def __init__(
schedule: list[list[ScheduledOperation]] | None = None,
**metadata: Any,
):
"""Initializes the object with the given instance and schedule.
Args:
instance:
The :class:`JobShopInstance` object that the schedule is for.
schedule:
A list of lists of :class:`ScheduledOperation` objects. Each
list represents the order of operations on a machine. If
not provided, the schedule is initialized as an empty schedule.
**metadata:
Additional information about the schedule.
"""
if schedule is None:
schedule = [[] for _ in range(instance.num_machines)]

Expand Down
Loading

0 comments on commit b5fed75

Please sign in to comment.