| """Core Scheduling Optimisation Environment implementing the OpenEnv API contract. |
| |
| Design principles |
| ----------------- |
| * reset() always returns a valid Observation — never raises. |
| * step() clamps reward to [0.0, 1.0] unconditionally. |
| * Task-aware instance routing: conflict_classification and schedule_repair |
| are shown only infeasible instances; feasibility_check sees all 12. |
| * Per-step contextual feedback: the context string and info['grading_breakdown'] |
| give the agent actionable signal on every step, enabling sample-efficient |
| multi-step improvement within a single episode. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import copy |
| import json |
| from typing import Any |
|
|
| from graders.grader_classification import ConflictGrader |
| from graders.grader_detection import FeasibilityGrader |
| from graders.grader_fix import RepairGrader |
| from models import Action, Observation |
|
|
| |
| _GRADERS: dict[str, Any] = { |
| "feasibility_check": FeasibilityGrader(), |
| "conflict_classification": ConflictGrader(), |
| "schedule_repair": RepairGrader(), |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| INSTANCE_BANK: list[dict[str, Any]] = [ |
| |
| |
| |
| |
| |
| { |
| "instance": { |
| "problem_id": "P01", |
| "jobs": [ |
| {"id": "J1", "duration": 4, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| {"id": "J2", "duration": 3, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| {"id": "J3", "duration": 2, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| ], |
| "machines": [ |
| {"id": "M1", "capacity": 1, "available_start": 0, "available_end": 24}, |
| {"id": "M2", "capacity": 1, "available_start": 0, "available_end": 24}, |
| ], |
| "proposed_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J2", "machine_id": "M1", "start_time": 2}, |
| {"job_id": "J3", "machine_id": "M2", "start_time": 0}, |
| ] |
| }, |
| }, |
| "is_feasible": False, |
| "violation_type": "resource_overload", |
| "optimal_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J2", "machine_id": "M1", "start_time": 4}, |
| {"job_id": "J3", "machine_id": "M2", "start_time": 0}, |
| ] |
| }, |
| "optimal_makespan": 7, |
| "description": "J1[0,4) and J2[2,5) overlap on M1 (capacity=1) → resource_overload.", |
| }, |
| |
| |
| |
| |
| |
| { |
| "instance": { |
| "problem_id": "P02", |
| "jobs": [ |
| {"id": "J1", "duration": 5, "deadline": 8, "dependencies": [], "resource_req": 1}, |
| {"id": "J2", "duration": 3, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| ], |
| "machines": [ |
| {"id": "M1", "capacity": 1, "available_start": 0, "available_end": 24}, |
| ], |
| "proposed_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 5}, |
| {"job_id": "J2", "machine_id": "M1", "start_time": 0}, |
| ] |
| }, |
| }, |
| "is_feasible": False, |
| "violation_type": "deadline_violation", |
| "optimal_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J2", "machine_id": "M1", "start_time": 5}, |
| ] |
| }, |
| "optimal_makespan": 8, |
| "description": "J1 starts at t=5 and finishes at t=10, violating deadline=8.", |
| }, |
| |
| |
| |
| |
| |
| { |
| "instance": { |
| "problem_id": "P03", |
| "jobs": [ |
| {"id": "J1", "duration": 3, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| {"id": "J2", "duration": 3, "deadline": 20, "dependencies": ["J1"], "resource_req": 1}, |
| ], |
| "machines": [ |
| {"id": "M1", "capacity": 1, "available_start": 0, "available_end": 24}, |
| {"id": "M2", "capacity": 1, "available_start": 0, "available_end": 24}, |
| ], |
| "proposed_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 5}, |
| {"job_id": "J2", "machine_id": "M2", "start_time": 0}, |
| ] |
| }, |
| }, |
| "is_feasible": False, |
| "violation_type": "precedence_violation", |
| "optimal_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J2", "machine_id": "M2", "start_time": 3}, |
| ] |
| }, |
| "optimal_makespan": 6, |
| "description": "J2 depends on J1; J2 starts at t=0 but J1 does not finish until t=8.", |
| }, |
| |
| |
| |
| |
| |
| { |
| "instance": { |
| "problem_id": "P04", |
| "jobs": [ |
| {"id": "J1", "duration": 4, "deadline": 24, "dependencies": [], "resource_req": 1}, |
| {"id": "J2", "duration": 3, "deadline": 24, "dependencies": [], "resource_req": 1}, |
| ], |
| "machines": [ |
| {"id": "M1", "capacity": 1, "available_start": 8, "available_end": 18}, |
| ], |
| "proposed_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 5}, |
| {"job_id": "J2", "machine_id": "M1", "start_time": 9}, |
| ] |
| }, |
| }, |
| "is_feasible": False, |
| "violation_type": "availability_conflict", |
| "optimal_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 8}, |
| {"job_id": "J2", "machine_id": "M1", "start_time": 12}, |
| ] |
| }, |
| "optimal_makespan": 15, |
| "description": "J1 starts at t=5, before M1's available window [8,18] → availability_conflict.", |
| }, |
| |
| |
| |
| |
| |
| { |
| "instance": { |
| "problem_id": "P05", |
| "jobs": [ |
| {"id": "J1", "duration": 3, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| {"id": "J2", "duration": 3, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| {"id": "J3", "duration": 3, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| ], |
| "machines": [ |
| {"id": "M1", "capacity": 2, "available_start": 0, "available_end": 24}, |
| ], |
| "proposed_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J2", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J3", "machine_id": "M1", "start_time": 0}, |
| ] |
| }, |
| }, |
| "is_feasible": False, |
| "violation_type": "capacity_exceeded", |
| "optimal_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J2", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J3", "machine_id": "M1", "start_time": 3}, |
| ] |
| }, |
| "optimal_makespan": 6, |
| "description": "3 jobs start simultaneously on M1 (capacity=2); concurrent load=3 > 2.", |
| }, |
| |
| |
| |
| |
| |
| { |
| "instance": { |
| "problem_id": "P06", |
| "jobs": [ |
| {"id": "J1", "duration": 5, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| {"id": "J2", "duration": 4, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| {"id": "J3", "duration": 2, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| ], |
| "machines": [ |
| {"id": "M1", "capacity": 1, "available_start": 0, "available_end": 24}, |
| ], |
| "proposed_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J2", "machine_id": "M1", "start_time": 1}, |
| {"job_id": "J3", "machine_id": "M1", "start_time": 8}, |
| ] |
| }, |
| }, |
| "is_feasible": False, |
| "violation_type": "resource_overload", |
| "optimal_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J2", "machine_id": "M1", "start_time": 5}, |
| {"job_id": "J3", "machine_id": "M1", "start_time": 9}, |
| ] |
| }, |
| "optimal_makespan": 11, |
| "description": "J1[0,5) and J2[1,5) overlap on M1 (capacity=1) → resource_overload.", |
| }, |
| |
| |
| |
| |
| |
| |
| { |
| "instance": { |
| "problem_id": "P07", |
| "jobs": [ |
| {"id": "J1", "duration": 4, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| {"id": "J2", "duration": 5, "deadline": 20, "dependencies": ["J1"], "resource_req": 1}, |
| {"id": "J3", "duration": 3, "deadline": 13, "dependencies": ["J2"], "resource_req": 1}, |
| ], |
| "machines": [ |
| {"id": "M1", "capacity": 1, "available_start": 0, "available_end": 24}, |
| {"id": "M2", "capacity": 1, "available_start": 0, "available_end": 24}, |
| {"id": "M3", "capacity": 1, "available_start": 0, "available_end": 24}, |
| ], |
| "proposed_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 3}, |
| {"job_id": "J2", "machine_id": "M2", "start_time": 7}, |
| {"job_id": "J3", "machine_id": "M3", "start_time": 12}, |
| ] |
| }, |
| }, |
| "is_feasible": False, |
| "violation_type": "deadline_violation", |
| |
| "optimal_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J2", "machine_id": "M2", "start_time": 4}, |
| {"job_id": "J3", "machine_id": "M3", "start_time": 9}, |
| ] |
| }, |
| "optimal_makespan": 12, |
| "description": "J1 starts at t=3 (unnecessary idle); J3 finishes at t=15 > deadline=13.", |
| }, |
| |
| |
| |
| |
| |
| { |
| "instance": { |
| "problem_id": "P08", |
| "jobs": [ |
| {"id": "J1", "duration": 3, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| {"id": "J2", "duration": 4, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| {"id": "J3", "duration": 2, "deadline": 20, "dependencies": ["J1", "J2"], "resource_req": 1}, |
| ], |
| "machines": [ |
| {"id": "M1", "capacity": 1, "available_start": 0, "available_end": 24}, |
| {"id": "M2", "capacity": 1, "available_start": 0, "available_end": 24}, |
| {"id": "M3", "capacity": 1, "available_start": 0, "available_end": 24}, |
| ], |
| "proposed_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J2", "machine_id": "M2", "start_time": 0}, |
| {"job_id": "J3", "machine_id": "M3", "start_time": 2}, |
| ] |
| }, |
| }, |
| "is_feasible": False, |
| "violation_type": "precedence_violation", |
| "optimal_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J2", "machine_id": "M2", "start_time": 0}, |
| {"job_id": "J3", "machine_id": "M3", "start_time": 4}, |
| ] |
| }, |
| "optimal_makespan": 6, |
| "description": "J3 depends on J1 and J2; J3 starts at t=2 but J2 does not finish until t=4.", |
| }, |
| |
| |
| |
| |
| |
| { |
| "instance": { |
| "problem_id": "P09", |
| "jobs": [ |
| {"id": "J1", "duration": 3, "deadline": 24, "dependencies": [], "resource_req": 1}, |
| {"id": "J2", "duration": 2, "deadline": 24, "dependencies": [], "resource_req": 1}, |
| ], |
| "machines": [ |
| { |
| "id": "M1", |
| "capacity": 1, |
| "available_start": 0, |
| "available_end": 10, |
| "note": "M1 under maintenance t=[10,15]; use window [0,10] only.", |
| }, |
| ], |
| "proposed_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 9}, |
| {"job_id": "J2", "machine_id": "M1", "start_time": 0}, |
| ] |
| }, |
| }, |
| "is_feasible": False, |
| "violation_type": "availability_conflict", |
| "optimal_schedule": { |
| "assignments": [ |
| {"job_id": "J2", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J1", "machine_id": "M1", "start_time": 2}, |
| ] |
| }, |
| "optimal_makespan": 5, |
| "description": "J1 starts at t=9, extends into maintenance window [10,15] → availability_conflict.", |
| }, |
| |
| |
| |
| |
| |
| { |
| "instance": { |
| "problem_id": "P10", |
| "jobs": [ |
| {"id": "J1", "duration": 2, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| {"id": "J2", "duration": 2, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| {"id": "J3", "duration": 2, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| {"id": "J4", "duration": 2, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| ], |
| "machines": [ |
| {"id": "M1", "capacity": 3, "available_start": 0, "available_end": 24}, |
| ], |
| "proposed_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J2", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J3", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J4", "machine_id": "M1", "start_time": 0}, |
| ] |
| }, |
| }, |
| "is_feasible": False, |
| "violation_type": "capacity_exceeded", |
| "optimal_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J2", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J3", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J4", "machine_id": "M1", "start_time": 2}, |
| ] |
| }, |
| "optimal_makespan": 4, |
| "description": "4 jobs start simultaneously on M1 (capacity=3); concurrent load=4 > 3.", |
| }, |
| |
| |
| |
| |
| { |
| "instance": { |
| "problem_id": "P11", |
| "jobs": [ |
| {"id": "J1", "duration": 4, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| {"id": "J2", "duration": 3, "deadline": 20, "dependencies": [], "resource_req": 1}, |
| {"id": "J3", "duration": 5, "deadline": 20, "dependencies": ["J1"], "resource_req": 1}, |
| ], |
| "machines": [ |
| {"id": "M1", "capacity": 1, "available_start": 0, "available_end": 24}, |
| {"id": "M2", "capacity": 1, "available_start": 0, "available_end": 24}, |
| ], |
| "proposed_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J2", "machine_id": "M2", "start_time": 0}, |
| {"job_id": "J3", "machine_id": "M2", "start_time": 4}, |
| ] |
| }, |
| }, |
| "is_feasible": True, |
| "violation_type": None, |
| "optimal_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J2", "machine_id": "M2", "start_time": 0}, |
| {"job_id": "J3", "machine_id": "M2", "start_time": 4}, |
| ] |
| }, |
| "optimal_makespan": 9, |
| "description": "Fully feasible 3-job, 2-machine schedule — all constraints satisfied.", |
| }, |
| |
| |
| |
| |
| { |
| "instance": { |
| "problem_id": "P12", |
| "jobs": [ |
| {"id": "J1", "duration": 3, "deadline": 30, "dependencies": [], "resource_req": 1}, |
| {"id": "J2", "duration": 2, "deadline": 30, "dependencies": [], "resource_req": 1}, |
| {"id": "J3", "duration": 4, "deadline": 30, "dependencies": [], "resource_req": 1}, |
| {"id": "J4", "duration": 3, "deadline": 30, "dependencies": ["J1", "J2"], "resource_req": 1}, |
| {"id": "J5", "duration": 2, "deadline": 30, "dependencies": ["J3"], "resource_req": 1}, |
| ], |
| "machines": [ |
| {"id": "M1", "capacity": 1, "available_start": 0, "available_end": 24}, |
| {"id": "M2", "capacity": 1, "available_start": 0, "available_end": 24}, |
| {"id": "M3", "capacity": 1, "available_start": 0, "available_end": 24}, |
| ], |
| "proposed_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J2", "machine_id": "M2", "start_time": 0}, |
| {"job_id": "J3", "machine_id": "M3", "start_time": 0}, |
| {"job_id": "J4", "machine_id": "M1", "start_time": 3}, |
| {"job_id": "J5", "machine_id": "M3", "start_time": 4}, |
| ] |
| }, |
| }, |
| "is_feasible": True, |
| "violation_type": None, |
| "optimal_schedule": { |
| "assignments": [ |
| {"job_id": "J1", "machine_id": "M1", "start_time": 0}, |
| {"job_id": "J2", "machine_id": "M2", "start_time": 0}, |
| {"job_id": "J3", "machine_id": "M3", "start_time": 0}, |
| {"job_id": "J4", "machine_id": "M1", "start_time": 3}, |
| {"job_id": "J5", "machine_id": "M3", "start_time": 4}, |
| ] |
| }, |
| "optimal_makespan": 6, |
| "description": "Fully feasible 5-job, 3-machine schedule with fan-in precedence — all constraints satisfied.", |
| }, |
| ] |
|
|
| |
| |
| |
| |
| |
| |
| |
| _TASK_POOLS: dict[str, list[dict[str, Any]]] = { |
| "feasibility_check": INSTANCE_BANK, |
| "conflict_classification": [e for e in INSTANCE_BANK if not e["is_feasible"]], |
| "schedule_repair": [ |
| e for e in INSTANCE_BANK if not e["is_feasible"] and e.get("optimal_schedule") |
| ], |
| } |
|
|
|
|
| class SchedulingOptEnv: |
| """OpenEnv-compatible scheduling optimisation environment. |
| |
| Public API (OpenEnv contract) |
| ----------------------------- |
| reset(task_id: str) → Observation |
| step(action: Action) → (Observation, float, bool, dict) |
| state() → dict |
| """ |
|
|
| def __init__(self) -> None: |
| self._task_id: str = "feasibility_check" |
| self._step: int = 0 |
| self._max_steps: int = 3 |
| |
| self._task_counters: dict[str, int] = {} |
| |
| self._current_instance: dict[str, Any] = {} |
| self._done: bool = True |
| self._history: list[dict[str, Any]] = [] |
| self._cumulative_reward: float = 0.0 |
|
|
| |
| |
| |
|
|
| def reset(self, task_id: str = "feasibility_check") -> Observation: |
| """Start a new episode. |
| |
| Selects the next instance from the task-appropriate pool in round-robin |
| order so that repeated resets present diverse scheduling problems. |
| Always succeeds — never raises an exception. |
| """ |
| self._task_id = task_id |
| self._step = 0 |
| self._done = False |
| self._history = [] |
| self._cumulative_reward = 0.0 |
|
|
| step_limits: dict[str, int] = { |
| "feasibility_check": 3, |
| "conflict_classification": 5, |
| "schedule_repair": 8, |
| } |
| self._max_steps = step_limits.get(task_id, 3) |
|
|
| |
| pool = _TASK_POOLS.get(task_id, INSTANCE_BANK) |
| idx = self._task_counters.get(task_id, 0) % len(pool) |
| self._current_instance = pool[idx] |
| self._task_counters[task_id] = idx + 1 |
|
|
| return Observation( |
| schedule_instance=json.dumps(self._current_instance["instance"], indent=2), |
| task_id=task_id, |
| context=self._build_context(task_id, step=0, last_reward=None), |
| step_number=0, |
| ) |
|
|
| def step(self, action: Action) -> tuple[Observation, float, bool, dict[str, Any]]: |
| """Process one agent action. |
| |
| Returns (observation, reward, done, info). |
| Reward is always clamped to [0.0, 1.0]. |
| """ |
| if self._done: |
| return ( |
| Observation( |
| schedule_instance="{}", |
| task_id=self._task_id, |
| context="Episode is over. Call /reset to start a new episode.", |
| step_number=self._step, |
| ), |
| 0.0, |
| True, |
| {"error": "episode_already_done"}, |
| ) |
|
|
| self._step += 1 |
|
|
| grader = _GRADERS.get(self._task_id, _GRADERS["feasibility_check"]) |
|
|
| reward: float = grader.grade(action, self._current_instance) |
| reward = max(0.0, min(1.0, float(reward))) |
| self._cumulative_reward += reward |
|
|
| |
| breakdown: dict[str, Any] = getattr(grader, "last_breakdown", {}) |
|
|
| |
| self._history.append({ |
| "step": self._step, |
| "action": action.response[:300], |
| "reward": round(reward, 4), |
| }) |
|
|
| |
| done = self._step >= self._max_steps or reward >= 0.95 |
| self._done = done |
|
|
| |
| if done: |
| best = max(h["reward"] for h in self._history) |
| ctx = ( |
| "Episode complete — constraint satisfied." |
| if reward >= 0.95 |
| else f"Max steps reached. Best reward this episode: {best:.2f}." |
| ) |
| obs = Observation( |
| schedule_instance="{}", |
| task_id=self._task_id, |
| context=ctx, |
| step_number=self._step, |
| ) |
| else: |
| obs = Observation( |
| schedule_instance=json.dumps( |
| self._current_instance["instance"], indent=2 |
| ), |
| task_id=self._task_id, |
| context=self._build_context( |
| self._task_id, step=self._step, last_reward=reward |
| ), |
| step_number=self._step, |
| ) |
|
|
| info: dict[str, Any] = { |
| "step_reward": round(reward, 4), |
| "cumulative_reward": round(self._cumulative_reward, 4), |
| "steps_remaining": max(0, self._max_steps - self._step), |
| "instance_description": self._current_instance.get("description", ""), |
| "grading_breakdown": breakdown, |
| } |
| return obs, round(reward, 4), done, info |
|
|
| def state(self) -> dict[str, Any]: |
| """Return a snapshot of the full internal environment state.""" |
| return { |
| "task_id": self._task_id, |
| "step": self._step, |
| "max_steps": self._max_steps, |
| "done": self._done, |
| "cumulative_reward": round(self._cumulative_reward, 4), |
| "history": copy.deepcopy(self._history), |
| "current_instance_id": ( |
| self._current_instance.get("instance", {}).get("problem_id", "") |
| ), |
| "current_instance_feasible": self._current_instance.get("is_feasible"), |
| "task_counters": dict(self._task_counters), |
| "instance_pool_sizes": {k: len(v) for k, v in _TASK_POOLS.items()}, |
| } |
|
|
| |
| |
| |
|
|
| @staticmethod |
| def _build_context( |
| task_id: str, step: int, last_reward: float | None |
| ) -> str: |
| """Build a context string that adapts to the current step and last reward. |
| |
| On the first step (step=0) a clear task description is returned. |
| On retry steps (step>0, last_reward<0.95) an informative hint is appended |
| to guide the agent toward a better answer. |
| """ |
| base_contexts: dict[str, str] = { |
| "feasibility_check": ( |
| "Examine the proposed_schedule against all four constraint categories " |
| "(machine capacity, job deadlines, precedence dependencies, machine " |
| "availability windows). Reply with exactly 'feasible' if every constraint " |
| "is satisfied, or 'infeasible' if any constraint is violated." |
| ), |
| "conflict_classification": ( |
| "The proposed_schedule is infeasible. Identify the PRIMARY constraint " |
| "violation and reply with exactly one of: resource_overload, " |
| "deadline_violation, precedence_violation, availability_conflict, " |
| "capacity_exceeded." |
| ), |
| "schedule_repair": ( |
| "The proposed_schedule is infeasible. Return ONLY a JSON object with key " |
| '"assignments": a list of {"job_id": str, "machine_id": str, ' |
| '"start_time": int} dicts that resolves ALL violations (capacity, ' |
| "deadlines, precedence, availability) and minimises total makespan." |
| ), |
| } |
| ctx = base_contexts.get(task_id, "Analyse the scheduling instance.") |
|
|
| |
| if step > 0 and last_reward is not None and last_reward < 0.95: |
| hints: dict[str, str] = { |
| "feasibility_check": ( |
| " ← Previous answer was incorrect. " |
| "Re-examine all four constraint types carefully." |
| ), |
| "conflict_classification": ( |
| " ← Previous classification was wrong. " |
| "Check whether jobs share a machine simultaneously (resource/capacity), " |
| "miss their deadlines, violate ordering, or run outside availability windows." |
| ), |
| "schedule_repair": ( |
| " ← Previous repair had remaining violations. " |
| "Ensure no two jobs overlap on a capacity-1 machine, every job " |
| "finishes before its deadline, precedence order is respected, and " |
| "all jobs run within machine availability windows." |
| ), |
| } |
| ctx += hints.get(task_id, "") |
|
|
| return ctx |
|
|
| @staticmethod |
| def get_instance_bank() -> list[dict[str, Any]]: |
| """Return the full instance bank (all 12 entries).""" |
| return INSTANCE_BANK |
|
|