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.
Files changed (132) hide show
  1. exonware/__init__.py +14 -0
  2. exonware/xwnode/__init__.py +127 -0
  3. exonware/xwnode/base.py +676 -0
  4. exonware/xwnode/config.py +178 -0
  5. exonware/xwnode/contracts.py +730 -0
  6. exonware/xwnode/errors.py +503 -0
  7. exonware/xwnode/facade.py +460 -0
  8. exonware/xwnode/strategies/__init__.py +158 -0
  9. exonware/xwnode/strategies/advisor.py +463 -0
  10. exonware/xwnode/strategies/edges/__init__.py +32 -0
  11. exonware/xwnode/strategies/edges/adj_list.py +227 -0
  12. exonware/xwnode/strategies/edges/adj_matrix.py +391 -0
  13. exonware/xwnode/strategies/edges/base.py +169 -0
  14. exonware/xwnode/strategies/flyweight.py +328 -0
  15. exonware/xwnode/strategies/impls/__init__.py +13 -0
  16. exonware/xwnode/strategies/impls/_base_edge.py +403 -0
  17. exonware/xwnode/strategies/impls/_base_node.py +307 -0
  18. exonware/xwnode/strategies/impls/edge_adj_list.py +353 -0
  19. exonware/xwnode/strategies/impls/edge_adj_matrix.py +445 -0
  20. exonware/xwnode/strategies/impls/edge_bidir_wrapper.py +455 -0
  21. exonware/xwnode/strategies/impls/edge_block_adj_matrix.py +539 -0
  22. exonware/xwnode/strategies/impls/edge_coo.py +533 -0
  23. exonware/xwnode/strategies/impls/edge_csc.py +447 -0
  24. exonware/xwnode/strategies/impls/edge_csr.py +492 -0
  25. exonware/xwnode/strategies/impls/edge_dynamic_adj_list.py +503 -0
  26. exonware/xwnode/strategies/impls/edge_flow_network.py +555 -0
  27. exonware/xwnode/strategies/impls/edge_hyperedge_set.py +516 -0
  28. exonware/xwnode/strategies/impls/edge_neural_graph.py +650 -0
  29. exonware/xwnode/strategies/impls/edge_octree.py +574 -0
  30. exonware/xwnode/strategies/impls/edge_property_store.py +655 -0
  31. exonware/xwnode/strategies/impls/edge_quadtree.py +519 -0
  32. exonware/xwnode/strategies/impls/edge_rtree.py +820 -0
  33. exonware/xwnode/strategies/impls/edge_temporal_edgeset.py +558 -0
  34. exonware/xwnode/strategies/impls/edge_tree_graph_basic.py +271 -0
  35. exonware/xwnode/strategies/impls/edge_weighted_graph.py +411 -0
  36. exonware/xwnode/strategies/manager.py +775 -0
  37. exonware/xwnode/strategies/metrics.py +538 -0
  38. exonware/xwnode/strategies/migration.py +432 -0
  39. exonware/xwnode/strategies/nodes/__init__.py +50 -0
  40. exonware/xwnode/strategies/nodes/_base_node.py +307 -0
  41. exonware/xwnode/strategies/nodes/adjacency_list.py +267 -0
  42. exonware/xwnode/strategies/nodes/aho_corasick.py +345 -0
  43. exonware/xwnode/strategies/nodes/array_list.py +209 -0
  44. exonware/xwnode/strategies/nodes/base.py +247 -0
  45. exonware/xwnode/strategies/nodes/deque.py +200 -0
  46. exonware/xwnode/strategies/nodes/hash_map.py +135 -0
  47. exonware/xwnode/strategies/nodes/heap.py +307 -0
  48. exonware/xwnode/strategies/nodes/linked_list.py +232 -0
  49. exonware/xwnode/strategies/nodes/node_aho_corasick.py +520 -0
  50. exonware/xwnode/strategies/nodes/node_array_list.py +175 -0
  51. exonware/xwnode/strategies/nodes/node_avl_tree.py +371 -0
  52. exonware/xwnode/strategies/nodes/node_b_plus_tree.py +542 -0
  53. exonware/xwnode/strategies/nodes/node_bitmap.py +420 -0
  54. exonware/xwnode/strategies/nodes/node_bitset_dynamic.py +513 -0
  55. exonware/xwnode/strategies/nodes/node_bloom_filter.py +347 -0
  56. exonware/xwnode/strategies/nodes/node_btree.py +357 -0
  57. exonware/xwnode/strategies/nodes/node_count_min_sketch.py +470 -0
  58. exonware/xwnode/strategies/nodes/node_cow_tree.py +473 -0
  59. exonware/xwnode/strategies/nodes/node_cuckoo_hash.py +392 -0
  60. exonware/xwnode/strategies/nodes/node_fenwick_tree.py +301 -0
  61. exonware/xwnode/strategies/nodes/node_hash_map.py +269 -0
  62. exonware/xwnode/strategies/nodes/node_heap.py +191 -0
  63. exonware/xwnode/strategies/nodes/node_hyperloglog.py +407 -0
  64. exonware/xwnode/strategies/nodes/node_linked_list.py +409 -0
  65. exonware/xwnode/strategies/nodes/node_lsm_tree.py +400 -0
  66. exonware/xwnode/strategies/nodes/node_ordered_map.py +390 -0
  67. exonware/xwnode/strategies/nodes/node_ordered_map_balanced.py +565 -0
  68. exonware/xwnode/strategies/nodes/node_patricia.py +512 -0
  69. exonware/xwnode/strategies/nodes/node_persistent_tree.py +378 -0
  70. exonware/xwnode/strategies/nodes/node_radix_trie.py +452 -0
  71. exonware/xwnode/strategies/nodes/node_red_black_tree.py +497 -0
  72. exonware/xwnode/strategies/nodes/node_roaring_bitmap.py +570 -0
  73. exonware/xwnode/strategies/nodes/node_segment_tree.py +289 -0
  74. exonware/xwnode/strategies/nodes/node_set_hash.py +354 -0
  75. exonware/xwnode/strategies/nodes/node_set_tree.py +480 -0
  76. exonware/xwnode/strategies/nodes/node_skip_list.py +316 -0
  77. exonware/xwnode/strategies/nodes/node_splay_tree.py +393 -0
  78. exonware/xwnode/strategies/nodes/node_suffix_array.py +487 -0
  79. exonware/xwnode/strategies/nodes/node_treap.py +387 -0
  80. exonware/xwnode/strategies/nodes/node_tree_graph_hybrid.py +1434 -0
  81. exonware/xwnode/strategies/nodes/node_trie.py +252 -0
  82. exonware/xwnode/strategies/nodes/node_union_find.py +187 -0
  83. exonware/xwnode/strategies/nodes/node_xdata_optimized.py +369 -0
  84. exonware/xwnode/strategies/nodes/priority_queue.py +209 -0
  85. exonware/xwnode/strategies/nodes/queue.py +161 -0
  86. exonware/xwnode/strategies/nodes/sparse_matrix.py +206 -0
  87. exonware/xwnode/strategies/nodes/stack.py +152 -0
  88. exonware/xwnode/strategies/nodes/trie.py +274 -0
  89. exonware/xwnode/strategies/nodes/union_find.py +283 -0
  90. exonware/xwnode/strategies/pattern_detector.py +603 -0
  91. exonware/xwnode/strategies/performance_monitor.py +487 -0
  92. exonware/xwnode/strategies/queries/__init__.py +24 -0
  93. exonware/xwnode/strategies/queries/base.py +236 -0
  94. exonware/xwnode/strategies/queries/cql.py +201 -0
  95. exonware/xwnode/strategies/queries/cypher.py +181 -0
  96. exonware/xwnode/strategies/queries/datalog.py +70 -0
  97. exonware/xwnode/strategies/queries/elastic_dsl.py +70 -0
  98. exonware/xwnode/strategies/queries/eql.py +70 -0
  99. exonware/xwnode/strategies/queries/flux.py +70 -0
  100. exonware/xwnode/strategies/queries/gql.py +70 -0
  101. exonware/xwnode/strategies/queries/graphql.py +240 -0
  102. exonware/xwnode/strategies/queries/gremlin.py +181 -0
  103. exonware/xwnode/strategies/queries/hiveql.py +214 -0
  104. exonware/xwnode/strategies/queries/hql.py +70 -0
  105. exonware/xwnode/strategies/queries/jmespath.py +219 -0
  106. exonware/xwnode/strategies/queries/jq.py +66 -0
  107. exonware/xwnode/strategies/queries/json_query.py +66 -0
  108. exonware/xwnode/strategies/queries/jsoniq.py +248 -0
  109. exonware/xwnode/strategies/queries/kql.py +70 -0
  110. exonware/xwnode/strategies/queries/linq.py +238 -0
  111. exonware/xwnode/strategies/queries/logql.py +70 -0
  112. exonware/xwnode/strategies/queries/mql.py +68 -0
  113. exonware/xwnode/strategies/queries/n1ql.py +210 -0
  114. exonware/xwnode/strategies/queries/partiql.py +70 -0
  115. exonware/xwnode/strategies/queries/pig.py +215 -0
  116. exonware/xwnode/strategies/queries/promql.py +70 -0
  117. exonware/xwnode/strategies/queries/sparql.py +220 -0
  118. exonware/xwnode/strategies/queries/sql.py +275 -0
  119. exonware/xwnode/strategies/queries/xml_query.py +66 -0
  120. exonware/xwnode/strategies/queries/xpath.py +223 -0
  121. exonware/xwnode/strategies/queries/xquery.py +258 -0
  122. exonware/xwnode/strategies/queries/xwnode_executor.py +332 -0
  123. exonware/xwnode/strategies/queries/xwquery_strategy.py +424 -0
  124. exonware/xwnode/strategies/registry.py +604 -0
  125. exonware/xwnode/strategies/simple.py +273 -0
  126. exonware/xwnode/strategies/utils.py +532 -0
  127. exonware/xwnode/types.py +912 -0
  128. exonware/xwnode/version.py +78 -0
  129. exonware_xwnode-0.0.1.12.dist-info/METADATA +169 -0
  130. exonware_xwnode-0.0.1.12.dist-info/RECORD +132 -0
  131. exonware_xwnode-0.0.1.12.dist-info/WHEEL +4 -0
  132. 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
+ }