Understanding Ready Operations Filters

[1]:
from job_shop_lib.benchmarking import load_benchmark_instance
from job_shop_lib.dispatching.rules import (
    DispatchingRuleSolver,
    DispatchingRuleType,
)
from job_shop_lib.dispatching import (
    ReadyOperationsFilterType,
    filter_dominated_operations,
    filter_non_immediate_machines,
    create_composite_operation_filter,
    filter_non_idle_machines,
)
import matplotlib.pyplot as plt

from job_shop_lib import JobShopInstance, Operation

from job_shop_lib.dispatching import (
    ReadyOperationsFilterType,
    create_composite_operation_filter,
)

from job_shop_lib.visualization.gantt import (
    create_gantt_chart_video,
    get_partial_gantt_chart_plotter,
)

plt.style.use("ggplot")
[2]:
CPU = 0
GPU = 1
DATA_CENTER = 2

job_1 = [Operation(CPU, 1), Operation(GPU, 1), Operation(DATA_CENTER, 7)]
job_2 = [Operation(GPU, 5), Operation(DATA_CENTER, 1), Operation(CPU, 1)]
job_3 = [Operation(DATA_CENTER, 1), Operation(CPU, 3), Operation(GPU, 2)]

jobs = [job_1, job_2, job_3]

instance = JobShopInstance(jobs, name="Example")
[3]:
solver = DispatchingRuleSolver(
    DispatchingRuleType.SHORTEST_PROCESSING_TIME,
    ready_operations_filter=filter_non_idle_machines,
)


create_gantt_chart_video(
    instance=instance,
    video_path="output/example_instance_spt_non_idle_machines_filter.mp4",
    solver=solver,
    remove_frames=False,
    plot_function=get_partial_gantt_chart_plotter(
        show_available_operations=True
    ),
)
[4]:
from IPython.display import HTML
from base64 import b64encode

mp4 = open(
    "output/example_instance_spt_non_idle_machines_filter.mp4", "rb"
).read()
data_url = (
    "data:output/example_instance_spt_non_idle_machines_filter/mp4;base64,"
    + b64encode(mp4).decode()
)
HTML(
    """
<video width=800 controls>
      <source src="%s" type="video/mp4">
</video>
"""
    % data_url
)
[4]:

Benchmarking

[5]:
import itertools
import pandas as pd


def print_makespans_for_optimizations(instance, dispatching_rule):
    # print(f"Dispatching rule: {dispatching_rule}")
    # print("-" * 50)

    # Initialize a list to store the results
    results = []

    # Get all the filter types from the enum
    filter_types = list(ReadyOperationsFilterType)

    # Generate all possible combinations of the filters (including the empty set)
    all_combinations = []
    for r in range(len(filter_types) + 1):  # From 0 to N filters
        combinations_r = itertools.combinations(filter_types, r)
        all_combinations.extend(combinations_r)

    # For each combination, run the solver
    for filter_combination in all_combinations:
        if not filter_combination:
            # No filters
            ready_operations_filter = None
            filter_description = "No filter"
        elif len(filter_combination) == 1:
            # Single filter
            ready_operations_filter = filter_combination[0]
            filter_description = f"{filter_combination[0].name} filter"
        else:
            # Multiple filters
            ready_operations_filter = create_composite_operation_filter(
                filter_combination
            )
            filter_names = " & ".join(f.name for f in filter_combination)
            filter_description = f"{filter_names} filters"

        # Solve the scheduling problem with the current filter combination
        schedule = DispatchingRuleSolver(
            dispatching_rule, ready_operations_filter=ready_operations_filter
        ).solve(instance)

        # Get the makespan
        makespan = schedule.makespan()

        # Print the result
        # print(f"Makespan {filter_description}: {makespan}")

        # Append the result to the list
        results.append(
            {"Filter Combination": filter_description, "Makespan": makespan}
        )

    # Create a pandas DataFrame from the results
    df = pd.DataFrame(results)

    return df
[6]:
help(filter_non_immediate_machines)
Help on function filter_non_immediate_machines in module job_shop_lib.dispatching._ready_operation_filters:

filter_non_immediate_machines(dispatcher: job_shop_lib.dispatching._dispatcher.Dispatcher, operations: list[job_shop_lib._operation.Operation]) -> list[job_shop_lib._operation.Operation]
    Filters out all the operations associated with machines which earliest
    operation is not the current time.

[7]:
help(filter_dominated_operations)
Help on function filter_dominated_operations in module job_shop_lib.dispatching._ready_operation_filters:

filter_dominated_operations(dispatcher: job_shop_lib.dispatching._dispatcher.Dispatcher, operations: list[job_shop_lib._operation.Operation]) -> list[job_shop_lib._operation.Operation]
    Filters out all the operations that are dominated.
    An operation is dominated if there is another operation that ends before
    it starts on the same machine.

[8]:
help(filter_non_idle_machines)
Help on function filter_non_idle_machines in module job_shop_lib.dispatching._ready_operation_filters:

filter_non_idle_machines(dispatcher: job_shop_lib.dispatching._dispatcher.Dispatcher, operations: list[job_shop_lib._operation.Operation]) -> list[job_shop_lib._operation.Operation]
    Filters out all the operations associated with non-idle machines.

    A machine is considered idle if there are no ongoing operations
    currently scheduled on it. This filter removes operations that are
    associated with machines that are busy (i.e., have at least one
    uncompleted operation).

    Utilizes :meth:``Dispatcher.ongoing_operations()`` to determine machine
    statuses.

    Args:
        dispatcher: The dispatcher object.
        operations: The list of operations to filter.

    Returns:
        The list of operations that are associated with idle machines.

[9]:
ta01 = load_benchmark_instance("ta01")
ta01
[9]:
JobShopInstance(name=ta01, num_jobs=15, num_machines=15)
[10]:
dfs = []
for pdr in DispatchingRuleType:
    df = print_makespans_for_optimizations(ta01, pdr)
    print()
    # Chamge the number of xticks to 10
    df.sort_values(by="Makespan", ascending=False).plot.barh(x="Filter Combination", y="Makespan", title=pdr.name, xlim=(1500, 2000), xticks=range(1500, 2001, 50))
    dfs.append(df)





../_images/examples_12-ReadyOperationsFilter_11_1.png
../_images/examples_12-ReadyOperationsFilter_11_2.png
../_images/examples_12-ReadyOperationsFilter_11_3.png
../_images/examples_12-ReadyOperationsFilter_11_4.png
../_images/examples_12-ReadyOperationsFilter_11_5.png
[11]:
df_combined = pd.concat(dfs, keys=[pdr.name for pdr in DispatchingRuleType])

df_combined.groupby("Filter Combination").agg("mean").sort_values(by="Makespan", ascending=False).plot.barh(title="Average Makespan")
[11]:
<Axes: title={'center': 'Average Makespan'}, ylabel='Filter Combination'>
../_images/examples_12-ReadyOperationsFilter_12_1.png
[ ]: