Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: Checks

on: [push, pull_request]

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
Expand All @@ -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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
121 changes: 89 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -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).
1 change: 1 addition & 0 deletions docs/experiments.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/simulation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
9 changes: 7 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[build-system]
requires = ["setuptools>=68"]
requires = ["setuptools>=77"]
build-backend = "setuptools.build_meta"

[project]
Expand All @@ -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"]
Expand Down
Loading