initial commit
This commit is contained in:
+166
@@ -0,0 +1,166 @@
|
||||
python -V
|
||||
Python 3.12.12
|
||||
|
||||
|
||||
pip -V
|
||||
pip 26.0.1 from /root/dijkstra/src/.venv/lib/python3.12/site-packages/pip (python 3.12)
|
||||
|
||||
|
||||
python -c "import platform; print(platform.platform())"
|
||||
Linux-6.8.12-17-pve-x86_64-with-glibc2.41
|
||||
|
||||
|
||||
lscpu | head
|
||||
Architecture: x86_64
|
||||
CPU op-mode(s): 32-bit, 64-bit
|
||||
Address sizes: 48 bits physical, 48 bits virtual
|
||||
Byte Order: Little Endian
|
||||
CPU(s): 16
|
||||
On-line CPU(s) list: 1-3,8
|
||||
Off-line CPU(s) list: 0,4-7,9-15
|
||||
Vendor ID: AuthenticAMD
|
||||
Model name: AMD Ryzen 7 7700 8-Core Processor
|
||||
CPU family: 25
|
||||
|
||||
|
||||
|
||||
## I. Introduction
|
||||
• 이론적 복잡도 vs 실제 성능 문제 제기
|
||||
• 기존 연구 간략 언급
|
||||
• RQ 제시
|
||||
• 핵심 주장 요약
|
||||
|
||||
## II. Theoretical Background
|
||||
이 부분이 추가되어야 한다.
|
||||
|
||||
### 2.1 Dijkstra 시간복잡도 구조
|
||||
Binary:
|
||||
T_B = c_1 E \log V
|
||||
|
||||
Fibonacci:
|
||||
T_F = c_2 E + c_3 V \log V
|
||||
|
||||
### 2.2 decrease-key의 역할
|
||||
• relax_success = decrease_key 발생 빈도
|
||||
• 입력 분포와 그래프 밀도가 이를 결정
|
||||
|
||||
→ 여기서 “역전 조건의 이론적 형태”를 먼저 제시
|
||||
|
||||
예:
|
||||
|
||||
c_{dec}^{F} \cdot D < c_{dec}^{B} \cdot D \log V
|
||||
|
||||
이게 논문의 수학적 중심이 된다.
|
||||
|
||||
⸻
|
||||
|
||||
III. Methodology
|
||||
|
||||
이건 반드시 독립 섹션으로.
|
||||
|
||||
3.1 실제 데이터 분석 방법
|
||||
• USA-road 기반 분포 추정
|
||||
• mean, std 계산
|
||||
• lognormal 변환 공식
|
||||
|
||||
3.2 가상 그래프 생성 방법
|
||||
• outdegree 기반 생성
|
||||
• density 정의
|
||||
• std 범위 정당화
|
||||
|
||||
3.3 측정 지표
|
||||
• relax_attempts
|
||||
• relax_success
|
||||
• relax_ratio
|
||||
• speedup 정의
|
||||
|
||||
3.4 연산 단위 비용 모델
|
||||
|
||||
선형 모델:
|
||||
|
||||
T = \beta_0 + \beta_1 extract + \beta_2 relax + \beta_3 decrease
|
||||
|
||||
이걸 명확히 수식으로 써야 함.
|
||||
|
||||
⸻
|
||||
|
||||
IV. Empirical Analysis
|
||||
|
||||
이제 네가 말한 흐름을 그대로 쓰면 된다.
|
||||
|
||||
⸻
|
||||
|
||||
4.1 실제 데이터 분포 분석
|
||||
• mean ≈ 3000
|
||||
• std ≈ 4000
|
||||
• lognormal 적합성
|
||||
|
||||
→ 실험 파라미터의 정당화
|
||||
|
||||
⸻
|
||||
|
||||
4.2 변수 간 관계 분석 (가상 데이터)
|
||||
|
||||
4.2.1 Density ↔ Relax_attempts
|
||||
|
||||
→ 거의 선형 관계
|
||||
|
||||
4.2.2 Sigma ↔ Relax_success_ratio
|
||||
|
||||
→ 양의 상관관계
|
||||
|
||||
4.2.3 Decrease_key ↔ Speedup
|
||||
|
||||
→ 거의 무관
|
||||
|
||||
⸻
|
||||
|
||||
4.3 런타임 다중 선형 회귀 분석
|
||||
• 단위 연산 비용 추정
|
||||
• Binary vs Fibonacci 비교
|
||||
• R² 제시
|
||||
|
||||
이 부분이 핵심 증거.
|
||||
|
||||
⸻
|
||||
|
||||
V. Deriving the Crossover Condition
|
||||
|
||||
여기가 논문의 “기여”다.
|
||||
|
||||
회귀 계수를 이용해:
|
||||
|
||||
Binary:
|
||||
T_B = a_1 extract\log V + a_2 decrease\log V + ...
|
||||
|
||||
Fibonacci:
|
||||
T_F = b_1 extract\log V + b_2 decrease + ...
|
||||
|
||||
역전 조건:
|
||||
|
||||
T_F < T_B
|
||||
|
||||
정리하면:
|
||||
|
||||
(b_2 - a_2 \log V) D + (b_1 - a_1)\log V + ... < 0
|
||||
|
||||
여기서:
|
||||
• 실제 계수 대입
|
||||
• 부등식이 성립하는 V, D 범위 계산
|
||||
|
||||
이게 논문의 가장 강력한 부분.
|
||||
|
||||
⸻
|
||||
|
||||
VI. Feasibility Analysis
|
||||
• Python 환경에서는 성립 불가
|
||||
• 상수항이 지배
|
||||
• C++ 가능성 언급
|
||||
|
||||
⸻
|
||||
|
||||
VII. Conclusion
|
||||
• RQ에 대한 명확한 답
|
||||
• 이론과 실제의 간극 강조
|
||||
• 실무적 함의
|
||||
• 한계 및 향후 연구
|
||||
@@ -0,0 +1,46 @@
|
||||
class DijkstraStats:
|
||||
def __init__(self):
|
||||
self.extract_min_calls = 0
|
||||
self.relax_attempts = 0
|
||||
self.relax_success = 0
|
||||
# self.decrease_key_calls = 0
|
||||
# self.add_calls = 0
|
||||
|
||||
def heap_dijkstra(nodes, adj, heap, start, end):
|
||||
INF = float('inf')
|
||||
dist = [INF] * nodes
|
||||
visited = [False] * nodes
|
||||
stats = DijkstraStats()
|
||||
|
||||
dist[start] = 0
|
||||
|
||||
for v in range(nodes):
|
||||
heap.add(v, dist[v])
|
||||
# stats.add_calls += 1
|
||||
|
||||
while True:
|
||||
res = heap.extract_min()
|
||||
stats.extract_min_calls += 1
|
||||
if res is None:
|
||||
break
|
||||
cur, cur_dist = res
|
||||
|
||||
if cur_dist == INF:
|
||||
break
|
||||
|
||||
if cur == end:
|
||||
break
|
||||
|
||||
for nxt, d in adj[cur]:
|
||||
stats.relax_attempts += 1
|
||||
if visited[nxt]:
|
||||
continue
|
||||
new = cur_dist + d
|
||||
if new < dist[nxt]:
|
||||
stats.relax_success += 1
|
||||
dist[nxt] = new
|
||||
heap.decrease_key(nxt, new)
|
||||
|
||||
visited[cur] = True
|
||||
|
||||
return dist[end], stats
|
||||
@@ -0,0 +1,32 @@
|
||||
def pure_dijkstra(nodes, adj, start, end):
|
||||
dist = [float('inf')] * nodes
|
||||
dist[start] = 0
|
||||
visited = [False] * nodes
|
||||
|
||||
cur = start
|
||||
|
||||
while True:
|
||||
if cur == -1:
|
||||
break
|
||||
|
||||
if cur == end:
|
||||
break
|
||||
|
||||
for next, d in adj[cur]:
|
||||
if not visited[next]:
|
||||
new = dist[cur] + d
|
||||
if new < dist[next]:
|
||||
dist[next] = new
|
||||
|
||||
visited[cur] = True
|
||||
|
||||
# Finding min node
|
||||
min_node = -1
|
||||
min_val = float('inf')
|
||||
for n in range(nodes):
|
||||
if not visited[n] and dist[n] < min_val:
|
||||
min_node = n
|
||||
min_val = dist[n]
|
||||
cur = min_node
|
||||
|
||||
return dist[end]
|
||||
@@ -0,0 +1,75 @@
|
||||
class BinHeap:
|
||||
def __init__(self, nodes):
|
||||
self.heap = []
|
||||
self.pos = [-1] * nodes
|
||||
|
||||
def sift_up(self, idx):
|
||||
heap = self.heap
|
||||
pos = self.pos
|
||||
|
||||
backup = heap[idx]
|
||||
cur = idx
|
||||
|
||||
while cur > 0:
|
||||
par = (cur - 1) // 2
|
||||
if backup[1] >= heap[par][1]:
|
||||
break
|
||||
heap[cur] = heap[par]
|
||||
pos[heap[cur][0]] = cur
|
||||
cur = par
|
||||
heap[cur] = backup
|
||||
pos[backup[0]] = cur
|
||||
|
||||
def sift_down(self, idx):
|
||||
heap = self.heap
|
||||
pos = self.pos
|
||||
|
||||
n = len(heap)
|
||||
|
||||
backup = heap[idx]
|
||||
cur = idx
|
||||
|
||||
while True:
|
||||
left = cur * 2 + 1
|
||||
if left >= n:
|
||||
break
|
||||
right = left + 1
|
||||
tar = right if right < n and heap[right][1] < heap[left][1] else left
|
||||
if backup[1] <= heap[tar][1]:
|
||||
break
|
||||
heap[cur] = heap[tar]
|
||||
pos[heap[cur][0]] = cur
|
||||
cur = tar
|
||||
heap[cur] = backup
|
||||
pos[backup[0]] = cur
|
||||
|
||||
def add(self, key, dist):
|
||||
idx = len(self.heap)
|
||||
self.heap.append([key, dist])
|
||||
self.pos[key] = idx
|
||||
self.sift_up(idx)
|
||||
|
||||
def extract_min(self):
|
||||
heap = self.heap
|
||||
pos = self.pos
|
||||
if not heap:
|
||||
return None
|
||||
min_node, min_dist = heap[0]
|
||||
pos[min_node] = -1
|
||||
|
||||
last = heap.pop()
|
||||
if heap:
|
||||
heap[0] = last
|
||||
pos[last[0]] = 0
|
||||
self.sift_down(0)
|
||||
return (min_node, min_dist)
|
||||
|
||||
def decrease_key(self, key, new_dist):
|
||||
idx = self.pos[key]
|
||||
if idx == -1:
|
||||
return None
|
||||
node = self.heap[idx]
|
||||
if new_dist >= node[1]:
|
||||
return None
|
||||
node[1] = new_dist
|
||||
self.sift_up(idx)
|
||||
@@ -0,0 +1,177 @@
|
||||
import math
|
||||
|
||||
class Node:
|
||||
__slots__ = ('key', 'dist', 'left', 'right', 'parent', 'child', 'degree', 'lost')
|
||||
|
||||
def __init__(self, key, dist):
|
||||
self.key = key
|
||||
self.dist = dist
|
||||
|
||||
self.left = self
|
||||
self.right = self
|
||||
|
||||
self.parent = None
|
||||
self.child = None
|
||||
|
||||
self.degree = 0
|
||||
self.lost = False
|
||||
|
||||
def connect_right(self, new_right):
|
||||
old_right = self.right
|
||||
new_left = new_right.left
|
||||
|
||||
new_right.left = self
|
||||
new_left.right = old_right
|
||||
|
||||
old_right.left = new_left
|
||||
self.right = new_right
|
||||
|
||||
def remove(self):
|
||||
self.left.right = self.right
|
||||
self.right.left = self.left
|
||||
self.left = self
|
||||
self.right = self
|
||||
|
||||
def connect_child(self, child):
|
||||
child.parent = self
|
||||
child.lost = False
|
||||
|
||||
if self.child is None:
|
||||
self.child = child
|
||||
else:
|
||||
self.child.connect_right(child)
|
||||
|
||||
self.degree += 1
|
||||
|
||||
class FiboHeap:
|
||||
def __init__(self, nodes):
|
||||
self.min = None
|
||||
self.n = 0
|
||||
self.pos = [None] * nodes
|
||||
|
||||
def cut(self, cur):
|
||||
p = cur.parent
|
||||
if p.child is cur:
|
||||
if cur.right is cur:
|
||||
p.child = None
|
||||
else:
|
||||
p.child = cur.right
|
||||
|
||||
cur.remove()
|
||||
p.degree -= 1
|
||||
|
||||
cur.parent = None
|
||||
cur.lost = False
|
||||
|
||||
self.min.connect_right(cur)
|
||||
if cur.dist < self.min.dist:
|
||||
self.min = cur
|
||||
|
||||
def cascading_cut(self, cur):
|
||||
while True:
|
||||
p = cur.parent
|
||||
if p is None:
|
||||
return
|
||||
if not cur.lost:
|
||||
cur.lost = True
|
||||
return
|
||||
self.cut(cur)
|
||||
cur = p
|
||||
|
||||
def add(self, key, dist):
|
||||
cur = Node(key, dist)
|
||||
|
||||
if self.min is None:
|
||||
self.min = cur
|
||||
else:
|
||||
self.min.connect_right(cur)
|
||||
if cur.dist < self.min.dist:
|
||||
self.min = cur
|
||||
|
||||
self.n += 1
|
||||
self.pos[key] = cur
|
||||
|
||||
def extract_min(self):
|
||||
# 1. Find min node
|
||||
min_node = self.min
|
||||
self.n -= 1
|
||||
|
||||
# 2. Make min node's child into individual tree
|
||||
c = min_node.child
|
||||
if c is not None:
|
||||
start = c
|
||||
cur = c
|
||||
while True:
|
||||
cur.parent = None
|
||||
cur.lost = False
|
||||
cur = cur.right
|
||||
if cur == start:
|
||||
break
|
||||
|
||||
min_node.connect_right(start)
|
||||
|
||||
if min_node.right is min_node:
|
||||
self.min = None
|
||||
return (min_node.key, min_node.dist)
|
||||
|
||||
nxt = min_node.right
|
||||
min_node.remove()
|
||||
self.min = nxt
|
||||
|
||||
min_node.child = None
|
||||
min_node.degree = 0
|
||||
self.pos[min_node.key] = None
|
||||
|
||||
# 3. Make into binomial tree
|
||||
roots = []
|
||||
start = self.min
|
||||
cur = start
|
||||
while True:
|
||||
roots.append(cur)
|
||||
cur = cur.right
|
||||
if cur == start:
|
||||
break
|
||||
|
||||
max_deg = int(math.log2(self.n)) + 2 if self.n > 0 else 1 # 여기서 +2 하는 이유는 사실은 log2가 아니라 fibonacci 수열 기반이기 때문에 +1 더 해주는 거임.
|
||||
A = [None] * (max_deg + 1)
|
||||
|
||||
for cur in roots:
|
||||
x = cur
|
||||
d = x.degree
|
||||
|
||||
while A[d] is not None:
|
||||
y = A[d]
|
||||
if y.dist < x.dist:
|
||||
x, y = y, x
|
||||
y.remove()
|
||||
x.connect_child(y)
|
||||
A[d] = None
|
||||
d = x.degree
|
||||
A[d] = x
|
||||
|
||||
# 4. Find new min
|
||||
self.min = None
|
||||
for cur in A:
|
||||
if cur is None:
|
||||
continue
|
||||
if self.min is None:
|
||||
self.min = cur
|
||||
else:
|
||||
if cur.dist < self.min.dist:
|
||||
self.min = cur
|
||||
|
||||
return (min_node.key, min_node.dist)
|
||||
|
||||
def decrease_key(self, key, new_dist):
|
||||
cur = self.pos[key]
|
||||
if cur is None or new_dist >= cur.dist:
|
||||
return
|
||||
cur.dist = new_dist
|
||||
p = cur.parent
|
||||
|
||||
if p is not None and cur.dist < p.dist:
|
||||
self.cut(cur)
|
||||
self.cascading_cut(p)
|
||||
|
||||
if cur.dist < self.min.dist:
|
||||
self.min = cur
|
||||
@@ -0,0 +1,95 @@
|
||||
import os
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import random
|
||||
|
||||
|
||||
def read_dimacs(file):
|
||||
idx = 0
|
||||
with open(f"{folder}/{file}", "r") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith("c"):
|
||||
continue
|
||||
elif line.startswith("p"):
|
||||
_, _, n, e = line.split()
|
||||
edges = int(e)
|
||||
data = [0] * edges
|
||||
elif line.startswith("a"):
|
||||
_, u, v, w = line.split()
|
||||
data[idx] = int(w)
|
||||
idx += 1
|
||||
return data
|
||||
|
||||
|
||||
def visualize_weights(file):
|
||||
data = read_dimacs(file)
|
||||
sample = np.array(data)
|
||||
|
||||
save_folder = f"{res_folder}/{file}"
|
||||
os.makedirs(save_folder, exist_ok=True)
|
||||
|
||||
plt.figure(figsize=(8, 5))
|
||||
plt.hist(sample, bins=100)
|
||||
plt.title("Edge Weight Distribution (Original Scale)")
|
||||
plt.xlabel("Weight")
|
||||
plt.ylabel("Frequency")
|
||||
plt.tight_layout()
|
||||
plt.savefig(f"{save_folder}/hist_original.png")
|
||||
plt.close()
|
||||
|
||||
log_sample = np.log(sample[sample > 0])
|
||||
|
||||
plt.figure(figsize=(8, 5))
|
||||
plt.hist(log_sample, bins=100)
|
||||
plt.title("Log(Weight) Distribution")
|
||||
plt.xlabel("log(Weight)")
|
||||
plt.ylabel("Frequency")
|
||||
plt.tight_layout()
|
||||
plt.savefig(f"{save_folder}/hist_log.png")
|
||||
plt.close()
|
||||
|
||||
plt.figure(figsize=(8, 5))
|
||||
plt.hist(log_sample, bins=100, density=True)
|
||||
plt.title("Log(Weight) Density")
|
||||
plt.xlabel("log(Weight)")
|
||||
plt.ylabel("Density")
|
||||
plt.tight_layout()
|
||||
plt.savefig(f"{save_folder}/hist_log_density.png")
|
||||
plt.close()
|
||||
|
||||
return {
|
||||
"file": file,
|
||||
"edges": len(sample),
|
||||
"mean": np.mean(sample),
|
||||
"std": np.std(sample),
|
||||
"min": np.min(sample),
|
||||
"max": np.max(sample)
|
||||
}
|
||||
|
||||
folder = "experiments/real_data/data/dimacs_data"
|
||||
files = [
|
||||
"USA-road-d.BAY.gr",
|
||||
"USA-road-d.CAL.gr",
|
||||
"USA-road-d.COL.gr",
|
||||
"USA-road-d.CTR.gr",
|
||||
"USA-road-d.E.gr",
|
||||
"USA-road-d.FLA.gr",
|
||||
"USA-road-d.LKS.gr",
|
||||
"USA-road-d.NE.gr",
|
||||
"USA-road-d.NW.gr",
|
||||
"USA-road-d.NY.gr",
|
||||
"USA-road-d.USA.gr",
|
||||
"USA-road-d.W.gr"
|
||||
]
|
||||
|
||||
res_folder = "results/real_data/derived/dimacs_graph_distribution"
|
||||
os.makedirs(res_folder, exist_ok=True)
|
||||
|
||||
rows = []
|
||||
for file in files:
|
||||
row = visualize_weights(file)
|
||||
rows.append(row)
|
||||
df = pd.DataFrame(rows)
|
||||
df.to_csv(f"{res_folder}/distribution.csv", index=False)
|
||||
@@ -0,0 +1,21 @@
|
||||
def dimacs_convert_graph(filename):
|
||||
with open(filename, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith('c'):
|
||||
continue
|
||||
|
||||
elif line.startswith('p'):
|
||||
_, _, n, e = line.split()
|
||||
nodes = int(n)
|
||||
edges = int(e)
|
||||
adj = [[] for _ in range(nodes + 1)]
|
||||
|
||||
elif line.startswith('a'):
|
||||
_, u, v, w = line.split()
|
||||
u, v, w = int(u) - 1, int(v) - 1, int(w)
|
||||
|
||||
adj[u].append((v, w))
|
||||
|
||||
return nodes, adj
|
||||
@@ -0,0 +1,102 @@
|
||||
import os
|
||||
import time
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
from experiments.real_data.graph_converters.dimacs_graph_converter import dimacs_convert_graph
|
||||
from core.dijkstra.heap_dijkstra import heap_dijkstra
|
||||
from core.heaps.binary_heap import BinHeap
|
||||
from core.heaps.fibonacci_heap import FiboHeap
|
||||
|
||||
def run_test(
|
||||
folder,
|
||||
files,
|
||||
trials,
|
||||
base_seed
|
||||
):
|
||||
rows = []
|
||||
timer = time.perf_counter
|
||||
rng = np.random.default_rng(base_seed)
|
||||
INF = float('inf')
|
||||
|
||||
for file in files:
|
||||
nodes, adj = dimacs_convert_graph(f'{folder}/{file}')
|
||||
density = len(adj) / (nodes * (nodes - 1))
|
||||
|
||||
for trial in range(1, trials + 1):
|
||||
start, end = rng.choice(nodes, size=2, replace=False) + 1
|
||||
|
||||
bin_heap = BinHeap(nodes)
|
||||
st = timer()
|
||||
dist, stats = heap_dijkstra(nodes, adj, bin_heap, start, end)
|
||||
et = timer()
|
||||
rows.append(
|
||||
{
|
||||
"file": file,
|
||||
"nodes": nodes,
|
||||
"density": density,
|
||||
"trial": trial,
|
||||
"start": start,
|
||||
"end": end,
|
||||
"time": et - st,
|
||||
"algorithm": "binary",
|
||||
"reached": dist != INF,
|
||||
"extract_min_calls": stats.extract_min_calls,
|
||||
"relax_attempts": stats.relax_attempts,
|
||||
"relax_success": stats.relax_success,
|
||||
}
|
||||
)
|
||||
|
||||
fibo_heap = FiboHeap(nodes)
|
||||
st = timer()
|
||||
dist, stats = heap_dijkstra(nodes, adj, fibo_heap, start, end)
|
||||
et = timer()
|
||||
rows.append(
|
||||
{
|
||||
"file": file,
|
||||
"nodes": nodes,
|
||||
"density": density,
|
||||
"trial": trial,
|
||||
"start": start,
|
||||
"end": end,
|
||||
"time": et - st,
|
||||
"algorithm": "fibonacci",
|
||||
"reached": dist != INF,
|
||||
"extract_min_calls": stats.extract_min_calls,
|
||||
"relax_attempts": stats.relax_attempts,
|
||||
"relax_success": stats.relax_success,
|
||||
}
|
||||
)
|
||||
|
||||
df = pd.DataFrame(rows)
|
||||
return df
|
||||
|
||||
# Settings
|
||||
folder = "experiments/real_data/data/dimacs_data"
|
||||
files = [
|
||||
"USA-road-d.BAY.gr",
|
||||
"USA-road-d.CAL.gr",
|
||||
"USA-road-d.COL.gr",
|
||||
"USA-road-d.CTR.gr",
|
||||
"USA-road-d.E.gr",
|
||||
"USA-road-d.FLA.gr",
|
||||
"USA-road-d.LKS.gr",
|
||||
"USA-road-d.NE.gr",
|
||||
"USA-road-d.NW.gr",
|
||||
"USA-road-d.NY.gr",
|
||||
"USA-road-d.USA.gr",
|
||||
"USA-road-d.W.gr"
|
||||
]
|
||||
trials = 200
|
||||
base_seed = 42
|
||||
|
||||
df = run_test(
|
||||
folder=folder,
|
||||
files=files,
|
||||
trials=trials,
|
||||
base_seed=base_seed
|
||||
)
|
||||
|
||||
save_folder = "results/real_data/raw"
|
||||
os.makedirs(save_folder, exist_ok=True)
|
||||
df.to_csv(f"{save_folder}/dimacs_t{trials}_s{base_seed}.csv", index=False)
|
||||
@@ -0,0 +1,50 @@
|
||||
import os
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
csv_file = "results/synthetic_data/raw/20260302_183202.csv"
|
||||
save_folder = f"results/synthetic_data/derived/{os.path.splitext(os.path.basename(csv_file))[0]}"
|
||||
os.makedirs(save_folder, exist_ok=True)
|
||||
|
||||
df = pd.read_csv(csv_file)
|
||||
df = df[df["algorithm"] == "binary"].copy()
|
||||
df["relax_success_ratio"] = df["relax_success"] / df["relax_attempts"]
|
||||
|
||||
# N vs extract_min_calls
|
||||
grouped = df.groupby("nodes")["extract_min_calls"].mean()
|
||||
|
||||
plt.figure(figsize=(6,4))
|
||||
plt.plot(grouped.index, grouped.values, marker='o')
|
||||
plt.xlabel("N (nodes)")
|
||||
plt.ylabel("Average extract_min_calls")
|
||||
plt.title("Nodes vs Extract-Min Calls")
|
||||
plt.grid(True)
|
||||
plt.tight_layout()
|
||||
plt.savefig(f"{save_folder}/N_vs_extract_min.png", dpi=300)
|
||||
plt.close()
|
||||
|
||||
# Density vs relax_attempts
|
||||
grouped = df.groupby("density")["relax_attempts"].mean()
|
||||
|
||||
plt.figure(figsize=(6,4))
|
||||
plt.plot(grouped.index, grouped.values, marker='o')
|
||||
plt.xlabel("Density")
|
||||
plt.ylabel("Average relax_attempts")
|
||||
plt.title("Density vs Relax Attempts")
|
||||
plt.grid(True)
|
||||
plt.tight_layout()
|
||||
plt.savefig(f"{save_folder}/density_vs_relax_attempts.png", dpi=300)
|
||||
plt.close()
|
||||
|
||||
# Sigma vs relax_success_ratio
|
||||
grouped = df.groupby("sigma")["relax_success_ratio"].mean()
|
||||
|
||||
plt.figure(figsize=(6,4))
|
||||
plt.plot(grouped.index, grouped.values, marker='o')
|
||||
plt.xlabel("Sigma (lognormal)")
|
||||
plt.ylabel("Relax Success Ratio")
|
||||
plt.title("Sigma vs Relax Success Ratio")
|
||||
plt.grid(True)
|
||||
plt.tight_layout()
|
||||
plt.savefig(f"{save_folder}/sigma_vs_relax_ratio.png", dpi=300)
|
||||
plt.close()
|
||||
@@ -0,0 +1,48 @@
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from sklearn.linear_model import LinearRegression
|
||||
from sklearn.metrics import r2_score
|
||||
|
||||
def fit_op_cost_functional(df, algo):
|
||||
sub = df[(df["algorithm"] == algo) & (df["reached"] == True)].copy()
|
||||
|
||||
sub["logN"] = np.log(sub["nodes"])
|
||||
sub = sub.rename(columns={"relax_success":"decrease_key_calls",
|
||||
"nodes":"add_calls"})
|
||||
|
||||
if algo == "binary":
|
||||
sub["f_extract"] = sub["extract_min_calls"] * sub["logN"]
|
||||
sub["f_decrease"] = sub["decrease_key_calls"] * sub["logN"]
|
||||
else: # fibonacci
|
||||
sub["f_extract"] = sub["extract_min_calls"] * sub["logN"]
|
||||
sub["f_decrease"] = sub["decrease_key_calls"]
|
||||
|
||||
X = sub[["add_calls", "f_extract", "relax_attempts", "f_decrease"]].to_numpy()
|
||||
y = sub["time"].to_numpy()
|
||||
|
||||
model = LinearRegression()
|
||||
model.fit(X, y)
|
||||
|
||||
y_pred = model.predict(X)
|
||||
r2 = r2_score(y, y_pred)
|
||||
|
||||
return {
|
||||
"algo": algo,
|
||||
"intercept": model.intercept_,
|
||||
"coef_add": model.coef_[0],
|
||||
"coef_extract": model.coef_[1],
|
||||
"coef_relax": model.coef_[2],
|
||||
"coef_decrease": model.coef_[3],
|
||||
"r2": r2,
|
||||
"n_samples": len(sub)
|
||||
}
|
||||
|
||||
def op_cost_functional_analysis(df):
|
||||
return pd.DataFrame([
|
||||
fit_op_cost_functional(df, "binary"),
|
||||
fit_op_cost_functional(df, "fibonacci")
|
||||
])
|
||||
|
||||
df = pd.read_csv("results/synthetic_data/raw/20260302_183202.csv")
|
||||
summary = op_cost_functional_analysis(df)
|
||||
print(summary)
|
||||
@@ -0,0 +1,38 @@
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import os
|
||||
|
||||
csv_file = "results/synthetic_data/raw/20260302_183202.csv"
|
||||
save_folder = f"results/synthetic_data/derived/{os.path.splitext(os.path.basename(csv_file))[0]}"
|
||||
os.makedirs(save_folder, exist_ok=True)
|
||||
|
||||
df = pd.read_csv(csv_file)
|
||||
# df = df[df["reached"] == True].copy()
|
||||
|
||||
key_cols = ["nodes", "density", "std", "sigma", "trial", "seed"]
|
||||
|
||||
pivot_time = (
|
||||
df.pivot_table(index=key_cols, columns="algorithm", values="time", aggfunc="mean")
|
||||
.reset_index()
|
||||
)
|
||||
pivot_time["speed_ratio"] = pivot_time["binary"] / pivot_time["fibonacci"]
|
||||
|
||||
# print((pivot_time["speed_ratio"] > 1).sum())
|
||||
# print((pivot_time["speed_ratio"] < 1).sum())
|
||||
|
||||
relax = (
|
||||
df[df["algorithm"] == "binary"][key_cols + ["relax_success"]]
|
||||
)
|
||||
|
||||
merged = pivot_time.merge(relax, on=key_cols, how="left")
|
||||
|
||||
plt.figure(figsize=(6, 4))
|
||||
plt.scatter(merged["relax_success"], merged["speed_ratio"], marker=".")
|
||||
plt.axhline(y=1.0, color='red', linestyle='--')
|
||||
plt.xlabel("decrease_key (binary)")
|
||||
plt.ylabel("speed_ratio (binary / fibonacci)")
|
||||
plt.title("decrease_key vs speed_ratio")
|
||||
plt.grid(True)
|
||||
plt.tight_layout()
|
||||
plt.savefig(f"{save_folder}/decrease_key_vs_speed_ratio.png", dpi=300)
|
||||
plt.close()
|
||||
@@ -0,0 +1,36 @@
|
||||
import numpy as np
|
||||
|
||||
def generate_graph(NODES=10, DENSITY=0.1, DISTRIBUTION=('lognormal', (100, 1)), SEED=42):
|
||||
rng = np.random.default_rng(SEED)
|
||||
|
||||
# Calculate variables
|
||||
max_edges = (NODES - 1) * NODES
|
||||
edges = int(round(DENSITY * max_edges))
|
||||
|
||||
# Make edges
|
||||
sel = rng.choice(max_edges, size=edges, replace=False)
|
||||
|
||||
# Make distance array
|
||||
dist_name, dist_params = DISTRIBUTION
|
||||
|
||||
if dist_name == "lognormal":
|
||||
mean, sigma = dist_params
|
||||
distances = rng.lognormal(mean=mean, sigma=sigma, size=edges)
|
||||
elif dist_name == "uniform":
|
||||
mean = dist_params[0]
|
||||
distances = rng.uniform(0.0, 2.0 * mean, size=edges)
|
||||
elif dist_name == "exponential":
|
||||
mean = dist_params[0]
|
||||
distances = rng.exponential(scale=mean, size=edges)
|
||||
else:
|
||||
raise ValueError("Unknown DISTRIBUTION")
|
||||
|
||||
# Make adj
|
||||
adj = [[] for _ in range(NODES)]
|
||||
for idx, dist in zip(sel, distances):
|
||||
u = idx // (NODES-1)
|
||||
r = idx % (NODES-1)
|
||||
v = r if r < u else r + 1
|
||||
adj[u].append((v, dist))
|
||||
|
||||
return adj
|
||||
@@ -0,0 +1,44 @@
|
||||
import numpy as np
|
||||
|
||||
def outdegree_generate_graph(NODES, DENSITY, DISTRIBUTION, SEED=42):
|
||||
rng = np.random.default_rng(SEED)
|
||||
|
||||
max_edges = (NODES - 1) * NODES
|
||||
edges = int(round(DENSITY * max_edges))
|
||||
|
||||
base = edges // NODES
|
||||
rem = edges % NODES
|
||||
|
||||
adj = [[] for _ in range(NODES)]
|
||||
|
||||
dist_name, dist_params = DISTRIBUTION
|
||||
|
||||
if dist_name == "lognormal":
|
||||
mean, sigma = dist_params
|
||||
dist_func = lambda size: rng.lognormal(mean=mean, sigma=sigma, size=size)
|
||||
elif dist_name == "uniform":
|
||||
mean = dist_params[0]
|
||||
dist_func = lambda size: rng.uniform(0.0, 2.0 * mean, size=size)
|
||||
elif dist_name == "exponential":
|
||||
mean = dist_params[0]
|
||||
dist_func = lambda size: rng.exponential(scale=mean, size=size)
|
||||
else:
|
||||
raise ValueError("Unknown DISTRIBUTION")
|
||||
|
||||
for u in range(NODES):
|
||||
d_u = base + (1 if u < rem else 0)
|
||||
|
||||
if d_u == 0:
|
||||
continue
|
||||
|
||||
targets = rng.choice(NODES-1, size=d_u, replace=False)
|
||||
targets = np.where(targets < u, targets, targets + 1)
|
||||
|
||||
weights = dist_func(d_u)
|
||||
weights = np.rint(weights).astype(np.int64)
|
||||
weights = np.maximum(weights, 1)
|
||||
|
||||
for v, w in zip(targets, weights):
|
||||
adj[u].append((v, float(w)))
|
||||
|
||||
return adj
|
||||
@@ -0,0 +1,149 @@
|
||||
import time
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
|
||||
from experiments.synthetic_data.graph_generators.outdegree_graph_generator import outdegree_generate_graph
|
||||
from core.dijkstra.heap_dijkstra import heap_dijkstra
|
||||
from core.heaps.binary_heap import BinHeap
|
||||
from core.heaps.fibonacci_heap import FiboHeap
|
||||
|
||||
def run_test(
|
||||
nodes_li,
|
||||
densities,
|
||||
stds,
|
||||
trials,
|
||||
mean_dist,
|
||||
start,
|
||||
end,
|
||||
base_seed
|
||||
):
|
||||
timer = time.perf_counter
|
||||
file_exists = False
|
||||
INF = float('inf')
|
||||
|
||||
total_jobs = (
|
||||
len(nodes_li) *
|
||||
len(densities) *
|
||||
len(stds) *
|
||||
trials
|
||||
)
|
||||
|
||||
with tqdm(total=total_jobs) as pbar:
|
||||
for nodes in nodes_li:
|
||||
rows = []
|
||||
|
||||
for density in densities:
|
||||
for std in stds:
|
||||
sigma = np.sqrt(np.log((std / mean_dist)**2 + 1))
|
||||
mu = np.log(mean_dist) - (sigma**2) / 2
|
||||
distribution = ('lognormal', (mu, sigma))
|
||||
|
||||
for trial in range(1, trials + 1):
|
||||
seed = (base_seed * 1_000_003) ^ (nodes * 9176) ^ int(density * 1e9) ^ int(sigma * 1e6) ^ trial
|
||||
seed &= 0xFFFFFFFF
|
||||
|
||||
adj = outdegree_generate_graph(
|
||||
NODES=nodes,
|
||||
DENSITY=density,
|
||||
DISTRIBUTION=distribution,
|
||||
SEED=seed
|
||||
)
|
||||
|
||||
bin_heap = BinHeap(nodes)
|
||||
st = timer()
|
||||
dist, stats = heap_dijkstra(nodes, adj, bin_heap, start, end)
|
||||
et = timer()
|
||||
rows.append(
|
||||
{
|
||||
"nodes": nodes,
|
||||
"density": density,
|
||||
"std": std,
|
||||
"sigma": sigma,
|
||||
"trial": trial,
|
||||
"seed": seed,
|
||||
"time": et - st,
|
||||
"algorithm": "binary",
|
||||
"reached": dist != INF,
|
||||
"extract_min_calls": stats.extract_min_calls,
|
||||
"relax_attempts": stats.relax_attempts,
|
||||
"relax_success": stats.relax_success,
|
||||
}
|
||||
)
|
||||
|
||||
fibo_heap = FiboHeap(nodes)
|
||||
st = timer()
|
||||
dist, stats = heap_dijkstra(nodes, adj, fibo_heap, start, end)
|
||||
et = timer()
|
||||
rows.append(
|
||||
{
|
||||
"nodes": nodes,
|
||||
"density": density,
|
||||
"std": std,
|
||||
"sigma": sigma,
|
||||
"trial": trial,
|
||||
"seed": seed,
|
||||
"time": et - st,
|
||||
"algorithm": "fibonacci",
|
||||
"reached": dist != INF,
|
||||
"extract_min_calls": stats.extract_min_calls,
|
||||
"relax_attempts": stats.relax_attempts,
|
||||
"relax_success": stats.relax_success,
|
||||
}
|
||||
)
|
||||
|
||||
pbar.update(1)
|
||||
|
||||
df_nodes = pd.DataFrame(rows)
|
||||
df_nodes.to_csv(
|
||||
csv_file,
|
||||
mode='a',
|
||||
header=not file_exists,
|
||||
index=False
|
||||
)
|
||||
file_exists = True
|
||||
print(f"=== SAVED nodes = {nodes} ({len(df_nodes)} rows) ===")
|
||||
return
|
||||
|
||||
# Settings
|
||||
nodes_li = [2000, 4000, 8000, 16000]
|
||||
densities = [0.00015, 0.0003, 0.0006, 0.0012, 0.0024, 0.0048, 0.0096, 0.0192, 0.0384]
|
||||
stds = [1000, 1500, 2000, 3000, 4000, 6000, 8000, 12000, 16000]
|
||||
trials = 400
|
||||
mean_dist = 3000
|
||||
start = 0
|
||||
end = 1
|
||||
base_seed = 42
|
||||
|
||||
save_folder = "results/synthetic_data/raw"
|
||||
os.makedirs(save_folder, exist_ok=True)
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
csv_file = f"{save_folder}/{timestamp}.csv"
|
||||
config_file = f"{save_folder}/{timestamp}.json"
|
||||
|
||||
config = {
|
||||
"nodes_li": nodes_li,
|
||||
"densities": densities,
|
||||
"stds": stds,
|
||||
"trials": trials,
|
||||
"mean_dist": mean_dist,
|
||||
"start": start,
|
||||
"end": end,
|
||||
"base_seed": base_seed,
|
||||
}
|
||||
with open(config_file, "w") as f:
|
||||
json.dump(config, f, indent=4)
|
||||
|
||||
df = run_test(
|
||||
nodes_li = nodes_li,
|
||||
densities = densities,
|
||||
stds = stds,
|
||||
trials = trials,
|
||||
mean_dist = mean_dist,
|
||||
start = start,
|
||||
end = end,
|
||||
base_seed = base_seed
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
mu = 3.5
|
||||
sigmas = [0.1, 0.3]
|
||||
size = 100000
|
||||
rng = np.random.default_rng(42)
|
||||
|
||||
fig, axes = plt.subplots(1, 2, figsize=(16, 6))
|
||||
|
||||
for sigma in sigmas:
|
||||
samples = rng.lognormal(mean=mu, sigma=sigma, size=size)
|
||||
|
||||
# lognormal → normal 변환
|
||||
log_samples = np.log(samples)
|
||||
|
||||
# Lognormal 플롯
|
||||
axes[0].hist(samples, bins=200, density=True, alpha=0.5, label=f"sigma={sigma}")
|
||||
axes[0].set_xlim(0, 200)
|
||||
axes[0].set_title("Lognormal Distribution")
|
||||
axes[0].set_xlabel("Weight")
|
||||
axes[0].set_ylabel("Density")
|
||||
axes[0].legend()
|
||||
|
||||
# Normal 플롯
|
||||
axes[1].hist(log_samples, bins=200, density=True, alpha=0.5, label=f"sigma={sigma}")
|
||||
axes[1].set_title("log(samples) → Normal Distribution")
|
||||
axes[1].set_xlabel("log(Weight)")
|
||||
axes[1].set_ylabel("Density")
|
||||
axes[1].legend()
|
||||
|
||||
plt.suptitle("Lognormal vs Normal (μ fixed)")
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
@@ -0,0 +1,18 @@
|
||||
contourpy==1.3.3
|
||||
cycler==0.12.1
|
||||
fonttools==4.61.1
|
||||
joblib==1.5.3
|
||||
kiwisolver==1.4.9
|
||||
matplotlib==3.10.8
|
||||
numpy==2.4.2
|
||||
packaging==26.0
|
||||
pandas==3.0.1
|
||||
pillow==12.1.1
|
||||
pyparsing==3.3.2
|
||||
python-dateutil==2.9.0.post0
|
||||
scikit-learn==1.8.0
|
||||
scipy==1.17.1
|
||||
seaborn==0.13.2
|
||||
six==1.17.0
|
||||
threadpoolctl==3.6.0
|
||||
tqdm==4.67.3
|
||||
Reference in New Issue
Block a user