{ "cells": [ { "cell_type": "markdown", "id": "b571493f", "metadata": {}, "source": [ "# Tutorial 03 - Generating New Problems (Random Instance Generation)\n", "\n", "This notebook shows how to generate random **JobShopInstance** objects using the function `modular_instance_generator`.\n", "\n", "> **Deprecated:** The classes `InstanceGenerator` and `GeneralInstanceGenerator` are deprecated. Use `modular_instance_generator` instead." ] }, { "cell_type": "markdown", "id": "4eb87edb", "metadata": {}, "source": [ "## Why a Modular Function?\n", "The design emphasizes two key patterns:\n", "\n", "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.\n", "2. **Strategy Pattern** – Each callable acts as a strategy. Swap routing, duration, or release-date strategies independently without modifying the core generator.\n", "\n", "The generator returns an **infinite stream** of instances. Use `next()` or slice it with `itertools.islice`." ] }, { "cell_type": "markdown", "id": "8e96b3df", "metadata": {}, "source": [ "## Signature (Annotated)\n", "```python\n", "modular_instance_generator(\n", " machine_matrix_creator: Callable[[random.Random], MachineMatrix],\n", " duration_matrix_creator: Callable[[MachineMatrix, random.Random], DurationMatrix],\n", " *,\n", " name_creator: Callable[[int], str] = lambda i: f\"generated_instance_{i}\",\n", " release_dates_matrix_creator: Callable[[DurationMatrix, random.Random], ReleaseDatesMatrix] | None = None,\n", " deadlines_matrix_creator: Callable[[DurationMatrix, random.Random], DeadlinesMatrix] | None = None,\n", " due_dates_matrix_creator: Callable[[DurationMatrix, random.Random], DueDatesMatrix] | None = None,\n", " seed: int | None = None,\n", ") -> Generator[JobShopInstance, None, None]\n", "```\n", "Where each *Matrix* is a nested list (ragged lists allowed for jobs)." ] }, { "cell_type": "code", "execution_count": null, "id": "3da71e8d", "metadata": {}, "outputs": [], "source": [ "# Imports and basic setup\n", "from itertools import islice\n", "from job_shop_lib.generation import (\n", " modular_instance_generator,\n", " get_default_machine_matrix_creator,\n", " get_default_duration_matrix_creator,\n", " range_size_selector,\n", " choice_size_selector,\n", " create_release_dates_matrix,\n", " get_mixed_release_date_strategy,\n", " compute_horizon_proxy,\n", ")\n", "import random\n", "\n", "# For deterministic examples\n", "BASE_SEED = 42" ] }, { "cell_type": "markdown", "id": "da62385c", "metadata": {}, "source": [ "## Basic Example\n", "Generate a fixed 3x3 instance without recirculation and durations in `[1, 10]`." ] }, { "cell_type": "code", "execution_count": 2, "id": "70bc9b22", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(JobShopInstance(name=generated_instance_0, num_jobs=3, num_machines=3),\n", " array([[ 5., 6., 4.],\n", " [ 5., 7., 10.],\n", " [ 9., 9., 5.]], dtype=float32))" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "machine_creator = get_default_machine_matrix_creator(\n", " size_selector=lambda rng: (3, 3),\n", " with_recirculation=False,\n", ")\n", "duration_creator = get_default_duration_matrix_creator((1, 10))\n", "\n", "gen_basic = modular_instance_generator(\n", " machine_matrix_creator=machine_creator,\n", " duration_matrix_creator=duration_creator,\n", " seed=BASE_SEED,\n", ")\n", "inst = next(gen_basic)\n", "inst, inst.duration_matrix_array" ] }, { "cell_type": "markdown", "id": "75085acf", "metadata": {}, "source": [ "## Custom Naming Strategy\n", "Provide a callable that maps the index to a formatted name." ] }, { "cell_type": "code", "execution_count": 3, "id": "a5750583", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['expA_seed42_000', 'expA_seed42_001', 'expA_seed42_002']" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def fancy_name(i: int) -> str: # zero-padded index\n", " return f\"expA_seed{BASE_SEED}_{i:03d}\"\n", "\n", "\n", "gen_named = modular_instance_generator(\n", " machine_matrix_creator=machine_creator,\n", " duration_matrix_creator=duration_creator,\n", " name_creator=fancy_name,\n", " seed=BASE_SEED,\n", ")\n", "[next(gen_named).name for _ in range(3)]" ] }, { "cell_type": "markdown", "id": "60c96d95", "metadata": {}, "source": [ "## Random Sizes (Size Selectors)\n", "Use range-based or discrete-choice size selectors captured in the machine matrix creator." ] }, { "cell_type": "code", "execution_count": 4, "id": "1824aa33", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[JobShopInstance(name=generated_instance_0, num_jobs=4, num_machines=5),\n", " JobShopInstance(name=generated_instance_1, num_jobs=6, num_machines=4)]" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Range-based size selection\n", "machine_creator_range = get_default_machine_matrix_creator(\n", " size_selector=lambda rng: range_size_selector(\n", " rng,\n", " num_jobs_range=(5, 7),\n", " num_machines_range=(3, 5),\n", " allow_less_jobs_than_machines=True,\n", " ),\n", " with_recirculation=True,\n", ")\n", "\n", "# Choice-based size selection\n", "OPTIONS = [(3, 3), (4, 5), (6, 4)]\n", "machine_creator_choice = get_default_machine_matrix_creator(\n", " size_selector=lambda rng: choice_size_selector(rng, OPTIONS),\n", " with_recirculation=False,\n", ")\n", "\n", "duration_creator_var = get_default_duration_matrix_creator((2, 15))\n", "\n", "gen_choice = modular_instance_generator(\n", " machine_matrix_creator=machine_creator_choice,\n", " duration_matrix_creator=duration_creator_var,\n", " seed=7,\n", ")\n", "[next(gen_choice) for _ in range(2)]" ] }, { "cell_type": "markdown", "id": "fc4f0db8", "metadata": {}, "source": [ "## Adding Release Dates\n", "You can wrap `create_release_dates_matrix` with a custom mixed strategy." ] }, { "cell_type": "code", "execution_count": null, "id": "d6d78183", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[[0, 6, 9], [0, 4, 7], [4, 5, 8]]" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def release_dates_creator(duration_matrix, rng):\n", " horizon = compute_horizon_proxy(duration_matrix)\n", " strat = get_mixed_release_date_strategy(0.6, 0.4, horizon)\n", " return create_release_dates_matrix(\n", " duration_matrix, strategy=strat, rng=rng\n", " )\n", "\n", "\n", "gen_with_release = modular_instance_generator(\n", " machine_matrix_creator=machine_creator,\n", " duration_matrix_creator=duration_creator,\n", " release_dates_matrix_creator=release_dates_creator,\n", " seed=123,\n", ")\n", "inst_with_release = next(gen_with_release)\n", "inst_with_release.release_dates_matrix" ] }, { "cell_type": "markdown", "id": "163cf717", "metadata": {}, "source": [ "## Deadlines & Due Dates\n", "Provide simple heuristic strategies. You decide semantics." ] }, { "cell_type": "code", "execution_count": null, "id": "dddd5aa9", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "JobShopInstance(name=generated_instance_0, num_jobs=3, num_machines=3)\n", "complex_instance.due_dates_matrix_array =\n", "[[14. 9. 25.]\n", " [ 4. 12. 14.]\n", " [13. 18. 27.]]\n", "complex_instance.deadlines_matrix_array =\n", "[[14. 18. 38.]\n", " [ 4. 18. 24.]\n", " [14. 30. 50.]]\n", "complex_instance.release_dates_matrix_array =\n", "[[ 3. 6. 8.]\n", " [ 3. 3. 6.]\n", " [ 4. 10. 10.]]\n" ] } ], "source": [ "def deadlines_creator(duration_matrix, rng):\n", " deadlines = []\n", " for job in duration_matrix:\n", " cum = 0\n", " row = []\n", " for duration in job:\n", " cum += duration\n", " row.append(int(cum * 2)) # 100% slack\n", " deadlines.append(row)\n", " return deadlines\n", "\n", "\n", "def due_dates_creator(duration_matrix, rng):\n", " due_dates = []\n", " for job in duration_matrix:\n", " cum = 0\n", " row = []\n", " for duration in job:\n", " cum += duration\n", " row.append(cum + rng.randint(0, duration))\n", " due_dates.append(row)\n", " return due_dates\n", "\n", "\n", "gen_with_all = modular_instance_generator(\n", " machine_matrix_creator=machine_creator,\n", " duration_matrix_creator=duration_creator,\n", " release_dates_matrix_creator=release_dates_creator,\n", " deadlines_matrix_creator=deadlines_creator,\n", " due_dates_matrix_creator=due_dates_creator,\n", " seed=99,\n", ")\n", "complex_instance = next(gen_with_all)\n", "print(complex_instance)\n", "print(\"complex_instance.due_dates_matrix_array =\")\n", "print(complex_instance.due_dates_matrix_array)\n", "print(\"complex_instance.deadlines_matrix_array =\")\n", "print(complex_instance.deadlines_matrix_array)\n", "print(\"complex_instance.release_dates_matrix_array =\")\n", "print(complex_instance.release_dates_matrix_array)" ] }, { "cell_type": "markdown", "id": "80bd8882", "metadata": {}, "source": [ "## Migration Example (Deprecated Class)\n", "Old approach (for reference, no execution here):\n", "```python\n", "# Deprecated\n", "# gen = GeneralInstanceGenerator(num_jobs=(5,10), num_machines=(4,6), duration_range=(2,20))\n", "# inst = gen.generate()\n", "```\n", "New modular approach:" ] }, { "cell_type": "code", "execution_count": 16, "id": "57a6eb24", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "JobShopInstance(name=generated_instance_0, num_jobs=8, num_machines=5)" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from functools import partial\n", "\n", "size_selector = partial(\n", " range_size_selector,\n", " num_jobs_range=(5, 8),\n", " num_machines_range=(4, 6),\n", " allow_less_jobs_than_machines=False,\n", ")\n", "migrated_machine_creator = get_default_machine_matrix_creator(\n", " size_selector=size_selector, with_recirculation=False\n", ")\n", "migrated_duration_creator = get_default_duration_matrix_creator((2, 20))\n", "gen_migrated = modular_instance_generator(\n", " migrated_machine_creator, migrated_duration_creator, seed=0\n", ")\n", "next(gen_migrated)" ] }, { "cell_type": "markdown", "id": "58650c8a", "metadata": {}, "source": [ "## Finite Sample Extraction\n", "Use `itertools.islice` to limit the infinite generator." ] }, { "cell_type": "code", "execution_count": 8, "id": "01d7d25f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "(3, ['generated_instance_1', 'generated_instance_2', 'generated_instance_3'])" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "finite_instances = list(islice(gen_basic, 3)) # reuse earlier generator\n", "len(finite_instances), [inst.name for inst in finite_instances]" ] }, { "cell_type": "markdown", "id": "b26f7fee", "metadata": {}, "source": [ "## Summary\n", "`modular_instance_generator` provides a clean, composable way to build\n", "random Job Shop instances. You control routing, durations, and time-related\n", "constraints via strategy callables—simplifying experimentation and testing.\n", "\n", "You can now integrate these instances into solvers, RL environments, or graph builders." ] } ], "metadata": { "kernelspec": { "display_name": "job-shop-lib-gOF0HMZJ-py3.12", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.3" } }, "nbformat": 4, "nbformat_minor": 5 }