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,533 @@
|
|
1
|
+
"""
|
2
|
+
COO (Coordinate) Edge Strategy Implementation
|
3
|
+
|
4
|
+
This module implements the COO strategy for sparse graph representation
|
5
|
+
using coordinate format for efficient sparse matrix operations and conversions.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import Any, Iterator, List, Dict, Set, Optional, Tuple
|
9
|
+
from collections import defaultdict
|
10
|
+
import bisect
|
11
|
+
from ._base_edge import aEdgeStrategy
|
12
|
+
from ...types import EdgeMode, EdgeTrait
|
13
|
+
|
14
|
+
|
15
|
+
class xCOOStrategy(aEdgeStrategy):
|
16
|
+
"""
|
17
|
+
COO (Coordinate) edge strategy for sparse graphs.
|
18
|
+
|
19
|
+
Stores edges as coordinate triplets (row, col, value) for efficient
|
20
|
+
sparse matrix operations and easy conversion to other formats.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(self, traits: EdgeTrait = EdgeTrait.NONE, **options):
|
24
|
+
"""Initialize the COO strategy."""
|
25
|
+
super().__init__(EdgeMode.COO, traits, **options)
|
26
|
+
|
27
|
+
self.weighted = options.get('weighted', True)
|
28
|
+
self.allow_duplicates = options.get('allow_duplicates', True)
|
29
|
+
self.sort_coordinates = options.get('sort_coordinates', True)
|
30
|
+
|
31
|
+
# COO format: three parallel arrays
|
32
|
+
self._row_indices: List[int] = [] # Row indices
|
33
|
+
self._col_indices: List[int] = [] # Column indices
|
34
|
+
self._values: List[float] = [] # Edge values/weights
|
35
|
+
|
36
|
+
# Vertex management
|
37
|
+
self._vertices: Set[str] = set()
|
38
|
+
self._vertex_to_id: Dict[str, int] = {}
|
39
|
+
self._id_to_vertex: Dict[int, str] = {}
|
40
|
+
self._next_vertex_id = 0
|
41
|
+
|
42
|
+
# Matrix dimensions and metadata
|
43
|
+
self._num_rows = 0
|
44
|
+
self._num_cols = 0
|
45
|
+
self._nnz = 0 # Number of non-zeros
|
46
|
+
self._is_sorted = True
|
47
|
+
|
48
|
+
# Quick access structures
|
49
|
+
self._edge_count = 0
|
50
|
+
self._coordinate_index: Dict[Tuple[int, int], List[int]] = defaultdict(list) # (row, col) -> [positions]
|
51
|
+
|
52
|
+
def get_supported_traits(self) -> EdgeTrait:
|
53
|
+
"""Get the traits supported by the COO strategy."""
|
54
|
+
return (EdgeTrait.SPARSE | EdgeTrait.COMPRESSED | EdgeTrait.MULTI)
|
55
|
+
|
56
|
+
def _get_or_create_vertex_id(self, vertex: str) -> int:
|
57
|
+
"""Get or create vertex ID."""
|
58
|
+
if vertex not in self._vertex_to_id:
|
59
|
+
vertex_id = self._next_vertex_id
|
60
|
+
self._vertex_to_id[vertex] = vertex_id
|
61
|
+
self._id_to_vertex[vertex_id] = vertex
|
62
|
+
self._vertices.add(vertex)
|
63
|
+
self._next_vertex_id += 1
|
64
|
+
return vertex_id
|
65
|
+
return self._vertex_to_id[vertex]
|
66
|
+
|
67
|
+
def _update_dimensions(self, row: int, col: int) -> None:
|
68
|
+
"""Update matrix dimensions."""
|
69
|
+
self._num_rows = max(self._num_rows, row + 1)
|
70
|
+
self._num_cols = max(self._num_cols, col + 1)
|
71
|
+
|
72
|
+
def _add_coordinate(self, row: int, col: int, value: float) -> None:
|
73
|
+
"""Add coordinate to COO format."""
|
74
|
+
position = len(self._row_indices)
|
75
|
+
|
76
|
+
self._row_indices.append(row)
|
77
|
+
self._col_indices.append(col)
|
78
|
+
self._values.append(value)
|
79
|
+
|
80
|
+
# Update coordinate index
|
81
|
+
coord_key = (row, col)
|
82
|
+
self._coordinate_index[coord_key].append(position)
|
83
|
+
|
84
|
+
self._nnz += 1
|
85
|
+
self._is_sorted = False
|
86
|
+
self._update_dimensions(row, col)
|
87
|
+
|
88
|
+
def _remove_coordinate_at_position(self, position: int) -> None:
|
89
|
+
"""Remove coordinate at specific position."""
|
90
|
+
if 0 <= position < len(self._row_indices):
|
91
|
+
row = self._row_indices[position]
|
92
|
+
col = self._col_indices[position]
|
93
|
+
|
94
|
+
# Remove from arrays
|
95
|
+
del self._row_indices[position]
|
96
|
+
del self._col_indices[position]
|
97
|
+
del self._values[position]
|
98
|
+
|
99
|
+
# Update coordinate index
|
100
|
+
coord_key = (row, col)
|
101
|
+
self._coordinate_index[coord_key].remove(position)
|
102
|
+
if not self._coordinate_index[coord_key]:
|
103
|
+
del self._coordinate_index[coord_key]
|
104
|
+
|
105
|
+
# Update positions in coordinate index
|
106
|
+
for key, positions in self._coordinate_index.items():
|
107
|
+
for i, pos in enumerate(positions):
|
108
|
+
if pos > position:
|
109
|
+
positions[i] = pos - 1
|
110
|
+
|
111
|
+
self._nnz -= 1
|
112
|
+
self._is_sorted = False
|
113
|
+
|
114
|
+
def _sort_coordinates(self) -> None:
|
115
|
+
"""Sort coordinates by (row, col)."""
|
116
|
+
if self._is_sorted or self._nnz == 0:
|
117
|
+
return
|
118
|
+
|
119
|
+
# Create list of (row, col, value, original_index) tuples
|
120
|
+
coords = list(zip(self._row_indices, self._col_indices, self._values, range(self._nnz)))
|
121
|
+
|
122
|
+
# Sort by (row, col)
|
123
|
+
coords.sort(key=lambda x: (x[0], x[1]))
|
124
|
+
|
125
|
+
# Rebuild arrays
|
126
|
+
self._row_indices = [coord[0] for coord in coords]
|
127
|
+
self._col_indices = [coord[1] for coord in coords]
|
128
|
+
self._values = [coord[2] for coord in coords]
|
129
|
+
|
130
|
+
# Rebuild coordinate index
|
131
|
+
self._coordinate_index.clear()
|
132
|
+
for i, (row, col, _, _) in enumerate(coords):
|
133
|
+
coord_key = (row, col)
|
134
|
+
self._coordinate_index[coord_key].append(i)
|
135
|
+
|
136
|
+
self._is_sorted = True
|
137
|
+
|
138
|
+
def _find_coordinate_positions(self, row: int, col: int) -> List[int]:
|
139
|
+
"""Find all positions of coordinate (row, col)."""
|
140
|
+
coord_key = (row, col)
|
141
|
+
return self._coordinate_index.get(coord_key, [])
|
142
|
+
|
143
|
+
# ============================================================================
|
144
|
+
# CORE EDGE OPERATIONS
|
145
|
+
# ============================================================================
|
146
|
+
|
147
|
+
def add_edge(self, source: str, target: str, **properties) -> str:
|
148
|
+
"""Add edge to COO matrix."""
|
149
|
+
row_id = self._get_or_create_vertex_id(source)
|
150
|
+
col_id = self._get_or_create_vertex_id(target)
|
151
|
+
|
152
|
+
weight = properties.get('weight', 1.0) if self.weighted else 1.0
|
153
|
+
|
154
|
+
# Check for existing edge
|
155
|
+
positions = self._find_coordinate_positions(row_id, col_id)
|
156
|
+
|
157
|
+
if positions and not self.allow_duplicates:
|
158
|
+
# Update existing edge (use first occurrence)
|
159
|
+
self._values[positions[0]] = weight
|
160
|
+
return f"{source}->{target}"
|
161
|
+
|
162
|
+
# Add new coordinate
|
163
|
+
self._add_coordinate(row_id, col_id, weight)
|
164
|
+
self._edge_count += 1
|
165
|
+
|
166
|
+
return f"{source}->{target}"
|
167
|
+
|
168
|
+
def remove_edge(self, source: str, target: str, edge_id: Optional[str] = None) -> bool:
|
169
|
+
"""Remove edge from COO matrix."""
|
170
|
+
if source not in self._vertex_to_id or target not in self._vertex_to_id:
|
171
|
+
return False
|
172
|
+
|
173
|
+
row_id = self._vertex_to_id[source]
|
174
|
+
col_id = self._vertex_to_id[target]
|
175
|
+
|
176
|
+
positions = self._find_coordinate_positions(row_id, col_id)
|
177
|
+
|
178
|
+
if positions:
|
179
|
+
# Remove first occurrence
|
180
|
+
self._remove_coordinate_at_position(positions[0])
|
181
|
+
self._edge_count -= 1
|
182
|
+
return True
|
183
|
+
|
184
|
+
return False
|
185
|
+
|
186
|
+
def has_edge(self, source: str, target: str) -> bool:
|
187
|
+
"""Check if edge exists."""
|
188
|
+
if source not in self._vertex_to_id or target not in self._vertex_to_id:
|
189
|
+
return False
|
190
|
+
|
191
|
+
row_id = self._vertex_to_id[source]
|
192
|
+
col_id = self._vertex_to_id[target]
|
193
|
+
|
194
|
+
return len(self._find_coordinate_positions(row_id, col_id)) > 0
|
195
|
+
|
196
|
+
def get_edge_data(self, source: str, target: str) -> Optional[Dict[str, Any]]:
|
197
|
+
"""Get edge data."""
|
198
|
+
if not self.has_edge(source, target):
|
199
|
+
return None
|
200
|
+
|
201
|
+
row_id = self._vertex_to_id[source]
|
202
|
+
col_id = self._vertex_to_id[target]
|
203
|
+
positions = self._find_coordinate_positions(row_id, col_id)
|
204
|
+
|
205
|
+
if positions:
|
206
|
+
value = self._values[positions[0]]
|
207
|
+
return {
|
208
|
+
'source': source,
|
209
|
+
'target': target,
|
210
|
+
'weight': value,
|
211
|
+
'row_id': row_id,
|
212
|
+
'col_id': col_id,
|
213
|
+
'duplicates': len(positions) - 1
|
214
|
+
}
|
215
|
+
|
216
|
+
return None
|
217
|
+
|
218
|
+
def neighbors(self, vertex: str, direction: str = 'out') -> Iterator[str]:
|
219
|
+
"""Get neighbors of vertex."""
|
220
|
+
if vertex not in self._vertex_to_id:
|
221
|
+
return
|
222
|
+
|
223
|
+
vertex_id = self._vertex_to_id[vertex]
|
224
|
+
neighbors_found = set()
|
225
|
+
|
226
|
+
if direction in ['out', 'both']:
|
227
|
+
# Outgoing: vertex is source (row)
|
228
|
+
for i in range(self._nnz):
|
229
|
+
if self._row_indices[i] == vertex_id:
|
230
|
+
col_id = self._col_indices[i]
|
231
|
+
neighbor = self._id_to_vertex.get(col_id)
|
232
|
+
if neighbor and neighbor not in neighbors_found:
|
233
|
+
neighbors_found.add(neighbor)
|
234
|
+
yield neighbor
|
235
|
+
|
236
|
+
if direction in ['in', 'both']:
|
237
|
+
# Incoming: vertex is target (column)
|
238
|
+
for i in range(self._nnz):
|
239
|
+
if self._col_indices[i] == vertex_id:
|
240
|
+
row_id = self._row_indices[i]
|
241
|
+
neighbor = self._id_to_vertex.get(row_id)
|
242
|
+
if neighbor and neighbor not in neighbors_found:
|
243
|
+
neighbors_found.add(neighbor)
|
244
|
+
yield neighbor
|
245
|
+
|
246
|
+
def degree(self, vertex: str, direction: str = 'out') -> int:
|
247
|
+
"""Get degree of vertex."""
|
248
|
+
return len(list(self.neighbors(vertex, direction)))
|
249
|
+
|
250
|
+
def edges(self, data: bool = False) -> Iterator[tuple]:
|
251
|
+
"""Get all edges."""
|
252
|
+
for i in range(self._nnz):
|
253
|
+
row_id = self._row_indices[i]
|
254
|
+
col_id = self._col_indices[i]
|
255
|
+
|
256
|
+
source = self._id_to_vertex.get(row_id)
|
257
|
+
target = self._id_to_vertex.get(col_id)
|
258
|
+
|
259
|
+
if source and target:
|
260
|
+
if data:
|
261
|
+
edge_data = {
|
262
|
+
'weight': self._values[i],
|
263
|
+
'row_id': row_id,
|
264
|
+
'col_id': col_id,
|
265
|
+
'position': i
|
266
|
+
}
|
267
|
+
yield (source, target, edge_data)
|
268
|
+
else:
|
269
|
+
yield (source, target)
|
270
|
+
|
271
|
+
def vertices(self) -> Iterator[str]:
|
272
|
+
"""Get all vertices."""
|
273
|
+
return iter(self._vertices)
|
274
|
+
|
275
|
+
def __len__(self) -> int:
|
276
|
+
"""Get number of edges."""
|
277
|
+
return self._edge_count
|
278
|
+
|
279
|
+
def vertex_count(self) -> int:
|
280
|
+
"""Get number of vertices."""
|
281
|
+
return len(self._vertices)
|
282
|
+
|
283
|
+
def clear(self) -> None:
|
284
|
+
"""Clear all data."""
|
285
|
+
self._row_indices.clear()
|
286
|
+
self._col_indices.clear()
|
287
|
+
self._values.clear()
|
288
|
+
self._vertices.clear()
|
289
|
+
self._vertex_to_id.clear()
|
290
|
+
self._id_to_vertex.clear()
|
291
|
+
self._coordinate_index.clear()
|
292
|
+
|
293
|
+
self._num_rows = 0
|
294
|
+
self._num_cols = 0
|
295
|
+
self._nnz = 0
|
296
|
+
self._edge_count = 0
|
297
|
+
self._next_vertex_id = 0
|
298
|
+
self._is_sorted = True
|
299
|
+
|
300
|
+
def add_vertex(self, vertex: str) -> None:
|
301
|
+
"""Add vertex to graph."""
|
302
|
+
self._get_or_create_vertex_id(vertex)
|
303
|
+
|
304
|
+
def remove_vertex(self, vertex: str) -> bool:
|
305
|
+
"""Remove vertex and all its edges."""
|
306
|
+
if vertex not in self._vertex_to_id:
|
307
|
+
return False
|
308
|
+
|
309
|
+
vertex_id = self._vertex_to_id[vertex]
|
310
|
+
|
311
|
+
# Remove all coordinates involving this vertex
|
312
|
+
positions_to_remove = []
|
313
|
+
for i in range(self._nnz):
|
314
|
+
if self._row_indices[i] == vertex_id or self._col_indices[i] == vertex_id:
|
315
|
+
positions_to_remove.append(i)
|
316
|
+
|
317
|
+
# Remove in reverse order to maintain indices
|
318
|
+
for pos in reversed(positions_to_remove):
|
319
|
+
self._remove_coordinate_at_position(pos)
|
320
|
+
self._edge_count -= 1
|
321
|
+
|
322
|
+
# Remove vertex
|
323
|
+
del self._vertex_to_id[vertex]
|
324
|
+
del self._id_to_vertex[vertex_id]
|
325
|
+
self._vertices.remove(vertex)
|
326
|
+
|
327
|
+
return True
|
328
|
+
|
329
|
+
# ============================================================================
|
330
|
+
# COO SPECIFIC OPERATIONS
|
331
|
+
# ============================================================================
|
332
|
+
|
333
|
+
def sort(self) -> None:
|
334
|
+
"""Sort coordinates by (row, col)."""
|
335
|
+
self._sort_coordinates()
|
336
|
+
|
337
|
+
def get_coordinates(self) -> Tuple[List[int], List[int], List[float]]:
|
338
|
+
"""Get COO coordinate arrays."""
|
339
|
+
if self.sort_coordinates:
|
340
|
+
self._sort_coordinates()
|
341
|
+
return (self._row_indices.copy(), self._col_indices.copy(), self._values.copy())
|
342
|
+
|
343
|
+
def sum_duplicates(self) -> None:
|
344
|
+
"""Sum duplicate coordinates."""
|
345
|
+
if self._nnz == 0:
|
346
|
+
return
|
347
|
+
|
348
|
+
self._sort_coordinates()
|
349
|
+
|
350
|
+
# Track unique coordinates and their sums
|
351
|
+
unique_coords = {}
|
352
|
+
for i in range(self._nnz):
|
353
|
+
coord_key = (self._row_indices[i], self._col_indices[i])
|
354
|
+
if coord_key in unique_coords:
|
355
|
+
unique_coords[coord_key] += self._values[i]
|
356
|
+
else:
|
357
|
+
unique_coords[coord_key] = self._values[i]
|
358
|
+
|
359
|
+
# Rebuild arrays
|
360
|
+
self._row_indices.clear()
|
361
|
+
self._col_indices.clear()
|
362
|
+
self._values.clear()
|
363
|
+
self._coordinate_index.clear()
|
364
|
+
|
365
|
+
for (row, col), value in unique_coords.items():
|
366
|
+
self._row_indices.append(row)
|
367
|
+
self._col_indices.append(col)
|
368
|
+
self._values.append(value)
|
369
|
+
|
370
|
+
coord_key = (row, col)
|
371
|
+
position = len(self._row_indices) - 1
|
372
|
+
self._coordinate_index[coord_key].append(position)
|
373
|
+
|
374
|
+
self._nnz = len(self._row_indices)
|
375
|
+
self._edge_count = self._nnz
|
376
|
+
self._is_sorted = True
|
377
|
+
|
378
|
+
def eliminate_zeros(self, tolerance: float = 1e-12) -> None:
|
379
|
+
"""Remove coordinates with zero or near-zero values."""
|
380
|
+
positions_to_remove = []
|
381
|
+
|
382
|
+
for i in range(self._nnz):
|
383
|
+
if abs(self._values[i]) <= tolerance:
|
384
|
+
positions_to_remove.append(i)
|
385
|
+
|
386
|
+
# Remove in reverse order
|
387
|
+
for pos in reversed(positions_to_remove):
|
388
|
+
self._remove_coordinate_at_position(pos)
|
389
|
+
self._edge_count -= 1
|
390
|
+
|
391
|
+
def to_dense_matrix(self) -> List[List[float]]:
|
392
|
+
"""Convert to dense matrix representation."""
|
393
|
+
if self._num_rows == 0 or self._num_cols == 0:
|
394
|
+
return []
|
395
|
+
|
396
|
+
# Initialize dense matrix with zeros
|
397
|
+
matrix = [[0.0 for _ in range(self._num_cols)] for _ in range(self._num_rows)]
|
398
|
+
|
399
|
+
# Fill with values
|
400
|
+
for i in range(self._nnz):
|
401
|
+
row = self._row_indices[i]
|
402
|
+
col = self._col_indices[i]
|
403
|
+
matrix[row][col] = self._values[i]
|
404
|
+
|
405
|
+
return matrix
|
406
|
+
|
407
|
+
def transpose(self) -> 'xCOOStrategy':
|
408
|
+
"""Create transposed COO matrix."""
|
409
|
+
transposed = xCOOStrategy(
|
410
|
+
traits=self._traits,
|
411
|
+
weighted=self.weighted,
|
412
|
+
allow_duplicates=self.allow_duplicates,
|
413
|
+
sort_coordinates=self.sort_coordinates
|
414
|
+
)
|
415
|
+
|
416
|
+
# Copy vertex mappings
|
417
|
+
transposed._vertices = self._vertices.copy()
|
418
|
+
transposed._vertex_to_id = self._vertex_to_id.copy()
|
419
|
+
transposed._id_to_vertex = self._id_to_vertex.copy()
|
420
|
+
transposed._next_vertex_id = self._next_vertex_id
|
421
|
+
|
422
|
+
# Transpose coordinates (swap row and col)
|
423
|
+
for i in range(self._nnz):
|
424
|
+
col = self._row_indices[i] # Swapped
|
425
|
+
row = self._col_indices[i] # Swapped
|
426
|
+
value = self._values[i]
|
427
|
+
|
428
|
+
transposed._add_coordinate(row, col, value)
|
429
|
+
|
430
|
+
transposed._edge_count = self._edge_count
|
431
|
+
transposed._num_rows = self._num_cols # Swapped
|
432
|
+
transposed._num_cols = self._num_rows # Swapped
|
433
|
+
|
434
|
+
return transposed
|
435
|
+
|
436
|
+
def get_sparsity(self) -> float:
|
437
|
+
"""Get sparsity ratio."""
|
438
|
+
total_entries = self._num_rows * self._num_cols
|
439
|
+
if total_entries == 0:
|
440
|
+
return 0.0
|
441
|
+
return 1.0 - (self._nnz / total_entries)
|
442
|
+
|
443
|
+
def get_memory_usage(self) -> Dict[str, int]:
|
444
|
+
"""Get detailed memory usage."""
|
445
|
+
return {
|
446
|
+
'row_indices_bytes': len(self._row_indices) * 4, # 4 bytes per int
|
447
|
+
'col_indices_bytes': len(self._col_indices) * 4,
|
448
|
+
'values_bytes': len(self._values) * 8, # 8 bytes per float
|
449
|
+
'coordinate_index_bytes': len(self._coordinate_index) * 50, # Estimated
|
450
|
+
'vertex_mapping_bytes': len(self._vertices) * 50,
|
451
|
+
'total_bytes': len(self._row_indices) * 4 + len(self._col_indices) * 4 + len(self._values) * 8 + len(self._coordinate_index) * 50 + len(self._vertices) * 50
|
452
|
+
}
|
453
|
+
|
454
|
+
def export_matrix(self) -> Dict[str, Any]:
|
455
|
+
"""Export COO matrix data."""
|
456
|
+
return {
|
457
|
+
'row_indices': self._row_indices.copy(),
|
458
|
+
'col_indices': self._col_indices.copy(),
|
459
|
+
'values': self._values.copy(),
|
460
|
+
'vertex_to_id': self._vertex_to_id.copy(),
|
461
|
+
'id_to_vertex': self._id_to_vertex.copy(),
|
462
|
+
'dimensions': (self._num_rows, self._num_cols),
|
463
|
+
'nnz': self._nnz,
|
464
|
+
'is_sorted': self._is_sorted
|
465
|
+
}
|
466
|
+
|
467
|
+
def get_statistics(self) -> Dict[str, Any]:
|
468
|
+
"""Get comprehensive COO statistics."""
|
469
|
+
memory = self.get_memory_usage()
|
470
|
+
|
471
|
+
# Calculate duplicate statistics
|
472
|
+
unique_coords = set()
|
473
|
+
total_duplicates = 0
|
474
|
+
for i in range(self._nnz):
|
475
|
+
coord = (self._row_indices[i], self._col_indices[i])
|
476
|
+
if coord in unique_coords:
|
477
|
+
total_duplicates += 1
|
478
|
+
else:
|
479
|
+
unique_coords.add(coord)
|
480
|
+
|
481
|
+
return {
|
482
|
+
'vertices': len(self._vertices),
|
483
|
+
'edges': self._edge_count,
|
484
|
+
'matrix_dimensions': (self._num_rows, self._num_cols),
|
485
|
+
'nnz': self._nnz,
|
486
|
+
'unique_coordinates': len(unique_coords),
|
487
|
+
'duplicate_coordinates': total_duplicates,
|
488
|
+
'sparsity': self.get_sparsity(),
|
489
|
+
'density': 1.0 - self.get_sparsity(),
|
490
|
+
'is_sorted': self._is_sorted,
|
491
|
+
'memory_usage': memory['total_bytes'],
|
492
|
+
'weighted': self.weighted,
|
493
|
+
'allow_duplicates': self.allow_duplicates
|
494
|
+
}
|
495
|
+
|
496
|
+
# ============================================================================
|
497
|
+
# PERFORMANCE CHARACTERISTICS
|
498
|
+
# ============================================================================
|
499
|
+
|
500
|
+
@property
|
501
|
+
def backend_info(self) -> Dict[str, Any]:
|
502
|
+
"""Get backend implementation info."""
|
503
|
+
return {
|
504
|
+
'strategy': 'COO',
|
505
|
+
'backend': 'Coordinate format with three parallel arrays',
|
506
|
+
'weighted': self.weighted,
|
507
|
+
'allow_duplicates': self.allow_duplicates,
|
508
|
+
'sort_coordinates': self.sort_coordinates,
|
509
|
+
'complexity': {
|
510
|
+
'add_edge': 'O(1)',
|
511
|
+
'remove_edge': 'O(nnz)', # Need to find and shift
|
512
|
+
'has_edge': 'O(nnz)', # Linear search if unsorted
|
513
|
+
'sort': 'O(nnz log nnz)',
|
514
|
+
'sum_duplicates': 'O(nnz log nnz)',
|
515
|
+
'space': 'O(nnz)'
|
516
|
+
}
|
517
|
+
}
|
518
|
+
|
519
|
+
@property
|
520
|
+
def metrics(self) -> Dict[str, Any]:
|
521
|
+
"""Get performance metrics."""
|
522
|
+
stats = self.get_statistics()
|
523
|
+
|
524
|
+
return {
|
525
|
+
'vertices': stats['vertices'],
|
526
|
+
'edges': stats['edges'],
|
527
|
+
'nnz': stats['nnz'],
|
528
|
+
'matrix_size': f"{stats['matrix_dimensions'][0]}x{stats['matrix_dimensions'][1]}",
|
529
|
+
'sparsity': f"{stats['sparsity'] * 100:.1f}%",
|
530
|
+
'duplicates': stats['duplicate_coordinates'],
|
531
|
+
'sorted': stats['is_sorted'],
|
532
|
+
'memory_usage': f"{stats['memory_usage']} bytes"
|
533
|
+
}
|