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.
@@ -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
+
@@ -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)
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ bpg-partitioning = bpg_partitioning.main:main
@@ -0,0 +1 @@
1
+ bpg_partitioning