Aller au contenu
DispatchAtlas
Rechercher

Tutoriels

Ces tutoriels sont des parcours orientés-apprentissage, de bout-en-bout : chaque étape est un script exécutable, chaque sortie montrée est une sortie réelle des données de test empaquetées, et chaque exécution est déterministe — les mêmes graines produisent les mêmes nombres sur votre machine. Complétez d'abord l'installation et le démarrage rapide, puis exécutez chaque script depuis le checkout du dépôt avec uv run python <file>.py.

🧭 Choisissez Un Tutoriel

TutorielVous apprendrezPaquets touchés
Modéliser et résoudre un premier problème de répartitionChoisir une instance de benchmark, exécuter deux solveurs dessus, comparer les horaires, et lire les métadonnées de solveur.dispatchatlas.core, dispatchatlas.bench, dispatchatlas.solve
Exécuter une petite campagne et lire ses résultatsConfigurer une campagne avec-points-de-contrôle, l'exécuter dans un workspace, et charger les résultats pour analyse.dispatchatlas.lab, dispatchatlas.analytica

Des chemins de référence plus profonds continuent là où les tutoriels finissent : contrats de domaine pour le modèle de planification, modèle de benchmarks pour la génération du catalogue, système de solveurs pour le registre complet, moteur de campagnes pour le détail d'orchestration, et exportations d'analyse pour les lots de preuves. Les campagnes plus grandes requièrent les gates statistiques et de publication décrits dans préparation.

🛠️ Tutoriel 1 : Modéliser Et Résoudre Un Premier Problème De Répartition

Le démarrage rapide a résolu un problème avec un solveur. Ce tutoriel va un niveau plus profond : vous choisissez une instance de benchmark spécifique, exécutez deux lignes-de-base de répartition dessus, comparez les horaires qu'elles construisent, et lisez les métadonnées qui expliquent chaque solveur.

1️⃣ Voir ce qu'offre le catalogue de test

Le catalogue de test empaqueté matérialise deux petites instances déterministes par famille de planification depuis une seule graine racine. Sauvegardez ceci comme list_problems.py et exécutez-le avec uv run python list_problems.py :

from dispatchatlas.bench import smoke_benchmark_provider
 
provider = smoke_benchmark_provider(root_seed=20260527)
for problem_id in provider.list_problem_ids():
    print(problem_id.value)

Sortie :

smoke-cloud-edge-0
smoke-cloud-edge-1
smoke-workflow-0
smoke-workflow-1
smoke-machine-scheduling-unrelated-0
smoke-machine-scheduling-unrelated-1
smoke-job-shop-0
smoke-job-shop-1
smoke-flexible-job-shop-0
smoke-flexible-job-shop-1
smoke-permutation-flow-shop-0
smoke-permutation-flow-shop-1
smoke-setup-flow-shop-0
smoke-setup-flow-shop-1
smoke-rcpsp-renewable-0
smoke-rcpsp-renewable-1
smoke-open-shop-0
smoke-open-shop-1
smoke-hybrid-flow-shop-0
smoke-hybrid-flow-shop-1
smoke-distributed-permutation-flow-shop-0
smoke-distributed-permutation-flow-shop-1
smoke-no-wait-flow-shop-0
smoke-no-wait-flow-shop-1
smoke-blocking-flow-shop-0
smoke-blocking-flow-shop-1
smoke-distributed-assembly-flow-shop-0
smoke-distributed-assembly-flow-shop-1
smoke-multi-objective-pfsp-0
smoke-multi-objective-pfsp-1
smoke-rcpsp-max-0
smoke-rcpsp-max-1
smoke-rcpsp-multi-mode-0
smoke-rcpsp-multi-mode-1
smoke-multi-project-rcpsp-0
smoke-multi-project-rcpsp-1
smoke-unrelated-parallel-setup-0
smoke-unrelated-parallel-setup-1
smoke-reentrant-fab-0
smoke-reentrant-fab-1
smoke-distributed-flexible-job-shop-0
smoke-distributed-flexible-job-shop-1
smoke-facility-assignment-0
smoke-facility-assignment-1

Chaque id nomme sa famille de planification et un index d'instance basé-sur-zéro. Le reste du tutoriel utilise smoke-job-shop-0.

2️⃣ Exécuter deux solveurs sur une instance

provider.get_problem prend un ProblemId et retourne un ValidatedProblem — la spécification du problème plus son tampon et rapport de validation, de sorte qu'un solveur ne reçoit jamais une instance non-validée. Les deux solveurs ci-dessous sont des lignes-de-base de répartition déterministes : earliest-start planifie les tâches en ordre topologique d'entrée, tandis que shortest-processing-time priorise les tâches plus courtes. Sauvegardez ceci comme compare_solvers.py :

from dispatchatlas.bench import smoke_benchmark_provider
from dispatchatlas.core import ProblemId, TerminationPolicy, derive_seed
from dispatchatlas.solve import default_solver_registry
 
provider = smoke_benchmark_provider(root_seed=20260527)
validated = provider.get_problem(ProblemId("smoke-job-shop-0"))
spec = validated.spec
 
print(f"problem: {spec.id.value}")
print(f"tasks: {len(spec.tasks)}  resources: {len(spec.resources)}")
 
registry = default_solver_registry()
for solver_id in ("earliest-start", "shortest-processing-time"):
    metadata = registry.get_metadata(solver_id)
    solver = registry.create(solver_id)
    run = solver.solve(
        problem=validated,
        stop=TerminationPolicy(max_iterations=metadata.default_stop.max_iterations),
        seed=derive_seed(20260610, "docs.tutorial.compare", 0),
    )
    makespan = run.result.objective_values[0]
    print(
        f"{solver_id}: feasible={run.result.feasible} "
        f"{makespan.objective_name}={makespan.value:.1f}"
    )

Exécutez-le avec uv run python compare_solvers.py :

problem: smoke-job-shop-0
tasks: 9  resources: 3
earliest-start: feasible=True makespan=353.0
shortest-processing-time: feasible=True makespan=433.0

Les deux horaires sont faisables, et sur cette instance c'est la règle la plus simple qui l'emporte : earliest-start finit à 353.0 tandis que shortest-processing-time, qui privilégie les tâches courtes, finit à 433.0. Cela mérite qu'on s'y arrête, car shortest-processing-time est une bonne règle en général — dans un job shop, elle peut différer une tâche longue qu'une tâche en aval attend, et tout l'horaire attend avec elle. La réputation d'une règle ne vous dit pas ce qu'elle fait sur votre instance.

Et c'est bien là l'intérêt de la forme plutôt que du chiffre. Une instance ne prouve rien dans un sens ni dans l'autre — c'est à cela que servent les campagnes et les méthodes statistiques — mais la comparaison ici (même problème validé, mêmes critères d'arrêt, même graine dérivée) est exactement comment une preuve plus grande est construite, et une exécution unique est exactement ce qu'elle n'est pas.

3️⃣ Lire les métadonnées du solveur

Chaque solveur enregistré porte des métadonnées comme son contrat public : objectifs supportés, étiquettes de capacité, stochasticité, critères d'arrêt par défaut, et une citation canonique. Sauvegardez ceci comme inspect_metadata.py :

from dispatchatlas.solve import default_solver_registry
 
registry = default_solver_registry()
metadata = registry.get_metadata("shortest-processing-time")
print(f"solver: {metadata.solver_id}")
print(f"stochasticity: {metadata.stochasticity}")
print(f"citation: {metadata.citation.reference}")
print("capabilities:", ", ".join(c.value for c in metadata.capabilities))

Exécutez-le avec uv run python inspect_metadata.py :

solver: shortest-processing-time
stochasticity: deterministic
citation: Smith, W. E. (1956). Various optimizers for single-stage production. Naval Research Logistics Quarterly, 3(1-2), 59-66.
capabilities: single-objective, capacity-aware, precedence-aware, constructive, dispatching, deterministic

Ces métadonnées sont ce sur quoi registry.select filtre et ce à partir de quoi le recommandeur de solveurs explique. Le registre complet — lignes-de-base, métaheuristiques, adaptateurs exacts, et la famille NDSO — est catalogué dans algorithmes et système de solveurs.

🧪 Tutoriel 2 : Exécuter Une Petite Campagne Et Lire Ses Résultats

Une campagne est un ensemble configuré d'exécutions — instances de benchmark croisées avec solveurs et objectifs — exécuté avec des graines, budgets, points-de-contrôle explicites, et un dépôt de résultats sur-disque. Ce tutoriel exécute la même forme que la recette suivie à experiments/configs/smoke-pilot.json : deux instances de test, deux lignes-de-base déterministes, un objectif.

1️⃣ Configurer et exécuter la campagne

Sauvegardez ceci comme first_campaign.py. Il déclare la campagne, la valide en un plan déterministe, estime son coût avec une exécution-à-blanc, puis l'exécute dans un workspace local my-campaigns/ :

from pathlib import Path
 
from dispatchatlas.core import TerminationPolicy
from dispatchatlas.lab import (
    CampaignConfig,
    CampaignKind,
    CampaignStage,
    ExecutionMode,
    OutputPolicy,
    ResourceBudget,
    default_campaign_runner,
)
 
workspace = Path("my-campaigns")
 
config = CampaignConfig(
    campaign_id="first-campaign",
    benchmark_ids=("smoke-job-shop-0", "smoke-workflow-0"),
    solver_ids=("earliest-start", "shortest-processing-time"),
    objectives=("makespan",),
    root_seed=20260610,
    seed_namespace="docs.tutorial.first-campaign",
    stop=TerminationPolicy(max_iterations=5),
    output=OutputPolicy(root_dir=str(workspace)),
    stage=CampaignStage.SMOKE,
    kind=CampaignKind.PILOT,
    resources=ResourceBudget(
        max_workers=2,
        max_concurrent_runs=2,
        estimated_seconds_per_run=0.5,
    ),
    execution_mode=ExecutionMode.SEQUENTIAL,
)
 
runner = default_campaign_runner(workspace)
plan = runner.validate(config)
budget = runner.dry_run(plan)
print(f"planned runs: {len(plan.runs)}")
print(f"estimated wall time: {budget.estimated_wall_time_seconds:.1f}s")
 
index = runner.run(plan)
print(f"completed runs: {index.run_count}")
print(f"failed attempts: {len(index.failures)}")

Exécutez-le avec uv run python first_campaign.py :

planned runs: 4
estimated wall time: 2.0s
completed runs: 4
failed attempts: 0

Quatre exécutions est exactement le produit croisé : 2 instances de benchmark × 2 solveurs × 1 objectif, avec une exécution par cellule parce que les deux solveurs sont déterministes. La graine de chaque exécution dérive de root_seed et de la position de l'exécution, de sorte que ré-exécuter le script reproduit les mêmes enregistrements ; le point-de-contrôle laisse une campagne interrompue reprendre sans répéter le travail achevé.

2️⃣ Inspecter ce qui a atterri sur disque

Le runner a écrit un workspace avec la même disposition que le workspace d'expériences suivi :

my-campaigns/
  .checkpoints/first-campaign.json
  ENVIRONMENT.md
  logs/first-campaign_<timestamp>.log
  results/first-campaign/
    plan.json
    environment.json
    earliest-start/smoke-job-shop-0/run_0.json
    earliest-start/smoke-workflow-0/run_0.json
    shortest-processing-time/smoke-job-shop-0/run_0.json
    shortest-processing-time/smoke-workflow-0/run_0.json

Un enregistrement JSON par exécution, adressé par campagne, solveur, benchmark, et index de réplique — le chemin lui-même est l'index. Chaque enregistrement est haché-par-contenu sur sa charge déterministe, ce contre quoi le mode replay vérifie.

3️⃣ Charger les résultats pour analyse

dispatchatlas.analytica lit les répertoires de campagne achevés sans importer le runtime de campagne. Sauvegardez ceci comme read_results.py :

from pathlib import Path
 
from dispatchatlas.analytica import load_result_dataset, summarize_dataset
 
dataset = load_result_dataset(Path("my-campaigns"), "first-campaign")
print(f"campaign: {dataset.campaign_id}")
print(f"completed runs: {len(dataset.completed)}")
 
summary = summarize_dataset(dataset)
for solver in summary.solver_summaries:
    print(
        f"{solver.solver_id}: runs={solver.count} "
        f"feasible={solver.feasible_count} "
        f"mean {solver.objective_name}={solver.mean:.1f}"
    )

Exécutez-le avec uv run python read_results.py :

campaign: first-campaign
completed runs: 4
earliest-start: runs=2 feasible=2 mean makespan=182.1
shortest-processing-time: runs=2 feasible=2 mean makespan=222.1

summarize_dataset calcule des statistiques descriptives par solveur et objectif. Avec seulement deux exécutions par solveur, les méthodes inférentielles (tests de signification, intervalles de confiance) s'acheminent vers la surface des limitations au lieu de produire des résultats sous-puissants — le plancher de puissance-statistique de 30 exécutions indépendantes par cellule stochastique solveur-instance est décrit dans exportations d'analyse.

4️⃣ Exporter un lot de preuves (optionnel)

Le même répertoire de campagne alimente la commande d'exportation, qui écrit un lot de preuves filtré-par-divulgation de tables, figures, et supplément :

uv run dispatchatlas export `
  --campaign-dir .\my-campaigns\results\first-campaign `
  --target-dir .\exports\first-campaign `
  --authorized-output-root .\exports `
  --tier core

Le lot atterrit à exports/first-campaign/evidence-bundles/first-campaign-core/. La page lots de preuves explique les quatre niveaux et le contenu du lot.

🎓 Où cela mène

  • La recette suivie experiments/scripts/run_smoke_pilot.py exécute cette même forme dans le workspace d'expériences et sérialise sa configuration pour l'interface en-ligne-de-commande dispatchatlas-lab.
  • La page moteur de campagnes couvre les modes d'exécution, la politique de réessai, la reprise, la vérification de replay, les protocoles d'arrêt duaux, et les garanties de comparaison-équitable.
  • Les campagnes de phase-complète restent bloquées jusqu'à ce que l'approbation de la conception statistique soit enregistrée — voir préparation de publication.