Tutoriales
Estos tutoriales son recorridos orientados-al-aprendizaje, de extremo-a-extremo: cada paso
es un script ejecutable, cada salida mostrada es salida real de los datos de prueba
empaquetados, y cada corrida es determinista — las mismas semillas producen los mismos
números en tu máquina. Completa primero la
instalación y el
inicio rápido, luego ejecuta cada script desde el
checkout del repositorio con uv run python <file>.py.
🧭 Elige Un Tutorial
| Tutorial | Aprenderás | Paquetes tocados |
|---|---|---|
| Modelar y resolver un primer problema de despacho | Elegir una instancia de benchmark, ejecutar dos solvers sobre ella, comparar horarios, y leer metadatos de solver. | dispatchatlas.core, dispatchatlas.bench, dispatchatlas.solve |
| Ejecutar una campaña pequeña y leer sus resultados | Configurar una campaña con-checkpoints, ejecutarla en un workspace, y cargar los resultados para análisis. | dispatchatlas.lab, dispatchatlas.analytica |
Rutas de referencia más profundas continúan donde los tutoriales terminan: contratos de dominio para el modelo de planificación, modelo de benchmarks para la generación del catálogo, sistema de solvers para el registro completo, motor de campañas para el detalle de orquestación, y exportaciones de análisis para los paquetes de evidencia. Las campañas mayores requieren los gates estadísticos y de lanzamiento descritos en preparación.
🛠️ Tutorial 1: Modelar Y Resolver Un Primer Problema De Despacho
El inicio rápido resolvió un problema con un solver. Este tutorial va un nivel más profundo: eliges una instancia de benchmark específica, ejecutas dos líneas-base de despacho sobre ella, comparas los horarios que construyen, y lees los metadatos que explican cada solver.
1️⃣ Ver qué ofrece el catálogo de prueba
El catálogo de prueba empaquetado materializa dos pequeñas instancias deterministas por
familia de planificación desde una única semilla raíz. Guarda esto como list_problems.py
y ejecútalo con 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)Salida:
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-1Cada id nombra su familia de planificación y un índice de instancia
de-base-cero. El resto del tutorial usa smoke-job-shop-0.
2️⃣ Ejecutar dos solvers sobre una instancia
provider.get_problem toma un ProblemId y devuelve un ValidatedProblem — la
especificación del problema más su sello y reporte de validación, de modo que un solver
nunca recibe una instancia no-validada. Los dos solvers de abajo son líneas-base de despacho
deterministas: earliest-start planifica tareas en orden topológico de entrada, mientras
que shortest-processing-time prioriza tareas más cortas. Guarda esto como
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}"
)Ejecútalo con 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.0Ambos horarios son factibles, y en esta instancia gana la regla más sencilla: earliest-start termina en 353.0 mientras que shortest-processing-time, que prioriza las tareas más cortas, termina en 433.0. Vale la pena detenerse aquí, porque shortest-processing-time es una buena regla en general — en un job shop puede posponer una tarea larga que una tarea posterior está esperando, y el horario entero espera con ella. La reputación de una regla no le dice lo que hace en su instancia.
Y de eso se trata: de la forma, no del número. Una instancia no prueba nada en ningún sentido — para eso están las campañas y los métodos estadísticos — pero la comparación aquí (mismo problema validado, mismos criterios de parada, misma semilla derivada) es exactamente cómo se construye evidencia mayor, y una sola ejecución es exactamente lo que no lo es.
3️⃣ Leer los metadatos del solver
Cada solver registrado lleva metadatos como su contrato público: objetivos soportados,
etiquetas de capacidad, estocasticidad, criterios de parada por defecto, y una citación
canónica. Guarda esto como 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))Ejecútalo con 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, deterministicEstos metadatos son sobre lo que registry.select filtra y desde lo que el
recomendador de solvers explica. El registro completo —
líneas-base, metaheurísticas, adaptadores exactos, y la familia NDSO — está catalogado en
algoritmos y sistema de solvers.
🧪 Tutorial 2: Ejecutar Una Campaña Pequeña Y Leer Sus Resultados
Una campaña es un conjunto configurado de corridas — instancias de benchmark cruzadas con
solvers y objetivos — ejecutado con semillas, presupuestos, checkpoints explícitos, y un
repositorio de resultados en-disco. Este tutorial ejecuta la misma forma que la receta
rastreada en experiments/configs/smoke-pilot.json: dos instancias de prueba, dos
líneas-base deterministas, un objetivo.
1️⃣ Configurar y ejecutar la campaña
Guarda esto como first_campaign.py. Declara la campaña, la valida en un plan determinista,
estima su costo con una corrida-seca, luego la ejecuta en 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)}")Ejecútalo con uv run python first_campaign.py:
planned runs: 4
estimated wall time: 2.0s
completed runs: 4
failed attempts: 0Cuatro corridas es exactamente el producto cruzado: 2 instancias de benchmark × 2 solvers ×
1 objetivo, con una corrida por celda porque ambos solvers son deterministas. La semilla de
cada corrida deriva de root_seed y la posición de la corrida, de modo que re-ejecutar el
script reproduce los mismos registros; el checkpoint deja que una campaña interrumpida
reanude sin repetir trabajo completado.
2️⃣ Inspeccionar qué aterrizó en disco
El runner escribió un workspace con la misma disposición que el workspace de experimentos rastreado:
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.jsonUn registro JSON por corrida, direccionado por campaña, solver, benchmark, e índice de
réplica — la ruta misma es el índice. Cada registro está hasheado-por-contenido sobre su
carga determinista, que es contra lo que el modo replay verifica.
3️⃣ Cargar los resultados para análisis
dispatchatlas.analytica lee directorios de campaña completados sin importar el runtime de
campaña. Guarda esto como 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}"
)Ejecútalo con 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.1summarize_dataset computa estadísticas descriptivas por solver y objetivo. Con solo dos
corridas por solver los métodos inferenciales (pruebas de significancia, intervalos de
confianza) se enrutan a la superficie de limitaciones en vez de producir resultados
sub-potenciados — el piso de potencia-estadística de 30 corridas independientes por celda
estocástica solver-instancia se describe en exportaciones de análisis.
4️⃣ Exportar un paquete de evidencia (opcional)
El mismo directorio de campaña alimenta el comando de exportación, que escribe un paquete de evidencia filtrado-por-divulgación de tablas, figuras, y suplemento:
uv run dispatchatlas export `
--campaign-dir .\my-campaigns\results\first-campaign `
--target-dir .\exports\first-campaign `
--authorized-output-root .\exports `
--tier coreEl paquete aterriza en
exports/first-campaign/evidence-bundles/first-campaign-core/. La página de
paquetes de evidencia explica los cuatro niveles y el contenido del
paquete.
🎓 A dónde lleva esto
- La receta rastreada
experiments/scripts/run_smoke_pilot.pyejecuta esta misma forma en el workspace de experimentos y serializa su configuración para la interfaz de línea-de-comandosdispatchatlas-lab. - La página del motor de campañas cubre modos de ejecución, política de reintentos, reanudación, verificación de replay, protocolos de parada duales, y garantías de comparación-justa.
- Las campañas de etapa-completa permanecen bloqueadas hasta que se registre la aprobación del diseño estadístico — ver preparación de lanzamiento.