|
16 | 16 | "3. **Iteratively explore neighbors:** The algorithm then iteratively explores \"neighboring\" solutions. A neighbor is a new schedule created by making a small change to the current one, for example, by swapping the order of two operations on a single machine.\n", |
17 | 17 | "4. **Accept or reject new solutions:**\n", |
18 | 18 | " * If the neighbor solution has a lower energy (is better), it is always accepted as the new current solution.\n", |
19 | | - " * If the neighbor solution has a higher energy (is worse), it might still be accepted with a certain probability. This probability is higher at the beginning (when the \"temperature\" is high) and decreases over time. This allows the algorithm to escape local optima and explore a wider range of solutions.\n", |
| 19 | + " * If the neighbor solution has a higher energy (is worse), it might still be accepted with a certain probability. This probability is higher at the beginning (when a threshold called \"temperature\" is high) and decreases over time. This allows the algorithm to escape local optima and explore a wider range of solutions.\n", |
20 | 20 | "5. **Cooling down:** The \"temperature\" gradually decreases, reducing the probability of accepting worse solutions. The process stops when the system has \"cooled down\" (the temperature is low) or after a certain number of iterations.\n", |
21 | 21 | "\n", |
22 | 22 | "## Core Components\n", |
|
28 | 28 | " * **Neighbor Generators:** These are functions that define how to create a \"neighbor\" schedule from a current one.\n", |
29 | 29 | " * **Objective Functions:** The `energy` function calculates the objective value of a schedule, which is typically the makespan plus any penalties for constraint violations.\n", |
30 | 30 | "\n", |
31 | | - "## The `SimulatedAnnealingSolver`\n", |
| 31 | + "### The `SimulatedAnnealingSolver`\n", |
32 | 32 | "\n", |
33 | 33 | "This is the main entry point for using the solver. When you create an instance of this class, you can configure various parameters of the annealing process:\n", |
34 | 34 | "\n", |
|
38 | 38 | " * `neighbor_generator`: The function used to generate neighboring solutions. The default is `swap_in_critical_path`.\n", |
39 | 39 | " * `seed`: A random seed for reproducibility.\n", |
40 | 40 | "\n", |
41 | | - "## Neighbor Generation Strategies\n", |
| 41 | + "### Neighbor Generation Strategies\n", |
42 | 42 | "\n", |
43 | 43 | "A key part of the simulated annealing process is how you explore the solution space by moving from one solution to a \"neighbor\". This implementation provides three different neighbor generation strategies in `_neighbor_generators.py`:\n", |
44 | 44 | "\n", |
|
48 | 48 | "\n", |
49 | 49 | "3. **`swap_in_critical_path` (Default):** This is the most sophisticated of the three. It identifies the critical path of the current schedule (the sequence of operations that determines the makespan) and looks for consecutive operations on that path that are on the same machine. It then swaps one of these pairs. The idea is that modifying the critical path is the most direct way to try to reduce the makespan. If no such pair exists, it falls back to a standard adjacent swap.\n", |
50 | 50 | "\n", |
51 | | - "## The Objective Function\n", |
| 51 | + "### The Objective Function\n", |
52 | 52 | "\n", |
53 | 53 | "The objective function is what the simulated annealing algorithm tries to minimize. In the context of job shop scheduling, this is typically the makespan (the total time to complete all jobs) plus any penalties for violating constraints (like deadlines).\n", |
54 | 54 | "\n", |
|
58 | 58 | "\n", |
59 | 59 | "### Basic Usage\n", |
60 | 60 | "\n", |
61 | | - "This example shows how to solve a benchmark instance (\"ft06\") with a specific seed to get a reproducible result." |
| 61 | + "This example shows how to solve a specific JSSP problem, the benchmark instance \"ft06\", with a specific seed to get a reproducible result." |
62 | 62 | ] |
63 | 63 | }, |
64 | 64 | { |
|
109 | 109 | "source": [ |
110 | 110 | "### Using a Different Neighbor Generator\n", |
111 | 111 | "\n", |
112 | | - "Although it's not recommended, you can easily plug in a different neighbor generation strategy by passing it to the `SimulatedAnnealingSolver`'s constructor. Here's how to use `swap_adjacent_operations`:\n" |
| 112 | + "You can specify a different neighbor generator by passing it to the `SimulatedAnnealingSolver` constructor. Here, we use `swap_adjacent_operations` as an example although its usage is not recommended: it produces very local changes and often generates infeasible neighbors that require repeated retries, which slows the search and reduces effectiveness. However, for the shake of experimentation:\n" |
113 | 113 | ] |
114 | 114 | }, |
115 | 115 | { |
|
313 | 313 | "# Durations between 2 and 15 to have some variability\n", |
314 | 314 | "duration_creator = get_default_duration_matrix_creator((2, 15))\n", |
315 | 315 | "\n", |
316 | | - "\n", |
317 | 316 | "def deadlines_creator(duration_matrix, rng):\n", |
318 | 317 | " deadlines: list[list[int]] = []\n", |
319 | 318 | " for job_row in duration_matrix:\n", |
|
326 | 325 | " deadlines.append(row)\n", |
327 | 326 | " return deadlines\n", |
328 | 327 | "\n", |
329 | | - "\n", |
330 | 328 | "instance_gen = modular_instance_generator(\n", |
331 | 329 | " machine_matrix_creator=machine_creator,\n", |
332 | 330 | " duration_matrix_creator=duration_creator,\n", |
|
350 | 348 | "baseline_schedule = baseline_solver.solve(instance)\n", |
351 | 349 | "\n", |
352 | 350 | "# Helper: count deadline violations\n", |
353 | | - "\n", |
354 | | - "\n", |
355 | 351 | "def count_deadline_violations(schedule):\n", |
356 | 352 | " violations = 0\n", |
357 | 353 | " for machine_sched in schedule.schedule:\n", |
|
438 | 434 | "source": [ |
439 | 435 | "Note that, in this case, we needed to use a higher initial temperature to effectively explore the solution space and reduce deadline violations. Otherwise, because of the high penalty, very few solutions would be accepted, hindering the search process. In general, the more violations we expect, the higher the initial temperature should be to allow the algorithm to explore a wider range of solutions." |
440 | 436 | ] |
441 | | - }, |
442 | | - { |
443 | | - "cell_type": "markdown", |
444 | | - "id": "51f0604d", |
445 | | - "metadata": {}, |
446 | | - "source": [] |
447 | 437 | } |
448 | 438 | ], |
449 | 439 | "metadata": { |
450 | 440 | "kernelspec": { |
451 | | - "display_name": "job-shop-lib-gOF0HMZJ-py3.12", |
| 441 | + "display_name": "Python 3", |
452 | 442 | "language": "python", |
453 | 443 | "name": "python3" |
454 | 444 | }, |
|
462 | 452 | "name": "python", |
463 | 453 | "nbconvert_exporter": "python", |
464 | 454 | "pygments_lexer": "ipython3", |
465 | | - "version": "3.12.3" |
| 455 | + "version": "3.12.11" |
466 | 456 | } |
467 | 457 | }, |
468 | 458 | "nbformat": 4, |
|
0 commit comments