الدروس التعليمية
هذه الدروس مسارات موجَّهة-للتعلّم من-البداية-إلى-النهاية: كل خطوة هي سكربت قابل للتشغيل، وكل
مخرَج معروض هو مخرَج حقيقي من بيانات الدخان المحزومة، وكل تشغيل حتمي — البذور نفسها تُنتِج
الأرقام نفسها على جهازك. أكمِل أولًا التثبيت و
البدء السريع، ثم شغّل كل سكربت من سحب المستودع عبر
uv run python <file>.py.
🧭 اختر درسًا
| الدرس | ستتعلّم | الحزم المُلامَسة |
|---|---|---|
| نمذجة وحلّ أول مسألة إرسال | اختيار نسخة معيار مرجعي، تشغيل مُحلّين عليها، مقارنة الجداول، وقراءة بيانات المُحلّ الوصفية. | dispatchatlas.core، dispatchatlas.bench، dispatchatlas.solve |
| تشغيل حملة صغيرة وقراءة نتائجها | تهيئة حملة بنقاط-تفتيش، تنفيذها إلى مساحة-عمل، وتحميل النتائج للتحليل. | dispatchatlas.lab، dispatchatlas.analytica |
تستمرّ مسارات مرجعية أعمق حيث تنتهي الدروس: عقود المجال لنموذج الجدولة، نموذج المعايير المرجعية لتوليد الكتالوج، نظام المُحلّات للسجل الكامل، محرّك الحملات لتفصيل التنسيق، و تصديرات التحليل لحزم الأدلة. الحملات الأكبر تتطلّب البوابات الإحصائية وبوابات الإصدار الموصوفة في الجاهزية.
🛠️ Tutorial 1: نمذجة وحلّ أول مسألة إرسال
حلّ البدء السريع مسألة واحدة بمُحلّ واحد. يذهب هذا الدرس مستوى أعمق: تختار نسخة معيار مرجعي محدّدة، وتشغّل عليها خطّي-أساس إرسال، وتقارن الجداول التي يبنيانها، وتقرأ البيانات الوصفية التي تشرح كل مُحلّ.
1️⃣ انظر ما يقدّمه كتالوج الدخان
يُجسِّد كتالوج الدخان المحزوم نسختين حتميتين صغيرتين لكل عائلة جدولة من بذرة جذرية واحدة. احفظ
هذا باسم list_problems.py وشغّله عبر 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)المخرَج:
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كل id يُسمّي عائلة جدولته وفهرس نسخة مُصفَّر-البداية. بقية الدرس تستخدم
smoke-job-shop-0.
2️⃣ تشغيل مُحلّين على نسخة واحدة
يأخذ provider.get_problem معرّف ProblemId ويُرجِع ValidatedProblem — مواصفة المسألة زائد
ختم وتقرير التحقّق، بحيث لا يتلقّى مُحلّ أبدًا نسخة غير-مُتحقَّقة. المُحلّان أدناه خطّا-أساس إرسال
حتميان: earliest-start يجدول المهام بترتيب الإدخال الطوبولوجي، بينما shortest-processing-time
يُعطي الأولوية للمهام الأقصر. احفظ هذا باسم 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}"
)شغّله عبر 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كلا الجدولين قابل، وعلى هذه النسخة تفوز القاعدة الأبسط: تنتهي earliest-start عند 353.0 بينما تنتهي shortest-processing-time، التي تُقدِّم المهام الأقصر، عند 433.0. ويستحق هذا وقفة، لأن shortest-processing-time قاعدة جيدة بوجه عام — ففي ورشة المهام (job shop) قد تؤجّل مهمة طويلة تنتظرها مهمة لاحقة، فينتظر الجدول كله معها. سمعة القاعدة لا تُخبِرك بما تفعله على نسختك أنت.
ولهذا فإن المقصود هو الشكل لا الرقم. نسخة واحدة لا تُثبِت شيئًا في أيٍّ من الاتجاهين — ذلك هو غرض الحملات والطرق الإحصائية — لكن المقارنة هنا (المسألة المُتحقَّقة نفسها، معايير التوقّف نفسها، البذرة المُشتقّة نفسها) هي بالضبط كيف يُبنى دليل أكبر، والتشغيل الواحد هو بالضبط ما ليس كذلك.
3️⃣ اقرأ بيانات المُحلّ الوصفية
يحمل كل مُحلّ مُسجَّل بيانات وصفية كعقده العام: الأهداف المدعومة، وسوم القدرة، العشوائية، معايير
التوقّف الافتراضية، واستشهاد قانوني. احفظ هذا باسم 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))شغّله عبر 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هذه البيانات الوصفية هي ما يُرشِّح عليه registry.select وما يشرح منه
مُوصِي المُحلّات. السجل الكامل — خطوط-الأساس، ما-وراء-الاستدلال،
المحوّلات الدقيقة، وعائلة NDSO — مُفهرَس في الخوارزميات و
نظام المُحلّات.
🧪 Tutorial 2: تشغيل حملة صغيرة وقراءة نتائجها
الحملة مجموعة مُهيّأة من التشغيلات — نسخ معيار مرجعي مُتقاطعة مع مُحلّات وأهداف — مُنفَّذة ببذور
وميزانيات ونقاط-تفتيش صريحة، ومستودع نتائج على-القرص. يُشغِّل هذا الدرس الشكل نفسه كالوصفة
المُتتبَّعة في experiments/configs/smoke-pilot.json: نسختا دخان، خطّا-أساس حتميان، هدف واحد.
1️⃣ هيّئ وشغّل الحملة
احفظ هذا باسم first_campaign.py. يُعلِن الحملة، ويتحقّق منها إلى خطة حتمية، ويُقدّر تكلفتها
بتشغيل-جاف، ثم يُنفِّذها إلى مساحة-عمل محلية 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)}")شغّله عبر uv run python first_campaign.py:
planned runs: 4
estimated wall time: 2.0s
completed runs: 4
failed attempts: 0أربع تشغيلات هي بالضبط الجداء الديكارتي: نسختا معيار مرجعي × مُحلّان × هدف واحد، بتشغيل واحد لكل
خلية لأن كلا المُحلّين حتمي. بذرة كل تشغيل تُشتقّ من root_seed وموضع التشغيل، فإعادة تشغيل
السكربت تُعيد إنتاج السجلات نفسها؛ نقطة-التفتيش تدع حملة مُقاطَعة تستأنف دون تكرار العمل
المُنجَز.
2️⃣ افحص ما هبط على القرص
كتب الـ runner مساحة-عمل بالتخطيط نفسه كـمساحة-عمل التجارب المُتتبَّعة:
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سجل JSON واحد لكل تشغيل، مُعنوَن بالحملة والمُحلّ والمعيار المرجعي وفهرس التكرار — المسار نفسه هو
الفهرس. كل سجل مُجزَّأ-بالمحتوى فوق حمولته الحتمية، وذلك ما يتحقّق منه وضع replay.
3️⃣ حمّل النتائج للتحليل
يقرأ dispatchatlas.analytica أدلّة الحملات المُكتمَلة دون استيراد وقت-تشغيل الحملة. احفظ هذا
باسم 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}"
)شغّله عبر 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 إحصاءات وصفية لكل مُحلّ وهدف. بتشغيلين فقط لكل مُحلّ تُوجَّه الطرق
الاستدلالية (اختبارات الدلالة، فترات الثقة) إلى سطح القيود بدل إنتاج نتائج ضعيفة-القوّة — أرضية
القوّة-الإحصائية البالغة 30 تشغيلًا مستقلًّا لكل خلية عشوائية مُحلّ-نسخة موصوفة في
تصديرات التحليل.
4️⃣ صدّر حزمة أدلة (اختياري)
يُغذّي دليل الحملة نفسه أمر التصدير، الذي يكتب حزمة أدلة مُرشَّحة-بالإفصاح من جداول وأشكال وملحق:
uv run dispatchatlas export `
--campaign-dir .\my-campaigns\results\first-campaign `
--target-dir .\exports\first-campaign `
--authorized-output-root .\exports `
--tier coreتهبط الحزمة في exports/first-campaign/evidence-bundles/first-campaign-core/. تشرح صفحة
حزم الأدلة المستويات الأربعة ومحتوى الحزمة.
🎓 إلى أين يقود هذا
- تُشغِّل الوصفة المُتتبَّعة
experiments/scripts/run_smoke_pilot.pyهذا الشكل نفسه إلى مساحة-عمل التجارب وتُسلسِل تهيئتها لواجهة سطر-الأوامرdispatchatlas-lab. - تُغطّي صفحة محرّك الحملات أوضاع التنفيذ، سياسة إعادة-المحاولة، الاستئناف، تحقّق replay، بروتوكولَي التوقّف المزدوجين، وضمانات المقارنة-العادلة.
- تبقى حملات المرحلة-الكاملة محجوبة حتى يُسجَّل اعتماد التصميم الإحصائي — انظر جاهزية الإصدار.