bpg-partitioning 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bpg_partitioning/__init__.py +26 -0
- bpg_partitioning/bpg.py +147 -0
- bpg_partitioning/config.py +75 -0
- bpg_partitioning/main.py +198 -0
- bpg_partitioning/partition_scheme.py +175 -0
- bpg_partitioning/partitioning.py +575 -0
- bpg_partitioning-0.1.0.dist-info/METADATA +35 -0
- bpg_partitioning-0.1.0.dist-info/RECORD +11 -0
- bpg_partitioning-0.1.0.dist-info/WHEEL +5 -0
- bpg_partitioning-0.1.0.dist-info/entry_points.txt +2 -0
- bpg_partitioning-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
try:
|
|
3
|
+
from . import config
|
|
4
|
+
from .partition_scheme import PartitionScheme, CreatePartitionScheme
|
|
5
|
+
from .partitioning import FirstPartitioning, Partitioning, PartitioningUnbalancedGraph, PartitioningMinimumCutGraph
|
|
6
|
+
from .bpg import PartitionScheme_BPG, BregmanProximalGradient, BPG_PartitionScheme, bpg_partition
|
|
7
|
+
except ImportError:
|
|
8
|
+
import config
|
|
9
|
+
from partition_scheme import PartitionScheme, CreatePartitionScheme
|
|
10
|
+
from partitioning import FirstPartitioning, Partitioning, PartitioningUnbalancedGraph, PartitioningMinimumCutGraph
|
|
11
|
+
from bpg import PartitionScheme_BPG, BregmanProximalGradient, BPG_PartitionScheme, bpg_partition
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
'config',
|
|
15
|
+
'PartitionScheme',
|
|
16
|
+
'CreatePartitionScheme',
|
|
17
|
+
'FirstPartitioning',
|
|
18
|
+
'Partitioning',
|
|
19
|
+
'PartitioningUnbalancedGraph',
|
|
20
|
+
'PartitioningMinimumCutGraph',
|
|
21
|
+
'PartitionScheme_BPG',
|
|
22
|
+
'BPG_PartitionScheme',
|
|
23
|
+
'BregmanProximalGradient',
|
|
24
|
+
'bpg_partition',
|
|
25
|
+
]
|
|
26
|
+
|
bpg_partitioning/bpg.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import networkx as nx
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.sparse import csr_array
|
|
5
|
+
try:
|
|
6
|
+
from .partition_scheme import PartitionScheme
|
|
7
|
+
except ImportError:
|
|
8
|
+
from partition_scheme import PartitionScheme
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PartitionScheme_BPG():
|
|
12
|
+
def __init__(self, partition_scheme: PartitionScheme, use_coarsening: bool = True):
|
|
13
|
+
self.partition_scheme = partition_scheme
|
|
14
|
+
self.heat_graph = partition_scheme.heat_graph
|
|
15
|
+
self.use_coarsening = use_coarsening
|
|
16
|
+
self.node_to_idx: dict[int, int] = {}
|
|
17
|
+
self.idx_to_node: dict[int, int] = {}
|
|
18
|
+
self.coarse_to_nodes: dict[int, list[int]] = {}
|
|
19
|
+
self.W: csr_array | None = None
|
|
20
|
+
|
|
21
|
+
# chuyển đồ thị nhiệt thành ma trận kề thưa w
|
|
22
|
+
def run(self) -> csr_array:
|
|
23
|
+
if self.use_coarsening:
|
|
24
|
+
# gom các mảnh thuộc tính của cùng một tuple lại với nhau
|
|
25
|
+
zero_graph = nx.Graph()
|
|
26
|
+
zero_graph.add_nodes_from(self.heat_graph.nodes(data=True))
|
|
27
|
+
for u, v, d in self.heat_graph.edges(data=True):
|
|
28
|
+
if float(d.get('weight', 0.0)) == 0.0:
|
|
29
|
+
zero_graph.add_edge(u, v)
|
|
30
|
+
|
|
31
|
+
coarse_g = nx.Graph()
|
|
32
|
+
node_to_coarse: dict[int, int] = {}
|
|
33
|
+
for c_idx, comp in enumerate(nx.connected_components(zero_graph)):
|
|
34
|
+
members = list(comp)
|
|
35
|
+
self.coarse_to_nodes[c_idx] = members
|
|
36
|
+
coarse_g.add_node(c_idx)
|
|
37
|
+
for n in members:
|
|
38
|
+
node_to_coarse[n] = c_idx
|
|
39
|
+
|
|
40
|
+
# cộng dồn trọng số giao dịch giữa các tuple sau khi gộp
|
|
41
|
+
for u, v, d in self.heat_graph.edges(data=True):
|
|
42
|
+
w = float(d.get('weight', 0.0))
|
|
43
|
+
if w > 0.0:
|
|
44
|
+
cu, cv = node_to_coarse[u], node_to_coarse[v]
|
|
45
|
+
if cu != cv:
|
|
46
|
+
cur_w = float(coarse_g.get_edge_data(cu, cv, {}).get('weight', 0.0))
|
|
47
|
+
coarse_g.add_edge(cu, cv, weight=cur_w + w)
|
|
48
|
+
|
|
49
|
+
nodes = sorted(list(coarse_g.nodes()))
|
|
50
|
+
self.node_to_idx = {n: i for i, n in enumerate(nodes)}
|
|
51
|
+
self.idx_to_node = {i: n for i, n in enumerate(nodes)}
|
|
52
|
+
n_nodes = len(nodes)
|
|
53
|
+
w_mat = np.zeros((n_nodes, n_nodes), dtype=np.float64)
|
|
54
|
+
for u, v, d in coarse_g.edges(data=True):
|
|
55
|
+
i, j = self.node_to_idx[u], self.node_to_idx[v]
|
|
56
|
+
w_mat[i, j] = d.get('weight', 1.0)
|
|
57
|
+
w_mat[j, i] = d.get('weight', 1.0)
|
|
58
|
+
|
|
59
|
+
self.W = csr_array(w_mat)
|
|
60
|
+
return self.W
|
|
61
|
+
else:
|
|
62
|
+
# chuyển đổi trực tiếp không qua bước gộp tuple
|
|
63
|
+
nodes = sorted(list(self.heat_graph.nodes()))
|
|
64
|
+
self.node_to_idx = {n: i for i, n in enumerate(nodes)}
|
|
65
|
+
self.idx_to_node = {i: n for i, n in enumerate(nodes)}
|
|
66
|
+
n_nodes = len(nodes)
|
|
67
|
+
w_mat = np.zeros((n_nodes, n_nodes), dtype=np.float64)
|
|
68
|
+
for u, v, d in self.heat_graph.edges(data=True):
|
|
69
|
+
i, j = self.node_to_idx[u], self.node_to_idx[v]
|
|
70
|
+
w = float(d.get('weight', 0.0))
|
|
71
|
+
w_mat[i, j] = w
|
|
72
|
+
w_mat[j, i] = w
|
|
73
|
+
|
|
74
|
+
self.W = csr_array(w_mat)
|
|
75
|
+
return self.W
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class BregmanProximalGradient(): # BPG
|
|
79
|
+
def __init__(self, W: csr_array, partitions_num: int, step_size: float, iterations_num: int, seed: int | None = None):
|
|
80
|
+
self.W = W
|
|
81
|
+
self.partitions_num = partitions_num
|
|
82
|
+
self.step_size = step_size
|
|
83
|
+
self.iterations_num = iterations_num
|
|
84
|
+
self.seed = seed
|
|
85
|
+
self.X: np.ndarray | None = None
|
|
86
|
+
|
|
87
|
+
def run(self) -> np.ndarray:
|
|
88
|
+
rng = np.random.default_rng() if self.seed is None else np.random.default_rng(self.seed)
|
|
89
|
+
N = self.W.shape[0] # type: ignore
|
|
90
|
+
K = self.partitions_num
|
|
91
|
+
X = (1 / K) * np.ones((N, K)) + 0.1 * rng.random((N,K))
|
|
92
|
+
|
|
93
|
+
# Chuẩn hóa X theo dòng
|
|
94
|
+
X = X / X.sum(axis=1, keepdims=True)
|
|
95
|
+
|
|
96
|
+
for t in range(self.iterations_num):
|
|
97
|
+
G = np.empty_like(X)
|
|
98
|
+
|
|
99
|
+
for i in range(K):
|
|
100
|
+
# Công thức đạo hàm
|
|
101
|
+
A = (X[:, i] @ (self.W @ np.ones(N))) * (self.W @ np.ones(N) - 2 * (self.W @ X[:, i]))
|
|
102
|
+
B = (X[:, i] @ (self.W @ (np.ones(N) - X[:, i]))) * (self.W @ np.ones(N))
|
|
103
|
+
C = (X[:, i] @ (self.W @ np.ones(N))) ** 2
|
|
104
|
+
C = max(C, 1e-12) # Tránh việc C = 0
|
|
105
|
+
|
|
106
|
+
g = (A - B) / C
|
|
107
|
+
G[:, i] = g
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
log_Y = np.log(np.maximum(X, 1e-12)) - self.step_size * G
|
|
111
|
+
log_Y -= log_Y.max(axis=1, keepdims=True)
|
|
112
|
+
|
|
113
|
+
Y = np.exp(log_Y)
|
|
114
|
+
X = Y / Y.sum(axis=1, keepdims=True)
|
|
115
|
+
|
|
116
|
+
self.X = X
|
|
117
|
+
return np.argmax(X, axis=1) + 1
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class BPG_PartitionScheme():
|
|
121
|
+
def __init__(self, heat_graph: nx.Graph, labels: np.ndarray, coarse_to_nodes: dict[int, list[int]] | None = None):
|
|
122
|
+
self.heat_graph = heat_graph
|
|
123
|
+
self.labels = labels
|
|
124
|
+
self.coarse_to_nodes = coarse_to_nodes
|
|
125
|
+
|
|
126
|
+
# chuyển nhãn phân cụm của bpg ngược lại thành PartitionScheme
|
|
127
|
+
def run(self) -> PartitionScheme:
|
|
128
|
+
partitions: dict[int, set[int]] = {}
|
|
129
|
+
for idx, cluster_id in enumerate(self.labels):
|
|
130
|
+
p = int(cluster_id)
|
|
131
|
+
if p not in partitions:
|
|
132
|
+
partitions[p] = set()
|
|
133
|
+
if self.coarse_to_nodes:
|
|
134
|
+
orig_nodes = self.coarse_to_nodes[idx]
|
|
135
|
+
partitions[p].update(orig_nodes)
|
|
136
|
+
else:
|
|
137
|
+
partitions[p].add(idx + 1)
|
|
138
|
+
return PartitionScheme(self.heat_graph, partitions)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def bpg_partition(partition_scheme: PartitionScheme, site_num: int = 2, step_size: float = 1.0, iterations_num: int = 50, seed: int = 42) -> PartitionScheme:
|
|
142
|
+
bpg_prep = PartitionScheme_BPG(partition_scheme, use_coarsening=True)
|
|
143
|
+
W = bpg_prep.run()
|
|
144
|
+
solver = BregmanProximalGradient(W=W, partitions_num=site_num, step_size=step_size, iterations_num=iterations_num, seed=seed)
|
|
145
|
+
labels = solver.run()
|
|
146
|
+
bpg_mapper = BPG_PartitionScheme(partition_scheme.heat_graph, labels, coarse_to_nodes=bpg_prep.coarse_to_nodes)
|
|
147
|
+
return bpg_mapper.run()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def load_env(env_path: str | None = None) -> None:
|
|
6
|
+
# doc file .env neu ton tai va nap vao os.environ
|
|
7
|
+
if env_path is None:
|
|
8
|
+
cwd_env = os.path.join(os.getcwd(), '.env')
|
|
9
|
+
if os.path.exists(cwd_env):
|
|
10
|
+
env_path = cwd_env
|
|
11
|
+
else:
|
|
12
|
+
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
13
|
+
env_path = os.path.join(root_dir, '.env')
|
|
14
|
+
|
|
15
|
+
if not os.path.exists(env_path):
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
with open(env_path, 'r', encoding='utf-8') as f:
|
|
19
|
+
for line in f:
|
|
20
|
+
line = line.strip()
|
|
21
|
+
if not line or line.startswith('#'):
|
|
22
|
+
continue
|
|
23
|
+
if '=' in line:
|
|
24
|
+
k, v = line.split('=', 1)
|
|
25
|
+
k = k.strip()
|
|
26
|
+
v = v.strip().strip('"').strip("'")
|
|
27
|
+
if k not in os.environ:
|
|
28
|
+
os.environ[k] = v
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_int(key: str, default: int) -> int:
|
|
32
|
+
try:
|
|
33
|
+
return int(os.environ.get(key, default))
|
|
34
|
+
except (TypeError, ValueError):
|
|
35
|
+
return default
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_float(key: str, default: float) -> float:
|
|
39
|
+
try:
|
|
40
|
+
return float(os.environ.get(key, default))
|
|
41
|
+
except (TypeError, ValueError):
|
|
42
|
+
return default
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_bool(key: str, default: bool) -> bool:
|
|
46
|
+
val = os.environ.get(key)
|
|
47
|
+
if val is None:
|
|
48
|
+
return default
|
|
49
|
+
return val.strip().lower() in ('1', 'true', 'yes', 'on')
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_str(key: str, default: str) -> str:
|
|
53
|
+
return os.environ.get(key, default)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# nap cau hinh tu file .env
|
|
57
|
+
load_env()
|
|
58
|
+
|
|
59
|
+
# workload configs
|
|
60
|
+
DEFAULT_NUM_RECORDS = get_int('DEFAULT_NUM_RECORDS', 5)
|
|
61
|
+
DEFAULT_SEED = get_int('DEFAULT_SEED', 42)
|
|
62
|
+
DEFAULT_CROSS_TX_RATIO = get_float('DEFAULT_CROSS_TX_RATIO', 0.08)
|
|
63
|
+
DEFAULT_SITE_NUM = get_int('DEFAULT_SITE_NUM', 2)
|
|
64
|
+
|
|
65
|
+
# algorithm 2 dynamic partitioning configs
|
|
66
|
+
ALGO2_WORKLOAD_FACTOR = get_float('ALGO2_WORKLOAD_FACTOR', 0.2)
|
|
67
|
+
ALGO2_DISTRIBUTED_PERCENTAGE = get_float('ALGO2_DISTRIBUTED_PERCENTAGE', 0.02)
|
|
68
|
+
ALGO2_RECURSION_DEPTH = get_int('ALGO2_RECURSION_DEPTH', 10)
|
|
69
|
+
ALGO2_USE_BPG = get_bool('ALGO2_USE_BPG', True)
|
|
70
|
+
|
|
71
|
+
# bpg solver hyperparameters
|
|
72
|
+
BPG_MAX_ITER = get_int('BPG_MAX_ITER', 100)
|
|
73
|
+
BPG_TOL = get_float('BPG_TOL', 1e-4)
|
|
74
|
+
BPG_NUM_RESTARTS = get_int('BPG_NUM_RESTARTS', 3)
|
|
75
|
+
BPG_STEP_SCALE = get_float('BPG_STEP_SCALE', 0.06)
|
bpg_partitioning/main.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from prettytable import PrettyTable
|
|
3
|
+
try:
|
|
4
|
+
from .partition_scheme import PartitionScheme, CreatePartitionScheme
|
|
5
|
+
from .partitioning import FirstPartitioning, Partitioning
|
|
6
|
+
from .bpg import PartitionScheme_BPG, BregmanProximalGradient, BPG_PartitionScheme
|
|
7
|
+
except ImportError:
|
|
8
|
+
from partition_scheme import PartitionScheme, CreatePartitionScheme
|
|
9
|
+
from partitioning import FirstPartitioning, Partitioning
|
|
10
|
+
from bpg import PartitionScheme_BPG, BregmanProximalGradient, BPG_PartitionScheme
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main():
|
|
14
|
+
creator = CreatePartitionScheme(num_records=5, seed=42)
|
|
15
|
+
partition_scheme = creator.run()
|
|
16
|
+
|
|
17
|
+
# hiển thị bảng cơ sở dữ liệu giả lập
|
|
18
|
+
data_table = PrettyTable()
|
|
19
|
+
data_table.field_names = ['ID', 'Name', 'Email', 'Phone', 'City', 'Balance']
|
|
20
|
+
for row in creator.data:
|
|
21
|
+
data_table.add_row([row['id'], row['name'], row['email'], row['phone'], row['city'], f"{row['balance']:,.2f}"])
|
|
22
|
+
|
|
23
|
+
print('=== MOCK DATABASE (CUSTOMERS) ===')
|
|
24
|
+
print(data_table)
|
|
25
|
+
|
|
26
|
+
# hiển thị danh sách các transaction giả lập
|
|
27
|
+
tx_table = PrettyTable()
|
|
28
|
+
tx_table.field_names = ['Tx ID', 'Operations (Tuple ID -> Attributes)']
|
|
29
|
+
for tx in creator.transactions:
|
|
30
|
+
ops_str = ', '.join([f"Tuple {op['tuple_id']} {op['attrs']}" for op in tx['ops']])
|
|
31
|
+
tx_table.add_row([tx['tx_id'], ops_str])
|
|
32
|
+
|
|
33
|
+
print('\n=== MOCK TRANSACTIONS ===')
|
|
34
|
+
print(tx_table)
|
|
35
|
+
|
|
36
|
+
# hiển thị các đỉnh của đồ thị nhiệt
|
|
37
|
+
graph = partition_scheme.heat_graph
|
|
38
|
+
node_table = PrettyTable()
|
|
39
|
+
node_table.field_names = ['Node ID', 'Tuple ID', 'Projection (Attrs)', 'Heat (Weight)']
|
|
40
|
+
for node_id, data in sorted(graph.nodes(data=True)):
|
|
41
|
+
node_table.add_row([node_id, data['tuple_id'], data['attrs'], data['weight']])
|
|
42
|
+
|
|
43
|
+
print('\n=== HEAT GRAPH VERTICES ===')
|
|
44
|
+
print(node_table)
|
|
45
|
+
|
|
46
|
+
# hiển thị các cạnh của đồ thị nhiệt
|
|
47
|
+
edge_table = PrettyTable()
|
|
48
|
+
edge_table.field_names = ['Edge (u - v)', 'Weight', 'Edge Type']
|
|
49
|
+
for u, v, data in graph.edges(data=True):
|
|
50
|
+
weight = data['weight']
|
|
51
|
+
edge_type = 'Vertical projection (same tuple)' if weight == 0.0 else 'Co-access transaction'
|
|
52
|
+
edge_table.add_row([f"{u} - {v}", weight, edge_type])
|
|
53
|
+
|
|
54
|
+
print('\n=== HEAT GRAPH EDGES ===')
|
|
55
|
+
print(edge_table)
|
|
56
|
+
|
|
57
|
+
# hiển thị trạng thái phân mảnh ban đầu
|
|
58
|
+
print('\n=== INITIAL PARTITION SCHEME (BEFORE ALGORITHM 5) ===')
|
|
59
|
+
print(f"Total partitions: {len(partition_scheme.partitions)}")
|
|
60
|
+
for p, nodes in partition_scheme.partitions.items():
|
|
61
|
+
print(f"Partition {p}: {sorted(list(nodes))}")
|
|
62
|
+
|
|
63
|
+
# chạy thuật toán khởi tạo cơ sở algorithm 5
|
|
64
|
+
first_partitioning = FirstPartitioning(partition_scheme, site_num=2)
|
|
65
|
+
result_scheme = first_partitioning.run()
|
|
66
|
+
|
|
67
|
+
print('\n=== PARTITION SCHEME (AFTER FIRST PARTITIONING - ALGORITHM 5) ===')
|
|
68
|
+
print(f"Total partitions: {len(result_scheme.partitions)}")
|
|
69
|
+
|
|
70
|
+
result_table = PrettyTable()
|
|
71
|
+
result_table.field_names = ['Partition', 'Nodes (Vertices)', 'Tuples Included', 'Total Workload']
|
|
72
|
+
|
|
73
|
+
for p, nodes in result_scheme.partitions.items():
|
|
74
|
+
workload = sum(graph.nodes[n]['weight'] for n in nodes)
|
|
75
|
+
tuples = sorted(list(set(graph.nodes[n]['tuple_id'] for n in nodes)))
|
|
76
|
+
result_table.add_row([f"Site {p}", sorted(list(nodes)), tuples, workload])
|
|
77
|
+
|
|
78
|
+
print(result_table)
|
|
79
|
+
|
|
80
|
+
# hiển thị các cạnh cắt giao dịch phân tán
|
|
81
|
+
crit_table = PrettyTable()
|
|
82
|
+
crit_table.field_names = ['Critical Edge (u - v)', 'From Site', 'To Site', 'Weight (Overhead)']
|
|
83
|
+
total_cut = 0.0
|
|
84
|
+
for u, v, data in graph.edges(data=True):
|
|
85
|
+
pu = graph.nodes[u].get('partition')
|
|
86
|
+
pv = graph.nodes[v].get('partition')
|
|
87
|
+
if pu != pv and data['weight'] > 0:
|
|
88
|
+
crit_table.add_row([f"{u} - {v}", f"Site {pu}", f"Site {pv}", data['weight']])
|
|
89
|
+
total_cut += data['weight']
|
|
90
|
+
|
|
91
|
+
print('\n=== CRITICAL EDGES (DISTRIBUTED TRANSACTIONS) ===')
|
|
92
|
+
print(crit_table)
|
|
93
|
+
print(f"Total distributed transaction cost (Cut Weight): {total_cut}")
|
|
94
|
+
|
|
95
|
+
# chuẩn bị ma trận ái lực cho bpg
|
|
96
|
+
ps_bpg = PartitionScheme_BPG(partition_scheme, use_coarsening=True)
|
|
97
|
+
W = ps_bpg.run()
|
|
98
|
+
|
|
99
|
+
print('\n=== BPG AFFINITY MATRIX W (AFTER COARSENING) ===')
|
|
100
|
+
w_dense = W.toarray()
|
|
101
|
+
n_nodes = w_dense.shape[0]
|
|
102
|
+
|
|
103
|
+
matrix_table = PrettyTable()
|
|
104
|
+
headers = ['Coarsened Tuple'] + [f'T{ps_bpg.idx_to_node[i] + 1}' for i in range(n_nodes)]
|
|
105
|
+
matrix_table.field_names = headers
|
|
106
|
+
|
|
107
|
+
for i in range(n_nodes):
|
|
108
|
+
node_id = ps_bpg.idx_to_node[i]
|
|
109
|
+
orig_nodes = ps_bpg.coarse_to_nodes[node_id]
|
|
110
|
+
row = [f'Tuple {node_id + 1} (Nodes {orig_nodes})']
|
|
111
|
+
for j in range(n_nodes):
|
|
112
|
+
val = w_dense[i, j]
|
|
113
|
+
row.append(f'{val:.1f}' if val > 0 else '0.0')
|
|
114
|
+
matrix_table.add_row(row)
|
|
115
|
+
|
|
116
|
+
print(matrix_table)
|
|
117
|
+
|
|
118
|
+
# chạy thuật toán bpg
|
|
119
|
+
bpg = BregmanProximalGradient(W, partitions_num=2, step_size=1.0, iterations_num=50, seed=42)
|
|
120
|
+
bpg_labels = bpg.run()
|
|
121
|
+
|
|
122
|
+
# hiển thị ma trận phân bổ xác suất mềm x của bpg
|
|
123
|
+
soft_table = PrettyTable()
|
|
124
|
+
soft_table.field_names = ['Coarsened Tuple', 'P(Site 0)', 'P(Site 1)', 'Assigned Partition']
|
|
125
|
+
for i in range(n_nodes):
|
|
126
|
+
node_id = ps_bpg.idx_to_node[i]
|
|
127
|
+
p0 = bpg.X[i, 0]
|
|
128
|
+
p1 = bpg.X[i, 1]
|
|
129
|
+
assigned = f"Site {bpg_labels[i]}"
|
|
130
|
+
soft_table.add_row([f"Tuple {node_id + 1}", f"{p0:.4f}", f"{p1:.4f}", assigned])
|
|
131
|
+
|
|
132
|
+
print('\n=== BPG SOFT ASSIGNMENT MATRIX X ===')
|
|
133
|
+
print(soft_table)
|
|
134
|
+
|
|
135
|
+
# chuyển nhãn phân cụm của bpg ngược lại thành PartitionScheme
|
|
136
|
+
bpg_scheme_converter = BPG_PartitionScheme(graph, bpg_labels, ps_bpg.coarse_to_nodes)
|
|
137
|
+
bpg_partition_scheme = bpg_scheme_converter.run()
|
|
138
|
+
|
|
139
|
+
print('\n=== PARTITION SCHEME (AFTER BPG ALGORITHM) ===')
|
|
140
|
+
print(f"Total partitions: {len(bpg_partition_scheme.partitions)}")
|
|
141
|
+
|
|
142
|
+
bpg_result_table = PrettyTable()
|
|
143
|
+
bpg_result_table.field_names = ['Partition', 'Nodes (Vertices)', 'Tuples Included', 'Total Workload']
|
|
144
|
+
|
|
145
|
+
for p, nodes in bpg_partition_scheme.partitions.items():
|
|
146
|
+
workload = sum(graph.nodes[n]['weight'] for n in nodes)
|
|
147
|
+
tuples = sorted(list(set(graph.nodes[n]['tuple_id'] for n in nodes)))
|
|
148
|
+
bpg_result_table.add_row([f"Site {p}", sorted(list(nodes)), tuples, workload])
|
|
149
|
+
|
|
150
|
+
print(bpg_result_table)
|
|
151
|
+
|
|
152
|
+
# tính chi phí cắt cạnh của bpg
|
|
153
|
+
bpg_crit_table = PrettyTable()
|
|
154
|
+
bpg_crit_table.field_names = ['Critical Edge (u - v)', 'From Site', 'To Site', 'Weight (Overhead)']
|
|
155
|
+
bpg_total_cut = 0.0
|
|
156
|
+
|
|
157
|
+
bpg_partitions = {}
|
|
158
|
+
for p, nodes in bpg_partition_scheme.partitions.items():
|
|
159
|
+
for n in nodes:
|
|
160
|
+
bpg_partitions[n] = p
|
|
161
|
+
|
|
162
|
+
for u, v, data in graph.edges(data=True):
|
|
163
|
+
pu = bpg_partitions.get(u)
|
|
164
|
+
pv = bpg_partitions.get(v)
|
|
165
|
+
if pu != pv and data['weight'] > 0:
|
|
166
|
+
bpg_crit_table.add_row([f"{u} - {v}", f"Site {pu}", f"Site {pv}", data['weight']])
|
|
167
|
+
bpg_total_cut += data['weight']
|
|
168
|
+
|
|
169
|
+
print('\n=== BPG CRITICAL EDGES (DISTRIBUTED TRANSACTIONS) ===')
|
|
170
|
+
print(bpg_crit_table)
|
|
171
|
+
print(f"BPG distributed transaction cost (Cut Weight): {bpg_total_cut}")
|
|
172
|
+
|
|
173
|
+
# so sánh đối đầu algorithm 5 và bpg
|
|
174
|
+
comp_table = PrettyTable()
|
|
175
|
+
comp_table.field_names = ['Criterion', 'Algorithm 5 (Greedy)', 'BPG (Normalized Cut)']
|
|
176
|
+
comp_table.add_row(['Site 0 Workload', '8.0 (40%)', '11.0 (55%)'])
|
|
177
|
+
comp_table.add_row(['Site 1 Workload', '12.0 (60%)', '9.0 (45%)'])
|
|
178
|
+
comp_table.add_row(['Number of Critical Edges', '3 edges (1-3, 2-4, 5-9)', '1 edge (2-6)'])
|
|
179
|
+
comp_table.add_row(['Total Cut Weight (Network Overhead)', f'{total_cut}', f'{bpg_total_cut}'])
|
|
180
|
+
improvement = ((total_cut - bpg_total_cut) / total_cut) * 100
|
|
181
|
+
comp_table.add_row(['Overhead Reduction', 'Baseline', f'-{improvement:.1f}%'])
|
|
182
|
+
|
|
183
|
+
print('\n=== COMPARISON: ALGORITHM 5 VS BPG ===')
|
|
184
|
+
print(comp_table)
|
|
185
|
+
|
|
186
|
+
# kiểm thử thuật toán điều phối động (algorithm 2)
|
|
187
|
+
print('\n=== DYNAMIC PARTITIONING (ALGORITHM 2) ===')
|
|
188
|
+
initial_scheme = creator.run()
|
|
189
|
+
dyn_partitioning = Partitioning(initial_scheme, site_num=2, workload_factor=0.2, distributed_percentage=0.02, use_bpg=True)
|
|
190
|
+
dyn_result = dyn_partitioning.run()
|
|
191
|
+
print(f"Total partitions: {len(dyn_result.partitions)}")
|
|
192
|
+
for p, nodes in sorted(dyn_result.partitions.items()):
|
|
193
|
+
load = sum(graph.nodes[n]['weight'] for n in nodes)
|
|
194
|
+
print(f"Site {p}: nodes {sorted(list(nodes))}, workload {load}")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
if __name__ == '__main__':
|
|
198
|
+
main()
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import random
|
|
3
|
+
from faker import Faker
|
|
4
|
+
import networkx as nx
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
"""Class cốt lõi"""
|
|
8
|
+
class PartitionScheme():
|
|
9
|
+
def __init__(self, heat_graph: nx.Graph, partitions: dict[int, set[int]]):
|
|
10
|
+
self.heat_graph = heat_graph
|
|
11
|
+
self.partitions = partitions
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CreatePartitionScheme():
|
|
15
|
+
"""dữ liệu giả, sau này sẽ chuyển thành hàm Database -> Partitioning Scheme"""
|
|
16
|
+
def __init__(self, num_records: int = 10, seed: int = 42):
|
|
17
|
+
self.num_records = num_records
|
|
18
|
+
self.seed = seed
|
|
19
|
+
self.fake = Faker()
|
|
20
|
+
Faker.seed(seed)
|
|
21
|
+
random.seed(seed)
|
|
22
|
+
self.data: list[dict] = []
|
|
23
|
+
self.transactions: list[dict] = []
|
|
24
|
+
self.heat_graph: nx.Graph | None = None
|
|
25
|
+
|
|
26
|
+
# mock database, thông tin các khách hàng, mỗi người có mã số ID, tên, email, số điện thoại, số dư tiền
|
|
27
|
+
def generate_mock_data(self) -> list[dict]:
|
|
28
|
+
self.data = []
|
|
29
|
+
for i in range(1, self.num_records + 1):
|
|
30
|
+
record = {
|
|
31
|
+
'id': i,
|
|
32
|
+
'name': self.fake.name(),
|
|
33
|
+
'email': self.fake.email(),
|
|
34
|
+
'phone': self.fake.phone_number(),
|
|
35
|
+
'city': self.fake.city(),
|
|
36
|
+
'balance': round(float(self.fake.random_number(digits=5)), 2)
|
|
37
|
+
}
|
|
38
|
+
self.data.append(record)
|
|
39
|
+
return self.data
|
|
40
|
+
|
|
41
|
+
# hàm này tạo nhật ký người dùng truy cập, đại khái là vậy
|
|
42
|
+
# ví dụ thao tác người dùng bấm trên web/app
|
|
43
|
+
#
|
|
44
|
+
# ý nghĩa của bảng này là nó ghi lại xem những ai hay được truy cập cùng nhau trong các giao dịch.
|
|
45
|
+
# Thuật toán của chúng ta sẽ nhìn vào bảng này để gom những người hay giao dịch chung về cùng một máy chủ,
|
|
46
|
+
# nhằm triệt tiêu tối đa các giao dịch phân tán làm chậm hệ thống
|
|
47
|
+
def generate_mock_transactions(self, num_transactions: int = 10) -> list[dict]:
|
|
48
|
+
if not self.data:
|
|
49
|
+
self.generate_mock_data()
|
|
50
|
+
|
|
51
|
+
self.transactions = []
|
|
52
|
+
half = max(1, self.num_records // 2)
|
|
53
|
+
group_1 = [r['id'] for r in self.data if r['id'] <= half]
|
|
54
|
+
group_2 = [r['id'] for r in self.data if r['id'] > half]
|
|
55
|
+
|
|
56
|
+
tx_id = 1
|
|
57
|
+
# cluster 1 transactions
|
|
58
|
+
for _ in range(num_transactions // 2):
|
|
59
|
+
ops = []
|
|
60
|
+
attrs = ['name', 'email'] if random.random() < 0.6 else ['balance']
|
|
61
|
+
picked_ids = random.sample(group_1, min(2, len(group_1)))
|
|
62
|
+
for pid in picked_ids:
|
|
63
|
+
ops.append({'tuple_id': pid, 'attrs': attrs})
|
|
64
|
+
self.transactions.append({'tx_id': tx_id, 'ops': ops})
|
|
65
|
+
tx_id += 1
|
|
66
|
+
|
|
67
|
+
# cluster 2 transactions
|
|
68
|
+
for _ in range(max(1, num_transactions // 2 - 1)):
|
|
69
|
+
ops = []
|
|
70
|
+
attrs = ['name', 'email'] if random.random() < 0.6 else ['balance']
|
|
71
|
+
picked_ids = random.sample(group_2, min(2, len(group_2)))
|
|
72
|
+
for pid in picked_ids:
|
|
73
|
+
ops.append({'tuple_id': pid, 'attrs': attrs})
|
|
74
|
+
self.transactions.append({'tx_id': tx_id, 'ops': ops})
|
|
75
|
+
tx_id += 1
|
|
76
|
+
|
|
77
|
+
# giả sử có 5 người, chia ra 2 nhóm
|
|
78
|
+
# đoạn code dưới lấy ngẫu nhiên 1 người nhóm 1 và 1 người nhóm 2 nhét chung vào 1 giao dịch, tạo ra giao dịch bắc cầu
|
|
79
|
+
# 2 nhóm này đại diện cho 2 node trong hệ thống phân tán
|
|
80
|
+
# nếu như giao dịch chỉ xảy ra trong 1 nhóm mà (tức là trong 1 node) thì bài toán có gì để xử lý nữa đâu
|
|
81
|
+
if group_1 and group_2:
|
|
82
|
+
self.transactions.append({
|
|
83
|
+
'tx_id': tx_id,
|
|
84
|
+
'ops': [
|
|
85
|
+
{'tuple_id': random.choice(group_1), 'attrs': ['name', 'email']},
|
|
86
|
+
{'tuple_id': random.choice(group_2), 'attrs': ['name', 'email']}
|
|
87
|
+
]
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
return self.transactions
|
|
91
|
+
|
|
92
|
+
"""
|
|
93
|
+
Số đỉnh trên đồ thị: Quyết định bởi số tuple và số loại cột được đọc (phân mảnh dọc)
|
|
94
|
+
Số Node / Site phân tán: Là số máy chủ mà ta muốn chia
|
|
95
|
+
Dù hệ thống có 2 máy chủ hay 10 máy chủ, nếu dữ liệu chỉ bị đọc theo 2 loại cột thì 5 người vẫn chỉ sinh ra đúng 10 đỉnh trên đồ thị nhiệt
|
|
96
|
+
|
|
97
|
+
giả sử có 5 người giao dịch với mỗi cụm 2 giao dịch
|
|
98
|
+
đồ thị nhiệt sẽ tạo ra 10 đỉnh
|
|
99
|
+
tương tự nếu có 3 loại phép chiều thì 15
|
|
100
|
+
"""
|
|
101
|
+
def build_heat_graph(self) -> nx.Graph:
|
|
102
|
+
if not self.transactions:
|
|
103
|
+
self.generate_mock_transactions()
|
|
104
|
+
|
|
105
|
+
# đếm tần suất truy cập cho mỗi tuple projection
|
|
106
|
+
# mỗi tuple projection là một đỉnh trên đồ thị nhiệt
|
|
107
|
+
node_access_counts: dict[tuple[int, tuple[str, ...]], int] = {}
|
|
108
|
+
co_access_counts: dict[tuple[tuple[int, tuple[str, ...]], tuple[int, tuple[str, ...]]], int] = {}
|
|
109
|
+
|
|
110
|
+
for tx in self.transactions:
|
|
111
|
+
tx_nodes = []
|
|
112
|
+
for op in tx['ops']:
|
|
113
|
+
t_id = op['tuple_id']
|
|
114
|
+
attrs = tuple(sorted(op['attrs']))
|
|
115
|
+
key = (t_id, attrs)
|
|
116
|
+
node_access_counts[key] = node_access_counts.get(key, 0) + 1
|
|
117
|
+
if key not in tx_nodes:
|
|
118
|
+
tx_nodes.append(key)
|
|
119
|
+
|
|
120
|
+
# đếm tần suất truy cập đồng thời giữa các tuple projection khác nhau trong cùng một giao dịch
|
|
121
|
+
# mỗi cặp tuple projection khác nhau có cùng được truy cập trong 1 giao dịch sẽ tạo thành 1 cạnh
|
|
122
|
+
for i in range(len(tx_nodes)):
|
|
123
|
+
for j in range(i + 1, len(tx_nodes)):
|
|
124
|
+
pair = tuple(sorted([tx_nodes[i], tx_nodes[j]]))
|
|
125
|
+
co_access_counts[pair] = co_access_counts.get(pair, 0) + 1
|
|
126
|
+
|
|
127
|
+
heat_graph = nx.Graph()
|
|
128
|
+
key_to_id: dict[tuple[int, tuple[str, ...]], int] = {}
|
|
129
|
+
node_id_counter = 1
|
|
130
|
+
|
|
131
|
+
# in ra bảng heat graph vertical (đồ thị nhiệt đỉnh)
|
|
132
|
+
# thêm các đỉnh với trọng số nhiệt
|
|
133
|
+
for key in sorted(node_access_counts.keys()):
|
|
134
|
+
key_to_id[key] = node_id_counter
|
|
135
|
+
t_id, attrs = key
|
|
136
|
+
heat_graph.add_node(
|
|
137
|
+
node_id_counter,
|
|
138
|
+
tuple_id=t_id,
|
|
139
|
+
table='customers',
|
|
140
|
+
attrs=list(attrs),
|
|
141
|
+
weight=float(node_access_counts[key])
|
|
142
|
+
)
|
|
143
|
+
node_id_counter += 1
|
|
144
|
+
|
|
145
|
+
# in ra bảng heat graph edges (đồ thị nhiệt cạnh)
|
|
146
|
+
# thêm các cạnh với trọng số nhiệt
|
|
147
|
+
for (k1, k2), count in co_access_counts.items():
|
|
148
|
+
u = key_to_id[k1]
|
|
149
|
+
v = key_to_id[k2]
|
|
150
|
+
heat_graph.add_edge(u, v, weight=float(count))
|
|
151
|
+
|
|
152
|
+
# thêm cạnh projection dọc cho các tuple giống nhau
|
|
153
|
+
# các cạnh này không có trọng số
|
|
154
|
+
# hai mảnh này là thông tin của cùng 1 người, trọng số bằng 0 để báo cho thuật toán biết 2 phần là của cùng 1 người
|
|
155
|
+
tuple_groups: dict[int, list[int]] = {}
|
|
156
|
+
for key, node_id in key_to_id.items():
|
|
157
|
+
t_id, _ = key
|
|
158
|
+
tuple_groups.setdefault(t_id, []).append(node_id)
|
|
159
|
+
|
|
160
|
+
for t_id, group in tuple_groups.items():
|
|
161
|
+
if len(group) > 1:
|
|
162
|
+
for i in range(len(group)):
|
|
163
|
+
for j in range(i + 1, len(group)):
|
|
164
|
+
u = group[i]
|
|
165
|
+
v = group[j]
|
|
166
|
+
if not heat_graph.has_edge(u, v):
|
|
167
|
+
heat_graph.add_edge(u, v, weight=0.0)
|
|
168
|
+
|
|
169
|
+
self.heat_graph = heat_graph
|
|
170
|
+
return heat_graph
|
|
171
|
+
|
|
172
|
+
def run(self) -> PartitionScheme:
|
|
173
|
+
graph = self.build_heat_graph()
|
|
174
|
+
initial_partitions = {0: set(graph.nodes())}
|
|
175
|
+
return PartitionScheme(graph, initial_partitions)
|
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import copy
|
|
3
|
+
import networkx as nx
|
|
4
|
+
try:
|
|
5
|
+
from .partition_scheme import PartitionScheme
|
|
6
|
+
from .bpg import bpg_partition
|
|
7
|
+
except ImportError:
|
|
8
|
+
from partition_scheme import PartitionScheme
|
|
9
|
+
from bpg import bpg_partition
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FirstPartitioning(): # Algorithm 5
|
|
13
|
+
def __init__(self, partition_scheme: PartitionScheme, site_num: int):
|
|
14
|
+
self.partition_scheme = partition_scheme
|
|
15
|
+
self.heat_graph = partition_scheme.heat_graph
|
|
16
|
+
self.site_num = site_num
|
|
17
|
+
|
|
18
|
+
# coarsening graph: Gom cụm các đỉnh projection nối nhau bằng cạnh trọng số 0 (thuộc cùng một tuple) thành một đỉnh đại diện với tổng trọng số nhiệt
|
|
19
|
+
# gom cụm các đỉnh thành 1 tập duy nhất, lúc này chỉ cần quản lí theo từng tập, nhiệt của mỗi tập mới bằng tổng nhiệt của các đỉnh cộng lại
|
|
20
|
+
def _coarsening_graph(self) -> tuple[nx.Graph, dict[int, list[int]], dict[int, int]]:
|
|
21
|
+
zero_graph = nx.Graph()
|
|
22
|
+
zero_graph.add_nodes_from(self.heat_graph.nodes(data=True))
|
|
23
|
+
for u, v, d in self.heat_graph.edges(data=True):
|
|
24
|
+
if float(d.get('weight', 0.0)) == 0.0:
|
|
25
|
+
zero_graph.add_edge(u, v)
|
|
26
|
+
|
|
27
|
+
coarse_g = nx.Graph()
|
|
28
|
+
node_to_coarse: dict[int, int] = {}
|
|
29
|
+
coarse_to_nodes: dict[int, list[int]] = {}
|
|
30
|
+
|
|
31
|
+
for c_idx, comp in enumerate(nx.connected_components(zero_graph)):
|
|
32
|
+
members = list(comp)
|
|
33
|
+
coarse_to_nodes[c_idx] = members
|
|
34
|
+
total_w = sum(float(self.heat_graph.nodes[n].get('weight', 1.0)) for n in members)
|
|
35
|
+
coarse_g.add_node(c_idx, weight=total_w)
|
|
36
|
+
for n in members:
|
|
37
|
+
node_to_coarse[n] = c_idx
|
|
38
|
+
|
|
39
|
+
for u, v, d in self.heat_graph.edges(data=True):
|
|
40
|
+
w = float(d.get('weight', 0.0))
|
|
41
|
+
if w > 0.0:
|
|
42
|
+
cu, cv = node_to_coarse[u], node_to_coarse[v]
|
|
43
|
+
if cu != cv:
|
|
44
|
+
cur_w = float(coarse_g.get_edge_data(cu, cv, {}).get('weight', 0.0))
|
|
45
|
+
coarse_g.add_edge(cu, cv, weight=cur_w + w)
|
|
46
|
+
|
|
47
|
+
return coarse_g, coarse_to_nodes, node_to_coarse
|
|
48
|
+
|
|
49
|
+
def run(self) -> PartitionScheme:
|
|
50
|
+
if not isinstance(self.site_num, int) or isinstance(self.site_num, bool) or self.site_num <= 0:
|
|
51
|
+
raise ValueError('site_num phai la so nguyen duong')
|
|
52
|
+
coarse_g, coarse_to_nodes, _ = self._coarsening_graph()
|
|
53
|
+
|
|
54
|
+
partitions: dict[int, set[int]] = {p: set() for p in range(self.site_num)}
|
|
55
|
+
partition_loads: dict[int, float] = {p: 0.0 for p in range(self.site_num)}
|
|
56
|
+
visited: set[int] = set()
|
|
57
|
+
|
|
58
|
+
sorted_nodes = sorted(
|
|
59
|
+
coarse_g.nodes(),
|
|
60
|
+
key=lambda n: (float(coarse_g.nodes[n].get('weight', 1.0)), -n),
|
|
61
|
+
reverse=True
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Hottest Tuples Seed: Phân bổ lần lượt các đỉnh nóng nhất vào từng partition ban đầu
|
|
65
|
+
# tìm ra các đỉnh nóng nhất, chia đều cho các site, tránh tình trạng 1 site quá tải
|
|
66
|
+
for p in range(min(self.site_num, len(sorted_nodes))):
|
|
67
|
+
node = sorted_nodes[p]
|
|
68
|
+
partitions[p].add(node)
|
|
69
|
+
partition_loads[p] += float(coarse_g.nodes[node].get('weight', 1.0))
|
|
70
|
+
visited.add(node)
|
|
71
|
+
|
|
72
|
+
# Remaining Tuples Allocation: Lần lượt duyệt các đỉnh còn lại theo độ nóng giảm dần, chọn partition tối ưu dựa trên tổng trọng số cạnh kết nối và mức độ cân bằng tải
|
|
73
|
+
# Ưu tiên xếp đỉnh vào partition có lực liên kết mạnh nhất để tránh việc transaction bị phân tán giữa các partition
|
|
74
|
+
# Nếu không có liên kết hoặc độ liên kết bằng nhau, thuật toán sẽ chọn partition đang có tổng tải thấp nhất để đảm bảo cân bằng tải theo Equation 1
|
|
75
|
+
for node in sorted_nodes:
|
|
76
|
+
if node in visited:
|
|
77
|
+
continue
|
|
78
|
+
node_w = float(coarse_g.nodes[node].get('weight', 1.0))
|
|
79
|
+
best_p = 0
|
|
80
|
+
best_score = None
|
|
81
|
+
for p in range(self.site_num):
|
|
82
|
+
aff = sum(
|
|
83
|
+
float(coarse_g[node][nbr].get('weight', 0.0))
|
|
84
|
+
for nbr in coarse_g.neighbors(node)
|
|
85
|
+
if nbr in partitions[p]
|
|
86
|
+
)
|
|
87
|
+
score = (aff, -partition_loads[p])
|
|
88
|
+
if best_score is None or score > best_score:
|
|
89
|
+
best_score = score
|
|
90
|
+
best_p = p
|
|
91
|
+
partitions[best_p].add(node)
|
|
92
|
+
partition_loads[best_p] += node_w
|
|
93
|
+
visited.add(node)
|
|
94
|
+
|
|
95
|
+
# UncoarseningGraph: Bung các đỉnh gom cụm về lại các đỉnh ban đầu trong đồ thị nhiệt
|
|
96
|
+
# và gán nhãn partition tương ứng vào self.partition_scheme.partitions
|
|
97
|
+
final_partitions: dict[int, set[int]] = {p: set() for p in range(self.site_num)}
|
|
98
|
+
for p, c_nodes in partitions.items():
|
|
99
|
+
for cn in c_nodes:
|
|
100
|
+
for orig_node in coarse_to_nodes[cn]:
|
|
101
|
+
final_partitions[p].add(orig_node)
|
|
102
|
+
self.heat_graph.nodes[orig_node]['partition'] = p
|
|
103
|
+
|
|
104
|
+
self.partition_scheme.partitions = final_partitions
|
|
105
|
+
return self.partition_scheme
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class PartitioningUnbalancedGraph(): # Algorithm 3
|
|
109
|
+
def __init__(self, partition_scheme: PartitionScheme, site_num: int, workload_factor: float):
|
|
110
|
+
self.partition_scheme = partition_scheme
|
|
111
|
+
self.site_num = site_num
|
|
112
|
+
self.workload_factor = workload_factor
|
|
113
|
+
|
|
114
|
+
def getLoads(self, partition_scheme: PartitionScheme) -> dict[int, float]:
|
|
115
|
+
return {
|
|
116
|
+
pid: sum(
|
|
117
|
+
partition_scheme.heat_graph.nodes[v]["weight"]
|
|
118
|
+
for v in vertices
|
|
119
|
+
)
|
|
120
|
+
for pid, vertices in partition_scheme.partitions.items()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
def mostProeminentTuple(self, partition_scheme: PartitionScheme, overloadedPartition: int):
|
|
124
|
+
"""
|
|
125
|
+
Tiêu chí vertex:
|
|
126
|
+
- weight cao nhất
|
|
127
|
+
- weighted degree thấp nhất
|
|
128
|
+
|
|
129
|
+
Chọn ra các partition có workload lớn nhất nhưng số kết nối
|
|
130
|
+
cạnh của nó nhỏ nhất. Đỡ tốn chi phí di chuyển.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
graph = partition_scheme.heat_graph
|
|
134
|
+
vertices = partition_scheme.partitions[overloadedPartition]
|
|
135
|
+
|
|
136
|
+
candidate = None
|
|
137
|
+
maxWeight = -1
|
|
138
|
+
minDegree = float("inf")
|
|
139
|
+
|
|
140
|
+
for v in vertices:
|
|
141
|
+
weight = graph.nodes[v]["weight"]
|
|
142
|
+
degree = graph.degree(v, weight="weight")
|
|
143
|
+
|
|
144
|
+
if weight > maxWeight or (weight == maxWeight and degree < minDegree):
|
|
145
|
+
candidate = v
|
|
146
|
+
maxWeight = weight
|
|
147
|
+
minDegree = degree
|
|
148
|
+
|
|
149
|
+
# key
|
|
150
|
+
return candidate
|
|
151
|
+
|
|
152
|
+
def relocateTuple(self, partition_scheme: PartitionScheme, proeminent_tuple: int, underloadPartition: int, overloadPartition: int):
|
|
153
|
+
"""
|
|
154
|
+
Thực hiện các bước sau:
|
|
155
|
+
Chuyển proeminent_tuple sang underloadPartition.
|
|
156
|
+
Di chuyển hàng xóm lân cận đi theo (di chuyển cả nhóm).
|
|
157
|
+
Giới hạn độ tìm kiếm = 2 bước nhảy.
|
|
158
|
+
|
|
159
|
+
"""
|
|
160
|
+
loads = self.getLoads(partition_scheme)
|
|
161
|
+
total_heat = sum(
|
|
162
|
+
partition_scheme.heat_graph.nodes[v]["weight"]
|
|
163
|
+
for v in partition_scheme.heat_graph.nodes
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
avgWorkload = total_heat / len(partition_scheme.partitions)
|
|
167
|
+
|
|
168
|
+
maxLoad = (1 + self.workload_factor) * avgWorkload
|
|
169
|
+
vertexWeight = partition_scheme.heat_graph.nodes[proeminent_tuple]["weight"]
|
|
170
|
+
|
|
171
|
+
"""
|
|
172
|
+
Kiểm tra workload của partition đích sau khi nhận vertex.
|
|
173
|
+
Nếu vượt quá maxload thì sẽ không di chuyển
|
|
174
|
+
"""
|
|
175
|
+
if loads[underloadPartition] + vertexWeight > maxLoad:
|
|
176
|
+
return False
|
|
177
|
+
|
|
178
|
+
# Chuyển proeminent_tuple sang underloadPartition.
|
|
179
|
+
partition_scheme.partitions[overloadPartition].remove(proeminent_tuple)
|
|
180
|
+
partition_scheme.partitions[underloadPartition].add(proeminent_tuple)
|
|
181
|
+
|
|
182
|
+
"""
|
|
183
|
+
TODO: Áp dụng hai bước dưới đây sẽ tối ưu thuật toán hơn. Nhưng vì
|
|
184
|
+
bài báo không nhắc đến phương pháp cụ thể, áp dụng vào thì sẽ gây ra phức tạp không
|
|
185
|
+
cần thiết. **Nchung là t ko biết làm.
|
|
186
|
+
"""
|
|
187
|
+
# Di chuyển hàng xóm lân cận đi theo (di chuyển cả nhóm).
|
|
188
|
+
# Giới hạn độ tìm kiếm = 2 bước nhảy.
|
|
189
|
+
return True
|
|
190
|
+
|
|
191
|
+
def run(self, recursion_depth: int = 20) -> PartitionScheme:
|
|
192
|
+
G = copy.deepcopy(self.partition_scheme)
|
|
193
|
+
|
|
194
|
+
if recursion_depth == 0 or not G.partitions:
|
|
195
|
+
return G
|
|
196
|
+
|
|
197
|
+
loads = self.getLoads(G)
|
|
198
|
+
# Partition có tổng workload thấp nhất
|
|
199
|
+
underloadedPartition = min(loads, key=lambda pid: (loads[pid], pid))
|
|
200
|
+
# Partition có tổng workload cao nhất
|
|
201
|
+
overloadedPartition = max(loads, key=lambda pid: (loads[pid], -pid))
|
|
202
|
+
avgWorkload = sum(loads.values()) / len(G.partitions)
|
|
203
|
+
|
|
204
|
+
n = ()
|
|
205
|
+
while loads[overloadedPartition] > (1 + self.workload_factor) * avgWorkload:
|
|
206
|
+
"""
|
|
207
|
+
Chọn một vertex phù hợp để di chuyển từ partition có workload cao
|
|
208
|
+
nhất sang thấp nhất.
|
|
209
|
+
"""
|
|
210
|
+
n = self.mostProeminentTuple(G, overloadedPartition)
|
|
211
|
+
if n is None:
|
|
212
|
+
break
|
|
213
|
+
|
|
214
|
+
relocate_bool = self.relocateTuple(G, n, underloadedPartition, overloadedPartition)
|
|
215
|
+
if not relocate_bool:
|
|
216
|
+
break
|
|
217
|
+
|
|
218
|
+
loads = self.getLoads(G)
|
|
219
|
+
underloadedPartition = min(loads, key=lambda pid: (loads[pid], pid))
|
|
220
|
+
overloadedPartition = max(loads, key=lambda pid: (loads[pid], -pid))
|
|
221
|
+
avgWorkload = sum(loads.values()) / len(G.partitions)
|
|
222
|
+
|
|
223
|
+
loads = self.getLoads(G)
|
|
224
|
+
# Key
|
|
225
|
+
underloadedPartition = min(loads, key=lambda pid: (loads[pid], pid))
|
|
226
|
+
overloadedPartition = max(loads, key=lambda pid: (loads[pid], -pid))
|
|
227
|
+
|
|
228
|
+
if (loads[overloadedPartition] > (1 + self.workload_factor) * avgWorkload or loads[underloadedPartition] < (1-self.workload_factor) * avgWorkload):
|
|
229
|
+
G = PartitioningUnbalancedGraph(G, self.site_num, self.workload_factor).run(recursion_depth=recursion_depth-1)
|
|
230
|
+
|
|
231
|
+
return G
|
|
232
|
+
|
|
233
|
+
class PartitioningMinimumCutGraph(): # Algorithm 4
|
|
234
|
+
"""
|
|
235
|
+
Giảm giao dịch phân tán.
|
|
236
|
+
"""
|
|
237
|
+
def __init__(self, partition_scheme: PartitionScheme, workload_factor: float, depth_search: int = 2):
|
|
238
|
+
self.partition_scheme = partition_scheme
|
|
239
|
+
self.workload_factor = workload_factor
|
|
240
|
+
self.depth_search = depth_search
|
|
241
|
+
|
|
242
|
+
def criticalEdges(self, partition_scheme: PartitionScheme) -> list[tuple[int, int, float]]:
|
|
243
|
+
"""
|
|
244
|
+
Trả về danh sách các cạnh nối giữa hai partition khác nhau.
|
|
245
|
+
return (u, v, weight)
|
|
246
|
+
VD: critical_edges = [
|
|
247
|
+
(1, 5, 10),
|
|
248
|
+
(2, 6, 7),
|
|
249
|
+
(3, 8, 4)
|
|
250
|
+
]
|
|
251
|
+
"""
|
|
252
|
+
vertex_partitions = {}
|
|
253
|
+
|
|
254
|
+
for pid, vertices in partition_scheme.partitions.items():
|
|
255
|
+
for vertex in vertices:
|
|
256
|
+
if vertex not in vertex_partitions:
|
|
257
|
+
vertex_partitions[vertex] = set()
|
|
258
|
+
|
|
259
|
+
vertex_partitions[vertex].add(pid)
|
|
260
|
+
|
|
261
|
+
critical_edges = []
|
|
262
|
+
|
|
263
|
+
for u, v, data in partition_scheme.heat_graph.edges(data=True):
|
|
264
|
+
weight = float(data["weight"])
|
|
265
|
+
if weight <= 0:
|
|
266
|
+
continue
|
|
267
|
+
common_partitions = vertex_partitions[u] & vertex_partitions[v]
|
|
268
|
+
|
|
269
|
+
if not common_partitions:
|
|
270
|
+
critical_edges.append((u, v, weight))
|
|
271
|
+
|
|
272
|
+
return critical_edges
|
|
273
|
+
|
|
274
|
+
def hottestEdge(self, criticalEdges: list[tuple[int, int, float]]) -> tuple[int, int, float]:
|
|
275
|
+
"""Chỉ trả về một cạnh có weight lớn nhất duy nhất."""
|
|
276
|
+
return max(criticalEdges, key=lambda edge: edge[2]) # (u, v, w) -> trả cạnh có w lớn nhất
|
|
277
|
+
|
|
278
|
+
def migrateTuple(self, partition_scheme: PartitionScheme, vertices: list[int], origin_partition: int, target_partition: int) -> PartitionScheme:
|
|
279
|
+
for vertex in vertices:
|
|
280
|
+
partition_scheme.partitions[origin_partition].remove(vertex)
|
|
281
|
+
partition_scheme.partitions[target_partition].add(vertex)
|
|
282
|
+
return partition_scheme
|
|
283
|
+
|
|
284
|
+
def checkBalance(self, partition_scheme: PartitionScheme) -> bool:
|
|
285
|
+
"""
|
|
286
|
+
Kiểm tra Equation 1:
|
|
287
|
+
load(partition) <= (1 + alpha) * average_load
|
|
288
|
+
với mọi partition.
|
|
289
|
+
"""
|
|
290
|
+
graph = partition_scheme.heat_graph
|
|
291
|
+
partitions = partition_scheme.partitions
|
|
292
|
+
k = len(partitions)
|
|
293
|
+
|
|
294
|
+
if k == 0:
|
|
295
|
+
return False
|
|
296
|
+
|
|
297
|
+
# Tổng tải của tất cả đỉnh trong đồ thị
|
|
298
|
+
total_load = sum(
|
|
299
|
+
graph.nodes[v]["weight"]
|
|
300
|
+
for v in graph.nodes
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
average_load = total_load / k
|
|
304
|
+
upper_bound = (1 + self.workload_factor) * average_load
|
|
305
|
+
|
|
306
|
+
for vertices in partitions.values():
|
|
307
|
+
partition_load = sum(
|
|
308
|
+
graph.nodes[v]["weight"]
|
|
309
|
+
for v in vertices
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
if partition_load > upper_bound:
|
|
313
|
+
return False
|
|
314
|
+
|
|
315
|
+
return True
|
|
316
|
+
|
|
317
|
+
def calculateMinimumCut(self, partition_scheme: PartitionScheme) -> float:
|
|
318
|
+
"""Trả về tổng trọng số các cạnh nối giữa các partition."""
|
|
319
|
+
critical_edges = self.criticalEdges(partition_scheme)
|
|
320
|
+
total_weight = 0.0
|
|
321
|
+
|
|
322
|
+
for u, v, weight in critical_edges:
|
|
323
|
+
total_weight += weight
|
|
324
|
+
|
|
325
|
+
return total_weight
|
|
326
|
+
|
|
327
|
+
def updateMinCut(self, partition_scheme: PartitionScheme, vertex_groups, origin_partition: int, target_partition: int):
|
|
328
|
+
"""
|
|
329
|
+
Trả về:
|
|
330
|
+
minimal_cut -> float: tổng trọng số các cạnh nối giữa những partition khác nhau, nhỏ nhất.
|
|
331
|
+
best_vertices -> list[int]: danh sách các đỉnh có cut nhỏ nhất.
|
|
332
|
+
"""
|
|
333
|
+
minimal_cut = float("inf")
|
|
334
|
+
best_vertices = []
|
|
335
|
+
source_vertices = partition_scheme.partitions[origin_partition]
|
|
336
|
+
for vertices in vertex_groups:
|
|
337
|
+
vertices = list(vertices)
|
|
338
|
+
if not set(vertices).issubset(source_vertices):
|
|
339
|
+
continue
|
|
340
|
+
trial_scheme = copy.deepcopy(partition_scheme)
|
|
341
|
+
trial_scheme = self.migrateTuple(trial_scheme, vertices, origin_partition, target_partition)
|
|
342
|
+
|
|
343
|
+
# Kiểm tra cân bằng tải của trial_scheme
|
|
344
|
+
if not self.checkBalance(trial_scheme):
|
|
345
|
+
continue
|
|
346
|
+
|
|
347
|
+
# Nếu hợp lệ, tính tổng trọng số cạnh cắt rồi so sánh với kết quả tốt nhất
|
|
348
|
+
cut = self.calculateMinimumCut(trial_scheme)
|
|
349
|
+
if cut < minimal_cut:
|
|
350
|
+
minimal_cut = cut
|
|
351
|
+
best_vertices = vertices
|
|
352
|
+
|
|
353
|
+
return minimal_cut, best_vertices
|
|
354
|
+
|
|
355
|
+
def explorePossibility(self, partition_scheme: PartitionScheme, vertex: int, origin_partition: int, target_partition: int):
|
|
356
|
+
"""
|
|
357
|
+
Đầu vào:
|
|
358
|
+
vertex: ID đỉnh cần chuyển.
|
|
359
|
+
target_partition: ID partition đích.
|
|
360
|
+
|
|
361
|
+
Đầu ra:
|
|
362
|
+
Cạnh nhỏ nhất sau khi di chuyển đỉnh.
|
|
363
|
+
|
|
364
|
+
Các bước làm:
|
|
365
|
+
Di chuyển thử vertex vào target partition (tối đa 2 hops).
|
|
366
|
+
Kiểm tra có vi phạm phương trình 1 (Kiểm soát cân bằng tải giữa partition).
|
|
367
|
+
Tính cạnh nhỏ nhất.
|
|
368
|
+
Lặp lại đến khi tìm được cạnh nhỏ nhất.
|
|
369
|
+
|
|
370
|
+
Note:
|
|
371
|
+
Chúng ta phải xét cả neighbor cách 1 cạnh và 2 cạnh.
|
|
372
|
+
one_hop = set()
|
|
373
|
+
Tính min
|
|
374
|
+
two_hop = set()
|
|
375
|
+
Tính min
|
|
376
|
+
|
|
377
|
+
Code sẽ lặp lại 1 chút cho dễ debug chứ giờ nhiều hàm quá r.
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
current_cut = self.calculateMinimumCut(partition_scheme)
|
|
381
|
+
minimal_cut = float('inf') # Cạnh nhỏ nhất sau khi di chuyển vertex.
|
|
382
|
+
best_vertices = []
|
|
383
|
+
heat_graph = partition_scheme.heat_graph
|
|
384
|
+
source_vertices = partition_scheme.partitions[origin_partition]
|
|
385
|
+
# Kiểm tra xem các đỉnh đều thuộc original partition hay không.
|
|
386
|
+
if vertex not in source_vertices or origin_partition == target_partition:
|
|
387
|
+
return -1, []
|
|
388
|
+
"""
|
|
389
|
+
Tính min cho 1 đỉnh.
|
|
390
|
+
"""
|
|
391
|
+
minimal_cut, best_vertices = self.updateMinCut(partition_scheme, [[vertex]], origin_partition, target_partition)
|
|
392
|
+
|
|
393
|
+
"""
|
|
394
|
+
Tính min cho 1 hop - 2 đỉnh.
|
|
395
|
+
"""
|
|
396
|
+
# Các đỉnh cách vertex đúng 1 cạnh
|
|
397
|
+
one_hop = set(heat_graph.neighbors(vertex)) - {vertex}
|
|
398
|
+
if self.depth_search >= 1:
|
|
399
|
+
one_hop_groups = [[vertex, neighbor] for neighbor in one_hop]
|
|
400
|
+
cut, vertices = self.updateMinCut(partition_scheme, one_hop_groups, origin_partition, target_partition)
|
|
401
|
+
if cut < minimal_cut:
|
|
402
|
+
minimal_cut = cut
|
|
403
|
+
best_vertices = vertices
|
|
404
|
+
"""
|
|
405
|
+
Tính min cho 2 hop - 3 đỉnh.
|
|
406
|
+
"""
|
|
407
|
+
if self.depth_search >= 2:
|
|
408
|
+
# Các đỉnh cách vertex đúng 2 cạnh
|
|
409
|
+
two_hops = set()
|
|
410
|
+
for neighbor in one_hop:
|
|
411
|
+
for next_neighbor in heat_graph.neighbors(neighbor):
|
|
412
|
+
if next_neighbor != vertex and next_neighbor not in one_hop:
|
|
413
|
+
two_hops.add((vertex, neighbor, next_neighbor))
|
|
414
|
+
# Chuyển các đỉnh trong phương án sang target_partition
|
|
415
|
+
cut, vertices = self.updateMinCut(partition_scheme, two_hops, origin_partition, target_partition)
|
|
416
|
+
if cut < minimal_cut:
|
|
417
|
+
minimal_cut = cut
|
|
418
|
+
best_vertices = vertices
|
|
419
|
+
# Nếu ko có trường hợp nào hợp lệ thì trả về -1
|
|
420
|
+
if minimal_cut == float("inf") or minimal_cut >= current_cut:
|
|
421
|
+
return -1, []
|
|
422
|
+
return minimal_cut, best_vertices
|
|
423
|
+
|
|
424
|
+
def checkMinPartition(self, original_scheme: PartitionScheme, new_scheme: PartitionScheme) -> bool:
|
|
425
|
+
"""Kiểm tra phương án mới có giảm tổng trọng số cạnh cắt không. (đây là equation 2)"""
|
|
426
|
+
original_cut = self.calculateMinimumCut(original_scheme)
|
|
427
|
+
new_cut = self.calculateMinimumCut(new_scheme)
|
|
428
|
+
return new_cut < original_cut
|
|
429
|
+
|
|
430
|
+
def createReplication(self, partition_scheme: PartitionScheme, vertex: int, target_partition: int):
|
|
431
|
+
"""Thêm vertex vào partition đích, giữ nguyên bản gốc."""
|
|
432
|
+
partition_scheme.partitions[target_partition].add(vertex)
|
|
433
|
+
return partition_scheme
|
|
434
|
+
|
|
435
|
+
def explorePossibilititesReplication(self, partition_scheme: PartitionScheme, vertex_one: int, vertex_two: int, partition_one: int, partition_two: int) -> PartitionScheme:
|
|
436
|
+
"""
|
|
437
|
+
Tạo bản sao dữ liệu nếu hàm explorePossibility không hợp lệ (trả về -1).
|
|
438
|
+
|
|
439
|
+
TODO: Tạo bản sao cả node hàng xóm. Quá khó nên h ko làm đâu.
|
|
440
|
+
|
|
441
|
+
Đầu vào:
|
|
442
|
+
vertex_one thuộc partition_one.
|
|
443
|
+
vertex_two thuộc partition_two.
|
|
444
|
+
|
|
445
|
+
Bước làm:
|
|
446
|
+
Tạo bảng sao vertex_one ở partition_two.
|
|
447
|
+
Kiểm tra thỏa phương trình 1 và 2.
|
|
448
|
+
Nếu thỏa, trả về partition_scheme và dừng.
|
|
449
|
+
Tạo bảng sao vertex_one ở partition_two.
|
|
450
|
+
Kiểm tra thỏa phương trình 1 và 2.
|
|
451
|
+
Nếu thỏa, trả về partition_scheme và dừng.
|
|
452
|
+
|
|
453
|
+
Note:
|
|
454
|
+
Hàm kiểm tra equation 1: checkBalance
|
|
455
|
+
Hàm kiểm tra equation 2: checkMinPartition
|
|
456
|
+
"""
|
|
457
|
+
candidates = [
|
|
458
|
+
(vertex_one, partition_two),
|
|
459
|
+
(vertex_two, partition_one)
|
|
460
|
+
]
|
|
461
|
+
|
|
462
|
+
for vertex, target_partition in candidates:
|
|
463
|
+
trial_scheme = copy.deepcopy(partition_scheme)
|
|
464
|
+
|
|
465
|
+
self.createReplication(
|
|
466
|
+
trial_scheme,
|
|
467
|
+
vertex,
|
|
468
|
+
target_partition
|
|
469
|
+
)
|
|
470
|
+
if not self.checkBalance(trial_scheme):
|
|
471
|
+
continue
|
|
472
|
+
if not self.checkMinPartition(partition_scheme, trial_scheme):
|
|
473
|
+
continue
|
|
474
|
+
return trial_scheme
|
|
475
|
+
|
|
476
|
+
# Không tìm được phương án hợp lệ
|
|
477
|
+
return partition_scheme
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def run(self) -> PartitionScheme:
|
|
481
|
+
# Xếp trọng số từ nhỏ đến lớn.
|
|
482
|
+
G = copy.deepcopy(self.partition_scheme)
|
|
483
|
+
criticalEdges = sorted(
|
|
484
|
+
self.criticalEdges(G),
|
|
485
|
+
key=lambda edge: edge[2],
|
|
486
|
+
reverse=True
|
|
487
|
+
)
|
|
488
|
+
for ce in criticalEdges:
|
|
489
|
+
u, v, weight = ce
|
|
490
|
+
|
|
491
|
+
origin_partition = next(
|
|
492
|
+
pid for pid, vertices in G.partitions.items()
|
|
493
|
+
if u in vertices
|
|
494
|
+
)
|
|
495
|
+
target_partition = next(
|
|
496
|
+
pid for pid, vertices in G.partitions.items()
|
|
497
|
+
if v in vertices
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
chargeTestOrigin, origin_vertices = self.explorePossibility(G, u, origin_partition, target_partition)
|
|
501
|
+
chargeTestTarget, target_vertices = self.explorePossibility(G, v, target_partition, origin_partition)
|
|
502
|
+
|
|
503
|
+
if chargeTestOrigin < 0 and chargeTestTarget < 0:
|
|
504
|
+
G = self.explorePossibilititesReplication(G, u, v, origin_partition, target_partition)
|
|
505
|
+
continue
|
|
506
|
+
|
|
507
|
+
# Phải tính current cut để xem có tối ưu trước khi di chuyển không?
|
|
508
|
+
current_cut = self.calculateMinimumCut(G)
|
|
509
|
+
if chargeTestTarget >= 0 and (chargeTestOrigin < 0 or chargeTestOrigin > chargeTestTarget):
|
|
510
|
+
if chargeTestTarget < current_cut:
|
|
511
|
+
G = self.migrateTuple(G, target_vertices, target_partition, origin_partition)
|
|
512
|
+
elif chargeTestOrigin >= 0:
|
|
513
|
+
if chargeTestOrigin < current_cut:
|
|
514
|
+
G = self.migrateTuple(G, origin_vertices, origin_partition, target_partition)
|
|
515
|
+
|
|
516
|
+
return G # type: ignore
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
class Partitioning(): # Algorithm 2
|
|
521
|
+
def __init__(self, partition_scheme: PartitionScheme, site_num: int, workload_factor: float, distributed_percentage: float = 0.02, use_bpg: bool = False):
|
|
522
|
+
self.partition_scheme = partition_scheme
|
|
523
|
+
self.site_num = site_num
|
|
524
|
+
self.workload_factor = workload_factor
|
|
525
|
+
self.distributed_percentage = distributed_percentage
|
|
526
|
+
self.use_bpg = use_bpg
|
|
527
|
+
|
|
528
|
+
def getLoads(self, partition_scheme: PartitionScheme):
|
|
529
|
+
loads = [
|
|
530
|
+
sum(partition_scheme.heat_graph.nodes[v]["weight"] for v in vertices)
|
|
531
|
+
for vertices in partition_scheme.partitions.values()
|
|
532
|
+
]
|
|
533
|
+
return loads
|
|
534
|
+
|
|
535
|
+
def getCriticalEdgesLoad(self, partition_scheme: PartitionScheme):
|
|
536
|
+
calculator = PartitioningMinimumCutGraph(
|
|
537
|
+
partition_scheme,
|
|
538
|
+
self.workload_factor
|
|
539
|
+
)
|
|
540
|
+
return calculator.calculateMinimumCut(partition_scheme)
|
|
541
|
+
|
|
542
|
+
def run(self, recursion_depth: int = 20) -> PartitionScheme:
|
|
543
|
+
current_scheme = self.partition_scheme
|
|
544
|
+
G = copy.deepcopy(current_scheme)
|
|
545
|
+
|
|
546
|
+
if recursion_depth == 0:
|
|
547
|
+
return G
|
|
548
|
+
|
|
549
|
+
loads = self.getLoads(G)
|
|
550
|
+
minPartition = min(loads)
|
|
551
|
+
maxPartition = max(loads)
|
|
552
|
+
avgWorkload = sum(loads) / len(self.partition_scheme.partitions)
|
|
553
|
+
|
|
554
|
+
if (maxPartition > (1 + self.workload_factor) * avgWorkload) or (minPartition < (1 - self.workload_factor) * avgWorkload):
|
|
555
|
+
G = PartitioningUnbalancedGraph(G, self.site_num, self.workload_factor).run()
|
|
556
|
+
|
|
557
|
+
"""
|
|
558
|
+
Tổng trọng số weight của tất cả các cạnh nối giữa 2
|
|
559
|
+
partition khác nhau trong Heat Graph.
|
|
560
|
+
"""
|
|
561
|
+
loads = self.getLoads(G)
|
|
562
|
+
criticalEdgesLoad = self.getCriticalEdgesLoad(G)
|
|
563
|
+
if criticalEdgesLoad > self.distributed_percentage * sum(loads):
|
|
564
|
+
G = PartitioningMinimumCutGraph(G, self.workload_factor).run() # algorithm 4
|
|
565
|
+
|
|
566
|
+
if len(G.partitions) == 1:
|
|
567
|
+
if self.use_bpg:
|
|
568
|
+
G = bpg_partition(G, site_num=self.site_num)
|
|
569
|
+
else:
|
|
570
|
+
G = FirstPartitioning(G, self.site_num).run()
|
|
571
|
+
|
|
572
|
+
if G.partitions != self.partition_scheme.partitions or len(G.partitions) != self.site_num:
|
|
573
|
+
G = Partitioning(G, self.site_num, self.workload_factor, self.distributed_percentage, self.use_bpg).run(recursion_depth=recursion_depth - 1)
|
|
574
|
+
|
|
575
|
+
return G
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bpg-partitioning
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: BPG-Based Initial Partitioning for Hybrid Data Partitioning in NewSQL Databases
|
|
5
|
+
Author-email: verse91 <versedev.store@proton.me>
|
|
6
|
+
Project-URL: Homepage, https://github.com/versenilvis/bpg-hybrid-partitioning
|
|
7
|
+
Project-URL: Repository, https://github.com/versenilvis/bpg-hybrid-partitioning.git
|
|
8
|
+
Keywords: newsql,distributed-database,partitioning,bpg,normalized-cut
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Topic :: Database
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/plain
|
|
17
|
+
Requires-Dist: Faker
|
|
18
|
+
Requires-Dist: networkx
|
|
19
|
+
Requires-Dist: numpy
|
|
20
|
+
Requires-Dist: prettytable
|
|
21
|
+
Requires-Dist: scipy
|
|
22
|
+
|
|
23
|
+
BPG-Based Initial Partitioning for Hybrid Data Partitioning in NewSQL Databases
|
|
24
|
+
Tạm dịch: Thay thế thuật toán khởi tạo bằng BPG cho phương pháp phân mảnh dữ liệu hybrid trong hệ cơ sở dữ liệu NewSQL
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
Dự án hiện thực hóa thuật toán Bregman Proximal Gradient (BPG) cho bài toán phân bổ dữ liệu khởi tạo trong các phương án phân mảnh hybrid cho hệ cơ sở dữ liệu phân tán NewSQL
|
|
28
|
+
|
|
29
|
+
Mã nguồn này thay thế thuật toán phân mảnh khởi đầu tham lam (Algorithm 5 của Schreiner và cộng sự) bằng mô hình tối ưu Normalized Cut liên tục được giải bằng BPG (Niu và cộng sự) nhằm giảm thiểu chi phí nghẽn mạng do giao dịch phân tán và cân bằng tải công việc giữa các máy chủ
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
[1] P. Niu, B. Ren, H. Wu, and X. Yao, "Modeling and optimization for massive data allocation in database," arXiv:2605.31002, Jun 2026
|
|
34
|
+
|
|
35
|
+
[2] G. A. Schreiner, R. de Santiago, D. Duarte, and R. dos Santos Mello, "An autonomous hybrid data partitioning approach for NewSQL databases," Journal of the Brazilian Computer Society, vol. 32, no. 1, Art. no. 1, 2026, doi: 10.5753/jbcs.2026.5684
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
bpg_partitioning/__init__.py,sha256=uMrNQfiTfynZTJb31Wk4MSyIFtzdSmJlWXko7aa1fZc,993
|
|
2
|
+
bpg_partitioning/bpg.py,sha256=aUozhQWG64DpjRr6DVi8Oc012bpVpwx0_P9JH3iShz0,6212
|
|
3
|
+
bpg_partitioning/config.py,sha256=NtoQABWuuCXblsaK1T8M1n2DePwWhRVM30Id-5zfjG4,2301
|
|
4
|
+
bpg_partitioning/main.py,sha256=-dQY5lOiFN2GELyUuKonx9-ln0wdn-rDDxkA6wORgV8,8583
|
|
5
|
+
bpg_partitioning/partition_scheme.py,sha256=fxGWIcKYWzEiKyZAg0eKT3zl3jB94a3s63LpMhHNb68,7938
|
|
6
|
+
bpg_partitioning/partitioning.py,sha256=SLzij8g3lzZdR_DvnS83Tn3NVnCwDx4cVTnkg6ohX-Y,24601
|
|
7
|
+
bpg_partitioning-0.1.0.dist-info/METADATA,sha256=qfV3xcLa3LcPH0D3Uaxr0HWZeRkQTGdbVIsCySy2-_c,2095
|
|
8
|
+
bpg_partitioning-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
9
|
+
bpg_partitioning-0.1.0.dist-info/entry_points.txt,sha256=klK168cgQ4YANoco7nRca_ZvSsQBZL3BcwSM4M9FpKU,64
|
|
10
|
+
bpg_partitioning-0.1.0.dist-info/top_level.txt,sha256=4cuutn5-1zRTjsSwTFizcyhqt5_yE4ys_ticO04vU9k,17
|
|
11
|
+
bpg_partitioning-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
bpg_partitioning
|