Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b5e918ddd | ||
|
|
a2f240c22b | ||
|
|
9045520b57 | ||
|
|
e657b76b52 | ||
|
|
5054ea6388 | ||
|
|
34f250e169 | ||
|
|
de2484754e | ||
|
|
1f64dc54e2 |
|
After Width: | Height: | Size: 538 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 158 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 391 KiB |
|
After Width: | Height: | Size: 454 KiB |
|
After Width: | Height: | Size: 367 KiB |
|
After Width: | Height: | Size: 523 KiB |
|
After Width: | Height: | Size: 448 KiB |
|
After Width: | Height: | Size: 520 KiB |
|
After Width: | Height: | Size: 469 KiB |
|
After Width: | Height: | Size: 401 KiB |
|
After Width: | Height: | Size: 393 KiB |
|
After Width: | Height: | Size: 408 KiB |
|
After Width: | Height: | Size: 400 KiB |
|
After Width: | Height: | Size: 424 KiB |
|
After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 168 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 308 KiB |
|
After Width: | Height: | Size: 229 KiB |
|
After Width: | Height: | Size: 284 KiB |
|
After Width: | Height: | Size: 255 KiB |
|
After Width: | Height: | Size: 237 KiB |
|
After Width: | Height: | Size: 222 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 218 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 297 KiB |
|
After Width: | Height: | Size: 323 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 167 KiB |
|
After Width: | Height: | Size: 553 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 1007 KiB |
|
After Width: | Height: | Size: 237 KiB |
@@ -0,0 +1,36 @@
|
||||
# 1. Hook
|
||||
|
||||
**예시**
|
||||
> Big-O 시간 복잡도의 한계를 넘어,
|
||||
알고리즘의 세부 작업 실행 횟수와 단위 실행 시간을 고려한
|
||||
가장 정확한 Dijkstra 알고리즘 런타임 예측 모델
|
||||
|
||||
짧은 Hook
|
||||
|
||||
# 2. 프로젝트 소개
|
||||
|
||||
**예시**
|
||||
> 네비게이션, 자율주행, 우주항공 등 많은 현대 기술들에게 최적 경로를 탐색하는 것은 중요한 일이며, Dijkstra 알고리즘은 최적 경로를 도출하는 가장 효율적인 알고리즘이다. \
|
||||
그러나 자율주행과 같이 실시간성과 속도가 중요한 문제가 많아지자, 휴리스틱 기반의 A* 알고리즘들이 적용되었다. 이들은 속도는 빠르지만, 항상 최적해를 보장하지는 않한다는 트레이드오프가 존재한다. \
|
||||
상황에 따라 적절한 알고리즘을 선택하면 될 듯 하지만, Big-O 시간복잡도는 실행 상황이 아닌 알고리즘만을 고려하기 때문에 Dijkstra의 실행시간이 충분한지 알기 어렵다. \
|
||||
그래서 **"이 트레이드오프를 감이 아닌 숫자로 계산해서 상황에 따른 정확한 알고리즘을 선택할 수는 없을까?"**라는 질문에서 출발하여 입력 그래프와 기기 성능만으로 Dijkstra의 실제 실행시간을 예측하는 모델을 연구했다.
|
||||
|
||||
IB 소논문 활동에서 시작해 계속 발전시켜 현재는 외부 학술지에도 기제된 연구로, 목표는 그래프의 노드 수, 간선 수, 가중치의 분산, 기기 성능만으로 이집 힙이 적용된 Dijkstra의 실행시간을 예측하는 것이다.
|
||||
|
||||
|
||||
- 제목
|
||||
- 배경
|
||||
- 문제 제시
|
||||
- 개인적 연결 (내가 관심을 갖게 된 이유)
|
||||
- 활동 부가 설명 (육하원칙으로)
|
||||
|
||||
# 3. 시행착오
|
||||
|
||||
- 제목
|
||||
- 문제
|
||||
- 해결
|
||||
|
||||
# 4. 결과
|
||||
|
||||
- 결과
|
||||
- 연결
|
||||
@@ -0,0 +1,565 @@
|
||||
#import "template.typ": *
|
||||
|
||||
|
||||
// ==================================================
|
||||
// Config Variables
|
||||
// ==================================================
|
||||
|
||||
#show: base.with(
|
||||
cur_domain: 1,
|
||||
total_domain: 5,
|
||||
domain_name: "Research",
|
||||
domain_detail: "본질을 향한 여정",
|
||||
date: "2026",
|
||||
author: "Seungjun Lee",
|
||||
main-hue: 60deg,
|
||||
)
|
||||
|
||||
|
||||
// ==================================================
|
||||
// Main content
|
||||
// ==================================================
|
||||
|
||||
#cover()
|
||||
|
||||
#counter(page).update(1)
|
||||
|
||||
#table_of_contents()
|
||||
|
||||
#domain_frame(
|
||||
bg_img_path: "assets/research/bg.jpg",
|
||||
cover_color: rgb("0F0F0F").transparentize(40%),
|
||||
font_color: color.oklch(95%, 0.01, 250deg),
|
||||
chrome_color: color.oklch(80%, 0.01, 250deg),
|
||||
title: [본질을 향한 여정],
|
||||
)[
|
||||
나는 세상을 하나의 질문으로 이해하고 싶었다. \
|
||||
답을 알수록 새로운 질문이 늘어난다는 것도, \
|
||||
그 질문들이 결국 하나로 이어지지 않는다는 것도 나중에 알았다.
|
||||
|
||||
생명 수업에서 인체를 배울 때 이 사실을 가장 선명하게 느꼈다. \
|
||||
나는 인간이면서도 내 몸 안에서 무슨 일이 일어나는지 인지하지 못한다. \
|
||||
신경계가, 세포가 어떻게 상호작용하는지 하나의 그림으로 설명하려 할수록 \
|
||||
설명은 흐려졌다.
|
||||
|
||||
전체를 통째로 이해하려는 시도는 매번 그렇게 무너졌다. \
|
||||
대신 남은 방법은 하나였다 — 쪼개서 이해하고, 그 이해들을 다시 쌓아 올리는 것. \
|
||||
전체가 아니라 부분에서, 이론이 아니라 실제 관찰에서 다시 시작하는 것.
|
||||
|
||||
이후의 모든 연구는 이 하나의 발견에서 출발한다. \
|
||||
그래프 알고리즘의 이론적 시간복잡도가 실제 실행 시간과 어긋나는 지점에서, \
|
||||
사회 현상을 하나의 법칙이 아니라 여러 변수의 상호작용으로 다시 볼 때, \
|
||||
그리고 인간의 선택이 이론적 모형을 벗어나는 순간에서 — \
|
||||
나는 같은 방법을 반복했다. 쪼개고, 어긋난 지점을 찾고, 다시 쌓아 올렸다.
|
||||
]
|
||||
|
||||
#activity_cover(
|
||||
name: [Dijkstra Runtime Analysis],
|
||||
hook: [Big-O가 버린 세부 실행비용을 다시 세우고, 실행해야만 알 수 있던 키 감소 횟수를 그래프 특성만으로 구해, 이진 힙 Dijkstra의 런타임 변동 96.9%를 실행 전에 설명한 모델.],
|
||||
)[
|
||||
네비게이션, 자율주행, 우주항공 등 많은 현대 기술들에게 최적 경로를 탐색하는 것은 중요한 일이며,
|
||||
Dijkstra 알고리즘은 최적 경로를 도출하는 가장 효율적인 알고리즘이다. \
|
||||
그러나 자율주행과 같이 실시간성과 속도가 중요한 문제가 많아지자, 휴리스틱 기반의 A\* 알고리즘들이 적용되었다. 이들은 속도는 빠르지만, 항상 최적해를 보장하지는 않한다는 트레이드오프가 존재한다. \
|
||||
상황에 따라 적절한 알고리즘을 선택하면 될 듯 하지만, Big-O 시간복잡도는 노드·간선 수 등 입력 크기에 따른 점근적 증가율만 나타낼 뿐, 상수항이나 그래프의 특성, 기기 성능 같은 요인은 반영하지 않기 때문에 Dijkstra의 실제 실행시간이 충분한지 알기 어렵다. \
|
||||
그래서 *"이 트레이드오프를 감이 아닌 숫자로 계산해서 상황에 따른 정확한 알고리즘을 선택할 수는 없을까?"*라는 질문에서 출발하여 입력 그래프와 기기 성능만으로 Dijkstra의 실제 실행시간을 예측하는 모델을 연구했다.
|
||||
|
||||
IB 소논문 활동에서 시작해 계속 발전시켜 현재는 외부 학술지에도 기제된 연구로, 목표는 그래프의 노드 수, 간선 수, 가중치의 분산, 기기 성능만으로 이집 힙이 적용된 Dijkstra의 실행시간을 예측하는 것이다.
|
||||
]
|
||||
|
||||
// #activity_intro(
|
||||
// title: "Big-O가 답하지 못하는 것",
|
||||
// )[
|
||||
// 네비게이션, 자율주행, 우주항공 등 많은 현대 기술들에게 최적 경로를 탐색하는 것은 중요한 일이며,
|
||||
// Dijkstra 알고리즘은 최적 경로를 도출하는 가장 효율적인 알고리즘이다. \
|
||||
// 그러나 자율주행과 같이 실시간성과 속도가 중요한 문제가 많아지자, 휴리스틱 기반의 A\* 알고리즘들이 적용되었다. 이들은 속도는 빠르지만, 항상 최적해를 보장하지는 않한다는 트레이드오프가 존재한다. \
|
||||
// 상황에 따라 적절한 알고리즘을 선택하면 될 듯 하지만, Big-O 시간복잡도는 노드·간선 수 등 입력 크기에 따른 점근적 증가율만 나타낼 뿐, 상수항이나 그래프의 특성, 기기 성능 같은 요인은 반영하지 않기 때문에 Dijkstra의 실제 실행시간이 충분한지 알기 어렵다. \
|
||||
// 그래서 *"이 트레이드오프를 감이 아닌 숫자로 계산해서 상황에 따른 정확한 알고리즘을 선택할 수는 없을까?"*라는 질문에서 출발하여 입력 그래프와 기기 성능만으로 Dijkstra의 실제 실행시간을 예측하는 모델을 연구했다.
|
||||
|
||||
// IB 소논문 활동에서 시작해 계속 발전시켜 현재는 외부 학술지에도 기제된 연구로, 목표는 그래프의 노드 수, 간선 수, 가중치의 분산, 기기 성능만으로 이집 힙이 적용된 Dijkstra의 실행시간을 예측하는 것이다.
|
||||
// ]
|
||||
|
||||
#activity_table()
|
||||
|
||||
#let RS = math.italic("RS")
|
||||
#let RSR = math.italic("RSR")
|
||||
|
||||
#activity_process(
|
||||
title: [*통제된 환경*],
|
||||
hook: [통제된 환경에서 벗어나다.],
|
||||
)[
|
||||
#problem()[
|
||||
실행 시간 측정의 신뢰도와 타당도를 높이기 위해서는 일정한 환경을 유지하는 것이 중요하다.
|
||||
그러나 일반적인 데스크탑이나 노트북 환경에서는 통제된 환경을 구현하기 어렵다.
|
||||
]
|
||||
|
||||
#hl[]
|
||||
|
||||
#solution()[
|
||||
운영중인 홈서버의 Proxmox VE에 특정한 스펙의 Debian CT를 만들어 환경을 통제한다.
|
||||
]
|
||||
|
||||
#figure(
|
||||
image("assets/research/dijkstra/pve.png"),
|
||||
caption: [CPU: AMD Ryzen 7 7700 8-Core Processor (4cores, Host) \/ RAM: 8GB \/ \
|
||||
OS: Debian GNU/Linux 13 (trixie) \/ Kernal: Linux 6.8.12-17-pve \/
|
||||
Python: Python 3.12],
|
||||
)
|
||||
]
|
||||
|
||||
#activity_process(
|
||||
title: "Big-O의 한계를 넘어서다",
|
||||
)[
|
||||
#problem()[
|
||||
`Big-O` 시간복잡도는 최고차항을 제외한 모든 항들을 무시하고, 계수마자 없앤다. \
|
||||
그렇기 때문에 시간복잡도가 $O(n^2)$인 알고리즘에 $n$을 넣는다고 실행시간을 구할 수 없다.
|
||||
]
|
||||
|
||||
#hl[]
|
||||
|
||||
#solution()[
|
||||
Dijkstra 알고리즘을 의사코드를 기반으로 세부 작업들로 쪼개어 실행시간 식을 도출한다.
|
||||
]
|
||||
|
||||
#block_equation(description: [
|
||||
$V$ : 노드 수, $E$ : 간선 수, $RS$ : 키 감소 실행 횟수
|
||||
])[
|
||||
$
|
||||
"Runtime" & = "Heap build" + "Min-node find" + "Vertex Iterate" + "Decrease key" \
|
||||
& = "V" dot k_1 log(V) + V dot k_2 log(V) + E dot k_3 + RS dot k_4 log(V) \
|
||||
& = V dot k_12 log(V) + E dot k_3 + RS dot k_4 log(V)
|
||||
$
|
||||
]
|
||||
|
||||
이진 힙이 적용된 Dijkstra 알고리즘은 크게 위의 4개의 동작으로 나눌 수 있다. \
|
||||
각각의 시간 복잡도와 계수 그리고 실행 횟수의 곱을 모두 더하면 전체 실행 시간이 나온다. \
|
||||
이때, 첫 두 항의 문자 부분이 같기 때문에 인수분해를 통해 두 식을 합친다.
|
||||
이때 개별 항의 계수를 구하는 것이 목적이 아니기 때문에 두 계수를 합쳐 $k_12$라고 한다.
|
||||
|
||||
이 식을 통해 $V, E, RS, k_12, k_3, k_4$만 알면 Dijkstra의 실행시간을 구할 수 있게 된다.
|
||||
]
|
||||
|
||||
#activity_process(
|
||||
title: "존재하지 않는 데이터를 만들다",
|
||||
)[
|
||||
#problem()[
|
||||
식을 학습시키려면 방대한 양의 실제 경로 데이터가 필요했다.
|
||||
그러나 현실에서는 학습에 필요한 만큼의 실제 경로 데이터를 구할 수 없다.
|
||||
]
|
||||
|
||||
#hl[]
|
||||
|
||||
#solution()[
|
||||
실제 경로 데이터의 특성들(크기, 밀도, 가중치 분포 등)을 분석하여, 이를 토대로 가상의 경로 데이터를 제작한다.
|
||||
]
|
||||
|
||||
#figure(image("assets/research/dijkstra/hist_original.png", width: 100%), caption: [원데이터 분포])
|
||||
|
||||
#grid(
|
||||
columns: (auto, auto),
|
||||
figure(
|
||||
image("assets/research/dijkstra/hist_log.png"),
|
||||
caption: [$log$ 데이터 Q-Q 플롯],
|
||||
),
|
||||
figure(
|
||||
image("assets/research/dijkstra/Q-Q_log.png"),
|
||||
caption: [$log$ 데이터 Q-Q 플롯],
|
||||
),
|
||||
)
|
||||
|
||||
원데이터의 분포와 Q-Q 플롯을 통해 데이터가 양의 왜도를 지닌 것을 파악하여, 가중치에 $log$를 취한 후 다시 Q-Q 플롯을 그리니 정규성을 보였다.
|
||||
|
||||
#figure(
|
||||
table(
|
||||
columns: (auto, auto),
|
||||
[*그래프 밀도*], [$tilde.eq 1.02 dot 10^(-07)$],
|
||||
[*가중치 평균*], [$tilde.eq 2950$],
|
||||
[*표준편차 평균*], [$tilde.eq 4070$],
|
||||
),
|
||||
caption: [실제 경로 데이터 분석 값],
|
||||
)
|
||||
|
||||
이를 바탕으로 그래프의 밀도와 가중치에 대한 추가적인 특성들을 분석하였다.
|
||||
|
||||
#figure(
|
||||
table(
|
||||
columns: (auto, auto),
|
||||
[*노드 수*], [${2000 dot 2^k | k = 0, 1, 2, 3}$],
|
||||
[*그래프 밀도*], [${10^k, 3 dot 10^k | k = -7, -6, ..., -1}$],
|
||||
[*표준편차*], [${1000, 1500, 2000, 3000, 4000, 6000, 8000, 12000, 16000}$],
|
||||
[*개수*], [$10$],
|
||||
[*가중치 평균*], [$3000$],
|
||||
[*시작 노드*], [$0$],
|
||||
[*랜덤 시드*], [$42$],
|
||||
),
|
||||
caption: [데이터 생성 매개변수 조합],
|
||||
)
|
||||
|
||||
분석 값들을 바탕으로 적절한 노드 수, 그래프 밀도, 가중치 분산 3개 변수의 쌍으로 로그 정규분포를 띄는 가상 그래프를 864,000개 생성했다.
|
||||
|
||||
#figure(
|
||||
image("assets/research/dijkstra/synthetic_data_code.png"),
|
||||
caption: u_link(
|
||||
"https://gitea.seung6lee.com/seung6lee/dijkstra-runtime-analysis/src/branch/main/codes/experiments/synthetic_data/graph_generators/outdegree_graph_generator.py",
|
||||
)[outdegree 기반 랜덤 경로 데이터 생성 프로그램],
|
||||
)
|
||||
|
||||
#figure(
|
||||
image("assets/research/dijkstra/synthetic_data.png"),
|
||||
caption: u_link(
|
||||
"https://gitea.seung6lee.com/seung6lee/dijkstra-runtime-analysis/src/branch/main/codes/results/real_data/raw/dimacs_s42.csv",
|
||||
)[가상 경로 데이터 864,000개],
|
||||
)
|
||||
]
|
||||
|
||||
#activity_process(
|
||||
title: "실행해야만 알 수 있는 값",
|
||||
)[
|
||||
#problem()[
|
||||
모델을 학습시켜 가중치 $k$들을 알아낼 수 있다.
|
||||
그리고 $V$와 $E$는 그래프에서 쉽계 계산할 수 있다.
|
||||
그러나 $RS$는 Dijkstra를 실행하지 않으면 알 수 없다.
|
||||
예측을 위해 실행이 선행되어야 하는 모순이 발생하는 것이다.
|
||||
]
|
||||
|
||||
#hl[]
|
||||
|
||||
#solution()[
|
||||
그래프의 특성만으로 Dijkstra 실행 없이 $RS$를 예측하는 방법을 찾는다.
|
||||
]
|
||||
|
||||
#steps(
|
||||
(
|
||||
[$bold(RS)$ 재정의],
|
||||
[
|
||||
$RS$를 그 자체가 아니라, 다른 변수들로 쪼개어서 정의한다.
|
||||
|
||||
#block_equation()[
|
||||
$ RS = "Relax Attemps" times "Relax Success Ratio" = E times RSR $
|
||||
]
|
||||
|
||||
시도 횟수는 $E$라는 것을 알기에, $RSR$만 예측하면 된다. \
|
||||
$RSR$은 비율이기에 절대량이 아닌 밀도나 분산 등에 영향을 받을 것이라고 판단한다.
|
||||
#fill_break()
|
||||
],
|
||||
),
|
||||
(
|
||||
[$bold(RSR) thin \& thin bold(sigma)$],
|
||||
[
|
||||
#figure(
|
||||
image("assets/research/dijkstra/sigma_rsr_01.png", width: 60%),
|
||||
caption: [$V = 2000, thin D = 0.01$ 에서의 $sigma "vs" RSR$],
|
||||
)
|
||||
각 $(V, D)$ 쌍에 대해 $(sigma, RSR)$ 그래프를 그린 결과,
|
||||
대부분의 경우 완벽한 로그 함수 관계를 보인다.
|
||||
|
||||
#grid(
|
||||
columns: (1fr, 1fr),
|
||||
gutter: 1em,
|
||||
figure(
|
||||
image("assets/research/dijkstra/sigma_rsr_02.png", width: 100%),
|
||||
caption: [$V = 2000, thin D = 3 dot 10^(-5)$ 에서의 $sigma "vs" RSR$],
|
||||
),
|
||||
figure(
|
||||
image("assets/research/dijkstra/sigma_rsr_03.png", width: 100%),
|
||||
caption: [$V = 2000, thin D = 0.0003$ 에서의 $sigma "vs" RSR$],
|
||||
),
|
||||
)
|
||||
그러나 $sigma$값에 관계없이 항상 $RSR=1$이거나, 경향성이 전혀 없는 경우도 나타난다.
|
||||
|
||||
#block_equation(description: [
|
||||
$a, b$는 상수
|
||||
])[
|
||||
$ RSR = a dot log(sigma) + b $
|
||||
]
|
||||
|
||||
$log$ 관계를 가정하고 위와 같이 $RSR$과 $sigma$의 관계를 식으로 나타내었다.
|
||||
|
||||
#fill_break()
|
||||
],
|
||||
),
|
||||
(
|
||||
[$bold(RSR) thin \& thin bold(D)$],
|
||||
[
|
||||
#figure(
|
||||
image("assets/research/dijkstra/density_rsr.png", width: 60%),
|
||||
caption: [$V = 10000, thin sigma = 0.3246$ 에서의 $D "vs" RSR$],
|
||||
)
|
||||
각 $(V, sigma)$ 쌍에 대해 $(D, RSR)$ 그래프를 그린 결과,
|
||||
초반에 급격하게 감소하다 완만해지고 꼬리가 길게 남는 개형을 보인다. \
|
||||
이는 멱법칙의 특성과 부합하여, $(log(D), log(RSR))$ 그래프를 그려본다.
|
||||
|
||||
#figure(
|
||||
image("assets/research/dijkstra/density_rsr_loglog.png", width: 60%),
|
||||
caption: [$V = 10000, thin sigma = 0.3246$ 에서의 $log(D) "vs" log(RSR)$],
|
||||
)
|
||||
특정 임계점을 기준으로 왼쪽은 항상 $RSR = 1$인 구간이고, 오른쪽은 두 변수가 완벽하게 멱법칙을 만족하는 구간이 나온다. \
|
||||
이 임계점의 위치와 의미를 설명하려 했으나, 해당 지점의 $D$나 $log(D)$의 유의미한 특징을 발견하지 못하였다.
|
||||
#fill_break()
|
||||
],
|
||||
),
|
||||
(
|
||||
[$bold(D)$ 분석],
|
||||
[
|
||||
$D$를 다른 변수로 대체해보았다. \
|
||||
방향 그래프에서 $D = E / V(V - 1)$로 나타낼 수 있고,
|
||||
평균 차수는 $macron(k) = E \/ V = (V - 1) D$로 나타낼 수 있다.
|
||||
$D$대신 $macron(k)$를 대입해 그래프를 그려보았으며,
|
||||
이때 $V$는 통제 변인으로 각 그래프에서 상수로 취급된다.
|
||||
|
||||
#grid(
|
||||
columns: (1fr, 1fr),
|
||||
figure(
|
||||
image("assets/research/dijkstra/avgdeg_rsr_loglog.png"),
|
||||
caption: [$V = 10000, thin sigma = 0.3246$ 에서의 $log(macron(k)) "vs" log(RSR)$],
|
||||
),
|
||||
figure(
|
||||
image("assets/research/dijkstra/phase_histogram.png"),
|
||||
caption: [$macron(k)$에 따른 경로 그래프 양상 분포],
|
||||
),
|
||||
)
|
||||
|
||||
그 결과, 임계점이 항상 $macron(k) = 1$인 구간에서 발생한다.
|
||||
이 지점은 각 노드에서 출발하는 간선이 최소 1개씩 있는 경우이다.
|
||||
|
||||
이 현상은 Erdős-Rényi 모델의 3가지 상과 연결된다는 것을 파악했다.
|
||||
해당 모델은 $G(n,p)$ 그래프를 $n p$의 값에 따라 아임계, 임계, 초임계 구간으로 분류한다.\
|
||||
$sigma$ 분석에서의 예외 상황들도 아임계 구간과 임계 구간으로 설명이 가능해졌다.
|
||||
|
||||
#block_equation(description: [
|
||||
$c, d$는 상수
|
||||
])[
|
||||
$ RSR = c dot macron(k)^d quad (macron(k) > 1) $
|
||||
]
|
||||
|
||||
$macron(k) > 1$인 조건에서 위와 같이 $RSR$과 $d$의 관계를 식으로 나타내었다.
|
||||
#fill_break()
|
||||
],
|
||||
),
|
||||
(
|
||||
[최종 식 도출],
|
||||
[
|
||||
$sigma$ 분석 과정에서 발견한 Erdős-Rényi 모델과의 연결성을 통해 $d$ 분석 과정에서의 한계를 극복하여, $sigma$와 $d$ 각각과 $RSR$간의 식을 확정하였다.
|
||||
|
||||
이때 $sigma$와 $d$는 그래프에서 서로 독립적이기 때문에 두 관계식을 곱하면 두 변수의 영향을 모두 반영하는 식을 도출할 수 있다.
|
||||
|
||||
#block_equation(description: [
|
||||
$alpha, beta, gamma$는 상수
|
||||
])[
|
||||
$
|
||||
RSR & = {a dot log(sigma) + b} times {c dot macron(k)^d} quad (macron(k) > 1) \
|
||||
& = macron(k)^d times {a c dot log(sigma) + b c} \
|
||||
& = macron(k)^alpha times {beta dot log(sigma) + gamma}
|
||||
$
|
||||
]
|
||||
],
|
||||
),
|
||||
(
|
||||
[회귀 분석],
|
||||
[
|
||||
#figure(
|
||||
image("assets/research/dijkstra/rsr_regression.png", width: 50%),
|
||||
caption: [비선형 회귀 결과 (
|
||||
$alpha = 0.0612 plus.minus 0.000202, thin
|
||||
beta = 0.928 plus.minus 0.000171, thin
|
||||
gamma = -0.711 plus.minus 0.000183$)],
|
||||
)
|
||||
|
||||
제작한 864,000개의 데이터에 대해 비선형 회귀를 진행하였더니 $RSR$에 대해 $R^2 = 0.9947$에 달하는 적합에 성공하였다.
|
||||
|
||||
이를 통해 Dijkstra 실행 없이도 $99%$의 정확도로 $RS$를 예측할 수 있게 되었다.
|
||||
],
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
#activity_process(title: "다중공선성")[
|
||||
#problem()[
|
||||
#block_equation(description: [
|
||||
$V$ : 노드 수, $E$ : 간선 수, $RS$ : 키 감소 실행 횟수
|
||||
])[
|
||||
$
|
||||
"Runtime" = V dot k_12 log(V) + E dot k_3 + RS dot k_4 log(V)
|
||||
$
|
||||
]
|
||||
|
||||
이제 회귀 분석을 통해 $k_12, k_3, k_4$만 구하면 된다.
|
||||
|
||||
그러나 $k_12$와 $k_4$가 포함된 두 항이 유사하다.
|
||||
공통적으로 $log(V)$가 있으며, $V$와 $RS$도 양의 상관관계를 가진다.
|
||||
따라서 회귀분석 시행하면 다중공선성이 발생할 가능성이 높다.
|
||||
]
|
||||
|
||||
#hl[]
|
||||
|
||||
#solution()[
|
||||
모든 데이터를 한번에 회귀하는 것이 아니라, 고유한 $V$별로 부분 회귀를 하고 결과를 종합하는 방법을 고안했다.
|
||||
|
||||
#block_equation()[
|
||||
$
|
||||
"UC"_1(V) & = k_12 log(V) \
|
||||
"UC"_2(V) & = k_4 log(V) \
|
||||
"Runtime" & = V dot "UC"_1(V) + E dot k_3 + RS dot "UC"_2(V)
|
||||
$
|
||||
]
|
||||
|
||||
$V = V_i$인 데이터로 회귀를 하면 $"UC"_1(V_i), "UC"_2(V_i), k_3$가 도출된다. \
|
||||
이때 $"UC"_1(V)$과 $"UC"_2(V)$는 $V$에 대해 $log$ 관계를 가져야하고, $k_3$는 상수여야한다.
|
||||
]
|
||||
]
|
||||
|
||||
#activity_process(title: "이론과 결과의 괴리")[
|
||||
#callout(title: [$bold("UC"_1(V_i)), bold("UC"_2(V_i)), bold(k_3)$ 적합 결과])[
|
||||
#grid(
|
||||
columns: (1fr, 1fr),
|
||||
gutter: 1em,
|
||||
figure(
|
||||
image("assets/research/dijkstra/node_uc1.png", width: 100%),
|
||||
caption: [$"UC"_1(V)$],
|
||||
),
|
||||
figure(
|
||||
image("assets/research/dijkstra/node_uc2.png", width: 100%),
|
||||
caption: [$"UC"_2(V)$],
|
||||
),
|
||||
)
|
||||
#figure(
|
||||
image("assets/research/dijkstra/node_k3.png", width: 60%),
|
||||
caption: [$"k"_3(V)$],
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
#problem()[
|
||||
$"UC"_2(V)$는 $V$와 정확한 $log$ 관계를 보였다.\
|
||||
그러나 $"UC"_1(V)$는 어떠한 경향성도 보이지 않았으며, \
|
||||
$k_3$는 고정되지 않고 우하향의 추세를 보였다.
|
||||
]
|
||||
|
||||
#hl[]
|
||||
|
||||
#solution()[
|
||||
#figure(
|
||||
image("assets/research/dijkstra/contribution_vs_N.png", width: 100%),
|
||||
caption: [세부 작업별 전체 실행시간 차지 비율],
|
||||
)
|
||||
|
||||
$"UC"_1(V)$의 경향성은 파악이 불가능했으나,
|
||||
전체 런타임에서 $V dot "UC"_1(V)$항이 차지하는 비율이 $V lt.eq.slant 20000$ 인 지점에서 평균 $4.2 %$ 이하라는 것을 고려해 평균을 대푯값으로 사용하기로 하였다.
|
||||
|
||||
$k_3$의 우하향 추세는 파이썬의 캐싱 효과로 인한 것이라고 추정하였다. \
|
||||
실행시간은 양수여야한다는 점을 고려하여 지수 포화 함수로 모델링하였다.
|
||||
|
||||
$"UC"_2(V)$는 $log$ 함수 적합을 통해 성공적으로 $k_4$를 구했다.
|
||||
]
|
||||
]
|
||||
|
||||
// #activity_result(title: "예측할 수 있게 된 것")[
|
||||
// #block_equation(
|
||||
// title: [키 감소 횟수],
|
||||
// description: [$V$ : 노드 수, $E$ : 간선 수, $sigma$ : 가중치 표준편차],
|
||||
// )[
|
||||
// $
|
||||
// RSR(macron(k), sigma) & = macron(k)^0.0612 (0.928 log sigma - 0.711), quad
|
||||
// macron(k)(V, E) & = E \/ V \
|
||||
// RS(V, E, sigma) & = E dot RSR(macron(k)(V, E), sigma)
|
||||
// $
|
||||
// ]
|
||||
|
||||
// 실행해야만 알 수 있던 $RS$를 세 변수만으로 계산할 수 있게 되었다.
|
||||
|
||||
// #block_equation(
|
||||
// title: [단위 실행시간],
|
||||
// description: [단위 : 초],
|
||||
// )[
|
||||
// $
|
||||
// "UC"_1 & = 2.15 times 10^(-7) \
|
||||
// k_3(V) & = 1.59 times 10^(-7) e^(-5.67 times 10^(-5) V) + 4.37 times 10^(-8) \
|
||||
// "UC"_2(V) & = 1.06 times 10^(-6) log_2 V - 1.09 times 10^(-5)
|
||||
// $
|
||||
// ]
|
||||
|
||||
// 세 값은 모두 $V$의 함수이거나 상수가 되어 식에 남아 있던 미지수가 전부 사라진다.
|
||||
|
||||
// #block_equation(
|
||||
// title: [최종 모델],
|
||||
// description: [입력 : $V$, $E$, $sigma$],
|
||||
// )[
|
||||
// $
|
||||
// "Runtime"(V, E, sigma) = V dot "UC"_1 + E dot k_3(V) + RS(V, E, sigma) dot "UC"_2(V)
|
||||
// $
|
||||
// ]
|
||||
|
||||
// 최종 식의 세 변수 모두 실행 전에 그래프에서 직접 추출할 수 있는 값이기에, \
|
||||
// *Dijkstra를 한 번도 실행하지 않고 실행시간을 예측할 수 있다.*
|
||||
// ]
|
||||
|
||||
// #activity_result(title: "남은 오차")[
|
||||
// #figure(
|
||||
// image("assets/research/dijkstra/comparison_plot.png"),
|
||||
// caption: [실제 경로 데이터에 대한 실행시간 예측과 $RS$ 예측],
|
||||
// )
|
||||
|
||||
// #figure(
|
||||
// image("assets/research/dijkstra/predicted_vs_real.png", width: 60%),
|
||||
// caption: [생성 그래프에 대한 예측값과 실측값],
|
||||
// )
|
||||
|
||||
// 내삽에 대해 실행 시간 예측에 대해서 $96.9%$를 설명에 성공했다. \
|
||||
// 게다가 실제 경로 데이터(외삽)에서도 $RS$예측에 대해 일관된 정확도를 보였다. \
|
||||
// 그러나 실전 상황에서 그래프가 커질수록 실행시간 예측은 과대평가하는 경항이 보였다.\
|
||||
|
||||
// 이 결과들에서 내 연구의 의의와 한계가 뚜렷하게 드러난다. \
|
||||
// $RS$는 그래프의 구조만으로 결정되므로, 그래프의 근본적인 특성까지 쪼개었더니 정확하게 설명이 가능해졌으나, 실행시간은 파이썬 아래의 캐싱이나 하드웨어적 특성까지 쪼개지 못해 예측과 실측값 차이가 벌어졌다.
|
||||
|
||||
// #table(
|
||||
// columns: (auto, 1fr),
|
||||
// table.header([*문제 상황*], [*극복 방법*]),
|
||||
// [학습 데이터의 부재], [분포를 분석해 864,000개를 직접 생성],
|
||||
// [실행해야 알 수 있는 값], [$E times RSR$로 쪼개 미지수를 하나로 축소],
|
||||
// [다중공선성], [$V$별 개별 회귀 후 기여도 기준으로 정밀도 배분],
|
||||
// )
|
||||
|
||||
// 모든 문제 상황에서는 직면한 큰 문제를 작은 단위들로 쪼개고, 작은 문제들의 해결책들을 종합하여 전체 문제를 해결해나갔다.
|
||||
// 실행시간 측정을 위한 통제된 환경은 직접 구축한 홈서버의 가상화 환경(PVE)를 통해 구현했다.
|
||||
|
||||
// 이제 남은 오차는 파이썬이라는 언어 아래의 저차원에 위치해 있다. \
|
||||
// 카이스트에서 언어와 하드웨어 수준의 저차원 요소를 배워 이 모델의 정확도를 끌어올리고, 완성한 모델을 실제 임베디드 시스템에서 알고리즘 선택의 지표로 적용하려 한다.
|
||||
|
||||
// #context ref_evidence()
|
||||
// ]
|
||||
|
||||
#add_evidence(
|
||||
activity_ref: ref(label("Dijkstra Runtime Analysis")),
|
||||
(
|
||||
[소스 코드 및 자료],
|
||||
u_link(
|
||||
"https://gitea.seung6lee.com/seung6lee/dijkstra-runtime-analysis",
|
||||
)[https://gitea.seung6lee.com/seung6lee/dijkstra-runtime-analysis],
|
||||
),
|
||||
(
|
||||
[International Journal of STEAM 기재],
|
||||
[
|
||||
#image("assets/research/dijkstra/short_paper_01.png", height: 70%)
|
||||
#image("assets/research/dijkstra/short_paper_02.png")
|
||||
],
|
||||
),
|
||||
(
|
||||
[정리 노트],
|
||||
{
|
||||
for i in range(1, 15) {
|
||||
let path = "assets/research/dijkstra/notes/note_" + pad_zeros(i, 2) + ".jpg"
|
||||
image(path)
|
||||
}
|
||||
},
|
||||
),
|
||||
(
|
||||
[논문 본문],
|
||||
{
|
||||
for i in range(1, 17) {
|
||||
let path = "assets/research/dijkstra/paper/paper_" + pad_zeros(i, 2) + ".png"
|
||||
image(path)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
#evidence()
|
||||
@@ -7,7 +7,7 @@
|
||||
#let font-display = ("Source Serif 4 Display", "Source Han Serif K")
|
||||
#let font-subhead = ("Source Serif 4 Subhead", "Source Han Serif K")
|
||||
#let font-caption = ("Source Serif 4 Caption", "Source Han Serif K")
|
||||
// #let font-code =
|
||||
#let font-code = ("Source Code Pro", "Pretendard")
|
||||
|
||||
#let color-surface = white
|
||||
#let color-ink = color.oklch(22%, 0.01, 250deg)
|
||||
@@ -19,6 +19,10 @@
|
||||
|
||||
#let bleed = 1.5cm
|
||||
|
||||
#let page-paper = "a4"
|
||||
#let page-height = 297mm
|
||||
#let page-margin = (x: 2.8cm, y: 4.2cm)
|
||||
|
||||
// ==================================================
|
||||
// Metadata
|
||||
// ==================================================
|
||||
@@ -48,12 +52,14 @@
|
||||
),
|
||||
)
|
||||
|
||||
#let evidence-data = state("evidence-data", none)
|
||||
|
||||
|
||||
// ==================================================
|
||||
// Util functions
|
||||
// ==================================================
|
||||
|
||||
#let pad-zeros(num, width) = {
|
||||
#let pad_zeros(num, width) = {
|
||||
let s = str(num)
|
||||
"0" * calc.max(0, width - s.len()) + s
|
||||
}
|
||||
@@ -68,41 +74,73 @@
|
||||
accent-faint: color.oklch(70%, 0.02, hue),
|
||||
)
|
||||
|
||||
#let fill_break() = context {
|
||||
v(1fr)
|
||||
}
|
||||
|
||||
#let heading_text(it) = {
|
||||
if it.has("text") {
|
||||
if type(it) == str {
|
||||
it
|
||||
} else if it == linebreak() {
|
||||
" "
|
||||
} else if it.has("text") {
|
||||
it.text
|
||||
} else if it.has("children") {
|
||||
it.children.map(heading_text).join("")
|
||||
} else if it == linebreak() {
|
||||
" "
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
// Must be called from inside a context block.
|
||||
#let cur_activity() = {
|
||||
let m = meta.get()
|
||||
let level1s = query(heading.where(level: 1, outlined: true))
|
||||
let n = counter(heading).get().at(0)
|
||||
let cur = level1s.at(n - 1)
|
||||
let prefix = str(m.cur_domain) + "." + str(n) + ". "
|
||||
|
||||
(
|
||||
prefix: prefix,
|
||||
name: heading_text(cur.body),
|
||||
location: cur.location(),
|
||||
)
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// Body components
|
||||
// ==================================================
|
||||
|
||||
#let callout(bg_color: none, border_color: none, body) = context {
|
||||
#let kicker(color: color-ink-helper, content) = {
|
||||
set text(
|
||||
font: font-body,
|
||||
weight: 700,
|
||||
size: 10pt,
|
||||
tracking: 1pt,
|
||||
fill: color,
|
||||
)
|
||||
|
||||
block(below: 1.5em)[
|
||||
#content
|
||||
]
|
||||
}
|
||||
|
||||
#let callout(title: "", bg_color: none, border_color: none, body) = context {
|
||||
let cp = color-palette.get()
|
||||
let bg_c = if bg_color == none { cp.callout-surface } else { bg_color }
|
||||
let border_c = if border_color == none { cp.callout-border } else { border_color }
|
||||
|
||||
let x_pad = 12pt
|
||||
let y_pad = 18pt
|
||||
let gap = 24pt
|
||||
|
||||
block(
|
||||
width: 100%,
|
||||
above: gap + y_pad,
|
||||
below: gap + y_pad,
|
||||
outset: (x: x_pad, y: y_pad),
|
||||
radius: 4pt,
|
||||
inset: (x: 12pt, y: 18pt),
|
||||
spacing: 1.6em,
|
||||
// radius: 4pt,
|
||||
fill: bg_c,
|
||||
stroke: (paint: border_c),
|
||||
)[
|
||||
#if title != "" {
|
||||
kicker(color: cp.accent)[#title]
|
||||
}
|
||||
#body
|
||||
]
|
||||
}
|
||||
@@ -118,6 +156,7 @@
|
||||
let body-size = measure(
|
||||
content,
|
||||
width: body-width,
|
||||
height: size.height,
|
||||
)
|
||||
|
||||
grid(
|
||||
@@ -129,7 +168,7 @@
|
||||
stroke: (
|
||||
paint: c,
|
||||
thickness: line-width,
|
||||
cap: "round",
|
||||
// cap: "round",
|
||||
),
|
||||
angle: 90deg,
|
||||
length: body-size.height + 15pt,
|
||||
@@ -140,20 +179,6 @@
|
||||
})
|
||||
}
|
||||
|
||||
#let kicker(color: color-ink-helper, content) = {
|
||||
set text(
|
||||
font: font-body,
|
||||
weight: 700,
|
||||
size: 10pt,
|
||||
tracking: 1pt,
|
||||
fill: color,
|
||||
)
|
||||
|
||||
block(below: 1.5em)[
|
||||
#content
|
||||
]
|
||||
}
|
||||
|
||||
#let problem(content) = context {
|
||||
let c = color-palette.get().problem
|
||||
|
||||
@@ -177,7 +202,9 @@
|
||||
#let hl(content) = context {
|
||||
let cp = color-palette.get()
|
||||
|
||||
// if content == "" {}
|
||||
let body = box(
|
||||
inset: (x: if content == [] { 0pt } else { 10pt }),
|
||||
)[#content]
|
||||
|
||||
set text(
|
||||
font: font-body,
|
||||
@@ -190,70 +217,74 @@
|
||||
grid(
|
||||
columns: (1fr, auto, 1fr),
|
||||
align: horizon,
|
||||
gutter: 10pt,
|
||||
line(length: 100%, stroke: (paint: cp.rule)), content, line(length: 100%, stroke: (paint: cp.rule)),
|
||||
line(length: 100%, stroke: (paint: cp.rule)), body, line(length: 100%, stroke: (paint: cp.rule)),
|
||||
)
|
||||
}
|
||||
|
||||
#let block_equation(content) = {
|
||||
#let block_equation(title: "", description: "", content) = context {
|
||||
let cp = color-palette.get()
|
||||
|
||||
callout()[
|
||||
#set par(
|
||||
spacing: 1em,
|
||||
)
|
||||
#if title != "" {
|
||||
kicker(color: cp.accent)[#title]
|
||||
}
|
||||
|
||||
#content
|
||||
|
||||
#if description != "" {
|
||||
set text(
|
||||
size: 9pt,
|
||||
fill: color-ink-helper,
|
||||
)
|
||||
align(right)[#description]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
#let stack(layers) = context {
|
||||
#let u_link(url, content) = link(url)[
|
||||
#underline(
|
||||
content,
|
||||
)
|
||||
]
|
||||
|
||||
#let steps(..layers) = context {
|
||||
let cp = color-palette.get()
|
||||
let radius = 12pt
|
||||
let gutter = 12pt
|
||||
let gap = 24pt
|
||||
let indent = radius + gutter
|
||||
|
||||
for (idx, (title, body)) in layers.enumerate() {
|
||||
for (idx, (title, body)) in layers.pos().enumerate() {
|
||||
let is_last = idx == layers.len() - 1
|
||||
|
||||
let content = {
|
||||
v(radius - 12pt / 2)
|
||||
text(
|
||||
weight: 700,
|
||||
)[
|
||||
#title
|
||||
#linebreak()
|
||||
]
|
||||
body
|
||||
v(gap)
|
||||
}
|
||||
|
||||
block(
|
||||
above: if idx == 0 { auto } else { 0pt },
|
||||
below: if is_last { auto } else { 0pt },
|
||||
inset: (left: indent),
|
||||
stroke: (left: (paint: cp.accent, thickness: 1pt)),
|
||||
width: 100%,
|
||||
)[
|
||||
#layout(size => {
|
||||
let content_width = size.width - radius * 2 - gutter
|
||||
let content_size = measure(content, width: content_width)
|
||||
|
||||
grid(
|
||||
columns: (radius * 2, 1fr),
|
||||
gutter: gutter,
|
||||
grid(
|
||||
rows: (radius * 2, auto),
|
||||
circle(radius: radius, fill: cp.accent)[
|
||||
#place(
|
||||
left + top,
|
||||
dx: -indent - radius,
|
||||
)[
|
||||
#circle(radius: radius, fill: cp.accent)[
|
||||
#set align(center + horizon)
|
||||
#set text(font: font-serif, fill: color-ink-inverse, size: 12pt, weight: 700)
|
||||
#{ idx + 1 }
|
||||
],
|
||||
line(
|
||||
start: (50%, 0pt),
|
||||
end: (50%, content_size.height - radius * 2),
|
||||
stroke: (
|
||||
paint: cp.accent,
|
||||
thickness: 1pt,
|
||||
cap: "round",
|
||||
),
|
||||
),
|
||||
),
|
||||
content,
|
||||
)
|
||||
})
|
||||
]
|
||||
]
|
||||
|
||||
#v(radius - 12pt / 2)
|
||||
#text(weight: 700)[
|
||||
#title
|
||||
#linebreak()
|
||||
]
|
||||
#body
|
||||
#v(gap)
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -285,6 +316,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
#let ref_evidence(kinds: none) = {
|
||||
let ca = cur_activity()
|
||||
|
||||
set ref(
|
||||
supplement: "증빙 자료.",
|
||||
)
|
||||
|
||||
align(bottom)[
|
||||
#hl[]
|
||||
*활동 증빙 자료:*
|
||||
#underline[#ref(label("evidence) " + ca.name))]
|
||||
#if kinds != none {
|
||||
text(fill: color-ink-helper)[(#kinds)]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
#let add_evidence(activity_ref: none, ..content) = context {
|
||||
let name = str(activity_ref.target)
|
||||
let data = (
|
||||
(
|
||||
name,
|
||||
content.pos().flatten(),
|
||||
),
|
||||
).to-dict()
|
||||
|
||||
evidence-data.update(old => {
|
||||
if old == none {
|
||||
data
|
||||
} else {
|
||||
old + data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#let header_template(dark: false, font_color: none, show_activity: false) = place(
|
||||
top + left,
|
||||
dy: 1cm,
|
||||
@@ -301,16 +367,13 @@
|
||||
)[
|
||||
#context {
|
||||
let m = meta.get()
|
||||
link((page: 1, x: 0pt, y: 0pt))[#m.cur_domain. #m.domain_name]
|
||||
link((page: 2, x: 0pt, y: 0pt))[#m.cur_domain. #m.domain_name]
|
||||
h(1fr)
|
||||
if show_activity {
|
||||
let level1s = query(heading.where(level: 1, outlined: true))
|
||||
let n = counter(heading).get().at(0)
|
||||
let cur = level1s.at(n - 1)
|
||||
let prefix = str(m.cur_domain) + "." + str(n) + ". "
|
||||
let ca = cur_activity()
|
||||
link(
|
||||
cur.location(),
|
||||
prefix + cur.body,
|
||||
ca.location,
|
||||
ca.prefix + ca.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -356,7 +419,7 @@
|
||||
)
|
||||
|
||||
#text(font: font-caption)[
|
||||
#pad-zeros(m.cur_domain, 2) / #pad-zeros(m.total_domain, 2)
|
||||
#pad_zeros(m.cur_domain, 2) / #pad_zeros(m.total_domain, 2)
|
||||
]
|
||||
|
||||
#v(12pt)
|
||||
@@ -381,32 +444,73 @@
|
||||
]
|
||||
}
|
||||
|
||||
// Table of contents templage function
|
||||
#let table_of_contents() = page(
|
||||
footer: footer_template(),
|
||||
)[
|
||||
#context {
|
||||
// Table of contents template function
|
||||
#let table_of_contents() = context {
|
||||
let cp = color-palette.get()
|
||||
let m = meta.get()
|
||||
heading(level: 1, outlined: false, numbering: none)[#m.cur_domain. #m.domain_name]
|
||||
|
||||
page(
|
||||
footer: footer_template(),
|
||||
)[
|
||||
#show outline.entry.where(level: 1): it => link(it.element.location())[
|
||||
#block(above: 25pt)[
|
||||
#grid(
|
||||
columns: (1fr, auto),
|
||||
align: horizon,
|
||||
text(font: font-subhead, size: 15pt, weight: 700)[
|
||||
#it.indented(it.prefix(), heading_text(it.body()))
|
||||
],
|
||||
text(size: 10pt, fill: color-ink-helper)[
|
||||
#it.page()
|
||||
],
|
||||
)
|
||||
|
||||
#let desc = it.element.supplement
|
||||
|
||||
#if desc != [domain] and desc != [evidence] {
|
||||
text(fill: color-ink-helper, size: 10pt)[
|
||||
#it.element.supplement
|
||||
]
|
||||
}
|
||||
|
||||
#outline(title: none, depth: 2)
|
||||
]
|
||||
#v(10pt)
|
||||
|
||||
#line(length: 100%, stroke: (paint: cp.accent-faint, thickness: 0.5pt))
|
||||
]
|
||||
]
|
||||
|
||||
#heading(level: 1, outlined: false, numbering: none)[#m.cur_domain. #m.domain_name]
|
||||
#label("table_of_contents")
|
||||
|
||||
#outline(title: none, depth: 1)
|
||||
]
|
||||
}
|
||||
|
||||
// Domain frame template function
|
||||
#let domain_frame(title: "", bg_img_path: none, font_color: color-ink, chrome_color: color-ink-helper, content) = page(
|
||||
background: place(
|
||||
center + horizon,
|
||||
if bg_img_path != none {
|
||||
image(bg_img_path, width: 100.5%, height: 100.5%, fit: "cover")
|
||||
},
|
||||
#let domain_frame(
|
||||
title: "",
|
||||
bg_img_path: none,
|
||||
cover_color: white.transparentize(50%),
|
||||
font_color: color-ink,
|
||||
chrome_color: color-ink-helper,
|
||||
content,
|
||||
) = page(
|
||||
background: if bg_img_path != none {
|
||||
stack(
|
||||
image(bg_img_path, width: 100.5%, height: 100.5%, fit: "cover"),
|
||||
move(
|
||||
dy: -50%,
|
||||
rect(width: 100.5%, height: 100.5%, fill: cover_color),
|
||||
),
|
||||
)
|
||||
} else { none },
|
||||
header: header_template(font_color: chrome_color),
|
||||
footer: footer_template(font_color: chrome_color),
|
||||
)[
|
||||
#set text(fill: font_color)
|
||||
#show strong: set text(fill: font_color)
|
||||
|
||||
#heading(level: 1)[
|
||||
#heading(level: 1, supplement: [domain])[
|
||||
#title
|
||||
]
|
||||
|
||||
@@ -414,61 +518,152 @@
|
||||
]
|
||||
|
||||
// Activity cover template function
|
||||
#let activity_cover(name: "", hook: "", content) = page(
|
||||
#let activity_cover(name: "", hook: "", content) = context {
|
||||
let cp = color-palette.get()
|
||||
|
||||
page(
|
||||
header: header_template(),
|
||||
footer: footer_template(),
|
||||
)[
|
||||
#heading(level: 1)[
|
||||
fill: cp.cover-surface,
|
||||
)[
|
||||
#show heading.where(level: 1): set block(above: 0em, below: 25pt)
|
||||
#heading(level: 1, supplement: hook)[
|
||||
#name
|
||||
]
|
||||
#label(heading_text(name))
|
||||
|
||||
#block_quote()[
|
||||
#hook
|
||||
]
|
||||
|
||||
#content
|
||||
]
|
||||
#v(25pt)
|
||||
|
||||
// Activity intro template function
|
||||
#let activity_intro(title: "", content) = page(
|
||||
#content
|
||||
]
|
||||
}
|
||||
|
||||
#let activity_table() = context {
|
||||
let cp = color-palette.get()
|
||||
let ca = cur_activity()
|
||||
let level1s = query(heading.where(level: 1, outlined: true))
|
||||
let n = counter(heading).get().at(0)
|
||||
let sel = heading.where(level: 2, outlined: true).after(ca.location)
|
||||
if n < level1s.len() {
|
||||
sel = sel.before(level1s.at(n).location(), inclusive: false)
|
||||
}
|
||||
|
||||
page(
|
||||
header: header_template(show_activity: true),
|
||||
footer: footer_template(),
|
||||
)[
|
||||
#show outline: it => [
|
||||
#line(length: 100%, stroke: (paint: cp.accent-faint, thickness: 0.5pt))
|
||||
|
||||
#it
|
||||
]
|
||||
|
||||
#show outline.entry.where(level: 2): it => link(it.element.location())[
|
||||
#set par(
|
||||
spacing: 1em,
|
||||
)
|
||||
|
||||
#block(above: 15pt)[
|
||||
#grid(
|
||||
columns: (1fr, auto),
|
||||
align: horizon,
|
||||
text(size: 12pt, weight: 700)[
|
||||
#it.body()
|
||||
],
|
||||
text(size: 8pt, fill: color-ink-helper)[
|
||||
#it.page()
|
||||
],
|
||||
)
|
||||
|
||||
#let desc = it.element.supplement
|
||||
|
||||
#par(
|
||||
spacing: 0pt,
|
||||
leading: 0.8em,
|
||||
)[
|
||||
#text(fill: color-ink-helper, size: 10pt)[
|
||||
#it.element.supplement
|
||||
]
|
||||
]
|
||||
|
||||
#line(length: 100%, stroke: (paint: cp.accent-faint, thickness: 0.5pt))
|
||||
]
|
||||
]
|
||||
|
||||
#heading(level: 2, outlined: false)[
|
||||
시행착오 목차
|
||||
]
|
||||
|
||||
#outline(title: none, target: sel)
|
||||
]
|
||||
}
|
||||
|
||||
// Activity process
|
||||
#let activity_process(title: "", hook: [], content) = page(
|
||||
header: header_template(show_activity: true),
|
||||
footer: footer_template(),
|
||||
)[
|
||||
#kicker("프로젝트 설명")
|
||||
|
||||
#heading(level: 2)[
|
||||
#title
|
||||
]
|
||||
|
||||
#content
|
||||
]
|
||||
|
||||
// Activity process
|
||||
#let activity_process(title: "", content) = page(
|
||||
header: header_template(show_activity: true),
|
||||
)[
|
||||
#kicker("시행착오")
|
||||
|
||||
#heading(level: 2)[
|
||||
#heading(level: 2, supplement: hook)[
|
||||
#title
|
||||
]
|
||||
|
||||
#content
|
||||
]
|
||||
|
||||
// Activity result
|
||||
#let activity_result(title: "", content) = page(
|
||||
header: header_template(show_activity: true),
|
||||
)[
|
||||
#kicker("결론")
|
||||
#let evidence() = context {
|
||||
let cp = color-palette.get()
|
||||
let ed = evidence-data.get()
|
||||
|
||||
#heading(level: 2)[
|
||||
#title
|
||||
page(
|
||||
header: header_template(),
|
||||
footer: footer_template(),
|
||||
)[
|
||||
#set heading(
|
||||
numbering: (..nums) => {
|
||||
numbering("A", nums.at(1))
|
||||
},
|
||||
supplement: [evidence],
|
||||
)
|
||||
#show heading.where(level: 2): it => {
|
||||
let num = counter(heading).display(
|
||||
(..nums) => numbering("A", nums.at(1)),
|
||||
)
|
||||
|
||||
block[
|
||||
#num. #it.body
|
||||
]
|
||||
}
|
||||
|
||||
#show table.cell.where(x: 0): set text(
|
||||
weight: 700,
|
||||
fill: cp.accent,
|
||||
)
|
||||
|
||||
#heading(level: 1, numbering: none)[
|
||||
증빙 자료
|
||||
]
|
||||
|
||||
#content
|
||||
]
|
||||
#let cnt = ed.len()
|
||||
#for (name, data) in ed [
|
||||
#heading(level: 2, supplement: "evidence")[#name]
|
||||
#label("evidence) " + name)
|
||||
|
||||
#table(
|
||||
columns: (10em, 1fr),
|
||||
..data,
|
||||
)
|
||||
|
||||
#{ cnt -= 1 }
|
||||
#if cnt > 0 { pagebreak() }
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
// ==================================================
|
||||
// Style Settings
|
||||
@@ -509,8 +704,8 @@
|
||||
)
|
||||
|
||||
set page(
|
||||
paper: "a4",
|
||||
margin: (x: 2.8cm, y: 4.2cm),
|
||||
paper: page-paper,
|
||||
margin: page-margin,
|
||||
fill: color-surface,
|
||||
)
|
||||
|
||||
@@ -524,25 +719,58 @@
|
||||
|
||||
set par(
|
||||
leading: 1.2em,
|
||||
spacing: 2.4em,
|
||||
spacing: 1.8em,
|
||||
)
|
||||
|
||||
show raw: set text(font: font-code)
|
||||
|
||||
show strong: set text(fill: cp.accent)
|
||||
|
||||
// underline config
|
||||
set underline(
|
||||
offset: 2pt,
|
||||
stroke: (paint: cp.accent, thickness: 1pt),
|
||||
)
|
||||
|
||||
// list config
|
||||
show list: set par(leading: 0.8em)
|
||||
set list(
|
||||
marker: ([--], [•]),
|
||||
indent: 0.6em,
|
||||
body-indent: 0.8em,
|
||||
spacing: 1.2em,
|
||||
spacing: 0.8em,
|
||||
)
|
||||
|
||||
show enum: set par(leading: 0.8em)
|
||||
set enum(
|
||||
indent: 0.6em,
|
||||
body-indent: 0.8em,
|
||||
spacing: 1.2em,
|
||||
spacing: 0.8em,
|
||||
)
|
||||
|
||||
// image config
|
||||
show image: set align(center)
|
||||
show image: set par(
|
||||
spacing: 1.6em,
|
||||
)
|
||||
|
||||
// figure config
|
||||
show figure: set text(
|
||||
fill: color-ink-helper,
|
||||
size: 10pt,
|
||||
)
|
||||
|
||||
show figure.caption: it => {
|
||||
"[ " + it.body + " ]"
|
||||
}
|
||||
|
||||
// table config
|
||||
show table: set text(size: 10pt)
|
||||
|
||||
set table(
|
||||
inset: 0.8em,
|
||||
align: left + top,
|
||||
stroke: (paint: cp.rule),
|
||||
)
|
||||
|
||||
// heading config
|
||||
@@ -567,48 +795,6 @@
|
||||
fill: color-ink,
|
||||
)
|
||||
|
||||
// outline config
|
||||
set outline(
|
||||
indent: 1em,
|
||||
)
|
||||
|
||||
show outline: set par(leading: 1em)
|
||||
|
||||
show outline.entry: it => link(
|
||||
it.element.location(),
|
||||
)[
|
||||
#grid(
|
||||
columns: (1fr, auto),
|
||||
it.indented(it.prefix(), heading_text(it.body())), it.page(),
|
||||
)
|
||||
]
|
||||
|
||||
show outline.entry.where(level: 1): it => {
|
||||
set text(
|
||||
font: font-subhead,
|
||||
size: 15pt,
|
||||
weight: 700,
|
||||
)
|
||||
|
||||
block(above: 50pt)[
|
||||
#it
|
||||
#line(length: 100%, stroke: (paint: color-ink-secondary, thickness: 1pt))
|
||||
]
|
||||
}
|
||||
|
||||
show outline.entry.where(level: 2): it => {
|
||||
set text(
|
||||
font: font-body,
|
||||
size: 12pt,
|
||||
weight: 700,
|
||||
fill: color-ink-secondary,
|
||||
)
|
||||
|
||||
block(above: 20pt)[
|
||||
#it
|
||||
]
|
||||
}
|
||||
|
||||
// render content
|
||||
doc
|
||||
}
|
||||
|
||||