{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ๐ŸŽฏ [MOV02] ์ˆ˜์†ก์ง€์› ์‹ค์ ยท๋น„์šฉ ๊ฐ€์‹œํ™” Tool\n", "### Google Colab ์‹ค์Šต ํ™˜๊ฒฝ (์ˆ˜์†กยท๋ฌผ๋ฅ˜ยท์ฐฝ๊ณ  / Level 2)\n", "\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/edunlab/milicontent2/blob/main/uploads/MOV02/MOV02_solution.ipynb)\n", "\n", "---\n", "\n", "## ๐Ÿ“Œ 1. ํ”„๋กœ์ ํŠธ ๊ฐœ์š” ๋ฐ ๋ฐฐ๊ฒฝ\n", "\n", "| ํ•ญ๋ชฉ | ๋‚ด์šฉ |\n", "| :--- | :--- |\n", "| **๊ณผ์ • ๋ถ„๋ฅ˜** | ์ˆ˜์†กยท๋ฌผ๋ฅ˜ยท์ฐฝ๊ณ  (Level 2) |\n", "| **ํ•™์Šต ์†Œ์š” ์‹œ๊ฐ„** | 10๊ฐœ ๋ฏธ์…˜ (5์ผ / ์ด 27.0์‹œ๊ฐ„) |\n", "| **ํ™˜๊ฒฝ ์‚ฌ์–‘** | Python 3.10+, numpy, pandas, matplotlib, scipy, scikit-learn |\n", "\n", "### ๐Ÿ“– ํ”„๋กœ์ ํŠธ ๋ชฉํ‘œ\n", "NATO ๋™๋งน์ „ํ™˜์‚ฌ๋ น๋ถ€(ACT)๋Š” ๋‹ค๊ตญ์  ๊ตฐ์ˆ˜ ์ง€์›์—์„œ ์ˆ˜์†ก ์š”์ฒญ๋ถ€ํ„ฐ ๋„์ฐฉ๊นŒ์ง€์˜ ์‹œ๊ฐ„, ์ฒ˜๋ฆฌ ๊ธฐ๊ฐ„, ๋ฌผ๋™๋Ÿ‰๊ณผ ์ˆ˜์†ก๋Šฅ๋ ฅ, ์ง€์—ฐ ์›์ธ์„ ํ•œ ํ™”๋ฉด์—์„œ ๋ณด๋Š” ๋ฌผ๋ฅ˜ยท์ˆ˜์†ก ์„ฑ๊ณผ ๊ฐ€์‹œํ™”๋ฅผ ๊ฐ•์กฐํ•ด ์™”๋‹ค. ๋ณธ ํ”„๋กœ์ ํŠธ๋Š” ์ด ๊ณต๊ฐœ ์‚ฌ๋ก€๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๊ต์œก์šฉ ๊ฐ€์ƒ ๋„๋กœ๋ง๊ณผ ์ˆ˜์†ก ์ž์‚ฐ ์ž๋ฃŒ๋ฅผ ์ฝ”๋“œ๋กœ ๋งŒ๋“ค์–ด, ์ˆ˜์†ก์ง€์› ์‹ค์ ๊ณผ ๋น„์šฉ์„ ๊ทผ๊ฑฐ๋กœ ๊ฒฝ๋กœ์™€ ์ž์‚ฐ์„ ๊ฒฐ์ •ํ•˜๋Š” ๊ณผ์ •์„ ๊ตฌํ˜„ํ•œ๋‹ค. ํ•™์Šต์ž๋Š” ์ž์‚ฐยท๋„๋กœ๋ง ์Šคํ‚ค๋งˆ ์ •์˜, ๋„๋กœ ํŒŒ๊ดดยทํ†ต์ œ ๊ตฌ์—ญ ์ „์ฒ˜๋ฆฌ, ๋‹ค์ต์ŠคํŠธ๋ผ Baseline, ์œ„ํ˜‘ ํšŒํ”ผยท์ ์žฌ ํšจ์œจ ์ถ”์ฒœ, ์ƒ์กดํ™•๋ฅ ยท์ •์‹œ๋„์ฐฉ ์‹ ๋ขฐ๋„ ์ ์ˆ˜, ๊ด€์ œ ๋Œ€์‹œ๋ณด๋“œ, ์ˆ˜์†กํ†ต์ œ๊ด€ ์Šน์ธ(HITL), ๋„๋กœ 50๊ณณ ์ฐจ๋‹จ ์ŠคํŠธ๋ ˆ์Šค ์‹œํ—˜, AAR ์žฌ๋ณด์ •, ์ตœ์ข… ํ†ตํ•ฉ์„ ์ฐจ๋ก€๋กœ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์ตœ์ข… ๋ชฉํ‘œ๋Š” ์ˆ˜์†ก ๋ฆฌ๋“œํƒ€์ž„๊ณผ ํ†คkm๋‹น ๋น„์šฉ์„ ๋‚ฎ์ถ”๋ฉด์„œ ์œ„ํ—˜ ๊ฒฝ๋กœ์˜ ๊ฐ•ํ–‰ ์—ฌ๋ถ€๋Š” ๋ฐ˜๋“œ์‹œ ์‚ฌ๋žŒ์ด ๊ฒฐ์ •ํ•˜๋„๋ก ํ•˜๋Š” ์ˆ˜์†ก์ง€์› ์‹ค์ ยท๋น„์šฉ ๊ฐ€์‹œํ™” ๋„๊ตฌ๋ฅผ ์™„์„ฑํ•˜๋Š” ๊ฒƒ์ด๋‹ค.\n", "\n", "> **โš ๏ธ [๊ต์œก์šฉ ๊ฐ€์ƒ ๋ฐ์ดํ„ฐ์…‹ ์›์น™]**\n", "> - ๋ณธ ๋…ธํŠธ๋ถ์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ(์ œ์›, ๋กœ๊ทธ, ์‹œ๋‚˜๋ฆฌ์˜ค ๋“ฑ)๋Š” ๋ณด์•ˆ ๊ทœ์ •์„ ์ค€์ˆ˜ํ•˜์—ฌ **์ฝ”๋“œ ๋‚ด ๋‚œ์ˆ˜ ๊ธฐ๋ฐ˜ ๊ฐ€์ƒ ๋ฐ์ดํ„ฐ๋กœ ์ƒ์„ฑ**๋ฉ๋‹ˆ๋‹ค.\n", "> - ์‹ค์ œ ๋ถ€๋Œ€ ์œ„์น˜, ์ž‘์ „ ์ธ๊ฐ€, ์‹ค์ œ ์ „์žฅ ์ •๋ณด, ๊ตฐ์‚ฌ ๊ธฐ๋ฐ€ ๋ฐ์ดํ„ฐ๋Š” ์ผ์ฒด ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.\n", "\n", "---\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# ============================================================================\n", "# [Google Colab ํ™˜๊ฒฝ ์„ค์ •] ํ•„์ˆ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜ ๋ฐ ์ž„ํฌํŠธ\n", "# ============================================================================\n", "!pip install -q numpy pandas matplotlib scipy scikit-learn\n", "\n", "import os\n", "import sys\n", "import time\n", "import math\n", "import json\n", "import re\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "import warnings\n", "warnings.filterwarnings('ignore')\n", "\n", "# ๊ทธ๋ž˜ํ”„ ์˜๋ฌธ ํฐํŠธ ๊ธฐ๋ณธ ์„ค์ • (ํ™˜๊ฒฝ ๋…๋ฆฝ์„ฑ ๋ณด์žฅ)\n", "plt.rcParams['font.sans-serif'] = 'DejaVu Sans'\n", "plt.rcParams['axes.unicode_minus'] = False\n", "\n", "print(f\"์‹คํ–‰ ํ™˜๊ฒฝ ์ค€๋น„ ์™„๋ฃŒ (Python {sys.version.split()[0]})\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 01. ์ „์žฅ ์ˆ˜์†ก ์ž์‚ฐ(ํŠธ๋Ÿญ, ํŠธ๋ ˆ์ผ๋Ÿฌ, ์ˆ˜์†ก๊ธฐ) ์ œ์› ๋ฐ ๋„๋กœ๋ง ๋„คํŠธ์›Œํฌ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ ์ •์˜\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 1.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ „์žฅ ์ˆ˜์†ก ์ž์‚ฐ(ํŠธ๋Ÿญ, ํŠธ๋ ˆ์ผ๋Ÿฌ) ์ œ์› ๋ฐ ๋„๋กœ๋ง ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ๋ฅผ ์ •์˜ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **ํŠธ๋ŸญยทํŠธ๋ ˆ์ผ๋Ÿฌยท์ˆ˜์†ก๊ธฐ์˜ ๊ต์œก์šฉ ๊ฐ€์ • ์ œ์›๊ณผ ๋„๋กœ๋ง(๋…ธ๋“œยท๊ฐ„์„ ยท๊ต๋Ÿ‰ ํ•˜์ค‘) ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ๋ฅผ ์ •์˜ํ•˜๊ณ , ์ˆ˜์†ก์ง€์› ์‹ค์ ยท๋น„์šฉ ๋ณด๊ณ  ์—…๋ฌด๋ฅผ AS-IS/TO-BE๋กœ ๋น„๊ตํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์ž์‚ฐ(์ ์žฌ๋Ÿ‰ยท์†๋„ยทkm๋‹น ๋น„์šฉ), ๋…ธ๋“œ, ๊ฐ„์„ (๊ฑฐ๋ฆฌยท๋„๋กœ ๋“ฑ๊ธ‰ยท๊ต๋Ÿ‰ ํ•˜์ค‘ยทํ†ตํ–‰ ์—ฌ๋ถ€), ์ˆ˜์†ก ์š”์ฒญ์˜ ํ•„๋“œ์™€ ํ—ˆ์šฉ๊ฐ’์„ ์ • ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `docs/mov02_as_is_to_be_spec.md` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ํ˜„ํ–‰ ์—…๋ฌด(AS-IS)์™€ ๊ฐœ์„  ์—…๋ฌด(TO-BE) ๋น„๊ต์˜ ๋ช…ํ™•์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์—…๋ฌด ๋‹จ๊ณ„ ๋ถ„ํ•ด 10๋‹จ๊ณ„ ์ด์ƒ, ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„ 3๊ฐœ์†Œ ์ด์ƒ ๋„์ถœ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์—…๋ฌด ์ •์˜์„œ ๋ฐ ์ž…์ถœ๋ ฅ ๋ฐ์ดํ„ฐ ๋ช…์„ธ ์ž‘์„ฑ ์™„๋ฃŒ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ์‹ค๋ฌด ํ”„๋กœ์„ธ์Šค์™€์˜ ์ผ์น˜์„ฑ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์ „์žฅ ์ˆ˜์†ก ์ž์‚ฐ(ํŠธ๋ŸญยทํŠธ๋ ˆ์ผ๋Ÿฌยท์ˆ˜์†ก๊ธฐ) ์ œ์›๊ณผ ๋„๋กœ๋ง(๋…ธ๋“œยท๊ฐ„์„ ) ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ๋ฅผ ์ •์˜ํ•ด ๊ฐ€์ƒ ์ƒ˜ํ”Œ์„ ๊ฒ€์ฆํ•˜๊ณ , ์ˆ˜์†ก์ง€์› ์‹ค์ ยท๋น„์šฉ ๋ณด๊ณ  ์—…๋ฌด๋ฅผ AS-IS/TO-BE ๋‹จ๊ณ„๋กœ ๋ถ„ํ•ดํ•ด ๋ณ‘๋ชฉ ๊ตฌ๊ฐ„์„ ๋„์ถœํ•œ๋‹ค.\"\"\"\n", "import copy\n", "import heapq\n", "import re\n", "import numpy as np\n", "import pandas as pd\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋„๋กœ๋งยท์ˆ˜์†ก ์ž์‚ฐ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์ง€๋ช…ยท๋ถ€๋Œ€ยท์žฅ๋น„ ์ œ์› ์—†์Œ)\n", "# ๊ฒฉ์žํ˜• ๊ฐ€์ƒ ๋„๋กœ๋ง(๋…ธ๋“œ=๊ต์ฐจ์ , ๊ฐ„์„ =๋„๋กœ ๊ตฌ๊ฐ„)์— ๋„๋กœ ๋“ฑ๊ธ‰ยท๊ต๋Ÿ‰ ํ•˜์ค‘ยท๊ฐ€์ƒ ์œ„ํ˜‘ ๊ตฌ์—ญ์„ ๋ถ™์ธ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "ASSETS = { # ๊ต์œก์šฉ ๊ฐ€์ • ์ œ์›(์‹ค์ œ ์žฅ๋น„ ์ œ์› ์•„๋‹˜)\n", " \"truck\": {\"name\": \"์ค‘ํ˜• ํŠธ๋Ÿญ\", \"cap_t\": 5.0, \"speed_kmh\": 50, \"cost_km\": 1.0, \"weight_t\": 10.0},\n", " \"trailer\": {\"name\": \"๋Œ€ํ˜• ํŠธ๋ ˆ์ผ๋Ÿฌ\", \"cap_t\": 20.0, \"speed_kmh\": 40, \"cost_km\": 2.4, \"weight_t\": 30.0},\n", " \"airlift\": {\"name\": \"์ˆ˜์†ก๊ธฐ\", \"cap_t\": 12.0, \"speed_kmh\": 300, \"cost_km\": 9.0, \"weight_t\": 0.0},\n", "}\n", "ROAD_FACTOR = {\"paved\": 1.0, \"unpaved\": 0.6} # ๋„๋กœ ๋“ฑ๊ธ‰๋ณ„ ์†๋„ ๊ณ„์ˆ˜\n", "\n", "\n", "def make_network(seed=5, rows=6, cols=8, spacing=5.0):\n", " \"\"\"๊ฒฉ์žํ˜• ๊ฐ€์ƒ ๋„๋กœ๋ง. ๋…ธ๋“œ ์ขŒํ‘œ(km)์™€ ๊ฐ„์„  ๋ชฉ๋ก(๊ฑฐ๋ฆฌยท๋„๋กœ๋“ฑ๊ธ‰ยท๊ต๋Ÿ‰ ํ•˜์ค‘)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " nodes = {f\"N{r:02d}{c:02d}\": (c * spacing + rng.uniform(-1, 1), r * spacing + rng.uniform(-1, 1))\n", " for r in range(rows) for c in range(cols)}\n", " edges = []\n", "\n", " def add(a, b):\n", " (x1, y1), (x2, y2) = nodes[a], nodes[b]\n", " bridge = bool(rng.random() < 0.15)\n", " edges.append({\"id\": f\"E{len(edges):03d}\", \"u\": a, \"v\": b,\n", " \"km\": round(float(np.hypot(x2 - x1, y2 - y1) * rng.uniform(1.0, 1.25)), 2),\n", " \"road\": \"paved\" if rng.random() < 0.65 else \"unpaved\", \"bridge\": bridge,\n", " \"limit_t\": float(rng.choice([20, 40, 60])) if bridge else None, \"open\": True})\n", " for r in range(rows):\n", " for c in range(cols):\n", " if c + 1 < cols:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r:02d}{c + 1:02d}\")\n", " if r + 1 < rows:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r + 1:02d}{c:02d}\")\n", " if r + 1 < rows and c + 1 < cols and rng.random() < 0.25:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r + 1:02d}{c + 1:02d}\")\n", " return nodes, edges\n", "\n", "\n", "def make_threats(seed=9, n=3, xlim=(8, 28), ylim=(3, 22)):\n", " \"\"\"๊ฐ€์ƒ ์œ„ํ˜‘ ๊ตฌ์—ญ(์›ํ˜•). p = ๊ตฌ๊ฐ„ 1ํšŒ ํ†ต๊ณผ ์‹œ ํ”ผํ•ด ๊ฐ€๋Šฅ์„ฑ(๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " return [{\"id\": f\"TZ{i + 1}\", \"x\": float(rng.uniform(*xlim)), \"y\": float(rng.uniform(*ylim)),\n", " \"r\": float(rng.uniform(3, 5)), \"p\": float(round(rng.uniform(0.05, 0.2), 3))} for i in range(n)]\n", "\n", "\n", "def edge_risk(e, nodes, threats):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 1] ์ „์žฅ ์ˆ˜์†ก ์ž์‚ฐ(ํŠธ๋Ÿญ, ํŠธ๋ ˆ์ผ๋Ÿฌ, ์ˆ˜์†ก๊ธฐ) ์ œ์› ๋ฐ ๋„๋กœ๋ง ๋„คํŠธ์›Œํฌ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ ์ •์˜ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def edge_minutes(e, asset=\"truck\"):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 1] ์ „์žฅ ์ˆ˜์†ก ์ž์‚ฐ(ํŠธ๋Ÿญ, ํŠธ๋ ˆ์ผ๋Ÿฌ, ์ˆ˜์†ก๊ธฐ) ์ œ์› ๋ฐ ๋„๋กœ๋ง ๋„คํŠธ์›Œํฌ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ ์ •์˜ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def passable(e, gross_t):\n", " return e[\"open\"] and (not e[\"bridge\"] or e[\"limit_t\"] >= gross_t)\n", "\n", "\n", "def build_adj(nodes, edges, weight_fn):\n", " \"\"\"weight_fn(e)๊ฐ€ None์ด๋ฉด ํ†ตํ–‰ ๋ถˆ๊ฐ€ ๊ฐ„์„ ์œผ๋กœ ๋ณด๊ณ  ์ œ์™ธํ•œ๋‹ค.\"\"\"\n", " adj = {n: [] for n in nodes}\n", " for e in edges:\n", " w = weight_fn(e)\n", " if w is not None:\n", " adj[e[\"u\"]].append((e[\"v\"], w, e))\n", " adj[e[\"v\"]].append((e[\"u\"], w, e))\n", " return adj\n", "\n", "\n", "def dijkstra(adj, src, dst):\n", " \"\"\"๋‹ค์ต์ŠคํŠธ๋ผ ์ตœ๋‹จ๊ฒฝ๋กœ. (๋น„์šฉ, ๋…ธ๋“œ ๊ฒฝ๋กœ, ๊ฐ„์„  ๋ชฉ๋ก). ๋„๋‹ฌ ๋ถˆ๊ฐ€๋ฉด (inf, [], []).\"\"\"\n", " dist, prev, done, pq = {src: 0.0}, {}, set(), [(0.0, src)]\n", " while pq:\n", " d, u = heapq.heappop(pq)\n", " if u in done:\n", " continue\n", " done.add(u)\n", " if u == dst:\n", " break\n", " for v, w, e in adj[u]:\n", " if d + w < dist.get(v, float(\"inf\")):\n", " dist[v], prev[v] = d + w, (u, e)\n", " heapq.heappush(pq, (d + w, v))\n", " if dst not in done:\n", " return float(\"inf\"), [], []\n", " path, es = [dst], []\n", " while path[-1] != src:\n", " u, e = prev[path[-1]]\n", " es.append(e); path.append(u)\n", " return dist[dst], path[::-1], es[::-1]\n", "# ---------------------------------------------------------------------------\n", "\n", "# 1) ์Šคํ‚ค๋งˆ ์ •์˜: ์ˆ˜์†ก ์ž์‚ฐ / ๋„๋กœ๋ง ๋…ธ๋“œ / ๋„๋กœ ๊ตฌ๊ฐ„(๊ฐ„์„ ) / ์ˆ˜์†ก ์š”์ฒญ\n", "SCHEMA = {\n", " \"asset\": {\"asset_id\": {\"type\": str, \"pattern\": r\"^(TRK|TRL|AIR)-\\d{3}$\"},\n", " \"kind\": {\"type\": str, \"enum\": list(ASSETS)},\n", " \"cap_t\": {\"type\": (int, float), \"min\": 0.5, \"max\": 50},\n", " \"speed_kmh\": {\"type\": (int, float), \"min\": 5, \"max\": 900},\n", " \"cost_km\": {\"type\": (int, float), \"min\": 0},\n", " \"status\": {\"type\": str, \"enum\": [\"๊ฐ€์šฉ\", \"์ •๋น„\", \"์šดํ–‰์ค‘\"]}},\n", " \"node\": {\"node_id\": {\"type\": str, \"pattern\": r\"^N\\d{4}$\"},\n", " \"x_km\": {\"type\": (int, float), \"min\": -100, \"max\": 100},\n", " \"y_km\": {\"type\": (int, float), \"min\": -100, \"max\": 100}},\n", " \"edge\": {\"id\": {\"type\": str, \"pattern\": r\"^E\\d{3}$\"},\n", " \"u\": {\"type\": str, \"pattern\": r\"^N\\d{4}$\"}, \"v\": {\"type\": str, \"pattern\": r\"^N\\d{4}$\"},\n", " \"km\": {\"type\": (int, float), \"min\": 0.1, \"max\": 200},\n", " \"road\": {\"type\": str, \"enum\": list(ROAD_FACTOR)},\n", " \"bridge\": {\"type\": bool}, \"open\": {\"type\": bool}},\n", " \"request\": {\"req_id\": {\"type\": str, \"pattern\": r\"^REQ-\\d{4}$\"},\n", " \"dest\": {\"type\": str, \"pattern\": r\"^N\\d{4}$\"},\n", " \"cargo_t\": {\"type\": (int, float), \"min\": 0.1, \"max\": 200},\n", " \"deadline_h\": {\"type\": (int, float), \"min\": 0.5, \"max\": 72}},\n", "}\n", "\n", "\n", "def check(name, value, rule):\n", " ok_type = isinstance(value, rule[\"type\"]) and (rule[\"type\"] is bool or not isinstance(value, bool))\n", " if not ok_type:\n", " return [f\"{name}: ์ž๋ฃŒํ˜• ์˜ค๋ฅ˜({type(value).__name__})\"]\n", " errs = []\n", " if \"enum\" in rule and value not in rule[\"enum\"]:\n", " errs.append(f\"{name}: ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ๊ฐ’ '{value}'\")\n", " if \"min\" in rule and value < rule[\"min\"]:\n", " errs.append(f\"{name}: ์ตœ์†Œ๊ฐ’ {rule['min']} ๋ฏธ๋งŒ\")\n", " if \"max\" in rule and value > rule[\"max\"]:\n", " errs.append(f\"{name}: ์ตœ๋Œ€๊ฐ’ {rule['max']} ์ดˆ๊ณผ\")\n", " if \"pattern\" in rule and not re.match(rule[\"pattern\"], value):\n", " errs.append(f\"{name}: ํ˜•์‹ ๋ถˆ์ผ์น˜ '{value}'\")\n", " return errs\n", "\n", "\n", "def validate(group, rec, node_ids=None):\n", " errs = []\n", " for k, rule in SCHEMA[group].items():\n", " errs += [f\"{group}.{k}: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\"] if k not in rec else check(f\"{group}.{k}\", rec[k], rule)\n", " if group == \"edge\" and node_ids is not None: # ์ฐธ์กฐ ๋ฌด๊ฒฐ์„ฑ: ์—†๋Š” ๋…ธ๋“œ๋ฅผ ์ž‡๋Š” ๊ฐ„์„ \n", " errs += [f\"edge.{k}: ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋…ธ๋“œ {rec[k]}\" for k in (\"u\", \"v\") if rec.get(k) not in node_ids]\n", " if group == \"edge\" and rec.get(\"bridge\") and not rec.get(\"limit_t\"):\n", " errs.append(\"edge.limit_t: ๊ต๋Ÿ‰ ํ•˜์ค‘ ์ œํ•œ ๋ˆ„๋ฝ\")\n", " return errs\n", "\n", "\n", "# 2) [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ์ƒ˜ํ”Œ ์ƒ์„ฑ: ์ž์‚ฐ 12๋Œ€, ๋„๋กœ๋ง 48๋…ธ๋“œ, ์ˆ˜์†ก ์š”์ฒญ 6๊ฑด\n", "nodes, edges = make_network()\n", "rng = np.random.default_rng(1)\n", "prefix = {\"truck\": \"TRK\", \"trailer\": \"TRL\", \"airlift\": \"AIR\"}\n", "assets = [{\"asset_id\": f\"{prefix[k]}-{i:03d}\", \"kind\": k, **{f: ASSETS[k][f] for f in (\"cap_t\", \"speed_kmh\", \"cost_km\")},\n", " \"status\": str(rng.choice([\"๊ฐ€์šฉ\", \"์ •๋น„\", \"์šดํ–‰์ค‘\"], p=[0.7, 0.15, 0.15]))}\n", " for i, k in enumerate([\"truck\"] * 7 + [\"trailer\"] * 3 + [\"airlift\"] * 2)]\n", "node_recs = [{\"node_id\": n, \"x_km\": round(x, 2), \"y_km\": round(y, 2)} for n, (x, y) in nodes.items()]\n", "requests = [{\"req_id\": f\"REQ-{i:04d}\", \"dest\": f\"N{r:02d}07\", \"cargo_t\": float(round(rng.uniform(3, 30), 1)),\n", " \"deadline_h\": float(rng.choice([2, 3, 4]))} for i, r in enumerate(range(6))]\n", "samples = [(\"asset\", a) for a in assets] + [(\"node\", n) for n in node_recs] + [(\"edge\", e) for e in edges] + [(\"request\", q) for q in requests]\n", "ids = set(nodes)\n", "n_err = sum(1 for g, r in samples if validate(g, r, ids))\n", "print(f\"์ •์ƒ ์ƒ˜ํ”Œ {len(samples)}๊ฑด(์ž์‚ฐ {len(assets)}, ๋…ธ๋“œ {len(node_recs)}, ๊ฐ„์„  {len(edges)}, ์š”์ฒญ {len(requests)}) ๊ฒ€์ฆ ์˜ค๋ฅ˜ {n_err}๊ฑด\")\n", "\n", "# 3) ์˜ค๋ฅ˜ ์ฃผ์ž… ์‹œํ—˜\n", "bad = [(\"asset\", {**assets[0], \"asset_id\": \"TRUCK-1\"}), (\"asset\", {**assets[1], \"kind\": \"tank\"}),\n", " (\"edge\", {**edges[0], \"v\": \"N9999\"}), (\"edge\", {**{k: v for k, v in edges[1].items() if k != \"km\"}}),\n", " (\"edge\", {**edges[2], \"bridge\": True, \"limit_t\": None}), (\"request\", {**requests[0], \"cargo_t\": -2.0})]\n", "print(\"\\n[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒฐ๊ณผ]\")\n", "caught = 0\n", "for g, r in bad:\n", " e = validate(g, copy.deepcopy(r), ids)\n", " caught += bool(e)\n", " print(\" -\", \"; \".join(e))\n", "print(f\"์ฃผ์ž… ์˜ค๋ฅ˜ {len(bad)}์ข… ์ค‘ {caught}์ข… ๊ฒ€์ถœ\")\n", "\n", "# 4) ์ˆ˜์†ก์ง€์› ์‹ค์ ยท๋น„์šฉ ๋ณด๊ณ  ์—…๋ฌด AS-IS ๋ถ„ํ•ด(๋‹จ๊ณ„๋ณ„ ์†Œ์š” ๋ถ„, ๊ฐ€์ƒ ์ธก์ •๊ฐ’)์™€ TO-BE ๊ฐœ์„ \n", "steps = [(\"์ˆ˜์†ก ์š”์ฒญ ์ ‘์ˆ˜(์ „ํ™”ยท๋ฉ”์‹ ์ €)\", 20, \"์š”์ฒญ ์„œ์‹ ํ‘œ์ค€ํ™”ยท์ž๋™ ๋“ฑ๋ก\", 2),\n", " (\"๊ฐ€์šฉ ์ž์‚ฐ ํ™•์ธ(์ฐจ๋Ÿ‰ ์ผ์ง€ ์ˆ˜๊ธฐ ๋Œ€์กฐ)\", 30, \"์ž์‚ฐ ์ƒํƒœ ์Šคํ‚ค๋งˆ๋กœ ์ž๋™ ์กฐํšŒ\", 2),\n", " (\"๋„๋กœ ์ƒํ™ฉ ํ™•์ธ(๊ตฌ๋‘ ๋ณด๊ณ  ์ทจํ•ฉ)\", 40, \"๋„๋กœ ์ œ์•ฝ ์ž๋™ ๋ฐ˜์˜(๋ฏธ์…˜ 2)\", 5),\n", " (\"๊ฒฝ๋กœ ์„ ์ •(์ง€๋„ ์ˆ˜์ž‘์—…)\", 35, \"์ตœ๋‹จยท์œ„ํ˜‘ํšŒํ”ผ ๊ฒฝ๋กœ ์ž๋™ ์ถ”์ฒœ\", 3),\n", " (\"๋ฐฐ์ฐจยท์ ์žฌ ๊ณ„ํš ์ž‘์„ฑ\", 25, \"์ ์žฌ ํšจ์œจ ๊ธฐ๋ฐ˜ ์ž๋™ ๋ฐฐ์ฐจ\", 5),\n", " (\"์ˆ˜์†กํ†ต์ œ๊ด€ ์Šน์ธ\", 15, \"HITL ์Šน์ธ ํ™”๋ฉด(๊ทผ๊ฑฐ ์ž๋™ ์ฒจ๋ถ€)\", 10),\n", " (\"์šดํ–‰ ๊ธฐ๋ก ์ˆ˜๊ธฐ ์ž‘์„ฑ\", 20, \"์šดํ–‰ ์ด๋ฒคํŠธ ์ž๋™ ๊ธฐ๋ก\", 3),\n", " (\"์ง€์—ฐยท์‚ฌ๊ณ  ๋ณด๊ณ  ์ž‘์„ฑ\", 25, \"์ง€์—ฐ ์›์ธ ์ฝ”๋“œ ์„ ํƒ์‹ ๊ธฐ๋ก\", 5),\n", " (\"์šดํ–‰ ์‹ค์  ์ง‘๊ณ„(์—‘์…€ ๋ณต์‚ฌยท๋ถ™์—ฌ๋„ฃ๊ธฐ)\", 60, \"์‹ค์  ์ž๋™ ์ง‘๊ณ„\", 1),\n", " (\"๋น„์šฉ ์‚ฐ์ถœ(์œ ๋ฅ˜ยท๊ฑฐ๋ฆฌ ๋‹จ๊ฐ€ ๊ณ„์‚ฐ)\", 45, \"์ž์‚ฐ๋ณ„ km ๋‹จ๊ฐ€ ์ž๋™ ๊ณ„์‚ฐ\", 1),\n", " (\"์‹ค์ ยท๋น„์šฉ ๋ณด๊ณ ์„œ ์ž‘์„ฑ\", 50, \"๋Œ€์‹œ๋ณด๋“œ ํ™”๋ฉด ์บก์ฒ˜ ๋Œ€์‹  ์ž๋™ ์š”์•ฝ\", 5),\n", " (\"์ƒ๊ธ‰ ๋ถ€๋Œ€ ๋ณด๊ณ ยท์ˆ˜์ • ์š”์ฒญ ๋ฐ˜์˜\", 30, \"๋‹จ์ผ ๋ฐ์ดํ„ฐ ๊ธฐ์ค€์œผ๋กœ ์ˆ˜์ • ์ตœ์†Œํ™”\", 10)]\n", "proc = pd.DataFrame(steps, columns=[\"์—…๋ฌด ๋‹จ๊ณ„\", \"AS-IS(๋ถ„)\", \"TO-BE ๊ฐœ์„ \", \"TO-BE(๋ถ„)\"])\n", "proc.index = range(1, len(proc) + 1)\n", "thr = proc[\"AS-IS(๋ถ„)\"].mean() + 0.5 * proc[\"AS-IS(๋ถ„)\"].std()\n", "proc[\"๋ณ‘๋ชฉ\"] = np.where(proc[\"AS-IS(๋ถ„)\"] >= thr, \"๋ณ‘๋ชฉ\", \"\")\n", "print(f\"\\n[AS-IS / TO-BE ์—…๋ฌด ๋‹จ๊ณ„ {len(proc)}๋‹จ๊ณ„] (๋ณ‘๋ชฉ ๊ธฐ์ค€: ํ‰๊ท +0.5ํ‘œ์ค€ํŽธ์ฐจ = {thr:.0f}๋ถ„ ์ด์ƒ)\")\n", "print(proc[[\"์—…๋ฌด ๋‹จ๊ณ„\", \"AS-IS(๋ถ„)\", \"TO-BE(๋ถ„)\", \"๋ณ‘๋ชฉ\"]].to_string())\n", "nb = int((proc[\"๋ณ‘๋ชฉ\"] == \"๋ณ‘๋ชฉ\").sum())\n", "asis, tobe = proc[\"AS-IS(๋ถ„)\"].sum(), proc[\"TO-BE(๋ถ„)\"].sum()\n", "print(f\"\\n์—…๋ฌด ๋‹จ๊ณ„ {len(proc)}๋‹จ๊ณ„(๋ชฉํ‘œ 10๋‹จ๊ณ„ ์ด์ƒ: {'๋‹ฌ์„ฑ' if len(proc) >= 10 else '๋ฏธ๋‹ฌ'}), ๋ณ‘๋ชฉ {nb}๊ฐœ์†Œ(๋ชฉํ‘œ 3๊ฐœ์†Œ ์ด์ƒ: {'๋‹ฌ์„ฑ' if nb >= 3 else '๋ฏธ๋‹ฌ'})\")\n", "print(f\"1ํšŒ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ AS-IS {asis}๋ถ„ -> TO-BE {tobe}๋ถ„ ({1 - tobe / asis:.0%} ๋‹จ์ถ• ์˜ˆ์ƒ)\")\n", "print(\"\\n[์Šคํ‚ค๋งˆ ๊ทธ๋ฃน๋ณ„ ํ•„๋“œ ์ˆ˜]\", {g: len(f) for g, f in SCHEMA.items()})\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 01 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "์ •์ƒ ์ƒ˜ํ”Œ 157๊ฑด(์ž์‚ฐ 12, ๋…ธ๋“œ 48, ๊ฐ„์„  91, ์š”์ฒญ 6) ๊ฒ€์ฆ ์˜ค๋ฅ˜ 0๊ฑด\n", "\n", "[์˜ค๋ฅ˜ ์ฃผ์ž… ๊ฒฐ๊ณผ]\n", " - asset.asset_id: ํ˜•์‹ ๋ถˆ์ผ์น˜ 'TRUCK-1'\n", " - asset.kind: ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ๊ฐ’ 'tank'\n", " - edge.v: ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋…ธ๋“œ N9999\n", " - edge.km: ํ•„์ˆ˜ ํ•„๋“œ ๋ˆ„๋ฝ\n", " - edge.limit_t: ๊ต๋Ÿ‰ ํ•˜์ค‘ ์ œํ•œ ๋ˆ„๋ฝ\n", " - request.cargo_t: ์ตœ์†Œ๊ฐ’ 0.1 ๋ฏธ๋งŒ\n", "์ฃผ์ž… ์˜ค๋ฅ˜ 6์ข… ์ค‘ 6์ข… ๊ฒ€์ถœ\n", "\n", "[AS-IS / TO-BE ์—…๋ฌด ๋‹จ๊ณ„ 12๋‹จ๊ณ„] (๋ณ‘๋ชฉ ๊ธฐ์ค€: ํ‰๊ท +0.5ํ‘œ์ค€ํŽธ์ฐจ = 40๋ถ„ ์ด์ƒ)\n", " ์—…๋ฌด ๋‹จ๊ณ„ AS-IS(๋ถ„) TO-BE(๋ถ„) ๋ณ‘๋ชฉ\n", "1 ์ˆ˜์†ก ์š”์ฒญ ์ ‘์ˆ˜(์ „ํ™”ยท๋ฉ”์‹ ์ €) 20 2 \n", "2 ๊ฐ€์šฉ ์ž์‚ฐ ํ™•์ธ(์ฐจ๋Ÿ‰ ์ผ์ง€ ์ˆ˜๊ธฐ ๋Œ€์กฐ) 30 2 \n", "3 ๋„๋กœ ์ƒํ™ฉ ํ™•์ธ(๊ตฌ๋‘ ๋ณด๊ณ  ์ทจํ•ฉ) 40 5 ๋ณ‘๋ชฉ\n", "4 ๊ฒฝ๋กœ ์„ ์ •(์ง€๋„ ์ˆ˜์ž‘์—…) 35 3 \n", "5 ๋ฐฐ์ฐจยท์ ์žฌ ๊ณ„ํš ์ž‘์„ฑ 25 5 \n", "6 ์ˆ˜์†กํ†ต์ œ๊ด€ ์Šน์ธ 15 10 \n", "7 ์šดํ–‰ ๊ธฐ๋ก ์ˆ˜๊ธฐ ์ž‘์„ฑ 20 3 \n", "8 ์ง€์—ฐยท์‚ฌ๊ณ  ๋ณด๊ณ  ์ž‘์„ฑ 25 5 \n", "9 ์šดํ–‰ ์‹ค์  ์ง‘๊ณ„(์—‘์…€ ๋ณต์‚ฌยท๋ถ™์—ฌ๋„ฃ๊ธฐ) 60 1 ๋ณ‘๋ชฉ\n", "10 ๋น„์šฉ ์‚ฐ์ถœ(์œ ๋ฅ˜ยท๊ฑฐ๋ฆฌ ๋‹จ๊ฐ€ ๊ณ„์‚ฐ) 45 1 ๋ณ‘๋ชฉ\n", "11 ์‹ค์ ยท๋น„์šฉ ๋ณด๊ณ ์„œ ์ž‘์„ฑ 50 5 ๋ณ‘๋ชฉ\n", "12 ์ƒ๊ธ‰ ๋ถ€๋Œ€ ๋ณด๊ณ ยท์ˆ˜์ • ์š”์ฒญ ๋ฐ˜์˜ 30 10 \n", "\n", "์—…๋ฌด ๋‹จ๊ณ„ 12๋‹จ๊ณ„(๋ชฉํ‘œ 10๋‹จ๊ณ„ ์ด์ƒ: ๋‹ฌ์„ฑ), ๋ณ‘๋ชฉ 4๊ฐœ์†Œ(๋ชฉํ‘œ 3๊ฐœ์†Œ ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "1ํšŒ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ AS-IS 395๋ถ„ -> TO-BE 52๋ถ„ (87% ๋‹จ์ถ• ์˜ˆ์ƒ)\n", "\n", "[์Šคํ‚ค๋งˆ ๊ทธ๋ฃน๋ณ„ ํ•„๋“œ ์ˆ˜] {'asset': 6, 'node': 3, 'edge': 7, 'request': 4}\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 02. ๋„๋กœ ํŒŒ๊ดด, ๊ต๋Ÿ‰ ๋ถ•๊ดด, ํ†ต์ œ ๊ตฌ์—ญ ๋“ฑ ์ „์žฅ ์ œ์•ฝ ์กฐ๊ฑด ๊ทธ๋ž˜ํ”„ ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋„๋กœ ํŒŒ๊ดด, ํ†ต์ œ ๊ตฌ์—ญ ๋“ฑ ์ „์žฅ ์ œ์•ฝ ์กฐ๊ฑด ๊ทธ๋ž˜ํ”„ ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋„๋กœ ํŒŒ๊ดด, ๊ต๋Ÿ‰ ๋ถ•๊ดดยทํ•˜์ค‘ ์ œํ•œ, ํ†ต์ œ ๊ตฌ์—ญ ๋ณด๊ณ ๋ฅผ ์—ฌ๋Ÿฌ ํ˜•์‹์—์„œ ์ฝ์–ด ๋„๋กœ๋ง ๊ทธ๋ž˜ํ”„์— ๋ฐ˜์˜ํ•˜๋Š” ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **CSV, ์—‘์…€ ๋‚ด๋ณด๋‚ด๊ธฐ(ํƒญ ๊ตฌ๋ถ„), JSON, ๊ณต๋ฌธ ํ…์ŠคํŠธ ํ˜•์‹์˜ ๊ฐ€์ƒ ๋ณด๊ณ ์„œ๋ฅผ ๊ฐ๊ฐ ํŒŒ์‹ฑํ•ด ํ‘œ์ค€ ์ œ์•ฝ ๋ ˆ์ฝ”๋“œ๋กœ ๋ฐ”๊พผ๋‹ค ๊ณผ์ •์„ ์ง„ํ–‰ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/connectors/mov02_data_loader.py ๋ฐ ์Šคํ‚ค๋งˆ ์ •์˜์„œ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตญ๋ฐฉ ์„œ์‹/์–‘์‹ ๋ณ€๊ฒฝ์— ๋Œ€ํ•œ ์œ ์—ฐํ•œ ํ™•์žฅ์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์ง€์› ํŒŒ์ผ ํฌ๋งท(CSV, Excel, JSON ๋“ฑ) ํŒŒ์‹ฑ ์„ฑ๊ณต๋ฅ  100%\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ๋ฐ์ดํ„ฐ ๋กœ๋” ๋ฐ ์Šคํ‚ค๋งˆ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๋ชจ๋“ˆ ์™„์„ฑ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๋‹ค์–‘ํ•œ ํ˜•ํƒœ์˜ ์ƒ˜ํ”Œ ๊ตฐ์ˆ˜ ๋ฌธ์„œ ๋Œ€์ƒ ํŒŒ์‹ฑ ํ…Œ์ŠคํŠธ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"๋„๋กœ ํŒŒ๊ดดยท๊ต๋Ÿ‰ ๋ถ•๊ดดยทํ•˜์ค‘ ์ œํ•œยทํ†ต์ œ ๊ตฌ์—ญ ๋ณด๊ณ ๋ฅผ ์—ฌ๋Ÿฌ ํ˜•์‹(CSVยท์—‘์…€ ๋‚ด๋ณด๋‚ด๊ธฐยทJSONยท๊ณต๋ฌธ ํ…์ŠคํŠธ)์˜ ๊ฐ€์ƒ ๋ฌธ์„œ์—์„œ ํŒŒ์‹ฑํ•ด ๋„๋กœ๋ง ๊ทธ๋ž˜ํ”„์— ๋ฐ˜์˜ํ•˜๋Š” ์ „์žฅ ์ œ์•ฝ ์กฐ๊ฑด ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ•˜๊ณ , ํŒŒ์‹ฑ ์„ฑ๊ณต๋ฅ ๊ณผ ๋ฐ˜์˜ ํ›„ ์—ฐ๊ฒฐ์„ฑ์„ ํ™•์ธํ•œ๋‹ค.\"\"\"\n", "import heapq\n", "import csv\n", "import io\n", "import json\n", "import re\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy.sparse import coo_matrix\n", "from scipy.sparse.csgraph import connected_components\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋„๋กœ๋งยท์ˆ˜์†ก ์ž์‚ฐ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์ง€๋ช…ยท๋ถ€๋Œ€ยท์žฅ๋น„ ์ œ์› ์—†์Œ)\n", "# ๊ฒฉ์žํ˜• ๊ฐ€์ƒ ๋„๋กœ๋ง(๋…ธ๋“œ=๊ต์ฐจ์ , ๊ฐ„์„ =๋„๋กœ ๊ตฌ๊ฐ„)์— ๋„๋กœ ๋“ฑ๊ธ‰ยท๊ต๋Ÿ‰ ํ•˜์ค‘ยท๊ฐ€์ƒ ์œ„ํ˜‘ ๊ตฌ์—ญ์„ ๋ถ™์ธ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "ASSETS = { # ๊ต์œก์šฉ ๊ฐ€์ • ์ œ์›(์‹ค์ œ ์žฅ๋น„ ์ œ์› ์•„๋‹˜)\n", " \"truck\": {\"name\": \"์ค‘ํ˜• ํŠธ๋Ÿญ\", \"cap_t\": 5.0, \"speed_kmh\": 50, \"cost_km\": 1.0, \"weight_t\": 10.0},\n", " \"trailer\": {\"name\": \"๋Œ€ํ˜• ํŠธ๋ ˆ์ผ๋Ÿฌ\", \"cap_t\": 20.0, \"speed_kmh\": 40, \"cost_km\": 2.4, \"weight_t\": 30.0},\n", " \"airlift\": {\"name\": \"์ˆ˜์†ก๊ธฐ\", \"cap_t\": 12.0, \"speed_kmh\": 300, \"cost_km\": 9.0, \"weight_t\": 0.0},\n", "}\n", "ROAD_FACTOR = {\"paved\": 1.0, \"unpaved\": 0.6} # ๋„๋กœ ๋“ฑ๊ธ‰๋ณ„ ์†๋„ ๊ณ„์ˆ˜\n", "\n", "\n", "def make_network(seed=5, rows=6, cols=8, spacing=5.0):\n", " \"\"\"๊ฒฉ์žํ˜• ๊ฐ€์ƒ ๋„๋กœ๋ง. ๋…ธ๋“œ ์ขŒํ‘œ(km)์™€ ๊ฐ„์„  ๋ชฉ๋ก(๊ฑฐ๋ฆฌยท๋„๋กœ๋“ฑ๊ธ‰ยท๊ต๋Ÿ‰ ํ•˜์ค‘)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " nodes = {f\"N{r:02d}{c:02d}\": (c * spacing + rng.uniform(-1, 1), r * spacing + rng.uniform(-1, 1))\n", " for r in range(rows) for c in range(cols)}\n", " edges = []\n", "\n", " def add(a, b):\n", " (x1, y1), (x2, y2) = nodes[a], nodes[b]\n", " bridge = bool(rng.random() < 0.15)\n", " edges.append({\"id\": f\"E{len(edges):03d}\", \"u\": a, \"v\": b,\n", " \"km\": round(float(np.hypot(x2 - x1, y2 - y1) * rng.uniform(1.0, 1.25)), 2),\n", " \"road\": \"paved\" if rng.random() < 0.65 else \"unpaved\", \"bridge\": bridge,\n", " \"limit_t\": float(rng.choice([20, 40, 60])) if bridge else None, \"open\": True})\n", " for r in range(rows):\n", " for c in range(cols):\n", " if c + 1 < cols:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r:02d}{c + 1:02d}\")\n", " if r + 1 < rows:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r + 1:02d}{c:02d}\")\n", " if r + 1 < rows and c + 1 < cols and rng.random() < 0.25:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r + 1:02d}{c + 1:02d}\")\n", " return nodes, edges\n", "\n", "\n", "def make_threats(seed=9, n=3, xlim=(8, 28), ylim=(3, 22)):\n", " \"\"\"๊ฐ€์ƒ ์œ„ํ˜‘ ๊ตฌ์—ญ(์›ํ˜•). p = ๊ตฌ๊ฐ„ 1ํšŒ ํ†ต๊ณผ ์‹œ ํ”ผํ•ด ๊ฐ€๋Šฅ์„ฑ(๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " return [{\"id\": f\"TZ{i + 1}\", \"x\": float(rng.uniform(*xlim)), \"y\": float(rng.uniform(*ylim)),\n", " \"r\": float(rng.uniform(3, 5)), \"p\": float(round(rng.uniform(0.05, 0.2), 3))} for i in range(n)]\n", "\n", "\n", "def edge_risk(e, nodes, threats):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 2] ๋„๋กœ ํŒŒ๊ดด, ๊ต๋Ÿ‰ ๋ถ•๊ดด, ํ†ต์ œ ๊ตฌ์—ญ ๋“ฑ ์ „์žฅ ์ œ์•ฝ ์กฐ๊ฑด ๊ทธ๋ž˜ํ”„ ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def edge_minutes(e, asset=\"truck\"):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 2] ๋„๋กœ ํŒŒ๊ดด, ๊ต๋Ÿ‰ ๋ถ•๊ดด, ํ†ต์ œ ๊ตฌ์—ญ ๋“ฑ ์ „์žฅ ์ œ์•ฝ ์กฐ๊ฑด ๊ทธ๋ž˜ํ”„ ์ „์ฒ˜๋ฆฌ ๋ชจ๋“ˆ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def passable(e, gross_t):\n", " return e[\"open\"] and (not e[\"bridge\"] or e[\"limit_t\"] >= gross_t)\n", "\n", "\n", "def build_adj(nodes, edges, weight_fn):\n", " \"\"\"weight_fn(e)๊ฐ€ None์ด๋ฉด ํ†ตํ–‰ ๋ถˆ๊ฐ€ ๊ฐ„์„ ์œผ๋กœ ๋ณด๊ณ  ์ œ์™ธํ•œ๋‹ค.\"\"\"\n", " adj = {n: [] for n in nodes}\n", " for e in edges:\n", " w = weight_fn(e)\n", " if w is not None:\n", " adj[e[\"u\"]].append((e[\"v\"], w, e))\n", " adj[e[\"v\"]].append((e[\"u\"], w, e))\n", " return adj\n", "\n", "\n", "def dijkstra(adj, src, dst):\n", " \"\"\"๋‹ค์ต์ŠคํŠธ๋ผ ์ตœ๋‹จ๊ฒฝ๋กœ. (๋น„์šฉ, ๋…ธ๋“œ ๊ฒฝ๋กœ, ๊ฐ„์„  ๋ชฉ๋ก). ๋„๋‹ฌ ๋ถˆ๊ฐ€๋ฉด (inf, [], []).\"\"\"\n", " dist, prev, done, pq = {src: 0.0}, {}, set(), [(0.0, src)]\n", " while pq:\n", " d, u = heapq.heappop(pq)\n", " if u in done:\n", " continue\n", " done.add(u)\n", " if u == dst:\n", " break\n", " for v, w, e in adj[u]:\n", " if d + w < dist.get(v, float(\"inf\")):\n", " dist[v], prev[v] = d + w, (u, e)\n", " heapq.heappush(pq, (d + w, v))\n", " if dst not in done:\n", " return float(\"inf\"), [], []\n", " path, es = [dst], []\n", " while path[-1] != src:\n", " u, e = prev[path[-1]]\n", " es.append(e); path.append(u)\n", " return dist[dst], path[::-1], es[::-1]\n", "# ---------------------------------------------------------------------------\n", "\n", "nodes, edges = make_network()\n", "E = {e[\"id\"]: e for e in edges}\n", "DEPOT, UNITS = \"N0000\", [f\"N{r:02d}07\" for r in range(6)]\n", "\n", "# 1) [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์˜ ๊ฐ€์ƒ ๋ณด๊ณ  ๋ฌธ์„œ 4์ข… (ํŒŒ์ผ ๋Œ€์‹  ๋ฌธ์ž์—ด๋กœ ๋ชจ์˜)\n", "CSV_DOC = \"\"\"edge_id,status,reason\n", "E017,ํŒŒ๊ดด,ํฌ๊ฒฉ์œผ๋กœ ๋…ธ๋ฉด ์œ ์‹ค\n", "E040,ํŒŒ๊ดด,์ง€๋ขฐ ์˜์‹ฌ ์‹ ๊ณ \n", "E058,์ •์ƒ,๋ณต๊ตฌ ์™„๋ฃŒ\n", "E071,ํŒŒ๊ดด,์ฐจ๋Ÿ‰ ์ž”ํ•ด ์ ์น˜\"\"\"\n", "EXCEL_DOC = \"๊ตฌ๊ฐ„ID\\t๊ต๋Ÿ‰ํ•˜์ค‘(t)\\t๋น„๊ณ \\nE025\\t15\\t๊ต๊ฐ ์†์ƒ\\nE064\\t20\\t์ž„์‹œ ๊ฐ€์„ค ๊ต๋Ÿ‰\" # ์—‘์…€ ๋‚ด๋ณด๋‚ด๊ธฐ(ํƒญ ๊ตฌ๋ถ„, ํ•œ๊ธ€ ๋จธ๋ฆฌ๊ธ€)\n", "JSON_DOC = json.dumps({\"control_zones\": [{\"zone\": \"CZ-1\", \"x_km\": 17.5, \"y_km\": 12.0, \"r_km\": 2.5, \"reason\": \"ํ™”์ƒ๋ฐฉ ์˜ค์—ผ ์˜์‹ฌ\"},\n", " {\"zone\": \"CZ-2\", \"x_km\": 30.0, \"y_km\": 21.0, \"r_km\": 1.8, \"reason\": \"๋ฏผ๊ฐ„์ธ ๋Œ€ํ”ผ ํ†ต๋กœ\"}]}, ensure_ascii=False)\n", "MEMO_DOC = \"\"\"[๊ฐ€์ƒ ๊ณต๋ฌธ] ์ œ๋ชฉ: ๋„๋กœ ์ƒํ™ฉ ๋ณ€๊ฒฝ ํ†ต๋ณด\n", "1. ๊ต๋Ÿ‰ E046 ๋ถ•๊ดด๋กœ ํ†ตํ–‰ ๋ถˆ๊ฐ€.\n", "2. E083 ๊ตฌ๊ฐ„ ๊ต๋Ÿ‰ ํ•˜์ค‘ ์ œํ•œ 20ํ†ค์œผ๋กœ ํ•˜ํ–ฅ.\n", "3. E999 ๊ตฌ๊ฐ„ ํŒŒ๊ดด ๋ณด๊ณ (๊ตฌ๊ฐ„ ๋ฒˆํ˜ธ ํ™•์ธ ํ•„์š”).\"\"\"\n", "\n", "ALIAS = {\"edge_id\": [\"edge_id\", \"๊ตฌ๊ฐ„ID\", \"๊ตฌ๊ฐ„\"], \"limit_t\": [\"limit_t\", \"๊ต๋Ÿ‰ํ•˜์ค‘(t)\", \"ํ•˜์ค‘\"], \"status\": [\"status\", \"์ƒํƒœ\"]}\n", "\n", "\n", "def std_cols(df):\n", " \"\"\"์–‘์‹์ด ๋ฐ”๋€Œ์–ด๋„(๋จธ๋ฆฌ๊ธ€ ๋ณ€๊ฒฝ) ๋ณ„์นญ ์‚ฌ์ „์œผ๋กœ ํ‘œ์ค€ ์ปฌ๋Ÿผ๋ช…์— ๋งž์ถ˜๋‹ค.\"\"\"\n", " ren = {c: k for k, al in ALIAS.items() for c in df.columns if c.strip() in al}\n", " return df.rename(columns=ren)\n", "\n", "\n", "def parse(fmt, text):\n", " \"\"\"ํ˜•์‹๋ณ„ ํŒŒ์„œ. ๊ฒฐ๊ณผ๋Š” ํ‘œ์ค€ ์ œ์•ฝ ๋ ˆ์ฝ”๋“œ ๋ชฉ๋ก {type, edge_id | zone, value, reason}.\"\"\"\n", " if fmt == \"csv\":\n", " df = std_cols(pd.DataFrame(list(csv.DictReader(io.StringIO(text)))))\n", " return [{\"type\": \"close\" if s == \"ํŒŒ๊ดด\" else \"reopen\", \"edge_id\": e, \"reason\": r} for e, s, r in zip(df.edge_id, df.status, df.reason)]\n", " if fmt == \"excel\":\n", " df = std_cols(pd.DataFrame(list(csv.DictReader(io.StringIO(text), delimiter=\"\\t\"))))\n", " return [{\"type\": \"limit\", \"edge_id\": e, \"value\": float(v), \"reason\": r} for e, v, r in zip(df.edge_id, df.limit_t, df[\"๋น„๊ณ \"])]\n", " if fmt == \"json\":\n", " return [{\"type\": \"zone\", \"zone\": z[\"zone\"], \"value\": (z[\"x_km\"], z[\"y_km\"], z[\"r_km\"]), \"reason\": z[\"reason\"]}\n", " for z in json.loads(text)[\"control_zones\"]]\n", " if fmt == \"memo\":\n", " out = []\n", " for line in text.splitlines():\n", " m = re.search(r\"(E\\d{3}).*?(๋ถ•๊ดด|ํŒŒ๊ดด|ํ†ตํ–‰ ๋ถˆ๊ฐ€)\", line)\n", " if m:\n", " out.append({\"type\": \"close\", \"edge_id\": m.group(1), \"reason\": line.strip()}); continue\n", " m = re.search(r\"(E\\d{3}).*?ํ•˜์ค‘ ์ œํ•œ (\\d+)\\s*ํ†ค\", line)\n", " if m:\n", " out.append({\"type\": \"limit\", \"edge_id\": m.group(1), \"value\": float(m.group(2)), \"reason\": line.strip()})\n", " return out\n", " raise ValueError(f\"์ง€์›ํ•˜์ง€ ์•Š๋Š” ํ˜•์‹: {fmt}\")\n", "\n", "\n", "def check_rec(r):\n", " \"\"\"์Šคํ‚ค๋งˆ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ: ๊ตฌ๊ฐ„ID ํ˜•์‹ยท์กด์žฌ ์—ฌ๋ถ€, ํ•˜์ค‘ 1~100t, ํ†ต์ œ ๊ตฌ์—ญ ๋ฐ˜๊ฒฝ 0.1~10km.\"\"\"\n", " if r[\"type\"] == \"zone\":\n", " return [] if 0.1 <= r[\"value\"][2] <= 10 else [f\"{r['zone']} ๋ฐ˜๊ฒฝ ๋ฒ”์œ„ ์˜ค๋ฅ˜\"]\n", " errs = [] if re.match(r\"^E\\d{3}$\", r[\"edge_id\"]) else [f\"{r['edge_id']} ํ˜•์‹ ์˜ค๋ฅ˜\"]\n", " if r[\"edge_id\"] not in E:\n", " errs.append(f\"{r['edge_id']} ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ตฌ๊ฐ„\")\n", " if r[\"type\"] == \"limit\" and not 1 <= r[\"value\"] <= 100:\n", " errs.append(f\"{r['edge_id']} ํ•˜์ค‘ ๋ฒ”์œ„ ์˜ค๋ฅ˜\")\n", " return errs\n", "\n", "\n", "def apply(edges, recs, log):\n", " \"\"\"์ œ์•ฝ ๋ ˆ์ฝ”๋“œ๋ฅผ ๊ทธ๋ž˜ํ”„์— ๋ฐ˜์˜. ์—†๋Š” ๊ตฌ๊ฐ„ ID๋Š” ๋ฐ˜์˜ํ•˜์ง€ ์•Š๊ณ  ๋กœ๊ทธ์— ๋‚จ๊ธด๋‹ค.\"\"\"\n", " new = [dict(e) for e in edges]\n", " idx = {e[\"id\"]: e for e in new}\n", " for r in recs:\n", " if r[\"type\"] == \"zone\":\n", " x, y, rad = r[\"value\"]\n", " for e in new:\n", " if edge_risk(e, nodes, [{\"x\": x, \"y\": y, \"r\": rad, \"p\": 1.0}]) > 0:\n", " e[\"open\"] = False; e[\"zone\"] = r[\"zone\"]\n", " log.append(f\"{r['zone']} ํ†ต์ œ ๊ตฌ์—ญ ๋ฐ˜์˜\")\n", " elif r[\"edge_id\"] not in idx:\n", " log.append(f\"{r['edge_id']} ์—†๋Š” ๊ตฌ๊ฐ„ ID - ๋ฐ˜์˜ ๋ณด๋ฅ˜(ํ™•์ธ ์š”์ฒญ)\")\n", " elif r[\"type\"] == \"close\":\n", " idx[r[\"edge_id\"]][\"open\"] = False\n", " elif r[\"type\"] == \"reopen\":\n", " idx[r[\"edge_id\"]][\"open\"] = True\n", " elif r[\"type\"] == \"limit\":\n", " idx[r[\"edge_id\"]].update(bridge=True, limit_t=r[\"value\"])\n", " return new\n", "\n", "\n", "docs = [(\"csv\", CSV_DOC), (\"excel\", EXCEL_DOC), (\"json\", JSON_DOC), (\"memo\", MEMO_DOC)]\n", "recs, rows = [], []\n", "for fmt, text in docs:\n", " try:\n", " r = parse(fmt, text); recs += r; rows.append({\"ํ˜•์‹\": fmt, \"ํŒŒ์‹ฑ\": \"์„ฑ๊ณต\", \"๋ ˆ์ฝ”๋“œ\": len(r)})\n", " except Exception as ex:\n", " rows.append({\"ํ˜•์‹\": fmt, \"ํŒŒ์‹ฑ\": f\"์‹คํŒจ({ex})\", \"๋ ˆ์ฝ”๋“œ\": 0})\n", "res = pd.DataFrame(rows)\n", "rate = (res[\"ํŒŒ์‹ฑ\"] == \"์„ฑ๊ณต\").mean()\n", "print(res.to_string(index=False))\n", "print(f\"ํŒŒ์‹ฑ ์„ฑ๊ณต๋ฅ  {rate:.0%} (๋ชฉํ‘œ 100%: {'๋‹ฌ์„ฑ' if rate == 1 else '๋ฏธ๋‹ฌ'}), ์ œ์•ฝ ๋ ˆ์ฝ”๋“œ {len(recs)}๊ฑด\")\n", "\n", "log = []\n", "after = apply(edges, recs, log)\n", "print(\"\\n[๋ฐ˜์˜ ๋กœ๊ทธ]\", \" / \".join(log))\n", "print(f\"ํ†ตํ–‰ ๋ถˆ๊ฐ€ ๊ตฌ๊ฐ„ {sum(not e['open'] for e in after)}๊ฐœ, ํ•˜์ค‘ ์ œํ•œ ๊ต๋Ÿ‰ {sum(e['bridge'] for e in after)}๊ฐœ (์›๋ณธ {sum(e['bridge'] for e in edges)}๊ฐœ)\")\n", "\n", "# 2) ๋ฐ˜์˜ ์ „ํ›„ ์—ฐ๊ฒฐ์„ฑยท๋„๋‹ฌ์„ฑ ์ ๊ฒ€ (ํŠธ๋Ÿญ ์ด์ค‘๋Ÿ‰ 15t, ํŠธ๋ ˆ์ผ๋Ÿฌ ์ด์ค‘๋Ÿ‰ 50t ๊ฐ€์ •)\n", "ids = list(nodes)\n", "pos = {n: i for i, n in enumerate(ids)}\n", "\n", "\n", "def n_components(es, gross):\n", " ok = [e for e in es if passable(e, gross)]\n", " g = coo_matrix((np.ones(len(ok)), ([pos[e[\"u\"]] for e in ok], [pos[e[\"v\"]] for e in ok])), shape=(len(ids), len(ids)))\n", " return connected_components(g, directed=False)[0]\n", "\n", "\n", "print(\"\\n์ฐจ์ข… ๋ฐ˜์˜ ์ „ ์—ฐ๊ฒฐ ์š”์†Œ ๋ฐ˜์˜ ํ›„ ์—ฐ๊ฒฐ ์š”์†Œ ๋„๋‹ฌ ๊ฐ€๋Šฅ ๋ถ€๋Œ€(ํ›„)\")\n", "for kind, gross in [(\"truck\", 15), (\"trailer\", 50)]:\n", " adj = build_adj(nodes, after, lambda e: edge_minutes(e, kind) if passable(e, gross) else None)\n", " reach = sum(dijkstra(adj, DEPOT, u)[0] < float(\"inf\") for u in UNITS)\n", " print(f\"{kind:8s} {n_components(edges, gross):16d} {n_components(after, gross):16d} {reach}/{len(UNITS)}\")\n", "\n", "# 3) ์–‘์‹ ๋ณ€๊ฒฝ ๋Œ€์‘ ์‹œํ—˜: ๋จธ๋ฆฌ๊ธ€์ด ๋ฐ”๋€ CSV๋„ ๋ณ„์นญ ์‚ฌ์ „์œผ๋กœ ์ฒ˜๋ฆฌ๋˜๋Š”์ง€ ํ™•์ธ\n", "changed = \"๊ตฌ๊ฐ„,์ƒํƒœ,reason\\nE017,ํŒŒ๊ดด,๋จธ๋ฆฌ๊ธ€ ๋ณ€๊ฒฝ ์–‘์‹\"\n", "print(\"\\n๋จธ๋ฆฌ๊ธ€ ๋ณ€๊ฒฝ ์–‘์‹ ํŒŒ์‹ฑ:\", parse(\"csv\", changed))\n", "errs = [x for r in recs for x in check_rec(r)]\n", "print(f\"์Šคํ‚ค๋งˆ ๊ฒ€์‚ฌ: ๋ ˆ์ฝ”๋“œ {len(recs)}๊ฑด ์ค‘ ์˜ค๋ฅ˜ {len(errs)}๊ฑด {errs} -> ํ•ด๋‹น ๊ฑด์€ ๋ฐ˜์˜ ๋ณด๋ฅ˜ ํ›„ ํ™•์ธ ์š”์ฒญ\")\n", "\n", "# 4) ๋ฐ˜์˜ ์ „ํ›„ ๊ทธ๋ž˜ํ”„ ๊ทธ๋ฆผ\n", "fig, ax = plt.subplots(1, 2, figsize=(13, 5))\n", "for a, es, title in [(ax[0], edges, \"Road network (before)\"), (ax[1], after, \"After battlefield constraints\")]:\n", " for e in es:\n", " (x1, y1), (x2, y2) = nodes[e[\"u\"]], nodes[e[\"v\"]]\n", " col = \"red\" if not e[\"open\"] else (\"orange\" if e[\"bridge\"] else (\"tab:blue\" if e[\"road\"] == \"paved\" else \"tab:gray\"))\n", " a.plot([x1, x2], [y1, y2], color=col, lw=2.5 if not e[\"open\"] else 1.5, ls=\"--\" if not e[\"open\"] else \"-\")\n", " a.plot(*nodes[DEPOT], \"ks\", ms=10)\n", " for u in UNITS:\n", " a.plot(*nodes[u], \"g^\", ms=9)\n", " a.set_title(title); a.set_xlabel(\"x (km)\"); a.set_ylabel(\"y (km)\"); a.set_aspect(\"equal\")\n", "for r in recs:\n", " if r[\"type\"] == \"zone\":\n", " ax[1].add_patch(plt.Circle(r[\"value\"][:2], r[\"value\"][2], color=\"purple\", alpha=0.2))\n", "ax[1].text(0, -4, \"red dashed = closed, orange = bridge (weight limit), purple = control zone\", fontsize=8)\n", "plt.suptitle(\"MOV02 constraint preprocessing (synthetic network)\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 02 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", " ํ˜•์‹ ํŒŒ์‹ฑ ๋ ˆ์ฝ”๋“œ\n", " csv ์„ฑ๊ณต 4\n", "excel ์„ฑ๊ณต 2\n", " json ์„ฑ๊ณต 2\n", " memo ์„ฑ๊ณต 3\n", "ํŒŒ์‹ฑ ์„ฑ๊ณต๋ฅ  100% (๋ชฉํ‘œ 100%: ๋‹ฌ์„ฑ), ์ œ์•ฝ ๋ ˆ์ฝ”๋“œ 11๊ฑด\n", "\n", "[๋ฐ˜์˜ ๋กœ๊ทธ] CZ-1 ํ†ต์ œ ๊ตฌ์—ญ ๋ฐ˜์˜ / CZ-2 ํ†ต์ œ ๊ตฌ์—ญ ๋ฐ˜์˜ / E999 ์—†๋Š” ๊ตฌ๊ฐ„ ID - ๋ฐ˜์˜ ๋ณด๋ฅ˜(ํ™•์ธ ์š”์ฒญ)\n", "ํ†ตํ–‰ ๋ถˆ๊ฐ€ ๊ตฌ๊ฐ„ 10๊ฐœ, ํ•˜์ค‘ ์ œํ•œ ๊ต๋Ÿ‰ 17๊ฐœ (์›๋ณธ 14๊ฐœ)\n", "\n", "์ฐจ์ข… ๋ฐ˜์˜ ์ „ ์—ฐ๊ฒฐ ์š”์†Œ ๋ฐ˜์˜ ํ›„ ์—ฐ๊ฒฐ ์š”์†Œ ๋„๋‹ฌ ๊ฐ€๋Šฅ ๋ถ€๋Œ€(ํ›„)\n", "truck 1 2 6/6\n", "trailer 1 2 6/6\n", "\n", "๋จธ๋ฆฌ๊ธ€ ๋ณ€๊ฒฝ ์–‘์‹ ํŒŒ์‹ฑ: [{'type': 'close', 'edge_id': 'E017', 'reason': '๋จธ๋ฆฌ๊ธ€ ๋ณ€๊ฒฝ ์–‘์‹'}]\n", "์Šคํ‚ค๋งˆ ๊ฒ€์‚ฌ: ๋ ˆ์ฝ”๋“œ 11๊ฑด ์ค‘ ์˜ค๋ฅ˜ 1๊ฑด ['E999 ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ตฌ๊ฐ„'] -> ํ•ด๋‹น ๊ฑด์€ ๋ฐ˜์˜ ๋ณด๋ฅ˜ ํ›„ ํ™•์ธ ์š”์ฒญ\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 03. ๋‹ค์ต์ŠคํŠธ๋ผ ๊ธฐ๋ฐ˜ ์ตœ๋‹จ๊ฑฐ๋ฆฌ ๋ณด๊ธ‰ ๊ฒฝ๋กœ ํƒ์ƒ‰ 1์ฐจ Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "๋‹ค์ต์ŠคํŠธ๋ผ ๊ธฐ๋ฐ˜ ์ตœ๋‹จ๊ฑฐ๋ฆฌ ๋ณด๊ธ‰ ๊ฒฝ๋กœ ํƒ์ƒ‰ Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **๋‹ค์ต์ŠคํŠธ๋ผ ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ๋ณด๊ธ‰์†Œ์—์„œ ์ง€์› ๋ถ€๋Œ€๊นŒ์ง€ ์ตœ๋‹จ์‹œ๊ฐ„ ๊ฒฝ๋กœ๋ฅผ ์ฐพ๋Š” Baseline๊ณผ ๊ฒฝ๋กœ ๊ฒ€์ฆ ๊ทœ์น™ ์—”์ง„์„ ๊ตฌํ˜„ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ฐ„์„  ๊ฐ€์ค‘์น˜๋ฅผ ์ฐจ์ข…๋ณ„ ์†Œ์š”์‹œ๊ฐ„์œผ๋กœ ๋‘๊ณ  ํ†ตํ–‰ ๋ถˆ๊ฐ€ยทํ•˜์ค‘ ์ดˆ๊ณผ ๊ตฌ๊ฐ„์„ ์ œ์™ธํ•ด ์ตœ๋‹จ ๊ฒฝ๋กœ๋ฅผ ๊ตฌ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/engine/mov02_rule_engine.py` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ ๊ทœ์ • ๋ฐ ์ง€์นจ ์กฐํ•ญ๊ณผ์˜ ์ •ํ™•ํ•œ ์ผ์น˜์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์—…๋ฌด ๊ฒ€์ฆ ๋ฃฐ 15์ข… ์ด์ƒ ์ฝ”๋”ฉ, ๋ฃฐ ์ฒ˜๋ฆฌ ์ง€์—ฐ์‹œ๊ฐ„ 50ms ์ดํ•˜\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ๋ฃฐ ์—”์ง„ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ 100% ํ†ต๊ณผ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๊ฒฝ๊ณ„ ์กฐ๊ฑด(Boundary condition)์— ๋Œ€ํ•œ ๋ฃฐ ํŒ์ • ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"๋‹ค์ต์ŠคํŠธ๋ผ ์•Œ๊ณ ๋ฆฌ์ฆ˜์œผ๋กœ ๋ณด๊ธ‰์†Œ์—์„œ ์ง€์› ๋ถ€๋Œ€๊นŒ์ง€ ์ตœ๋‹จ์‹œ๊ฐ„ ๋ณด๊ธ‰ ๊ฒฝ๋กœ๋ฅผ ์ฐพ๋Š” Baseline์„ ๊ตฌํ˜„ํ•˜๊ณ , ์„ค์ • ๋ฌธ์ž์—ด๋กœ ๋ถ„๋ฆฌํ•œ ๊ฒฝ๋กœ ๊ฒ€์ฆ ๊ทœ์น™ 15์ข…๊ณผ ๊ฒฝ๊ณ„ ์กฐ๊ฑด ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋กœ ๊ฒฐ๊ณผ๋ฅผ ํŒ์ •ํ•œ๋‹ค.\"\"\"\n", "import heapq\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from scipy.sparse import csr_matrix\n", "from scipy.sparse.csgraph import dijkstra as sp_dijkstra\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋„๋กœ๋งยท์ˆ˜์†ก ์ž์‚ฐ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์ง€๋ช…ยท๋ถ€๋Œ€ยท์žฅ๋น„ ์ œ์› ์—†์Œ)\n", "# ๊ฒฉ์žํ˜• ๊ฐ€์ƒ ๋„๋กœ๋ง(๋…ธ๋“œ=๊ต์ฐจ์ , ๊ฐ„์„ =๋„๋กœ ๊ตฌ๊ฐ„)์— ๋„๋กœ ๋“ฑ๊ธ‰ยท๊ต๋Ÿ‰ ํ•˜์ค‘ยท๊ฐ€์ƒ ์œ„ํ˜‘ ๊ตฌ์—ญ์„ ๋ถ™์ธ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "ASSETS = { # ๊ต์œก์šฉ ๊ฐ€์ • ์ œ์›(์‹ค์ œ ์žฅ๋น„ ์ œ์› ์•„๋‹˜)\n", " \"truck\": {\"name\": \"์ค‘ํ˜• ํŠธ๋Ÿญ\", \"cap_t\": 5.0, \"speed_kmh\": 50, \"cost_km\": 1.0, \"weight_t\": 10.0},\n", " \"trailer\": {\"name\": \"๋Œ€ํ˜• ํŠธ๋ ˆ์ผ๋Ÿฌ\", \"cap_t\": 20.0, \"speed_kmh\": 40, \"cost_km\": 2.4, \"weight_t\": 30.0},\n", " \"airlift\": {\"name\": \"์ˆ˜์†ก๊ธฐ\", \"cap_t\": 12.0, \"speed_kmh\": 300, \"cost_km\": 9.0, \"weight_t\": 0.0},\n", "}\n", "ROAD_FACTOR = {\"paved\": 1.0, \"unpaved\": 0.6} # ๋„๋กœ ๋“ฑ๊ธ‰๋ณ„ ์†๋„ ๊ณ„์ˆ˜\n", "\n", "\n", "def make_network(seed=5, rows=6, cols=8, spacing=5.0):\n", " \"\"\"๊ฒฉ์žํ˜• ๊ฐ€์ƒ ๋„๋กœ๋ง. ๋…ธ๋“œ ์ขŒํ‘œ(km)์™€ ๊ฐ„์„  ๋ชฉ๋ก(๊ฑฐ๋ฆฌยท๋„๋กœ๋“ฑ๊ธ‰ยท๊ต๋Ÿ‰ ํ•˜์ค‘)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " nodes = {f\"N{r:02d}{c:02d}\": (c * spacing + rng.uniform(-1, 1), r * spacing + rng.uniform(-1, 1))\n", " for r in range(rows) for c in range(cols)}\n", " edges = []\n", "\n", " def add(a, b):\n", " (x1, y1), (x2, y2) = nodes[a], nodes[b]\n", " bridge = bool(rng.random() < 0.15)\n", " edges.append({\"id\": f\"E{len(edges):03d}\", \"u\": a, \"v\": b,\n", " \"km\": round(float(np.hypot(x2 - x1, y2 - y1) * rng.uniform(1.0, 1.25)), 2),\n", " \"road\": \"paved\" if rng.random() < 0.65 else \"unpaved\", \"bridge\": bridge,\n", " \"limit_t\": float(rng.choice([20, 40, 60])) if bridge else None, \"open\": True})\n", " for r in range(rows):\n", " for c in range(cols):\n", " if c + 1 < cols:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r:02d}{c + 1:02d}\")\n", " if r + 1 < rows:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r + 1:02d}{c:02d}\")\n", " if r + 1 < rows and c + 1 < cols and rng.random() < 0.25:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r + 1:02d}{c + 1:02d}\")\n", " return nodes, edges\n", "\n", "\n", "def make_threats(seed=9, n=3, xlim=(8, 28), ylim=(3, 22)):\n", " \"\"\"๊ฐ€์ƒ ์œ„ํ˜‘ ๊ตฌ์—ญ(์›ํ˜•). p = ๊ตฌ๊ฐ„ 1ํšŒ ํ†ต๊ณผ ์‹œ ํ”ผํ•ด ๊ฐ€๋Šฅ์„ฑ(๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " return [{\"id\": f\"TZ{i + 1}\", \"x\": float(rng.uniform(*xlim)), \"y\": float(rng.uniform(*ylim)),\n", " \"r\": float(rng.uniform(3, 5)), \"p\": float(round(rng.uniform(0.05, 0.2), 3))} for i in range(n)]\n", "\n", "\n", "def edge_risk(e, nodes, threats):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 3] ๋‹ค์ต์ŠคํŠธ๋ผ ๊ธฐ๋ฐ˜ ์ตœ๋‹จ๊ฑฐ๋ฆฌ ๋ณด๊ธ‰ ๊ฒฝ๋กœ ํƒ์ƒ‰ 1์ฐจ Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def edge_minutes(e, asset=\"truck\"):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 3] ๋‹ค์ต์ŠคํŠธ๋ผ ๊ธฐ๋ฐ˜ ์ตœ๋‹จ๊ฑฐ๋ฆฌ ๋ณด๊ธ‰ ๊ฒฝ๋กœ ํƒ์ƒ‰ 1์ฐจ Baseline ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def passable(e, gross_t):\n", " return e[\"open\"] and (not e[\"bridge\"] or e[\"limit_t\"] >= gross_t)\n", "\n", "\n", "def build_adj(nodes, edges, weight_fn):\n", " \"\"\"weight_fn(e)๊ฐ€ None์ด๋ฉด ํ†ตํ–‰ ๋ถˆ๊ฐ€ ๊ฐ„์„ ์œผ๋กœ ๋ณด๊ณ  ์ œ์™ธํ•œ๋‹ค.\"\"\"\n", " adj = {n: [] for n in nodes}\n", " for e in edges:\n", " w = weight_fn(e)\n", " if w is not None:\n", " adj[e[\"u\"]].append((e[\"v\"], w, e))\n", " adj[e[\"v\"]].append((e[\"u\"], w, e))\n", " return adj\n", "\n", "\n", "def dijkstra(adj, src, dst):\n", " \"\"\"๋‹ค์ต์ŠคํŠธ๋ผ ์ตœ๋‹จ๊ฒฝ๋กœ. (๋น„์šฉ, ๋…ธ๋“œ ๊ฒฝ๋กœ, ๊ฐ„์„  ๋ชฉ๋ก). ๋„๋‹ฌ ๋ถˆ๊ฐ€๋ฉด (inf, [], []).\"\"\"\n", " dist, prev, done, pq = {src: 0.0}, {}, set(), [(0.0, src)]\n", " while pq:\n", " d, u = heapq.heappop(pq)\n", " if u in done:\n", " continue\n", " done.add(u)\n", " if u == dst:\n", " break\n", " for v, w, e in adj[u]:\n", " if d + w < dist.get(v, float(\"inf\")):\n", " dist[v], prev[v] = d + w, (u, e)\n", " heapq.heappush(pq, (d + w, v))\n", " if dst not in done:\n", " return float(\"inf\"), [], []\n", " path, es = [dst], []\n", " while path[-1] != src:\n", " u, e = prev[path[-1]]\n", " es.append(e); path.append(u)\n", " return dist[dst], path[::-1], es[::-1]\n", "# ---------------------------------------------------------------------------\n", "\n", "# ๊ทœ์ •ยท๊ธฐ์ค€์ด ๋ฐ”๋€Œ๋ฉด ์ฝ”๋“œ ๋Œ€์‹  ์ด ์„ค์ •๋งŒ ๊ณ ์นœ๋‹ค(YAML ํ˜•์‹ ๋ฌธ์ž์—ด)\n", "RULES_YAML = \"\"\"\n", "max_unpaved_ratio: 0.5\n", "detour_factor: 2.5\n", "max_threat_edges: 1\n", "truck_count: 3\n", "\"\"\"\n", "CFG = {k.strip(): float(v) for k, v in (line.split(\":\") for line in RULES_YAML.strip().splitlines())}\n", "\n", "nodes, edges = make_network()\n", "threats = make_threats()\n", "for e in edges: # ๋ฏธ์…˜ 2 ์ „์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ(ํ์‡„ยทํ•˜์ค‘ ์ œํ•œยทํ†ต์ œ ๊ตฌ์—ญ) ์š”์•ฝ ๋ฐ˜์˜\n", " if e[\"id\"] in (\"E017\", \"E040\", \"E046\", \"E071\"):\n", " e[\"open\"] = False\n", " if e[\"id\"] in (\"E025\", \"E064\", \"E083\"):\n", " e.update(bridge=True, limit_t={\"E025\": 15.0, \"E064\": 20.0, \"E083\": 20.0}[e[\"id\"]])\n", " if edge_risk(e, nodes, [{\"x\": 17.5, \"y\": 12.0, \"r\": 2.5, \"p\": 1}]) > 0:\n", " e[\"open\"], e[\"zone\"] = False, \"CZ-1\"\n", " e[\"risk\"] = edge_risk(e, nodes, threats)\n", "DEPOT = \"N0000\"\n", "ASSET = {\"kind\": \"truck\", \"status\": \"๊ฐ€์šฉ\", **ASSETS[\"truck\"]}\n", "\n", "\n", "def route(dest, cargo_t, kind=\"truck\"):\n", " gross = ASSETS[kind][\"weight_t\"] + min(cargo_t, ASSETS[kind][\"cap_t\"])\n", " adj = build_adj(nodes, edges, lambda e: edge_minutes(e, kind) if passable(e, gross) else None)\n", " return dijkstra(adj, DEPOT, dest)\n", "\n", "\n", "def rules(req, cost, path, es, asset=ASSET):\n", " \"\"\"๊ฒฝ๋กœ ๊ฒ€์ฆ ๊ทœ์น™ 15์ข…. ๊ฒฐ๊ณผ: ๊ทœ์น™๋ช… -> PASS / FAIL / WARN.\"\"\"\n", " gross = asset[\"weight_t\"] + asset[\"cap_t\"]\n", " km = sum(e[\"km\"] for e in es)\n", " straight = np.hypot(*np.subtract(nodes[req[\"dest\"]], nodes[DEPOT]))\n", " pairs = {frozenset((e[\"u\"], e[\"v\"])) for e in edges}\n", " R = {\n", " \"R01 ์ถœ๋ฐœ์ง€=๋ณด๊ธ‰์†Œ\": path[:1] == [DEPOT],\n", " \"R02 ๋„์ฐฉ์ง€=์š”์ฒญ ๋ถ€๋Œ€\": path[-1:] == [req[\"dest\"]],\n", " \"R03 ์—ฐ์† ๋…ธ๋“œ ๊ฐ„ ๋„๋กœ ์กด์žฌ\": all(frozenset(p) in pairs for p in zip(path, path[1:])),\n", " \"R04 ํ์‡„ ๊ตฌ๊ฐ„ ๋ฏธํฌํ•จ\": all(e[\"open\"] for e in es),\n", " \"R05 ๊ต๋Ÿ‰ ํ•˜์ค‘ ์ด๋‚ด\": all(not e[\"bridge\"] or e[\"limit_t\"] >= gross for e in es),\n", " \"R06 ํ†ต์ œ ๊ตฌ์—ญ ๋ฏธํ†ต๊ณผ\": all(\"zone\" not in e for e in es),\n", " \"R07 ๋…ธ๋“œ ์ค‘๋ณต ๋ฐฉ๋ฌธ ์—†์Œ\": len(path) == len(set(path)),\n", " \"R08 ๊ฑฐ๋ฆฌ ํ•ฉ๊ณ„ ์ •ํ•ฉ\": abs(km - sum(e[\"km\"] for e in es)) < 1e-9 and len(es) == len(path) - 1,\n", " \"R09 ์†Œ์š”์‹œ๊ฐ„ ๊ธฐํ•œ ์ด๋‚ด\": cost <= req[\"deadline_h\"] * 60,\n", " \"R10 ์ ์žฌ๋Ÿ‰ ์ˆ˜์†ก๋Šฅ๋ ฅ ์ด๋‚ด\": req[\"cargo_t\"] <= asset[\"cap_t\"] * CFG[\"truck_count\"],\n", " \"R11 ์ž์‚ฐ ์ƒํƒœ ๊ฐ€์šฉ\": asset[\"status\"] == \"๊ฐ€์šฉ\",\n", " \"R12 ๋น„ํฌ์žฅ ๋น„์œจ ๊ธฐ์ค€ ์ด๋‚ด\": sum(e[\"km\"] for e in es if e[\"road\"] == \"unpaved\") <= CFG[\"max_unpaved_ratio\"] * max(km, 1e-9),\n", " \"R13 ์šฐํšŒ ๊ณผ๋‹ค ์—†์Œ\": km <= CFG[\"detour_factor\"] * straight,\n", " \"R14 ๊ฒฝ๋กœ ๊ตฌ๊ฐ„ 1๊ฐœ ์ด์ƒ\": len(es) >= 1,\n", " \"R15 ์œ„ํ˜‘ ๊ตฌ์—ญ ํ†ต๊ณผ ๊ธฐ์ค€ ์ด๋‚ด\": sum(e[\"risk\"] > 0 for e in es) <= CFG[\"max_threat_edges\"],\n", " }\n", " out = {k: \"PASS\" if v else \"FAIL\" for k, v in R.items()}\n", " if out[\"R15 ์œ„ํ˜‘ ๊ตฌ์—ญ ํ†ต๊ณผ ๊ธฐ์ค€ ์ด๋‚ด\"] == \"FAIL\":\n", " out[\"R15 ์œ„ํ˜‘ ๊ตฌ์—ญ ํ†ต๊ณผ ๊ธฐ์ค€ ์ด๋‚ด\"] = \"WARN\" # Baseline์€ ์œ„ํ˜‘์„ ๊ณ ๋ คํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๊ฒฝ๊ณ ๋งŒ ํ‘œ์‹œ\n", " return out\n", "\n", "\n", "rng = np.random.default_rng(2)\n", "reqs = [{\"req\": f\"REQ-{i:04d}\", \"dest\": f\"N{r:02d}07\", \"cargo_t\": float(round(rng.uniform(4, 14), 1)), \"deadline_h\": 1.5} for i, r in enumerate(range(6))]\n", "rows, lat, all_res = [], [], []\n", "for q in reqs:\n", " cost, path, es = route(q[\"dest\"], q[\"cargo_t\"])\n", " t0 = time.perf_counter()\n", " res = rules(q, cost, path, es)\n", " lat.append((time.perf_counter() - t0) * 1000)\n", " all_res.append(res)\n", " rows.append({\"์š”์ฒญ\": q[\"req\"], \"๋ชฉ์ ์ง€\": q[\"dest\"], \"๊ฑฐ๋ฆฌ(km)\": round(sum(e[\"km\"] for e in es), 1), \"์†Œ์š”(๋ถ„)\": round(cost, 1),\n", " \"๊ตฌ๊ฐ„ ์ˆ˜\": len(es), \"์œ„ํ˜‘ ํ†ต๊ณผ ๊ตฌ๊ฐ„\": sum(e[\"risk\"] > 0 for e in es),\n", " \"PASS\": sum(v == \"PASS\" for v in res.values()), \"WARN\": sum(v == \"WARN\" for v in res.values()), \"FAIL\": sum(v == \"FAIL\" for v in res.values())})\n", "print(\"[Baseline ์ตœ๋‹จ์‹œ๊ฐ„ ๊ฒฝ๋กœ ๊ฒฐ๊ณผ - ์ค‘ํ˜• ํŠธ๋Ÿญ]\")\n", "print(pd.DataFrame(rows).to_string(index=False))\n", "print(f\"\\n๊ฒ€์ฆ ๊ทœ์น™ {len(all_res[0])}์ข…(๋ชฉํ‘œ 15์ข… ์ด์ƒ), ๊ทœ์น™ ์ฒ˜๋ฆฌ ํ‰๊ท  {np.mean(lat):.2f} ms / ์ตœ๋Œ€ {np.max(lat):.2f} ms (๋ชฉํ‘œ 50 ms ์ดํ•˜: {'๋‹ฌ์„ฑ' if max(lat) <= 50 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# 1) ์ตœ์ ์„ฑ ๊ฒ€์ฆ: scipy ๋‹ค์ต์ŠคํŠธ๋ผ์™€ ๋น„์šฉ ๋น„๊ต\n", "ids = list(nodes); ix = {n: i for i, n in enumerate(ids)}\n", "ok_e = [e for e in edges if passable(e, 10 + 5)]\n", "w = [edge_minutes(e) for e in ok_e]\n", "M = csr_matrix((w + w, ([ix[e[\"u\"]] for e in ok_e] + [ix[e[\"v\"]] for e in ok_e], [ix[e[\"v\"]] for e in ok_e] + [ix[e[\"u\"]] for e in ok_e])), shape=(len(ids),) * 2)\n", "ref = sp_dijkstra(M, indices=ix[DEPOT])\n", "same = sum(abs(ref[ix[q[\"dest\"]]] - route(q[\"dest\"], 5.0)[0]) < 1e-6 for q in reqs)\n", "print(f\"์ตœ์ ์„ฑ ๊ฒ€์ฆ(scipy ๊ฒฐ๊ณผ์™€ ์ผ์น˜): {same}/{len(reqs)}\")\n", "\n", "# 2) ๊ฒฝ๊ณ„ ์กฐ๊ฑด ๋‹จ์œ„ ํ…Œ์ŠคํŠธ\n", "cost, path, es = route(\"N0507\", 5.0)\n", "tests = {\n", " \"์ ์žฌ๋Ÿ‰=์ˆ˜์†ก๋Šฅ๋ ฅ ํ•œ๊ณ„(15t)\": rules({\"dest\": \"N0507\", \"cargo_t\": 15.0, \"deadline_h\": 9}, cost, path, es)[\"R10 ์ ์žฌ๋Ÿ‰ ์ˆ˜์†ก๋Šฅ๋ ฅ ์ด๋‚ด\"] == \"PASS\",\n", " \"์ ์žฌ๋Ÿ‰ ํ•œ๊ณ„+0.1t\": rules({\"dest\": \"N0507\", \"cargo_t\": 15.1, \"deadline_h\": 9}, cost, path, es)[\"R10 ์ ์žฌ๋Ÿ‰ ์ˆ˜์†ก๋Šฅ๋ ฅ ์ด๋‚ด\"] == \"FAIL\",\n", " \"์†Œ์š”์‹œ๊ฐ„=๊ธฐํ•œ ์ •ํ™•ํžˆ ์ผ์น˜\": rules({\"dest\": \"N0507\", \"cargo_t\": 5, \"deadline_h\": cost / 60}, cost, path, es)[\"R09 ์†Œ์š”์‹œ๊ฐ„ ๊ธฐํ•œ ์ด๋‚ด\"] == \"PASS\",\n", " \"๋„๋‹ฌ ๋ถˆ๊ฐ€ ๋ชฉ์ ์ง€ ์ฒ˜๋ฆฌ(๊ณ ๋ฆฝ ๋…ธ๋“œ)\": dijkstra(build_adj(nodes, edges, lambda e: None if \"N0507\" in (e[\"u\"], e[\"v\"]) else 1.0), DEPOT, \"N0507\") == (float(\"inf\"), [], []),\n", " \"์ถœ๋ฐœ์ง€=๋„์ฐฉ์ง€\": dijkstra(build_adj(nodes, edges, lambda e: 1.0), DEPOT, DEPOT)[1] == [DEPOT],\n", " \"์ •๋น„ ์ค‘ ์ž์‚ฐ ์ฐจ๋‹จ\": rules({\"dest\": \"N0507\", \"cargo_t\": 5, \"deadline_h\": 9}, cost, path, es, {**ASSET, \"status\": \"์ •๋น„\"})[\"R11 ์ž์‚ฐ ์ƒํƒœ ๊ฐ€์šฉ\"] == \"FAIL\",\n", "}\n", "for k, v in tests.items():\n", " print(f\" [{'PASS' if v else 'FAIL'}] {k}\")\n", "print(f\"๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ์œจ {np.mean(list(tests.values())):.0%}\")\n", "print(\"Baseline ํ•œ๊ณ„: ์‹œ๊ฐ„๋งŒ ์ตœ์†Œํ™”ํ•ด ์œ„ํ˜‘ ๊ตฌ์—ญ์„ ์ง€๋‚˜๋Š” ๊ฒฝ๋กœ(WARN)๊ฐ€ ๋‚˜์˜จ๋‹ค -> ๋ฏธ์…˜ 4์—์„œ ์œ„ํ˜‘ ํšŒํ”ผยท์ ์žฌ ํšจ์œจ์„ ๋ฐ˜์˜ํ•œ๋‹ค.\")\n", "\n", "plt.figure(figsize=(8, 5.5))\n", "for e in edges:\n", " (x1, y1), (x2, y2) = nodes[e[\"u\"]], nodes[e[\"v\"]]\n", " plt.plot([x1, x2], [y1, y2], color=\"lightgray\" if e[\"open\"] else \"red\", lw=1, ls=\"-\" if e[\"open\"] else \"--\")\n", "for t in threats:\n", " plt.gca().add_patch(plt.Circle((t[\"x\"], t[\"y\"]), t[\"r\"], color=\"red\", alpha=0.12))\n", "for q, c in zip(reqs, plt.cm.tab10.colors):\n", " _, path, _ = route(q[\"dest\"], q[\"cargo_t\"])\n", " xy = np.array([nodes[n] for n in path]); plt.plot(xy[:, 0], xy[:, 1], color=c, lw=2.5, label=f\"to {q['dest']}\")\n", "plt.plot(*nodes[DEPOT], \"ks\", ms=10, label=\"depot\")\n", "plt.gca().set_aspect(\"equal\"); plt.legend(fontsize=8, loc=\"upper left\"); plt.xlabel(\"x (km)\"); plt.ylabel(\"y (km)\")\n", "plt.title(\"Baseline shortest-time routes (red circles = threat zones, synthetic)\")\n", "plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 03 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[Baseline ์ตœ๋‹จ์‹œ๊ฐ„ ๊ฒฝ๋กœ ๊ฒฐ๊ณผ - ์ค‘ํ˜• ํŠธ๋Ÿญ]\n", " ์š”์ฒญ ๋ชฉ์ ์ง€ ๊ฑฐ๋ฆฌ(km) ์†Œ์š”(๋ถ„) ๊ตฌ๊ฐ„ ์ˆ˜ ์œ„ํ˜‘ ํ†ต๊ณผ ๊ตฌ๊ฐ„ PASS WARN FAIL\n", "REQ-0000 N0007 44.2 58.9 8 2 14 1 0\n", "REQ-0001 N0107 40.2 54.1 7 2 14 1 0\n", "REQ-0002 N0207 44.4 53.2 8 3 14 1 0\n", "REQ-0003 N0307 49.0 58.8 9 3 14 1 0\n", "REQ-0004 N0407 53.3 67.4 10 3 14 1 0\n", "REQ-0005 N0507 58.4 73.5 11 3 14 1 0\n", "\n", "๊ฒ€์ฆ ๊ทœ์น™ 15์ข…(๋ชฉํ‘œ 15์ข… ์ด์ƒ), ๊ทœ์น™ ์ฒ˜๋ฆฌ ํ‰๊ท  0.06 ms / ์ตœ๋Œ€ 0.11 ms (๋ชฉํ‘œ 50 ms ์ดํ•˜: ๋‹ฌ์„ฑ)\n", "์ตœ์ ์„ฑ ๊ฒ€์ฆ(scipy ๊ฒฐ๊ณผ์™€ ์ผ์น˜): 6/6\n", " [PASS] ์ ์žฌ๋Ÿ‰=์ˆ˜์†ก๋Šฅ๋ ฅ ํ•œ๊ณ„(15t)\n", " [PASS] ์ ์žฌ๋Ÿ‰ ํ•œ๊ณ„+0.1t\n", " [PASS] ์†Œ์š”์‹œ๊ฐ„=๊ธฐํ•œ ์ •ํ™•ํžˆ ์ผ์น˜\n", " [PASS] ๋„๋‹ฌ ๋ถˆ๊ฐ€ ๋ชฉ์ ์ง€ ์ฒ˜๋ฆฌ(๊ณ ๋ฆฝ ๋…ธ๋“œ)\n", " [PASS] ์ถœ๋ฐœ์ง€=๋„์ฐฉ์ง€\n", " [PASS] ์ •๋น„ ์ค‘ ์ž์‚ฐ ์ฐจ๋‹จ\n", "๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ์œจ 100%\n", "Baseline ํ•œ๊ณ„: ์‹œ๊ฐ„๋งŒ ์ตœ์†Œํ™”ํ•ด ์œ„ํ˜‘ ๊ตฌ์—ญ์„ ์ง€๋‚˜๋Š” ๊ฒฝ๋กœ(WARN)๊ฐ€ ๋‚˜์˜จ๋‹ค -> ๋ฏธ์…˜ 4์—์„œ ์œ„ํ˜‘ ํšŒํ”ผยท์ ์žฌ ํšจ์œจ์„ ๋ฐ˜์˜ํ•œ๋‹ค.\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 04. ์  ์œ„ํ˜‘ ๋ฐ˜๊ฒฝ ํšŒํ”ผ ๋ฐ ์ž์‚ฐ ์ ์žฌ ํšจ์œจ์„ ๊ณ ๋ คํ•œ ์ตœ์  ์ˆ˜์†ก ๊ฒฝ๋กœ ์ถ”์ฒœ ๋ชจ๋ธ ๊ณ ๋„ํ™”\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์  ์œ„ํ˜‘ ๋ฐ˜๊ฒฝ ํšŒํ”ผ ๋ฐ ์ ์žฌ ํšจ์œจ์„ ๊ณ ๋ คํ•œ ์ตœ์  ์ˆ˜์†ก ๊ฒฝ๋กœ ์ถ”์ฒœ ๋ชจ๋ธ์„ ๊ณ ๋„ํ™”ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์  ์œ„ํ˜‘ ๋ฐ˜๊ฒฝ ํšŒํ”ผ์™€ ์ ์žฌ ํšจ์œจ(์ฐจ์ข… ์กฐํ•ฉ)์„ ํ•จ๊ป˜ ๊ณ ๋ คํ•œ ์ตœ์  ์ˆ˜์†ก ๊ฒฝ๋กœ ์ถ”์ฒœ ๋ชจ๋ธ๋กœ ๊ณ ๋„ํ™”ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **๊ฐ„์„  ๋น„์šฉ์— ์†Œ์š”์‹œ๊ฐ„๊ณผ ์œ„ํ˜‘ ์œ„ํ—˜ ๊ฐ€์ค‘์น˜๋ฅผ ํ•จ๊ป˜ ๋„ฃ๊ณ , ๊ณผ๊ฑฐ ์ˆ˜์†กํ†ต์ œ๊ด€ ์Šน์ธ ์‚ฌ๋ก€(๊ฐ€์ƒ)๋กœ ์œ„ํ—˜ ๊ฐ€์ค‘์น˜๋ฅผ ํ•™์Šต ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/ai/mov02_intelligence_module.py` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์ถ”์ฒœ ์‚ฌ์œ ์˜ ํ•ฉ๋ฆฌ์„ฑ ๋ฐ ์‹ค๋ฌด ์ˆ˜์šฉ์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์ถ”์ฒœ/๋ถ„๋ฅ˜ ์ •ํ™•๋„ 85% ์ด์ƒ ๋‹ฌ์„ฑ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: AI ์ถ”๋ก  ๋ชจ๋“ˆ ๊ตฌํ˜„ ๋ฐ ๋ฒค์น˜๋งˆํฌ ํ…Œ์ŠคํŠธ ์™„๋ฃŒ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๊ณผ๊ฑฐ ์Šน์ธ/์ฒ˜๋ฆฌ ์™„๋ฃŒ ๋ฐ์ดํ„ฐ์™€์˜ ์ถ”์ฒœ ์ผ์น˜๋„ ๊ฒ€์ฆ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"๊ฐ€์ƒ ์œ„ํ˜‘ ๊ตฌ์—ญ ํšŒํ”ผ ๋น„์šฉ๊ณผ ์ ์žฌ ํšจ์œจ(์ฐจ์ข… ์กฐํ•ฉ)์„ ํ•จ๊ป˜ ๊ณ ๋ คํ•˜๋Š” ์ˆ˜์†ก ๊ฒฝ๋กœ ์ถ”์ฒœ ๋ชจ๋ธ์„ ๊ตฌํ˜„ํ•œ๋‹ค. ๊ณผ๊ฑฐ ์ˆ˜์†กํ†ต์ œ๊ด€ ์Šน์ธ ์‚ฌ๋ก€๋กœ ์œ„ํ—˜ ๊ฐ€์ค‘์น˜๋ฅผ ํ•™์Šตํ•ด ์ถ”์ฒœ ์ผ์น˜๋„(Top-1ยทTop-3)๋ฅผ ๊ฒ€์ฆํ•˜๊ณ , ์ถ”์ฒœ ์‚ฌ์œ ์™€ ์œ ์‚ฌ๋„ ์ ์ˆ˜๋ฅผ ์ œ์‹œํ•œ๋‹ค.\"\"\"\n", "import heapq\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋„๋กœ๋งยท์ˆ˜์†ก ์ž์‚ฐ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์ง€๋ช…ยท๋ถ€๋Œ€ยท์žฅ๋น„ ์ œ์› ์—†์Œ)\n", "# ๊ฒฉ์žํ˜• ๊ฐ€์ƒ ๋„๋กœ๋ง(๋…ธ๋“œ=๊ต์ฐจ์ , ๊ฐ„์„ =๋„๋กœ ๊ตฌ๊ฐ„)์— ๋„๋กœ ๋“ฑ๊ธ‰ยท๊ต๋Ÿ‰ ํ•˜์ค‘ยท๊ฐ€์ƒ ์œ„ํ˜‘ ๊ตฌ์—ญ์„ ๋ถ™์ธ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "ASSETS = { # ๊ต์œก์šฉ ๊ฐ€์ • ์ œ์›(์‹ค์ œ ์žฅ๋น„ ์ œ์› ์•„๋‹˜)\n", " \"truck\": {\"name\": \"์ค‘ํ˜• ํŠธ๋Ÿญ\", \"cap_t\": 5.0, \"speed_kmh\": 50, \"cost_km\": 1.0, \"weight_t\": 10.0},\n", " \"trailer\": {\"name\": \"๋Œ€ํ˜• ํŠธ๋ ˆ์ผ๋Ÿฌ\", \"cap_t\": 20.0, \"speed_kmh\": 40, \"cost_km\": 2.4, \"weight_t\": 30.0},\n", " \"airlift\": {\"name\": \"์ˆ˜์†ก๊ธฐ\", \"cap_t\": 12.0, \"speed_kmh\": 300, \"cost_km\": 9.0, \"weight_t\": 0.0},\n", "}\n", "ROAD_FACTOR = {\"paved\": 1.0, \"unpaved\": 0.6} # ๋„๋กœ ๋“ฑ๊ธ‰๋ณ„ ์†๋„ ๊ณ„์ˆ˜\n", "\n", "\n", "def make_network(seed=5, rows=6, cols=8, spacing=5.0):\n", " \"\"\"๊ฒฉ์žํ˜• ๊ฐ€์ƒ ๋„๋กœ๋ง. ๋…ธ๋“œ ์ขŒํ‘œ(km)์™€ ๊ฐ„์„  ๋ชฉ๋ก(๊ฑฐ๋ฆฌยท๋„๋กœ๋“ฑ๊ธ‰ยท๊ต๋Ÿ‰ ํ•˜์ค‘)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " nodes = {f\"N{r:02d}{c:02d}\": (c * spacing + rng.uniform(-1, 1), r * spacing + rng.uniform(-1, 1))\n", " for r in range(rows) for c in range(cols)}\n", " edges = []\n", "\n", " def add(a, b):\n", " (x1, y1), (x2, y2) = nodes[a], nodes[b]\n", " bridge = bool(rng.random() < 0.15)\n", " edges.append({\"id\": f\"E{len(edges):03d}\", \"u\": a, \"v\": b,\n", " \"km\": round(float(np.hypot(x2 - x1, y2 - y1) * rng.uniform(1.0, 1.25)), 2),\n", " \"road\": \"paved\" if rng.random() < 0.65 else \"unpaved\", \"bridge\": bridge,\n", " \"limit_t\": float(rng.choice([20, 40, 60])) if bridge else None, \"open\": True})\n", " for r in range(rows):\n", " for c in range(cols):\n", " if c + 1 < cols:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r:02d}{c + 1:02d}\")\n", " if r + 1 < rows:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r + 1:02d}{c:02d}\")\n", " if r + 1 < rows and c + 1 < cols and rng.random() < 0.25:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r + 1:02d}{c + 1:02d}\")\n", " return nodes, edges\n", "\n", "\n", "def make_threats(seed=9, n=3, xlim=(8, 28), ylim=(3, 22)):\n", " \"\"\"๊ฐ€์ƒ ์œ„ํ˜‘ ๊ตฌ์—ญ(์›ํ˜•). p = ๊ตฌ๊ฐ„ 1ํšŒ ํ†ต๊ณผ ์‹œ ํ”ผํ•ด ๊ฐ€๋Šฅ์„ฑ(๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " return [{\"id\": f\"TZ{i + 1}\", \"x\": float(rng.uniform(*xlim)), \"y\": float(rng.uniform(*ylim)),\n", " \"r\": float(rng.uniform(3, 5)), \"p\": float(round(rng.uniform(0.05, 0.2), 3))} for i in range(n)]\n", "\n", "\n", "def edge_risk(e, nodes, threats):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 4] ์  ์œ„ํ˜‘ ๋ฐ˜๊ฒฝ ํšŒํ”ผ ๋ฐ ์ž์‚ฐ ์ ์žฌ ํšจ์œจ์„ ๊ณ ๋ คํ•œ ์ตœ์  ์ˆ˜์†ก ๊ฒฝ๋กœ ์ถ”์ฒœ ๋ชจ๋ธ ๊ณ ๋„ํ™” ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def edge_minutes(e, asset=\"truck\"):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 4] ์  ์œ„ํ˜‘ ๋ฐ˜๊ฒฝ ํšŒํ”ผ ๋ฐ ์ž์‚ฐ ์ ์žฌ ํšจ์œจ์„ ๊ณ ๋ คํ•œ ์ตœ์  ์ˆ˜์†ก ๊ฒฝ๋กœ ์ถ”์ฒœ ๋ชจ๋ธ ๊ณ ๋„ํ™” ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def passable(e, gross_t):\n", " return e[\"open\"] and (not e[\"bridge\"] or e[\"limit_t\"] >= gross_t)\n", "\n", "\n", "def build_adj(nodes, edges, weight_fn):\n", " \"\"\"weight_fn(e)๊ฐ€ None์ด๋ฉด ํ†ตํ–‰ ๋ถˆ๊ฐ€ ๊ฐ„์„ ์œผ๋กœ ๋ณด๊ณ  ์ œ์™ธํ•œ๋‹ค.\"\"\"\n", " adj = {n: [] for n in nodes}\n", " for e in edges:\n", " w = weight_fn(e)\n", " if w is not None:\n", " adj[e[\"u\"]].append((e[\"v\"], w, e))\n", " adj[e[\"v\"]].append((e[\"u\"], w, e))\n", " return adj\n", "\n", "\n", "def dijkstra(adj, src, dst):\n", " \"\"\"๋‹ค์ต์ŠคํŠธ๋ผ ์ตœ๋‹จ๊ฒฝ๋กœ. (๋น„์šฉ, ๋…ธ๋“œ ๊ฒฝ๋กœ, ๊ฐ„์„  ๋ชฉ๋ก). ๋„๋‹ฌ ๋ถˆ๊ฐ€๋ฉด (inf, [], []).\"\"\"\n", " dist, prev, done, pq = {src: 0.0}, {}, set(), [(0.0, src)]\n", " while pq:\n", " d, u = heapq.heappop(pq)\n", " if u in done:\n", " continue\n", " done.add(u)\n", " if u == dst:\n", " break\n", " for v, w, e in adj[u]:\n", " if d + w < dist.get(v, float(\"inf\")):\n", " dist[v], prev[v] = d + w, (u, e)\n", " heapq.heappush(pq, (d + w, v))\n", " if dst not in done:\n", " return float(\"inf\"), [], []\n", " path, es = [dst], []\n", " while path[-1] != src:\n", " u, e = prev[path[-1]]\n", " es.append(e); path.append(u)\n", " return dist[dst], path[::-1], es[::-1]\n", "# ---------------------------------------------------------------------------\n", "\n", "nodes, edges = make_network()\n", "DEPOT, UNITS = \"N0000\", [f\"N{r:02d}07\" for r in range(6)]\n", "for e in edges:\n", " if e[\"id\"] in (\"E017\", \"E040\", \"E046\", \"E071\"):\n", " e[\"open\"] = False\n", "LAMS = [0.0, 0.3, 0.7, 1.5, 3.0] # ์œ„ํ—˜ ํšŒํ”ผ ๊ฐ•๋„๋ณ„ ํ›„๋ณด ๊ฒฝ๋กœ ์ƒ์„ฑ\n", "\n", "\n", "def candidates(dest, threats, kind=\"truck\", gross=15, k=5):\n", " \"\"\"์œ„ํ—˜ ํšŒํ”ผ ๊ฐ•๋„(lam)๋ฅผ ๋ฐ”๊พธ๊ณ , ์ด๋ฏธ ์ฐพ์€ ๊ฒฝ๋กœ ๊ตฌ๊ฐ„์— ๋ฒŒ์ ์„ ์ฃผ์–ด ์„œ๋กœ ๋‹ค๋ฅธ ํ›„๋ณด ๊ฒฝ๋กœ(์ตœ๋Œ€ k๊ฐœ)๋ฅผ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " risk = {e[\"id\"]: edge_risk(e, nodes, threats) for e in edges}\n", " out, seen, pen = [], set(), {}\n", " for lam in LAMS + [0.0, 0.0, 0.3, 0.3]:\n", " adj = build_adj(nodes, edges, lambda e: (edge_minutes(e, kind) + lam * risk[e[\"id\"]] * 100) * pen.get(e[\"id\"], 1.0)\n", " if passable(e, gross) else None)\n", " _, path, es = dijkstra(adj, DEPOT, dest)\n", " for e in es:\n", " pen[e[\"id\"]] = pen.get(e[\"id\"], 1.0) * 1.3\n", " if path and tuple(path) not in seen and len(out) < k:\n", " seen.add(tuple(path))\n", " surv = float(np.prod([1 - risk[e[\"id\"]] for e in es]))\n", " out.append({\"path\": path, \"min\": sum(edge_minutes(e, kind) for e in es), \"km\": sum(e[\"km\"] for e in es),\n", " \"surv\": surv, \"risk_score\": -np.log(surv) * 100, \"threat_edges\": sum(risk[e[\"id\"]] > 0 for e in es)})\n", " return out\n", "\n", "\n", "def utility(c, w):\n", " return c[\"min\"] + w * c[\"risk_score\"] # ๋‚ฎ์„์ˆ˜๋ก ์ข‹์Œ: ์†Œ์š”์‹œ๊ฐ„(๋ถ„) + ์œ„ํ—˜ ๊ฐ€์ค‘์น˜ x ์œ„ํ—˜ ์ ์ˆ˜\n", "\n", "\n", "# 1) [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ณผ๊ฑฐ ์ˆ˜์†กํ†ต์ œ๊ด€ ์Šน์ธ ์‚ฌ๋ก€ 40๊ฑด: ์ˆจ์€ ์„ ํ˜ธ(w=1.2)์™€ ํŒ๋‹จ ํŽธ์ฐจ(์žก์Œ)๋กœ ํ•œ ๊ฒฝ๋กœ๋ฅผ ์Šน์ธ\n", "rng = np.random.default_rng(4)\n", "cases = []\n", "for i in range(60):\n", " th = make_threats(seed=100 + i, n=int(rng.integers(2, 5)))\n", " cand = candidates(str(rng.choice(UNITS)), th)\n", " if len(cand) < 2:\n", " continue\n", " approved = int(np.argmin([utility(c, 1.2) + rng.normal(0, 1.0) for c in cand]))\n", " cases.append({\"cand\": cand, \"approved\": approved})\n", "train, test = cases[:len(cases) // 2], cases[len(cases) // 2:]\n", "\n", "\n", "def agreement(cs, w, top=1):\n", " return np.mean([c[\"approved\"] in np.argsort([utility(x, w) for x in c[\"cand\"]])[:top] for c in cs])\n", "\n", "\n", "def loglik(cs, w, T=1.5):\n", " \"\"\"์„ ํƒ ํ™•๋ฅ  P(i) ~ exp(-ํšจ์šฉ/T) ๊ฐ€์ •์˜ ๋กœ๊ทธ์šฐ๋„(์กฐ๊ฑด๋ถ€ ๋กœ์ง“). ์ผ์น˜๋„๋ณด๋‹ค ๋งค๋„๋Ÿฌ์›Œ ๊ฐ€์ค‘์น˜ ์ถ”์ •์ด ์•ˆ์ •์ ์ด๋‹ค.\"\"\"\n", " ll = 0.0\n", " for c in cs:\n", " u = -np.array([utility(x, w) for x in c[\"cand\"]]) / T\n", " ll += u[c[\"approved\"]] - (u.max() + np.log(np.exp(u - u.max()).sum()))\n", " return ll\n", "\n", "\n", "grid = np.round(np.arange(0, 3.01, 0.1), 1)\n", "w_best = grid[int(np.argmax([loglik(train, w) for w in grid]))]\n", "print(f\"๊ณผ๊ฑฐ ์Šน์ธ ์‚ฌ๋ก€ {len(cases)}๊ฑด(ํ•™์Šต {len(train)} / ๊ฒ€์ฆ {len(test)}), ํ•™์Šต๋œ ์œ„ํ—˜ ๊ฐ€์ค‘์น˜ w = {w_best}\")\n", "rows = [{\"๋ชจ๋ธ\": \"Baseline(์ตœ๋‹จ์‹œ๊ฐ„, w=0)\", \"Top-1 ์ผ์น˜๋„\": agreement(test, 0.0), \"Top-3 ํฌํ•จ๋ฅ \": agreement(test, 0.0, 3)},\n", " {\"๋ชจ๋ธ\": f\"์œ„ํ˜‘ ํšŒํ”ผ ์ถ”์ฒœ(w={w_best})\", \"Top-1 ์ผ์น˜๋„\": agreement(test, w_best), \"Top-3 ํฌํ•จ๋ฅ \": agreement(test, w_best, 3)}]\n", "res = pd.DataFrame(rows)\n", "print(res.to_string(index=False, float_format=lambda v: f\"{v:.1%}\"))\n", "acc = rows[1][\"Top-1 ์ผ์น˜๋„\"]\n", "print(f\"์ถ”์ฒœ ์ •ํ™•๋„ {acc:.1%} (๋ชฉํ‘œ 85% ์ด์ƒ: {'๋‹ฌ์„ฑ' if acc >= 0.85 else '๋ฏธ๋‹ฌ'})\")\n", "\n", "# 2) ํ˜„์žฌ ์š”์ฒญ์— ๋Œ€ํ•œ Top-3 ์ถ”์ฒœ๊ณผ ์ถ”์ฒœ ์‚ฌ์œ \n", "threats = make_threats()\n", "dest, cargo = \"N0307\", 38.0\n", "cand = candidates(dest, threats)\n", "order = np.argsort([utility(c, w_best) for c in cand])[:3]\n", "best_u = utility(cand[order[0]], w_best)\n", "print(f\"\\n[์š”์ฒญ: {dest} ๋ณด๊ธ‰ {cargo}t] ์ถ”์ฒœ Top-3 (์œ ์‚ฌ๋„ = 1์ˆœ์œ„ ๋Œ€๋น„ ํšจ์šฉ ๋น„์œจ)\")\n", "base = min(cand, key=lambda c: c[\"min\"])\n", "for rank, i in enumerate(order, 1):\n", " c = cand[i]\n", " print(f\" {rank}์ˆœ์œ„ ์†Œ์š” {c['min']:.0f}๋ถ„, ๊ฑฐ๋ฆฌ {c['km']:.1f}km, ์œ„ํ˜‘ ํ†ต๊ณผ {c['threat_edges']}๊ตฌ๊ฐ„, ์ƒ์กดํ™•๋ฅ  {c['surv']:.1%}, ์œ ์‚ฌ๋„ {best_u / utility(c, w_best):.2f}\")\n", "top = cand[order[0]]\n", "print(f\" ์ถ”์ฒœ ์‚ฌ์œ : ์ตœ๋‹จ์‹œ๊ฐ„ ๊ฒฝ๋กœ ๋Œ€๋น„ ์†Œ์š” {top['min'] - base['min']:+.0f}๋ถ„, ์œ„ํ˜‘ ํ†ต๊ณผ {base['threat_edges']}->{top['threat_edges']}๊ตฌ๊ฐ„, ์ƒ์กดํ™•๋ฅ  {base['surv']:.1%}->{top['surv']:.1%}\")\n", "\n", "\n", "# 3) ์ ์žฌ ํšจ์œจ: ์ฐจ์ข… ์กฐํ•ฉ(ํŠธ๋Ÿญ 5t, ํŠธ๋ ˆ์ผ๋Ÿฌ 20t) ์ตœ์ ํ™” - ๊ต๋Ÿ‰ ํ•˜์ค‘ ๋•Œ๋ฌธ์— ํŠธ๋ ˆ์ผ๋Ÿฌ ๊ฒฝ๋กœ๊ฐ€ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Œ\n", "def plan(cargo, allow_trailer=True):\n", " best = None\n", " for n_trl in range(0, 4 if allow_trailer else 1):\n", " n_trk = int(max(0, np.ceil((cargo - 20 * n_trl) / 5)))\n", " if n_trk > 10:\n", " continue\n", " cost = 0.0\n", " if n_trl:\n", " c = candidates(dest, threats, \"trailer\", 50)\n", " if not c:\n", " continue\n", " cost += n_trl * min(c, key=lambda x: utility(x, w_best))[\"km\"] * ASSETS[\"trailer\"][\"cost_km\"]\n", " cost += n_trk * top[\"km\"] * ASSETS[\"truck\"][\"cost_km\"]\n", " cap = 20 * n_trl + 5 * n_trk\n", " cand_plan = {\"ํŠธ๋ ˆ์ผ๋Ÿฌ\": n_trl, \"ํŠธ๋Ÿญ\": n_trk, \"์ˆ˜์†ก๋Šฅ๋ ฅ(t)\": cap, \"์ ์žฌ ํšจ์œจ\": cargo / cap, \"๋น„์šฉ(๋‹จ๊ฐ€x km)\": round(cost, 1)}\n", " if best is None or cost < best[\"๋น„์šฉ(๋‹จ๊ฐ€x km)\"]:\n", " best = cand_plan\n", " return best\n", "\n", "\n", "p0, p1 = plan(cargo, False), plan(cargo)\n", "print(\"\\n[์ ์žฌ ๊ณ„ํš ๋น„๊ต]\")\n", "print(pd.DataFrame([{\"๋ฐฉ์‹\": \"ํŠธ๋Ÿญ๋งŒ ๋ฐฐ์ฐจ\", **p0}, {\"๋ฐฉ์‹\": \"์ฐจ์ข… ์กฐํ•ฉ ์ตœ์ ํ™”\", **p1}]).to_string(index=False, float_format=lambda v: f\"{v:.1%}\" if v < 1 else f\"{v:.1f}\"))\n", "print(f\"๋น„์šฉ {1 - p1['๋น„์šฉ(๋‹จ๊ฐ€x km)'] / p0['๋น„์šฉ(๋‹จ๊ฐ€x km)']:.1%} ์ ˆ๊ฐ, ํˆฌ์ž… ์ฐจ๋Ÿ‰ {p0['ํŠธ๋Ÿญ']}๋Œ€ -> {p1['ํŠธ๋Ÿญ'] + p1['ํŠธ๋ ˆ์ผ๋Ÿฌ']}๋Œ€\")\n", "\n", "fig, ax = plt.subplots(1, 2, figsize=(13, 5))\n", "for e in edges:\n", " (x1, y1), (x2, y2) = nodes[e[\"u\"]], nodes[e[\"v\"]]\n", " ax[0].plot([x1, x2], [y1, y2], color=\"lightgray\", lw=1)\n", "for t in threats:\n", " ax[0].add_patch(plt.Circle((t[\"x\"], t[\"y\"]), t[\"r\"], color=\"red\", alpha=0.15))\n", "for rank, (i, col) in enumerate(zip(order, [\"tab:green\", \"tab:blue\", \"tab:orange\"]), 1):\n", " xy = np.array([nodes[n] for n in cand[i][\"path\"]]); ax[0].plot(xy[:, 0], xy[:, 1], color=col, lw=3 - 0.6 * rank, label=f\"rank {rank}\")\n", "xy = np.array([nodes[n] for n in base[\"path\"]]); ax[0].plot(xy[:, 0], xy[:, 1], \"k--\", lw=1, label=\"shortest-time\")\n", "ax[0].set_aspect(\"equal\"); ax[0].legend(fontsize=8); ax[0].set_title(\"Threat-aware route recommendation (synthetic)\")\n", "ax[1].plot(grid, [agreement(train, w) for w in grid], \"o-\", label=\"train\")\n", "ax[1].plot(grid, [agreement(test, w) for w in grid], \"s-\", label=\"test\")\n", "ax[1].axhline(0.85, ls=\"--\", c=\"gray\"); ax[1].set_xlabel(\"risk weight w\"); ax[1].set_ylabel(\"Top-1 agreement\"); ax[1].legend()\n", "ax[1].set_title(\"Agreement with past approvals\")\n", "plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 04 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "๊ณผ๊ฑฐ ์Šน์ธ ์‚ฌ๋ก€ 60๊ฑด(ํ•™์Šต 30 / ๊ฒ€์ฆ 30), ํ•™์Šต๋œ ์œ„ํ—˜ ๊ฐ€์ค‘์น˜ w = 2.3\n", " ๋ชจ๋ธ Top-1 ์ผ์น˜๋„ Top-3 ํฌํ•จ๋ฅ \n", "Baseline(์ตœ๋‹จ์‹œ๊ฐ„, w=0) 43.3% 100.0%\n", " ์œ„ํ˜‘ ํšŒํ”ผ ์ถ”์ฒœ(w=2.3) 90.0% 96.7%\n", "์ถ”์ฒœ ์ •ํ™•๋„ 90.0% (๋ชฉํ‘œ 85% ์ด์ƒ: ๋‹ฌ์„ฑ)\n", "\n", "[์š”์ฒญ: N0307 ๋ณด๊ธ‰ 38.0t] ์ถ”์ฒœ Top-3 (์œ ์‚ฌ๋„ = 1์ˆœ์œ„ ๋Œ€๋น„ ํšจ์šฉ ๋น„์œจ)\n", " 1์ˆœ์œ„ ์†Œ์š” 60๋ถ„, ๊ฑฐ๋ฆฌ 50.0km, ์œ„ํ˜‘ ํ†ต๊ณผ 0๊ตฌ๊ฐ„, ์ƒ์กดํ™•๋ฅ  100.0%, ์œ ์‚ฌ๋„ 1.00\n", " 2์ˆœ์œ„ ์†Œ์š” 66๋ถ„, ๊ฑฐ๋ฆฌ 50.9km, ์œ„ํ˜‘ ํ†ต๊ณผ 0๊ตฌ๊ฐ„, ์ƒ์กดํ™•๋ฅ  100.0%, ์œ ์‚ฌ๋„ 0.91\n", " 3์ˆœ์œ„ ์†Œ์š” 73๋ถ„, ๊ฑฐ๋ฆฌ 55.8km, ์œ„ํ˜‘ ํ†ต๊ณผ 0๊ตฌ๊ฐ„, ์ƒ์กดํ™•๋ฅ  100.0%, ์œ ์‚ฌ๋„ 0.82\n", " ์ถ”์ฒœ ์‚ฌ์œ : ์ตœ๋‹จ์‹œ๊ฐ„ ๊ฒฝ๋กœ ๋Œ€๋น„ ์†Œ์š” +1๋ถ„, ์œ„ํ˜‘ ํ†ต๊ณผ 3->0๊ตฌ๊ฐ„, ์ƒ์กดํ™•๋ฅ  57.8%->100.0%\n", "\n", "[์ ์žฌ ๊ณ„ํš ๋น„๊ต]\n", " ๋ฐฉ์‹ ํŠธ๋ ˆ์ผ๋Ÿฌ ํŠธ๋Ÿญ ์ˆ˜์†ก๋Šฅ๋ ฅ(t) ์ ์žฌ ํšจ์œจ ๋น„์šฉ(๋‹จ๊ฐ€x km)\n", " ํŠธ๋Ÿญ๋งŒ ๋ฐฐ์ฐจ 0 8 40 95.0% 400.0\n", "์ฐจ์ข… ์กฐํ•ฉ ์ตœ์ ํ™” 2 0 40 95.0% 287.1\n", "๋น„์šฉ 28.2% ์ ˆ๊ฐ, ํˆฌ์ž… ์ฐจ๋Ÿ‰ 8๋Œ€ -> 2๋Œ€\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 05. ์ˆ˜์†ก์„ ๋‹จ ์ƒ์กด ํ™•๋ฅ  ๋ฐ ์ •์‹œ ๋„์ฐฉ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์‚ฐ์ถœ ์—”์ง„ ๊ฐœ๋ฐœ\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 3.0์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ˆ˜์†ก์„ ๋‹จ ์ƒ์กด ํ™•๋ฅ  ๋ฐ ์ •์‹œ ๋„์ฐฉ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์‚ฐ์ถœ ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **ํ›„๋ณด ๊ฒฝ๋กœ๋งˆ๋‹ค ์ˆ˜์†ก์„ ๋‹จ ์ƒ์กดํ™•๋ฅ ๊ณผ ์ •์‹œ ๋„์ฐฉ ์‹ ๋ขฐ๋„๋ฅผ ๊ณ„์‚ฐํ•ด 0~100์  ์‹ ๋ขฐ๋„ ์ ์ˆ˜์™€ ๋“ฑ๊ธ‰์„ ์‚ฐ์ถœํ•˜๋Š” ์—”์ง„์„ ๊ฐœ๋ฐœํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์œ„ํ˜‘ ๊ตฌ๊ฐ„ ํ†ต๊ณผ ์ˆ˜๋กœ ์ƒ์กดํ™•๋ฅ ์„, ๋„๋กœ ๋“ฑ๊ธ‰ยท๊ต๋Ÿ‰ ๋Œ€๊ธฐยท์œ„ํ˜‘ ๊ตฌ์—ญ ๊ฐ์†์„ ๋ฐ˜์˜ํ•œ ๋ชฌํ…Œ์นด๋ฅผ๋กœ ๋ชจ์˜๋กœ ์ •์‹œ ๋„์ฐฉ ํ™•๋ฅ ์„ ๊ตฌ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒ€์ฆํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/ui/mov02_app.py` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ๊ตฐ ์‹ค๋ฌด์ž ์นœํ™”์  ๋””์ž์ธ ๋ฐ ์กฐ์ž‘ ํŽธ์˜์„ฑ\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ํ•ต์‹ฌ ํ™”๋ฉด 4์ข…(์ž…๋ ฅ-๋ถ„์„-๊ฒ€ํ† -๊ฒฐ๊ณผ) ๊ตฌํ˜„ ์™„๋ฃŒ\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: UI ํ”„๋กœํ† ํƒ€์ž… ๋กœ์ปฌ ์„œ๋ฒ„ ๊ตฌ๋™ ๋ฐ ์ „์ฒด ํŽ˜์ด์ง€ ๋„ค๋น„๊ฒŒ์ด์…˜ ํ™•์ธ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ์‚ฌ์šฉ์„ฑ ํ‰๊ฐ€(UX Walkthrough) ๋ฐ ์ธํ„ฐ๋ž™์…˜ ํ…Œ์ŠคํŠธ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"ํ›„๋ณด ์ˆ˜์†ก ๊ฒฝ๋กœ๋งˆ๋‹ค ์ˆ˜์†ก์„ ๋‹จ ์ƒ์กด ํ™•๋ฅ (์œ„ํ˜‘ ๊ตฌ์—ญ ํ†ต๊ณผ ๊ธฐ๋ฐ˜)๊ณผ ์ •์‹œ ๋„์ฐฉ ์‹ ๋ขฐ๋„(๋ชฌํ…Œ์นด๋ฅผ๋กœ ์†Œ์š”์‹œ๊ฐ„ ๋ชจ์˜)๋ฅผ ๊ณ„์‚ฐํ•ด 0~100์  ์‹ ๋ขฐ๋„ ์ ์ˆ˜์™€ ๋“ฑ๊ธ‰์„ ์‚ฐ์ถœํ•˜๋Š” ์—”์ง„์„ ๋งŒ๋“ค๊ณ , ์ž…๋ ฅ-๋ถ„์„-๊ฒ€ํ† -๊ฒฐ๊ณผ 4๊ฐœ ํ™”๋ฉด์œผ๋กœ ํ‘œ์‹œํ•˜๋ฉฐ ์˜ˆ์ธก ํ™•๋ฅ ์˜ ๋ณด์ • ์ •ํ™•๋„๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.\"\"\"\n", "import heapq\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋„๋กœ๋งยท์ˆ˜์†ก ์ž์‚ฐ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์ง€๋ช…ยท๋ถ€๋Œ€ยท์žฅ๋น„ ์ œ์› ์—†์Œ)\n", "# ๊ฒฉ์žํ˜• ๊ฐ€์ƒ ๋„๋กœ๋ง(๋…ธ๋“œ=๊ต์ฐจ์ , ๊ฐ„์„ =๋„๋กœ ๊ตฌ๊ฐ„)์— ๋„๋กœ ๋“ฑ๊ธ‰ยท๊ต๋Ÿ‰ ํ•˜์ค‘ยท๊ฐ€์ƒ ์œ„ํ˜‘ ๊ตฌ์—ญ์„ ๋ถ™์ธ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "ASSETS = { # ๊ต์œก์šฉ ๊ฐ€์ • ์ œ์›(์‹ค์ œ ์žฅ๋น„ ์ œ์› ์•„๋‹˜)\n", " \"truck\": {\"name\": \"์ค‘ํ˜• ํŠธ๋Ÿญ\", \"cap_t\": 5.0, \"speed_kmh\": 50, \"cost_km\": 1.0, \"weight_t\": 10.0},\n", " \"trailer\": {\"name\": \"๋Œ€ํ˜• ํŠธ๋ ˆ์ผ๋Ÿฌ\", \"cap_t\": 20.0, \"speed_kmh\": 40, \"cost_km\": 2.4, \"weight_t\": 30.0},\n", " \"airlift\": {\"name\": \"์ˆ˜์†ก๊ธฐ\", \"cap_t\": 12.0, \"speed_kmh\": 300, \"cost_km\": 9.0, \"weight_t\": 0.0},\n", "}\n", "ROAD_FACTOR = {\"paved\": 1.0, \"unpaved\": 0.6} # ๋„๋กœ ๋“ฑ๊ธ‰๋ณ„ ์†๋„ ๊ณ„์ˆ˜\n", "\n", "\n", "def make_network(seed=5, rows=6, cols=8, spacing=5.0):\n", " \"\"\"๊ฒฉ์žํ˜• ๊ฐ€์ƒ ๋„๋กœ๋ง. ๋…ธ๋“œ ์ขŒํ‘œ(km)์™€ ๊ฐ„์„  ๋ชฉ๋ก(๊ฑฐ๋ฆฌยท๋„๋กœ๋“ฑ๊ธ‰ยท๊ต๋Ÿ‰ ํ•˜์ค‘)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " nodes = {f\"N{r:02d}{c:02d}\": (c * spacing + rng.uniform(-1, 1), r * spacing + rng.uniform(-1, 1))\n", " for r in range(rows) for c in range(cols)}\n", " edges = []\n", "\n", " def add(a, b):\n", " (x1, y1), (x2, y2) = nodes[a], nodes[b]\n", " bridge = bool(rng.random() < 0.15)\n", " edges.append({\"id\": f\"E{len(edges):03d}\", \"u\": a, \"v\": b,\n", " \"km\": round(float(np.hypot(x2 - x1, y2 - y1) * rng.uniform(1.0, 1.25)), 2),\n", " \"road\": \"paved\" if rng.random() < 0.65 else \"unpaved\", \"bridge\": bridge,\n", " \"limit_t\": float(rng.choice([20, 40, 60])) if bridge else None, \"open\": True})\n", " for r in range(rows):\n", " for c in range(cols):\n", " if c + 1 < cols:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r:02d}{c + 1:02d}\")\n", " if r + 1 < rows:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r + 1:02d}{c:02d}\")\n", " if r + 1 < rows and c + 1 < cols and rng.random() < 0.25:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r + 1:02d}{c + 1:02d}\")\n", " return nodes, edges\n", "\n", "\n", "def make_threats(seed=9, n=3, xlim=(8, 28), ylim=(3, 22)):\n", " \"\"\"๊ฐ€์ƒ ์œ„ํ˜‘ ๊ตฌ์—ญ(์›ํ˜•). p = ๊ตฌ๊ฐ„ 1ํšŒ ํ†ต๊ณผ ์‹œ ํ”ผํ•ด ๊ฐ€๋Šฅ์„ฑ(๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " return [{\"id\": f\"TZ{i + 1}\", \"x\": float(rng.uniform(*xlim)), \"y\": float(rng.uniform(*ylim)),\n", " \"r\": float(rng.uniform(3, 5)), \"p\": float(round(rng.uniform(0.05, 0.2), 3))} for i in range(n)]\n", "\n", "\n", "def edge_risk(e, nodes, threats):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 5] ์ˆ˜์†ก์„ ๋‹จ ์ƒ์กด ํ™•๋ฅ  ๋ฐ ์ •์‹œ ๋„์ฐฉ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์‚ฐ์ถœ ์—”์ง„ ๊ฐœ๋ฐœ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def edge_minutes(e, asset=\"truck\"):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 5] ์ˆ˜์†ก์„ ๋‹จ ์ƒ์กด ํ™•๋ฅ  ๋ฐ ์ •์‹œ ๋„์ฐฉ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ ์‚ฐ์ถœ ์—”์ง„ ๊ฐœ๋ฐœ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def passable(e, gross_t):\n", " return e[\"open\"] and (not e[\"bridge\"] or e[\"limit_t\"] >= gross_t)\n", "\n", "\n", "def build_adj(nodes, edges, weight_fn):\n", " \"\"\"weight_fn(e)๊ฐ€ None์ด๋ฉด ํ†ตํ–‰ ๋ถˆ๊ฐ€ ๊ฐ„์„ ์œผ๋กœ ๋ณด๊ณ  ์ œ์™ธํ•œ๋‹ค.\"\"\"\n", " adj = {n: [] for n in nodes}\n", " for e in edges:\n", " w = weight_fn(e)\n", " if w is not None:\n", " adj[e[\"u\"]].append((e[\"v\"], w, e))\n", " adj[e[\"v\"]].append((e[\"u\"], w, e))\n", " return adj\n", "\n", "\n", "def dijkstra(adj, src, dst):\n", " \"\"\"๋‹ค์ต์ŠคํŠธ๋ผ ์ตœ๋‹จ๊ฒฝ๋กœ. (๋น„์šฉ, ๋…ธ๋“œ ๊ฒฝ๋กœ, ๊ฐ„์„  ๋ชฉ๋ก). ๋„๋‹ฌ ๋ถˆ๊ฐ€๋ฉด (inf, [], []).\"\"\"\n", " dist, prev, done, pq = {src: 0.0}, {}, set(), [(0.0, src)]\n", " while pq:\n", " d, u = heapq.heappop(pq)\n", " if u in done:\n", " continue\n", " done.add(u)\n", " if u == dst:\n", " break\n", " for v, w, e in adj[u]:\n", " if d + w < dist.get(v, float(\"inf\")):\n", " dist[v], prev[v] = d + w, (u, e)\n", " heapq.heappush(pq, (d + w, v))\n", " if dst not in done:\n", " return float(\"inf\"), [], []\n", " path, es = [dst], []\n", " while path[-1] != src:\n", " u, e = prev[path[-1]]\n", " es.append(e); path.append(u)\n", " return dist[dst], path[::-1], es[::-1]\n", "# ---------------------------------------------------------------------------\n", "\n", "nodes, edges = make_network()\n", "threats = make_threats()\n", "DEPOT = \"N0000\"\n", "for e in edges:\n", " if e[\"id\"] in (\"E017\", \"E040\", \"E046\", \"E071\"):\n", " e[\"open\"] = False\n", " e[\"risk\"] = edge_risk(e, nodes, threats)\n", "SIGMA = {\"paved\": 0.12, \"unpaved\": 0.30} # ๋„๋กœ ๋“ฑ๊ธ‰๋ณ„ ์†Œ์š”์‹œ๊ฐ„ ๋ณ€๋™(๋กœ๊ทธ์ •๊ทœ ํ‘œ์ค€ํŽธ์ฐจ, ๊ฐ€์ •๊ฐ’)\n", "\n", "\n", "def candidates(dest, lams=(0.0, 0.5, 1.5, 3.0, 0.0, 0.5), k=4):\n", " \"\"\"์œ„ํ—˜ ํšŒํ”ผ ๊ฐ•๋„๋ฅผ ๋ฐ”๊พธ๊ณ  ์ด๋ฏธ ์“ด ๊ตฌ๊ฐ„์— ๋ฒŒ์ ์„ ์ฃผ์–ด ์„œ๋กœ ๋‹ค๋ฅธ ํ›„๋ณด ๊ฒฝ๋กœ๋ฅผ ์ตœ๋Œ€ k๊ฐœ ๋งŒ๋“ ๋‹ค.\"\"\"\n", " out, seen, pen = [], set(), {}\n", " for lam in lams:\n", " adj = build_adj(nodes, edges, lambda e: (edge_minutes(e) + lam * e[\"risk\"] * 100) * pen.get(e[\"id\"], 1.0) if passable(e, 15) else None)\n", " _, path, es = dijkstra(adj, DEPOT, dest)\n", " for e in es:\n", " pen[e[\"id\"]] = pen.get(e[\"id\"], 1.0) * 1.3\n", " if path and tuple(path) not in seen and len(out) < k:\n", " seen.add(tuple(path)); out.append({\"path\": path, \"es\": es})\n", " return out\n", "\n", "\n", "def simulate_minutes(es, n, rng):\n", " \"\"\"๊ฒฝ๋กœ ์†Œ์š”์‹œ๊ฐ„ ๋ชฌํ…Œ์นด๋ฅผ๋กœ: ๋„๋กœ ๋“ฑ๊ธ‰๋ณ„ ๋ณ€๋™ + ๊ต๋Ÿ‰ ํ†ต๊ณผ ๋Œ€๊ธฐ + ์œ„ํ˜‘ ๊ตฌ์—ญ ๊ฐ์†.\"\"\"\n", " total = np.zeros(n)\n", " for e in es:\n", " total += edge_minutes(e) * rng.lognormal(0, SIGMA[e[\"road\"]], n)\n", " if e[\"bridge\"]:\n", " total += rng.exponential(3, n)\n", " if e[\"risk\"] > 0:\n", " total += rng.uniform(0, 10, n)\n", " return total\n", "\n", "\n", "def score_route(c, deadline_min, n=3000, seed=0):\n", " surv = float(np.prod([1 - e[\"risk\"] for e in c[\"es\"]]))\n", " sims = simulate_minutes(c[\"es\"], n, np.random.default_rng(seed))\n", " ontime = float((sims <= deadline_min).mean())\n", " s = 100 * surv * ontime\n", " return {\"์ƒ์กดํ™•๋ฅ \": surv, \"์ •์‹œ๋„์ฐฉ\": ontime, \"์˜ˆ์ƒ์†Œ์š”(P50)\": float(np.median(sims)), \"์˜ˆ์ƒ์†Œ์š”(P90)\": float(np.percentile(sims, 90)),\n", " \"์ ์ˆ˜\": round(s, 1), \"๋“ฑ๊ธ‰\": \"์‹ ๋ขฐ\" if s >= 85 else (\"์ฃผ์˜\" if s >= 60 else \"์œ„ํ—˜\"), \"sims\": sims}\n", "\n", "\n", "# ---------------- ํ™”๋ฉด 1. ์ž…๋ ฅ ----------------\n", "REQ = {\"req_id\": \"REQ-0101\", \"dest\": \"N0307\", \"cargo_t\": 12.0, \"deadline_min\": 80}\n", "print(\"[ํ™”๋ฉด 1: ์ž…๋ ฅ] ์ˆ˜์†ก ์š”์ฒญ\", {k: v for k, v in REQ.items()}, \"| ๊ฐ€์ƒ ์œ„ํ˜‘ ๊ตฌ์—ญ\", len(threats), \"๊ฐœ\")\n", "\n", "# ---------------- ํ™”๋ฉด 2. ๋ถ„์„ ----------------\n", "cands = candidates(REQ[\"dest\"])\n", "rows = []\n", "for i, c in enumerate(cands):\n", " r = score_route(c, REQ[\"deadline_min\"], seed=i)\n", " c.update(r)\n", " rows.append({\"๊ฒฝ๋กœ\": f\"R{i + 1}\", \"๊ตฌ๊ฐ„ ์ˆ˜\": len(c[\"es\"]), \"์œ„ํ˜‘ ํ†ต๊ณผ\": sum(e[\"risk\"] > 0 for e in c[\"es\"]),\n", " **{k: r[k] for k in (\"์ƒ์กดํ™•๋ฅ \", \"์ •์‹œ๋„์ฐฉ\", \"์˜ˆ์ƒ์†Œ์š”(P50)\", \"์˜ˆ์ƒ์†Œ์š”(P90)\", \"์ ์ˆ˜\", \"๋“ฑ๊ธ‰\")}})\n", "table = pd.DataFrame(rows)\n", "fmt = {\"์ƒ์กดํ™•๋ฅ \": \"{:.1%}\", \"์ •์‹œ๋„์ฐฉ\": \"{:.1%}\", \"์˜ˆ์ƒ์†Œ์š”(P50)\": \"{:.0f}๋ถ„\", \"์˜ˆ์ƒ์†Œ์š”(P90)\": \"{:.0f}๋ถ„\"}\n", "show = table.copy()\n", "for k, f in fmt.items():\n", " show[k] = show[k].map(f.format)\n", "print(\"\\n[ํ™”๋ฉด 2: ๋ถ„์„] ํ›„๋ณด ๊ฒฝ๋กœ๋ณ„ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ (์ ์ˆ˜ = 100 x ์ƒ์กดํ™•๋ฅ  x ์ •์‹œ๋„์ฐฉํ™•๋ฅ )\")\n", "print(show.to_string(index=False))\n", "\n", "# ---------------- ํ™”๋ฉด 3. ๊ฒ€ํ†  (์ƒํƒœ ๋ฑƒ์ง€ยทํ•„ํ„ฐ) ----------------\n", "badge = {\"์‹ ๋ขฐ\": \"์™„๋ฃŒ(์ถ”์ฒœ ๊ฐ€๋Šฅ)\", \"์ฃผ์˜\": \"์Šน์ธ๋Œ€๊ธฐ(ํ†ต์ œ๊ด€ ๊ฒ€ํ† )\", \"์œ„ํ—˜\": \"๋ฐ˜๋ ค(์žฌํƒ์ƒ‰)\"}\n", "table[\"์ƒํƒœ\"] = table[\"๋“ฑ๊ธ‰\"].map(badge)\n", "print(\"\\n[ํ™”๋ฉด 3: ๊ฒ€ํ† ] ์ƒํƒœ๋ณ„ ํ•„ํ„ฐ\")\n", "for st in badge.values():\n", " print(f\" {st}: {table.loc[table['์ƒํƒœ'] == st, '๊ฒฝ๋กœ'].tolist()}\")\n", "\n", "# ---------------- ํ™”๋ฉด 4. ๊ฒฐ๊ณผ ----------------\n", "best = table.sort_values([\"์ ์ˆ˜\", \"์˜ˆ์ƒ์†Œ์š”(P50)\"], ascending=[False, True]).iloc[0]\n", "print(f\"\\n[ํ™”๋ฉด 4: ๊ฒฐ๊ณผ] ์ถ”์ฒœ {best['๊ฒฝ๋กœ']} - ์ ์ˆ˜ {best['์ ์ˆ˜']}({best['๋“ฑ๊ธ‰']}), ์ƒ์กดํ™•๋ฅ  {best['์ƒ์กดํ™•๋ฅ ']:.1%}, ์ •์‹œ๋„์ฐฉ {best['์ •์‹œ๋„์ฐฉ']:.1%}\")\n", "print(\"ํ•ต์‹ฌ ํ™”๋ฉด 4์ข…(์ž…๋ ฅ-๋ถ„์„-๊ฒ€ํ† -๊ฒฐ๊ณผ) ๊ตฌํ˜„ ์™„๋ฃŒ: 4/4\")\n", "\n", "# ---------------- ์˜ˆ์ธก ๋ณด์ •(Calibration) ๊ฒ€์ฆ ----------------\n", "# ์„œ๋กœ ๋‹ค๋ฅธ ๋ชฉ์ ์ง€ยท๊ธฐํ•œ์˜ ๊ฐ€์ƒ ์ˆ˜์†ก 60๊ฑด: ์˜ˆ์ธก ์ •์‹œํ™•๋ฅ  vs ๋ณ„๋„ ๋‚œ์ˆ˜๋กœ ๋ชจ์˜ํ•œ '์‹ค์ œ ์šดํ–‰ ๊ฒฐ๊ณผ'\n", "rng = np.random.default_rng(21)\n", "pred, actual = [], []\n", "for k in range(60):\n", " c = candidates(f\"N{int(rng.integers(0, 6)):02d}07\")[int(rng.integers(0, 2))] if k % 2 else candidates(f\"N{int(rng.integers(0, 6)):02d}07\")[0]\n", " dl = float(rng.uniform(60, 100))\n", " pred.append(score_route(c, dl, n=2000, seed=k)[\"์ •์‹œ๋„์ฐฉ\"])\n", " actual.append(float(simulate_minutes(c[\"es\"], 1, rng)[0] <= dl))\n", "pred, actual = np.array(pred), np.array(actual)\n", "brier = np.mean((pred - actual) ** 2)\n", "print(f\"\\n์ •์‹œ๋„์ฐฉ ์˜ˆ์ธก ๋ณด์ • ๊ฒ€์ฆ(๊ฐ€์ƒ ์šดํ–‰ 60๊ฑด): Brier ์ ์ˆ˜ {brier:.3f} (0์— ๊ฐ€๊นŒ์šธ์ˆ˜๋ก ์ข‹์Œ, ๋ฌด์ •๋ณด ์˜ˆ์ธก 0.25)\")\n", "for lo, hi in [(0, 0.5), (0.5, 0.9), (0.9, 1.01)]:\n", " m = (pred >= lo) & (pred < hi)\n", " if m.any():\n", " print(f\" ์˜ˆ์ธก {lo:.0%}~{min(hi, 1):.0%} ๊ตฌ๊ฐ„ {m.sum():2d}๊ฑด: ํ‰๊ท  ์˜ˆ์ธก {pred[m].mean():.1%}, ์‹ค์ œ ์ •์‹œ์œจ {actual[m].mean():.1%}\")\n", "\n", "fig, ax = plt.subplots(2, 2, figsize=(12, 8))\n", "for e in edges:\n", " (x1, y1), (x2, y2) = nodes[e[\"u\"]], nodes[e[\"v\"]]\n", " ax[0, 0].plot([x1, x2], [y1, y2], color=\"lightgray\", lw=1)\n", "for t in threats:\n", " ax[0, 0].add_patch(plt.Circle((t[\"x\"], t[\"y\"]), t[\"r\"], color=\"red\", alpha=0.15))\n", "for i, c in enumerate(cands):\n", " xy = np.array([nodes[n] for n in c[\"path\"]]); ax[0, 0].plot(xy[:, 0], xy[:, 1], lw=2, label=f\"R{i + 1}\")\n", "ax[0, 0].set_aspect(\"equal\"); ax[0, 0].legend(fontsize=8); ax[0, 0].set_title(\"1. Input: request and candidate routes\")\n", "for i, c in enumerate(cands):\n", " ax[0, 1].hist(c[\"sims\"], bins=40, alpha=0.5, label=f\"R{i + 1}\")\n", "ax[0, 1].axvline(REQ[\"deadline_min\"], color=\"k\", ls=\"--\"); ax[0, 1].set_xlabel(\"travel time (min)\"); ax[0, 1].legend()\n", "ax[0, 1].set_title(\"2. Analysis: Monte Carlo travel time\")\n", "cols = {\"์‹ ๋ขฐ\": \"tab:green\", \"์ฃผ์˜\": \"tab:orange\", \"์œ„ํ—˜\": \"tab:red\"}\n", "ax[1, 0].bar(table[\"๊ฒฝ๋กœ\"], table[\"์ ์ˆ˜\"], color=[cols[g] for g in table[\"๋“ฑ๊ธ‰\"]])\n", "ax[1, 0].axhline(85, ls=\"--\", c=\"gray\"); ax[1, 0].axhline(60, ls=\":\", c=\"gray\"); ax[1, 0].set_ylim(0, 100)\n", "ax[1, 0].set_title(\"3. Review: score (green=trusted, orange=review, red=reject)\")\n", "ax[1, 1].plot([0, 1], [0, 1], \"k--\", lw=1)\n", "bins = np.digitize(pred, [0.2, 0.4, 0.6, 0.8])\n", "ax[1, 1].plot([pred[bins == b].mean() for b in np.unique(bins)], [actual[bins == b].mean() for b in np.unique(bins)], \"o-\")\n", "ax[1, 1].set_xlabel(\"predicted on-time prob.\"); ax[1, 1].set_ylabel(\"observed on-time rate\"); ax[1, 1].set_title(\"4. Result: calibration check\")\n", "plt.suptitle(\"MOV02 convoy reliability score engine (synthetic)\"); plt.tight_layout(); plt.show()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "๐Ÿ’ก [์ฐธ๊ณ ] ๋ฏธ์…˜ 05 ์˜ˆ์ƒ ์‹คํ–‰ ๊ฒฐ๊ณผ (Output)\n", "\n", "```text\n", "[ํ™”๋ฉด 1: ์ž…๋ ฅ] ์ˆ˜์†ก ์š”์ฒญ {'req_id': 'REQ-0101', 'dest': 'N0307', 'cargo_t': 12.0, 'deadline_min': 80} | ๊ฐ€์ƒ ์œ„ํ˜‘ ๊ตฌ์—ญ 3 ๊ฐœ\n", "\n", "[ํ™”๋ฉด 2: ๋ถ„์„] ํ›„๋ณด ๊ฒฝ๋กœ๋ณ„ ์‹ ๋ขฐ๋„ ์ ์ˆ˜ (์ ์ˆ˜ = 100 x ์ƒ์กดํ™•๋ฅ  x ์ •์‹œ๋„์ฐฉํ™•๋ฅ )\n", "๊ฒฝ๋กœ ๊ตฌ๊ฐ„ ์ˆ˜ ์œ„ํ˜‘ ํ†ต๊ณผ ์ƒ์กดํ™•๋ฅ  ์ •์‹œ๋„์ฐฉ ์˜ˆ์ƒ์†Œ์š”(P50) ์˜ˆ์ƒ์†Œ์š”(P90) ์ ์ˆ˜ ๋“ฑ๊ธ‰\n", "R1 9 3 57.8% 67.4% 77๋ถ„ 86๋ถ„ 38.9 ์œ„ํ—˜\n", "R2 10 0 100.0% 95.1% 69๋ถ„ 77๋ถ„ 95.1 ์‹ ๋ขฐ\n", "R3 10 0 100.0% 75.3% 75๋ถ„ 85๋ถ„ 75.3 ์ฃผ์˜\n", "R4 10 0 100.0% 54.2% 79๋ถ„ 89๋ถ„ 54.2 ์œ„ํ—˜\n", "\n", "[ํ™”๋ฉด 3: ๊ฒ€ํ† ] ์ƒํƒœ๋ณ„ ํ•„ํ„ฐ\n", " ์™„๋ฃŒ(์ถ”์ฒœ ๊ฐ€๋Šฅ): ['R2']\n", " ์Šน์ธ๋Œ€๊ธฐ(ํ†ต์ œ๊ด€ ๊ฒ€ํ† ): ['R3']\n", " ๋ฐ˜๋ ค(์žฌํƒ์ƒ‰): ['R1', 'R4']\n", "\n", "[ํ™”๋ฉด 4: ๊ฒฐ๊ณผ] ์ถ”์ฒœ R2 - ์ ์ˆ˜ 95.1(์‹ ๋ขฐ), ์ƒ์กดํ™•๋ฅ  100.0%, ์ •์‹œ๋„์ฐฉ 95.1%\n", "ํ•ต์‹ฌ ํ™”๋ฉด 4์ข…(์ž…๋ ฅ-๋ถ„์„-๊ฒ€ํ† -๊ฒฐ๊ณผ) ๊ตฌํ˜„ ์™„๋ฃŒ: 4/4\n", "\n", "์ •์‹œ๋„์ฐฉ ์˜ˆ์ธก ๋ณด์ • ๊ฒ€์ฆ(๊ฐ€์ƒ ์šดํ–‰ 60๊ฑด): Brier ์ ์ˆ˜ 0.086 (0์— ๊ฐ€๊นŒ์šธ์ˆ˜๋ก ์ข‹์Œ, ๋ฌด์ •๋ณด ์˜ˆ์ธก 0.25)\n", " ์˜ˆ์ธก 0%~50% ๊ตฌ๊ฐ„ 21๊ฑด: ํ‰๊ท  ์˜ˆ์ธก 23.3%, ์‹ค์ œ ์ •์‹œ์œจ 19.0%\n", " ์˜ˆ์ธก 50%~90% ๊ตฌ๊ฐ„ 14๊ฑด: ํ‰๊ท  ์˜ˆ์ธก 72.6%, ์‹ค์ œ ์ •์‹œ์œจ 78.6%\n", " ์˜ˆ์ธก 90%~100% ๊ตฌ๊ฐ„ 25๊ฑด: ํ‰๊ท  ์˜ˆ์ธก 98.4%, ์‹ค์ œ ์ •์‹œ์œจ 100.0%\n", "```\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---\n", "\n", "## ๐ŸŽฏ ๋ฏธ์…˜ 06. ์ „์ˆ  ์ˆ˜์†ก ๊ฒฝ๋กœ ๋ฐ ์ž์‚ฐ ๋ฐฐ์ • ํ˜„ํ™ฉ ๊ฐ€์‹œํ™” 2D/3D ์ˆ˜์†ก ๊ด€์ œ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„\n", "**์†Œ์š”์‹œ๊ฐ„**: ์•ฝ 2.5์‹œ๊ฐ„\n", "\n", "### ๐Ÿ“‹ ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ์ˆ˜ํ–‰ ์ง€์นจ\n", "#### ๋ฏธ์…˜ ๋ฐฐ๊ฒฝ ๋ฐ ๋‹น์œ„์„ฑ\n", "\n", "์ „์ˆ  ์ˆ˜์†ก ๊ฒฝ๋กœ ๋ฐ ์ž์‚ฐ ๋ฐฐ์ • ํ˜„ํ™ฉ ๊ฐ€์‹œํ™” ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์ „์ˆ  ํ˜„์žฅ์—์„œ๋Š” ๋‹จ์ˆœ ํƒ์ง€์— ๊ทธ์น˜์ง€ ์•Š๊ณ  ์‹ ์†ํ•œ ์œ„ํ˜‘ ํŒ๋‹จ๊ณผ ์‹ ๋ขฐ๋„ ๋†’์€ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ, **์ˆ˜์†ก ์š”์ฒญ๋ณ„ ์ž์‚ฐ ๋ฐฐ์ •๊ณผ ๊ฒฝ๋กœ๋ฅผ ๊ด€์ œ ๋Œ€์‹œ๋ณด๋“œ๋กœ ๊ฐ€์‹œํ™”ํ•˜๊ณ , ์ž…๋ ฅ ๊ฒ€์ฆ๊ธฐ์™€ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ธฐ๋กœ ์˜ˆ์™ธ ์ƒํ™ฉ์—์„œ๋„ ๋ฉˆ์ถ”์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค.** ๊ณผ์—…์„ ์™„์ˆ˜ํ•˜์—ฌ ์˜คํŒ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์ž‘์ „ ๋Œ€์‘๋ ฅ์„ ๊ทน๋Œ€ํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณธ ๋ฏธ์…˜์—์„œ๋Š” **์š”์ฒญ์„ ์ ์žฌ๋Ÿ‰์— ๋งž๊ฒŒ ํŠธ๋ ˆ์ผ๋ŸฌยทํŠธ๋Ÿญ์— ๋ฐฐ์ •ํ•˜๊ณ  ๊ฒฝ๋กœ ์ง€๋„, ์ž์‚ฐ ๊ฐ€๋™๋ฅ , ์šดํ–‰ ์ผ์ •(์ง€์—ฐ ํ‘œ์‹œ), ์š”์ฒญ๋ณ„ ๋น„์šฉ์„ 2x2 ๋Œ€์‹œ๋ณด๋“œ๋กœ ๊ทธ๋ฆฐ๋‹ค ๊ณผ์ •์„ ์ง„ํ–‰ํ•˜๋Š”** ์‹ค์Šต์„ ์ฒด๊ณ„์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "ํ•™์Šต์ž๋Š” ์‹ค์Šต๊ฐ€์ด๋“œ์— ์ œ์‹œ๋œ ์ด 4๊ฐœ ์„ธ๋ถ€ ํ™œ๋™(๊ธฐ๋ณธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ โž” ๊ฒฐ๊ณผ ์ง€ํ‘œ ๊ฒ€์ฆ โž” ํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹ ๋ฐ ๋„์ „๊ณผ์ œ)์„ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰ํ•˜๋ฉฐ ํ•ต์‹ฌ ํŒ์ • ๊ธฐ์ค€์„ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", "\n", "**์ด๋ฒˆ ๊ฒฐ๊ณผ๋ฌผ:** `src/utils/mov02_validator.py ๋ฐ ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ` ํ˜•ํƒœ์˜ ๊ฒฐ๊ณผ๋ฌผ์„ ์™„์„ฑํ•œ๋‹ค.\n", "\n", "### ๐Ÿ” ํ‰๊ฐ€ ๋ฐ ๊ฒ€์ฆ ๊ธฐ์ค€ (PASS ์กฐ๊ฑด)\n", "#### ๋ฏธ์…˜ ํŒ์ •\n", "\n", "**ํ‰๊ฐ€ ๊ตฌ๋ถ„:** ํ•„์ˆ˜\n", "\n", "**ํ•ต์‹ฌ ํ‰๊ฐ€์˜์—ญ:** ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๊ธฐ ์‰ฌ์šด ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๋ฐ ์กฐ์น˜ ๊ฐ€์ด๋“œ ์ œ๊ณต\n", "\n", "#### ๋ฏธ์…˜ PASS ์กฐ๊ฑด\n", "\n", "- ์ •๋Ÿ‰ ๊ธฐ์ค€: ์˜ˆ์™ธ ์ƒํ™ฉ ๋ฐœ์ƒ ์‹œ ํฌ๋ž˜์‹œ(Crash) 0๊ฑด, ์—๋Ÿฌ ๋กœ๊ทธ ๊ธฐ๋ก๋ฅ  100%\n", "\n", "\n", "- ์ง„ํ–‰ ๊ธฐ์ค€: ์˜ˆ์™ธ ์ฃผ์ž… ํ…Œ์ŠคํŠธ 10์ข… ํ†ต๊ณผ ๋ฐ ๊ฐ์‚ฌ ๋กœ๊ทธ ํŒŒ์ผ ์ƒ์„ฑ\n", "\n", "\n", "- ๊ฒ€์ฆ ๋ฐฉ๋ฒ•: ๋น„์ •์ƒ ๋ฐ์ดํ„ฐ(๊ฒฐ์ธก, ํŠน์ˆ˜๋ฌธ์ž, ๋Œ€์šฉ๋Ÿ‰) ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ\n", "\n", "\n", "#### ์ฑ„์ ์ž ํŒ์ • ๋ฉ”๋ชจ\n", "\n", "> ๋‹จ์ˆœ ์ฝ”๋“œ ์‹คํ–‰ ์—ฌ๋ถ€๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ถœ๋ ฅ๋œ ์ˆ˜์น˜์™€ ๊ทผ๊ฑฐ ์ž๋ฃŒ๊ฐ€ ๊ตฐ ์‹ค๋ฌด ์š”๊ตฌ์‚ฌํ•ญ์— ๋ถ€ํ•ฉํ•˜๋Š”์ง€ ๋ฉด๋ฐ€ํžˆ ํ™•์ธํ•œ๋‹ค.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\"\"\"์ˆ˜์†ก ์š”์ฒญ์— ์ž์‚ฐ(ํŠธ๋ŸญยทํŠธ๋ ˆ์ผ๋Ÿฌ)๊ณผ ์œ„ํ˜‘ ํšŒํ”ผ ๊ฒฝ๋กœ๋ฅผ ๋ฐฐ์ •ํ•ด ๊ด€์ œ ๋Œ€์‹œ๋ณด๋“œ(๊ฒฝ๋กœ ์ง€๋„ยท์ž์‚ฐ ๊ฐ€๋™๋ฅ ยท์šดํ–‰ ์ผ์ •ยท๋น„์šฉ ์‹ค์ )๋กœ ๊ฐ€์‹œํ™”ํ•˜๊ณ , ์ž…๋ ฅ ๊ฒ€์ฆ๊ธฐยท์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ธฐ๋กœ ์˜ˆ์™ธ ์ฃผ์ž… 10์ข…์„ ํฌ๋ž˜์‹œ ์—†์ด ๊ธฐ๋กยท์•ˆ๋‚ดํ•˜๋ฉฐ ์žฌ์‹œ๋„์™€ ๋กค๋ฐฑ์„ ํ™•์ธํ•œ๋‹ค.\"\"\"\n", "import heapq\n", "import json\n", "import time\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "# ---------------------------------------------------------------------------\n", "# [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ] ๊ฐ€์ƒ ๋„๋กœ๋งยท์ˆ˜์†ก ์ž์‚ฐ ์ƒ์„ฑ๊ธฐ (์‹ค์ œ ์ง€๋ช…ยท๋ถ€๋Œ€ยท์žฅ๋น„ ์ œ์› ์—†์Œ)\n", "# ๊ฒฉ์žํ˜• ๊ฐ€์ƒ ๋„๋กœ๋ง(๋…ธ๋“œ=๊ต์ฐจ์ , ๊ฐ„์„ =๋„๋กœ ๊ตฌ๊ฐ„)์— ๋„๋กœ ๋“ฑ๊ธ‰ยท๊ต๋Ÿ‰ ํ•˜์ค‘ยท๊ฐ€์ƒ ์œ„ํ˜‘ ๊ตฌ์—ญ์„ ๋ถ™์ธ๋‹ค.\n", "# ---------------------------------------------------------------------------\n", "ASSETS = { # ๊ต์œก์šฉ ๊ฐ€์ • ์ œ์›(์‹ค์ œ ์žฅ๋น„ ์ œ์› ์•„๋‹˜)\n", " \"truck\": {\"name\": \"์ค‘ํ˜• ํŠธ๋Ÿญ\", \"cap_t\": 5.0, \"speed_kmh\": 50, \"cost_km\": 1.0, \"weight_t\": 10.0},\n", " \"trailer\": {\"name\": \"๋Œ€ํ˜• ํŠธ๋ ˆ์ผ๋Ÿฌ\", \"cap_t\": 20.0, \"speed_kmh\": 40, \"cost_km\": 2.4, \"weight_t\": 30.0},\n", " \"airlift\": {\"name\": \"์ˆ˜์†ก๊ธฐ\", \"cap_t\": 12.0, \"speed_kmh\": 300, \"cost_km\": 9.0, \"weight_t\": 0.0},\n", "}\n", "ROAD_FACTOR = {\"paved\": 1.0, \"unpaved\": 0.6} # ๋„๋กœ ๋“ฑ๊ธ‰๋ณ„ ์†๋„ ๊ณ„์ˆ˜\n", "\n", "\n", "def make_network(seed=5, rows=6, cols=8, spacing=5.0):\n", " \"\"\"๊ฒฉ์žํ˜• ๊ฐ€์ƒ ๋„๋กœ๋ง. ๋…ธ๋“œ ์ขŒํ‘œ(km)์™€ ๊ฐ„์„  ๋ชฉ๋ก(๊ฑฐ๋ฆฌยท๋„๋กœ๋“ฑ๊ธ‰ยท๊ต๋Ÿ‰ ํ•˜์ค‘)์„ ๋Œ๋ ค์ค€๋‹ค.\"\"\"\n", " rng = np.random.default_rng(seed)\n", " nodes = {f\"N{r:02d}{c:02d}\": (c * spacing + rng.uniform(-1, 1), r * spacing + rng.uniform(-1, 1))\n", " for r in range(rows) for c in range(cols)}\n", " edges = []\n", "\n", " def add(a, b):\n", " (x1, y1), (x2, y2) = nodes[a], nodes[b]\n", " bridge = bool(rng.random() < 0.15)\n", " edges.append({\"id\": f\"E{len(edges):03d}\", \"u\": a, \"v\": b,\n", " \"km\": round(float(np.hypot(x2 - x1, y2 - y1) * rng.uniform(1.0, 1.25)), 2),\n", " \"road\": \"paved\" if rng.random() < 0.65 else \"unpaved\", \"bridge\": bridge,\n", " \"limit_t\": float(rng.choice([20, 40, 60])) if bridge else None, \"open\": True})\n", " for r in range(rows):\n", " for c in range(cols):\n", " if c + 1 < cols:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r:02d}{c + 1:02d}\")\n", " if r + 1 < rows:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r + 1:02d}{c:02d}\")\n", " if r + 1 < rows and c + 1 < cols and rng.random() < 0.25:\n", " add(f\"N{r:02d}{c:02d}\", f\"N{r + 1:02d}{c + 1:02d}\")\n", " return nodes, edges\n", "\n", "\n", "def make_threats(seed=9, n=3, xlim=(8, 28), ylim=(3, 22)):\n", " \"\"\"๊ฐ€์ƒ ์œ„ํ˜‘ ๊ตฌ์—ญ(์›ํ˜•). p = ๊ตฌ๊ฐ„ 1ํšŒ ํ†ต๊ณผ ์‹œ ํ”ผํ•ด ๊ฐ€๋Šฅ์„ฑ(๊ต์œก์šฉ ๊ฐ€์ •๊ฐ’).\"\"\"\n", " rng = np.random.default_rng(seed)\n", " return [{\"id\": f\"TZ{i + 1}\", \"x\": float(rng.uniform(*xlim)), \"y\": float(rng.uniform(*ylim)),\n", " \"r\": float(rng.uniform(3, 5)), \"p\": float(round(rng.uniform(0.05, 0.2), 3))} for i in range(n)]\n", "\n", "\n", "def edge_risk(e, nodes, threats):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 6] ์ „์ˆ  ์ˆ˜์†ก ๊ฒฝ๋กœ ๋ฐ ์ž์‚ฐ ๋ฐฐ์ • ํ˜„ํ™ฉ ๊ฐ€์‹œํ™” 2D/3D ์ˆ˜์†ก ๊ด€์ œ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def edge_minutes(e, asset=\"truck\"):\n", " # [TODO : ํ•™์ƒ ์‹ค์Šต 6] ์ „์ˆ  ์ˆ˜์†ก ๊ฒฝ๋กœ ๋ฐ ์ž์‚ฐ ๋ฐฐ์ • ํ˜„ํ™ฉ ๊ฐ€์‹œํ™” 2D/3D ์ˆ˜์†ก ๊ด€์ œ ๋Œ€์‹œ๋ณด๋“œ ๊ตฌํ˜„ ๊ด€๋ จ ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.\n", " # ํžŒํŠธ: ์ƒ๋‹จ ์ฃผ์„ ๋ฐ ๋ฐ์ดํ„ฐ ์Šคํ‚ค๋งˆ/์ง€์นจ์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.\n", " # <<< ์—ฌ๊ธฐ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š” >>>\n", " pass # ์ž‘์„ฑ ํ›„ pass๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.\n", "def passable(e, gross_t):\n", " return e[\"open\"] and (not e[\"bridge\"] or e[\"limit_t\"] >= gross_t)\n", "\n", "\n", "def build_adj(nodes, edges, weight_fn):\n", " \"\"\"weight_fn(e)๊ฐ€ None์ด๋ฉด ํ†ตํ–‰ ๋ถˆ๊ฐ€ ๊ฐ„์„ ์œผ๋กœ ๋ณด๊ณ  ์ œ์™ธํ•œ๋‹ค.\"\"\"\n", " adj = {n: [] for n in nodes}\n", " for e in edges:\n", " w = weight_fn(e)\n", " if w is not None:\n", " adj[e[\"u\"]].append((e[\"v\"], w, e))\n", " adj[e[\"v\"]].append((e[\"u\"], w, e))\n", " return adj\n", "\n", "\n", "def dijkstra(adj, src, dst):\n", " \"\"\"๋‹ค์ต์ŠคํŠธ๋ผ ์ตœ๋‹จ๊ฒฝ๋กœ. (๋น„์šฉ, ๋…ธ๋“œ ๊ฒฝ๋กœ, ๊ฐ„์„  ๋ชฉ๋ก). ๋„๋‹ฌ ๋ถˆ๊ฐ€๋ฉด (inf, [], []).\"\"\"\n", " dist, prev, done, pq = {src: 0.0}, {}, set(), [(0.0, src)]\n", " while pq:\n", " d, u = heapq.heappop(pq)\n", " if u in done:\n", " continue\n", " done.add(u)\n", " if u == dst:\n", " break\n", " for v, w, e in adj[u]:\n", " if d + w < dist.get(v, float(\"inf\")):\n", " dist[v], prev[v] = d + w, (u, e)\n", " heapq.heappush(pq, (d + w, v))\n", " if dst not in done:\n", " return float(\"inf\"), [], []\n", " path, es = [dst], []\n", " while path[-1] != src:\n", " u, e = prev[path[-1]]\n", " es.append(e); path.append(u)\n", " return dist[dst], path[::-1], es[::-1]\n", "# ---------------------------------------------------------------------------\n", "\n", "nodes, edges = make_network()\n", "threats = make_threats()\n", "DEPOT = \"N0000\"\n", "for e in edges:\n", " if e[\"id\"] in (\"E017\", \"E040\", \"E046\", \"E071\"):\n", " e[\"open\"] = False\n", " e[\"risk\"] = edge_risk(e, nodes, threats)\n", "for e in edges: # ๊ฐ€์ƒ ๊ณ ๋ฆฝ ์ง€์ : N0506 ์ฃผ๋ณ€ ๋„๋กœ ์ „๋ถ€ ํŒŒ๊ดด\n", " if \"N0506\" in (e[\"u\"], e[\"v\"]):\n", " e[\"open\"] = False\n", "\n", "FLEET = {**{f\"T{i:02d}\": \"truck\" for i in range(1, 9)}, **{f\"L{i:02d}\": \"trailer\" for i in range(1, 3)}}\n", "LOG = [] # ์—๋Ÿฌยท์ฒ˜๋ฆฌ ๋กœ๊ทธ(๊ฐ์‚ฌ์šฉ)\n", "\n", "\n", "class InputError(Exception):\n", " def __init__(self, code, msg, guide):\n", " super().__init__(msg); self.code, self.msg, self.guide = code, msg, guide\n", "\n", "\n", "class TransientError(Exception):\n", " pass\n", "\n", "\n", "def validate(req, seen):\n", " \"\"\"์ž…๋ ฅ ๊ฒ€์ฆ: ํ•„์ˆ˜ ํ•„๋“œยทํ˜•์‹ยท๋ฒ”์œ„ยท์ค‘๋ณตยท์กด์žฌ ์—ฌ๋ถ€. ์œ„๋ฐ˜ ์‹œ InputError(์ฝ”๋“œ, ๋ฉ”์‹œ์ง€, ์กฐ์น˜ ๊ฐ€์ด๋“œ).\"\"\"\n", " for k in (\"req_id\", \"dest\", \"cargo_t\", \"deadline_min\"):\n", " if k not in req or req[k] is None:\n", " raise InputError(\"V01\", f\"ํ•„์ˆ˜ ํ•ญ๋ชฉ '{k}' ๋ˆ„๋ฝ\", f\"์ˆ˜์†ก ์š”์ฒญ์„œ์˜ '{k}' ์นธ์„ ์ฑ„์›Œ ๋‹ค์‹œ ์ œ์ถœ\")\n", " rid = str(req[\"req_id\"])\n", " if not rid.replace(\"-\", \"\").isalnum() or len(rid) > 20:\n", " raise InputError(\"V02\", f\"์š”์ฒญ๋ฒˆํ˜ธ ํ˜•์‹ ์˜ค๋ฅ˜({rid[:12]}...)\", \"์˜๋ฌธยท์ˆซ์žยท'-'๋งŒ 20์ž ์ด๋‚ด๋กœ ์ž…๋ ฅ\")\n", " if rid in seen:\n", " raise InputError(\"V03\", f\"์ค‘๋ณต ์š”์ฒญ๋ฒˆํ˜ธ {rid}\", \"๊ธฐ์กด ์š”์ฒญ ์ˆ˜์ • ํ™”๋ฉด์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์ƒˆ ๋ฒˆํ˜ธ ๋ฐœ๊ธ‰\")\n", " try:\n", " cargo, dl = float(req[\"cargo_t\"]), float(req[\"deadline_min\"])\n", " except (TypeError, ValueError):\n", " raise InputError(\"V04\", f\"์ˆซ์ž ํ˜•์‹ ์˜ค๋ฅ˜(cargo_t={req['cargo_t']!r})\", \"๋‹จ์œ„ ์—†์ด ์ˆซ์ž๋งŒ ์ž…๋ ฅ(์˜ˆ: 12)\")\n", " if not np.isfinite(cargo) or not np.isfinite(dl):\n", " raise InputError(\"V05\", \"์ ์žฌ๋Ÿ‰ยท๊ธฐํ•œ ๊ฐ’์ด ๋น„์–ด ์žˆ์Œ(NaN)\", \"์ ์žฌ๋Ÿ‰(t)๊ณผ ๋„์ฐฉ ๊ธฐํ•œ(๋ถ„)์„ ํ™•์ธ\")\n", " if cargo <= 0 or dl <= 0:\n", " raise InputError(\"V06\", f\"์Œ์ˆ˜ยท0 ๊ฐ’(cargo_t={cargo}, deadline={dl})\", \"0๋ณด๋‹ค ํฐ ๊ฐ’์œผ๋กœ ์ž…๋ ฅ\")\n", " if req[\"dest\"] not in nodes:\n", " raise InputError(\"V07\", f\"์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ชฉ์ ์ง€ {req['dest']}\", \"๋„๋กœ๋ง ๋…ธ๋“œ ๋ชฉ๋ก์—์„œ ๋ชฉ์ ์ง€ ์„ ํƒ\")\n", " cap = sum(ASSETS[k][\"cap_t\"] for k in FLEET.values())\n", " if cargo > cap:\n", " raise InputError(\"V08\", f\"์ ์žฌ๋Ÿ‰ {cargo}t > ๊ฐ€์šฉ ์ˆ˜์†ก๋Šฅ๋ ฅ {cap}t\", \"๋ถ„ํ•  ์ˆ˜์†ก ์š”์ฒญ ๋˜๋Š” ์ƒ๊ธ‰ ์ œ๋Œ€ ์ง€์› ์š”์ฒญ\")\n", " return cargo, dl\n", "\n", "\n", "def route(dest, kind, gross, fail_first=False, _state={\"n\": 0}):\n", " if fail_first and _state[\"n\"] == 0: # ๊ฒฝ๋กœ ๊ณ„์‚ฐ ์„œ๋น„์Šค ์ผ์‹œ ์žฅ์•  ๋ชจ์˜(์ฒซ ํ˜ธ์ถœ๋งŒ ์‹คํŒจ)\n", " _state[\"n\"] += 1\n", " raise TransientError(\"๊ฒฝ๋กœ ๊ณ„์‚ฐ ์‹œ๊ฐ„ ์ดˆ๊ณผ\")\n", " adj = build_adj(nodes, edges, lambda e: edge_minutes(e, kind) + 150 * e[\"risk\"] if passable(e, gross) else None)\n", " cost, path, es = dijkstra(adj, DEPOT, dest)\n", " if not path:\n", " raise InputError(\"R01\", f\"{dest}๊นŒ์ง€ ํ†ตํ–‰ ๊ฐ€๋Šฅํ•œ ๊ฒฝ๋กœ ์—†์Œ\", \"๋„๋กœ ๋ณต๊ตฌ ์ •๋ณด ํ™•์ธ ํ›„ ๊ณต์ค‘์ˆ˜์†ก ๊ฒ€ํ† ๋ฅผ ํ†ต์ œ๊ด€์— ์š”์ฒญ\")\n", " return path, es\n", "\n", "\n", "def assign(req, cargo, dl, state, fail_first=False):\n", " \"\"\"์ ์žฌ๋Ÿ‰์— ๋งž๊ฒŒ ์ž์‚ฐ์„ ๊ณ ๋ฅด๊ณ (ํŠธ๋ ˆ์ผ๋Ÿฌ ์šฐ์„ , ๋‚˜๋จธ์ง€ ํŠธ๋Ÿญ) ๊ฒฝ๋กœ๋ฅผ ๋ฐฐ์ •ํ•œ๋‹ค. ์‹คํŒจ ์‹œ ํ˜ธ์ถœ์ž๊ฐ€ ๋กค๋ฐฑ.\"\"\"\n", " picks, left = [], cargo\n", " for vid in sorted(FLEET, key=lambda v: FLEET[v] != \"trailer\"): # 10t ์ดˆ๊ณผ๋ถ„์€ ํŠธ๋ ˆ์ผ๋Ÿฌ ์šฐ์„ , ๋‚˜๋จธ์ง€๋Š” ํŠธ๋Ÿญ\n", " if left <= 0:\n", " break\n", " if vid in state[\"busy\"] or (FLEET[vid] == \"trailer\" and left <= 10):\n", " continue\n", " picks.append(vid); state[\"busy\"].add(vid); left -= ASSETS[FLEET[vid]][\"cap_t\"] # ์ƒํƒœ ๋ณ€๊ฒฝ(๋กค๋ฐฑ ๋Œ€์ƒ)\n", " if left > 0:\n", " raise InputError(\"A01\", \"๊ฐ€์šฉ ์ž์‚ฐ ๋ถ€์กฑ\", \"๋Œ€๊ธฐ ํ›„ ์žฌ๋ฐฐ์ • ๋˜๋Š” ๋‹ค๋ฅธ ์š”์ฒญ๊ณผ ํ†ตํ•ฉ ์ˆ˜์†ก\")\n", " out = []\n", " for vid in picks:\n", " kind = FLEET[vid]\n", " for attempt in range(3): # ์ผ์‹œ ์žฅ์•  ์ž๋™ ์žฌ์‹œ๋„(์ตœ๋Œ€ 3ํšŒ)\n", " try:\n", " path, es = route(req[\"dest\"], kind, ASSETS[kind][\"weight_t\"] + min(ASSETS[kind][\"cap_t\"], cargo), fail_first and attempt == 0)\n", " break\n", " except TransientError as ex:\n", " LOG.append({\"req\": req[\"req_id\"], \"level\": \"WARN\", \"code\": \"T01\", \"msg\": f\"{ex} -> ์žฌ์‹œ๋„ {attempt + 1}ํšŒ\"})\n", " mins = sum(edge_minutes(e, kind) for e in es); km = sum(e[\"km\"] for e in es)\n", " out.append({\"req\": req[\"req_id\"], \"vid\": vid, \"kind\": kind, \"dest\": req[\"dest\"], \"path\": path, \"km\": km, \"min\": mins,\n", " \"cost\": km * ASSETS[kind][\"cost_km\"], \"threat\": sum(e[\"risk\"] > 0 for e in es), \"ontime\": mins <= dl})\n", " return out\n", "\n", "\n", "def process(batch):\n", " state, seen, result = {\"busy\": set()}, set(), []\n", " for req in batch:\n", " snap = set(state[\"busy\"]) # ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘ ์‹œ์  ์Šค๋ƒ…์ƒท\n", " try:\n", " cargo, dl = validate(req, seen)\n", " rows = assign(req, cargo, dl, state, fail_first=req.get(\"_flaky\", False))\n", " seen.add(str(req[\"req_id\"])); result += rows\n", " LOG.append({\"req\": req[\"req_id\"], \"level\": \"INFO\", \"code\": \"OK\", \"msg\": f\"๋ฐฐ์ • {len(rows)}๋Œ€\"})\n", " except InputError as ex:\n", " state[\"busy\"] = snap # ๋กค๋ฐฑ: ๋ถ€๋ถ„ ๋ฐฐ์ •ํ•œ ์ž์‚ฐ ๋ฐ˜ํ™˜\n", " LOG.append({\"req\": str(req.get(\"req_id\"))[:20], \"level\": \"ERROR\", \"code\": ex.code, \"msg\": ex.msg, \"guide\": ex.guide})\n", " except Exception as ex: # ์˜ˆ์ƒ ๋ชปํ•œ ์˜ค๋ฅ˜๋„ ํฌ๋ž˜์‹œ ์—†์ด ๊ธฐ๋ก\n", " state[\"busy\"] = snap\n", " LOG.append({\"req\": str(req.get(\"req_id\"))[:20], \"level\": \"ERROR\", \"code\": \"X99\", \"msg\": repr(ex)[:60], \"guide\": \"์šด์˜ ๋‹ด๋‹น์ž์—๊ฒŒ ๋กœ๊ทธ ์ „๋‹ฌ\"})\n", " return result, state\n", "\n", "\n", "# ---------------- 1) ์ •์ƒ ์š”์ฒญ ๋ฐฐ์ • + ๊ด€์ œ ๋Œ€์‹œ๋ณด๋“œ ----------------\n", "NORMAL = [{\"req_id\": f\"REQ-02{i:02d}\", \"dest\": d, \"cargo_t\": c, \"deadline_min\": dl}\n", " for i, (d, c, dl) in enumerate([(\"N0307\", 12, 90), (\"N0107\", 8, 80), (\"N0407\", 4, 70), (\"N0205\", 9, 75), (\"N0503\", 5, 60)])]\n", "NORMAL[3][\"_flaky\"] = True # ๊ฒฝ๋กœ ๊ณ„์‚ฐ ์ผ์‹œ ์žฅ์• ๊ฐ€ ๋‚˜๋„ ์žฌ์‹œ๋„๋กœ ์ฒ˜๋ฆฌ๋˜๋Š”์ง€ ํ™•์ธ\n", "plan, st = process(NORMAL)\n", "df = pd.DataFrame(plan)\n", "print(\"[๊ด€์ œ ํ˜„ํ™ฉํŒ] ์š”์ฒญ๋ณ„ ์ž์‚ฐ ๋ฐฐ์ •ยท๊ฒฝ๋กœ [๊ต์œก์šฉ๊ฐ€์ƒ์ž๋ฃŒ]\")\n", "summ = df.groupby(\"req\").agg(์ž์‚ฐ=(\"vid\", lambda s: \",\".join(s)), ๋ชฉ์ ์ง€=(\"dest\", \"first\"), ๊ฑฐ๋ฆฌkm=(\"km\", \"max\"),\n", " ์†Œ์š”๋ถ„=(\"min\", \"max\"), ์œ„ํ˜‘ํ†ต๊ณผ=(\"threat\", \"max\"), ์ •์‹œ=(\"ontime\", \"all\"), ๋น„์šฉ=(\"cost\", \"sum\")).round(1)\n", "print(summ.to_string())\n", "util = len(st[\"busy\"]) / len(FLEET)\n", "print(f\"์ž์‚ฐ ๊ฐ€๋™๋ฅ  {util:.0%} ({len(st['busy'])}/{len(FLEET)}๋Œ€), ์ด ์šดํ–‰๋น„์šฉ {df['cost'].sum():.1f}(๋‹จ๊ฐ€x km), \"\n", " f\"์ •์‹œ ๋„์ฐฉ ์˜ˆ์ƒ {summ['์ •์‹œ'].mean():.0%}, ์œ„ํ˜‘ ๊ตฌ์—ญ ํ†ต๊ณผ ์ฐจ๋Ÿ‰ {int((df['threat'] > 0).sum())}๋Œ€\")\n", "\n", "print(\"์ผ์‹œ ์žฅ์•  ์ž๋™ ์žฌ์‹œ๋„:\", [f\"{l['req']} {l['msg']}\" for l in LOG if l[\"code\"] == \"T01\"], \"-> ์žฌ์‹œ๋„ ํ›„ ์ •์ƒ ๋ฐฐ์ •\")\n", "print(\"์ง€์—ฐ ์˜ˆ์ƒ ์š”์ฒญ์€ ๋ถ‰์€์ƒ‰์œผ๋กœ ํ‘œ์‹œํ•˜๊ณ  ํ†ต์ œ๊ด€์—๊ฒŒ ๊ธฐํ•œ ์กฐ์ • ๋˜๋Š” ์ž์‚ฐ ๋ณ€๊ฒฝ์„ ๊ฑด์˜ํ•œ๋‹ค.\")\n", "\n", "# ---------------- 2) ์˜ˆ์™ธ ์ฃผ์ž… ํ…Œ์ŠคํŠธ 10์ข… ----------------\n", "LOG.clear()\n", "BAD = [{\"dest\": \"N0307\", \"cargo_t\": 3, \"deadline_min\": 60}, # 1 ํ•„์ˆ˜ ํ•ญ๋ชฉ ๋ˆ„๋ฝ\n", " {\"req_id\": \"REQ-