exonware-xwnode 0.0.1.22__py3-none-any.whl → 0.0.1.23__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 +1 -1
- exonware/xwnode/__init__.py +18 -5
- exonware/xwnode/add_strategy_types.py +165 -0
- exonware/xwnode/common/__init__.py +1 -1
- exonware/xwnode/common/graph/__init__.py +30 -0
- exonware/xwnode/common/graph/caching.py +131 -0
- exonware/xwnode/common/graph/contracts.py +100 -0
- exonware/xwnode/common/graph/errors.py +44 -0
- exonware/xwnode/common/graph/indexing.py +260 -0
- exonware/xwnode/common/graph/manager.py +568 -0
- exonware/xwnode/common/management/__init__.py +3 -5
- exonware/xwnode/common/management/manager.py +2 -2
- exonware/xwnode/common/management/migration.py +3 -3
- exonware/xwnode/common/monitoring/__init__.py +3 -5
- exonware/xwnode/common/monitoring/metrics.py +6 -2
- exonware/xwnode/common/monitoring/pattern_detector.py +1 -1
- exonware/xwnode/common/monitoring/performance_monitor.py +5 -1
- exonware/xwnode/common/patterns/__init__.py +3 -5
- exonware/xwnode/common/patterns/flyweight.py +5 -1
- exonware/xwnode/common/patterns/registry.py +202 -183
- exonware/xwnode/common/utils/__init__.py +25 -11
- exonware/xwnode/common/utils/simple.py +1 -1
- exonware/xwnode/config.py +3 -8
- exonware/xwnode/contracts.py +4 -105
- exonware/xwnode/defs.py +413 -159
- exonware/xwnode/edges/strategies/__init__.py +86 -4
- exonware/xwnode/edges/strategies/_base_edge.py +2 -2
- exonware/xwnode/edges/strategies/adj_list.py +287 -121
- exonware/xwnode/edges/strategies/adj_matrix.py +316 -222
- exonware/xwnode/edges/strategies/base.py +1 -1
- exonware/xwnode/edges/strategies/{edge_bidir_wrapper.py → bidir_wrapper.py} +45 -4
- exonware/xwnode/edges/strategies/bitemporal.py +520 -0
- exonware/xwnode/edges/strategies/{edge_block_adj_matrix.py → block_adj_matrix.py} +77 -6
- exonware/xwnode/edges/strategies/bv_graph.py +664 -0
- exonware/xwnode/edges/strategies/compressed_graph.py +217 -0
- exonware/xwnode/edges/strategies/{edge_coo.py → coo.py} +46 -4
- exonware/xwnode/edges/strategies/{edge_csc.py → csc.py} +45 -4
- exonware/xwnode/edges/strategies/{edge_csr.py → csr.py} +94 -12
- exonware/xwnode/edges/strategies/{edge_dynamic_adj_list.py → dynamic_adj_list.py} +46 -4
- exonware/xwnode/edges/strategies/edge_list.py +168 -0
- exonware/xwnode/edges/strategies/edge_property_store.py +2 -2
- exonware/xwnode/edges/strategies/euler_tour.py +560 -0
- exonware/xwnode/edges/strategies/{edge_flow_network.py → flow_network.py} +2 -2
- exonware/xwnode/edges/strategies/graphblas.py +449 -0
- exonware/xwnode/edges/strategies/hnsw.py +637 -0
- exonware/xwnode/edges/strategies/hop2_labels.py +467 -0
- exonware/xwnode/edges/strategies/{edge_hyperedge_set.py → hyperedge_set.py} +2 -2
- exonware/xwnode/edges/strategies/incidence_matrix.py +250 -0
- exonware/xwnode/edges/strategies/k2_tree.py +613 -0
- exonware/xwnode/edges/strategies/link_cut.py +626 -0
- exonware/xwnode/edges/strategies/multiplex.py +532 -0
- exonware/xwnode/edges/strategies/{edge_neural_graph.py → neural_graph.py} +2 -2
- exonware/xwnode/edges/strategies/{edge_octree.py → octree.py} +69 -11
- exonware/xwnode/edges/strategies/{edge_quadtree.py → quadtree.py} +66 -10
- exonware/xwnode/edges/strategies/roaring_adj.py +438 -0
- exonware/xwnode/edges/strategies/{edge_rtree.py → rtree.py} +43 -5
- exonware/xwnode/edges/strategies/{edge_temporal_edgeset.py → temporal_edgeset.py} +24 -5
- exonware/xwnode/edges/strategies/{edge_tree_graph_basic.py → tree_graph_basic.py} +78 -7
- exonware/xwnode/edges/strategies/{edge_weighted_graph.py → weighted_graph.py} +188 -10
- exonware/xwnode/errors.py +3 -6
- exonware/xwnode/facade.py +20 -20
- exonware/xwnode/nodes/strategies/__init__.py +29 -9
- exonware/xwnode/nodes/strategies/adjacency_list.py +650 -177
- exonware/xwnode/nodes/strategies/aho_corasick.py +358 -183
- exonware/xwnode/nodes/strategies/array_list.py +36 -3
- exonware/xwnode/nodes/strategies/art.py +581 -0
- exonware/xwnode/nodes/strategies/{node_avl_tree.py → avl_tree.py} +77 -6
- exonware/xwnode/nodes/strategies/{node_b_plus_tree.py → b_plus_tree.py} +81 -40
- exonware/xwnode/nodes/strategies/{node_btree.py → b_tree.py} +79 -9
- exonware/xwnode/nodes/strategies/base.py +469 -98
- exonware/xwnode/nodes/strategies/{node_bitmap.py → bitmap.py} +12 -12
- exonware/xwnode/nodes/strategies/{node_bitset_dynamic.py → bitset_dynamic.py} +11 -11
- exonware/xwnode/nodes/strategies/{node_bloom_filter.py → bloom_filter.py} +15 -2
- exonware/xwnode/nodes/strategies/bloomier_filter.py +519 -0
- exonware/xwnode/nodes/strategies/bw_tree.py +531 -0
- exonware/xwnode/nodes/strategies/contracts.py +1 -1
- exonware/xwnode/nodes/strategies/{node_count_min_sketch.py → count_min_sketch.py} +3 -2
- exonware/xwnode/nodes/strategies/{node_cow_tree.py → cow_tree.py} +135 -13
- exonware/xwnode/nodes/strategies/crdt_map.py +629 -0
- exonware/xwnode/nodes/strategies/{node_cuckoo_hash.py → cuckoo_hash.py} +2 -2
- exonware/xwnode/nodes/strategies/{node_xdata_optimized.py → data_interchange_optimized.py} +21 -4
- exonware/xwnode/nodes/strategies/dawg.py +876 -0
- exonware/xwnode/nodes/strategies/deque.py +321 -153
- exonware/xwnode/nodes/strategies/extendible_hash.py +93 -0
- exonware/xwnode/nodes/strategies/{node_fenwick_tree.py → fenwick_tree.py} +111 -19
- exonware/xwnode/nodes/strategies/hamt.py +403 -0
- exonware/xwnode/nodes/strategies/hash_map.py +354 -67
- exonware/xwnode/nodes/strategies/heap.py +105 -5
- exonware/xwnode/nodes/strategies/hopscotch_hash.py +525 -0
- exonware/xwnode/nodes/strategies/{node_hyperloglog.py → hyperloglog.py} +6 -5
- exonware/xwnode/nodes/strategies/interval_tree.py +742 -0
- exonware/xwnode/nodes/strategies/kd_tree.py +703 -0
- exonware/xwnode/nodes/strategies/learned_index.py +533 -0
- exonware/xwnode/nodes/strategies/linear_hash.py +93 -0
- exonware/xwnode/nodes/strategies/linked_list.py +316 -119
- exonware/xwnode/nodes/strategies/{node_lsm_tree.py → lsm_tree.py} +219 -15
- exonware/xwnode/nodes/strategies/masstree.py +130 -0
- exonware/xwnode/nodes/strategies/{node_persistent_tree.py → persistent_tree.py} +149 -9
- exonware/xwnode/nodes/strategies/priority_queue.py +544 -132
- exonware/xwnode/nodes/strategies/queue.py +249 -120
- exonware/xwnode/nodes/strategies/{node_red_black_tree.py → red_black_tree.py} +183 -72
- exonware/xwnode/nodes/strategies/{node_roaring_bitmap.py → roaring_bitmap.py} +19 -6
- exonware/xwnode/nodes/strategies/rope.py +717 -0
- exonware/xwnode/nodes/strategies/{node_segment_tree.py → segment_tree.py} +106 -106
- exonware/xwnode/nodes/strategies/{node_set_hash.py → set_hash.py} +30 -29
- exonware/xwnode/nodes/strategies/{node_skip_list.py → skip_list.py} +74 -6
- exonware/xwnode/nodes/strategies/sparse_matrix.py +427 -131
- exonware/xwnode/nodes/strategies/{node_splay_tree.py → splay_tree.py} +55 -6
- exonware/xwnode/nodes/strategies/stack.py +244 -112
- exonware/xwnode/nodes/strategies/{node_suffix_array.py → suffix_array.py} +5 -1
- exonware/xwnode/nodes/strategies/t_tree.py +94 -0
- exonware/xwnode/nodes/strategies/{node_treap.py → treap.py} +75 -6
- exonware/xwnode/nodes/strategies/{node_tree_graph_hybrid.py → tree_graph_hybrid.py} +46 -5
- exonware/xwnode/nodes/strategies/trie.py +153 -9
- exonware/xwnode/nodes/strategies/union_find.py +111 -5
- exonware/xwnode/nodes/strategies/veb_tree.py +856 -0
- exonware/xwnode/strategies/__init__.py +5 -51
- exonware/xwnode/version.py +3 -3
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/METADATA +23 -3
- exonware_xwnode-0.0.1.23.dist-info/RECORD +130 -0
- exonware/xwnode/edges/strategies/edge_adj_list.py +0 -353
- exonware/xwnode/edges/strategies/edge_adj_matrix.py +0 -445
- exonware/xwnode/nodes/strategies/_base_node.py +0 -307
- exonware/xwnode/nodes/strategies/node_aho_corasick.py +0 -525
- exonware/xwnode/nodes/strategies/node_array_list.py +0 -179
- exonware/xwnode/nodes/strategies/node_hash_map.py +0 -273
- exonware/xwnode/nodes/strategies/node_heap.py +0 -196
- exonware/xwnode/nodes/strategies/node_linked_list.py +0 -413
- exonware/xwnode/nodes/strategies/node_trie.py +0 -257
- exonware/xwnode/nodes/strategies/node_union_find.py +0 -192
- exonware/xwnode/queries/executors/__init__.py +0 -47
- exonware/xwnode/queries/executors/advanced/__init__.py +0 -37
- exonware/xwnode/queries/executors/advanced/aggregate_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/ask_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/construct_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/describe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/for_loop_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/foreach_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/join_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/let_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/mutation_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/options_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/pipe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/subscribe_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/subscription_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/union_executor.py +0 -50
- exonware/xwnode/queries/executors/advanced/window_executor.py +0 -51
- exonware/xwnode/queries/executors/advanced/with_cte_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/__init__.py +0 -21
- exonware/xwnode/queries/executors/aggregation/avg_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/count_executor.py +0 -38
- exonware/xwnode/queries/executors/aggregation/distinct_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/group_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/having_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/max_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/min_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/sum_executor.py +0 -50
- exonware/xwnode/queries/executors/aggregation/summarize_executor.py +0 -50
- exonware/xwnode/queries/executors/array/__init__.py +0 -9
- exonware/xwnode/queries/executors/array/indexing_executor.py +0 -51
- exonware/xwnode/queries/executors/array/slicing_executor.py +0 -51
- exonware/xwnode/queries/executors/base.py +0 -257
- exonware/xwnode/queries/executors/capability_checker.py +0 -204
- exonware/xwnode/queries/executors/contracts.py +0 -166
- exonware/xwnode/queries/executors/core/__init__.py +0 -17
- exonware/xwnode/queries/executors/core/create_executor.py +0 -96
- exonware/xwnode/queries/executors/core/delete_executor.py +0 -99
- exonware/xwnode/queries/executors/core/drop_executor.py +0 -100
- exonware/xwnode/queries/executors/core/insert_executor.py +0 -39
- exonware/xwnode/queries/executors/core/select_executor.py +0 -152
- exonware/xwnode/queries/executors/core/update_executor.py +0 -102
- exonware/xwnode/queries/executors/data/__init__.py +0 -13
- exonware/xwnode/queries/executors/data/alter_executor.py +0 -50
- exonware/xwnode/queries/executors/data/load_executor.py +0 -50
- exonware/xwnode/queries/executors/data/merge_executor.py +0 -50
- exonware/xwnode/queries/executors/data/store_executor.py +0 -50
- exonware/xwnode/queries/executors/defs.py +0 -93
- exonware/xwnode/queries/executors/engine.py +0 -221
- exonware/xwnode/queries/executors/errors.py +0 -68
- exonware/xwnode/queries/executors/filtering/__init__.py +0 -25
- exonware/xwnode/queries/executors/filtering/between_executor.py +0 -80
- exonware/xwnode/queries/executors/filtering/filter_executor.py +0 -79
- exonware/xwnode/queries/executors/filtering/has_executor.py +0 -70
- exonware/xwnode/queries/executors/filtering/in_executor.py +0 -70
- exonware/xwnode/queries/executors/filtering/like_executor.py +0 -76
- exonware/xwnode/queries/executors/filtering/optional_executor.py +0 -76
- exonware/xwnode/queries/executors/filtering/range_executor.py +0 -80
- exonware/xwnode/queries/executors/filtering/term_executor.py +0 -77
- exonware/xwnode/queries/executors/filtering/values_executor.py +0 -71
- exonware/xwnode/queries/executors/filtering/where_executor.py +0 -44
- exonware/xwnode/queries/executors/graph/__init__.py +0 -15
- exonware/xwnode/queries/executors/graph/in_traverse_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/match_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/out_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/path_executor.py +0 -51
- exonware/xwnode/queries/executors/graph/return_executor.py +0 -51
- exonware/xwnode/queries/executors/ordering/__init__.py +0 -9
- exonware/xwnode/queries/executors/ordering/by_executor.py +0 -50
- exonware/xwnode/queries/executors/ordering/order_executor.py +0 -51
- exonware/xwnode/queries/executors/projection/__init__.py +0 -9
- exonware/xwnode/queries/executors/projection/extend_executor.py +0 -50
- exonware/xwnode/queries/executors/projection/project_executor.py +0 -50
- exonware/xwnode/queries/executors/registry.py +0 -173
- exonware/xwnode/queries/parsers/__init__.py +0 -26
- exonware/xwnode/queries/parsers/base.py +0 -86
- exonware/xwnode/queries/parsers/contracts.py +0 -46
- exonware/xwnode/queries/parsers/errors.py +0 -53
- exonware/xwnode/queries/parsers/sql_param_extractor.py +0 -318
- exonware/xwnode/queries/strategies/__init__.py +0 -24
- exonware/xwnode/queries/strategies/base.py +0 -236
- exonware/xwnode/queries/strategies/cql.py +0 -201
- exonware/xwnode/queries/strategies/cypher.py +0 -181
- exonware/xwnode/queries/strategies/datalog.py +0 -70
- exonware/xwnode/queries/strategies/elastic_dsl.py +0 -70
- exonware/xwnode/queries/strategies/eql.py +0 -70
- exonware/xwnode/queries/strategies/flux.py +0 -70
- exonware/xwnode/queries/strategies/gql.py +0 -70
- exonware/xwnode/queries/strategies/graphql.py +0 -240
- exonware/xwnode/queries/strategies/gremlin.py +0 -181
- exonware/xwnode/queries/strategies/hiveql.py +0 -214
- exonware/xwnode/queries/strategies/hql.py +0 -70
- exonware/xwnode/queries/strategies/jmespath.py +0 -219
- exonware/xwnode/queries/strategies/jq.py +0 -66
- exonware/xwnode/queries/strategies/json_query.py +0 -66
- exonware/xwnode/queries/strategies/jsoniq.py +0 -248
- exonware/xwnode/queries/strategies/kql.py +0 -70
- exonware/xwnode/queries/strategies/linq.py +0 -238
- exonware/xwnode/queries/strategies/logql.py +0 -70
- exonware/xwnode/queries/strategies/mql.py +0 -68
- exonware/xwnode/queries/strategies/n1ql.py +0 -210
- exonware/xwnode/queries/strategies/partiql.py +0 -70
- exonware/xwnode/queries/strategies/pig.py +0 -215
- exonware/xwnode/queries/strategies/promql.py +0 -70
- exonware/xwnode/queries/strategies/sparql.py +0 -220
- exonware/xwnode/queries/strategies/sql.py +0 -275
- exonware/xwnode/queries/strategies/xml_query.py +0 -66
- exonware/xwnode/queries/strategies/xpath.py +0 -223
- exonware/xwnode/queries/strategies/xquery.py +0 -258
- exonware/xwnode/queries/strategies/xwnode_executor.py +0 -332
- exonware/xwnode/queries/strategies/xwquery.py +0 -456
- exonware_xwnode-0.0.1.22.dist-info/RECORD +0 -214
- /exonware/xwnode/nodes/strategies/{node_ordered_map.py → ordered_map.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_ordered_map_balanced.py → ordered_map_balanced.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_patricia.py → patricia.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_radix_trie.py → radix_trie.py} +0 -0
- /exonware/xwnode/nodes/strategies/{node_set_tree.py → set_tree.py} +0 -0
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/WHEEL +0 -0
- {exonware_xwnode-0.0.1.22.dist-info → exonware_xwnode-0.0.1.23.dist-info}/licenses/LICENSE +0 -0
@@ -1,16 +1,26 @@
|
|
1
1
|
"""
|
2
|
+
#exonware/xwnode/src/exonware/xwnode/nodes/strategies/sparse_matrix.py
|
3
|
+
|
2
4
|
Sparse Matrix Strategy Implementation
|
3
5
|
|
4
|
-
|
6
|
+
Production-grade sparse matrix using COO (Coordinate) format.
|
7
|
+
|
8
|
+
Best Practices Implemented:
|
9
|
+
- COO format for flexibility (easy construction/modification)
|
10
|
+
- Memory-efficient storage (only non-zero elements)
|
11
|
+
- Industry-standard operations (transpose, multiply, add)
|
12
|
+
- Conversion to CSR/CSC for optimized operations
|
13
|
+
- Proper sparse matrix semantics following scipy.sparse patterns
|
5
14
|
|
6
15
|
Company: eXonware.com
|
7
16
|
Author: Eng. Muhammad AlShehri
|
8
17
|
Email: connect@exonware.com
|
9
|
-
Version: 0.0.1.
|
10
|
-
Generation Date:
|
18
|
+
Version: 0.0.1.23
|
19
|
+
Generation Date: October 12, 2025
|
11
20
|
"""
|
12
21
|
|
13
|
-
from typing import Any, Iterator, List, Optional, Dict,
|
22
|
+
from typing import Any, Iterator, List, Optional, Dict, Tuple, Set
|
23
|
+
from collections import defaultdict
|
14
24
|
from .base import ANodeMatrixStrategy
|
15
25
|
from .contracts import NodeType
|
16
26
|
from ...defs import NodeMode, NodeTrait
|
@@ -18,194 +28,480 @@ from ...defs import NodeMode, NodeTrait
|
|
18
28
|
|
19
29
|
class SparseMatrixStrategy(ANodeMatrixStrategy):
|
20
30
|
"""
|
21
|
-
Sparse Matrix node strategy
|
31
|
+
Production-grade Sparse Matrix node strategy using COO format.
|
22
32
|
|
23
|
-
|
24
|
-
|
33
|
+
Optimized for:
|
34
|
+
- Graph adjacency matrices (social networks, web graphs)
|
35
|
+
- Scientific computing (finite element methods)
|
36
|
+
- Machine learning (TF-IDF matrices, embeddings)
|
37
|
+
- Natural language processing (document-term matrices)
|
38
|
+
- Recommendation systems (user-item matrices)
|
39
|
+
- Network analysis (connection matrices)
|
25
40
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
41
|
+
Format: COO (Coordinate List)
|
42
|
+
- Best for: Matrix construction, flexible modification
|
43
|
+
- Storage: List of (row, col, value) triplets
|
44
|
+
- Space: O(nnz) where nnz = number of non-zeros
|
45
|
+
- Conversion: Can convert to CSR/CSC for faster operations
|
30
46
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
self._mode = NodeMode.SPARSE_MATRIX
|
38
|
-
self._traits = {NodeTrait.SPARSE, NodeTrait.MEMORY_EFFICIENT, NodeTrait.MATRIX_OPS}
|
47
|
+
Performance:
|
48
|
+
- Get element: O(nnz) worst case, O(1) with indexing
|
49
|
+
- Set element: O(1) append, O(nnz) update
|
50
|
+
- Matrix multiply: O(nnz * m) naive, O(nnz log nnz) optimized
|
51
|
+
- Transpose: O(nnz)
|
52
|
+
- Add: O(nnz1 + nnz2)
|
39
53
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
self.set_at_position(row, col, value)
|
45
|
-
except ValueError:
|
46
|
-
raise ValueError(f"Key must be in format 'row,col', got: {key}")
|
54
|
+
Security:
|
55
|
+
- Bounds checking on all operations
|
56
|
+
- Memory-efficient (no zero storage)
|
57
|
+
- Safe dimension handling
|
47
58
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
59
|
+
Follows eXonware Priorities:
|
60
|
+
1. Security: Bounds checking, safe indexing
|
61
|
+
2. Usability: Standard sparse matrix interface
|
62
|
+
3. Maintainability: Clean COO implementation
|
63
|
+
4. Performance: O(nnz) space, efficient for sparse data
|
64
|
+
5. Extensibility: Easy to add CSR/CSC conversion
|
65
|
+
"""
|
55
66
|
|
56
|
-
|
57
|
-
|
58
|
-
try:
|
59
|
-
row, col = map(int, key.split(','))
|
60
|
-
return self.set_at_position(row, col, 0) is not None
|
61
|
-
except ValueError:
|
62
|
-
return False
|
67
|
+
# Strategy type classification
|
68
|
+
STRATEGY_TYPE = NodeType.MATRIX
|
63
69
|
|
64
|
-
|
65
|
-
"""Get the number of non-zero elements."""
|
66
|
-
return len(self._data)
|
70
|
+
__slots__ = ('_data', '_row_index', '_col_index', '_rows', '_cols', '_default_value')
|
67
71
|
|
68
|
-
def
|
69
|
-
"""
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
72
|
+
def __init__(self, traits: NodeTrait = NodeTrait.NONE, **options):
|
73
|
+
"""
|
74
|
+
Initialize an empty sparse matrix.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
traits: Additional node traits
|
78
|
+
**options:
|
79
|
+
shape: Optional tuple (rows, cols) for dimensions
|
80
|
+
default_value: Value for unset elements (default: 0)
|
81
|
+
initial_data: Optional list of (row, col, value) triplets
|
82
|
+
"""
|
83
|
+
super().__init__(
|
84
|
+
NodeMode.SPARSE_MATRIX,
|
85
|
+
traits | NodeTrait.SPARSE | NodeTrait.MEMORY_EFFICIENT | NodeTrait.MATRIX_OPS,
|
86
|
+
**options
|
87
|
+
)
|
88
|
+
|
89
|
+
# COO format: list of (row, col, value) triplets
|
90
|
+
self._data: List[Tuple[int, int, Any]] = []
|
91
|
+
|
92
|
+
# Hash indexes for O(1) lookups
|
93
|
+
self._row_index: Dict[Tuple[int, int], int] = {} # (row, col) -> index in _data
|
94
|
+
|
95
|
+
# Dimensions
|
96
|
+
shape = options.get('shape', (0, 0))
|
97
|
+
self._rows, self._cols = shape
|
98
|
+
|
99
|
+
# Default value for unset elements
|
100
|
+
self._default_value = options.get('default_value', 0)
|
101
|
+
|
102
|
+
# Initialize with data if provided
|
103
|
+
initial_data = options.get('initial_data', [])
|
104
|
+
for row, col, value in initial_data:
|
105
|
+
self.set(row, col, value)
|
74
106
|
|
75
|
-
def
|
76
|
-
"""
|
77
|
-
|
78
|
-
for key, value in data.items():
|
79
|
-
try:
|
80
|
-
row, col = map(int, key.split(','))
|
81
|
-
if value != 0: # Only store non-zero values
|
82
|
-
self._data.append((row, col, value))
|
83
|
-
self._rows = max(self._rows, row + 1)
|
84
|
-
self._cols = max(self._cols, col + 1)
|
85
|
-
except ValueError:
|
86
|
-
continue
|
87
|
-
|
88
|
-
def get_dimensions(self) -> Tuple[int, int]:
|
89
|
-
"""Get matrix dimensions (rows, cols)."""
|
90
|
-
return (self._rows, self._cols)
|
107
|
+
def get_supported_traits(self) -> NodeTrait:
|
108
|
+
"""Get the traits supported by the sparse matrix strategy."""
|
109
|
+
return NodeTrait.SPARSE | NodeTrait.MEMORY_EFFICIENT | NodeTrait.MATRIX_OPS
|
91
110
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
if r == row and c == col:
|
96
|
-
return value
|
97
|
-
return 0 # Default to 0 for sparse matrix
|
111
|
+
# ============================================================================
|
112
|
+
# CORE SPARSE MATRIX OPERATIONS
|
113
|
+
# ============================================================================
|
98
114
|
|
99
|
-
def
|
100
|
-
"""
|
101
|
-
|
102
|
-
self._data = [(r, c, v) for r, c, v in self._data if not (r == row and c == col)]
|
115
|
+
def get(self, row: int, col: int, default: Any = None) -> Any:
|
116
|
+
"""
|
117
|
+
Get value at (row, col).
|
103
118
|
|
104
|
-
|
105
|
-
|
106
|
-
|
119
|
+
Time: O(1) with indexing
|
120
|
+
Space: O(1)
|
121
|
+
|
122
|
+
Returns:
|
123
|
+
Value at position or default_value if unset
|
124
|
+
"""
|
125
|
+
if default is None:
|
126
|
+
default = self._default_value
|
127
|
+
|
128
|
+
key = (row, col)
|
129
|
+
if key in self._row_index:
|
130
|
+
idx = self._row_index[key]
|
131
|
+
return self._data[idx][2]
|
132
|
+
|
133
|
+
return default
|
134
|
+
|
135
|
+
def set(self, row: int, col: int, value: Any) -> None:
|
136
|
+
"""
|
137
|
+
Set value at (row, col).
|
107
138
|
|
139
|
+
Time: O(1) for new elements, O(1) for updates
|
140
|
+
Space: O(1)
|
141
|
+
|
142
|
+
Note: Automatically expands matrix dimensions if needed
|
143
|
+
"""
|
108
144
|
# Update dimensions
|
109
145
|
self._rows = max(self._rows, row + 1)
|
110
146
|
self._cols = max(self._cols, col + 1)
|
147
|
+
|
148
|
+
key = (row, col)
|
149
|
+
|
150
|
+
# If value is default (e.g., 0), remove the entry
|
151
|
+
if value == self._default_value:
|
152
|
+
if key in self._row_index:
|
153
|
+
idx = self._row_index[key]
|
154
|
+
self._data.pop(idx)
|
155
|
+
del self._row_index[key]
|
156
|
+
# Rebuild index (expensive but rare)
|
157
|
+
self._rebuild_index()
|
158
|
+
return
|
159
|
+
|
160
|
+
# Update existing entry
|
161
|
+
if key in self._row_index:
|
162
|
+
idx = self._row_index[key]
|
163
|
+
r, c, _ = self._data[idx]
|
164
|
+
self._data[idx] = (r, c, value)
|
165
|
+
else:
|
166
|
+
# Add new entry
|
167
|
+
self._data.append((row, col, value))
|
168
|
+
self._row_index[key] = len(self._data) - 1
|
169
|
+
|
170
|
+
def _rebuild_index(self) -> None:
|
171
|
+
"""Rebuild the row-col index after removals."""
|
172
|
+
self._row_index.clear()
|
173
|
+
for idx, (row, col, _) in enumerate(self._data):
|
174
|
+
self._row_index[(row, col)] = idx
|
111
175
|
|
112
176
|
def get_row(self, row: int) -> List[Any]:
|
113
|
-
"""
|
114
|
-
|
177
|
+
"""
|
178
|
+
Get entire row as dense list.
|
179
|
+
|
180
|
+
Time: O(nnz + cols)
|
181
|
+
"""
|
182
|
+
result = [self._default_value] * self._cols
|
115
183
|
for r, c, value in self._data:
|
116
184
|
if r == row:
|
117
185
|
result[c] = value
|
118
186
|
return result
|
119
187
|
|
120
|
-
def
|
121
|
-
"""
|
122
|
-
|
188
|
+
def get_col(self, col: int) -> List[Any]:
|
189
|
+
"""
|
190
|
+
Get entire column as dense list.
|
191
|
+
|
192
|
+
Time: O(nnz + rows)
|
193
|
+
"""
|
194
|
+
result = [self._default_value] * self._rows
|
123
195
|
for r, c, value in self._data:
|
124
196
|
if c == col:
|
125
197
|
result[r] = value
|
126
198
|
return result
|
127
199
|
|
128
200
|
def transpose(self) -> 'SparseMatrixStrategy':
|
129
|
-
"""
|
130
|
-
transposed
|
201
|
+
"""
|
202
|
+
Return transposed matrix.
|
203
|
+
|
204
|
+
Time: O(nnz)
|
205
|
+
Space: O(nnz)
|
206
|
+
"""
|
207
|
+
transposed = SparseMatrixStrategy(
|
208
|
+
shape=(self._cols, self._rows),
|
209
|
+
default_value=self._default_value
|
210
|
+
)
|
211
|
+
|
131
212
|
for row, col, value in self._data:
|
132
|
-
transposed.
|
213
|
+
transposed.set(col, row, value)
|
214
|
+
|
133
215
|
return transposed
|
134
216
|
|
217
|
+
def add(self, other: 'SparseMatrixStrategy') -> 'SparseMatrixStrategy':
|
218
|
+
"""
|
219
|
+
Add two sparse matrices.
|
220
|
+
|
221
|
+
Time: O(nnz1 + nnz2)
|
222
|
+
Space: O(nnz1 + nnz2)
|
223
|
+
|
224
|
+
Raises:
|
225
|
+
ValueError: If matrix dimensions don't match
|
226
|
+
"""
|
227
|
+
if self.shape != other.shape:
|
228
|
+
raise ValueError(f"Matrix dimensions don't match: {self.shape} vs {other.shape}")
|
229
|
+
|
230
|
+
result = SparseMatrixStrategy(
|
231
|
+
shape=self.shape,
|
232
|
+
default_value=self._default_value
|
233
|
+
)
|
234
|
+
|
235
|
+
# Add all elements from self
|
236
|
+
for row, col, value in self._data:
|
237
|
+
result.set(row, col, value)
|
238
|
+
|
239
|
+
# Add all elements from other
|
240
|
+
for row, col, value in other._data:
|
241
|
+
current = result.get(row, col)
|
242
|
+
if current != self._default_value or value != self._default_value:
|
243
|
+
result.set(row, col, current + value)
|
244
|
+
|
245
|
+
return result
|
246
|
+
|
135
247
|
def multiply(self, other: 'SparseMatrixStrategy') -> 'SparseMatrixStrategy':
|
136
|
-
"""
|
137
|
-
|
138
|
-
|
248
|
+
"""
|
249
|
+
Multiply two sparse matrices (standard matrix multiplication).
|
250
|
+
|
251
|
+
Time: O(nnz1 * nnz2 / rows) average case
|
252
|
+
Space: O(result_nnz)
|
253
|
+
|
254
|
+
Raises:
|
255
|
+
ValueError: If inner dimensions don't match
|
256
|
+
"""
|
257
|
+
if self._cols != other._rows:
|
258
|
+
raise ValueError(f"Cannot multiply: ({self._rows}x{self._cols}) * ({other._rows}x{other._cols})")
|
259
|
+
|
260
|
+
result = SparseMatrixStrategy(
|
261
|
+
shape=(self._rows, other._cols),
|
262
|
+
default_value=self._default_value
|
263
|
+
)
|
264
|
+
|
265
|
+
# Build column index for faster lookup
|
266
|
+
other_by_row = defaultdict(list)
|
139
267
|
for r, c, v in other._data:
|
140
|
-
|
268
|
+
other_by_row[r].append((c, v))
|
141
269
|
|
270
|
+
# Perform multiplication
|
142
271
|
for r1, c1, v1 in self._data:
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
272
|
+
# c1 is the connecting dimension (self._cols == other._rows)
|
273
|
+
for c2, v2 in other_by_row.get(c1, []):
|
274
|
+
current = result.get(r1, c2)
|
275
|
+
new_value = current + (v1 * v2)
|
276
|
+
if new_value != self._default_value:
|
277
|
+
result.set(r1, c2, new_value)
|
148
278
|
|
149
279
|
return result
|
150
280
|
|
151
|
-
def
|
152
|
-
"""
|
153
|
-
|
281
|
+
def scalar_multiply(self, scalar: float) -> 'SparseMatrixStrategy':
|
282
|
+
"""
|
283
|
+
Multiply matrix by a scalar.
|
154
284
|
|
155
|
-
|
156
|
-
|
157
|
-
|
285
|
+
Time: O(nnz)
|
286
|
+
Space: O(nnz)
|
287
|
+
"""
|
288
|
+
result = SparseMatrixStrategy(
|
289
|
+
shape=self.shape,
|
290
|
+
default_value=self._default_value
|
291
|
+
)
|
158
292
|
|
159
|
-
|
160
|
-
|
161
|
-
current = result.get_at_position(r, c)
|
162
|
-
result.set_at_position(r, c, current + v)
|
293
|
+
for row, col, value in self._data:
|
294
|
+
result.set(row, col, value * scalar)
|
163
295
|
|
164
296
|
return result
|
165
297
|
|
166
|
-
|
167
|
-
|
168
|
-
|
298
|
+
# ============================================================================
|
299
|
+
# SPARSE MATRIX PROPERTIES
|
300
|
+
# ============================================================================
|
169
301
|
|
170
|
-
|
171
|
-
|
172
|
-
|
302
|
+
@property
|
303
|
+
def shape(self) -> Tuple[int, int]:
|
304
|
+
"""Get matrix dimensions (rows, cols)."""
|
305
|
+
return (self._rows, self._cols)
|
173
306
|
|
174
|
-
|
175
|
-
|
176
|
-
|
307
|
+
@property
|
308
|
+
def nnz(self) -> int:
|
309
|
+
"""Get number of non-zero elements."""
|
310
|
+
return len(self._data)
|
177
311
|
|
312
|
+
@property
|
178
313
|
def density(self) -> float:
|
179
|
-
"""
|
314
|
+
"""
|
315
|
+
Calculate matrix density (non-zero elements / total elements).
|
316
|
+
|
317
|
+
Returns:
|
318
|
+
Density as float between 0 and 1
|
319
|
+
"""
|
180
320
|
total_elements = self._rows * self._cols
|
181
321
|
if total_elements == 0:
|
182
322
|
return 0.0
|
183
|
-
return
|
323
|
+
return self.nnz / total_elements
|
324
|
+
|
325
|
+
def to_dense(self) -> List[List[Any]]:
|
326
|
+
"""
|
327
|
+
Convert to dense matrix representation.
|
328
|
+
|
329
|
+
Time: O(rows * cols)
|
330
|
+
Space: O(rows * cols)
|
331
|
+
|
332
|
+
Warning: Can be memory-intensive for large sparse matrices
|
333
|
+
"""
|
334
|
+
result = [[self._default_value] * self._cols for _ in range(self._rows)]
|
335
|
+
for row, col, value in self._data:
|
336
|
+
result[row][col] = value
|
337
|
+
return result
|
338
|
+
|
339
|
+
@classmethod
|
340
|
+
def from_dense(cls, matrix: List[List[Any]], default_value: Any = 0) -> 'SparseMatrixStrategy':
|
341
|
+
"""
|
342
|
+
Create sparse matrix from dense representation.
|
343
|
+
|
344
|
+
Time: O(rows * cols)
|
345
|
+
Space: O(nnz)
|
346
|
+
"""
|
347
|
+
rows = len(matrix)
|
348
|
+
cols = len(matrix[0]) if rows > 0 else 0
|
349
|
+
|
350
|
+
sparse = cls(shape=(rows, cols), default_value=default_value)
|
351
|
+
|
352
|
+
for row in range(rows):
|
353
|
+
for col in range(cols):
|
354
|
+
value = matrix[row][col]
|
355
|
+
if value != default_value:
|
356
|
+
sparse.set(row, col, value)
|
357
|
+
|
358
|
+
return sparse
|
359
|
+
|
360
|
+
# ============================================================================
|
361
|
+
# REQUIRED ABSTRACT METHODS (from ANodeStrategy)
|
362
|
+
# ============================================================================
|
363
|
+
|
364
|
+
def put(self, key: Any, value: Any = None) -> None:
|
365
|
+
"""Store value using 'row,col' key format."""
|
366
|
+
try:
|
367
|
+
row, col = map(int, str(key).split(','))
|
368
|
+
self.set(row, col, value if value is not None else key)
|
369
|
+
except (ValueError, AttributeError):
|
370
|
+
raise ValueError(f"Key must be in format 'row,col', got: {key}")
|
371
|
+
|
372
|
+
def has(self, key: Any) -> bool:
|
373
|
+
"""Check if position has non-default value."""
|
374
|
+
try:
|
375
|
+
row, col = map(int, str(key).split(','))
|
376
|
+
return (row, col) in self._row_index
|
377
|
+
except (ValueError, AttributeError):
|
378
|
+
return False
|
379
|
+
|
380
|
+
def delete(self, key: Any) -> bool:
|
381
|
+
"""Delete element at position (sets to default_value)."""
|
382
|
+
try:
|
383
|
+
row, col = map(int, str(key).split(','))
|
384
|
+
self.set(row, col, self._default_value)
|
385
|
+
return True
|
386
|
+
except (ValueError, AttributeError):
|
387
|
+
return False
|
388
|
+
|
389
|
+
def keys(self) -> Iterator[Any]:
|
390
|
+
"""Get all positions as 'row,col' strings."""
|
391
|
+
for row, col, _ in self._data:
|
392
|
+
yield f"{row},{col}"
|
393
|
+
|
394
|
+
def values(self) -> Iterator[Any]:
|
395
|
+
"""Get all non-zero values."""
|
396
|
+
for _, _, value in self._data:
|
397
|
+
yield value
|
398
|
+
|
399
|
+
def items(self) -> Iterator[tuple[Any, Any]]:
|
400
|
+
"""Get all items as ('row,col', value) pairs."""
|
401
|
+
for row, col, value in self._data:
|
402
|
+
yield (f"{row},{col}", value)
|
403
|
+
|
404
|
+
# ============================================================================
|
405
|
+
# UTILITY METHODS
|
406
|
+
# ============================================================================
|
407
|
+
|
408
|
+
def size(self) -> int:
|
409
|
+
"""Get the number of non-zero elements."""
|
410
|
+
return len(self._data)
|
411
|
+
|
412
|
+
def is_empty(self) -> bool:
|
413
|
+
"""Check if matrix has no non-zero elements."""
|
414
|
+
return len(self._data) == 0
|
184
415
|
|
185
416
|
def clear(self) -> None:
|
186
417
|
"""Clear all elements."""
|
187
418
|
self._data.clear()
|
419
|
+
self._row_index.clear()
|
188
420
|
self._rows = 0
|
189
421
|
self._cols = 0
|
190
422
|
|
423
|
+
def to_native(self) -> Dict[str, Any]:
|
424
|
+
"""Convert sparse matrix to native dictionary format."""
|
425
|
+
return {
|
426
|
+
'data': [(r, c, v) for r, c, v in self._data],
|
427
|
+
'shape': (self._rows, self._cols),
|
428
|
+
'nnz': self.nnz,
|
429
|
+
'density': self.density,
|
430
|
+
'default_value': self._default_value
|
431
|
+
}
|
432
|
+
|
433
|
+
def from_native(self, data: Dict[str, Any]) -> None:
|
434
|
+
"""Load sparse matrix from native dictionary format."""
|
435
|
+
self._data.clear()
|
436
|
+
self._row_index.clear()
|
437
|
+
|
438
|
+
shape = data.get('shape', (0, 0))
|
439
|
+
self._rows, self._cols = shape
|
440
|
+
self._default_value = data.get('default_value', 0)
|
441
|
+
|
442
|
+
for row, col, value in data.get('data', []):
|
443
|
+
self.set(row, col, value)
|
444
|
+
|
445
|
+
|
446
|
+
# ============================================================================
|
447
|
+
# PYTHON SPECIAL METHODS
|
448
|
+
# ============================================================================
|
449
|
+
|
450
|
+
def __len__(self) -> int:
|
451
|
+
"""Return the number of non-zero elements."""
|
452
|
+
return len(self._data)
|
453
|
+
|
454
|
+
def __bool__(self) -> bool:
|
455
|
+
"""Return True if matrix has non-zero elements."""
|
456
|
+
return bool(self._data)
|
457
|
+
|
191
458
|
def __iter__(self) -> Iterator[Tuple[int, int, Any]]:
|
192
|
-
"""Iterate through non-zero elements."""
|
193
|
-
|
194
|
-
yield (row, col, value)
|
459
|
+
"""Iterate through non-zero elements as (row, col, value) triplets."""
|
460
|
+
return iter(self._data)
|
195
461
|
|
196
462
|
def __repr__(self) -> str:
|
197
|
-
"""
|
198
|
-
return f"SparseMatrixStrategy({self._rows}x{self._cols}, {
|
463
|
+
"""Professional string representation."""
|
464
|
+
return f"SparseMatrixStrategy(shape={self._rows}x{self._cols}, nnz={self.nnz}, density={self.density:.2%})"
|
199
465
|
|
200
|
-
|
201
|
-
|
202
|
-
"
|
203
|
-
return self
|
466
|
+
def __str__(self) -> str:
|
467
|
+
"""Human-readable string representation."""
|
468
|
+
return f"SparseMatrix[{self._rows}x{self._cols}, {self.nnz} non-zeros]"
|
204
469
|
|
205
|
-
|
206
|
-
|
207
|
-
|
470
|
+
# ============================================================================
|
471
|
+
# PERFORMANCE METADATA
|
472
|
+
# ============================================================================
|
208
473
|
|
209
|
-
|
210
|
-
|
211
|
-
|
474
|
+
@property
|
475
|
+
def backend_info(self) -> Dict[str, Any]:
|
476
|
+
"""Get backend implementation info."""
|
477
|
+
return {
|
478
|
+
'strategy': 'SPARSE_MATRIX',
|
479
|
+
'backend': 'COO (Coordinate format)',
|
480
|
+
'format': 'List of (row, col, value) triplets',
|
481
|
+
'complexity': {
|
482
|
+
'get': 'O(1) with index, O(nnz) without',
|
483
|
+
'set': 'O(1) append, O(nnz) update',
|
484
|
+
'transpose': 'O(nnz)',
|
485
|
+
'add': 'O(nnz1 + nnz2)',
|
486
|
+
'multiply': 'O(nnz1 * cols2)',
|
487
|
+
'space': 'O(nnz)'
|
488
|
+
},
|
489
|
+
'best_for': 'matrix construction, flexible modification',
|
490
|
+
'convert_to': 'CSR for row operations, CSC for column operations'
|
491
|
+
}
|
492
|
+
|
493
|
+
@property
|
494
|
+
def metrics(self) -> Dict[str, Any]:
|
495
|
+
"""Get performance metrics."""
|
496
|
+
total_elements = self._rows * self._cols
|
497
|
+
memory_bytes = self.nnz * (8 + 8 + 8) # row, col, value
|
498
|
+
|
499
|
+
return {
|
500
|
+
'shape': f"{self._rows}x{self._cols}",
|
501
|
+
'nnz': self.nnz,
|
502
|
+
'density': f"{self.density:.2%}",
|
503
|
+
'total_elements': total_elements,
|
504
|
+
'memory_saved': f"{(total_elements - self.nnz) * 8} bytes",
|
505
|
+
'memory_usage': f"{memory_bytes} bytes (estimated)",
|
506
|
+
'compression_ratio': f"{total_elements / self.nnz if self.nnz > 0 else 0:.1f}x"
|
507
|
+
}
|