From 4c7e2a33d2c70459e4b5042206871142ab060a54 Mon Sep 17 00:00:00 2001 From: TohruSu <238259854+TohruSu@users.noreply.github.com> Date: Thu, 15 Oct 2026 14:07:08 +0000 Subject: [PATCH] Refine portfolio documentation and verify package readiness --- .github/workflows/checks.yml | 10 +++ .gitignore | 3 + README.md | 121 ++++++++++++++++++++++++++--------- docs/experiments.md | 1 + docs/simulation.md | 2 +- pyproject.toml | 9 ++- 6 files changed, 111 insertions(+), 35 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 1c7ae3b..41383c9 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -2,6 +2,9 @@ name: Checks on: [push, pull_request] +permissions: + contents: read + jobs: test: runs-on: ubuntu-latest @@ -18,3 +21,10 @@ jobs: - run: ruff check . - run: ruff format --check . - run: mypy + - name: Build and smoke-test wheel + run: | + python -m pip wheel --no-deps --wheel-dir dist . + python -m pip install --force-reinstall --no-deps dist/*.whl + cd /tmp + adaptivequeue --workload mixed --policy edf --seed 42 + python -m adaptivequeue --help diff --git a/.gitignore b/.gitignore index 51f5ed7..61e2562 100644 --- a/.gitignore +++ b/.gitignore @@ -221,3 +221,6 @@ __marimo__/ # Default local benchmark output; reference results live in benchmarks/default. benchmark-results/ + +# Model produced by the README training example. +/policy.json diff --git a/README.md b/README.md index 6da910c..738dc80 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,119 @@ # AdaptiveQueue -A lightweight Python project for experimenting with adaptive background job -scheduling. Includes a discrete-time, single-worker simulator, FIFO, SPT, EDF, -Priority, and seeded Random baselines, and a tabular Q-Learning meta-scheduler. +## Overview -Requires Python 3.10 or newer. Simulation and learning have no runtime -dependencies; benchmark figures use the optional Matplotlib extra. +AdaptiveQueue is a lightweight Python project for comparing background job +schedulers. A single worker must handle jobs with different processing times, +priority weights, and deadlines: a long backup can delay a short, urgent task. +The project investigates whether learning to switch between established scheduling +rules reduces scheduling cost compared with using one rule throughout an episode. -```sh -python -m venv .venv -source .venv/bin/activate -python -m pip install -e '.[dev,benchmark]' +## How the scheduler works + +Jobs execute one at a time without preemption. When the worker becomes idle, the +tabular Q-Learning meta-scheduler selects **FIFO, SPT, EDF, or Priority**; that rule +chooses the next job. The agent observes only waiting jobs and the current time, +using buckets for queue length, deadline slack, short-job proportion, and whether +high-priority work is waiting. It cannot inspect future arrivals. + +```mermaid +flowchart LR + Q["Ready queue + time"] --> S["Q-table selects a rule"] + S --> J["Rule selects one job"] + J --> W["Worker runs job to completion"] + W --> Q ``` -Run an episode with 60 jobs: +Training uses the negative cost of each completed job as reward. Evaluation uses +greedy actions with exploration and learning disabled. FIFO, SPT, EDF, Priority, +and seeded Random can also run as standalone baselines. + +## Experimental results + +The primary objective is to minimize +`J = mean(priority_weight * (tardiness + 0.1 * flow_time))`. +Deadline-miss rate, waiting time, and flow time are secondary metrics. + +Three models trained independently on `mixed` workloads for 1,000 episodes each. +The table shows mean J over 30 matching evaluation seeds per workload, with all +three training runs kept separate: + +| Policy / training seed | Stable | Bursty | Mixed | +| --- | ---: | ---: | ---: | +| Priority | 9.852 | 30.867 | 9.212 | +| Q-Learning / 42 | 9.270 | 28.804 | 9.284 | +| Q-Learning / 2042 | 10.164 | 31.471 | 10.402 | +| Q-Learning / 4042 | 9.210 | 27.130 | 9.900 | + +Priority is the strongest classical baseline on mean J in all three tested +workloads. Two RL models outperform it on stable and bursty workloads, but all +three have higher mean cost on mixed workloads. SPT achieves the lowest mean +deadline-miss rate across all three workloads. + +![Scheduling cost distributions for five baselines and three Q-Learning models](benchmarks/default/figures/scheduling_cost.png) + +See the [full experimental analysis](docs/experiments.md) for all baselines, +secondary metrics, paired comparisons, and limitations. The +[summary CSV](benchmarks/default/summary.csv), +[per-episode CSV](benchmarks/default/per_episode.csv), and +[action frequencies](benchmarks/default/action_distribution.csv) retain the data +behind these comparisons. + +## Quick Start + +Use Python 3.10 or newer from the repository root. Simulation and learning have +no runtime dependencies; plotting uses the optional Matplotlib extra. ```sh +python -m venv .venv +source .venv/bin/activate +python -m pip install -e . adaptivequeue --workload mixed --policy edf --seed 42 ``` -The CLI reports priority-weighted scheduling cost (lower is better), deadline miss -rate, average and 95th-percentile waiting time, and average flow time. Workloads -are `stable`, `bursty`, or `mixed`; policies are `fifo`, `spt`, `edf`, `priority`, -or `random`. Use the same seed and workload settings to compare policies fairly. - -Train a meta-scheduler that chooses FIFO, SPT, EDF, or Priority at each decision, -then evaluate its frozen Q-table on held-out workload seeds: +Train and evaluate a model on held-out workload seeds: ```sh adaptivequeue train --workload mixed --episodes 1000 --seed 42 --output policy.json adaptivequeue evaluate --workload mixed --episodes 20 --model policy.json ``` -Evaluation defaults to seeds following the saved training range, with exploration -disabled. Use `--seed` to choose a different disjoint range. Both commands accept -the workload options below; `train --help` lists learning and exploration settings. - -Run the fixed paired benchmark (three independently trained models and all five -baselines, across three workloads and 30 evaluation seeds): +Evaluation defaults to seeds following the saved training range. Reproduce the +fixed benchmark with all five baselines and three independently trained models: ```sh +python -m pip install -e '.[benchmark]' adaptivequeue benchmark --output-dir benchmark-results ``` -Models are cached in the output directory; `--retrain` regenerates them. -See [experimental results and reproduction details](docs/experiments.md) for -the observed comparisons and CSV definitions. +Benchmark models are cached in the output directory; `--retrain` regenerates them. +Use `adaptivequeue --help`, `adaptivequeue train --help`, or +`adaptivequeue evaluate --help` for workload and learning options. +`python -m adaptivequeue` also works as the command entry point. + +## Implementation and documentation + +The typed Python package uses immutable job records and one shared simulation +engine for complete episodes and individual scheduling steps. Q-tables use +versioned JSON. CSV aggregation uses the standard library; Matplotlib is used +only for benchmark figures. + +- [Simulation rules, state thresholds, and metrics](docs/simulation.md) +- [Experiment protocol and interpretation](docs/experiments.md) +- [Reference outputs and saved Q-tables](benchmarks/default) +- [Source code](src/adaptivequeue) and [focused tests](tests) + +Run the existing checks locally; GitHub Actions runs them on Python 3.10 and 3.12 +and verifies that the built wheel installs and runs: ```sh -python -m adaptivequeue --workload bursty --policy spt --seed 7 \ - --jobs 60 --arrival-rate 0.3 --deadline-factor 3 +python -m pip install -e '.[dev,benchmark]' python -m pytest ruff check . ruff format --check . mypy ``` -See [simulation rules and parameters](docs/simulation.md) or run -`adaptivequeue --help`. The Python API also exposes per-job execution records. -The current scope is simulation, classical baselines, CPU-only tabular -Q-Learning, and reproducible benchmarking. +## License + +[MIT](LICENSE). diff --git a/docs/experiments.md b/docs/experiments.md index 3a5bf48..c0951e8 100644 --- a/docs/experiments.md +++ b/docs/experiments.md @@ -39,6 +39,7 @@ Python 3.12.14 and Matplotlib 3.11.2. - `summary.csv`: 24 rows, one per workload/policy/training run. Metrics are arithmetic means of the 30 **episode metrics**. In particular, mean P95 waiting time averages episode percentiles; it is not a percentile over pooled jobs. + The standalone `evaluate` command pools jobs across episodes instead. `std_primary_cost` is the sample standard deviation over evaluation episodes, not training variability or a confidence interval. - `mean_cost_delta_vs_*` is mean paired `J(policy) - J(baseline)` using matching diff --git a/docs/simulation.md b/docs/simulation.md index 8990022..2a6ce25 100644 --- a/docs/simulation.md +++ b/docs/simulation.md @@ -96,7 +96,7 @@ same engine. Only the engine holds future arrivals. `qlearning.step_action` accepts an action index and returns the completed job, reward, next encoded state, and `done`. Terminal states are represented by `None`, including empty episodes. -The state tuple has 96 possible values: three zero-based bucket indices and a +The state tuple has up to 96 bucket combinations: three zero-based indices and a boolean, computed only from currently waiting jobs and the current time: | Feature | Buckets in order | diff --git a/pyproject.toml b/pyproject.toml index 2499b44..3a2a913 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=68"] +requires = ["setuptools>=77"] build-backend = "setuptools.build_meta" [project] @@ -8,9 +8,14 @@ version = "0.1.0" description = "A job scheduling simulator with classical and tabular Q-Learning policies" readme = "README.md" requires-python = ">=3.10" -license = {file = "LICENSE"} +license = "MIT" +license-files = ["LICENSE"] dependencies = [] +[project.urls] +Repository = "https://github.com/TohruSu/adaptivequeue" +Documentation = "https://github.com/TohruSu/adaptivequeue/tree/main/docs" + [project.optional-dependencies] dev = ["pytest>=8", "ruff>=0.9", "mypy>=1.14"] benchmark = ["matplotlib>=3.8"]