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,463 @@
|
|
1
|
+
"""
|
2
|
+
#exonware/xwnode/src/exonware/xwnode/strategies/advisor.py
|
3
|
+
|
4
|
+
Strategy Advisor
|
5
|
+
|
6
|
+
This module provides the StrategyAdvisor class for intelligent strategy selection,
|
7
|
+
performance monitoring, and optimization recommendations in the strategy system.
|
8
|
+
"""
|
9
|
+
|
10
|
+
import time
|
11
|
+
import threading
|
12
|
+
from typing import Dict, List, Optional, Tuple, Any, NamedTuple
|
13
|
+
from dataclasses import dataclass
|
14
|
+
from collections import defaultdict, deque
|
15
|
+
from exonware.xwsystem import get_logger
|
16
|
+
|
17
|
+
logger = get_logger(__name__)
|
18
|
+
|
19
|
+
from ..types import NodeMode, EdgeMode, NodeTrait, EdgeTrait, NODE_STRATEGY_METADATA, EDGE_STRATEGY_METADATA
|
20
|
+
|
21
|
+
|
22
|
+
@dataclass
|
23
|
+
class StrategyRecommendation:
|
24
|
+
"""A strategy recommendation with rationale and estimated gain."""
|
25
|
+
mode: NodeMode | EdgeMode
|
26
|
+
rationale: str
|
27
|
+
estimated_gain_percent: float
|
28
|
+
confidence: float
|
29
|
+
migration_cost: str
|
30
|
+
data_loss_risk: bool
|
31
|
+
|
32
|
+
|
33
|
+
@dataclass
|
34
|
+
class PerformanceMetrics:
|
35
|
+
"""Performance metrics for strategy monitoring."""
|
36
|
+
operation_count: int = 0
|
37
|
+
total_time: float = 0.0
|
38
|
+
avg_time: float = 0.0
|
39
|
+
min_time: float = float('inf')
|
40
|
+
max_time: float = 0.0
|
41
|
+
memory_usage: float = 0.0
|
42
|
+
last_updated: float = 0.0
|
43
|
+
|
44
|
+
def update(self, operation_time: float, memory_usage: float = 0.0):
|
45
|
+
"""Update metrics with new operation data."""
|
46
|
+
self.operation_count += 1
|
47
|
+
self.total_time += operation_time
|
48
|
+
self.avg_time = self.total_time / self.operation_count
|
49
|
+
self.min_time = min(self.min_time, operation_time)
|
50
|
+
self.max_time = max(self.max_time, operation_time)
|
51
|
+
self.memory_usage = memory_usage
|
52
|
+
self.last_updated = time.time()
|
53
|
+
|
54
|
+
|
55
|
+
class StrategyAdvisor:
|
56
|
+
"""
|
57
|
+
Intelligent advisor for strategy selection and optimization.
|
58
|
+
|
59
|
+
This class provides:
|
60
|
+
- Performance monitoring and metrics collection
|
61
|
+
- Intelligent strategy recommendations
|
62
|
+
- Migration cost analysis
|
63
|
+
- Heuristic-based mode selection
|
64
|
+
"""
|
65
|
+
|
66
|
+
def __init__(self, history_size: int = 1000):
|
67
|
+
"""Initialize the strategy advisor."""
|
68
|
+
self.history_size = history_size
|
69
|
+
self._node_metrics: Dict[str, PerformanceMetrics] = defaultdict(PerformanceMetrics)
|
70
|
+
self._edge_metrics: Dict[str, PerformanceMetrics] = defaultdict(PerformanceMetrics)
|
71
|
+
self._operation_history: deque = deque(maxlen=history_size)
|
72
|
+
self._lock = threading.RLock()
|
73
|
+
|
74
|
+
# Heuristic thresholds
|
75
|
+
self._thresholds = {
|
76
|
+
'small_dataset': 100,
|
77
|
+
'medium_dataset': 10000,
|
78
|
+
'large_dataset': 1000000,
|
79
|
+
'sparse_graph': 0.02,
|
80
|
+
'dense_graph': 0.15,
|
81
|
+
'high_churn': 0.1,
|
82
|
+
'write_heavy': 0.7,
|
83
|
+
'lookup_heavy': 0.7,
|
84
|
+
'ordered_heavy': 0.2,
|
85
|
+
'prefix_heavy': 0.1,
|
86
|
+
'priority_heavy': 0.2,
|
87
|
+
'spatial_heavy': 0.1,
|
88
|
+
'temporal_heavy': 0.1,
|
89
|
+
}
|
90
|
+
|
91
|
+
def record_operation(self, strategy_id: str, operation: str,
|
92
|
+
duration: float, memory_usage: float = 0.0,
|
93
|
+
is_node: bool = True) -> None:
|
94
|
+
"""
|
95
|
+
Record an operation for performance monitoring.
|
96
|
+
|
97
|
+
Args:
|
98
|
+
strategy_id: Unique identifier for the strategy
|
99
|
+
operation: Operation name (e.g., 'get', 'set', 'add_edge')
|
100
|
+
duration: Operation duration in seconds
|
101
|
+
memory_usage: Memory usage in bytes
|
102
|
+
is_node: Whether this is a node or edge operation
|
103
|
+
"""
|
104
|
+
with self._lock:
|
105
|
+
metrics = self._node_metrics if is_node else self._edge_metrics
|
106
|
+
key = f"{strategy_id}:{operation}"
|
107
|
+
metrics[key].update(duration, memory_usage)
|
108
|
+
|
109
|
+
# Record in history
|
110
|
+
self._operation_history.append({
|
111
|
+
'timestamp': time.time(),
|
112
|
+
'strategy_id': strategy_id,
|
113
|
+
'operation': operation,
|
114
|
+
'duration': duration,
|
115
|
+
'memory_usage': memory_usage,
|
116
|
+
'is_node': is_node
|
117
|
+
})
|
118
|
+
|
119
|
+
def get_performance_profile(self, strategy_id: str, is_node: bool = True) -> Dict[str, Any]:
|
120
|
+
"""
|
121
|
+
Get performance profile for a strategy.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
strategy_id: Strategy identifier
|
125
|
+
is_node: Whether this is a node or edge strategy
|
126
|
+
|
127
|
+
Returns:
|
128
|
+
Performance profile dictionary
|
129
|
+
"""
|
130
|
+
with self._lock:
|
131
|
+
metrics = self._node_metrics if is_node else self._edge_metrics
|
132
|
+
|
133
|
+
profile = {
|
134
|
+
'strategy_id': strategy_id,
|
135
|
+
'is_node': is_node,
|
136
|
+
'operations': {},
|
137
|
+
'overall': {
|
138
|
+
'total_operations': 0,
|
139
|
+
'avg_duration': 0.0,
|
140
|
+
'total_duration': 0.0,
|
141
|
+
'memory_usage': 0.0
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
total_ops = 0
|
146
|
+
total_duration = 0.0
|
147
|
+
total_memory = 0.0
|
148
|
+
|
149
|
+
for key, metric in metrics.items():
|
150
|
+
if key.startswith(f"{strategy_id}:"):
|
151
|
+
operation = key.split(":", 1)[1]
|
152
|
+
profile['operations'][operation] = {
|
153
|
+
'count': metric.operation_count,
|
154
|
+
'avg_time': metric.avg_time,
|
155
|
+
'min_time': metric.min_time,
|
156
|
+
'max_time': metric.max_time,
|
157
|
+
'total_time': metric.total_time,
|
158
|
+
'memory_usage': metric.memory_usage,
|
159
|
+
'last_updated': metric.last_updated
|
160
|
+
}
|
161
|
+
|
162
|
+
total_ops += metric.operation_count
|
163
|
+
total_duration += metric.total_time
|
164
|
+
total_memory = max(total_memory, metric.memory_usage)
|
165
|
+
|
166
|
+
if total_ops > 0:
|
167
|
+
profile['overall']['total_operations'] = total_ops
|
168
|
+
profile['overall']['total_duration'] = total_duration
|
169
|
+
profile['overall']['avg_duration'] = total_duration / total_ops
|
170
|
+
profile['overall']['memory_usage'] = total_memory
|
171
|
+
|
172
|
+
return profile
|
173
|
+
|
174
|
+
def suggest_node_strategy(self, data_profile: Dict[str, Any],
|
175
|
+
current_mode: Optional[NodeMode] = None) -> StrategyRecommendation:
|
176
|
+
"""
|
177
|
+
Suggest optimal node strategy based on data profile.
|
178
|
+
|
179
|
+
Args:
|
180
|
+
data_profile: Profile of the data and usage patterns
|
181
|
+
current_mode: Current strategy mode (if any)
|
182
|
+
|
183
|
+
Returns:
|
184
|
+
Strategy recommendation
|
185
|
+
"""
|
186
|
+
size = data_profile.get('size', 0)
|
187
|
+
operations = data_profile.get('operations', {})
|
188
|
+
total_ops = sum(operations.values()) if operations else 0
|
189
|
+
|
190
|
+
# Calculate operation ratios
|
191
|
+
if total_ops > 0:
|
192
|
+
lookup_ratio = operations.get('get', 0) / total_ops
|
193
|
+
insert_ratio = operations.get('set', 0) / total_ops
|
194
|
+
ordered_ratio = operations.get('ordered_ops', 0) / total_ops
|
195
|
+
range_ratio = operations.get('range_ops', 0) / total_ops
|
196
|
+
prefix_ratio = operations.get('prefix_ops', 0) / total_ops
|
197
|
+
priority_ratio = operations.get('priority_ops', 0) / total_ops
|
198
|
+
else:
|
199
|
+
lookup_ratio = insert_ratio = ordered_ratio = range_ratio = prefix_ratio = priority_ratio = 0.0
|
200
|
+
|
201
|
+
# Decision logic based on heuristics
|
202
|
+
if data_profile.get('persistent', False):
|
203
|
+
if range_ratio > self._thresholds['ordered_heavy']:
|
204
|
+
mode = NodeMode.B_PLUS_TREE
|
205
|
+
rationale = f"Persistent data with {range_ratio:.1%} range operations"
|
206
|
+
gain = 50.0
|
207
|
+
else:
|
208
|
+
mode = NodeMode.B_TREE
|
209
|
+
rationale = f"Persistent data storage"
|
210
|
+
gain = 30.0
|
211
|
+
|
212
|
+
elif data_profile.get('write_heavy', False) or insert_ratio > self._thresholds['write_heavy']:
|
213
|
+
mode = NodeMode.LSM_TREE
|
214
|
+
rationale = f"Write-heavy workload ({insert_ratio:.1%} inserts)"
|
215
|
+
gain = 80.0
|
216
|
+
|
217
|
+
elif range_ratio > self._thresholds['ordered_heavy']:
|
218
|
+
if data_profile.get('updates', False):
|
219
|
+
mode = NodeMode.SEGMENT_TREE
|
220
|
+
rationale = f"Range operations with updates ({range_ratio:.1%})"
|
221
|
+
gain = 60.0
|
222
|
+
else:
|
223
|
+
mode = NodeMode.FENWICK_TREE
|
224
|
+
rationale = f"Range queries ({range_ratio:.1%})"
|
225
|
+
gain = 40.0
|
226
|
+
|
227
|
+
elif prefix_ratio > self._thresholds['prefix_heavy']:
|
228
|
+
if data_profile.get('binary', False):
|
229
|
+
mode = NodeMode.PATRICIA
|
230
|
+
rationale = f"Binary prefix operations ({prefix_ratio:.1%})"
|
231
|
+
gain = 50.0
|
232
|
+
else:
|
233
|
+
mode = NodeMode.RADIX_TRIE
|
234
|
+
rationale = f"String prefix operations ({prefix_ratio:.1%})"
|
235
|
+
gain = 40.0
|
236
|
+
|
237
|
+
elif priority_ratio > self._thresholds['priority_heavy']:
|
238
|
+
mode = NodeMode.HEAP
|
239
|
+
rationale = f"Priority operations ({priority_ratio:.1%})"
|
240
|
+
gain = 30.0
|
241
|
+
|
242
|
+
elif lookup_ratio > self._thresholds['lookup_heavy']:
|
243
|
+
if size > self._thresholds['large_dataset']:
|
244
|
+
mode = NodeMode.HASH_MAP
|
245
|
+
rationale = f"High lookup ratio ({lookup_ratio:.1%}) with large dataset"
|
246
|
+
gain = 70.0
|
247
|
+
else:
|
248
|
+
mode = NodeMode.ARRAY_LIST
|
249
|
+
rationale = f"High lookup ratio ({lookup_ratio:.1%}) with small dataset"
|
250
|
+
gain = 20.0
|
251
|
+
|
252
|
+
elif data_profile.get('connectivity', False):
|
253
|
+
mode = NodeMode.UNION_FIND
|
254
|
+
rationale = "Connectivity/disjoint set operations"
|
255
|
+
gain = 60.0
|
256
|
+
|
257
|
+
elif data_profile.get('streaming', False):
|
258
|
+
if data_profile.get('frequency', False):
|
259
|
+
mode = NodeMode.COUNT_MIN_SKETCH
|
260
|
+
rationale = "Streaming frequency estimation"
|
261
|
+
gain = 90.0
|
262
|
+
else:
|
263
|
+
mode = NodeMode.HYPERLOGLOG
|
264
|
+
rationale = "Streaming cardinality estimation"
|
265
|
+
gain = 85.0
|
266
|
+
|
267
|
+
elif size < self._thresholds['small_dataset']:
|
268
|
+
mode = NodeMode.ARRAY_LIST
|
269
|
+
rationale = f"Small dataset ({size} items)"
|
270
|
+
gain = 15.0
|
271
|
+
|
272
|
+
elif ordered_ratio > self._thresholds['ordered_heavy']:
|
273
|
+
mode = NodeMode.ORDERED_MAP_BALANCED
|
274
|
+
rationale = f"Ordered operations ({ordered_ratio:.1%})"
|
275
|
+
gain = 25.0
|
276
|
+
|
277
|
+
else:
|
278
|
+
mode = NodeMode.HASH_MAP
|
279
|
+
rationale = "General purpose hash map"
|
280
|
+
gain = 20.0
|
281
|
+
|
282
|
+
# Calculate migration cost
|
283
|
+
migration_cost = self._calculate_migration_cost(current_mode, mode, is_node=True)
|
284
|
+
data_loss_risk = self._assess_data_loss_risk(current_mode, mode, is_node=True)
|
285
|
+
|
286
|
+
return StrategyRecommendation(
|
287
|
+
mode=mode,
|
288
|
+
rationale=rationale,
|
289
|
+
estimated_gain_percent=gain,
|
290
|
+
confidence=0.8,
|
291
|
+
migration_cost=migration_cost,
|
292
|
+
data_loss_risk=data_loss_risk
|
293
|
+
)
|
294
|
+
|
295
|
+
def suggest_edge_strategy(self, graph_profile: Dict[str, Any],
|
296
|
+
current_mode: Optional[EdgeMode] = None) -> StrategyRecommendation:
|
297
|
+
"""
|
298
|
+
Suggest optimal edge strategy based on graph profile.
|
299
|
+
|
300
|
+
Args:
|
301
|
+
graph_profile: Profile of the graph and usage patterns
|
302
|
+
current_mode: Current strategy mode (if any)
|
303
|
+
|
304
|
+
Returns:
|
305
|
+
Strategy recommendation
|
306
|
+
"""
|
307
|
+
n = graph_profile.get('vertices', 0)
|
308
|
+
m = graph_profile.get('edges', 0)
|
309
|
+
density = m / (n * n) if n > 0 else 0
|
310
|
+
|
311
|
+
# Graph characteristics
|
312
|
+
is_spatial = graph_profile.get('spatial', False)
|
313
|
+
is_temporal = graph_profile.get('temporal', False)
|
314
|
+
is_hyper = graph_profile.get('hyper', False)
|
315
|
+
is_undirected = graph_profile.get('undirected', False)
|
316
|
+
high_churn = graph_profile.get('high_churn', False)
|
317
|
+
|
318
|
+
# Decision logic
|
319
|
+
if is_hyper:
|
320
|
+
mode = EdgeMode.HYPEREDGE_SET
|
321
|
+
rationale = "Hypergraph with multi-vertex edges"
|
322
|
+
gain = 40.0
|
323
|
+
|
324
|
+
elif is_temporal:
|
325
|
+
mode = EdgeMode.TEMPORAL_EDGESET
|
326
|
+
rationale = "Time-aware edge storage"
|
327
|
+
gain = 50.0
|
328
|
+
|
329
|
+
elif is_spatial:
|
330
|
+
dims = graph_profile.get('dimensions', 2)
|
331
|
+
if dims == 2:
|
332
|
+
mode = EdgeMode.QUADTREE
|
333
|
+
rationale = "2D spatial partitioning"
|
334
|
+
gain = 60.0
|
335
|
+
elif dims == 3:
|
336
|
+
mode = EdgeMode.OCTREE
|
337
|
+
rationale = "3D spatial partitioning"
|
338
|
+
gain = 60.0
|
339
|
+
else:
|
340
|
+
mode = EdgeMode.R_TREE
|
341
|
+
rationale = "Spatial indexing for arbitrary dimensions"
|
342
|
+
gain = 50.0
|
343
|
+
|
344
|
+
elif is_undirected:
|
345
|
+
mode = EdgeMode.BIDIR_WRAPPER
|
346
|
+
rationale = "Undirected graph via bidirectional wrapper"
|
347
|
+
gain = 30.0
|
348
|
+
|
349
|
+
elif high_churn:
|
350
|
+
mode = EdgeMode.DYNAMIC_ADJ_LIST
|
351
|
+
rationale = "High churn graph with frequent updates"
|
352
|
+
gain = 45.0
|
353
|
+
|
354
|
+
elif density <= self._thresholds['sparse_graph']:
|
355
|
+
mode = EdgeMode.CSR
|
356
|
+
rationale = f"Sparse graph (density: {density:.3f})"
|
357
|
+
gain = 35.0
|
358
|
+
|
359
|
+
elif density <= self._thresholds['dense_graph']:
|
360
|
+
mode = EdgeMode.ADJ_LIST
|
361
|
+
rationale = f"Medium density graph (density: {density:.3f})"
|
362
|
+
gain = 25.0
|
363
|
+
|
364
|
+
else:
|
365
|
+
mode = EdgeMode.BLOCK_ADJ_MATRIX
|
366
|
+
rationale = f"Dense graph (density: {density:.3f}) with cache optimization"
|
367
|
+
gain = 40.0
|
368
|
+
|
369
|
+
# Calculate migration cost
|
370
|
+
migration_cost = self._calculate_migration_cost(current_mode, mode, is_node=False)
|
371
|
+
data_loss_risk = self._assess_data_loss_risk(current_mode, mode, is_node=False)
|
372
|
+
|
373
|
+
return StrategyRecommendation(
|
374
|
+
mode=mode,
|
375
|
+
rationale=rationale,
|
376
|
+
estimated_gain_percent=gain,
|
377
|
+
confidence=0.8,
|
378
|
+
migration_cost=migration_cost,
|
379
|
+
data_loss_risk=data_loss_risk
|
380
|
+
)
|
381
|
+
|
382
|
+
def _calculate_migration_cost(self, from_mode: Optional[NodeMode | EdgeMode],
|
383
|
+
to_mode: NodeMode | EdgeMode, is_node: bool) -> str:
|
384
|
+
"""Calculate the cost of migrating between strategies."""
|
385
|
+
if from_mode is None:
|
386
|
+
return "low"
|
387
|
+
|
388
|
+
# Define migration cost matrix
|
389
|
+
if is_node:
|
390
|
+
cost_matrix = {
|
391
|
+
NodeMode.ARRAY_LIST: {NodeMode.ORDERED_MAP: "medium", NodeMode.HASH_MAP: "medium"},
|
392
|
+
NodeMode.ORDERED_MAP: {NodeMode.ORDERED_MAP_BALANCED: "low", NodeMode.B_TREE: "medium"},
|
393
|
+
NodeMode.TRIE: {NodeMode.RADIX_TRIE: "low", NodeMode.PATRICIA: "low"},
|
394
|
+
NodeMode.BITMAP: {NodeMode.BITSET_DYNAMIC: "low", NodeMode.ROARING_BITMAP: "medium"},
|
395
|
+
}
|
396
|
+
else:
|
397
|
+
cost_matrix = {
|
398
|
+
EdgeMode.ADJ_LIST: {EdgeMode.DYNAMIC_ADJ_LIST: "low", EdgeMode.CSR: "medium"},
|
399
|
+
EdgeMode.ADJ_MATRIX: {EdgeMode.BLOCK_ADJ_MATRIX: "low"},
|
400
|
+
EdgeMode.QUADTREE: {EdgeMode.OCTREE: "low", EdgeMode.R_TREE: "medium"},
|
401
|
+
}
|
402
|
+
|
403
|
+
# Check if migration is in cost matrix
|
404
|
+
if from_mode in cost_matrix and to_mode in cost_matrix[from_mode]:
|
405
|
+
return cost_matrix[from_mode][to_mode]
|
406
|
+
|
407
|
+
# Default cost based on mode types
|
408
|
+
if from_mode == to_mode:
|
409
|
+
return "none"
|
410
|
+
elif is_node and (from_mode in [NodeMode.ARRAY_LIST, NodeMode.LINKED_LIST] and
|
411
|
+
to_mode in [NodeMode.HASH_MAP, NodeMode.ORDERED_MAP]):
|
412
|
+
return "medium"
|
413
|
+
elif not is_node and (from_mode in [EdgeMode.ADJ_LIST, EdgeMode.ADJ_MATRIX] and
|
414
|
+
to_mode in [EdgeMode.CSR, EdgeMode.CSC]):
|
415
|
+
return "medium"
|
416
|
+
else:
|
417
|
+
return "high"
|
418
|
+
|
419
|
+
def _assess_data_loss_risk(self, from_mode: Optional[NodeMode | EdgeMode],
|
420
|
+
to_mode: NodeMode | EdgeMode, is_node: bool) -> bool:
|
421
|
+
"""Assess risk of data loss during migration."""
|
422
|
+
if from_mode is None:
|
423
|
+
return False
|
424
|
+
|
425
|
+
# Define lossy migrations
|
426
|
+
if is_node:
|
427
|
+
lossy_migrations = [
|
428
|
+
(NodeMode.ORDERED_MAP, NodeMode.HASH_MAP), # Order loss
|
429
|
+
(NodeMode.TRIE, NodeMode.HASH_MAP), # Prefix structure loss
|
430
|
+
(NodeMode.HEAP, NodeMode.HASH_MAP), # Priority order loss
|
431
|
+
(NodeMode.BLOOM_FILTER, NodeMode.HASH_MAP), # Probabilistic nature loss
|
432
|
+
]
|
433
|
+
else:
|
434
|
+
lossy_migrations = [
|
435
|
+
(EdgeMode.HYPEREDGE_SET, EdgeMode.ADJ_LIST), # Hyperedge structure loss
|
436
|
+
(EdgeMode.TEMPORAL_EDGESET, EdgeMode.ADJ_LIST), # Temporal info loss
|
437
|
+
(EdgeMode.BIDIR_WRAPPER, EdgeMode.ADJ_LIST), # Bidirectional info loss
|
438
|
+
]
|
439
|
+
|
440
|
+
return (from_mode, to_mode) in lossy_migrations
|
441
|
+
|
442
|
+
def get_advisor_stats(self) -> Dict[str, Any]:
|
443
|
+
"""Get advisor statistics."""
|
444
|
+
with self._lock:
|
445
|
+
return {
|
446
|
+
'history_size': len(self._operation_history),
|
447
|
+
'node_metrics_count': len(self._node_metrics),
|
448
|
+
'edge_metrics_count': len(self._edge_metrics),
|
449
|
+
'thresholds': self._thresholds.copy(),
|
450
|
+
'last_operation': self._operation_history[-1] if self._operation_history else None
|
451
|
+
}
|
452
|
+
|
453
|
+
|
454
|
+
# Global advisor instance
|
455
|
+
_advisor = None
|
456
|
+
|
457
|
+
|
458
|
+
def get_advisor() -> StrategyAdvisor:
|
459
|
+
"""Get the global strategy advisor instance."""
|
460
|
+
global _advisor
|
461
|
+
if _advisor is None:
|
462
|
+
_advisor = StrategyAdvisor()
|
463
|
+
return _advisor
|
@@ -0,0 +1,32 @@
|
|
1
|
+
"""
|
2
|
+
Edge Strategies Package
|
3
|
+
|
4
|
+
This package contains all edge strategy implementations organized by type:
|
5
|
+
- Linear edges (sequential connections)
|
6
|
+
- Tree edges (hierarchical connections)
|
7
|
+
- Graph edges (network connections)
|
8
|
+
|
9
|
+
Company: eXonware.com
|
10
|
+
Author: Eng. Muhammad AlShehri
|
11
|
+
Email: connect@exonware.com
|
12
|
+
Version: 0.0.1.12
|
13
|
+
Generation Date: January 2, 2025
|
14
|
+
"""
|
15
|
+
|
16
|
+
from .base import AEdgeStrategy, ALinearEdgeStrategy, ATreeEdgeStrategy, AGraphEdgeStrategy
|
17
|
+
|
18
|
+
# Graph edge strategies
|
19
|
+
from .adj_list import AdjListStrategy
|
20
|
+
from .adj_matrix import AdjMatrixStrategy
|
21
|
+
|
22
|
+
__all__ = [
|
23
|
+
# Base classes
|
24
|
+
'AEdgeStrategy',
|
25
|
+
'ALinearEdgeStrategy',
|
26
|
+
'ATreeEdgeStrategy',
|
27
|
+
'AGraphEdgeStrategy',
|
28
|
+
|
29
|
+
# Graph edge strategies
|
30
|
+
'AdjListStrategy',
|
31
|
+
'AdjMatrixStrategy'
|
32
|
+
]
|