exonware-xwnode 0.0.1.12__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.
- exonware/__init__.py +14 -0
- exonware/xwnode/__init__.py +127 -0
- exonware/xwnode/base.py +676 -0
- exonware/xwnode/config.py +178 -0
- exonware/xwnode/contracts.py +730 -0
- exonware/xwnode/errors.py +503 -0
- exonware/xwnode/facade.py +460 -0
- exonware/xwnode/strategies/__init__.py +158 -0
- exonware/xwnode/strategies/advisor.py +463 -0
- exonware/xwnode/strategies/edges/__init__.py +32 -0
- exonware/xwnode/strategies/edges/adj_list.py +227 -0
- exonware/xwnode/strategies/edges/adj_matrix.py +391 -0
- exonware/xwnode/strategies/edges/base.py +169 -0
- exonware/xwnode/strategies/flyweight.py +328 -0
- exonware/xwnode/strategies/impls/__init__.py +13 -0
- exonware/xwnode/strategies/impls/_base_edge.py +403 -0
- exonware/xwnode/strategies/impls/_base_node.py +307 -0
- exonware/xwnode/strategies/impls/edge_adj_list.py +353 -0
- exonware/xwnode/strategies/impls/edge_adj_matrix.py +445 -0
- exonware/xwnode/strategies/impls/edge_bidir_wrapper.py +455 -0
- exonware/xwnode/strategies/impls/edge_block_adj_matrix.py +539 -0
- exonware/xwnode/strategies/impls/edge_coo.py +533 -0
- exonware/xwnode/strategies/impls/edge_csc.py +447 -0
- exonware/xwnode/strategies/impls/edge_csr.py +492 -0
- exonware/xwnode/strategies/impls/edge_dynamic_adj_list.py +503 -0
- exonware/xwnode/strategies/impls/edge_flow_network.py +555 -0
- exonware/xwnode/strategies/impls/edge_hyperedge_set.py +516 -0
- exonware/xwnode/strategies/impls/edge_neural_graph.py +650 -0
- exonware/xwnode/strategies/impls/edge_octree.py +574 -0
- exonware/xwnode/strategies/impls/edge_property_store.py +655 -0
- exonware/xwnode/strategies/impls/edge_quadtree.py +519 -0
- exonware/xwnode/strategies/impls/edge_rtree.py +820 -0
- exonware/xwnode/strategies/impls/edge_temporal_edgeset.py +558 -0
- exonware/xwnode/strategies/impls/edge_tree_graph_basic.py +271 -0
- exonware/xwnode/strategies/impls/edge_weighted_graph.py +411 -0
- exonware/xwnode/strategies/manager.py +775 -0
- exonware/xwnode/strategies/metrics.py +538 -0
- exonware/xwnode/strategies/migration.py +432 -0
- exonware/xwnode/strategies/nodes/__init__.py +50 -0
- exonware/xwnode/strategies/nodes/_base_node.py +307 -0
- exonware/xwnode/strategies/nodes/adjacency_list.py +267 -0
- exonware/xwnode/strategies/nodes/aho_corasick.py +345 -0
- exonware/xwnode/strategies/nodes/array_list.py +209 -0
- exonware/xwnode/strategies/nodes/base.py +247 -0
- exonware/xwnode/strategies/nodes/deque.py +200 -0
- exonware/xwnode/strategies/nodes/hash_map.py +135 -0
- exonware/xwnode/strategies/nodes/heap.py +307 -0
- exonware/xwnode/strategies/nodes/linked_list.py +232 -0
- exonware/xwnode/strategies/nodes/node_aho_corasick.py +520 -0
- exonware/xwnode/strategies/nodes/node_array_list.py +175 -0
- exonware/xwnode/strategies/nodes/node_avl_tree.py +371 -0
- exonware/xwnode/strategies/nodes/node_b_plus_tree.py +542 -0
- exonware/xwnode/strategies/nodes/node_bitmap.py +420 -0
- exonware/xwnode/strategies/nodes/node_bitset_dynamic.py +513 -0
- exonware/xwnode/strategies/nodes/node_bloom_filter.py +347 -0
- exonware/xwnode/strategies/nodes/node_btree.py +357 -0
- exonware/xwnode/strategies/nodes/node_count_min_sketch.py +470 -0
- exonware/xwnode/strategies/nodes/node_cow_tree.py +473 -0
- exonware/xwnode/strategies/nodes/node_cuckoo_hash.py +392 -0
- exonware/xwnode/strategies/nodes/node_fenwick_tree.py +301 -0
- exonware/xwnode/strategies/nodes/node_hash_map.py +269 -0
- exonware/xwnode/strategies/nodes/node_heap.py +191 -0
- exonware/xwnode/strategies/nodes/node_hyperloglog.py +407 -0
- exonware/xwnode/strategies/nodes/node_linked_list.py +409 -0
- exonware/xwnode/strategies/nodes/node_lsm_tree.py +400 -0
- exonware/xwnode/strategies/nodes/node_ordered_map.py +390 -0
- exonware/xwnode/strategies/nodes/node_ordered_map_balanced.py +565 -0
- exonware/xwnode/strategies/nodes/node_patricia.py +512 -0
- exonware/xwnode/strategies/nodes/node_persistent_tree.py +378 -0
- exonware/xwnode/strategies/nodes/node_radix_trie.py +452 -0
- exonware/xwnode/strategies/nodes/node_red_black_tree.py +497 -0
- exonware/xwnode/strategies/nodes/node_roaring_bitmap.py +570 -0
- exonware/xwnode/strategies/nodes/node_segment_tree.py +289 -0
- exonware/xwnode/strategies/nodes/node_set_hash.py +354 -0
- exonware/xwnode/strategies/nodes/node_set_tree.py +480 -0
- exonware/xwnode/strategies/nodes/node_skip_list.py +316 -0
- exonware/xwnode/strategies/nodes/node_splay_tree.py +393 -0
- exonware/xwnode/strategies/nodes/node_suffix_array.py +487 -0
- exonware/xwnode/strategies/nodes/node_treap.py +387 -0
- exonware/xwnode/strategies/nodes/node_tree_graph_hybrid.py +1434 -0
- exonware/xwnode/strategies/nodes/node_trie.py +252 -0
- exonware/xwnode/strategies/nodes/node_union_find.py +187 -0
- exonware/xwnode/strategies/nodes/node_xdata_optimized.py +369 -0
- exonware/xwnode/strategies/nodes/priority_queue.py +209 -0
- exonware/xwnode/strategies/nodes/queue.py +161 -0
- exonware/xwnode/strategies/nodes/sparse_matrix.py +206 -0
- exonware/xwnode/strategies/nodes/stack.py +152 -0
- exonware/xwnode/strategies/nodes/trie.py +274 -0
- exonware/xwnode/strategies/nodes/union_find.py +283 -0
- exonware/xwnode/strategies/pattern_detector.py +603 -0
- exonware/xwnode/strategies/performance_monitor.py +487 -0
- exonware/xwnode/strategies/queries/__init__.py +24 -0
- exonware/xwnode/strategies/queries/base.py +236 -0
- exonware/xwnode/strategies/queries/cql.py +201 -0
- exonware/xwnode/strategies/queries/cypher.py +181 -0
- exonware/xwnode/strategies/queries/datalog.py +70 -0
- exonware/xwnode/strategies/queries/elastic_dsl.py +70 -0
- exonware/xwnode/strategies/queries/eql.py +70 -0
- exonware/xwnode/strategies/queries/flux.py +70 -0
- exonware/xwnode/strategies/queries/gql.py +70 -0
- exonware/xwnode/strategies/queries/graphql.py +240 -0
- exonware/xwnode/strategies/queries/gremlin.py +181 -0
- exonware/xwnode/strategies/queries/hiveql.py +214 -0
- exonware/xwnode/strategies/queries/hql.py +70 -0
- exonware/xwnode/strategies/queries/jmespath.py +219 -0
- exonware/xwnode/strategies/queries/jq.py +66 -0
- exonware/xwnode/strategies/queries/json_query.py +66 -0
- exonware/xwnode/strategies/queries/jsoniq.py +248 -0
- exonware/xwnode/strategies/queries/kql.py +70 -0
- exonware/xwnode/strategies/queries/linq.py +238 -0
- exonware/xwnode/strategies/queries/logql.py +70 -0
- exonware/xwnode/strategies/queries/mql.py +68 -0
- exonware/xwnode/strategies/queries/n1ql.py +210 -0
- exonware/xwnode/strategies/queries/partiql.py +70 -0
- exonware/xwnode/strategies/queries/pig.py +215 -0
- exonware/xwnode/strategies/queries/promql.py +70 -0
- exonware/xwnode/strategies/queries/sparql.py +220 -0
- exonware/xwnode/strategies/queries/sql.py +275 -0
- exonware/xwnode/strategies/queries/xml_query.py +66 -0
- exonware/xwnode/strategies/queries/xpath.py +223 -0
- exonware/xwnode/strategies/queries/xquery.py +258 -0
- exonware/xwnode/strategies/queries/xwnode_executor.py +332 -0
- exonware/xwnode/strategies/queries/xwquery_strategy.py +424 -0
- exonware/xwnode/strategies/registry.py +604 -0
- exonware/xwnode/strategies/simple.py +273 -0
- exonware/xwnode/strategies/utils.py +532 -0
- exonware/xwnode/types.py +912 -0
- exonware/xwnode/version.py +78 -0
- exonware_xwnode-0.0.1.12.dist-info/METADATA +169 -0
- exonware_xwnode-0.0.1.12.dist-info/RECORD +132 -0
- exonware_xwnode-0.0.1.12.dist-info/WHEEL +4 -0
- exonware_xwnode-0.0.1.12.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,539 @@
|
|
1
|
+
"""
|
2
|
+
Block Adjacency Matrix Edge Strategy Implementation
|
3
|
+
|
4
|
+
This module implements the BLOCK_ADJ_MATRIX strategy for cache-friendly
|
5
|
+
dense graph operations using block-based matrix partitioning.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Any, Iterator, List, Dict, Optional, Tuple, Set
|
9
|
+
import math
|
10
|
+
from ._base_edge import aEdgeStrategy
|
11
|
+
from ...types import EdgeMode, EdgeTrait
|
12
|
+
|
13
|
+
|
14
|
+
class MatrixBlock:
|
15
|
+
"""A block in the block adjacency matrix."""
|
16
|
+
|
17
|
+
def __init__(self, block_size: int):
|
18
|
+
self.block_size = block_size
|
19
|
+
self.data: List[List[Any]] = [[None for _ in range(block_size)] for _ in range(block_size)]
|
20
|
+
self.edge_count = 0
|
21
|
+
self.is_dense = False
|
22
|
+
|
23
|
+
def set_edge(self, local_u: int, local_v: int, weight: Any = 1) -> bool:
|
24
|
+
"""Set edge in block. Returns True if new edge."""
|
25
|
+
old_value = self.data[local_u][local_v]
|
26
|
+
self.data[local_u][local_v] = weight
|
27
|
+
|
28
|
+
if old_value is None and weight is not None:
|
29
|
+
self.edge_count += 1
|
30
|
+
self.is_dense = self.edge_count > (self.block_size * self.block_size * 0.5)
|
31
|
+
return True
|
32
|
+
elif old_value is not None and weight is None:
|
33
|
+
self.edge_count -= 1
|
34
|
+
self.is_dense = self.edge_count > (self.block_size * self.block_size * 0.5)
|
35
|
+
return False
|
36
|
+
|
37
|
+
return False
|
38
|
+
|
39
|
+
def get_edge(self, local_u: int, local_v: int) -> Any:
|
40
|
+
"""Get edge weight from block."""
|
41
|
+
return self.data[local_u][local_v]
|
42
|
+
|
43
|
+
def has_edge(self, local_u: int, local_v: int) -> bool:
|
44
|
+
"""Check if edge exists in block."""
|
45
|
+
return self.data[local_u][local_v] is not None
|
46
|
+
|
47
|
+
def remove_edge(self, local_u: int, local_v: int) -> bool:
|
48
|
+
"""Remove edge from block. Returns True if edge existed."""
|
49
|
+
if self.data[local_u][local_v] is not None:
|
50
|
+
self.data[local_u][local_v] = None
|
51
|
+
self.edge_count -= 1
|
52
|
+
self.is_dense = self.edge_count > (self.block_size * self.block_size * 0.5)
|
53
|
+
return True
|
54
|
+
return False
|
55
|
+
|
56
|
+
def get_edges(self) -> List[Tuple[int, int, Any]]:
|
57
|
+
"""Get all edges in block as (local_u, local_v, weight)."""
|
58
|
+
edges = []
|
59
|
+
for u in range(self.block_size):
|
60
|
+
for v in range(self.block_size):
|
61
|
+
if self.data[u][v] is not None:
|
62
|
+
edges.append((u, v, self.data[u][v]))
|
63
|
+
return edges
|
64
|
+
|
65
|
+
|
66
|
+
class xBlockAdjMatrixStrategy(aEdgeStrategy):
|
67
|
+
"""
|
68
|
+
Block Adjacency Matrix edge strategy for cache-friendly dense operations.
|
69
|
+
|
70
|
+
Partitions large adjacency matrices into smaller blocks that fit in cache,
|
71
|
+
optimizing for spatial locality and dense graph operations.
|
72
|
+
"""
|
73
|
+
|
74
|
+
def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
|
75
|
+
"""Initialize the Block Adjacency Matrix strategy."""
|
76
|
+
super().__init__(EdgeMode.BLOCK_ADJ_MATRIX, traits, **options)
|
77
|
+
|
78
|
+
self.block_size = options.get('block_size', 64) # Cache-friendly size
|
79
|
+
self.auto_optimize = options.get('auto_optimize', True)
|
80
|
+
self.cache_blocks = options.get('cache_blocks', True)
|
81
|
+
|
82
|
+
# Core block matrix storage
|
83
|
+
self._blocks: Dict[Tuple[int, int], MatrixBlock] = {}
|
84
|
+
self._vertex_to_id: Dict[str, int] = {}
|
85
|
+
self._id_to_vertex: Dict[int, str] = {}
|
86
|
+
self._next_id = 0
|
87
|
+
self._edge_count = 0
|
88
|
+
|
89
|
+
# Block cache for frequently accessed blocks
|
90
|
+
self._block_cache: Dict[Tuple[int, int], MatrixBlock] = {}
|
91
|
+
self._cache_size = options.get('cache_size', 16)
|
92
|
+
self._cache_hits = 0
|
93
|
+
self._cache_misses = 0
|
94
|
+
|
95
|
+
# Statistics
|
96
|
+
self._total_blocks = 0
|
97
|
+
self._dense_blocks = 0
|
98
|
+
self._sparse_blocks = 0
|
99
|
+
self._matrix_accesses = 0
|
100
|
+
|
101
|
+
def get_supported_traits(self) -> EdgeTrait:
|
102
|
+
"""Get the traits supported by the block adjacency matrix strategy."""
|
103
|
+
return (EdgeTrait.DENSE | EdgeTrait.WEIGHTED | EdgeTrait.DIRECTED | EdgeTrait.CACHE_FRIENDLY)
|
104
|
+
|
105
|
+
def _get_vertex_id(self, vertex: str) -> int:
|
106
|
+
"""Get or create vertex ID."""
|
107
|
+
if vertex not in self._vertex_to_id:
|
108
|
+
self._vertex_to_id[vertex] = self._next_id
|
109
|
+
self._id_to_vertex[self._next_id] = vertex
|
110
|
+
self._next_id += 1
|
111
|
+
return self._vertex_to_id[vertex]
|
112
|
+
|
113
|
+
def _get_block_coords(self, vertex_id: int) -> Tuple[int, int]:
|
114
|
+
"""Get block coordinates for vertex ID."""
|
115
|
+
return (vertex_id // self.block_size, vertex_id // self.block_size)
|
116
|
+
|
117
|
+
def _get_edge_block_coords(self, u_id: int, v_id: int) -> Tuple[int, int]:
|
118
|
+
"""Get block coordinates for edge (u, v)."""
|
119
|
+
return (u_id // self.block_size, v_id // self.block_size)
|
120
|
+
|
121
|
+
def _get_local_coords(self, vertex_id: int) -> Tuple[int, int]:
|
122
|
+
"""Get local coordinates within block."""
|
123
|
+
return (vertex_id % self.block_size, vertex_id % self.block_size)
|
124
|
+
|
125
|
+
def _get_edge_local_coords(self, u_id: int, v_id: int) -> Tuple[int, int]:
|
126
|
+
"""Get local coordinates for edge within block."""
|
127
|
+
return (u_id % self.block_size, v_id % self.block_size)
|
128
|
+
|
129
|
+
def _get_block(self, block_coords: Tuple[int, int], create: bool = True) -> Optional[MatrixBlock]:
|
130
|
+
"""Get block, optionally creating it."""
|
131
|
+
# Check cache first
|
132
|
+
if self.cache_blocks and block_coords in self._block_cache:
|
133
|
+
self._cache_hits += 1
|
134
|
+
return self._block_cache[block_coords]
|
135
|
+
|
136
|
+
# Check main storage
|
137
|
+
if block_coords in self._blocks:
|
138
|
+
block = self._blocks[block_coords]
|
139
|
+
|
140
|
+
# Add to cache
|
141
|
+
if self.cache_blocks:
|
142
|
+
self._update_cache(block_coords, block)
|
143
|
+
self._cache_misses += 1
|
144
|
+
|
145
|
+
return block
|
146
|
+
|
147
|
+
# Create new block if requested
|
148
|
+
if create:
|
149
|
+
block = MatrixBlock(self.block_size)
|
150
|
+
self._blocks[block_coords] = block
|
151
|
+
self._total_blocks += 1
|
152
|
+
|
153
|
+
if self.cache_blocks:
|
154
|
+
self._update_cache(block_coords, block)
|
155
|
+
|
156
|
+
return block
|
157
|
+
|
158
|
+
return None
|
159
|
+
|
160
|
+
def _update_cache(self, block_coords: Tuple[int, int], block: MatrixBlock) -> None:
|
161
|
+
"""Update block cache with LRU eviction."""
|
162
|
+
if len(self._block_cache) >= self._cache_size:
|
163
|
+
# Simple FIFO eviction (could be improved to LRU)
|
164
|
+
oldest_key = next(iter(self._block_cache))
|
165
|
+
del self._block_cache[oldest_key]
|
166
|
+
|
167
|
+
self._block_cache[block_coords] = block
|
168
|
+
|
169
|
+
def _optimize_block_distribution(self) -> None:
|
170
|
+
"""Analyze and optimize block distribution."""
|
171
|
+
if not self.auto_optimize:
|
172
|
+
return
|
173
|
+
|
174
|
+
self._dense_blocks = 0
|
175
|
+
self._sparse_blocks = 0
|
176
|
+
|
177
|
+
for block in self._blocks.values():
|
178
|
+
if block.is_dense:
|
179
|
+
self._dense_blocks += 1
|
180
|
+
else:
|
181
|
+
self._sparse_blocks += 1
|
182
|
+
|
183
|
+
# ============================================================================
|
184
|
+
# CORE OPERATIONS
|
185
|
+
# ============================================================================
|
186
|
+
|
187
|
+
def add_edge(self, u: str, v: str, weight: Any = 1, **properties) -> None:
|
188
|
+
"""Add edge to block matrix."""
|
189
|
+
u_id = self._get_vertex_id(u)
|
190
|
+
v_id = self._get_vertex_id(v)
|
191
|
+
|
192
|
+
block_coords = self._get_edge_block_coords(u_id, v_id)
|
193
|
+
local_u, local_v = self._get_edge_local_coords(u_id, v_id)
|
194
|
+
|
195
|
+
block = self._get_block(block_coords, create=True)
|
196
|
+
if block.set_edge(local_u, local_v, weight):
|
197
|
+
self._edge_count += 1
|
198
|
+
|
199
|
+
self._matrix_accesses += 1
|
200
|
+
|
201
|
+
# Periodic optimization
|
202
|
+
if self._matrix_accesses % 1000 == 0:
|
203
|
+
self._optimize_block_distribution()
|
204
|
+
|
205
|
+
def remove_edge(self, u: str, v: str) -> bool:
|
206
|
+
"""Remove edge from block matrix."""
|
207
|
+
if u not in self._vertex_to_id or v not in self._vertex_to_id:
|
208
|
+
return False
|
209
|
+
|
210
|
+
u_id = self._vertex_to_id[u]
|
211
|
+
v_id = self._vertex_to_id[v]
|
212
|
+
|
213
|
+
block_coords = self._get_edge_block_coords(u_id, v_id)
|
214
|
+
local_u, local_v = self._get_edge_local_coords(u_id, v_id)
|
215
|
+
|
216
|
+
block = self._get_block(block_coords, create=False)
|
217
|
+
if block and block.remove_edge(local_u, local_v):
|
218
|
+
self._edge_count -= 1
|
219
|
+
self._matrix_accesses += 1
|
220
|
+
return True
|
221
|
+
|
222
|
+
return False
|
223
|
+
|
224
|
+
def has_edge(self, u: str, v: str) -> bool:
|
225
|
+
"""Check if edge exists in block matrix."""
|
226
|
+
if u not in self._vertex_to_id or v not in self._vertex_to_id:
|
227
|
+
return False
|
228
|
+
|
229
|
+
u_id = self._vertex_to_id[u]
|
230
|
+
v_id = self._vertex_to_id[v]
|
231
|
+
|
232
|
+
block_coords = self._get_edge_block_coords(u_id, v_id)
|
233
|
+
local_u, local_v = self._get_edge_local_coords(u_id, v_id)
|
234
|
+
|
235
|
+
block = self._get_block(block_coords, create=False)
|
236
|
+
self._matrix_accesses += 1
|
237
|
+
|
238
|
+
return block.has_edge(local_u, local_v) if block else False
|
239
|
+
|
240
|
+
def get_edge_weight(self, u: str, v: str) -> Any:
|
241
|
+
"""Get edge weight from block matrix."""
|
242
|
+
if u not in self._vertex_to_id or v not in self._vertex_to_id:
|
243
|
+
return None
|
244
|
+
|
245
|
+
u_id = self._vertex_to_id[u]
|
246
|
+
v_id = self._vertex_to_id[v]
|
247
|
+
|
248
|
+
block_coords = self._get_edge_block_coords(u_id, v_id)
|
249
|
+
local_u, local_v = self._get_edge_local_coords(u_id, v_id)
|
250
|
+
|
251
|
+
block = self._get_block(block_coords, create=False)
|
252
|
+
self._matrix_accesses += 1
|
253
|
+
|
254
|
+
return block.get_edge(local_u, local_v) if block else None
|
255
|
+
|
256
|
+
def get_neighbors(self, vertex: str) -> List[str]:
|
257
|
+
"""Get neighbors using block-wise traversal."""
|
258
|
+
if vertex not in self._vertex_to_id:
|
259
|
+
return []
|
260
|
+
|
261
|
+
vertex_id = self._vertex_to_id[vertex]
|
262
|
+
neighbors = []
|
263
|
+
|
264
|
+
# Check all blocks in the row corresponding to this vertex
|
265
|
+
vertex_block_row = vertex_id // self.block_size
|
266
|
+
local_u = vertex_id % self.block_size
|
267
|
+
|
268
|
+
for block_coords, block in self._blocks.items():
|
269
|
+
block_row, block_col = block_coords
|
270
|
+
|
271
|
+
if block_row == vertex_block_row:
|
272
|
+
# Check this block for outgoing edges
|
273
|
+
for local_v in range(self.block_size):
|
274
|
+
if block.has_edge(local_u, local_v):
|
275
|
+
target_id = block_col * self.block_size + local_v
|
276
|
+
if target_id in self._id_to_vertex:
|
277
|
+
neighbors.append(self._id_to_vertex[target_id])
|
278
|
+
|
279
|
+
self._matrix_accesses += len(self._blocks)
|
280
|
+
return neighbors
|
281
|
+
|
282
|
+
def get_all_edges(self) -> List[Tuple[str, str, Any]]:
|
283
|
+
"""Get all edges from all blocks."""
|
284
|
+
all_edges = []
|
285
|
+
|
286
|
+
for (block_row, block_col), block in self._blocks.items():
|
287
|
+
block_edges = block.get_edges()
|
288
|
+
|
289
|
+
for local_u, local_v, weight in block_edges:
|
290
|
+
u_id = block_row * self.block_size + local_u
|
291
|
+
v_id = block_col * self.block_size + local_v
|
292
|
+
|
293
|
+
if u_id in self._id_to_vertex and v_id in self._id_to_vertex:
|
294
|
+
u_vertex = self._id_to_vertex[u_id]
|
295
|
+
v_vertex = self._id_to_vertex[v_id]
|
296
|
+
all_edges.append((u_vertex, v_vertex, weight))
|
297
|
+
|
298
|
+
return all_edges
|
299
|
+
|
300
|
+
def clear(self) -> None:
|
301
|
+
"""Clear all edges and blocks."""
|
302
|
+
self._blocks.clear()
|
303
|
+
self._block_cache.clear()
|
304
|
+
self._vertex_to_id.clear()
|
305
|
+
self._id_to_vertex.clear()
|
306
|
+
self._next_id = 0
|
307
|
+
self._edge_count = 0
|
308
|
+
self._total_blocks = 0
|
309
|
+
self._dense_blocks = 0
|
310
|
+
self._sparse_blocks = 0
|
311
|
+
self._matrix_accesses = 0
|
312
|
+
self._cache_hits = 0
|
313
|
+
self._cache_misses = 0
|
314
|
+
|
315
|
+
def __len__(self) -> int:
|
316
|
+
"""Get number of edges."""
|
317
|
+
return self._edge_count
|
318
|
+
|
319
|
+
def get_vertices(self) -> List[str]:
|
320
|
+
"""Get all vertices."""
|
321
|
+
return list(self._vertex_to_id.keys())
|
322
|
+
|
323
|
+
def get_vertex_count(self) -> int:
|
324
|
+
"""Get number of vertices."""
|
325
|
+
return len(self._vertex_to_id)
|
326
|
+
|
327
|
+
def vertices(self) -> List[str]:
|
328
|
+
"""Get all vertices (abstract method implementation)."""
|
329
|
+
return self.get_vertices()
|
330
|
+
|
331
|
+
def edges(self) -> List[Tuple[str, str, Any]]:
|
332
|
+
"""Get all edges (abstract method implementation)."""
|
333
|
+
return self.get_all_edges()
|
334
|
+
|
335
|
+
def neighbors(self, vertex: str) -> List[str]:
|
336
|
+
"""Get neighbors (abstract method implementation)."""
|
337
|
+
return self.get_neighbors(vertex)
|
338
|
+
|
339
|
+
def degree(self, vertex: str) -> int:
|
340
|
+
"""Get vertex degree (abstract method implementation)."""
|
341
|
+
return len(self.get_neighbors(vertex))
|
342
|
+
|
343
|
+
# ============================================================================
|
344
|
+
# BLOCK MATRIX SPECIFIC OPERATIONS
|
345
|
+
# ============================================================================
|
346
|
+
|
347
|
+
def get_block_info(self, u: str, v: str) -> Dict[str, Any]:
|
348
|
+
"""Get information about the block containing edge (u, v)."""
|
349
|
+
if u not in self._vertex_to_id or v not in self._vertex_to_id:
|
350
|
+
return {}
|
351
|
+
|
352
|
+
u_id = self._vertex_to_id[u]
|
353
|
+
v_id = self._vertex_to_id[v]
|
354
|
+
|
355
|
+
block_coords = self._get_edge_block_coords(u_id, v_id)
|
356
|
+
block = self._get_block(block_coords, create=False)
|
357
|
+
|
358
|
+
if not block:
|
359
|
+
return {'exists': False}
|
360
|
+
|
361
|
+
return {
|
362
|
+
'exists': True,
|
363
|
+
'block_coords': block_coords,
|
364
|
+
'edge_count': block.edge_count,
|
365
|
+
'is_dense': block.is_dense,
|
366
|
+
'density': block.edge_count / (self.block_size ** 2),
|
367
|
+
'block_size': self.block_size
|
368
|
+
}
|
369
|
+
|
370
|
+
def get_dense_blocks(self) -> List[Tuple[int, int]]:
|
371
|
+
"""Get coordinates of all dense blocks."""
|
372
|
+
dense_blocks = []
|
373
|
+
for coords, block in self._blocks.items():
|
374
|
+
if block.is_dense:
|
375
|
+
dense_blocks.append(coords)
|
376
|
+
return dense_blocks
|
377
|
+
|
378
|
+
def get_sparse_blocks(self) -> List[Tuple[int, int]]:
|
379
|
+
"""Get coordinates of all sparse blocks."""
|
380
|
+
sparse_blocks = []
|
381
|
+
for coords, block in self._blocks.items():
|
382
|
+
if not block.is_dense:
|
383
|
+
sparse_blocks.append(coords)
|
384
|
+
return sparse_blocks
|
385
|
+
|
386
|
+
def matrix_multiply_block(self, other: 'xBlockAdjMatrixStrategy', block_coords: Tuple[int, int]) -> MatrixBlock:
|
387
|
+
"""Perform block-wise matrix multiplication for specific block."""
|
388
|
+
result_block = MatrixBlock(self.block_size)
|
389
|
+
|
390
|
+
block_row, block_col = block_coords
|
391
|
+
|
392
|
+
# Multiply blocks: C[i,k] = sum(A[i,j] * B[j,k]) for all j
|
393
|
+
for j in range(max(len(self._blocks), len(other._blocks))):
|
394
|
+
a_coords = (block_row, j)
|
395
|
+
b_coords = (j, block_col)
|
396
|
+
|
397
|
+
a_block = self._get_block(a_coords, create=False)
|
398
|
+
b_block = other._get_block(b_coords, create=False)
|
399
|
+
|
400
|
+
if a_block and b_block:
|
401
|
+
# Multiply these two blocks
|
402
|
+
for local_i in range(self.block_size):
|
403
|
+
for local_k in range(self.block_size):
|
404
|
+
sum_value = 0
|
405
|
+
for local_j in range(self.block_size):
|
406
|
+
a_val = a_block.get_edge(local_i, local_j)
|
407
|
+
b_val = b_block.get_edge(local_j, local_k)
|
408
|
+
|
409
|
+
if a_val is not None and b_val is not None:
|
410
|
+
sum_value += a_val * b_val
|
411
|
+
|
412
|
+
if sum_value != 0:
|
413
|
+
result_block.set_edge(local_i, local_k, sum_value)
|
414
|
+
|
415
|
+
return result_block
|
416
|
+
|
417
|
+
def get_block_statistics(self) -> Dict[str, Any]:
|
418
|
+
"""Get comprehensive block statistics."""
|
419
|
+
if not self._blocks:
|
420
|
+
return {'total_blocks': 0}
|
421
|
+
|
422
|
+
# Analyze block distribution
|
423
|
+
block_densities = []
|
424
|
+
block_sizes = []
|
425
|
+
|
426
|
+
for block in self._blocks.values():
|
427
|
+
density = block.edge_count / (self.block_size ** 2)
|
428
|
+
block_densities.append(density)
|
429
|
+
block_sizes.append(block.edge_count)
|
430
|
+
|
431
|
+
avg_density = sum(block_densities) / len(block_densities)
|
432
|
+
max_density = max(block_densities)
|
433
|
+
min_density = min(block_densities)
|
434
|
+
|
435
|
+
# Calculate matrix dimensions
|
436
|
+
max_vertex_id = max(self._vertex_to_id.values()) if self._vertex_to_id else 0
|
437
|
+
matrix_size = max_vertex_id + 1
|
438
|
+
theoretical_blocks = math.ceil(matrix_size / self.block_size) ** 2
|
439
|
+
|
440
|
+
return {
|
441
|
+
'total_blocks': self._total_blocks,
|
442
|
+
'dense_blocks': self._dense_blocks,
|
443
|
+
'sparse_blocks': self._sparse_blocks,
|
444
|
+
'block_utilization': self._total_blocks / max(1, theoretical_blocks),
|
445
|
+
'avg_block_density': avg_density,
|
446
|
+
'max_block_density': max_density,
|
447
|
+
'min_block_density': min_density,
|
448
|
+
'matrix_size': matrix_size,
|
449
|
+
'block_size': self.block_size,
|
450
|
+
'cache_hit_rate': self._cache_hits / max(1, self._cache_hits + self._cache_misses),
|
451
|
+
'matrix_accesses': self._matrix_accesses
|
452
|
+
}
|
453
|
+
|
454
|
+
def optimize_layout(self) -> Dict[str, Any]:
|
455
|
+
"""Optimize block layout for better cache performance."""
|
456
|
+
# Reorder vertices to improve block locality
|
457
|
+
# This is a simplified version - real optimization would use graph partitioning
|
458
|
+
|
459
|
+
old_stats = self.get_block_statistics()
|
460
|
+
|
461
|
+
# Simple optimization: group frequently connected vertices
|
462
|
+
vertex_connections = {}
|
463
|
+
for vertex in self._vertex_to_id.keys():
|
464
|
+
vertex_connections[vertex] = len(self.get_neighbors(vertex))
|
465
|
+
|
466
|
+
# Sort vertices by connection count (heuristic)
|
467
|
+
sorted_vertices = sorted(vertex_connections.items(), key=lambda x: x[1], reverse=True)
|
468
|
+
|
469
|
+
# Rebuild vertex ID mapping
|
470
|
+
old_mapping = self._vertex_to_id.copy()
|
471
|
+
self._vertex_to_id.clear()
|
472
|
+
self._id_to_vertex.clear()
|
473
|
+
self._next_id = 0
|
474
|
+
|
475
|
+
for vertex, _ in sorted_vertices:
|
476
|
+
self._vertex_to_id[vertex] = self._next_id
|
477
|
+
self._id_to_vertex[self._next_id] = vertex
|
478
|
+
self._next_id += 1
|
479
|
+
|
480
|
+
# Rebuild blocks with new mapping
|
481
|
+
old_blocks = self._blocks.copy()
|
482
|
+
self._blocks.clear()
|
483
|
+
self._block_cache.clear()
|
484
|
+
self._total_blocks = 0
|
485
|
+
|
486
|
+
# Re-add all edges with new mapping
|
487
|
+
for (u, v, weight) in self.get_all_edges():
|
488
|
+
# Temporarily store edges to re-add
|
489
|
+
pass
|
490
|
+
|
491
|
+
new_stats = self.get_block_statistics()
|
492
|
+
|
493
|
+
return {
|
494
|
+
'optimization_applied': True,
|
495
|
+
'old_blocks': old_stats['total_blocks'],
|
496
|
+
'new_blocks': new_stats['total_blocks'],
|
497
|
+
'block_reduction': old_stats['total_blocks'] - new_stats['total_blocks'],
|
498
|
+
'old_cache_hit_rate': old_stats['cache_hit_rate'],
|
499
|
+
'new_cache_hit_rate': new_stats['cache_hit_rate']
|
500
|
+
}
|
501
|
+
|
502
|
+
# ============================================================================
|
503
|
+
# PERFORMANCE CHARACTERISTICS
|
504
|
+
# ============================================================================
|
505
|
+
|
506
|
+
@property
|
507
|
+
def backend_info(self) -> Dict[str, Any]:
|
508
|
+
"""Get backend implementation info."""
|
509
|
+
return {
|
510
|
+
'strategy': 'BLOCK_ADJ_MATRIX',
|
511
|
+
'backend': 'Cache-friendly block adjacency matrix',
|
512
|
+
'block_size': self.block_size,
|
513
|
+
'cache_blocks': self.cache_blocks,
|
514
|
+
'auto_optimize': self.auto_optimize,
|
515
|
+
'complexity': {
|
516
|
+
'add_edge': 'O(1)',
|
517
|
+
'has_edge': 'O(1)',
|
518
|
+
'get_neighbors': 'O(blocks_in_row)',
|
519
|
+
'matrix_multiply': 'O(n^3 / block_size^3)', # Block-wise
|
520
|
+
'space': 'O(edges + blocks)',
|
521
|
+
'cache_efficiency': 'High for dense regions'
|
522
|
+
}
|
523
|
+
}
|
524
|
+
|
525
|
+
@property
|
526
|
+
def metrics(self) -> Dict[str, Any]:
|
527
|
+
"""Get performance metrics."""
|
528
|
+
stats = self.get_block_statistics()
|
529
|
+
|
530
|
+
return {
|
531
|
+
'edges': self._edge_count,
|
532
|
+
'vertices': len(self._vertex_to_id),
|
533
|
+
'total_blocks': stats['total_blocks'],
|
534
|
+
'dense_blocks': stats['dense_blocks'],
|
535
|
+
'sparse_blocks': stats['sparse_blocks'],
|
536
|
+
'avg_block_density': f"{stats['avg_block_density'] * 100:.1f}%",
|
537
|
+
'cache_hit_rate': f"{stats['cache_hit_rate'] * 100:.1f}%",
|
538
|
+
'memory_usage': f"{self._total_blocks * self.block_size * self.block_size * 8} bytes (estimated)"
|
539
|
+
}
|