Source code for labscheduler.solvers.cp_solver
"""
A solver implementation using google OR-tools to model and solve the JSSP as a constraint program(CP)
"""
import contextlib
import importlib.util
import pickle
import subprocess
from pathlib import Path
from threading import Thread
if not importlib.util.find_spec("ortools"):
msg = (
"The required optional dependency 'ortools' is not installed. Please install it using "
"'pip install .[cpsolver]' to use the CP solver."
)
raise ModuleNotFoundError(msg)
from labscheduler.logging_manager import scheduler_logger
from labscheduler.solver_interface import AlgorithmInfo, JSSPSolver
from labscheduler.structures import (
JSSP,
Schedule,
SolutionQuality,
)
[docs]
class CPSolver(JSSPSolver):
[docs]
def compute_schedule(
self,
inst: JSSP,
time_limit: float,
offset: float,
**kwargs,
) -> tuple[Schedule | None, SolutionQuality]:
problem_data = {"inst": inst, "time_limit": time_limit, "offset": offset, **kwargs}
# Get the path to cp_worker.py relative to this module
worker_path = Path(__file__).parent / "cp_worker.py"
proc = subprocess.Popen( # noqa: S603
["python", str(worker_path)], # noqa: S607
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
# Send pickled problem
pickle.dump(problem_data, proc.stdin)
proc.stdin.close()
result = {}
# Try to receive the pickled result
def reader():
with contextlib.suppress(Exception):
result.update(pickle.load(proc.stdout)) # noqa: S301
# give the process generous (1 extra second) time to produce a result
t = Thread(target=reader, daemon=True)
t.start()
t.join(time_limit + 1)
if t.is_alive():
# timeout reached
proc.kill()
proc.wait()
# get the result
try:
quality = result.get("quality")
schedule = result.get("schedule")
except KeyError:
schedule = None
quality = SolutionQuality.INFEASIBLE
# log stderr for debugging
err = proc.stderr.read()
if err:
scheduler_logger.error(f"Worker stderr:{err.decode('utf-8', errors='replace')}")
return schedule, quality
[docs]
@staticmethod
def get_algorithm_info() -> AlgorithmInfo:
return AlgorithmInfo(
name="CP-Solver",
is_optimal=True,
success_guaranty=True,
max_problem_size=300,
)
[docs]
def is_solvable(self, inst: JSSP) -> bool:
# model, vars_ = self.create_model(inst, 0) # noqa: ERA001
# TODO: there is a method in OR-tools to check solvability
# state = self.solve_cp(model, vars_, 5) # noqa: ERA001
# return state in {OPTIMAL, FEASIBLE} # noqa: ERA001
return True