Skip to content

Commit 75b2d18

Browse files
authored
Write instances (#7)
* Split-up Instance; implement write * Update docstring Instance.py * Correct calculation of metadata * Update README
1 parent 5983716 commit 75b2d18

7 files changed

Lines changed: 103 additions & 31 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ FJSPLIB is a Python package for reading and writing flexible job shop problem (F
1313
The FJSPLIB format is as follows:
1414

1515
``` sh
16-
<num jobs> <num machines> <average operations per job>
16+
<num jobs> <num machines> <avg num machines per operation>
1717
<num operations> * (<num machines> * (<machine idx> <duration>))
1818
...
1919
```
2020

21-
The first line contains data about the number of jobs, number machines and average number of operations per job.
21+
The first line contains data about the number of jobs, number machines and average number of machines that can process an operation.
2222
The following lines each represent the job data, one line for each job.
2323
These lines are each parsed as follows:
2424
- The first number denotes the number of operations for this job.

fjsplib/Instance.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from dataclasses import dataclass
2+
3+
4+
@dataclass
5+
class Instance:
6+
"""
7+
The FJSPLIB instance data.
8+
9+
Parameters
10+
----------
11+
num_jobs
12+
The number of jobs.
13+
num_machines
14+
The number of machines.
15+
num_operations
16+
The number of operations.
17+
jobs
18+
A list of job data, each job consisting of a list of operations.
19+
Operations are list of tuples, where each tuple consists of a machine
20+
index and a processing time.
21+
precedences
22+
A list of tuples consisting of two operation indices, representing the
23+
precedence relationship of two operations.
24+
"""
25+
26+
num_jobs: int
27+
num_machines: int
28+
num_operations: int
29+
jobs: list[list[list[tuple[int, int]]]]
30+
precedences: list[tuple[int, int]]

fjsplib/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
from .Instance import Instance as Instance
12
from .read import read as read
3+
from .write import write as write

fjsplib/read.py

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,12 @@
1-
from dataclasses import dataclass
21
from pathlib import Path
32
from typing import Union
43

4+
from fjsplib.Instance import Instance
5+
56
ProcessingData = list[tuple[int, int]]
67
Arc = tuple[int, int]
78

89

9-
@dataclass
10-
class Instance:
11-
"""
12-
The FJSPLIB instance data.
13-
14-
Parameters
15-
----------
16-
num_jobs
17-
The number of jobs.
18-
num_machines
19-
The number of machines.
20-
num_operations
21-
The number of operations.
22-
jobs
23-
A list of job data, each job consisting of a list of operation indices.
24-
precedences
25-
A list of tuples consisting of two operation indices, representing the
26-
precedence relationship of two operations.
27-
"""
28-
29-
num_jobs: int
30-
num_machines: int
31-
num_operations: int
32-
jobs: list[list[ProcessingData]]
33-
precedences: list[tuple[int, int]]
34-
35-
3610
def parse_job_line(line: list[int]) -> list[ProcessingData]:
3711
"""
3812
Parses a FJSPLIB job data line of the following form:

fjsplib/write.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from pathlib import Path
2+
from typing import Union
3+
4+
from .Instance import Instance
5+
6+
7+
def write(where: Union[Path, str], instance: Instance):
8+
"""
9+
Writes a problem instance to file in FJSPLIB format.
10+
11+
Parameters
12+
----------
13+
where
14+
Location to write the instance to.
15+
instance
16+
The problem instance.
17+
"""
18+
lines = []
19+
20+
# The flexibility is the average number of eligible machines per operation.
21+
num_eligible = sum([len(task) for ops in instance.jobs for task in ops])
22+
flexibility = round(num_eligible / instance.num_operations, 1)
23+
24+
metadata = f"{instance.num_jobs} {instance.num_machines} {flexibility}"
25+
lines.append(metadata)
26+
27+
for operations in instance.jobs:
28+
job = [len(operations)]
29+
30+
for processing_data in operations:
31+
num_eligible = len(processing_data)
32+
job.append(num_eligible)
33+
34+
for machine, duration in processing_data:
35+
# Machine indices are 1-indexed in FJSPLIB.
36+
job.extend([machine + 1, duration])
37+
38+
line = " ".join(str(num) for num in job)
39+
lines.append(line)
40+
41+
formatted = "\n".join(lines)
42+
43+
with open(where, "w") as fh:
44+
fh.write(formatted)

tests/data/classic.fjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
2 3 2.2
1+
2 3 1.7
22
1 2 1 1 2 2
33
2 1 1 1 2 3 1 2 1

tests/test_write.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from pathlib import Path
2+
3+
from numpy.testing import assert_equal
4+
5+
from fjsplib.write import write
6+
from tests.utils import read
7+
8+
9+
def test_write(tmp_path: Path):
10+
"""
11+
Tests that ``write`` correctly writes an FJSPLIB instance to a file.
12+
"""
13+
instance = read("data/classic.fjs")
14+
write(tmp_path / "classic.fjs", instance)
15+
16+
expected = [
17+
"2 3 1.7",
18+
"1 2 1 1 2 2",
19+
"2 1 1 1 2 3 1 2 1",
20+
]
21+
with open(tmp_path / "classic.fjs", "r") as fh:
22+
assert_equal(fh.read(), "\n".join(expected))

0 commit comments

Comments
 (0)