Tutorial 03 - Generating New Problems (Random Instance Generation)

This notebook shows how to generate random JobShopInstance objects using the function modular_instance_generator.

Deprecated: The classes InstanceGenerator and GeneralInstanceGenerator are deprecated. Use modular_instance_generator instead.

Why a Modular Function?

The design emphasizes two key patterns:

  1. Dependency Injection – You inject callables that build each matrix (machine routing, durations, release dates, etc.) instead of the generator hard-coding logic. This increases testability and flexibility.

  2. Strategy Pattern – Each callable acts as a strategy. Swap routing, duration, or release-date strategies independently without modifying the core generator.

The generator returns an infinite stream of instances. Use next() or slice it with itertools.islice.

Signature (Annotated)

modular_instance_generator(
    machine_matrix_creator: Callable[[random.Random], MachineMatrix],
    duration_matrix_creator: Callable[[MachineMatrix, random.Random], DurationMatrix],
    *,
    name_creator: Callable[[int], str] = lambda i: f"generated_instance_{i}",
    release_dates_matrix_creator: Callable[[DurationMatrix, random.Random], ReleaseDatesMatrix] | None = None,
    deadlines_matrix_creator: Callable[[DurationMatrix, random.Random], DeadlinesMatrix] | None = None,
    due_dates_matrix_creator: Callable[[DurationMatrix, random.Random], DueDatesMatrix] | None = None,
    seed: int | None = None,
) -> Generator[JobShopInstance, None, None]

Where each Matrix is a nested list (ragged lists allowed for jobs).

[ ]:
# Imports and basic setup
from itertools import islice
from job_shop_lib.generation import (
    modular_instance_generator,
    get_default_machine_matrix_creator,
    get_default_duration_matrix_creator,
    range_size_selector,
    choice_size_selector,
    create_release_dates_matrix,
    get_mixed_release_date_strategy,
    compute_horizon_proxy,
)
import random

# For deterministic examples
BASE_SEED = 42

Basic Example

Generate a fixed 3x3 instance without recirculation and durations in [1, 10].

[2]:
machine_creator = get_default_machine_matrix_creator(
    size_selector=lambda rng: (3, 3),
    with_recirculation=False,
)
duration_creator = get_default_duration_matrix_creator((1, 10))

gen_basic = modular_instance_generator(
    machine_matrix_creator=machine_creator,
    duration_matrix_creator=duration_creator,
    seed=BASE_SEED,
)
inst = next(gen_basic)
inst, inst.duration_matrix_array
[2]:
(JobShopInstance(name=generated_instance_0, num_jobs=3, num_machines=3),
 array([[ 5.,  6.,  4.],
        [ 5.,  7., 10.],
        [ 9.,  9.,  5.]], dtype=float32))

Custom Naming Strategy

Provide a callable that maps the index to a formatted name.

[3]:
def fancy_name(i: int) -> str:  # zero-padded index
    return f"expA_seed{BASE_SEED}_{i:03d}"


gen_named = modular_instance_generator(
    machine_matrix_creator=machine_creator,
    duration_matrix_creator=duration_creator,
    name_creator=fancy_name,
    seed=BASE_SEED,
)
[next(gen_named).name for _ in range(3)]
[3]:
['expA_seed42_000', 'expA_seed42_001', 'expA_seed42_002']

Random Sizes (Size Selectors)

Use range-based or discrete-choice size selectors captured in the machine matrix creator.

[4]:
# Range-based size selection
machine_creator_range = get_default_machine_matrix_creator(
    size_selector=lambda rng: range_size_selector(
        rng,
        num_jobs_range=(5, 7),
        num_machines_range=(3, 5),
        allow_less_jobs_than_machines=True,
    ),
    with_recirculation=True,
)

# Choice-based size selection
OPTIONS = [(3, 3), (4, 5), (6, 4)]
machine_creator_choice = get_default_machine_matrix_creator(
    size_selector=lambda rng: choice_size_selector(rng, OPTIONS),
    with_recirculation=False,
)

duration_creator_var = get_default_duration_matrix_creator((2, 15))

gen_choice = modular_instance_generator(
    machine_matrix_creator=machine_creator_choice,
    duration_matrix_creator=duration_creator_var,
    seed=7,
)
[next(gen_choice) for _ in range(2)]
[4]:
[JobShopInstance(name=generated_instance_0, num_jobs=4, num_machines=5),
 JobShopInstance(name=generated_instance_1, num_jobs=6, num_machines=4)]

Adding Release Dates

You can wrap create_release_dates_matrix with a custom mixed strategy.

[ ]:
def release_dates_creator(duration_matrix, rng):
    horizon = compute_horizon_proxy(duration_matrix)
    strat = get_mixed_release_date_strategy(0.6, 0.4, horizon)
    return create_release_dates_matrix(
        duration_matrix, strategy=strat, rng=rng
    )


gen_with_release = modular_instance_generator(
    machine_matrix_creator=machine_creator,
    duration_matrix_creator=duration_creator,
    release_dates_matrix_creator=release_dates_creator,
    seed=123,
)
inst_with_release = next(gen_with_release)
inst_with_release.release_dates_matrix
[[0, 6, 9], [0, 4, 7], [4, 5, 8]]

Deadlines & Due Dates

Provide simple heuristic strategies. You decide semantics.

[ ]:
def deadlines_creator(duration_matrix, rng):
    deadlines = []
    for job in duration_matrix:
        cum = 0
        row = []
        for duration in job:
            cum += duration
            row.append(int(cum * 2))  # 100% slack
        deadlines.append(row)
    return deadlines


def due_dates_creator(duration_matrix, rng):
    due_dates = []
    for job in duration_matrix:
        cum = 0
        row = []
        for duration in job:
            cum += duration
            row.append(cum + rng.randint(0, duration))
        due_dates.append(row)
    return due_dates


gen_with_all = modular_instance_generator(
    machine_matrix_creator=machine_creator,
    duration_matrix_creator=duration_creator,
    release_dates_matrix_creator=release_dates_creator,
    deadlines_matrix_creator=deadlines_creator,
    due_dates_matrix_creator=due_dates_creator,
    seed=99,
)
complex_instance = next(gen_with_all)
print(complex_instance)
print("complex_instance.due_dates_matrix_array =")
print(complex_instance.due_dates_matrix_array)
print("complex_instance.deadlines_matrix_array =")
print(complex_instance.deadlines_matrix_array)
print("complex_instance.release_dates_matrix_array =")
print(complex_instance.release_dates_matrix_array)
JobShopInstance(name=generated_instance_0, num_jobs=3, num_machines=3)
complex_instance.due_dates_matrix_array =
[[14.  9. 25.]
 [ 4. 12. 14.]
 [13. 18. 27.]]
complex_instance.deadlines_matrix_array =
[[14. 18. 38.]
 [ 4. 18. 24.]
 [14. 30. 50.]]
complex_instance.release_dates_matrix_array =
[[ 3.  6.  8.]
 [ 3.  3.  6.]
 [ 4. 10. 10.]]

Migration Example (Deprecated Class)

Old approach (for reference, no execution here):

# Deprecated
# gen = GeneralInstanceGenerator(num_jobs=(5,10), num_machines=(4,6), duration_range=(2,20))
# inst = gen.generate()

New modular approach:

[16]:
from functools import partial

size_selector = partial(
    range_size_selector,
    num_jobs_range=(5, 8),
    num_machines_range=(4, 6),
    allow_less_jobs_than_machines=False,
)
migrated_machine_creator = get_default_machine_matrix_creator(
    size_selector=size_selector, with_recirculation=False
)
migrated_duration_creator = get_default_duration_matrix_creator((2, 20))
gen_migrated = modular_instance_generator(
    migrated_machine_creator, migrated_duration_creator, seed=0
)
next(gen_migrated)
[16]:
JobShopInstance(name=generated_instance_0, num_jobs=8, num_machines=5)

Finite Sample Extraction

Use itertools.islice to limit the infinite generator.

[8]:
finite_instances = list(islice(gen_basic, 3))  # reuse earlier generator
len(finite_instances), [inst.name for inst in finite_instances]
[8]:
(3, ['generated_instance_1', 'generated_instance_2', 'generated_instance_3'])

Summary

modular_instance_generator provides a clean, composable way to build random Job Shop instances. You control routing, durations, and time-related constraints via strategy callables—simplifying experimentation and testing.

You can now integrate these instances into solvers, RL environments, or graph builders.