Skip to content

Commit

Permalink
Write instances (#7)
Browse files Browse the repository at this point in the history
* Split-up Instance; implement write

* Update docstring Instance.py

* Correct calculation of metadata

* Update README
  • Loading branch information
leonlan authored Feb 20, 2024
1 parent 5983716 commit 75b2d18
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 31 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ FJSPLIB is a Python package for reading and writing flexible job shop problem (F
The FJSPLIB format is as follows:

``` sh
<num jobs> <num machines> <average operations per job>
<num jobs> <num machines> <avg num machines per operation>
<num operations> * (<num machines> * (<machine idx> <duration>))
...
```

The first line contains data about the number of jobs, number machines and average number of operations per job.
The first line contains data about the number of jobs, number machines and average number of machines that can process an operation.
The following lines each represent the job data, one line for each job.
These lines are each parsed as follows:
- The first number denotes the number of operations for this job.
Expand Down
30 changes: 30 additions & 0 deletions fjsplib/Instance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from dataclasses import dataclass


@dataclass
class Instance:
"""
The FJSPLIB instance data.
Parameters
----------
num_jobs
The number of jobs.
num_machines
The number of machines.
num_operations
The number of operations.
jobs
A list of job data, each job consisting of a list of operations.
Operations are list of tuples, where each tuple consists of a machine
index and a processing time.
precedences
A list of tuples consisting of two operation indices, representing the
precedence relationship of two operations.
"""

num_jobs: int
num_machines: int
num_operations: int
jobs: list[list[list[tuple[int, int]]]]
precedences: list[tuple[int, int]]
2 changes: 2 additions & 0 deletions fjsplib/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from .Instance import Instance as Instance
from .read import read as read
from .write import write as write
30 changes: 2 additions & 28 deletions fjsplib/read.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,12 @@
from dataclasses import dataclass
from pathlib import Path
from typing import Union

from fjsplib.Instance import Instance

ProcessingData = list[tuple[int, int]]
Arc = tuple[int, int]


@dataclass
class Instance:
"""
The FJSPLIB instance data.
Parameters
----------
num_jobs
The number of jobs.
num_machines
The number of machines.
num_operations
The number of operations.
jobs
A list of job data, each job consisting of a list of operation indices.
precedences
A list of tuples consisting of two operation indices, representing the
precedence relationship of two operations.
"""

num_jobs: int
num_machines: int
num_operations: int
jobs: list[list[ProcessingData]]
precedences: list[tuple[int, int]]


def parse_job_line(line: list[int]) -> list[ProcessingData]:
"""
Parses a FJSPLIB job data line of the following form:
Expand Down
44 changes: 44 additions & 0 deletions fjsplib/write.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from pathlib import Path
from typing import Union

from .Instance import Instance


def write(where: Union[Path, str], instance: Instance):
"""
Writes a problem instance to file in FJSPLIB format.
Parameters
----------
where
Location to write the instance to.
instance
The problem instance.
"""
lines = []

# The flexibility is the average number of eligible machines per operation.
num_eligible = sum([len(task) for ops in instance.jobs for task in ops])
flexibility = round(num_eligible / instance.num_operations, 1)

metadata = f"{instance.num_jobs} {instance.num_machines} {flexibility}"
lines.append(metadata)

for operations in instance.jobs:
job = [len(operations)]

for processing_data in operations:
num_eligible = len(processing_data)
job.append(num_eligible)

for machine, duration in processing_data:
# Machine indices are 1-indexed in FJSPLIB.
job.extend([machine + 1, duration])

line = " ".join(str(num) for num in job)
lines.append(line)

formatted = "\n".join(lines)

with open(where, "w") as fh:
fh.write(formatted)
2 changes: 1 addition & 1 deletion tests/data/classic.fjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
2 3 2.2
2 3 1.7
1 2 1 1 2 2
2 1 1 1 2 3 1 2 1
22 changes: 22 additions & 0 deletions tests/test_write.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from pathlib import Path

from numpy.testing import assert_equal

from fjsplib.write import write
from tests.utils import read


def test_write(tmp_path: Path):
"""
Tests that ``write`` correctly writes an FJSPLIB instance to a file.
"""
instance = read("data/classic.fjs")
write(tmp_path / "classic.fjs", instance)

expected = [
"2 3 1.7",
"1 2 1 1 2 2",
"2 1 1 1 2 3 1 2 1",
]
with open(tmp_path / "classic.fjs", "r") as fh:
assert_equal(fh.read(), "\n".join(expected))

0 comments on commit 75b2d18

Please sign in to comment.